diff --git a/api/urls.py b/api/urls.py index 0c49667..390e456 100644 --- a/api/urls.py +++ b/api/urls.py @@ -8,7 +8,7 @@ from authentication.views import CustomAuthToken, Logout, ChangePasswordView router = routers.DefaultRouter() router.register(r'users', views.UserViewSet) -router.register(r'user-names', views.ShortUserViewSet) +router.register(r'user-agents', views.ShortUserViewSet) router.register(r'clubs', views.ClubViewSet) router.register(r'tournaments', views.TournamentViewSet) router.register(r'events', views.EventViewSet) diff --git a/api/views.py b/api/views.py index be20311..0ded140 100644 --- a/api/views.py +++ b/api/views.py @@ -300,6 +300,9 @@ class ShortUserViewSet(viewsets.ModelViewSet): serializer_class = ShortUserSerializer permission_classes = [] # Users are public whereas the other requests are only for logged users + def get_queryset(self): + return self.request.user.agents + @api_view(['POST']) @permission_classes([IsAuthenticated]) def process_refund(request, team_registration_id): diff --git a/authentication/views.py b/authentication/views.py index f8ba5c0..a742876 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -31,7 +31,7 @@ class CustomAuthToken(APIView): password = request.data.get('password') device_id = request.data.get('device_id') - logger.info(f'Login attempt from {username}') + # logger.info(f'Login attempt from {username}') user = authenticate(username=username, password=password) if user is None and is_valid_email(username) == True: diff --git a/sync/models/base.py b/sync/models/base.py index cf90f58..5d57e5b 100644 --- a/sync/models/base.py +++ b/sync/models/base.py @@ -35,7 +35,7 @@ class BaseModel(models.Model): for child in children: if (child.one_to_many or child.one_to_one) and child.auto_created: model_name = child.related_model.__name__ - # print(f'>>> add children for {model_name}') + print(f'>>> add children for {model_name}') related_objects[model_name] = getattr(self, child.name).all() return related_objects @@ -58,6 +58,7 @@ class BaseModel(models.Model): # Get the parent instance using the related name parent_instance = getattr(self, field.get_accessor_name()) if parent_instance: + print(f'>>> add parent for OneToOneRel : {model_name}') parents[model_name] = parent_instance # Also check for direct foreign key relationships that might represent parent relationships @@ -65,6 +66,7 @@ class BaseModel(models.Model): model_name = field.related_model.__name__ parent_instance = getattr(self, field.name) if parent_instance: + print(f'>>> add parent for ForeignKey : {model_name}') parents[model_name] = parent_instance return parents diff --git a/sync/models/model_log.py b/sync/models/model_log.py index 18ebf7e..cd4e801 100644 --- a/sync/models/model_log.py +++ b/sync/models/model_log.py @@ -7,8 +7,6 @@ class ModelOperation(models.TextChoices): DELETE = 'DELETE', 'DELETE' GRANT_ACCESS = 'GRANT_ACCESS', 'GRANT_ACCESS' REVOKE_ACCESS = 'REVOKE_ACCESS', 'REVOKE_ACCESS' - RELATIONSHIP_SET = 'RELATIONSHIP_SET', 'RELATIONSHIP_SET' - RELATIONSHIP_REMOVED = 'RELATIONSHIP_REMOVED', 'RELATIONSHIP_REMOVED' SHARED_RELATIONSHIP_SET = 'SHARED_RELATIONSHIP_SET', 'SHARED_RELATIONSHIP_SET' SHARED_RELATIONSHIP_REMOVED = 'SHARED_RELATIONSHIP_REMOVED', 'SHARED_RELATIONSHIP_REMOVED' @@ -43,5 +41,11 @@ class ModelLog(models.Model): self.date = self.date.replace(microsecond=microseconds) super().save(*args, **kwargs) + def data_identifier_dict(self): + return { + 'model_id': self.model_id, + 'store_id': self.store_id + } + def retrieved(self): self.count += 1 diff --git a/sync/signals.py b/sync/signals.py index ae3650d..bf4570c 100644 --- a/sync/signals.py +++ b/sync/signals.py @@ -224,11 +224,11 @@ def process_foreign_key_changes(sender, instance, **kwargs): save_model_log(shared, 'SHARED_RELATIONSHIP_REMOVED', model_name, change['old_value'].id, change['old_value'].get_store_id()) - if owner: - print(f"RELATIONSHIP_REMOVED: owner={owner}, model_name={model_name}") - save_model_log(owner, 'RELATIONSHIP_REMOVED', - model_name, change['old_value'].id, - change['old_value'].get_store_id()) + # if owner: + # print(f"RELATIONSHIP_REMOVED: owner={owner}, model_name={model_name}") + # save_model_log(owner, 'RELATIONSHIP_REMOVED', + # model_name, change['old_value'].id, + # change['old_value'].get_store_id()) if change['new_value']: model_name = change['new_value'].__class__.__name__ if shared: @@ -236,11 +236,11 @@ def process_foreign_key_changes(sender, instance, **kwargs): save_model_log(shared, 'SHARED_RELATIONSHIP_SET', model_name, change['new_value'].id, change['new_value'].get_store_id()) - if owner: - print(f"RELATIONSHIP_SET: owner={owner}, model_name={model_name}") - save_model_log(owner, 'RELATIONSHIP_SET', - model_name, change['old_value'].id, - change['old_value'].get_store_id()) + # if owner: + # print(f"RELATIONSHIP_SET: owner={owner}, model_name={model_name}") + # save_model_log(owner, 'RELATIONSHIP_SET', + # model_name, change['old_value'].id, + # change['old_value'].get_store_id()) ### Data Access diff --git a/sync/views.py b/sync/views.py index 37557f3..2ab7108 100644 --- a/sync/views.py +++ b/sync/views.py @@ -33,6 +33,7 @@ def add_children_recursively(instance, updates): for child_model_name, children in child_models.items(): for child in children: if isinstance(child, BaseModel): + print(f'add_children_recursively: {child_model_name}') serializer = get_serializer(child, child_model_name) updates[child_model_name][child.id] = serializer.data add_children_recursively(child, updates) @@ -56,6 +57,7 @@ def add_parents_with_hierarchy_organizer(instance, hierarchy_organizer, current_ } # Add parent at the next level + print(f'*** add parent: {parent_model_name}: {parent.id}') hierarchy_organizer.add_item(parent_model_name, parent_data, current_level + 1) # Recursively process parent's parents @@ -130,7 +132,7 @@ class SynchronizationApi(APIView): result = serializer.data response_status = status.HTTP_201_CREATED else: - print(f'Data invalid ! {serializer.errors}') + print(f'{model_name} POST: Data invalid ! {serializer.errors}') message = json.dumps(serializer.errors) response_status = status.HTTP_400_BAD_REQUEST elif model_operation == 'PUT': @@ -145,7 +147,7 @@ class SynchronizationApi(APIView): result = serializer.data response_status = status.HTTP_203_NON_AUTHORITATIVE_INFORMATION else: - print(f'Data invalid ! {serializer.errors}') + print(f'{model_name} PUT: Data invalid ! {serializer.errors}') response_status = status.HTTP_400_BAD_REQUEST elif model_operation == 'DELETE': try: @@ -181,102 +183,6 @@ class SynchronizationApi(APIView): 'results': results }, status=207) # Multi-Status - # def get(self, request, *args, **kwargs): - # last_update_str = request.query_params.get('last_update') - # device_id = request.query_params.get('device_id') - - # # print(f'last_update_str = {last_update_str}') - # decoded_last_update = unquote(last_update_str) # Decodes %2B into + - - # # print(f'last_update_str = {last_update_str}') - # # print(f'decoded_last_update = {decoded_last_update}') - # if not decoded_last_update: - # return Response({"error": "last_update parameter is required"}, status=status.HTTP_400_BAD_REQUEST) - # try: - # last_update = timezone.datetime.fromisoformat(decoded_last_update) - # except ValueError: - # return Response({"error": f"Invalid date format for last_update: {decoded_last_update}"}, status=status.HTTP_400_BAD_REQUEST) - - # # print(f'/data GET: {last_update}') - - # logs = self.query_model_logs(last_update, request.user, device_id) - # print(f'>>> log count = {len(logs)}') - - # updates = defaultdict(dict) - # deletions = defaultdict(list) - # grants = defaultdict(dict) - # revocations = defaultdict(list) - # revocations_parents_organizer = HierarchyOrganizer() - - # # revocated_parents = defaultdict(dict) - - # last_log_date = None - # for log in logs: - - # # log.retrieved() - # # log.save() - - # # print(f'log date = {log.date}') - # last_log_date = log.date - # try: - # if log.operation in ['POST', 'PUT']: - # data = get_serialized_data(log.model_name, log.model_id) - # updates[log.model_name][log.model_id] = data - # elif log.operation == 'DELETE': - # deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) - # elif log.operation == 'GRANT_ACCESS': - - # model = model_registry.get_model(log.model_name) - # instance = model.objects.get(id=log.model_id) - # serializer = get_serializer(instance, log.model_name) - - # grants[log.model_name][log.model_id] = serializer.data - # self.add_children_recursively(instance, grants) - # self.add_parents_recursively(instance, grants) - # elif log.operation == 'REVOKE_ACCESS': - # print(f'revoke access {log.model_id} - {log.store_id}') - # revocations[log.model_name].append({ - # 'model_id': log.model_id, - # 'store_id': log.store_id - # }) - - # # Get the model instance and add its parents to hierarchy - # model = model_registry.get_model(log.model_name) - # try: - # instance = model.objects.get(id=log.model_id) - # self.add_parents_with_hierarchy_organizer(instance, revocations_parents_organizer) - # except model.DoesNotExist: - # pass - # except ObjectDoesNotExist: - # pass - - # # Convert updates dict to list for each model - # for model_name in updates: - # updates[model_name] = list(updates[model_name].values()) - - # # Convert deletions set to list for each model - # for model_name in deletions: - # deletions[model_name] = deletions[model_name] - - # for model_name in grants: - # grants[model_name] = list(grants[model_name].values()) - - # # for model_name in revocation_parents: - # # revocation_parents[model_name] = list(revocation_parents[model_name].values()) - - # response_data = { - # "updates": dict(updates), - # "deletions": dict(deletions), - # "grants": dict(grants), - # "revocations": dict(revocations), - # "revocation_parents": revocations_parents_organizer.get_organized_data(), - # "date": last_log_date - # } - - # # 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) - ## GET def get(self, request, *args, **kwargs): @@ -293,8 +199,10 @@ class SynchronizationApi(APIView): return Response({"error": f"Invalid date format for last_update: {decoded_last_update}"}, status=status.HTTP_400_BAD_REQUEST) + print(f'>>> GET last modifications since: {last_update_str} / converted = {last_update}') + logs = self.query_model_logs(last_update, request.user, device_id) - print(f'>>> log count = {len(logs)}') + print(f'>>> user = {request.user.username} > log count = {logs.count()}, device_id = {device_id}') # Process all logs and get response data result = LogProcessingResult() @@ -303,147 +211,12 @@ class SynchronizationApi(APIView): return Response(response_data, status=status.HTTP_200_OK) - # def process_logs(self, logs): - # """Process logs to collect basic operations and handle grant/revoke efficiently.""" - # # Create an instance of the LogProcessingResult class - # result = LogProcessingResult() - # last_log_date = None - - # for log in logs: - # last_log_date = log.date - # try: - # if log.operation in ['POST', 'PUT']: - # data = get_serialized_data(log.model_name, log.model_id) - # result.updates[log.model_name][log.model_id] = data - # elif log.operation == 'DELETE': - # result.deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id}) - # elif log.operation == 'GRANT_ACCESS': - # # Remove any existing revocations for this model_id - # self._remove_revocation(result.revoke_info, log.model_name, log.model_id) - - # # Add to grant instances if not already there - # if log.model_id not in result.grant_instances[log.model_name]: - # model = model_registry.get_model(log.model_name) - # try: - # instance = model.objects.get(id=log.model_id) - # result.grant_instances[log.model_name][log.model_id] = instance - # except model.DoesNotExist: - # pass - # elif log.operation == 'REVOKE_ACCESS': - # print(f'revoke access {log.model_id} - {log.store_id}') - - # # Remove any existing grants for this model_id - # self._remove_grant(result.grant_instances, log.model_name, log.model_id) - - # # Add to revocations - # result.revoke_info[log.model_name].append({ - # 'model_id': log.model_id, - # 'store_id': log.store_id - # }) - # elif log.operation == 'RELATIONSHIP_SET': - # data = get_serialized_data(log.model_name, log.model_id) - # result.relationship_sets[log.model_name][log.model_id] = data - # elif log.operation == 'RELATIONSHIP_REMOVED': - # result.relationship_removals[log.model_name].append({ - # 'model_id': log.model_id, - # 'store_id': log.store_id - # }) - # elif log.operation == 'SHARED_RELATIONSHIP_SET': - # data = get_serialized_data(log.model_name, log.model_id) - # result.shared_relationship_sets[log.model_name][log.model_id] = data - # elif log.operation == 'SHARED_RELATIONSHIP_REMOVED': - # result.shared_relationship_removals[log.model_name].append({ - # 'model_id': log.model_id, - # 'store_id': log.store_id - # }) - # except ObjectDoesNotExist: - # pass - - # # Convert updates dict to list for each model - # for model_name in result.updates: - # result.updates[model_name] = list(result.updates[model_name].values()) - - # return result, last_log_date - - # def _remove_revocation(self, revoke_info, model_name, model_id): - # """Remove any revocation entries for the specified model and ID.""" - # if model_name in revoke_info: - # revoke_info[model_name] = [ - # r for r in revoke_info[model_name] - # if r['model_id'] != model_id - # ] - # # Clean up empty lists - # if not revoke_info[model_name]: - # del revoke_info[model_name] - - # def _remove_grant(self, grant_instances, model_name, model_id): - # """Remove any grant entries for the specified model and ID.""" - # if model_name in grant_instances and model_id in grant_instances[model_name]: - # del grant_instances[model_name][model_id] - # # Clean up empty dictionaries - # if not grant_instances[model_name]: - # del grant_instances[model_name] - - # def process_grants(self, grant_instances): - # """Process grants and their hierarchies.""" - # grants = defaultdict(dict) - - # # Process each grant instance - # for model_name, instances in grant_instances.items(): - # for model_id, instance in instances.items(): - # serializer = get_serializer(instance, model_name) - # grants[model_name][model_id] = serializer.data - - # # Add hierarchies only once per instance - # self.add_children_recursively(instance, grants) - # self.add_parents_recursively(instance, grants) - - # # Convert to lists - # for model_name in grants: - # grants[model_name] = list(grants[model_name].values()) - - # return grants - - # def process_revocations(self, revoke_info): - # """Process revocations and their hierarchies.""" - # revocations = defaultdict(list) - # revocations_parents_organizer = HierarchyOrganizer() - - # # First, collect all revocations - # for model_name, items in revoke_info.items(): - # revocations[model_name].extend(items) - - # # Process parent hierarchies for each revoked item - # model = model_registry.get_model(model_name) - # for item in items: - # try: - # instance = model.objects.get(id=item['model_id']) - # self.add_parents_with_hierarchy_organizer(instance, revocations_parents_organizer) - # except model.DoesNotExist: - # pass - - # return revocations, revocations_parents_organizer - def query_model_logs(self, last_update, user, device_id): log_query = Q(date__gt=last_update, user=user) if device_id: log_query &= ~Q(device_id=device_id) # exclude query return ModelLog.objects.filter(log_query).order_by('date') -# class LogProcessingResult: -# """Class to hold all the results from log processing.""" - -# def __init__(self): -# # Initialize all the collections -# self.updates = defaultdict(dict) -# self.deletions = defaultdict(list) -# self.grant_instances = defaultdict(dict) # {model_name: {model_id: instance}} -# self.revoke_info = defaultdict(list) # {model_name: [{model_id, store_id}]} -# self.relationship_sets = defaultdict(dict) -# self.relationship_removals = defaultdict(list) -# self.shared_relationship_sets = defaultdict(dict) -# self.shared_relationship_removals = defaultdict(list) - class LogProcessingResult: """Class to handle all log processing and organize the results.""" @@ -453,8 +226,6 @@ class LogProcessingResult: self.deletions = defaultdict(list) self.grant_instances = defaultdict(dict) # {model_name: {model_id: instance}} self.revoke_info = defaultdict(list) # {model_name: [{model_id, store_id}]} - self.relationship_sets = defaultdict(dict) - self.relationship_removals = defaultdict(list) self.shared_relationship_sets = defaultdict(dict) self.shared_relationship_removals = defaultdict(list) self.last_log_date = None @@ -468,7 +239,7 @@ class LogProcessingResult: data = get_serialized_data(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({'model_id': log.model_id, 'store_id': log.store_id}) + self.deletions[log.model_name].append(log.data_identifier_dict()) elif log.operation == 'GRANT_ACCESS': # Remove any existing revocations for this model_id self._remove_revocation(log.model_name, log.model_id) @@ -488,32 +259,25 @@ class LogProcessingResult: self._remove_grant(log.model_name, log.model_id) # Add to revocations - self.revoke_info[log.model_name].append({ - 'model_id': log.model_id, - 'store_id': log.store_id - }) - elif log.operation == 'RELATIONSHIP_SET': - data = get_serialized_data(log.model_name, log.model_id) - self.relationship_sets[log.model_name][log.model_id] = data - elif log.operation == 'RELATIONSHIP_REMOVED': - self.relationship_removals[log.model_name].append({ - 'model_id': log.model_id, - 'store_id': log.store_id - }) + self.revoke_info[log.model_name].append(log.data_identifier_dict()) + # elif log.operation == 'RELATIONSHIP_SET': + # data = get_serialized_data(log.model_name, log.model_id) + # self.relationship_sets[log.model_name][log.model_id] = data + # 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) 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({ - 'model_id': log.model_id, - 'store_id': log.store_id - }) + self.shared_relationship_removals[log.model_name].append(log.data_identifier_dict()) except ObjectDoesNotExist: pass # Convert updates dict to list for each model for model_name in self.updates: self.updates[model_name] = list(self.updates[model_name].values()) + for model_name in self.shared_relationship_sets: + self.shared_relationship_sets[model_name] = list(self.shared_relationship_sets[model_name].values()) # return self @@ -561,15 +325,20 @@ class LogProcessingResult: revocations = defaultdict(list) revocations_parents_organizer = HierarchyOrganizer() + print(f'*** process_revocations: {len(self.revoke_info)}') + # First, collect all revocations for model_name, items in self.revoke_info.items(): revocations[model_name].extend(items) + print(f'*** process_revocations for {model_name}') + # Process parent hierarchies for each revoked item model = model_registry.get_model(model_name) for item in items: try: instance = model.objects.get(id=item['model_id']) + print(f'*** process revoked item parents of {model_name} : {item['model_id']}') add_parents_with_hierarchy_organizer(instance, revocations_parents_organizer) except model.DoesNotExist: pass @@ -581,16 +350,18 @@ class LogProcessingResult: grants = self.process_grants() revocations, revocations_parents_organizer = self.process_revocations() + # print(f'self.deletions = {dict(self.deletions)}') + # print(f'self.shared_relationship_sets = {self.shared_relationship_sets}') + # print(f'self.shared_relationship_removals = {self.shared_relationship_removals}') + return { "updates": dict(self.updates), "deletions": dict(self.deletions), "grants": dict(grants), "revocations": dict(revocations), "revocation_parents": revocations_parents_organizer.get_organized_data(), - "relationship_sets": dict(self.relationship_sets), - "relationship_removals": dict(self.relationship_removals), - "shared_relationship_sets": dict(self.shared_relationship_sets), - "shared_relationship_removals": dict(self.shared_relationship_removals), + "shared_relationship_sets": self.shared_relationship_sets, + "shared_relationship_removals": self.shared_relationship_removals, "date": self.last_log_date } diff --git a/sync/ws_sender.py b/sync/ws_sender.py index d3e689e..ff2af3a 100644 --- a/sync/ws_sender.py +++ b/sync/ws_sender.py @@ -34,7 +34,7 @@ class WebSocketSender: channel_layer = get_channel_layer() group_name = f"sync_{user_id}" - print(f">>> send to group {group_name}, device_id={device_id}") + # print(f">>> send to group {group_name}, device_id={device_id}") # print(f'channel_layer = {channel_layer}') device_id = device_id if device_id else "std_msg_lol" # a not empty message is required! diff --git a/tournaments/backends.py b/tournaments/backends.py index 761d22b..9d77ae7 100644 --- a/tournaments/backends.py +++ b/tournaments/backends.py @@ -10,18 +10,18 @@ class EmailOrUsernameModelBackend(ModelBackend): def authenticate(self, request, username=None, password=None, **kwargs): UserModel = get_user_model() - print(f"Backend attempting authentication for: {username}") # Debug print + # print(f"Backend attempting authentication for: {username}") # Debug print logger.info(f"Backend attempting authentication for: {username}") try: user = UserModel.objects.get( Q(username__iexact=username) | Q(email__iexact=username) ) - print(f"User found: {user}") # Debug print + # print(f"User found: {user}") # Debug print logger.info(f"User found: {user}") if user.check_password(password): - print("Password check successful") # Debug print + # print("Password check successful") # Debug print logger.info("Password check successful") return user print("Password check failed") # Debug print