diff --git a/csgomatches/admin.py b/csgomatches/admin.py index 527f727..4240652 100644 --- a/csgomatches/admin.py +++ b/csgomatches/admin.py @@ -1,12 +1,12 @@ from django.contrib import admin, messages from django.utils.safestring import mark_safe -from . import models +from csgomatches import models # Inlines -class LineupPlayerInline(admin.TabularInline): - model = models.LineupPlayer +class CsLineupPlayerInline(admin.TabularInline): + model = models.CsLineupPlayer autocomplete_fields = ['player'] extra = 5 max_num = 5 @@ -17,8 +17,8 @@ class LineupInline(admin.TabularInline): extra = 0 -class MatchMapInline(admin.TabularInline): - model = models.MatchMap +class CsMatchMapInline(admin.TabularInline): + model = models.CsMatchMap extra = 0 verbose_name = 'Map' verbose_name_plural = 'Match Maps' @@ -31,8 +31,8 @@ class ExternalLinkInline(admin.TabularInline): # Models class MatchMapAdmin(admin.ModelAdmin): - list_display = ['match', 'rounds_won_team_a', 'rounds_won_team_b', 'played_map', 'has_ended', 'is_live', 'delay_minutes', 'starting_at', 'unplayed', 'map_nr'] - list_editable = ['rounds_won_team_a', 'rounds_won_team_b', 'played_map', 'map_nr'] + list_display = ['match', 'rounds_won_team_a', 'rounds_won_team_b', 'map', 'has_ended', 'is_live', 'starting_at', 'unplayed', 'map_nr'] + list_editable = ['rounds_won_team_a', 'rounds_won_team_b', 'map', 'map_nr'] list_filter = ['match__tournament', 'match__lineup_a', 'match__lineup_b', 'map_nr'] ordering = ['-starting_at'] @@ -48,7 +48,8 @@ def is_live(self, obj): is_live.boolean = True -class TournamentAdmin(admin.ModelAdmin): +# Currently no generic implementation of ModelAdmin for Tournament class +class CsTournamentAdmin(admin.ModelAdmin): search_fields = ['name', 'name_alt', 'name_hltv', 'name_99dmg'] list_display = ['name', 'name_alt', 'name_hltv', 'name_99dmg'] actions = ['cleanup', 'merge_two'] @@ -182,7 +183,7 @@ class LineupAdmin(admin.ModelAdmin): list_display = ['team', 'game', 'team_logo_url', 'active_from', 'get_is_active', 'is_active'] autocomplete_fields = ['team'] list_filter = ['game', 'is_active'] - inlines = [LineupPlayerInline] + inlines = [CsLineupPlayerInline] def get_is_active(self, obj): return obj.get_is_active() @@ -194,7 +195,7 @@ class MatchAdmin(admin.ModelAdmin): list_filter = ['lineup_a', 'lineup_b'] search_fields = ['lineup_b__team__name', 'lineup_b__team__name_long', 'tournament__name'] autocomplete_fields = ['lineup_a', 'lineup_b', 'tournament'] - inlines = [MatchMapInline, ExternalLinkInline] + inlines = [CsMatchMapInline, ExternalLinkInline] # replace "Save and add another" button with "Save as new" to use previous matches as template # https://docs.djangoproject.com/en/5.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_as save_as = True @@ -210,7 +211,7 @@ class PlayerAdmin(admin.ModelAdmin): list_editable = ['ingame_name', 'first_name', 'last_name'] -class LineupPlayerAdmin(admin.ModelAdmin): +class CsLineupPlayerAdmin(admin.ModelAdmin): list_display = ['player', 'role', 'lineup'] list_editable = ['role'] list_filter = ['role'] @@ -238,15 +239,14 @@ def save_global(modeladmin, request, queryset): admin.site.register(models.Team, TeamAdmin) -admin.site.register(models.Lineup, LineupAdmin) -admin.site.register(models.LineupPlayer, LineupPlayerAdmin) +admin.site.register(models.CsLineup, LineupAdmin) +admin.site.register(models.CsLineupPlayer, CsLineupPlayerAdmin) admin.site.register(models.ExternalLink, ExternalLinkAdmin) -admin.site.register(models.Map) -admin.site.register(models.Match, MatchAdmin) -admin.site.register(models.MatchMap, MatchMapAdmin) -admin.site.register(models.Player, PlayerAdmin) -admin.site.register(models.PlayerRole) -admin.site.register(models.Tournament, TournamentAdmin) +admin.site.register(models.CsMap) +admin.site.register(models.CsMatch, MatchAdmin) +admin.site.register(models.CsMatchMap, MatchMapAdmin) +admin.site.register(models.CsPlayer, PlayerAdmin) +admin.site.register(models.CsTournament, CsTournamentAdmin) admin.site.register(models.Game, GameAdmin) admin.site.register(models.StaticPage) diff --git a/csgomatches/drf_api/ser.py b/csgomatches/drf_api/ser.py index 870d77c..a880aa7 100644 --- a/csgomatches/drf_api/ser.py +++ b/csgomatches/drf_api/ser.py @@ -3,88 +3,101 @@ from django.apps import apps from . import ser_objects +from csgomatches.models.cs_models import CSLineupPlayerRole -class CSGOTournamentSerializer(serializers.ModelSerializer): +class CsTournamentSerializer(serializers.ModelSerializer): class Meta: - model = apps.get_model('csgomatches.Tournament') + model = apps.get_model('csgomatches.CsTournament') fields = ['name', 'name_alt', 'name_hltv', 'name_99dmg', 'id'] -class CSGOTeamSerializer(serializers.ModelSerializer): +class TeamSerializer(serializers.ModelSerializer): class Meta: model = apps.get_model('csgomatches.Team') fields = ['name', 'name_long', 'name_alt', 'hltv_id', 'id'] -class CSGOPlayerShortSerializer(serializers.ModelSerializer): +class CsPlayerShortSerializer(serializers.ModelSerializer): class Meta: - model = apps.get_model('csgomatches.Player') + model = apps.get_model('csgomatches.CsPlayer') fields = ['ingame_name',] +class CsLineupPlayerSerializer(serializers.ModelSerializer): + player = CsPlayerShortSerializer() + role = serializers.SerializerMethodField() -class CSGOPlayerRoleShortSerializer(serializers.ModelSerializer): class Meta: - model = apps.get_model('csgomatches.PlayerRole') - fields = ['name',] + model = apps.get_model('csgomatches.CsLineupPlayer') + fields = ['player', 'role'] -class CSGOLineupPlayerSerializer(serializers.ModelSerializer): - player = CSGOPlayerShortSerializer() - role = CSGOPlayerRoleShortSerializer() + def to_representation(self, instance): + representation = super().to_representation(instance) + # Map the enum value to a more human-readable string + representation['role'] = CSLineupPlayerRole(instance.role).name if instance.role else None + return representation - class Meta: - model = apps.get_model('csgomatches.LineupPlayer') - fields = ['player', 'role', ] + def to_internal_value(self, data): + # Convert string to enum value during deserialization + internal_value = super().to_internal_value(data) + role = data.get('role') + + if role is not None: + try: + internal_value['role'] = CSLineupPlayerRole[role].value + except KeyError: + raise serializers.ValidationError(f"Invalid role: {role}") + return internal_value -class CSGOLineupSerializer(serializers.ModelSerializer): - team = CSGOTeamSerializer(read_only=True) - players = CSGOLineupPlayerSerializer(many=True, read_only=True, source='lineupplayer_set') +class CsLineupSerializer(serializers.ModelSerializer): + team = TeamSerializer(read_only=True) + players = CsLineupPlayerSerializer(many=True, read_only=True, source='lineupplayer_set') class Meta: - model = apps.get_model('csgomatches.Lineup') + model = apps.get_model('csgomatches.CsLineup') fields = ['team', 'team_logo_url', 'active_from', 'players', 'id'] -class CSGOMapSerializer(serializers.ModelSerializer): +class CsMapSerializer(serializers.ModelSerializer): class Meta: - model = apps.get_model('csgomatches.Map') + model = apps.get_model('csgomatches.CsMap') fields = ['name', 'cs_name', 'id'] -class CSGOMatchMapSerializer(serializers.ModelSerializer): - map_pick_of = CSGOLineupSerializer(read_only=True) - played_map = CSGOMapSerializer(read_only=True) +class CsMatchMapSerializer(serializers.ModelSerializer): + map_pick_of = CsLineupSerializer(read_only=True) + played = CsMapSerializer(read_only=True) class Meta: - model = apps.get_model('csgomatches.MatchMap') - fields = ['rounds_won_team_a', 'rounds_won_team_b', 'starting_at', 'map_pick_of', 'played_map', 'id'] + model = apps.get_model('csgomatches.CsMatchMap') + fields = ['rounds_won_team_a', 'rounds_won_team_b', 'starting_at', 'map_pick_of', 'map', 'id'] -class CSGOMatchSerializer(serializers.ModelSerializer): - tournament = CSGOTournamentSerializer(read_only=True) - lineup_a = CSGOLineupSerializer(read_only=True) - lineup_b = CSGOLineupSerializer(read_only=True) +class CsMatchSerializer(serializers.ModelSerializer): + tournament = CsTournamentSerializer(read_only=True) + lineup_a = CsLineupSerializer(read_only=True) + lineup_b = CsLineupSerializer(read_only=True) livescore_url = serializers.SerializerMethodField(read_only=True, source='get_livescore_url') html_detail_url = serializers.SerializerMethodField(read_only=True, source='get_html_detail_url') - matchmaps = CSGOMatchMapSerializer(many=True, source='matchmap_set') + matchmaps = CsMatchMapSerializer(many=True, source='matchmap_set') def get_livescore_url(self, obj): if obj.hltv_match_id: url = reverse('match_livescore-detail', kwargs={'pk': obj.hltv_match_id}) request = self.context.get('request') - return request.build_absolute_uri(url) + return request.build_absolute_uri(url) # type: ignore def get_html_detail_url(self, obj): url = obj.get_absolute_url() request = self.context.get('request') - return request.build_absolute_uri(url) + return request.build_absolute_uri(url) # type: ignore class Meta: - model = apps.get_model('csgomatches.Match') + model = apps.get_model('csgomatches.CsMatch') fields = ['tournament', 'lineup_a', 'lineup_b', 'slug', 'bestof', 'first_map_at', 'cancelled', 'hltv_match_id', 'livescore_url', 'html_detail_url', 'matchmaps'] -class CSGOMatchMapUpdateSerializer(serializers.ModelSerializer): +class CsMatchMapUpdateSerializer(serializers.ModelSerializer): class Meta: - model = apps.get_model('csgomatches.MatchMap') + model = apps.get_model('csgomatches.CsMatchMap') fields = ['map_nr', 'rounds_won_team_a', 'rounds_won_team_b',] @@ -117,7 +130,7 @@ def get_api_match_url(self, obj): if csgo_match: url = reverse('match_all-detail', kwargs={'pk': csgo_match.pk}) request = self.context.get('request') - return request.build_absolute_uri(url) + return request.build_absolute_uri(url) # type: ignore def create(self, validated_data): hltv_match_id = validated_data.get('hltv_match_id') diff --git a/csgomatches/drf_api/urls.py b/csgomatches/drf_api/urls.py index 39cfb89..3652d08 100644 --- a/csgomatches/drf_api/urls.py +++ b/csgomatches/drf_api/urls.py @@ -1,8 +1,8 @@ -from rest_framework import routers, renderers -from . import views +from rest_framework import routers +from csgomatches.drf_api import views router = routers.DefaultRouter() -router.APIRootView = views.CSGOAPIRootView +router.APIRootView = views.CsAPIRootView router.register(r'team', viewset=views.TeamViewSet) router.register(r'tournament', viewset=views.TournamentViewSet) router.register(r'lineup', viewset=views.LineupViewSet) diff --git a/csgomatches/drf_api/views.py b/csgomatches/drf_api/views.py index 49f7f32..6d340bb 100644 --- a/csgomatches/drf_api/views.py +++ b/csgomatches/drf_api/views.py @@ -13,16 +13,16 @@ from csgomatches.utils.scrapers.faceit import check_hubs_for_matches -class CSGOPagination(pagination.LimitOffsetPagination): +class CsPagination(pagination.LimitOffsetPagination): max_limit = 50 default_limit = 20 -class CSGOView(views.APIView): - pagination_class = CSGOPagination +class CsView(views.APIView): + pagination_class = CsPagination def get_renderers(self): - rend = super(CSGOView, self).get_renderers() + rend = super(CsView, self).get_renderers() for r in rend: if isinstance(r, renderers.BrowsableAPIRenderer): r.template = 'csgomatches/rest_framework/csgo_api.html' @@ -34,7 +34,7 @@ def name(self): return "API für " + self.request.build_absolute_uri() -class CSGOAPIRootView(CSGOView, routers.APIRootView): +class CsAPIRootView(CsView, routers.APIRootView): def get_view_description(self, html=False): if html: return mark_safe('

