Fixes and improvements

sync
Laurent 11 months ago
parent d50e391d16
commit d0e49971b5
  1. 5
      api/serializers.py
  2. 4
      api/urls.py
  3. 39
      api/views.py
  4. 49
      sync/models/data_access.py
  5. 5
      sync/registry.py
  6. 243
      sync/views.py

@ -85,6 +85,11 @@ class CustomUserSerializer(serializers.ModelSerializer): ### the one matching th
model = CustomUser
fields = CustomUser.fields_for_update()
class ShortUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id', 'first_name', 'last_name']
class ClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club

@ -3,10 +3,11 @@ from rest_framework import routers
from rest_framework.authtoken.views import obtain_auth_token
from . import views
from sync.views import SynchronizationApi, DataAccessViewSet
from sync.views import SynchronizationApi, UserDataAccessApi, DataAccessViewSet
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'users-search', views.ShortUserViewSet)
router.register(r'clubs', views.ClubViewSet)
router.register(r'tournaments', views.TournamentViewSet)
router.register(r'events', views.EventViewSet)
@ -28,6 +29,7 @@ urlpatterns = [
path('', include(router.urls)),
path('data/', SynchronizationApi.as_view(), name="data"),
path('user-data-access/', UserDataAccessApi.as_view(), name="user-data-access"),
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
path("user-by-token/", views.user_by_token, name="user_by_token"),

@ -1,4 +1,4 @@
from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, CustomUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer
from .serializers import ClubSerializer, CourtSerializer, DateIntervalSerializer, TournamentSerializer, UserSerializer, ChangePasswordSerializer, EventSerializer, RoundSerializer, GroupStageSerializer, MatchSerializer, TeamScoreSerializer, TeamRegistrationSerializer, PlayerRegistrationSerializer, LiveMatchSerializer, PurchaseSerializer, CustomUserSerializer, FailedApiCallSerializer, LogSerializer, DeviceTokenSerializer, ShortUserSerializer
from tournaments.models import Club, Tournament, CustomUser, Event, Round, GroupStage, Match, TeamScore, TeamRegistration, PlayerRegistration, Court, DateInterval, Purchase, FailedApiCall, Log, DeviceToken
from rest_framework import viewsets, permissions
@ -100,6 +100,23 @@ class UserViewSet(viewsets.ModelViewSet):
return UserSerializer
return self.serializer_class
class ShortUserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = ShortUserSerializer
permission_classes = [] # Users are public whereas the other requests are only for logged users
def get_queryset(self):
queryset = CustomUser.objects.all()
search_term = self.request.query_params.get('search', None)
if search_term:
queryset = queryset.filter(
Q(first_name__icontains=search_term) |
Q(last_name__icontains=search_term)
)
return queryset
class ClubViewSet(viewsets.ModelViewSet):
queryset = Club.objects.all()
serializer_class = ClubSerializer
@ -118,11 +135,7 @@ class EventViewSet(viewsets.ModelViewSet):
return []
# return self.queryset.filter(creator=self.request.user)
return self.queryset.filter(
Q(creator=self.request.user) |
Q(id__in=DataAccess.objects.filter(
shared_with=self.request.user,
model_name=self.queryset.model.__name__
).values_list('model_id', flat=True))
Q(creator=self.request.user)
)
class TournamentViewSet(viewsets.ModelViewSet):
@ -134,11 +147,7 @@ class TournamentViewSet(viewsets.ModelViewSet):
return []
return self.queryset.filter(
Q(event__creator=self.request.user) |
Q(id__in=DataAccess.objects.filter(
shared_with=self.request.user,
model_name=self.queryset.model.__name__
).values_list('model_id', flat=True))
Q(event__creator=self.request.user)
)
class PurchaseViewSet(viewsets.ModelViewSet):
@ -151,12 +160,8 @@ class PurchaseViewSet(viewsets.ModelViewSet):
# return self.queryset.filter(user=self.request.user)
return self.queryset.filter(
Q(user=self.request.user) |
Q(id__in=DataAccess.objects.filter(
shared_with=self.request.user,
model_name=self.queryset.model.__name__
).values_list('model_id', flat=True))
)
Q(user=self.request.user)
)
def patch(self, request, pk):
raise MethodNotAllowed('PATCH')

