Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin customization #129

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
926f253
Refactored associations
jupadin Oct 20, 2023
0fffb3e
Refactored districts
jupadin Oct 20, 2023
e68db35
Refactored games
jupadin Oct 20, 2023
2a4a4ac
Refactored games.
jupadin Oct 23, 2023
8f4f7e0
Refactored teams.
jupadin Oct 23, 2023
ad7c4fc
Changed verbose name of app 'teams'.
jupadin Oct 23, 2023
84f0503
Refactored sports halls.
jupadin Oct 23, 2023
80403ad
Refactored leagues.
jupadin Oct 23, 2023
8ee456e
Rerefactored districts.
jupadin Oct 23, 2023
5adc2ab
Refactored base.
jupadin Oct 23, 2023
77caac5
Run pipenv run mypy src.
jupadin Oct 23, 2023
b837197
Run pipenv run flake8.
jupadin Oct 23, 2023
9505c79
Added missing migration files.
jupadin Oct 23, 2023
c9582d1
Merge branch 'master' into admin-customization
djbrown Oct 30, 2023
62be689
Handled circumstance that source_url is not necessary.
jupadin Oct 31, 2023
15edf10
Updated gitignore for mails dir.
jupadin Oct 31, 2023
aac720e
Merge branch 'master' of https://github.com/djbrown/hbscorez into adm…
jupadin Oct 31, 2023
81f8821
Added missing GameOutcome model.
jupadin Oct 31, 2023
b213b32
Fixed pylint warning.
jupadin Oct 31, 2023
90afef0
Merge branch 'master' into admin-customization
jupadin Nov 2, 2023
1418e89
Merge branch 'master' of https://github.com/djbrown/hbscorez into adm…
jupadin Nov 2, 2023
75296b2
FR: Used API-URL to retrieve association abbreviations.
jupadin Nov 2, 2023
5ed3ba6
Merge branch 'admin-customization' of https://github.com/jupadin/hbsc…
jupadin Nov 2, 2023
82853a3
Refactored association abbreviation retrieval.
jupadin Nov 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ docker-stack*.yml
geckodriver.log
hbscorez.log
src/hbscorez/settings_docker_stack*.py
src/mails/
3 changes: 2 additions & 1 deletion src/associations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

@admin.register(Association)
class AssociationAdmin(admin.ModelAdmin):
search_fields = ASSOCIATION_SEARCH_FIELDS
list_display = ['bhv_id', 'abbreviation', 'name']
search_fields = ['name', 'abbreviation', 'bhv_id']
1 change: 1 addition & 0 deletions src/associations/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

class AssociationsConfig(AppConfig):
name = 'associations'
verbose_name = 'Verbände'
16 changes: 11 additions & 5 deletions src/associations/management/commands/import_associations.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,23 @@ def scrape_association(url: str, options):
html = http.get_text(url)
dom = parsing.html_dom(html)

abbreviation = parsing.parse_association_abbreviation(url)
name = parsing.parse_association_name(dom)
bhv_id = parsing.parse_association_bhv_id(dom)
name = parsing.parse_association_name(dom)

association_api_url = f'{settings.API_URL_TEMPLATE}cmd=po&og={bhv_id}'
abbreviation = parsing.parse_association_abbreviation(association_api_url)

if options['associations'] and bhv_id not in options['associations']:
LOGGER.debug('SKIPPING Association (options): %s %s', bhv_id, name)
return

association = Association.objects.filter(bhv_id=bhv_id).first()
if association is None:
association = Association.objects.create(name=name, abbreviation=abbreviation, bhv_id=bhv_id, source_url=url)
try:
association = Association.objects.get(bhv_id=bhv_id)
except Association.MultipleObjectsReturned:
LOGGER.debug('Database contains multiple association objects with same bhv_id.')
return
except Association.DoesNotExist:
association = Association.objects.create(name=name, abbreviation=abbreviation, bhv_id=bhv_id)
LOGGER.info('CREATED Association: %s', association)
return

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.4 on 2023-10-20 10:45

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("associations", "0002_association_source_url"),
]

