@ -11,7 +11,6 @@ from rest_framework import status
from django . utils import timezone
from django . core . exceptions import ObjectDoesNotExist
from django . db import transaction
from django . db . models import Q
from collections import defaultdict
@ -23,69 +22,69 @@ from .models import ModelLog, BaseModel, SideStoreModel, DataAccess
from . registry import model_registry , device_registry
class HierarchyApiView ( APIView ) :
def add_children_recursively ( self , instance , updates ) :
"""
Recursively add all children of an instance to the updates dictionary .
"""
child_models = instance . get_children_by_model ( )
for child_model_name , children in child_models . items ( ) :
for child in children :
if isinstance ( child , BaseModel ) :
serializer = get_serializer ( child , child_model_name )
updates [ child_model_name ] [ child . id ] = serializer . data
self . add_children_recursively ( child , updates )
def add_parents_with_hierarchy_organizer ( self , instance , hierarchy_organizer , current_level = 0 ) :
"""
Recursively add all parents of an instance to the hierarchy organizer .
Parents are added at a higher level than their children .
"""
parent_models = instance . get_parents_by_model ( )
for parent_model_name , parent in parent_models . items ( ) :
if isinstance ( parent , BaseModel ) :
# class HierarchyApiView(APIView):
def add_children_recursively ( instance , updates ) :
"""
Recursively add all children of an instance to the updates dictionary .
"""
child_models = instance . get_children_by_model ( )
for child_model_name , children in child_models . items ( ) :
for child in children :
if isinstance ( child , BaseModel ) :
serializer = get_serializer ( child , child_model_name )
updates [ child_model_name ] [ child . id ] = serializer . data
add_children_recursively ( child , updates )
def add_parents_with_hierarchy_organizer ( instance , hierarchy_organizer , current_level = 0 ) :
"""
Recursively add all parents of an instance to the hierarchy organizer .
Parents are added at a higher level than their children .
"""
parent_models = instance . get_parents_by_model ( )
for parent_model_name , parent in parent_models . items ( ) :
if isinstance ( parent , BaseModel ) :
store_id = None
if isinstance ( parent , SideStoreModel ) :
store_id = parent . store_id
parent_data = {
' model_id ' : parent . id ,
' store_id ' : store_id
}
# Add parent at the next level
hierarchy_organizer . add_item ( parent_model_name , parent_data , current_level + 1 )
# Recursively process parent's parents
add_parents_with_hierarchy_organizer ( parent , hierarchy_organizer , current_level + 1 )
def add_parents_recursively ( instance , dictionary , minimal = False ) :
"""
Recursively add all parents of an instance to the updates dictionary .
If minimal = True , only add id and store_id .
"""
parent_models = instance . get_parents_by_model ( )
for parent_model_name , parent in parent_models . items ( ) :
if isinstance ( parent , BaseModel ) :
if minimal :
store_id = None
if isinstance ( parent , SideStoreModel ) :
store_id = parent . store_id
parent_data = {
dictionary [ parent_model_name ] [ parent . id ] = {
' model_id ' : parent . id ,
' store_id ' : store_id
}
else :
serializer = get_serializer ( parent , parent_model_name )
dictionary [ parent_model_name ] [ parent . id ] = serializer . data
add_parents_recursively ( parent , dictionary , minimal )
# Add parent at the next level
hierarchy_organizer . add_item ( parent_model_name , parent_data , current_level + 1 )
# Recursively process parent's parents
self . add_parents_with_hierarchy_organizer ( parent , hierarchy_organizer , current_level + 1 )
def add_parents_recursively ( self , instance , dictionary , minimal = False ) :
"""
Recursively add all parents of an instance to the updates dictionary .
If minimal = True , only add id and store_id .
"""
parent_models = instance . get_parents_by_model ( )
for parent_model_name , parent in parent_models . items ( ) :
if isinstance ( parent , BaseModel ) :
if minimal :
store_id = None
if isinstance ( parent , SideStoreModel ) :
store_id = parent . store_id
dictionary [ parent_model_name ] [ parent . id ] = {
' model_id ' : parent . id ,
' store_id ' : store_id
}
else :
serializer = get_serializer ( parent , parent_model_name )
dictionary [ parent_model_name ] [ parent . id ] = serializer . data
self . add_parents_recursively ( parent , dictionary , minimal )
class SynchronizationApi ( HierarchyApiView ) :
class SynchronizationApi ( APIView ) :
permission_classes = [ IsAuthenticated ]
def post ( self , request , * args , * * kwargs ) :
@ -297,61 +296,215 @@ class SynchronizationApi(HierarchyApiView):
logs = self . query_model_logs ( last_update , request . user , device_id )
print ( f ' >>> log count = { len ( logs ) } ' )
# First pass: Process logs to collect basic operations
updates , deletions , grant_instances , revoke_info , last_log_date = self . process_logs ( logs )
# Process all logs and get response data
result = LogProcessingResult ( )
result . process_logs ( logs )
response_data = result . get_response_data ( )
# Second pass: Process hierarchies for grants and revocations
grants = self . process_grants ( grant_instances )
revocations_parents_organizer = self . process_revocations ( revoke_info )
return Response ( response_data , status = status . HTTP_200_OK )
response_data = {
" updates " : dict ( updates ) ,
" deletions " : dict ( deletions ) ,
" grants " : dict ( grants ) ,
" revocations " : dict ( revocations_parents_organizer [ 0 ] ) ,
" revocation_parents " : revocations_parents_organizer [ 1 ] . get_organized_data ( ) ,
" date " : last_log_date
}
# 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
return Response ( response_data , status = status . HTTP_200_OK )
# 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}')
def process_logs ( self , logs ) :
""" Process logs to collect basic operations and handle grant/revoke efficiently. """
updates = defaultdict ( dict )
deletions = defaultdict ( list )
grant_instances = defaultdict ( dict ) # {model_name: {model_id: instance}}
revoke_info = defaultdict ( list ) # {model_name: [{model_id, 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 ' )
last_log_date = None
# 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. """
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 )
self . last_log_date = None
def process_logs ( self , logs ) :
""" Process logs to collect basic operations and handle grant/revoke efficiently. """
for log in logs :
last_log_date = log . date
self . 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
self . 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 } )
self . 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 ( revoke_info , log . model_name , log . model_id )
self . _remove_revocation ( log . model_name , log . model_id )
# Add to grant instances if not already there
if log . model_id not in grant_instances [ log . model_name ] :
if log . model_id not in self . grant_instances [ log . model_name ] :
model = model_registry . get_model ( log . model_name )
try :
instance = model . objects . get ( id = log . model_id )
grant_instances [ log . model_name ] [ log . model_id ] = instance
self . 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 ( grant_instances , log . model_name , log . model_id )
self . _remove_grant ( log . model_name , log . model_id )
# Add to revocations
revoke_info [ log . model_name ] . append ( {
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
} )
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
} )
@ -359,43 +512,43 @@ class SynchronizationApi(HierarchyApiView):
pass
# Convert updates dict to list for each model
for model_name in updates :
updates [ model_name ] = list ( updates [ model_name ] . values ( ) )
for model_name in self . updates :
self . updates [ model_name ] = list ( self . updates [ model_name ] . values ( ) )
return updates , deletions , grant_instances , revoke_info , last_log_date
# return self
def _remove_revocation ( self , revoke_info , model_name , model_id ) :
def _remove_revocation ( self , 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 model_name in self . revoke_info :
self . revoke_info [ model_name ] = [
r for r in self . 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 ]
if not self . revoke_info [ model_name ] :
del self . revoke_info [ model_name ]
def _remove_grant ( self , grant_instances , model_name , model_id ) :
def _remove_grant ( self , 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 ]
if model_name in self . grant_instances and model_id in self . grant_instances [ model_name ] :
del self . grant_instances [ model_name ] [ model_id ]
# Clean up empty dictionaries
if not grant_instances [ model_name ] :
del grant_instances [ model_name ]
if not self . grant_instances [ model_name ] :
del self . grant_instances [ model_name ]
def process_grants ( self , grant_instances ) :
def process_grants ( self ) :
""" Process grants and their hierarchies. """
grants = defaultdict ( dict )
# Process each grant instance
for model_name , instances in grant_instances . items ( ) :
for model_name , instances in self . 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 )
add_children_recursively ( instance , grants )
add_parents_recursively ( instance , grants )
# Convert to lists
for model_name in grants :
@ -403,13 +556,13 @@ class SynchronizationApi(HierarchyApiView):
return grants
def process_revocations ( self , revoke_info ) :
def process_revocations ( self ) :
""" Process revocations and their hierarchies. """
revocations = defaultdict ( list )
revocations_parents_organizer = HierarchyOrganizer ( )
# First, collect all revocations
for model_name , items in revoke_info . items ( ) :
for model_name , items in self . revoke_info . items ( ) :
revocations [ model_name ] . extend ( items )
# Process parent hierarchies for each revoked item
@ -417,19 +570,31 @@ class SynchronizationApi(HierarchyApiView):
for item in items :
try :
instance = model . objects . get ( id = item [ ' model_id ' ] )
self . add_parents_with_hierarchy_organizer ( instance , revocations_parents_organizer )
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 ' )
def get_response_data ( self ) :
""" Construct the complete response data structure. """
grants = self . process_grants ( )
revocations , revocations_parents_organizer = self . process_revocations ( )
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 ) ,
" date " : self . last_log_date
}
class UserDataAccessApi ( HierarchyApiView ) :
class UserDataAccessApi ( API View) :
permission_classes = [ IsAuthenticated ]
def get ( self , request , * args , * * kwargs ) :
@ -454,8 +619,8 @@ class UserDataAccessApi(HierarchyApiView):
data_by_model [ data_access . model_name ] [ data_access . model_id ] = serializer . data
# Add parents & children recursively
self . add_children_recursively ( instance , data_by_model )
self . add_parents_recursively ( instance , data_by_model )
add_children_recursively ( instance , data_by_model )
add_parents_recursively ( instance , data_by_model )
except ObjectDoesNotExist :
continue