diff --git a/sync/admin.py b/sync/admin.py index cb97d80..48b3ee0 100644 --- a/sync/admin.py +++ b/sync/admin.py @@ -6,13 +6,14 @@ from .models import BaseModel, ModelLog, DataAccess class SyncedObjectAdmin(admin.ModelAdmin): exclude = ('data_access_ids',) + raw_id_fields = ['related_user'] def save_model(self, request, obj, form, change): if isinstance(obj, BaseModel): obj.last_updated_by = request.user obj.last_update = timezone.now() - if obj.related_user is None: - obj.related_user = request.user + # if obj.related_user is None: + # obj.related_user = request.user super().save_model(request, obj, form, change) def delete_model(self, request, obj): diff --git a/sync/models/data_access.py b/sync/models/data_access.py index e4ba81d..5a38ae3 100644 --- a/sync/models/data_access.py +++ b/sync/models/data_access.py @@ -8,7 +8,9 @@ from ..registry import model_registry import uuid from . import ModelLog, SideStoreModel, BaseModel +import logging +logger = logging.getLogger(__name__) class DataAccess(BaseModel): id = models.UUIDField(primary_key=True, default=uuid.uuid4) @@ -40,6 +42,9 @@ class DataAccess(BaseModel): store_id = obj.store_id for user in users: + + logger.info(f'=== create ModelLog for: {operation} > {users}') + existing_log = ModelLog.objects.filter(user=user, model_id=self.model_id, operation=operation).first() if existing_log: existing_log.date = timezone.now() @@ -55,9 +60,10 @@ class DataAccess(BaseModel): store_id=store_id ) except ObjectDoesNotExist: + logger.warn(f'!!! object does not exists any more: {self.model_name} : {self.model_id} : {operation}') pass else: - print(f'model not found: {self.model_name}') + logger.warn(f'!!!model not found: {self.model_name}') def add_references(self): model_class = model_registry.get_model(self.model_name) diff --git a/sync/signals.py b/sync/signals.py index 9ce0b59..e0c0fba 100644 --- a/sync/signals.py +++ b/sync/signals.py @@ -6,11 +6,14 @@ from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreMo from authentication.models import Device from django.contrib.auth import get_user_model -from django.utils import timezone from .ws_sender import websocket_sender from .registry import device_registry, related_users_registry +import logging + +logger = logging.getLogger(__name__) + User = get_user_model() ### Sync @@ -28,12 +31,18 @@ def presave_handler(sender, instance, **kwargs): 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_for_shared_instances(sender, instance) + sig_type = 'pre_save' + else: + sig_type = 'pre_delete' + logger.info(f'* {sig_type} : {instance.__class__.__name__} > impacted users = {users}') + @receiver([post_save, post_delete]) def synchronization_notifications(sender, instance, created=False, **kwargs): @@ -87,7 +96,14 @@ def notify_impacted_users(instance): def save_model_log_if_possible(instance, signal, created): users = related_users_registry.get_users(instance.id) - # print(f'*** save_model_log >>> users = {users}, instance = {instance}') + logger.debug(f'*** save_model_log_if_possible >>> users from registry = {users}, instance = {instance}') + + if not users: + logger.warning(f'!!! Registry returned empty users for instance {instance.id} ({instance.__class__.__name__})') + # Try to recalculate users as fallback + users = related_users(instance) + logger.info(f'!!! Recalculated users for fallback: {users}') + if users: if signal == post_save or signal == pre_save: if created: @@ -111,29 +127,43 @@ def save_model_log_if_possible(instance, signal, created): save_model_log(users, operation, model_name, instance.id, store_id) else: - print(f'>>> Model Log could not be created because no linked user could be found: {instance.__class__.__name__} {instance}, {signal}') + logger.info(f'!!! Model Log could not be created because no linked user could be found: {instance.__class__.__name__} {instance}, {signal}') def save_model_log(users, model_operation, model_name, model_id, store_id): device_id = device_registry.get_device_id(model_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.can_synchronize - ] + logger.info(f'*** creating ModelLogs for: {model_operation} {model_name} : {users}') + + try: + with transaction.atomic(): + created_logs = [] + for user in users: + logger.debug(f'Creating ModelLog for user {user.id} ({user.username})') + model_log = ModelLog( + user=user, + operation=model_operation, + model_name=model_name, + model_id=model_id, + store_id=store_id, + device_id=device_id + ) + model_log.save() + created_logs.append(model_log.id) + logger.debug(f'Successfully created ModelLog {model_log.id}') + + logger.info(f'*** Successfully created {len(created_logs)} ModelLogs: {created_logs}') + + # Verify ModelLogs were actually persisted + persisted_count = ModelLog.objects.filter(id__in=created_logs).count() + if persisted_count != len(created_logs): + logger.error(f'*** PERSISTENCE VERIFICATION FAILED! Created {len(created_logs)} ModelLogs but only {persisted_count} were persisted to database') + else: + logger.debug(f'*** PERSISTENCE VERIFIED: All {persisted_count} ModelLogs successfully persisted') - with transaction.atomic(): - ModelLog.objects.bulk_create(logs_to_create) + except Exception as e: + logger.error(f'*** FAILED to create ModelLogs for: {model_operation} {model_name}, users: {[u.id for u in users]}, error: {e}', exc_info=True) + raise # with transaction.atomic(): # for user in users: @@ -258,10 +288,11 @@ def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs): # print(f'm2m changed = {pk_set}') users = User.objects.filter(id__in=pk_set) - if action == "post_add": - instance.create_access_log(users, 'SHARED_ACCESS') - elif action == "post_remove": - instance.create_access_log(users, 'REVOKED_ACCESS') + with transaction.atomic(): + if action == "post_add": + instance.create_access_log(users, 'SHARED_ACCESS') + elif action == "post_remove": + instance.create_access_log(users, 'REVOKED_ACCESS') device_id = device_registry.get_device_id(instance.id) websocket_sender.send_message(pk_set, device_id) @@ -302,18 +333,8 @@ def related_users(instance): users.add(instance) elif isinstance(instance, BaseModel): users.add(instance.related_user) - # users.add(instance.last_updated_by) - - # look in hierarchy - # related_instances = instance.related_instances() - # print(f'related_instances = {related_instances}') - # related_users = [ri.related_user for ri in related_instances if isinstance(ri, BaseModel)] - # users.update(related_users) - data_access_list = DataAccess.objects.filter(id__in=instance.data_access_ids) - # 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)}') for data_access in data_access_list: users.add(data_access.related_user) diff --git a/tournaments/admin.py b/tournaments/admin.py index f8f7b60..3b67f56 100644 --- a/tournaments/admin.py +++ b/tournaments/admin.py @@ -24,6 +24,7 @@ class CustomUserAdmin(UserAdmin): list_display = ['email', 'first_name', 'last_name', 'username', 'date_joined', 'latest_event_club_name', 'is_active', 'event_count', 'origin', 'registration_payment_mode', 'licence_id'] list_filter = ['is_active', 'origin'] ordering = ['-date_joined'] + raw_id_fields = ['agents'] fieldsets = [ (None, {'fields': ['id', 'username', 'email', 'password', 'first_name', 'last_name', 'is_active']}), ('Permissions', {'fields': ['is_staff', 'is_superuser', 'groups', 'user_permissions']}), @@ -32,7 +33,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', 'origin', 'agents', 'should_synchronize' + 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'origin', 'agents', 'should_synchronize', 'can_synchronize' ]}), ] @@ -298,7 +299,7 @@ class TeamRegistrationAdmin(SyncedObjectAdmin): class TeamScoreAdmin(SyncedObjectAdmin): list_display = ['team_registration', 'score', 'walk_out', 'match'] list_filter = [TeamScoreTournamentListFilter] - search_fields = ['id'] + search_fields = ['id', 'team_registration__player_registrations__first_name', 'team_registration__player_registrations__last_name'] raw_id_fields = ['team_registration', 'match'] # Add this line list_per_page = 50 # Controls pagination on the list view