operations = [
migrations.AlterModelOptions(
name="association",
options={"verbose_name": "Verband", "verbose_name_plural": "Verbände"},
),
migrations.RemoveField(
model_name="association",
name="source_url",
),
Comment on lines +16 to +19
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attribut source_url wird nach meinem Verständnis noch benötigt

migrations.AlterField(
model_name="association",
name="abbreviation",
field=models.CharField(max_length=255, verbose_name="Abkürzung"),
),
migrations.AlterField(
model_name="association",
name="bhv_id",
field=models.IntegerField(unique=True, verbose_name="ID"),
),
migrations.AlterField(
model_name="association",
name="name",
field=models.CharField(max_length=255, unique=True, verbose_name="Name"),
),
]
11 changes: 7 additions & 4 deletions src/associations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@


class Association(models.Model):
name = models.TextField(unique=True)
abbreviation = models.TextField()
bhv_id = models.IntegerField(unique=True)
source_url = models.TextField()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attribut source_url wird nach meinem Verständnis noch benötigt

bhv_id = models.IntegerField('ID', unique=True)
name = models.CharField('Name', max_length=255, unique=True)
abbreviation = models.CharField('Abkürzung', max_length=255)

class Meta:
verbose_name = 'Verband'
verbose_name_plural = 'Verbände'

def __str__(self):
return f'{self.bhv_id} {self.abbreviation}'
Expand Down
6 changes: 5 additions & 1 deletion src/base/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@

from base.models import Env, GlobalMessage

admin.site.register(Env)
admin.site.register(GlobalMessage)


@admin.register(Env)
class EnvAdmin(admin.ModelAdmin):
list_display = ('name', 'value')
8 changes: 8 additions & 0 deletions src/base/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ def get_text(url) -> str:
response.encoding = 'utf-8'
return response.text

def get_json(url) -> list[dict]:
response = _http.get(url, timeout=5)
response.encoding = 'utf-8'
try:
return response.json()
except requests.exceptions.JSONDecodeError:
return dict()


class EmptyResponseError(RequestException):
"""The response payload was empty."""
Expand Down
24 changes: 24 additions & 0 deletions src/base/migrations/0003_alter_env_options_alter_env_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 4.2.4 on 2023-10-23 20:08

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("base", "0002_globalmessage"),
]

operations = [
migrations.AlterModelOptions(
name="env",
options={
"verbose_name": "Umgebungsvariable",
"verbose_name_plural": "Umgebungsvariablen",
},
),
migrations.AlterField(
model_name="env",
name="name",
field=models.CharField(max_length=255, unique=True),
),
]
6 changes: 5 additions & 1 deletion src/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ class Value(Enum):


class Env(models.Model):
name = models.TextField(unique=True)
name = models.CharField(unique=True, max_length=255)
value = models.TextField()

class Meta:
verbose_name = 'Umgebungsvariable'
verbose_name_plural = 'Umgebungsvariablen'

def set_value(self, value: Value):
self.value = value.value
self.save()
Expand Down
5 changes: 3 additions & 2 deletions src/base/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from teams.models import Team

from base import http

def html_dom(html_text: str) -> _Element:
return html.fromstring(html_text)
Expand All @@ -26,8 +27,8 @@ def parse_association_urls(dom: _Element) -> list[str]:
return cast(list[str], dom.xpath('//ul[@id="main-navi"]/li[contains(@class, "active")]//li/a/@href'))


def parse_association_abbreviation(association_url: str) -> str:
return association_url.rsplit('/', 1)[1].upper()
def parse_association_abbreviation(association_url: str) -> str | None:
return http.get_json(association_url)[0].get('head', {}).get('sname', None)


def parse_association_name(dom: _Element) -> str:
Expand Down
20 changes: 20 additions & 0 deletions src/districts/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from django.forms import CheckboxSelectMultiple
from django.db import models

from associations.admin import ASSOCIATION_SEARCH_FIELDS
from districts.models import District
Expand All @@ -9,4 +13,20 @@

@admin.register(District)
class DistrictAdmin(admin.ModelAdmin):
list_display = ('bhv_id', 'name', 'get_associations')
list_display_links = ('bhv_id', 'name')
search_fields = DISTRICT_SEARCH_FIELDS

formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple}
}

@admin.display(description='Verband')
def get_associations(self, obj: District) -> str:
association_links = [
format_html('<a href="{}">{}</a>',
reverse('admin:associations_association_change', args=[a.pk]),
a.name
) for a in obj.associations.all()
]
return ', '.join(association_links)
1 change: 1 addition & 0 deletions src/districts/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