@ -4,7 +4,7 @@ from django.apps import apps
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from ..registry import sync_registry
import uuid
from . import ModelLog, SideStoreModel, BaseModel
@ -23,26 +23,29 @@ class DataAccess(BaseModel):
def create_access_log(self, users, operation):
"""Create an access log for a list of users """
model_class = apps.get_model('tournaments', self.model_name)
try:
obj = model_class.objects.get(id=self.model_id)
store_id = None
if isinstance(obj, SideStoreModel):
store_id = obj.store_id
model_class = sync_registry.get_model(self.model_name)
if model_class:
try:
obj = model_class.objects.get(id=self.model_id)
store_id = None
if isinstance(obj, SideStoreModel):
store_id = obj.store_id
existing_log = ModelLog.objects.filter(users__in=users, model_id=self.model_id, operation=operation).first()
if existing_log:
existing_log.date = timezone.now()
existing_log.model_operation = operation
existing_log.save()
else:
model_log = ModelLog.objects.create(
model_id=self.model_id,
model_name=self.model_name,
operation=operation,
date=timezone.now(),
store_id=store_id
)
model_log.users.set(users)
except ObjectDoesNotExist:
pass
existing_log = ModelLog.objects.filter(users__in=users, model_id=self.model_id, operation=operation).first()
if existing_log:
existing_log.date = timezone.now()
existing_log.model_operation = operation
existing_log.save()
else:
model_log = ModelLog.objects.create(
model_id=self.model_id,
model_name=self.model_name,
operation=operation,
date=timezone.now(),
store_id=store_id
)
model_log.users.set(users)
except ObjectDoesNotExist:
pass
else:
print(f'model not found: {self.model_name}')

@ -5,12 +5,10 @@ from .models import BaseModel
class SyncRegistry:
def __init__(self):
self._registry = {}
self.load_sync_apps()
def load_sync_apps(self):
sync_apps = getattr(settings, 'SYNC_APPS', {})
for app_label, config in sync_apps.items():
print(f'app_label = {app_label}')
app_models = apps.get_app_config(app_label).get_models()
for model in app_models:
if hasattr(model, '_meta') and not model._meta.abstract:
@ -27,10 +25,11 @@ class SyncRegistry:
return True
def register(self, model):
# print(f'>>> Registers {model.__name__}')
self._registry[model.__name__] = model
def get_model(self, model_name):
if not self._registry:
self.load_sync_apps()
return self._registry.get(model_name)
# Create singleton instance

@ -21,7 +21,62 @@ from .models import ModelLog, BaseModel, SideStoreModel, DataAccess
from .registry import sync_registry
class SynchronizationApi(APIView):
class HierarchyApiView(APIView):
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 SynchronizationApi(HierarchyApiView):
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
@ -74,70 +129,6 @@ class SynchronizationApi(APIView):
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 +
@ -235,58 +226,86 @@ class SynchronizationApi(APIView):
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):
class UserDataAccessApi(HierarchyApiView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
# Get all GRANT_ACCESS and REVOKE_ACCESS logs for the user, ordered by date
all_logs = ModelLog.objects.filter(
Q(users=request.user) &
(Q(operation='GRANT_ACCESS') | Q(operation='REVOKE_ACCESS'))
).order_by('date')
# Track latest status for each (model_name, model_id)
active_grants = {}
# Process logs chronologically to determine current access state
for log in all_logs:
if log.operation == 'GRANT_ACCESS':
active_grants[log.model_id] = log
elif log.operation == 'REVOKE_ACCESS':
if log.model_id in active_grants and active_grants[log.model_id].date < log.date:
del active_grants[log.model_id]
# Convert active_grants dict to list of grant logs
active_grants = list(active_grants.values())
# Prepare response data structure
data_by_model = defaultdict(dict)
print(f'>>> grants = {len(active_grants)}')
for log in active_grants:
try:
model = sync_registry.get_model(log.model_name)
instance = model.objects.get(id=log.model_id)
# Get the base data
serializer = get_serializer(instance, log.model_name)
data_by_model[log.model_name][log.model_id] = serializer.data
# Add parents & children recursively
self.add_children_recursively(instance, data_by_model)
self.add_parents_recursively(instance, data_by_model)
except ObjectDoesNotExist:
continue
# Convert dictionary values to lists
response_data = {
model_name: list(model_data.values())
for model_name, model_data in data_by_model.items()
}
print(f'response_data = {response_data}')
return Response(response_data, status=status.HTTP_200_OK)
# def _add_children_recursively(self, instance, data_dict):
# """
# Recursively add all children of an instance to the data 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)
# data_dict[child_model_name][child.id] = serializer.data
# self._add_children_recursively(child, data_dict)
# def _add_parents_recursively(self, instance, data_dict):
# """
# Recursively add all parents of an instance to the data dictionary.
# """
# 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)
# serializer = get_serializer(parent, parent_model_name)
# data_dict[parent_model_name][parent.id] = serializer.data
# self._add_parents_recursively(parent, data_dict)
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()

Loading…
Cancel
Save