diff --git a/padelclub_backend/settings_local.py.dist b/padelclub_backend/settings_local.py.dist index 6ac9fee..1ad1a9b 100644 --- a/padelclub_backend/settings_local.py.dist +++ b/padelclub_backend/settings_local.py.dist @@ -19,6 +19,7 @@ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', + 'ATOMIC_REQUESTS': True, } } diff --git a/sync/consumers.py b/sync/consumers.py index 7b9c7d0..95f683d 100644 --- a/sync/consumers.py +++ b/sync/consumers.py @@ -38,7 +38,7 @@ class UserConsumer(WebsocketConsumer): # Receive message from room group def sync_update(self, event): message = event["message"] - print(f'event = {event}') + # print(f'event = {event}') # Send message to WebSocket self.send(text_data=message) diff --git a/sync/signals.py b/sync/signals.py index ab45103..1900236 100644 --- a/sync/signals.py +++ b/sync/signals.py @@ -1,5 +1,5 @@ from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed -from django.db import models +from django.db import models, transaction from django.dispatch import receiver from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreModel @@ -10,8 +10,20 @@ from .ws_sender import websocket_sender User = get_user_model() -@receiver([pre_save, pre_delete]) -def synchronization_prepare(sender, instance, created=False, **kwargs): +@receiver(pre_save) +def presave_handler(sender, instance, **kwargs): + try: + sender.objects.get(pk=instance.pk) + created = False + except sender.DoesNotExist: + created = True + synchronization_prepare(sender, instance, created, **kwargs) + +@receiver(pre_delete) +def predelete_handler(sender, instance, **kwargs): + synchronization_prepare(sender, instance, False, **kwargs) + +def synchronization_prepare(sender, instance, created, **kwargs): signal = kwargs.get('signal') @@ -69,19 +81,15 @@ def notify_impacted_users(instance): if hasattr(instance, '_device_id'): device_id = instance._device_id - print(f'notify: {user_ids}') + # print(f'notify: {user_ids}') for user_id in user_ids: websocket_sender.send_user_message(user_id, device_id) # send_user_message(user_id) def save_model_log_if_possible(instance, signal, created, device_id): - if isinstance(instance, User): - users = {instance} - else: - users = related_users(instance) - - # print(f'users = {users}') + users = related_users(instance) + # print(f'users = {len(users)}, instance = {instance}') if users: if signal == post_save or signal == pre_save: if created: @@ -101,15 +109,16 @@ def save_model_log_if_possible(instance, signal, created, device_id): user_ids = [user.id for user in users] - print(f'users to notify: {user_ids}') + # print(f'users to notify: {user_ids}') instance._users_to_notify = user_ids # save this for the post_save signal save_model_log(users, operation, model_name, instance.id, store_id, device_id) else: - print('>>> Model Log could not be created because instance.last_updated_by is None') + print(f'>>> Model Log could not be created because no linked user could be found: {instance}, {signal}') def save_model_log(users, model_operation, model_name, model_id, store_id, device_id): now = timezone.now() + # print(f'ML users = {len(users)}') existing_log = ModelLog.objects.filter(users__in=users, model_id=model_id, operation=model_operation).first() if existing_log: # print(f'update existing log {existing_log.users} ') diff --git a/sync/views.py b/sync/views.py index 6ad1e38..d009369 100644 --- a/sync/views.py +++ b/sync/views.py @@ -91,10 +91,12 @@ class SynchronizationApi(HierarchyApiView): def post(self, request, *args, **kwargs): device_id = request.data.get('device_id') - print(f"DataApi post > device: {device_id}") operations = request.data['operations'] results = [] + print(f"DataApi post, {len(operations)} operations / device: {device_id}") + + models = set() for op in operations: result = None @@ -104,6 +106,8 @@ class SynchronizationApi(HierarchyApiView): model_name = op.get('model_name') data = op.get('data') + models.add(model_name) + serializer_class = build_serializer_class(model_name) data['last_updated_by'] = request.user.id # always refresh the user performing the operation @@ -162,6 +166,8 @@ class SynchronizationApi(HierarchyApiView): 'message': message }) + print(f"sync POST completed for models: {models}") + return Response({ 'results': results }, status=207) # Multi-Status @@ -252,7 +258,8 @@ class SynchronizationApi(HierarchyApiView): "date": last_log_date } - print(f'response_data = {response_data}') + print(f'sync GET response. UP = {len(updates)} / DEL = {len(deletions)} / G = {len(grants)} / R = {len(revocations)}') + # print(f'sync GET response. response = {response_data}') return Response(response_data, status=status.HTTP_200_OK) def query_model_logs(self, last_update, user, device_id): @@ -265,56 +272,66 @@ class UserDataAccessApi(HierarchyApiView): permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): - # Get all GRANT_ACCESS and REVOKE_ACCESS logs for the user, ordered by date - all_logs = ModelLog.objects.filter( - Q(users=request.user) & - (Q(operation='GRANT_ACCESS') | Q(operation='REVOKE_ACCESS')) - ).order_by('date') - - # Track latest status for each (model_name, model_id) - active_grants = {} - - # Process logs chronologically to determine current access state - for log in all_logs: - if log.operation == 'GRANT_ACCESS': - active_grants[log.model_id] = log - elif log.operation == 'REVOKE_ACCESS': - if log.model_id in active_grants and active_grants[log.model_id].date < log.date: - del active_grants[log.model_id] - - # Convert active_grants dict to list of grant logs - active_grants = list(active_grants.values()) - - # Prepare response data structure - data_by_model = defaultdict(dict) - print(f'>>> grants = {len(active_grants)}') + try: + # Get all DataAccess objects where the requesting user is in shared_with + data_access_objects = DataAccess.objects.filter( + shared_with=request.user + ).prefetch_related('shared_with') # Use prefetch_related for better performance - for log in active_grants: - try: - model = sync_registry.get_model(log.model_name) - instance = model.objects.get(id=log.model_id) + data_by_model = defaultdict(dict) - # Get the base data - serializer = get_serializer(instance, log.model_name) - data_by_model[log.model_name][log.model_id] = serializer.data + print(f'>>> grants = {len(data_access_objects)}') - # Add parents & children recursively - self.add_children_recursively(instance, data_by_model) - self.add_parents_recursively(instance, data_by_model) + for data_access in data_access_objects: + try: + model = sync_registry.get_model(data_access.model_name) + instance = model.objects.get(id=data_access.model_id) - except ObjectDoesNotExist: - continue + # Get the base data + serializer = get_serializer(instance, data_access.model_name) + data_by_model[data_access.model_name][data_access.model_id] = serializer.data - # Convert dictionary values to lists - response_data = { - model_name: list(model_data.values()) - for model_name, model_data in data_by_model.items() - } + # Add parents & children recursively + self.add_children_recursively(instance, data_by_model) + self.add_parents_recursively(instance, data_by_model) - print(f'response_data = {response_data}') + except ObjectDoesNotExist: + continue - return Response(response_data, status=status.HTTP_200_OK) + response_data = { + model_name: list(model_data.values()) + for model_name, model_data in data_by_model.items() + } + print(f'response_data = {response_data}') + return Response(response_data, status=status.HTTP_200_OK) + + except Exception as e: + return Response({ + 'status': 'error', + 'message': str(e) + }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + + # # Get all GRANT_ACCESS and REVOKE_ACCESS logs for the user, ordered by date + # all_logs = ModelLog.objects.filter( + # Q(users=request.user) & + # (Q(operation='GRANT_ACCESS') | Q(operation='REVOKE_ACCESS')) + # ).order_by('date') + + # # Track latest status for each (model_name, model_id) + # active_grants = {} + + # # Process logs chronologically to determine current access state + # for log in all_logs: + # if log.operation == 'GRANT_ACCESS': + # active_grants[log.model_id] = log + # elif log.operation == 'REVOKE_ACCESS': + # if log.model_id in active_grants and active_grants[log.model_id].date < log.date: + # del active_grants[log.model_id] + + # # Convert active_grants dict to list of grant logs + # active_grants = list(active_grants.values()) # def _add_children_recursively(self, instance, data_dict): # """ diff --git a/sync/ws_sender.py b/sync/ws_sender.py index 833cee5..d3e689e 100644 --- a/sync/ws_sender.py +++ b/sync/ws_sender.py @@ -14,7 +14,7 @@ class WebSocketSender: """ Schedules a notification for a specific user with debouncing. """ - print(f'>>> send message: {device_id}') + # print(f'>>> send message: {device_id}') # Cancel existing timer for this user if any if user_id in self._user_timers and self._user_timers[user_id]: self._user_timers[user_id].cancel() diff --git a/tournaments/admin.py b/tournaments/admin.py index 8eacaff..0ca5702 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -51,6 +51,7 @@ class TournamentAdmin(admin.ModelAdmin): class TeamRegistrationAdmin(AutoUpdateAdmin): list_display = ['player_names', 'group_stage_position', 'name', 'tournament'] list_filter = [SimpleTournamentListFilter] + search_fields = ['id'] class TeamScoreAdmin(AutoUpdateAdmin): list_display = ['team_registration', 'score', 'walk_out', 'match']