From 0829a1e2460e26d27e3c83b414fa475482ef1404 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 8 Nov 2024 19:11:05 +0100 Subject: [PATCH] data access first working version --- api/views.py | 32 ++++++++++++++++--- ...ent_model_id_modellog_parent_model_name.py | 23 +++++++++++++ ...cess_shared_with_dataaccess_shared_with.py | 23 +++++++++++++ ...cess_shared_with_dataaccess_shared_with.py | 25 +++++++++++++++ .../0096_alter_modellog_operation_and_more.py | 28 ++++++++++++++++ tournaments/models/base.py | 3 ++ tournaments/models/data_access.py | 14 +++++--- tournaments/models/date_interval.py | 4 +++ tournaments/models/enums.py | 2 ++ tournaments/models/event.py | 6 ++++ tournaments/models/group_stage.py | 9 ++++++ tournaments/models/match.py | 9 ++++++ tournaments/models/model_log.py | 4 +-- tournaments/models/player_registration.py | 4 +++ tournaments/models/round.py | 9 ++++++ tournaments/models/team_registration.py | 8 +++++ tournaments/models/tournament.py | 13 ++++++++ tournaments/signals.py | 30 +++++++++++++++-- 18 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 tournaments/migrations/0093_modellog_parent_model_id_modellog_parent_model_name.py create mode 100644 tournaments/migrations/0094_remove_dataaccess_shared_with_dataaccess_shared_with.py create mode 100644 tournaments/migrations/0095_remove_dataaccess_shared_with_dataaccess_shared_with.py create mode 100644 tournaments/migrations/0096_alter_modellog_operation_and_more.py diff --git a/api/views.py b/api/views.py index b8ed962..97d3755 100644 --- a/api/views.py +++ b/api/views.py @@ -138,25 +138,29 @@ class DataApi(APIView): updates = defaultdict(dict) deletions = defaultdict(list) + print(f'>>> log count = {len(logs)}') + for log in logs: model = apps.get_model(app_label='tournaments', model_name=log.model_name) if log.operation in ['POST', 'PUT']: try: data = self.get_data(model, log) - - # instance = model.objects.get(id=log.model_id) - # serializer_class = build_serializer_class(log.model_name) - # serializer = serializer_class(instance) updates[log.model_name][log.model_id] = data except model.DoesNotExist: # If the instance doesn't exist, it might have been deleted after this log was created pass elif log.operation == 'DELETE': deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) - elif log.operation == 'SHARE': + elif log.operation == 'GRANT_ACCESS': data = self.get_data(model, log) updates[log.model_name][log.model_id] = data + instance = model.objects.get(id=log.model_id) + self.add_children_recursively(instance, updates) + elif log.operation == 'REVOKE_ACCESS': + print(f'revoke access {log.model_id} - {log.store_id}') + # data = self.get_data(model, log) + deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) # Convert updates dict to list for each model for model_name in updates: @@ -176,6 +180,24 @@ class DataApi(APIView): return Response(response_data, status=status.HTTP_200_OK) + def add_children_recursively(self, instance, updates): + """ + Recursively add all children of an instance to the updates dictionary. + """ + child_models = instance.get_child_models() + + for child_model_name, related_name in child_models.items(): + child_model = apps.get_model(app_label='tournaments', model_name=child_model_name) + children = getattr(instance, related_name).all() + + for child in children: + serializer_class = build_serializer_class(child_model_name) + serializer = serializer_class(child) + updates[child_model_name][child.id] = serializer.data + + # Recursive call for each child + self.add_children_recursively(child, updates) + def get_data(self, model, log): instance = model.objects.get(id=log.model_id) serializer_class = build_serializer_class(log.model_name) diff --git a/tournaments/migrations/0093_modellog_parent_model_id_modellog_parent_model_name.py b/tournaments/migrations/0093_modellog_parent_model_id_modellog_parent_model_name.py new file mode 100644 index 0000000..c66282c --- /dev/null +++ b/tournaments/migrations/0093_modellog_parent_model_id_modellog_parent_model_name.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1 on 2024-11-08 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0092_dataaccess'), + ] + + operations = [ + migrations.AddField( + model_name='modellog', + name='parent_model_id', + field=models.UUIDField(null=True), + ), + migrations.AddField( + model_name='modellog', + name='parent_model_name', + field=models.CharField(max_length=50, null=True), + ), + ] diff --git a/tournaments/migrations/0094_remove_dataaccess_shared_with_dataaccess_shared_with.py b/tournaments/migrations/0094_remove_dataaccess_shared_with_dataaccess_shared_with.py new file mode 100644 index 0000000..dfc7ffe --- /dev/null +++ b/tournaments/migrations/0094_remove_dataaccess_shared_with_dataaccess_shared_with.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1 on 2024-11-08 16:16 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0093_modellog_parent_model_id_modellog_parent_model_name'), + ] + + operations = [ + migrations.RemoveField( + model_name='dataaccess', + name='shared_with', + ), + migrations.AddField( + model_name='dataaccess', + name='shared_with', + field=models.ManyToManyField(related_name='shared_data', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/tournaments/migrations/0095_remove_dataaccess_shared_with_dataaccess_shared_with.py b/tournaments/migrations/0095_remove_dataaccess_shared_with_dataaccess_shared_with.py new file mode 100644 index 0000000..5ec1a34 --- /dev/null +++ b/tournaments/migrations/0095_remove_dataaccess_shared_with_dataaccess_shared_with.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1 on 2024-11-08 16:45 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0094_remove_dataaccess_shared_with_dataaccess_shared_with'), + ] + + operations = [ + migrations.RemoveField( + model_name='dataaccess', + name='shared_with', + ), + migrations.AddField( + model_name='dataaccess', + name='shared_with', + field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='shared_data', to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/tournaments/migrations/0096_alter_modellog_operation_and_more.py b/tournaments/migrations/0096_alter_modellog_operation_and_more.py new file mode 100644 index 0000000..5689fbb --- /dev/null +++ b/tournaments/migrations/0096_alter_modellog_operation_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.1 on 2024-11-08 16:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0095_remove_dataaccess_shared_with_dataaccess_shared_with'), + ] + + operations = [ + migrations.AlterField( + model_name='modellog', + name='operation', + field=models.CharField(choices=[('POST', 'POST'), ('PUT', 'PUT'), ('DELETE', 'DELETE'), ('GRANT_ACCESS', 'GRANT_ACCESS'), ('REVOKE_ACCESS', 'REVOKE_ACCESS')], max_length=50), + ), + migrations.AlterField( + model_name='modellog', + name='parent_model_id', + field=models.UUIDField(blank=True, null=True), + ), + migrations.AlterField( + model_name='modellog', + name='parent_model_name', + field=models.CharField(blank=True, max_length=50, null=True), + ), + ] diff --git a/tournaments/models/base.py b/tournaments/models/base.py index 563d1c8..a506f08 100644 --- a/tournaments/models/base.py +++ b/tournaments/models/base.py @@ -12,6 +12,9 @@ class BaseModel(models.Model): """Override in child models to provide parent reference""" return None, None + def get_child_models(self): + return {} + class SideStoreModel(BaseModel): store_id = models.CharField(max_length=100) diff --git a/tournaments/models/data_access.py b/tournaments/models/data_access.py index 37f95be..ed265f5 100644 --- a/tournaments/models/data_access.py +++ b/tournaments/models/data_access.py @@ -18,11 +18,17 @@ class DataAccess(models.Model): super().save(*args, **kwargs) if is_new: - self.create_initial_sync_logs() + self.create_initial_log() - def create_initial_sync_logs(self): + # def delete(self, *args, **kwargs): + # print('>>> delete data access') + # # Create deletion sync logs before deleting the access + # self.create_deletion_log() + # return super().delete(*args, **kwargs) - model_class = apps.get_model(self.model_name) + def create_initial_log(self): + + model_class = apps.get_model('tournaments', self.model_name) obj = model_class.objects.get(id=self.model_id) parent_model, parent_id = obj.get_parent_reference() store_id = None @@ -33,7 +39,7 @@ class DataAccess(models.Model): user=self.shared_with, # The user receiving access model_id=self.model_id, model_name=self.model_name, - operation='SHARE', # New operation type + operation='GRANT_ACCESS', date=timezone.now(), store_id=store_id, parent_model_id=parent_id, diff --git a/tournaments/models/date_interval.py b/tournaments/models/date_interval.py index 9763445..bf6871d 100644 --- a/tournaments/models/date_interval.py +++ b/tournaments/models/date_interval.py @@ -8,3 +8,7 @@ class DateInterval(BaseModel): court_index = models.IntegerField() start_date = models.DateTimeField() end_date = models.DateTimeField() + + # Data Access + def get_parent_reference(self): + return 'Event', self.event.id diff --git a/tournaments/models/enums.py b/tournaments/models/enums.py index 71c0d82..71e62a8 100644 --- a/tournaments/models/enums.py +++ b/tournaments/models/enums.py @@ -61,3 +61,5 @@ class ModelOperation(models.TextChoices): POST = 'POST', 'POST' PUT = 'PUT', 'PUT' DELETE = 'DELETE', 'DELETE' + GRANT_ACCESS = 'GRANT_ACCESS', 'GRANT_ACCESS' + REVOKE_ACCESS = 'REVOKE_ACCESS', 'REVOKE_ACCESS' diff --git a/tournaments/models/event.py b/tournaments/models/event.py index 2351354..f4e4341 100644 --- a/tournaments/models/event.py +++ b/tournaments/models/event.py @@ -12,6 +12,12 @@ class Event(BaseModel): tenup_id = models.CharField(max_length=20, null=True, blank=True) creator_full_name = models.CharField(max_length=200, null=True, blank=True) + # Data Access + def get_child_models(self): + return { + 'Tournament': 'tournament_set', + } + def __str__(self): return self.display_name() diff --git a/tournaments/models/group_stage.py b/tournaments/models/group_stage.py index 4af2608..91be198 100644 --- a/tournaments/models/group_stage.py +++ b/tournaments/models/group_stage.py @@ -15,6 +15,15 @@ class GroupStage(SideStoreModel): start_date = models.DateTimeField(null=True, blank=True) name = models.CharField(max_length=200, null=True, blank=True) + # Data Access + def get_parent_reference(self): + return 'Event', self.tournament.event.id + + def get_child_models(self): + return { + 'Match': 'match_set', + } + def __str__(self): return self.display_name() # return f"{self.tournament.display_name()} - {self.display_name()}" diff --git a/tournaments/models/match.py b/tournaments/models/match.py index 70bffa6..870c378 100644 --- a/tournaments/models/match.py +++ b/tournaments/models/match.py @@ -26,6 +26,15 @@ class Match(SideStoreModel): court_index = models.IntegerField(null=True, blank=True) confirmed = models.BooleanField(default=False) + # Data Access + def get_parent_reference(self): + return 'Event', self.tournament().event.id + + def get_child_models(self): + return { + 'TeamScore': 'team_scores', + } + def __str__(self): names = " / ".join(self.player_names()) return f"{self.stage_name()} #{self.index}: {names}" diff --git a/tournaments/models/model_log.py b/tournaments/models/model_log.py index b4d9ea7..46835dd 100644 --- a/tournaments/models/model_log.py +++ b/tournaments/models/model_log.py @@ -10,5 +10,5 @@ class ModelLog(models.Model): date = models.DateTimeField() model_name = models.CharField(max_length=50) store_id = models.CharField(max_length=200, blank=True, null=True) - parent_model_id = models.UUIDField(null=True) - parent_model_name = models.CharField(max_length=50, null=True) + parent_model_id = models.UUIDField(blank=True, null=True) + parent_model_name = models.CharField(max_length=50, blank=True, null=True) diff --git a/tournaments/models/player_registration.py b/tournaments/models/player_registration.py index 433c53e..869bb07 100644 --- a/tournaments/models/player_registration.py +++ b/tournaments/models/player_registration.py @@ -34,6 +34,10 @@ class PlayerRegistration(SideStoreModel): source = models.IntegerField(choices=PlayerDataSource.choices, null=True, blank=True) has_arrived = models.BooleanField(default=False) + # Data Access + def get_parent_reference(self): + return 'Event', self.team_registration.tournament.event.id + def __str__(self): return self.name() diff --git a/tournaments/models/round.py b/tournaments/models/round.py index f830239..de4756f 100644 --- a/tournaments/models/round.py +++ b/tournaments/models/round.py @@ -10,6 +10,15 @@ class Round(SideStoreModel): format = models.IntegerField(default=FederalMatchCategory.NINE_GAMES, choices=FederalMatchCategory.choices, null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True) + # Data Access + def get_parent_reference(self): + return 'Event', self.tournament.event.id + + def get_child_models(self): + return { + 'Match': 'match_set', + } + def __str__(self): if self.parent: return f"LB: {self.name()}" diff --git a/tournaments/models/team_registration.py b/tournaments/models/team_registration.py index 5d93414..8d48b89 100644 --- a/tournaments/models/team_registration.py +++ b/tournaments/models/team_registration.py @@ -30,6 +30,14 @@ class TeamRegistration(SideStoreModel): final_ranking = models.IntegerField(null=True, blank=True) points_earned = models.IntegerField(null=True, blank=True) + # Data Access + def get_parent_reference(self): + return 'Event', self.tournament.event.id + + def get_child_models(self): + return { + 'PlayerRegistration': 'playerregistration_set', + } def __str__(self): if self.name: diff --git a/tournaments/models/tournament.py b/tournaments/models/tournament.py index ba2fffc..1076bc7 100644 --- a/tournaments/models/tournament.py +++ b/tournaments/models/tournament.py @@ -60,6 +60,19 @@ class Tournament(BaseModel): hide_points_earned = models.BooleanField(default=False) publish_rankings = models.BooleanField(default=False) + def get_parent_reference(self): + return 'Event', self.event.id + + def get_child_models(self): + """ + Returns a dictionary of child model names and their related_names. + """ + return { + 'Round': 'round_set', + 'GroupStage': 'groupstage_set', + 'TeamRegistration': 'teamregistration_set' + } + def __str__(self): if self.name: return self.name diff --git a/tournaments/signals.py b/tournaments/signals.py index 5c5205f..100a5e9 100644 --- a/tournaments/signals.py +++ b/tournaments/signals.py @@ -1,10 +1,12 @@ import random import string -from django.db.models.signals import post_save +from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from django.conf import settings +from django.apps import apps +from django.utils import timezone -from .models import Club, FailedApiCall, CustomUser, Log +from .models import Club, FailedApiCall, CustomUser, Log, DataAccess, ModelLog import requests def generate_unique_code(): @@ -52,3 +54,27 @@ def send_discord_message(webhook_url, content): raise ValueError( f'Error sending message to Discord webhook: {response.status_code}, {response.text}' ) + +@receiver(pre_delete, sender=DataAccess) +def data_access_pre_delete(sender, instance, **kwargs): + print('>>> delete data access signal') + try: + model_class = apps.get_model('tournaments', instance.model_name) + obj = model_class.objects.get(id=instance.model_id) + parent_model, parent_id = obj.get_parent_reference() + store_id = None + if hasattr(obj, 'store_id'): + store_id = obj.store_id + + ModelLog.objects.create( + user=instance.shared_with, + model_id=instance.model_id, + model_name=instance.model_name, + operation='REVOKE_ACCESS', + date=timezone.now(), + store_id=store_id, + parent_model_id=parent_id, + parent_model_name=parent_model + ) + except Exception as e: + print(f"Error in data_access_pre_delete signal: {e}")