Willkommen! API made by ' @@ -47,7 +47,7 @@ def get_view_name(self): return "Browse API" def get_renderers(self): - rend = super(CSGOAPIRootView, self).get_renderers() + rend = super(CsAPIRootView, self).get_renderers() for r in rend: if isinstance(r, renderers.BrowsableAPIRenderer): r.template = 'csgomatches/rest_framework/csgo_api.html' @@ -55,28 +55,28 @@ def get_renderers(self): return rend -class TeamViewSet(CSGOView, viewsets.ReadOnlyModelViewSet): +class TeamViewSet(CsView, viewsets.ReadOnlyModelViewSet): """ - CSGO Teams + Cs Teams """ queryset = apps.get_model('csgomatches.Team').objects.all() - serializer_class = ser.CSGOTeamSerializer + serializer_class = ser.TeamSerializer def get_view_name(self): return 'Teams' -class TournamentViewSet(CSGOView, viewsets.ReadOnlyModelViewSet): - queryset = apps.get_model('csgomatches.Tournament').objects.all() - serializer_class = ser.CSGOTournamentSerializer +class TournamentViewSet(CsView, viewsets.ReadOnlyModelViewSet): + queryset = apps.get_model('csgomatches.CsTournament').objects.all() + serializer_class = ser.CsTournamentSerializer def get_view_name(self): return 'Tournaments' -class MatchViewSet(CSGOView, viewsets.ReadOnlyModelViewSet): - queryset = apps.get_model('csgomatches.Match').objects.all() - serializer_class = ser.CSGOMatchSerializer +class MatchViewSet(CsView, viewsets.ReadOnlyModelViewSet): + queryset = apps.get_model('csgomatches.CsMatch').objects.all() + serializer_class = ser.CsMatchSerializer def get_view_name(self): return 'Matches (all)' @@ -86,9 +86,9 @@ def list(self, request, *args, **kwargs): return super(MatchViewSet, self).list(request, *args, **kwargs) -class MatchUpcomingViewSet(CSGOView, viewsets.ReadOnlyModelViewSet): - queryset = apps.get_model('csgomatches.Match').objects.filter(first_map_at__gte=timezone.now()) - serializer_class = ser.CSGOMatchSerializer +class MatchUpcomingViewSet(CsView, viewsets.ReadOnlyModelViewSet): + queryset = apps.get_model('csgomatches.CsMatch').objects.filter(first_map_at__gte=timezone.now()) + serializer_class = ser.CsMatchSerializer def get_view_name(self): return 'Matches (upcoming)' @@ -98,9 +98,9 @@ def list(self, request, *args, **kwargs): return super(MatchUpcomingViewSet, self).list(request, *args, **kwargs) -class LineupViewSet(CSGOView, viewsets.ReadOnlyModelViewSet): - queryset = apps.get_model('csgomatches.Lineup').objects.all() - serializer_class = ser.CSGOLineupSerializer +class LineupViewSet(CsView, viewsets.ReadOnlyModelViewSet): + queryset = apps.get_model('csgomatches.CsLineup').objects.all() + serializer_class = ser.CsLineupSerializer def get_view_name(self): return 'Team Lineups' @@ -110,7 +110,7 @@ def list(self, request, *args, **kwargs): return super(LineupViewSet, self).list(request, *args, **kwargs) -class HLTVLiveScoreViewSet(CSGOView, mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): +class HLTVLiveScoreViewSet(CsView, mixins.RetrieveModelMixin, mixins.ListModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet): """ Get HLTV Live Score from any Match """ @@ -120,14 +120,14 @@ def get_view_name(self): def get_queryset(self): last_7_days = timezone.now() - timezone.timedelta(days=7) - return apps.get_model('csgomatches.Match').objects.filter( + return apps.get_model('csgomatches.CsMatch').objects.filter( hltv_match_id__isnull=False, first_map_at__gte=last_7_days ) def get_serializer(self, *args, **kwargs): ser_by_action = { - 'list': ser.CSGOMatchSerializer, + 'list': ser.CsMatchSerializer, 'retrieve': ser.HLTVMatchSerializer, 'create': ser.HLTVMatchSerializer, } @@ -147,13 +147,13 @@ def get_object(self): hltv_match_instance = ser_objects.HLTVMatch(hltv_match_id=hltv_match_id) return hltv_match_instance -class MatchMapUpdateView(CSGOView, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): +class MatchMapUpdateView(CsView, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): permission_classes = (permissions.IsAdminUser, ) authentication_classes = (authentication.BasicAuthentication, ) - queryset = apps.get_model('csgomatches.MatchMap').objects.all() - serializer_class = ser.CSGOMatchMapUpdateSerializer + queryset = apps.get_model('csgomatches.CsMatchMap').objects.all() + serializer_class = ser.CsMatchMapUpdateSerializer -class FaceitProLeagueMatchesView(CSGOView, viewsets.ViewSet): +class FaceitProLeagueMatchesView(CsView, viewsets.ViewSet): serializer_class = ser.FaceitProLeagueMatchesSerializer def get_instances(self): diff --git a/csgomatches/management/commands/csgo_fetch_matches.py b/csgomatches/management/commands/csgo_fetch_matches.py index 3ad7f65..f873c19 100644 --- a/csgomatches/management/commands/csgo_fetch_matches.py +++ b/csgomatches/management/commands/csgo_fetch_matches.py @@ -2,6 +2,7 @@ from json import JSONDecodeError import dateutil.parser +from django.db.models.base import Model import requests from bs4 import BeautifulSoup from django.apps import apps @@ -386,14 +387,14 @@ def crawl_y0fl0w_de(self, include_archive_pages=True, fake=False): # set map cs_name = HLTV_MAP_NAMES_TO_CS_NAME.get(name, 'de_' + name.lower()) # not found in HLTV_MAP_NAMES_TO_CS_NAME - played_map, played_map_created = apps.get_model('csgomatches.Map').objects.get_or_create( + map, map_created = apps.get_model('csgomatches.Map').objects.get_or_create( cs_name=cs_name, defaults={ 'name': name } ) print("[crawl_y0fl0w_de] - Setting Map name", name, match) - matchmap.played_map = played_map + matchmap.map = map matchmap.save() if map_pick_lineup and matchmap.map_pick_of is None: @@ -581,16 +582,16 @@ def crawl_99damage_de(self, include_archive_pages=False, fake=False): # get or create Maps if 'de_tba' in mapinfos.get('name'): - played_map = None + map = None else: - played_map, played_map_created = apps.get_model('csgomatches.Map').objects.get_or_create( + map, map_created = apps.get_model('csgomatches.Map').objects.get_or_create( cs_name=mapinfos.get('name'), defaults={ 'name': mapinfos.get('name') } ) - matchmap = apps.get_model('csgomatches.MatchMap').objects.filter( + matchmap = apps.get_model('csgomatches.CsMatchMap').objects.filter( match=match, map_nr=map_nr ).first() @@ -598,8 +599,8 @@ def crawl_99damage_de(self, include_archive_pages=False, fake=False): print("[crawl_99damage_de] - found Matchmap #", matchmap.map_nr, matchmap, sep='') if matchmap: - if score_left > matchmap.rounds_won_team_a or score_right > matchmap.rounds_won_team_b or played_map != matchmap.played_map: - matchmap.played_map = played_map + if score_left > matchmap.rounds_won_team_a or score_right > matchmap.rounds_won_team_b or map != matchmap.map: + matchmap.map = map matchmap.rounds_won_team_a = score_left matchmap.rounds_won_team_b = score_right matchmap.save() @@ -608,7 +609,7 @@ def crawl_99damage_de(self, include_archive_pages=False, fake=False): matchmap = apps.get_model('csgomatches.MatchMap')( match=match, map_nr=map_nr, - played_map=played_map, + map=map, rounds_won_team_a=score_left, rounds_won_team_b=score_right, starting_at=m_datetime + timezone.timedelta(hours=map_nr), diff --git a/csgomatches/migrations/0049_rename_player_csplayer.py b/csgomatches/migrations/0049_rename_player_csplayer.py new file mode 100644 index 0000000..1dc47b9 --- /dev/null +++ b/csgomatches/migrations/0049_rename_player_csplayer.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-28 15:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0048_alter_match_options'), + ] + + operations = [ + migrations.RenameModel( + old_name='Player', + new_name='CsPlayer', + ), + ] diff --git a/csgomatches/migrations/0050_rename_match_csmatch_rename_tournament_cstournament.py b/csgomatches/migrations/0050_rename_match_csmatch_rename_tournament_cstournament.py new file mode 100644 index 0000000..0fd112e --- /dev/null +++ b/csgomatches/migrations/0050_rename_match_csmatch_rename_tournament_cstournament.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1 on 2024-09-29 09:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0049_rename_player_csplayer'), + ] + + operations = [ + migrations.RenameModel( + old_name='Match', + new_name='CsMatch', + ), + migrations.RenameModel( + old_name='Tournament', + new_name='CsTournament', + ), + ] diff --git a/csgomatches/migrations/0051_rename_csgositesetting_sitesetting.py b/csgomatches/migrations/0051_rename_csgositesetting_sitesetting.py new file mode 100644 index 0000000..af1047d --- /dev/null +++ b/csgomatches/migrations/0051_rename_csgositesetting_sitesetting.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-29 09:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0050_rename_match_csmatch_rename_tournament_cstournament'), + ('sites', '0002_alter_domain_unique'), + ] + + operations = [ + migrations.RenameModel( + old_name='CSGOSiteSetting', + new_name='SiteSetting', + ), + ] diff --git a/csgomatches/migrations/0052_rename_lineupplayer_cslineupplayer.py b/csgomatches/migrations/0052_rename_lineupplayer_cslineupplayer.py new file mode 100644 index 0000000..75b5113 --- /dev/null +++ b/csgomatches/migrations/0052_rename_lineupplayer_cslineupplayer.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-29 11:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0051_rename_csgositesetting_sitesetting'), + ] + + operations = [ + migrations.RenameModel( + old_name='LineupPlayer', + new_name='CsLineupPlayer', + ), + ] diff --git a/csgomatches/migrations/0053_alter_cslineupplayer_options_alter_csmatch_options_and_more.py b/csgomatches/migrations/0053_alter_cslineupplayer_options_alter_csmatch_options_and_more.py new file mode 100644 index 0000000..ea86106 --- /dev/null +++ b/csgomatches/migrations/0053_alter_cslineupplayer_options_alter_csmatch_options_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1 on 2024-09-29 12:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0052_rename_lineupplayer_cslineupplayer'), + ] + + operations = [ + migrations.AlterModelOptions( + name='cslineupplayer', + options={'verbose_name': 'CS Lineup Player', 'verbose_name_plural': 'CS Lineup Players'}, + ), + migrations.AlterModelOptions( + name='csmatch', + options={'verbose_name': 'CS Match', 'verbose_name_plural': 'CS Matches'}, + ), + migrations.AlterModelOptions( + name='csplayer', + options={'verbose_name': 'CS Player', 'verbose_name_plural': 'CS Players'}, + ), + migrations.AlterModelOptions( + name='cstournament', + options={'verbose_name': 'CS Tournament', 'verbose_name_plural': 'CS Tournaments'}, + ), + ] diff --git a/csgomatches/migrations/0054_alter_cslineupplayer_role_delete_playerrole.py b/csgomatches/migrations/0054_alter_cslineupplayer_role_delete_playerrole.py new file mode 100644 index 0000000..29a2387 --- /dev/null +++ b/csgomatches/migrations/0054_alter_cslineupplayer_role_delete_playerrole.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1 on 2024-09-29 12:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0053_alter_cslineupplayer_options_alter_csmatch_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='cslineupplayer', + name='role', + field=models.CharField(blank=True, choices=[('RIFLE', 'rifle'), ('IGL_RIFLE', 'igl_rifle'), ('AWP', 'awp'), ('IGL_AWP', 'igl_awp')], max_length=20, null=True), + ), + migrations.DeleteModel( + name='PlayerRole', + ), + ] diff --git a/csgomatches/migrations/0055_alter_csmatch_tournament.py b/csgomatches/migrations/0055_alter_csmatch_tournament.py new file mode 100644 index 0000000..ed6687b --- /dev/null +++ b/csgomatches/migrations/0055_alter_csmatch_tournament.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1 on 2024-09-29 12:41 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0054_alter_cslineupplayer_role_delete_playerrole'), + ] + + operations = [ + migrations.AlterField( + model_name='csmatch', + name='tournament', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='match_set', to='csgomatches.cstournament'), + ), + ] diff --git a/csgomatches/migrations/0056_remove_map_cs_name.py b/csgomatches/migrations/0056_remove_map_cs_name.py new file mode 100644 index 0000000..d19086a --- /dev/null +++ b/csgomatches/migrations/0056_remove_map_cs_name.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-29 12:57 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0055_alter_csmatch_tournament'), + ] + + operations = [ + migrations.RemoveField( + model_name='map', + name='cs_name', + ), + ] diff --git a/csgomatches/migrations/0057_rename_matchmap_csmatchmap.py b/csgomatches/migrations/0057_rename_matchmap_csmatchmap.py new file mode 100644 index 0000000..f086f10 --- /dev/null +++ b/csgomatches/migrations/0057_rename_matchmap_csmatchmap.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-29 15:14 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0056_remove_map_cs_name'), + ] + + operations = [ + migrations.RenameModel( + old_name='MatchMap', + new_name='CsMatchMap', + ), + ] diff --git a/csgomatches/migrations/0058_alter_csmatchmap_options.py b/csgomatches/migrations/0058_alter_csmatchmap_options.py new file mode 100644 index 0000000..6d350c2 --- /dev/null +++ b/csgomatches/migrations/0058_alter_csmatchmap_options.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-29 15:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0057_rename_matchmap_csmatchmap'), + ] + + operations = [ + migrations.AlterModelOptions( + name='csmatchmap', + options={'verbose_name': 'CS Match Map', 'verbose_name_plural': 'CS Match Maps'}, + ), + ] diff --git a/csgomatches/migrations/0059_rename_map_csmap_alter_csmap_options.py b/csgomatches/migrations/0059_rename_map_csmap_alter_csmap_options.py new file mode 100644 index 0000000..8455e44 --- /dev/null +++ b/csgomatches/migrations/0059_rename_map_csmap_alter_csmap_options.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1 on 2024-09-29 15:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0058_alter_csmatchmap_options'), + ] + + operations = [ + migrations.RenameModel( + old_name='Map', + new_name='CsMap', + ), + migrations.AlterModelOptions( + name='csmap', + options={'verbose_name': 'CS Map', 'verbose_name_plural': 'CS Maps'}, + ), + ] diff --git a/csgomatches/migrations/0060_remove_csmatchmap_delay_minutes.py b/csgomatches/migrations/0060_remove_csmatchmap_delay_minutes.py new file mode 100644 index 0000000..94eb5ef --- /dev/null +++ b/csgomatches/migrations/0060_remove_csmatchmap_delay_minutes.py @@ -0,0 +1,17 @@ +# Generated by Django 5.1 on 2024-09-29 15:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0059_rename_map_csmap_alter_csmap_options'), + ] + + operations = [ + migrations.RemoveField( + model_name='csmatchmap', + name='delay_minutes', + ), + ] diff --git a/csgomatches/migrations/0061_rename_played_map_csmatchmap_map.py b/csgomatches/migrations/0061_rename_played_map_csmatchmap_map.py new file mode 100644 index 0000000..d7e7c62 --- /dev/null +++ b/csgomatches/migrations/0061_rename_played_map_csmatchmap_map.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-29 15:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0060_remove_csmatchmap_delay_minutes'), + ] + + operations = [ + migrations.RenameField( + model_name='csmatchmap', + old_name='played_map', + new_name='map', + ), + ] diff --git a/csgomatches/migrations/0062_rename_lineup_cslineup_alter_cslineup_options.py b/csgomatches/migrations/0062_rename_lineup_cslineup_alter_cslineup_options.py new file mode 100644 index 0000000..be4889e --- /dev/null +++ b/csgomatches/migrations/0062_rename_lineup_cslineup_alter_cslineup_options.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1 on 2024-09-29 16:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0061_rename_played_map_csmatchmap_map'), + ] + + operations = [ + migrations.RenameModel( + old_name='Lineup', + new_name='CsLineup', + ), + migrations.AlterModelOptions( + name='cslineup', + options={'ordering': ['team__name', '-active_from'], 'verbose_name': 'CS Lineup', 'verbose_name_plural': 'CS Lineups'}, + ), + ] diff --git a/csgomatches/migrations/0063_alter_csmatch_options_alter_csmatchmap_options_and_more.py b/csgomatches/migrations/0063_alter_csmatch_options_alter_csmatchmap_options_and_more.py new file mode 100644 index 0000000..3e37c32 --- /dev/null +++ b/csgomatches/migrations/0063_alter_csmatch_options_alter_csmatchmap_options_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1 on 2024-09-29 16:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0062_rename_lineup_cslineup_alter_cslineup_options'), + ] + + operations = [ + migrations.AlterModelOptions( + name='csmatch', + options={'ordering': ['-first_map_at'], 'verbose_name': 'CS Match', 'verbose_name_plural': 'CS Matches'}, + ), + migrations.AlterModelOptions( + name='csmatchmap', + options={'ordering': ['starting_at'], 'verbose_name': 'CS Match Map', 'verbose_name_plural': 'CS Match Maps'}, + ), + migrations.AlterModelOptions( + name='cstournament', + options={'ordering': ['name'], 'verbose_name': 'CS Tournament', 'verbose_name_plural': 'CS Tournaments'}, + ), + ] diff --git a/csgomatches/migrations/0064_alter_csmatchmap_match.py b/csgomatches/migrations/0064_alter_csmatchmap_match.py new file mode 100644 index 0000000..77d7d80 --- /dev/null +++ b/csgomatches/migrations/0064_alter_csmatchmap_match.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1 on 2024-12-02 11:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0063_alter_csmatch_options_alter_csmatchmap_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='csmatchmap', + name='match', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='matchmap_set', to='csgomatches.csmatch'), + ), + ] diff --git a/csgomatches/migrations/0065_alter_cslineup_team.py b/csgomatches/migrations/0065_alter_cslineup_team.py new file mode 100644 index 0000000..4553143 --- /dev/null +++ b/csgomatches/migrations/0065_alter_cslineup_team.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1 on 2024-12-02 11:35 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('csgomatches', '0064_alter_csmatchmap_match'), + ] + + operations = [ + migrations.AlterField( + model_name='cslineup', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lineup_set', to='csgomatches.team'), + ), + ] diff --git a/csgomatches/models/__init__.py b/csgomatches/models/__init__.py new file mode 100644 index 0000000..e5a8cf8 --- /dev/null +++ b/csgomatches/models/__init__.py @@ -0,0 +1,2 @@ +from .global_models import * +from .cs_models import * diff --git a/csgomatches/models/cs_models.py b/csgomatches/models/cs_models.py new file mode 100644 index 0000000..3e9eb77 --- /dev/null +++ b/csgomatches/models/cs_models.py @@ -0,0 +1,139 @@ +from enum import Enum +from django.db import models +from django.db.models import QuerySet +from django.urls import reverse +import requests + +import csgomatches.models.global_models as global_models + + +class CsPlayer(global_models.Player): + hltv_id = models.IntegerField(null=True, blank=True) + esea_user_id = models.IntegerField(null=True, blank=True) + + class Meta: + verbose_name = "CS Player" + verbose_name_plural = "CS Players" + + +class CsTournament(global_models.Tournament): + name_hltv = models.CharField(max_length=255, null=True, blank=True) + name_99dmg = models.CharField(max_length=255, null=True, blank=True) + esea_bracket_id = models.IntegerField(null=True, blank=True) + esea_bracket_team_ids = models.CharField(max_length=255, null=True, blank=True, help_text='Comma Separated') + + class Meta(global_models.Tournament.Meta): + verbose_name = "CS Tournament" + verbose_name_plural = "CS Tournaments" + + +class CsLineup(global_models.Lineup): + team = models.ForeignKey(global_models.Team, on_delete=models.CASCADE, related_name='lineup_set') + class Meta(global_models.Lineup.Meta): + verbose_name = "CS Lineup" + verbose_name_plural = "CS Lineups" + + +class CsMap(global_models.Map): + class Meta: + verbose_name = "CS Map" + verbose_name_plural = "CS Maps" + + +class CsMatch(global_models.OneOnOneMatch): + tournament = models.ForeignKey(CsTournament, on_delete=models.CASCADE, related_name='match_set') + lineup_a = models.ForeignKey(CsLineup, on_delete=models.CASCADE, related_name='matches_as_lineup_a_set', null=True, blank=True) + lineup_b = models.ForeignKey(CsLineup, on_delete=models.CASCADE, related_name='matches_as_lineup_b_set', null=True, blank=True) + + hltv_match_id = models.CharField(max_length=20, null=True, blank=True, help_text='For HLTV Livescore during match') + esea_match_id = models.CharField(max_length=255, null=True, blank=True) + enable_99dmg = models.BooleanField(default=False) + enable_hltv = models.BooleanField(default=True) + + matchmap_set: QuerySet['CsMatchMap'] + + class Meta(global_models.OneOnOneMatch.Meta): + verbose_name = "CS Match" + verbose_name_plural = "CS Matches" + + def get_livescore_url(self, request): + if self.hltv_match_id: + url = reverse('match_livescore-detail', kwargs={'pk': self.hltv_match_id}) + return request.build_absolute_uri(url) + + def update_hltv_livescore(self, request) -> None: + # Guard clause in case lineup_a is None + if not self.lineup_a: + return + + url = self.get_livescore_url(request=request) + if url: + response = requests.get(url=url, params={'format': 'json'}).json() + maps = response.get('maps', []) + for map_data in maps: + map_nr = map_data.get('map_nr') + mm_obj = self.matchmap_set.filter(map_nr=map_nr).first() + if mm_obj: + mm_obj.map = CsMap.objects.filter( + models.Q(name=map_data.get('map_name')) | + models.Q(cs_name=map_data.get('map_name')) + ).first() + score_a, score_b = map_data.get('score_a'), map_data.get('score_b') + swap_score = False + team_a_hltv_id = response.get('team_a_id') + if team_a_hltv_id != self.lineup_a.team.hltv_id: + swap_score = True + + if swap_score: + score_b, score_a = score_a, score_b + + mm_obj.rounds_won_team_a = score_a + mm_obj.rounds_won_team_b = score_b + mm_obj.save() + + +class CSLineupPlayerRole(Enum): + RIFLE = "rifle" + IGL_RIFLE = "igl_rifle" + AWP = "awp" + IGL_AWP = "igl_awp" + + @classmethod + def choices(cls): + return [(key.name, key.value) for key in cls] + + +class CsLineupPlayer(global_models.LineupPlayer): + player = models.ForeignKey(CsPlayer, on_delete=models.CASCADE) + lineup = models.ForeignKey(CsLineup, on_delete=models.CASCADE) + role = models.CharField( + max_length=20, + choices=CSLineupPlayerRole.choices(), + null=True, + blank=True + ) + + class Meta: + verbose_name = "CS Lineup Player" + verbose_name_plural = "CS Lineup Players" + + def __str__(self): + if self.role: + return f'{self.player.ingame_name} ({self.role}) @ {self.lineup.team.name}' + return f'{self.player.ingame_name} @ {self.lineup.team.name}' + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + + +class CsMatchMap(global_models.OneOnOneMatchMap): + match = models.ForeignKey(CsMatch, on_delete=models.CASCADE, related_name='matchmap_set') + map = models.ForeignKey(CsMap, on_delete=models.CASCADE, null=True, blank=True) + map_pick_of = models.ForeignKey(CsLineup, null=True, blank=True, on_delete=models.CASCADE) + + class Meta(global_models.OneOnOneMatchMap.Meta): + verbose_name = "CS Match Map" + verbose_name_plural = "CS Match Maps" + + def has_ended(self) -> bool: + return (self.rounds_won_team_a >= 13 or self.rounds_won_team_b >= 13) and abs(self.rounds_won_team_a - self.rounds_won_team_b) >= 2 diff --git a/csgomatches/models.py b/csgomatches/models/global_models.py similarity index 75% rename from csgomatches/models.py rename to csgomatches/models/global_models.py index 5fb8933..8636813 100644 --- a/csgomatches/models.py +++ b/csgomatches/models/global_models.py @@ -1,16 +1,19 @@ +from abc import abstractmethod import os -import requests +import importlib.resources import twitter + +from typing import TYPE_CHECKING, Self + from django.contrib.sites.models import Site from django.db import models -from django.db.models import QuerySet -# Create your models here. +from django.db.models import QuerySet, Manager from django.urls import reverse from django.utils import timezone from django.utils.text import slugify -from . import managers -from .utils.publishing import twitter_api +from csgomatches import managers +from csgomatches.utils.publishing import twitter_api def get_flags_choices()-> list[tuple[str, str]]: @@ -18,12 +21,12 @@ def get_flags_choices()-> list[tuple[str, str]]: returns a list of tuples of all available flags by looking at png files in 'static/csgomatches/flags' """ choices: list[tuple[str, str]] = [] - base_pth = os.path.dirname(os.path.abspath(__file__)) - flags_pth = os.path.join(base_pth, 'static/csgomatches/flags') - for fn in os.listdir(flags_pth): - if fn.endswith('.png'): - short_fn = fn.replace('.png', '') - choices.append((short_fn, short_fn)) + with importlib.resources.path('csgomatches', 'static') as static_folder_path: + flags_folder_path = os.path.join(static_folder_path, 'csgomatches', 'flags') + for fn in os.listdir(flags_folder_path): + if fn.endswith('.png'): + short_fn = fn.replace('.png', '') + choices.append((short_fn, short_fn)) choices.sort(key=lambda x: x[0]) return choices @@ -47,12 +50,12 @@ class Team(models.Model): hltv_id = models.IntegerField(null=True, blank=True) esea_team_id = models.IntegerField(null=True, blank=True) - lineup_set: QuerySet['Lineup'] + lineup_set: managers.LineupQuerySet objects = managers.TeamManager() def get_hltv_id_from_name(self): - from .utils.scrapers.hltv import get_hltv_id_from_team_name + from csgomatches.utils.scrapers.hltv import get_hltv_id_from_team_name return get_hltv_id_from_team_name(team_mdl=self) def get_hltv_team_link(self): @@ -70,20 +73,12 @@ class Player(models.Model): ingame_name = models.CharField(max_length=255) first_name = models.CharField(max_length=255) last_name = models.CharField(max_length=255) - hltv_id = models.IntegerField(null=True, blank=True) - esea_user_id = models.IntegerField(null=True, blank=True) - - def __str__(self): - return f'{self.first_name} "{self.ingame_name}" {self.last_name}' - -class PlayerRole(models.Model): - name = models.CharField(max_length=255) - - # i.e. Fragger, Support, Leader, AWPer, Lurker, Coach + class Meta: + abstract = True def __str__(self): - return self.name + return f'{self.first_name} "{self.ingame_name}" {self.last_name}' class Lineup(models.Model): @@ -93,8 +88,15 @@ class Lineup(models.Model): active_from = models.DateTimeField(help_text='Set -10 Days to avoid multiple Lineup creations') is_active = models.BooleanField(default=True) + lineupplayer_set: Manager['LineupPlayer'] + objects = managers.LineupQuerySet.as_manager() + class Meta: + abstract = True + ordering = ['team__name', '-active_from'] + unique_together = ('team', 'active_from') + def get_previous_lineup(self) -> 'Lineup | None': return self.team.lineup_set.filter( active_from__lt=self.active_from @@ -126,44 +128,42 @@ def save(self, *args, **kwargs): prev_lu.save() super(Lineup, self).save(*args, **kwargs) - class Meta: - ordering = ['team__name', '-active_from'] - unique_together = ('team', 'active_from') - class LineupPlayer(models.Model): player = models.ForeignKey(Player, on_delete=models.CASCADE) - role = models.ForeignKey(PlayerRole, on_delete=models.CASCADE, null=True, blank=True) lineup = models.ForeignKey(Lineup, on_delete=models.CASCADE) + class Meta: + abstract = True + def __str__(self): - if self.role: - return f'{self.player.ingame_name} ({self.role.name})' return f'{self.player.ingame_name} @ {self.lineup.team.name}' class Tournament(models.Model): name = models.CharField(max_length=255) name_alt = models.CharField(max_length=255, null=True, blank=True) - name_hltv = models.CharField(max_length=255, null=True, blank=True) - name_99dmg = models.CharField(max_length=255, null=True, blank=True) - esea_bracket_id = models.IntegerField(null=True, blank=True) - esea_bracket_team_ids = models.CharField(max_length=255, null=True, blank=True, help_text='Comma Separated') - match_set: QuerySet['Match'] + class Meta: + abstract = True + ordering = ['name'] + + # this will not actually exist, as the Tournament class is abstract + # it's just here for type checking + if TYPE_CHECKING: + match_set: QuerySet['Match'] # mappool = models.ManyToManyField(Map) def __str__(self): return self.name - class Meta: - ordering = ['name'] - class Map(models.Model): name = models.CharField(max_length=255) - cs_name = models.CharField(max_length=255, default='de_') + + class Meta: + abstract = True def __str__(self): return self.name @@ -172,8 +172,6 @@ def __str__(self): class Match(models.Model): tournament = models.ForeignKey(Tournament, on_delete=models.CASCADE) slug = models.SlugField(unique=True, allow_unicode=False, null=True, blank=True, max_length=255) - lineup_a = models.ForeignKey(Lineup, on_delete=models.CASCADE, related_name='matches_as_lineup_a_set', null=True, blank=True) - lineup_b = models.ForeignKey(Lineup, on_delete=models.CASCADE, related_name='matches_as_lineup_b_set', null=True, blank=True) bestof = models.IntegerField(choices=( (1, 'BO1'), (2, 'BO2'), @@ -190,26 +188,15 @@ class Match(models.Model): (2, 'Defwin in favour of team B'), ) ) - hltv_match_id = models.CharField(max_length=20, null=True, blank=True, help_text='For HLTV Livescore during match') - esea_match_id = models.CharField(max_length=255, null=True, blank=True) enable_tweet = models.BooleanField(default=True) last_tweet = models.DateTimeField(null=True, blank=True) last_tweet_id = models.CharField(max_length=255, null=True, blank=True) - enable_99dmg = models.BooleanField(default=False) - enable_hltv = models.BooleanField(default=True) matchmap_set: QuerySet['MatchMap'] class Meta: + abstract = True ordering = ['-first_map_at'] - verbose_name_plural = "matches" - - def __str__(self) -> str: - if self.lineup_a and self.lineup_b: - return f'{self.lineup_a.team.name} vs {self.lineup_b.team.name}' - elif self.lineup_a and not self.lineup_b: - return f'{self.lineup_a.team.name}' - return 'TBA vs TBA' def get_first_matchmap(self) -> 'MatchMap | None': return self.matchmap_set.order_by('starting_at').first() @@ -235,16 +222,34 @@ def has_ended(self) -> bool: if last_map: if last_map.has_ended(): return True - # if last_map.starting_at - team_a, team_b = self.get_overall_score() - if team_a > team_b or team_b > team_a: - return True + else: + raise ValueError(f"No last map found for Match {self}") return False def is_upcoming(self) -> bool | None: if self.first_map_at: return self.first_map_at > timezone.now() + def get_absolute_url(self) -> str: + return reverse('match_details', kwargs={'slug': self.slug}) + + +class OneOnOneMatch(Match): + lineup_a = models.ForeignKey(Lineup, on_delete=models.CASCADE, related_name='matches_as_lineup_a_set', null=True, blank=True) + lineup_b = models.ForeignKey(Lineup, on_delete=models.CASCADE, related_name='matches_as_lineup_b_set', null=True, blank=True) + + matchmap_set: QuerySet['OneOnOneMatchMap'] + + class Meta(Match.Meta): + abstract = True + + def __str__(self) -> str: + if self.lineup_a and self.lineup_b: + return f'{self.lineup_a.team.name} vs {self.lineup_b.team.name}' + elif self.lineup_a and not self.lineup_b: + return f'{self.lineup_a.team.name}' + return 'TBA vs TBA' + def get_overall_score(self) -> tuple[int, int]: lineup_a_mapwins = 0 lineup_b_mapwins = 0 @@ -274,7 +279,9 @@ def is_draw(self) -> bool: def save(self, *args, **kwargs): similar_matches_in_same_tournament = self.tournament.match_set.filter( - lineup_a=self.lineup_a, lineup_b=self.lineup_b + lineup_a=self.lineup_a, + lineup_b=self.lineup_b, + first_map_at=self.first_map_at ) if similar_matches_in_same_tournament.exclude(pk=self.pk).exists(): try: @@ -286,94 +293,73 @@ def save(self, *args, **kwargs): self.slug = slugify(f"{self.tournament.name}-{self}-{idx}") else: self.slug = slugify(f"{self.tournament.name}-{self}") - existing_slugs = Match.objects.filter(slug=self.slug).exclude(pk=self.pk) + existing_slugs = self.__class__.objects.filter(slug=self.slug).exclude(pk=self.pk) if existing_slugs.exists(): self.slug = slugify(f"id-{self.pk}") super(Match, self).save(*args, **kwargs) - def get_absolute_url(self) -> str: - return reverse('match_details', kwargs={'slug': self.slug}) - - def get_livescore_url(self, request): - if self.hltv_match_id: - url = reverse('match_livescore-detail', kwargs={'pk': self.hltv_match_id}) - return request.build_absolute_uri(url) - - def update_hltv_livescore(self, request) -> None: - # Guard clause in case lineup_a is None - if not self.lineup_a: - return - - url = self.get_livescore_url(request=request) - if url: - response = requests.get(url=url, params={'format': 'json'}).json() - maps = response.get('maps', []) - for map_data in maps: - map_nr = map_data.get('map_nr') - mm_obj = self.matchmap_set.filter(map_nr=map_nr).first() - if mm_obj: - mm_obj.played_map = Map.objects.filter( - models.Q(name=map_data.get('map_name')) | - models.Q(cs_name=map_data.get('map_name')) - ).first() - score_a, score_b = map_data.get('score_a'), map_data.get('score_b') - swap_score = False - team_a_hltv_id = response.get('team_a_id') - if team_a_hltv_id != self.lineup_a.team.hltv_id: - swap_score = True - - if swap_score: - score_b, score_a = score_a, score_b - - mm_obj.rounds_won_team_a = score_a - mm_obj.rounds_won_team_b = score_b - mm_obj.save() - class MatchMap(models.Model): match = models.ForeignKey(Match, on_delete=models.CASCADE) - played_map = models.ForeignKey(Map, on_delete=models.CASCADE, null=True, blank=True) - rounds_won_team_a = models.IntegerField(default=0) - rounds_won_team_b = models.IntegerField(default=0) + map = models.ForeignKey(Map, on_delete=models.CASCADE, null=True, blank=True) starting_at = models.DateTimeField() - delay_minutes = models.IntegerField(default=0) map_nr = models.IntegerField(null=True) - map_pick_of = models.ForeignKey(Lineup, null=True, blank=True, on_delete=models.CASCADE) unplayed = models.BooleanField(default=False) # defwin_reason = models.CharField(max_length=255, null=True, blank=True) # defwin = models.BooleanField(default=False) cancelled = models.BooleanField(default=False) - def get_prev_map(self) -> 'MatchMap | None': - return self.match.matchmap_set.filter(map_nr__lt=self.map_nr).order_by('map_nr').last() + class Meta: + abstract = True + ordering = ['starting_at'] + + def get_prev_map(self) -> 'Self | None': + # Filter using self.__class__ to ensure we are working with the subclass + return self.__class__.objects.filter( + match=self.match, + map_nr__lt=self.map_nr + ).order_by('map_nr').last() - def get_next_map(self) -> 'MatchMap | None': - return self.match.matchmap_set.filter(map_nr__gt=self.map_nr).order_by('map_nr').first() + def get_next_map(self) -> 'Self | None': + return self.__class__.objects.filter( + match=self.match, + map_nr_gt=self.map_nr + ).order_by('map_nr').first() + @abstractmethod def has_ended(self) -> bool: - return (self.rounds_won_team_a >= 13 or self.rounds_won_team_b >= 13) and abs(self.rounds_won_team_a - self.rounds_won_team_b) >= 2 + """Has the MatchMap ended?""" - def is_live(self): + def is_live(self) -> bool: prev = self.get_prev_map() if prev and prev.is_live(): return False - has_ended = self.has_ended() - if has_ended: + if self.has_ended(): return False calc_end = self.starting_at + timezone.timedelta(minutes=100) return self.starting_at < timezone.now() < calc_end + def __str__(self) -> str: + return f'{self.match} - {self.starting_at.date()} Map #{self.map_nr} (ID = {self.pk if self.pk else "-"})' + + +class OneOnOneMatchMap(MatchMap): + match = models.ForeignKey(OneOnOneMatch, on_delete=models.CASCADE) + rounds_won_team_a = models.IntegerField(default=0) + rounds_won_team_b = models.IntegerField(default=0) + map_pick_of = models.ForeignKey(Lineup, null=True, blank=True, on_delete=models.CASCADE) + + class Meta(MatchMap.Meta): + abstract = True + def team_a_won(self) -> bool: - return (self.rounds_won_team_a > self.rounds_won_team_b) and (self.rounds_won_team_a >= 13 or self.rounds_won_team_b >= 13) + return (self.rounds_won_team_a > self.rounds_won_team_b) and self.has_ended() def is_draw(self) -> bool: - return self.rounds_won_team_a == self.rounds_won_team_b + return self.rounds_won_team_a == self.rounds_won_team_b and self.has_ended() def team_b_won(self) -> bool: - return (self.rounds_won_team_a < self.rounds_won_team_b) and (self.rounds_won_team_a >= 13 or self.rounds_won_team_b >= 13) - - def __str__(self): - return f'{self.match} - {self.starting_at.date()} Map #{self.map_nr} (ID = {self.pk if self.pk else "-"})' + return (self.rounds_won_team_a < self.rounds_won_team_b) and self.has_ended() def send_tweet(self, prev_instance=None, interval=180.) -> None: # Guard clause in case either lineup_a or lineup_b are None @@ -390,7 +376,7 @@ def send_tweet(self, prev_instance=None, interval=180.) -> None: 'score_a': self.rounds_won_team_a, 'score_b': self.rounds_won_team_b, 'map_nr': self.map_nr, - 'map_name': self.played_map.name if self.played_map else "-", + 'map_name': self.map.name if self.map else "-", 'tournament': self.match.tournament.name, 'slug': self.match.get_absolute_url() } @@ -427,7 +413,7 @@ def send_tweet(self, prev_instance=None, interval=180.) -> None: def save(self, *args, **kwargs): prev_instance = None if self.pk: - prev_instance = MatchMap.objects.get(pk=self.pk) + prev_instance = type(self).objects.get(pk=self.pk) super(MatchMap, self).save(*args, **kwargs) first_matchmap = self.match.get_first_matchmap() @@ -436,12 +422,9 @@ def save(self, *args, **kwargs): self.match.save() self.send_tweet(prev_instance=prev_instance) - class Meta: - ordering = ['starting_at'] - class ExternalLink(models.Model): - match = models.ForeignKey(Match, on_delete=models.CASCADE) + match = models.ForeignKey("CsMatch", on_delete=models.CASCADE) link_type = models.CharField(max_length=255, choices=( ('hltv_match', 'HLTV'), ('esea_match', 'ESEA Match'), @@ -471,7 +454,7 @@ class Meta: ordering = ['match', 'link_flag', 'link_type'] -class CSGOSiteSetting(models.Model): +class SiteSetting(models.Model): site = models.ForeignKey(Site, on_delete=models.CASCADE) main_team = models.ForeignKey('csgomatches.Team', on_delete=models.CASCADE, related_name='main_team_settings') second_team = models.ForeignKey('csgomatches.Team', on_delete=models.CASCADE, related_name='sec_team_settings') diff --git a/csgomatches/templates/csgomatches/base/base.html b/csgomatches/templates/csgomatches/base/base.html index 149c6ed..b8272ea 100644 --- a/csgomatches/templates/csgomatches/base/base.html +++ b/csgomatches/templates/csgomatches/base/base.html @@ -306,7 +306,7 @@ border: 1px solid #ffc000; } - .played_map { + .map { white-space: nowrap; font-weight: 500; color: white; @@ -691,10 +691,10 @@

