diff --git a/.gitignore b/.gitignore
index 90a57429..a1f5f510 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ docker-stack*.yml
geckodriver.log
hbscorez.log
src/hbscorez/settings_docker_stack*.py
+src/mails/
\ No newline at end of file
diff --git a/src/associations/admin.py b/src/associations/admin.py
index 05985675..e4d15d07 100644
--- a/src/associations/admin.py
+++ b/src/associations/admin.py
@@ -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']
diff --git a/src/associations/apps.py b/src/associations/apps.py
index 38418ffe..1d45decb 100644
--- a/src/associations/apps.py
+++ b/src/associations/apps.py
@@ -3,3 +3,4 @@
class AssociationsConfig(AppConfig):
name = 'associations'
+ verbose_name = 'Verbände'
diff --git a/src/associations/management/commands/import_associations.py b/src/associations/management/commands/import_associations.py
index bf612e4b..a636f628 100644
--- a/src/associations/management/commands/import_associations.py
+++ b/src/associations/management/commands/import_associations.py
@@ -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
diff --git a/src/associations/migrations/0003_alter_association_options_and_more.py b/src/associations/migrations/0003_alter_association_options_and_more.py
new file mode 100644
index 00000000..e9df130f
--- /dev/null
+++ b/src/associations/migrations/0003_alter_association_options_and_more.py
@@ -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",
+ ),
+ 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"),
+ ),
+ ]
diff --git a/src/associations/models.py b/src/associations/models.py
index ad13b712..daeb7db2 100644
--- a/src/associations/models.py
+++ b/src/associations/models.py
@@ -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()
+ 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}'
diff --git a/src/base/admin.py b/src/base/admin.py
index 0d531daa..84dd3a7a 100644
--- a/src/base/admin.py
+++ b/src/base/admin.py
@@ -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')
diff --git a/src/base/http.py b/src/base/http.py
index 736a42ad..416cd2fc 100644
--- a/src/base/http.py
+++ b/src/base/http.py
@@ -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."""
diff --git a/src/base/migrations/0003_alter_env_options_alter_env_name.py b/src/base/migrations/0003_alter_env_options_alter_env_name.py
new file mode 100644
index 00000000..c73ececd
--- /dev/null
+++ b/src/base/migrations/0003_alter_env_options_alter_env_name.py
@@ -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),
+ ),
+ ]
diff --git a/src/base/models.py b/src/base/models.py
index c9130728..662072da 100644
--- a/src/base/models.py
+++ b/src/base/models.py
@@ -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()
diff --git a/src/base/parsing.py b/src/base/parsing.py
index 9317a221..bea4af56 100644
--- a/src/base/parsing.py
+++ b/src/base/parsing.py
@@ -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)
@@ -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:
diff --git a/src/districts/admin.py b/src/districts/admin.py
index ba71e22c..2ce23d6e 100644
--- a/src/districts/admin.py
+++ b/src/districts/admin.py
@@ -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
@@ -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('{}',
+ reverse('admin:associations_association_change', args=[a.pk]),
+ a.name
+ ) for a in obj.associations.all()
+ ]
+ return ', '.join(association_links)
diff --git a/src/districts/apps.py b/src/districts/apps.py
index eb8a8e07..b0881b3d 100644
--- a/src/districts/apps.py
+++ b/src/districts/apps.py
@@ -3,3 +3,4 @@
class DistrictsConfig(AppConfig):
name = 'districts'
+ verbose_name = 'Bezirke'
diff --git a/src/districts/migrations/0002_alter_district_options_alter_district_associations_and_more.py b/src/districts/migrations/0002_alter_district_options_alter_district_associations_and_more.py
new file mode 100644
index 00000000..ca8600a0
--- /dev/null
+++ b/src/districts/migrations/0002_alter_district_options_alter_district_associations_and_more.py
@@ -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"),
+ ),
+ ]
diff --git a/src/districts/models.py b/src/districts/models.py
index 7cd4b398..5132f9d2 100644
--- a/src/districts/models.py
+++ b/src/districts/models.py
@@ -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}'
diff --git a/src/games/admin.py b/src/games/admin.py
index 82b29d95..36be2e7f 100644
--- a/src/games/admin.py
+++ b/src/games/admin.py
@@ -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
@@ -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('{}', url, name)
+ if obj.outcome() == GameOutcome.TIE:
+ return format_html('{}', url, name)
+ return format_html('{}', 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('{}', url, name)
+ if obj.outcome() == GameOutcome.TIE:
+ return format_html('{}', url, name)
+ return format_html('{}', 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('{}', url, report_nr)
diff --git a/src/games/apps.py b/src/games/apps.py
index b74f62c9..77911d2c 100644
--- a/src/games/apps.py
+++ b/src/games/apps.py
@@ -3,3 +3,4 @@
class GamesConfig(AppConfig):
name = 'games'
+ verbose_name = 'Spiele'
diff --git a/src/games/migrations/0003_alter_game_options_alter_game_guest_goals_and_more.py b/src/games/migrations/0003_alter_game_options_alter_game_guest_goals_and_more.py
new file mode 100644
index 00000000..4bf5b645
--- /dev/null
+++ b/src/games/migrations/0003_alter_game_options_alter_game_guest_goals_and_more.py
@@ -0,0 +1,95 @@
+# Generated by Django 4.2.4 on 2023-10-23 20:08
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "sports_halls",
+ "0002_alter_sportshall_options_alter_sportshall_address_and_more",
+ ),
+ ("teams", "0003_alter_team_options_alter_team_bhv_id_and_more"),
+ ("leagues", "0004_alter_league_options_alter_season_options_and_more"),
+ ("games", "0002_game_spectators"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="game",
+ options={"verbose_name": "Spiel", "verbose_name_plural": "Spiele"},
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="guest_goals",
+ field=models.IntegerField(blank=True, null=True, verbose_name="Gast"),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="guest_team",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="guest_team",
+ to="teams.team",
+ verbose_name="Gastmannschaft",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="home_goals",
+ field=models.IntegerField(blank=True, null=True, verbose_name="Heim"),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="home_team",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="home_team",
+ to="teams.team",
+ verbose_name="Heimmannschaft",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="league",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="leagues.league",
+ verbose_name="Liga",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="number",
+ field=models.IntegerField(verbose_name="Spiel-Nr."),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="opening_whistle",
+ field=models.DateTimeField(blank=True, null=True, verbose_name="Anpfiff"),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="report_number",
+ field=models.IntegerField(
+ blank=True, null=True, unique=True, verbose_name="Bericht-Nr."
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="spectators",
+ field=models.IntegerField(blank=True, null=True, verbose_name="Zuschauer"),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="sports_hall",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="sports_halls.sportshall",
+ verbose_name="Sporthalle",
+ ),
+ ),
+ ]
diff --git a/src/games/migrations/0004_alter_game_guest_goals_alter_game_guest_team_and_more.py b/src/games/migrations/0004_alter_game_guest_goals_alter_game_guest_team_and_more.py
new file mode 100644
index 00000000..669322b4
--- /dev/null
+++ b/src/games/migrations/0004_alter_game_guest_goals_alter_game_guest_team_and_more.py
@@ -0,0 +1,79 @@
+# Generated by Django 4.2.4 on 2023-10-31 16:20
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("teams", "0003_alter_team_options_alter_team_bhv_id_and_more"),
+ ("leagues", "0004_alter_league_options_alter_season_options_and_more"),
+ (
+ "sports_halls",
+ "0002_alter_sportshall_options_alter_sportshall_address_and_more",
+ ),
+ ("games", "0003_alter_game_options_alter_game_guest_goals_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="game",
+ name="guest_goals",
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="guest_team",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="guest_team",
+ to="teams.team",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="home_goals",
+ field=models.IntegerField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="home_team",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="home_team",
+ to="teams.team",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="league",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE, to="leagues.league"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="number",
+ field=models.IntegerField(),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="opening_whistle",
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="report_number",
+ field=models.IntegerField(blank=True, null=True, unique=True),
+ ),
+ migrations.AlterField(
+ model_name="game",
+ name="sports_hall",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="sports_halls.sportshall",
+ ),
+ ),
+ ]
diff --git a/src/games/models.py b/src/games/models.py
index f9bbf448..28f47a3c 100644
--- a/src/games/models.py
+++ b/src/games/models.py
@@ -42,13 +42,15 @@ class Game(models.Model):
report_number = models.IntegerField(blank=True, null=True, unique=True)
forfeiting_team = models.ForeignKey(Team, on_delete=models.CASCADE, blank=True, null=True,
related_name='forfeiting_team')
- spectators = models.IntegerField(blank=True, null=True)
+ spectators = models.IntegerField('Zuschauer', blank=True, null=True)
class Meta:
- unique_together = ('number', 'league')
+ verbose_name = 'Spiel'
+ verbose_name_plural = 'Spiele'
+ unique_together = ['number', 'league']
def __str__(self):
- return f'{self.number} {self.league} {self.home_team.short_name} vs. {self.guest_team.short_name}'
+ return f'{self.number} | {self.league}: {self.home_team.short_name} vs. {self.guest_team.short_name}'
@staticmethod
def build_report_source_url(report_number):
diff --git a/src/hbscorez/settings.py b/src/hbscorez/settings.py
index 76e1d4cc..5e902e9f 100644
--- a/src/hbscorez/settings.py
+++ b/src/hbscorez/settings.py
@@ -210,4 +210,6 @@
NEW_ROOT_SOURCE_URL = 'https://www.handball4all.de/'
+API_URL_TEMPLATE = 'https://spo.handball4all.de/service/if_g_json.php?'
+
BROWSER_TIMEOUT = 3
diff --git a/src/leagues/admin.py b/src/leagues/admin.py
index 48a2ac13..9234fdb6 100644
--- a/src/leagues/admin.py
+++ b/src/leagues/admin.py
@@ -12,11 +12,15 @@
@ admin.register(Season)
class SeasonAdmin(admin.ModelAdmin):
+ list_display = ('__str__',)
search_fields = SEASON_SEARCH_FIELDS
@ admin.register(League)
class LeagueAdmin(admin.ModelAdmin):
+ list_display = ('bhv_id', 'name', 'season')
+ list_display_links = ('bhv_id', 'name')
+ list_filter = ('season',)
search_fields = LEAGUE_SEARCH_FIELDS
diff --git a/src/leagues/apps.py b/src/leagues/apps.py
index 4abf4fa9..facaffed 100644
--- a/src/leagues/apps.py
+++ b/src/leagues/apps.py
@@ -3,3 +3,4 @@
class LeaguesConfig(AppConfig):
name = 'leagues'
+ verbose_name = 'Ligen'
diff --git a/src/leagues/migrations/0004_alter_league_options_alter_season_options_and_more.py b/src/leagues/migrations/0004_alter_league_options_alter_season_options_and_more.py
new file mode 100644
index 00000000..8c8f6e54
--- /dev/null
+++ b/src/leagues/migrations/0004_alter_league_options_alter_season_options_and_more.py
@@ -0,0 +1,62 @@
+# Generated by Django 4.2.4 on 2023-10-23 20:08
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "districts",
+ "0002_alter_district_options_alter_district_associations_and_more",
+ ),
+ ("leagues", "0003_leaguename"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="league",
+ options={"verbose_name": "Liga", "verbose_name_plural": "Ligen"},
+ ),
+ migrations.AlterModelOptions(
+ name="season",
+ options={
+ "ordering": ("start_year",),
+ "verbose_name": "Saison",
+ "verbose_name_plural": "Saisons",
+ },
+ ),
+ migrations.AlterField(
+ model_name="league",
+ name="abbreviation",
+ field=models.CharField(max_length=255),
+ ),
+ migrations.AlterField(
+ model_name="league",
+ name="bhv_id",
+ field=models.IntegerField(unique=True, verbose_name="ID"),
+ ),
+ migrations.AlterField(
+ model_name="league",
+ name="district",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="districts.district",
+ verbose_name="Bezirk",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="league",
+ name="name",
+ field=models.CharField(max_length=255, verbose_name="Name"),
+ ),
+ migrations.AlterField(
+ model_name="league",
+ name="season",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="leagues.season",
+ verbose_name="Saison",
+ ),
+ ),
+ ]
diff --git a/src/leagues/models.py b/src/leagues/models.py
index 8fcf42f8..cf2a1b53 100644
--- a/src/leagues/models.py
+++ b/src/leagues/models.py
@@ -11,18 +11,25 @@ class Season(models.Model):
validators.MinValueValidator(1990),
validators.MaxValueValidator(2050)])
+ class Meta:
+ ordering = ('start_year',)
+ verbose_name = 'Saison'
+ verbose_name_plural = 'Saisons'
+
def __str__(self):
return f'{self.start_year}/{self.start_year + 1}'
class League(models.Model):
- name = models.TextField()
- abbreviation = models.TextField()
- district = models.ForeignKey(District, on_delete=models.CASCADE)
- season = models.ForeignKey(Season, on_delete=models.CASCADE)
- bhv_id = models.IntegerField(unique=True)
+ bhv_id = models.IntegerField(verbose_name='ID', unique=True)
+ name = models.CharField(verbose_name='Name', max_length=255)
+ abbreviation = models.CharField(max_length=255)
+ district = models.ForeignKey(District, verbose_name='Bezirk', on_delete=models.CASCADE)
+ season = models.ForeignKey(Season, verbose_name='Saison', on_delete=models.CASCADE)
class Meta:
+ verbose_name = 'Liga'
+ verbose_name_plural = 'Ligen'
unique_together = (('name', 'district', 'season'), ('abbreviation', 'district', 'season'))
def __str__(self):
diff --git a/src/sports_halls/admin.py b/src/sports_halls/admin.py
index 22c62cee..66841f7f 100644
--- a/src/sports_halls/admin.py
+++ b/src/sports_halls/admin.py
@@ -5,4 +5,10 @@
@admin.register(SportsHall)
class SportsHallAdmin(admin.ModelAdmin):
- search_fields = ('number', 'name', 'address', 'bhv_id')
+ list_display = ('bhv_id', 'number', 'name', 'address', 'location')
+ list_display_links = ('bhv_id', 'number', 'name')
+ search_fields = ('bhv_id', 'number', 'name', 'address')
+
+ @admin.display(description='Ort')
+ def location(self, obj: SportsHall) -> str:
+ return obj.address.rpartition(' ')[2]
diff --git a/src/sports_halls/apps.py b/src/sports_halls/apps.py
index 1c49f06d..34be9e4e 100644
--- a/src/sports_halls/apps.py
+++ b/src/sports_halls/apps.py
@@ -3,3 +3,4 @@
class SportsHallsConfig(AppConfig):
name = 'sports_halls'
+ verbose_name = 'Sporthallen'
diff --git a/src/sports_halls/migrations/0002_alter_sportshall_options_alter_sportshall_address_and_more.py b/src/sports_halls/migrations/0002_alter_sportshall_options_alter_sportshall_address_and_more.py
new file mode 100644
index 00000000..42b21397
--- /dev/null
+++ b/src/sports_halls/migrations/0002_alter_sportshall_options_alter_sportshall_address_and_more.py
@@ -0,0 +1,46 @@
+# Generated by Django 4.2.4 on 2023-10-23 20:08
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("sports_halls", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="sportshall",
+ options={
+ "verbose_name": "Sporthalle",
+ "verbose_name_plural": "Sporthallen",
+ },
+ ),
+ migrations.AlterField(
+ model_name="sportshall",
+ name="address",
+ field=models.CharField(max_length=255, verbose_name="Adresse"),
+ ),
+ migrations.AlterField(
+ model_name="sportshall",
+ name="bhv_id",
+ field=models.IntegerField(unique=True, verbose_name="ID"),
+ ),
+ migrations.AlterField(
+ model_name="sportshall",
+ name="name",
+ field=models.CharField(max_length=255, verbose_name="Name"),
+ ),
+ migrations.AlterField(
+ model_name="sportshall",
+ name="number",
+ field=models.IntegerField(unique=True, verbose_name="Hallennummer"),
+ ),
+ migrations.AlterField(
+ model_name="sportshall",
+ name="phone_number",
+ field=models.CharField(
+ blank=True, max_length=255, null=True, verbose_name="Telefonnummer"
+ ),
+ ),
+ ]
diff --git a/src/sports_halls/models.py b/src/sports_halls/models.py
index 883bbea0..2ee2624d 100644
--- a/src/sports_halls/models.py
+++ b/src/sports_halls/models.py
@@ -3,13 +3,17 @@
class SportsHall(models.Model):
- number = models.IntegerField(unique=True)
- name = models.TextField()
- address = models.TextField()
- phone_number = models.TextField(blank=True, null=True)
+ bhv_id = models.IntegerField(verbose_name='ID', unique=True)
+ number = models.IntegerField(verbose_name='Hallennummer', unique=True)
+ name = models.CharField(verbose_name='Name', max_length=255)
+ address = models.CharField(verbose_name='Adresse', max_length=255)
+ phone_number = models.CharField(verbose_name='Telefonnummer', max_length=255, blank=True, null=True)
latitude = models.DecimalField(blank=True, null=True, max_digits=9, decimal_places=6)
longitude = models.DecimalField(blank=True, null=True, max_digits=9, decimal_places=6)
- bhv_id = models.IntegerField(unique=True)
+
+ class Meta:
+ verbose_name = 'Sporthalle'
+ verbose_name_plural = 'Sporthallen'
def __str__(self):
return f"{self.number} {self.name}"
diff --git a/src/teams/admin.py b/src/teams/admin.py
index 732259b7..e1b8085b 100644
--- a/src/teams/admin.py
+++ b/src/teams/admin.py
@@ -9,4 +9,14 @@
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
+ list_display = ('name', 'season', 'get_league')
+ list_filter = ('league__season',)
search_fields = TEAM_SEARCH_FIELDS
+
+ @admin.display(description='Saison')
+ def season(self, obj: Team) -> str:
+ return str(obj.league.season)
+
+ @admin.display(description='Liga')
+ def get_league(self, obj: Team) -> str:
+ return str(obj.league.name)
diff --git a/src/teams/apps.py b/src/teams/apps.py
index 17954d66..1b23d616 100644
--- a/src/teams/apps.py
+++ b/src/teams/apps.py
@@ -3,3 +3,4 @@
class TeamsConfig(AppConfig):
name = 'teams'
+ verbose_name = 'Mannschaften'
diff --git a/src/teams/migrations/0003_alter_team_options_alter_team_bhv_id_and_more.py b/src/teams/migrations/0003_alter_team_options_alter_team_bhv_id_and_more.py
new file mode 100644
index 00000000..2eb74794
--- /dev/null
+++ b/src/teams/migrations/0003_alter_team_options_alter_team_bhv_id_and_more.py
@@ -0,0 +1,50 @@
+# Generated by Django 4.2.4 on 2023-10-23 20:08
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("leagues", "0004_alter_league_options_alter_season_options_and_more"),
+ ("teams", "0002_team_retirement"),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="team",
+ options={
+ "verbose_name": "Mannschaft",
+ "verbose_name_plural": "Mannschaften",
+ },
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="bhv_id",
+ field=models.IntegerField(unique=True, verbose_name="ID"),
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="league",
+ field=models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="leagues.league",
+ verbose_name="Liga",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="name",
+ field=models.CharField(max_length=255, verbose_name="Name"),
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="retirement",
+ field=models.DateField(blank=True, null=True, verbose_name="Abgemeldet am"),
+ ),
+ migrations.AlterField(
+ model_name="team",
+ name="short_name",
+ field=models.CharField(max_length=255, verbose_name="Abkürzung"),
+ ),
+ ]
diff --git a/src/teams/models.py b/src/teams/models.py
index d6e0ae5b..46a31603 100644
--- a/src/teams/models.py
+++ b/src/teams/models.py
@@ -10,20 +10,22 @@
class Team(models.Model):
- name = models.TextField()
- short_name = models.TextField()
- league = models.ForeignKey(League, on_delete=models.CASCADE)
- bhv_id = models.IntegerField(unique=True)
- retirement = models.DateField(blank=True, null=True)
+ bhv_id = models.IntegerField(verbose_name='ID', unique=True)
+ name = models.CharField(verbose_name='Name', max_length=255)
+ short_name = models.CharField(verbose_name='Abkürzung', max_length=255)
+ league = models.ForeignKey(League, verbose_name='Liga', on_delete=models.CASCADE)
+ retirement = models.DateField(verbose_name='Abgemeldet am', blank=True, null=True)
class Meta:
+ verbose_name = 'Mannschaft'
+ verbose_name_plural = 'Mannschaften'
unique_together = (('name', 'league'), ('short_name', 'league'))
def __str__(self):
- return f'{self.bhv_id} {self.short_name}'
+ return f'{self.bhv_id} {self.short_name} ({self.league.season})'
def get_absolute_url(self):
- return reverse('teams:detail', kwargs={'bhv_id': self.bhv_id, })
+ return reverse('teams:detail', kwargs={'bhv_id': self.bhv_id})
@staticmethod
def build_source_url(league_bhv_id, team_bhv_id):