|
|
|
|
@ -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 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|