{% block content_header_h1 %}Wann spiel {% if statistics %}

Fun-Facts:


{% endif %} @@ -703,7 +703,7 @@

{% block content_header_h1 %}Wann spiel Export kommende Matches [iCal, iCal CS, JSON] [API] - + diff --git a/csgomatches/templates/csgomatches/blocks/box_matchmap.html b/csgomatches/templates/csgomatches/blocks/box_matchmap.html index ec013b7..b38f966 100644 --- a/csgomatches/templates/csgomatches/blocks/box_matchmap.html +++ b/csgomatches/templates/csgomatches/blocks/box_matchmap.html @@ -23,10 +23,10 @@ {% endif %} {% endcomment %} {% endif %} - {{ mm.played_map|default_if_none:"-" }} + {{ mm.map|default_if_none:"-" }}
{% if mm.map_pick_of %} ({{ mm.map_pick_of }})
{% endif %} - \ No newline at end of file + diff --git a/csgomatches/templates/csgomatches/blocks/match.html b/csgomatches/templates/csgomatches/blocks/csmatch.html similarity index 98% rename from csgomatches/templates/csgomatches/blocks/match.html rename to csgomatches/templates/csgomatches/blocks/csmatch.html index cbab549..19d00ba 100644 --- a/csgomatches/templates/csgomatches/blocks/match.html +++ b/csgomatches/templates/csgomatches/blocks/csmatch.html @@ -29,7 +29,7 @@