class DistrictsConfig(AppConfig):
name = 'districts'
verbose_name = 'Bezirke'
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.4 on 2023-10-23 20:08

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("associations", "0003_alter_association_options_and_more"),
("districts", "0001_initial"),
]

operations = [
migrations.AlterModelOptions(
name="district",
options={
"ordering": ("bhv_id",),
"verbose_name": "Bezirk",
"verbose_name_plural": "Bezirke",
},
),
migrations.AlterField(
model_name="district",
name="associations",
field=models.ManyToManyField(
to="associations.association", verbose_name="Verbände"
),
),
migrations.AlterField(
model_name="district",
name="bhv_id",
field=models.IntegerField(unique=True, verbose_name="ID"),
),
migrations.AlterField(
model_name="district",
name="name",
field=models.CharField(max_length=255, unique=True, verbose_name="Name"),
),
]
11 changes: 8 additions & 3 deletions src/districts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@


class District(models.Model):
name = models.TextField(unique=True)
associations = models.ManyToManyField(Association)
bhv_id = models.IntegerField(unique=True)
bhv_id = models.IntegerField(verbose_name='ID', unique=True)
name = models.CharField('Name', max_length=255, unique=True)
associations = models.ManyToManyField(Association, verbose_name='Verbände')

class Meta:
verbose_name = 'Bezirk'
verbose_name_plural = 'Bezirke'
ordering = ('bhv_id',)

def __str__(self):
return f'{self.bhv_id} {self.name}'
Expand Down
49 changes: 48 additions & 1 deletion src/games/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse

from games.models import Game
from games.models import Game, GameOutcome
from leagues.admin import LEAGUE_SEARCH_FIELDS
from teams.admin import TEAM_SEARCH_FIELDS

Expand All @@ -12,4 +14,49 @@

@admin.register(Game)
class GameAdmin(admin.ModelAdmin):
list_display = ('number', 'league_year', 'show_opening_whistle', 'league_name',
'home_team_name', 'guest_team_name', 'report', 'home_goals', 'guest_goals', 'spectators')
list_filter = ('league__season',)
search_fields = GAME_SEARCH_FIELDS

@admin.display(description='Heimmannschaft')
def home_team_name(self, obj: Game) -> str:
url = reverse('admin:teams_team_change', args=(obj.home_team.pk,))
name = obj.home_team.name
if obj.outcome() == GameOutcome.HOME_WIN:
return format_html('<a style="color:#28a745;" href="{}">{}</a>', url, name)
if obj.outcome() == GameOutcome.TIE:
return format_html('<a style="color:#ffc107;" href="{}">{}</a>', url, name)
return format_html('<a style="color:#dc3545;" href="{}">{}</a>', url, name)

@admin.display(description='Gastmannschaft')
def guest_team_name(self, obj: Game) -> str:
url = reverse('admin:teams_team_change', args=(obj.guest_team.pk,))
name = obj.guest_team.name
if obj.outcome() == GameOutcome.AWAY_WIN:
return format_html('<a style="color:#28a745;" href="{}">{}</a>', url, name)
if obj.outcome() == GameOutcome.TIE:
return format_html('<a style="color:#ffc107;" href="{}">{}</a>', url, name)
return format_html('<a style="color:#dc3545;" href="{}">{}</a>', url, name)

@admin.display(description='Saison', ordering='league__season__start_year')
def league_year(self, obj: Game) -> str:
return str(obj.league.season)

@admin.display(description='Liga')
def league_name(self, obj: Game) -> str:
return str(obj.league.name)

@admin.display(description='Anpfiff', ordering='opening_whistle')
def show_opening_whistle(self, obj: Game) -> str | None:
if not obj.opening_whistle:
return None
return obj.opening_whistle.strftime('%d.%m.%Y %H:%M')

@admin.display(description='Bericht')
def report(self, obj: Game) -> str | None:
report_nr = obj.report_number
if report_nr is None:
return None
url = obj.report_source_url()
return format_html('<a href="{}">{}</a>', url, report_nr)
1 change: 1 addition & 0 deletions src/games/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

class GamesConfig(AppConfig):
name = 'games'
verbose_name = 'Spiele'
Loading
Loading