From e4beca4840252e6b349f1350fc4fb86d6bcdc418 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 28 May 2025 14:57:51 +0200 Subject: [PATCH] minor improvements + crash fix --- sync/models/base.py | 5 +++- sync/models/data_access.py | 1 + sync/signals.py | 57 +++++++++++++++++++++++--------------- sync/utils.py | 7 ++--- sync/views.py | 15 +++++----- 5 files changed, 48 insertions(+), 37 deletions(-) diff --git a/sync/models/base.py b/sync/models/base.py index de8b166..8668e9d 100644 --- a/sync/models/base.py +++ b/sync/models/base.py @@ -51,7 +51,10 @@ class BaseModel(models.Model): self.data_access_ids.append(str_id) def remove_data_access_relation(self, data_access): - self.data_access_ids.remove(str(data_access.id)) + try: + self.data_access_ids.remove(str(data_access.id)) + except ValueError: + pass def get_children_by_model(self): """ diff --git a/sync/models/data_access.py b/sync/models/data_access.py index de2c8d1..e4ba81d 100644 --- a/sync/models/data_access.py +++ b/sync/models/data_access.py @@ -79,6 +79,7 @@ class DataAccess(BaseModel): try: obj = model_class.objects.get(id=self.model_id) related_instance = obj.related_instances() + related_instance.append(obj) for instance in related_instance: if isinstance(instance, BaseModel): instance.remove_data_access_relation(self) diff --git a/sync/signals.py b/sync/signals.py index 33705d6..db9a839 100644 --- a/sync/signals.py +++ b/sync/signals.py @@ -17,28 +17,23 @@ User = get_user_model() @receiver([pre_save, pre_delete]) def presave_handler(sender, instance, **kwargs): - synchronization_prepare(sender, instance, **kwargs) -def synchronization_prepare(sender, instance, **kwargs): + # some other classes are excluded in settings_app.py: SYNC_APPS + if not isinstance(instance, (BaseModel, User)): + return - # print(f'*** synchronization_prepare for instance: {instance}') signal = kwargs.get('signal') - # avoid crash in manage.py createsuperuser + delete user in the admin if isinstance(instance, User) and (instance._state.db is None or signal == pre_delete): return - # some other classes are excluded in settings_app.py: SYNC_APPS - if not isinstance(instance, BaseModel) and not isinstance(instance, User): - return - users = related_users(instance) # print(f'* impacted users = {users}') related_users_registry.register(instance.id, users) # user_ids = [user.id for user in users] if signal == pre_save: - detect_foreign_key_changes(sender, instance) + detect_foreign_key_changes_for_shared_instances(sender, instance) @receiver([post_save, post_delete]) def synchronization_notifications(sender, instance, created=False, **kwargs): @@ -123,19 +118,35 @@ def save_model_log(users, model_operation, model_name, model_id, store_id): # print(f'>> creating Model Log for: {model_operation} {model_name}') + logs_to_create = [ + ModelLog( + user=user, + operation=model_operation, + model_name=model_name, + model_id=model_id, + store_id=store_id, + device_id=device_id + ) + for user in users + # if user.should_synchronize + ] + with transaction.atomic(): - for user in users: - # print(f' * {user.username}') - - # 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() + ModelLog.objects.bulk_create(logs_to_create) + + # with transaction.atomic(): + # for user in users: + # # print(f' * {user.username}') + + # # 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() @@ -157,7 +168,7 @@ def save_model_log(users, model_operation, model_name, model_id, store_id): # model_log.save() # model_log.users.set(users) -def detect_foreign_key_changes(sender, instance): +def detect_foreign_key_changes_for_shared_instances(sender, instance): if not hasattr(instance, 'pk') or not instance.pk: return if not isinstance(instance, BaseModel): @@ -296,7 +307,7 @@ def related_users(instance): # look in related DataAccess # data_access_list = instances_related_data_access(instance, related_instances) - print(f'instance = {instance.__class__.__name__}, data access count = {len(data_access_list)}') + # print(f'instance = {instance.__class__.__name__}, data access count = {len(data_access_list)}') for data_access in data_access_list: users.add(data_access.related_user) users.update(data_access.shared_with.all()) diff --git a/sync/utils.py b/sync/utils.py index 493c0b6..cf3ed11 100644 --- a/sync/utils.py +++ b/sync/utils.py @@ -31,16 +31,13 @@ def get_serializer(instance, model_name): def get_data(model_name, model_id): model = model_registry.get_model(model_name) - # print(f'model_name = {model_name}') - # model = apps.get_model(app_label=app_label, model_name=model_name) return model.objects.get(id=model_id) -def get_serialized_data(model_name, model_id): +def get_serialized_data_by_id(model_name, model_id): # print(f'model_name = {model_name}') model = model_registry.get_model(model_name) instance = model.objects.get(id=model_id) - serializer_class = build_serializer_class(model_name) - serializer = serializer_class(instance) + serializer = get_serializer(instance, model_name) return serializer.data class HierarchyOrganizer: diff --git a/sync/views.py b/sync/views.py index 0fc7779..6282db9 100644 --- a/sync/views.py +++ b/sync/views.py @@ -17,7 +17,7 @@ from collections import defaultdict from urllib.parse import unquote from .serializers import DataAccessSerializer -from .utils import get_serializer, build_serializer_class, get_data, get_serialized_data, HierarchyOrganizer +from .utils import get_serializer, build_serializer_class, get_data, get_serialized_data_by_id, HierarchyOrganizer from .models import ModelLog, BaseModel, SideStoreModel, DataAccess @@ -212,13 +212,11 @@ class SynchronizationApi(APIView): def get(self, request, *args, **kwargs): last_update_str = request.query_params.get('last_update') - device_id = request.query_params.get('device_id') - - decoded_last_update = unquote(last_update_str) # Decodes %2B into + - - if not decoded_last_update: + if not last_update_str: return Response({"error": "last_update parameter is required"}, status=status.HTTP_400_BAD_REQUEST) + try: + decoded_last_update = unquote(last_update_str) # Decodes %2B into + last_update = timezone.datetime.fromisoformat(decoded_last_update) except ValueError: return Response({"error": f"Invalid date format for last_update: {decoded_last_update}"}, @@ -226,6 +224,7 @@ class SynchronizationApi(APIView): print(f'>>> GET last modifications since: {last_update_str} / converted = {last_update}') + device_id = request.query_params.get('device_id') logs = self.query_model_logs(last_update, request.user, device_id) print(f'>>> user = {request.user.username} > log count = {logs.count()}, device_id = {device_id}') @@ -262,7 +261,7 @@ class LogProcessingResult: self.last_log_date = log.date try: if log.operation in ['POST', 'PUT']: - data = get_serialized_data(log.model_name, log.model_id) + data = get_serialized_data_by_id(log.model_name, log.model_id) self.updates[log.model_name][log.model_id] = data elif log.operation == 'DELETE': self.deletions[log.model_name].append(log.data_identifier_dict()) @@ -292,7 +291,7 @@ class LogProcessingResult: # elif log.operation == 'RELATIONSHIP_REMOVED': # self.relationship_removals[log.model_name].append(log.data_identifier_dict()) elif log.operation == 'SHARED_RELATIONSHIP_SET': - data = get_serialized_data(log.model_name, log.model_id) + data = get_serialized_data_by_id(log.model_name, log.model_id) self.shared_relationship_sets[log.model_name][log.model_id] = data elif log.operation == 'SHARED_RELATIONSHIP_REMOVED': self.shared_relationship_removals[log.model_name].append(log.data_identifier_dict())