{{ link.get_link_type_display }} {% endfor %} {% if user.is_staff %} - + {% if match.enable_tweet %} 🐦 {% else %} diff --git a/csgomatches/templates/csgomatches/match_archive_year.html b/csgomatches/templates/csgomatches/csmatch_archive_year.html similarity index 91% rename from csgomatches/templates/csgomatches/match_archive_year.html rename to csgomatches/templates/csgomatches/csmatch_archive_year.html index 785e027..226c300 100644 --- a/csgomatches/templates/csgomatches/match_archive_year.html +++ b/csgomatches/templates/csgomatches/csmatch_archive_year.html @@ -10,7 +10,7 @@

Match-Archiv {{ year.year }}

{% for match in object_list %} - {% include 'csgomatches/blocks/match.html' %} + {% include 'csgomatches/blocks/csmatch.html' %} {% empty %}

Keine Matches im Archiv von {{ year.year }} gefunden.

@@ -26,4 +26,4 @@

Match-Archiv {{ year.year }}

  • {{ date|date:"Y" }}
  • {% endfor %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/csgomatches/templates/csgomatches/match_detail.html b/csgomatches/templates/csgomatches/csmatch_detail.html similarity index 59% rename from csgomatches/templates/csgomatches/match_detail.html rename to csgomatches/templates/csgomatches/csmatch_detail.html index 0cc7683..a365f18 100644 --- a/csgomatches/templates/csgomatches/match_detail.html +++ b/csgomatches/templates/csgomatches/csmatch_detail.html @@ -3,15 +3,15 @@ {% load static %} {% block page_title %} - {{ match }} - {{ match.first_map_at|date:"SHORT_DATE_FORMAT" }} - {{ match.tournament.name }} - Matchdetails - Wann spielt BIG? + {{ csmatch }} - {{ csmatch.first_map_at|date:"SHORT_DATE_FORMAT" }} - {{ csmatch.tournament.name }} - Matchdetails - Wann spielt BIG? {% endblock %} {% block meta %} - {% if match.is_live %} + {% if csmatch.is_live %} {% if update_seconds %} {% endif %} - {% elif match.is_upcoming %} + {% elif csmatch.is_upcoming %} {% endif %} {% endblock %} @@ -23,51 +23,51 @@ {% block content %}

    - {{ match.first_map_at|date:'d.m.y H:i' }} - {{ match.lineup_a.team }} - {% if match.lineup_b %} - {{ score.0 }}: - {{ score.1 }} - {{ match.lineup_b.team }} + {{ csmatch.first_map_at|date:'d.m.y H:i' }} + {{ csmatch.lineup_a.team }} + {% if csmatch.lineup_b %} + {{ score.0 }}: + {{ score.1 }} + {{ csmatch.lineup_b.team }} {% else %} - at {{ match.tournament.name }} + at {{ csmatch.tournament.name }} {% endif %} - {{ match.tournament.name }} + {{ csmatch.tournament.name }}

    -

    {{ match.tournament.name }}

    +

    {{ csmatch.tournament.name }}

    {% include 'csgomatches/blocks/box_matchrow_detail.html' %} - {% if match.lineup_a and match.lineup_a.lineupplayer_set.exists %} + {% if csmatch.lineup_a and csmatch.lineup_a.lineupplayer_set.exists %}
    -

    {% if match.lineup_a.game %}{{ match.lineup_a.game.name }}-{% endif %}Lineup {{ match.lineup_a.team.name }}

    - {% for lp in match.lineup_a.lineupplayer_set.all %} +

    {% if csmatch.lineup_a.game %}{{ csmatch.lineup_a.game.name }}-{% endif %}Lineup {{ csmatch.lineup_a.team.name }}

    + {% for lp in csmatch.lineup_a.lineupplayer_set.all %} {{ lp.player.ingame_name }} {% endfor %}
    {% endif %} - {% if match.lineup_b and match.lineup_b.lineupplayer_set.exists %} + {% if csmatch.lineup_b and csmatch.lineup_b.lineupplayer_set.exists %}
    -

    Lineup {{ match.lineup_b.team.name }}

    - {% for lp in match.lineup_b.lineupplayer_set.all %} +

    Lineup {{ csmatch.lineup_b.team.name }}

    + {% for lp in csmatch.lineup_b.lineupplayer_set.all %} {{ lp.player.ingame_name }} {% endfor %}
    {% endif %} - {% with matchlinks=match.externallink_set.visible %} + {% with matchlinks=csmatch.externallink_set.visible %} {% if matchlinks.exists %}