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 @@ Fun-Facts: Keine Matches im Archiv von {{ year.year }} gefunden.
Update-Intervall:
{% for c in update_choices %}
- {% if c == 0 %}Aus{% else %}{{ c }}s{% endif %}
+ {% if c == 0 %}Aus{% else %}{{ c }}s{% endif %}
{% if not forloop.last %} | {% endif %}
{% endfor %}
BIG disband? ;-) Keine kommenden Matches eingetragen.{% block content_header_h1 %}Wann spiel
{% if statistics %}
-
{% 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 }}
Match-Archiv {{ year.year }}
- {{ 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 %}
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 %}
Links:
- {% for link in match.externallink_set.visible %}
+ {% for link in csmatch.externallink_set.visible %}
{% ifchanged link.link_flag %}
Links: