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/sync/views.py

298 lines
13 KiB

# from django.shortcuts import render
from .serializers import DataAccessSerializer
from django.db.models import Q
from rest_framework import viewsets
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.core.exceptions import ObjectDoesNotExist
from collections import defaultdict
from urllib.parse import unquote
from .utils import get_serializer, build_serializer_class, get_data, get_serialized_data
from .models import ModelLog, BaseModel, SideStoreModel, DataAccess
from .registry import sync_registry
class SynchronizationApi(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)
data['last_updated_by'] = request.user.id # always refresh the user performing the operation
# model = apps.get_model(app_label='tournaments', model_name=model_name)
model = sync_registry.get_model(model_name)
if model_operation == 'POST':
serializer = serializer_class(data=data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
elif model_operation == 'PUT':
data_id = data.get('id')
instance = get_data(model_name, data_id)
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')
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
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)
elif model_operation == 'DELETE':
data_id = data.get('id')
try:
instance = get_data(model_name, data_id)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except model.DoesNotExist: # POST
delete = ModelLog.objects.filter(model_id=data_id, operation=model_operation).first()
if delete:
return Response(status=status.HTTP_208_ALREADY_REPORTED)
else:
return Response(status=status.HTTP_404_NOT_FOUND)
# def get(self, request, *args, **kwargs):
# last_update_str = request.query_params.get('last_update')
# decoded_last_update = unquote(last_update_str)
# 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)
# logs = self.query_model_logs(last_update, request.user)
# updates = defaultdict(dict)
# deletions = defaultdict(list)
# grants = defaultdict(dict)
# revocations = defaultdict(list)
# revocation_parents = defaultdict(dict)
# last_log_date = None
# for log in logs:
# last_log_date = log.date
# self._process_log(log, updates, deletions, grants, revocations, revocation_parents)
# response_data = {
# "updates": {k: list(v.values()) for k, v in updates.items()},
# "deletions": dict(deletions),
# "grants": {k: list(v.values()) for k, v in grants.items()},
# "revocations": dict(revocations),
# "revocation_parents": {k: list(v.values()) for k, v in revocation_parents.items()},
# "date": last_log_date
# }
# return Response(response_data, status=status.HTTP_200_OK)
# def _process_log(self, log, updates, deletions, grants, revocations, revocation_parents):
# try:
# 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 = 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':
# revocations[log.model_name].append({
# 'model_id': log.model_id,
# 'store_id': log.store_id
# })
# model = apps.get_model('tournaments', model_name=log.model_name)
# try:
# instance = model.objects.get(id=log.model_id)
# self.add_parents_recursively(instance, revocation_parents, minimal=True)
# except model.DoesNotExist:
# pass
# except ObjectDoesNotExist:
# pass
def get(self, request, *args, **kwargs):
last_update_str = request.query_params.get('last_update')
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)
print(f'>>> log count = {len(logs)}')
updates = defaultdict(dict)
deletions = defaultdict(list)
grants = defaultdict(dict)
revocations = defaultdict(list) # New dictionary for revocations
revocation_parents = defaultdict(dict)
last_log_date = None
for log in logs:
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 = sync_registry.get_model(log.model_name)
# 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)
serializer = get_serializer(instance, log.model_name)
grants[log.model_name][log.model_id] = serializer.data
# instance = model.objects.get(id=log.model_id)
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}')
# Add to revocations instead of deletions
revocations[log.model_name].append({
'model_id': log.model_id,
'store_id': log.store_id
})
# Get the model instance and add its parents to revocation_parents
model = sync_registry.get_model(log.model_name)
try:
instance = model.objects.get(id=log.model_id)
self.add_parents_recursively(instance, revocation_parents, minimal=True)
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": dict(revocation_parents),
"date": last_log_date
}
print(f'response_data = {response_data}')
return Response(response_data, status=status.HTTP_200_OK)
def query_model_logs(self, last_update, user):
# print(f'last_update = {last_update}')
log_query = Q(date__gt=last_update) & Q(users=user)
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 = get_serializer(child, child_model_name)
# 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 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)
# Add full serialized data
# serializer_class = build_serializer_class(parent_model_name)
# serializer = serializer_class(parent)
dictionary[parent_model_name][parent.id] = serializer.data
self.add_parents_recursively(parent, dictionary, minimal)
class DataAccessViewSet(viewsets.ModelViewSet):
queryset = DataAccess.objects.all()
serializer_class = DataAccessSerializer
def get_queryset(self):
if self.request.user:
return self.queryset.filter(Q(owner=self.request.user) | Q(shared_with__in=[self.request.user]))
return []