From 3d6aa8282a931ce8d93fae94ee3282d7d62ac578 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 14 Feb 2025 17:48:34 +0100 Subject: [PATCH] Adds control over when to save ModelLogs --- api/views.py | 3 + sync/models/data_access.py | 29 ++++--- sync/models/model_log.py | 4 - sync/signals.py | 85 +++++++++++++------ sync/views.py | 2 +- tournaments/admin.py | 2 +- .../0111_customuser_should_synchronize.py | 18 ++++ tournaments/models/custom_user.py | 3 +- 8 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 tournaments/migrations/0111_customuser_should_synchronize.py diff --git a/api/views.py b/api/views.py index f2dea6c..de2f1d5 100644 --- a/api/views.py +++ b/api/views.py @@ -46,6 +46,7 @@ class CustomAuthToken(APIView): if user: # user.device_id = device_id # user.save() + # self.create_or_update_device(user, device_id) # token, created = Token.objects.get_or_create(user=user) # return Response({'token': token.key}) @@ -90,6 +91,8 @@ class Logout(APIView): request.user.device_id = None request.user.save() + Device.objects.filter(id=device_id).delete() + return Response(status=status.HTTP_200_OK) @api_view(['GET']) diff --git a/sync/models/data_access.py b/sync/models/data_access.py index 59f7a1e..94f4c8e 100644 --- a/sync/models/data_access.py +++ b/sync/models/data_access.py @@ -37,20 +37,21 @@ class DataAccess(BaseModel): if isinstance(obj, SideStoreModel): store_id = obj.store_id - existing_log = ModelLog.objects.filter(users__in=users, model_id=self.model_id, operation=operation).first() - if existing_log: - existing_log.date = timezone.now() - existing_log.model_operation = operation - existing_log.save() - else: - model_log = ModelLog.objects.create( - model_id=self.model_id, - model_name=self.model_name, - operation=operation, - date=timezone.now(), - store_id=store_id - ) - model_log.users.set(users) + for user in users: + existing_log = ModelLog.objects.filter(user=user, model_id=self.model_id, operation=operation).first() + if existing_log: + existing_log.date = timezone.now() + existing_log.model_operation = operation + existing_log.save() + else: + ModelLog.objects.create( + user=user, + model_id=self.model_id, + model_name=self.model_name, + operation=operation, + date=timezone.now(), + store_id=store_id + ) except ObjectDoesNotExist: pass else: diff --git a/sync/models/model_log.py b/sync/models/model_log.py index 5f4ac74..31ed0a9 100644 --- a/sync/models/model_log.py +++ b/sync/models/model_log.py @@ -1,10 +1,6 @@ from django.db import models from django.conf import settings -import uuid -from tkinter.constants import CASCADE - - class ModelOperation(models.TextChoices): POST = 'POST', 'POST' PUT = 'PUT', 'PUT' diff --git a/sync/signals.py b/sync/signals.py index 8dfc99b..ef05eb7 100644 --- a/sync/signals.py +++ b/sync/signals.py @@ -2,7 +2,7 @@ from django.db.models.signals import pre_save, post_save, pre_delete, post_delet from django.db import models, transaction from django.dispatch import receiver -from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreModel +from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreModel, Device from django.contrib.auth import get_user_model from django.utils import timezone @@ -10,14 +10,26 @@ from .ws_sender import websocket_sender User = get_user_model() +@receiver(post_save, sender=Device) +def device_created(sender, instance, **kwargs): + if not instance.user: + return + evaluate_if_user_should_sync(instance.user) + +@receiver(pre_delete, sender=Device) +def device_pre_delete(sender, instance, **kwargs): + instance._user = instance.user if instance.user else None + +@receiver(post_delete, sender=Device) +def device_post_delete(sender, instance, **kwargs): + if not hasattr(instance, '_user') or not instance._user: + return + evaluate_if_user_should_sync(instance._user) + @receiver(pre_save) def presave_handler(sender, instance, **kwargs): synchronization_prepare(sender, instance, **kwargs) -# @receiver(pre_delete) -# def predelete_handler(sender, instance, **kwargs): -# synchronization_prepare(sender, instance, False, **kwargs) - def synchronization_prepare(sender, instance, **kwargs): signal = kwargs.get('signal') @@ -122,14 +134,15 @@ def save_model_log(users, model_operation, model_name, model_id, store_id, devic with transaction.atomic(): for user in users: - model_log = ModelLog() - model_log.user = user - model_log.operation = model_operation - model_log.model_name = model_name - model_log.model_id = model_id - model_log.store_id = store_id - model_log.device_id = device_id - model_log.save() + if user.should_synchronize: + model_log = ModelLog() + model_log.user = user + model_log.operation = model_operation + model_log.model_name = model_name + model_log.model_id = model_id + model_log.store_id = store_id + model_log.device_id = device_id + model_log.save() # print(f'ML users = {len(users)}') # existing_log = ModelLog.objects.filter(users__in=users, model_id=model_id, operation=model_operation).first() @@ -177,18 +190,6 @@ def detect_foreign_key_changes(sender, instance, device_id): 'new_value': new_value }) - # for data_access in data_access_list: - # if old_value: - # model_name = old_value.__class__.__name__ - # save_model_log(data_access.concerned_users(), 'REVOKE_ACCESS', model_name, old_value.id, old_value.get_store_id(), device_id) - # if new_value: - # model_name = new_value.__class__.__name__ - # save_model_log(data_access.concerned_users(), 'GRANT_ACCESS', model_name, new_value.id, new_value.get_store_id(), device_id) - - # # REVOKE access for old_value and GRANT new_value - # print(f"Foreign key changed in {sender.__name__}: " - # f"{field.name} from {old_value} to {new_value}") - def process_foreign_key_changes(sender, instance, device_id, **kwargs): if hasattr(instance, '_fk_changes'): for change in instance._fk_changes: @@ -206,6 +207,8 @@ def process_foreign_key_changes(sender, instance, device_id, **kwargs): @receiver(post_delete) def delete_data_access_if_necessary(sender, instance, **kwargs): + if not isinstance(instance, BaseModel): + return if hasattr(instance, 'id'): DataAccess.objects.filter(model_id=instance.id).delete() @@ -225,10 +228,24 @@ def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs): for user_id in pk_set: websocket_sender.send_user_message(user_id, device_id) + for user in users: + evaluate_if_user_should_sync(user) + +@receiver(post_save, sender=DataAccess) +def data_access_post_save(sender, instance, **kwargs): + if instance.related_user: + evaluate_if_user_should_sync(instance.related_user) @receiver(pre_delete, sender=DataAccess) def revoke_access_after_delete(sender, instance, **kwargs): instance.create_revoke_access_log() + instance._user = instance.related_user + +@receiver(post_delete, sender=DataAccess) +def data_access_post_delete(sender, instance, **kwargs): + if not hasattr(instance, '_user') or not instance._user: + return + evaluate_if_user_should_sync(instance._user) def related_users(instance): users = set() @@ -259,7 +276,23 @@ def related_data_access(instance): return instances_related_data_access(instance, related_instances) def instances_related_data_access(instance, related_instances): - # related_instances = instance.related_instances() related_ids = [ri.id for ri in related_instances] related_ids.append(instance.id) return DataAccess.objects.filter(model_id__in=related_ids) + +def evaluate_if_user_should_sync(user): + should_synchronize = False + if user.devices.count() > 1: + should_synchronize = True + elif DataAccess.objects.filter( + models.Q(shared_with=user) | + models.Q(related_user=user) + ).count() > 0: + should_synchronize = True + + print(f'should_synchronize = {should_synchronize}') + + with transaction.atomic(): + user.should_synchronize = should_synchronize + # if we go from True to False we might want to delete ModelLog once the last device has synchronized + user.save() diff --git a/sync/views.py b/sync/views.py index bc86e77..f97ee2e 100644 --- a/sync/views.py +++ b/sync/views.py @@ -160,7 +160,7 @@ class SynchronizationApi(HierarchyApiView): except Exception as e: response_status = status.HTTP_400_BAD_REQUEST - print(f'other exception: {str(e)}') + print(f'other exception: {str(e)}, type: {type(e)}, args: {e.args}, traceback: {e.__traceback__}') message = str(e) results.append({ diff --git a/tournaments/admin.py b/tournaments/admin.py index 217e135..70ef07c 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -22,7 +22,7 @@ class CustomUserAdmin(UserAdmin): 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_display_format', 'summons_display_entry_fee', 'summons_use_full_custom_message', 'match_formats_default_duration', 'bracket_match_format_preference', 'group_stage_match_format_preference', - 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'groups', 'origin', 'agents' + 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'groups', 'origin', 'agents', 'should_synchronize' ]}), ] add_fieldsets = [ diff --git a/tournaments/migrations/0111_customuser_should_synchronize.py b/tournaments/migrations/0111_customuser_should_synchronize.py new file mode 100644 index 0000000..b9a778f --- /dev/null +++ b/tournaments/migrations/0111_customuser_should_synchronize.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2025-02-14 16:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tournaments', '0110_remove_failedapicall_creation_date_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='should_synchronize', + field=models.BooleanField(default=False), + ), + ] diff --git a/tournaments/models/custom_user.py b/tournaments/models/custom_user.py index 1b0752e..8369a12 100644 --- a/tournaments/models/custom_user.py +++ b/tournaments/models/custom_user.py @@ -37,6 +37,7 @@ class CustomUser(AbstractUser): loser_bracket_mode = models.IntegerField(default=0) origin = models.IntegerField(default=enums.UserOrigin.ADMIN, choices=enums.UserOrigin.choices, null=True, blank=True) + should_synchronize = models.BooleanField(default=False) ### ### ### ### ### ### ### ### ### ### ### WARNING ### ### ### ### ### ### ### ### ### ### ### WARNING : Any added field MUST be inserted in the method below: fields_for_update() ### @@ -49,7 +50,7 @@ class CustomUser(AbstractUser): 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_display_format', 'summons_display_entry_fee', 'summons_use_full_custom_message', 'match_formats_default_duration', 'bracket_match_format_preference', - 'group_stage_match_format_preference', 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'origin', 'agents'] + 'group_stage_match_format_preference', 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'origin', 'agents', 'should_synchronize'] def __str__(self): return self.username