You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
padelclub_backend/api/sync.py

227 lines
9.7 KiB

from django.db.models.fields.related_lookups import RelatedExact
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status
from django.apps import apps
from django.utils import timezone
from django.db.models import Q
from collections import defaultdict
from .utils import build_serializer_class, get_data, get_serialized_data
from tournaments.models import ModelLog, DataAccess, BaseModel
class DataApi(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
# unfold content
model_operation = request.data.get('operation')
model_name = request.data.get('model_name')
data = request.data.get('data')
store_id = request.data.get('store_id')
print(f"DataApi post > {model_operation} {model_name}")
serializer_class = build_serializer_class(model_name)
model = apps.get_model(app_label='tournaments', model_name=model_name)
now = timezone.localtime(timezone.now())
try:
data_id = data.get('id')
# instance = model.objects.get(id=data_id)
instance = get_data('tournaments', model_name, data_id)
# update the possible data access objects with the current date
if model_operation == 'DELETE':
parent_model, parent_id = instance.get_parent_reference()
return self.delete_and_save_log(request, data_id, model_operation, model_name, store_id, now)
else: # PUT
serializer = serializer_class(instance, data=data, context={'request': request})
if serializer.is_valid():
if instance.last_update <= serializer.validated_data.get('last_update'):
print('>>> update')
return self.save_and_create_log(request, serializer, model_operation, model_name, store_id, now)
else:
print('>>> return 203')
return Response(serializer.data, status=status.HTTP_203_NON_AUTHORITATIVE_INFORMATION)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except model.DoesNotExist: # POST
print('>>> insert')
serializer = serializer_class(data=data, context={'request': request})
if serializer.is_valid():
return self.save_and_create_log(request, serializer, model_operation, model_name, store_id, now)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def save_and_create_log(self, request, serializer, model_operation, model_name, store_id, date):
instance = serializer.save()
self.create_and_save_model_log(
user=request.user,
model_operation=model_operation,
model_name=model_name,
model_id=instance.id,
store_id=store_id,
date=date
)
self.update_linked_data_access(instance, date)
return Response(serializer.data, status=status.HTTP_201_CREATED)
def delete_and_save_log(self, request, data_id, model_operation, model_name, store_id, date):
instance = get_data('tournaments', model_name, data_id)
self.update_linked_data_access(instance, date)
instance.delete()
# we delete all previous logs linked to the instance because they won't be needed anymore
ModelLog.objects.filter(model_id=data_id).delete()
self.create_and_save_model_log(
user=request.user,
model_operation=model_operation,
model_name=model_name,
model_id=data_id,
store_id=store_id,
date=date
)
return Response(status=status.HTTP_204_NO_CONTENT)
def update_linked_data_access(self, instance, date):
related_instances = instance.related_instances()
related_ids = [ri.id for ri in instance.related_instances()]
related_ids.append(instance.id)
data_access_list = DataAccess.objects.filter(model_id__in=related_ids)
for data_access in data_access_list:
data_access.last_hierarchy_update = date
data_access.save()
def create_and_save_model_log(self, user, model_operation, model_name, model_id, store_id, date):
model_log = ModelLog()
model_log.user = user
model_log.operation = model_operation
model_log.date = date
model_log.model_name = model_name
model_log.model_id = model_id
model_log.store_id = store_id
model_log.save()
def get(self, request, *args, **kwargs):
last_update = request.query_params.get('last_update')
if not last_update:
return Response({"error": "last_update parameter is required"}, status=status.HTTP_400_BAD_REQUEST)
print(f'/data GET: {last_update}')
logs = self.query_model_logs(last_update, request.user)
updates = defaultdict(dict)
deletions = defaultdict(list)
print(f'>>> log count = {len(logs)}')
for log in logs:
if log.operation in ['POST', 'PUT']:
data = get_serialized_data('tournaments', 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 = apps.get_model('tournaments', model_name=log.model_name)
instance = model.objects.get(id=log.model_id)
serializer_class = build_serializer_class(log.model_name)
serializer = serializer_class(instance)
# data = get_serialized_data('tournaments', log.model_name, log.model_id)
updates[log.model_name][log.model_id] = serializer.data
# instance = model.objects.get(id=log.model_id)
self.add_children_recursively(instance, updates)
self.add_parents_recursively(instance, updates)
elif log.operation == 'delete data access signal':
print(f'revoke access {log.model_id} - {log.store_id}')
deletions[log.model_name].append({'model_id': log.model_id, 'store_id': log.store_id})
# 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]
response_data = {
"updates": dict(updates),
"deletions": dict(deletions)
}
return Response(response_data, status=status.HTTP_200_OK)
def query_model_logs(self, last_update, user):
try:
last_update = timezone.datetime.fromisoformat(last_update)
except ValueError:
return Response({"error": f"Invalid date format for last_update: {last_update}"}, status=status.HTTP_400_BAD_REQUEST)
# get recently modified DataAccess
data_access_query = Q(last_hierarchy_update__gt=last_update) & (Q(shared_with__in=[user]) | Q(owner=user))
data_access_list = DataAccess.objects.filter(data_access_query) #.values_list('model_id', flat=True)
print(f'>> da count = {len(data_access_list)}')
# get ids of all recently updated related instances of each shared data
model_ids = []
for data_access in data_access_list:
instance = get_data('tournaments', data_access.model_name, data_access.model_id)
related_instances = instance.related_instances()
related_ids = [ri.id for ri in instance.related_instances() if ri.last_update > last_update]
model_ids.extend(related_ids)
model_ids.append(instance.id)
# get all ModelLog list since the last_update, from the user and from the data he has access to
log_query = Q(date__gt=last_update) & (Q(user=user) | Q(model_id__in=model_ids))
return ModelLog.objects.filter(log_query).order_by('date')
def add_children_recursively(self, instance, updates):
"""
Recursively add all children of an instance to the updates dictionary.
"""
# print(f"Instance class: {instance.__class__}")
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_class = build_serializer_class(child_model_name)
serializer = serializer_class(child)
updates[child_model_name][child.id] = serializer.data
self.add_children_recursively(child, updates)
def add_parents_recursively(self, instance, updates):
parent_models = instance.get_parents_by_model()
for parent_model_name, parent in parent_models.items():
# print(f'parent = {parent_model_name}')
if isinstance(parent, BaseModel):
serializer_class = build_serializer_class(parent_model_name)
serializer = serializer_class(parent)
updates[parent_model_name][parent.id] = serializer.data
self.add_parents_recursively(parent, updates)
# def get_data(self, model, log):
# instance = model.objects.get(id=log.model_id)
# serializer_class = build_serializer_class(log.model_name)
# serializer = serializer_class(instance)
# return serializer.data