Adds control over when to save ModelLogs

sync
Laurent 9 months ago
parent d9a5e98ad3
commit 3d6aa8282a
  1. 3
      api/views.py
  2. 29
      sync/models/data_access.py
  3. 4
      sync/models/model_log.py
  4. 85
      sync/signals.py
  5. 2
      sync/views.py
  6. 2
      tournaments/admin.py
  7. 18
      tournaments/migrations/0111_customuser_should_synchronize.py
  8. 3
      tournaments/models/custom_user.py

@ -46,6 +46,7 @@ class CustomAuthToken(APIView):
if user: if user:
# user.device_id = device_id # user.device_id = device_id
# user.save() # user.save()
# self.create_or_update_device(user, device_id)
# token, created = Token.objects.get_or_create(user=user) # token, created = Token.objects.get_or_create(user=user)
# return Response({'token': token.key}) # return Response({'token': token.key})
@ -90,6 +91,8 @@ class Logout(APIView):
request.user.device_id = None request.user.device_id = None
request.user.save() request.user.save()
Device.objects.filter(id=device_id).delete()
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
@api_view(['GET']) @api_view(['GET'])

@ -37,20 +37,21 @@ class DataAccess(BaseModel):
if isinstance(obj, SideStoreModel): if isinstance(obj, SideStoreModel):
store_id = obj.store_id store_id = obj.store_id
existing_log = ModelLog.objects.filter(users__in=users, model_id=self.model_id, operation=operation).first() for user in users:
if existing_log: existing_log = ModelLog.objects.filter(user=user, model_id=self.model_id, operation=operation).first()
existing_log.date = timezone.now() if existing_log:
existing_log.model_operation = operation existing_log.date = timezone.now()
existing_log.save() existing_log.model_operation = operation
else: existing_log.save()
model_log = ModelLog.objects.create( else:
model_id=self.model_id, ModelLog.objects.create(
model_name=self.model_name, user=user,
operation=operation, model_id=self.model_id,
date=timezone.now(), model_name=self.model_name,
store_id=store_id operation=operation,
) date=timezone.now(),
model_log.users.set(users) store_id=store_id
)
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
else: else:

@ -1,10 +1,6 @@
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
import uuid
from tkinter.constants import CASCADE
class ModelOperation(models.TextChoices): class ModelOperation(models.TextChoices):
POST = 'POST', 'POST' POST = 'POST', 'POST'
PUT = 'PUT', 'PUT' PUT = 'PUT', 'PUT'

@ -2,7 +2,7 @@ from django.db.models.signals import pre_save, post_save, pre_delete, post_delet
from django.db import models, transaction from django.db import models, transaction
from django.dispatch import receiver from django.dispatch import receiver
from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreModel from .models import DataAccess, ModelLog, ModelOperation, BaseModel, SideStoreModel, Device
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils import timezone from django.utils import timezone
@ -10,14 +10,26 @@ from .ws_sender import websocket_sender
User = get_user_model() User = get_user_model()
@receiver(post_save, sender=Device)
def device_created(sender, instance, **kwargs):
if not instance.user:
return
evaluate_if_user_should_sync(instance.user)
@receiver(pre_delete, sender=Device)
def device_pre_delete(sender, instance, **kwargs):
instance._user = instance.user if instance.user else None
@receiver(post_delete, sender=Device)
def device_post_delete(sender, instance, **kwargs):
if not hasattr(instance, '_user') or not instance._user:
return
evaluate_if_user_should_sync(instance._user)
@receiver(pre_save) @receiver(pre_save)
def presave_handler(sender, instance, **kwargs): def presave_handler(sender, instance, **kwargs):
synchronization_prepare(sender, instance, **kwargs) synchronization_prepare(sender, instance, **kwargs)
# @receiver(pre_delete)
# def predelete_handler(sender, instance, **kwargs):
# synchronization_prepare(sender, instance, False, **kwargs)
def synchronization_prepare(sender, instance, **kwargs): def synchronization_prepare(sender, instance, **kwargs):
signal = kwargs.get('signal') signal = kwargs.get('signal')
@ -122,14 +134,15 @@ def save_model_log(users, model_operation, model_name, model_id, store_id, devic
with transaction.atomic(): with transaction.atomic():
for user in users: for user in users:
model_log = ModelLog() if user.should_synchronize:
model_log.user = user model_log = ModelLog()
model_log.operation = model_operation model_log.user = user
model_log.model_name = model_name model_log.operation = model_operation
model_log.model_id = model_id model_log.model_name = model_name
model_log.store_id = store_id model_log.model_id = model_id
model_log.device_id = device_id model_log.store_id = store_id
model_log.save() model_log.device_id = device_id
model_log.save()
# print(f'ML users = {len(users)}') # print(f'ML users = {len(users)}')
# existing_log = ModelLog.objects.filter(users__in=users, model_id=model_id, operation=model_operation).first() # existing_log = ModelLog.objects.filter(users__in=users, model_id=model_id, operation=model_operation).first()
@ -177,18 +190,6 @@ def detect_foreign_key_changes(sender, instance, device_id):
'new_value': new_value 'new_value': new_value
}) })
# for data_access in data_access_list:
# if old_value:
# model_name = old_value.__class__.__name__
# save_model_log(data_access.concerned_users(), 'REVOKE_ACCESS', model_name, old_value.id, old_value.get_store_id(), device_id)
# if new_value:
# model_name = new_value.__class__.__name__
# save_model_log(data_access.concerned_users(), 'GRANT_ACCESS', model_name, new_value.id, new_value.get_store_id(), device_id)
# # REVOKE access for old_value and GRANT new_value
# print(f"Foreign key changed in {sender.__name__}: "
# f"{field.name} from {old_value} to {new_value}")
def process_foreign_key_changes(sender, instance, device_id, **kwargs): def process_foreign_key_changes(sender, instance, device_id, **kwargs):
if hasattr(instance, '_fk_changes'): if hasattr(instance, '_fk_changes'):
for change in instance._fk_changes: for change in instance._fk_changes:
@ -206,6 +207,8 @@ def process_foreign_key_changes(sender, instance, device_id, **kwargs):
@receiver(post_delete) @receiver(post_delete)
def delete_data_access_if_necessary(sender, instance, **kwargs): def delete_data_access_if_necessary(sender, instance, **kwargs):
if not isinstance(instance, BaseModel):
return
if hasattr(instance, 'id'): if hasattr(instance, 'id'):
DataAccess.objects.filter(model_id=instance.id).delete() DataAccess.objects.filter(model_id=instance.id).delete()
@ -225,10 +228,24 @@ def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs):
for user_id in pk_set: for user_id in pk_set:
websocket_sender.send_user_message(user_id, device_id) websocket_sender.send_user_message(user_id, device_id)
for user in users:
evaluate_if_user_should_sync(user)
@receiver(post_save, sender=DataAccess)
def data_access_post_save(sender, instance, **kwargs):
if instance.related_user:
evaluate_if_user_should_sync(instance.related_user)
@receiver(pre_delete, sender=DataAccess) @receiver(pre_delete, sender=DataAccess)
def revoke_access_after_delete(sender, instance, **kwargs): def revoke_access_after_delete(sender, instance, **kwargs):
instance.create_revoke_access_log() instance.create_revoke_access_log()
instance._user = instance.related_user
@receiver(post_delete, sender=DataAccess)
def data_access_post_delete(sender, instance, **kwargs):
if not hasattr(instance, '_user') or not instance._user:
return
evaluate_if_user_should_sync(instance._user)
def related_users(instance): def related_users(instance):
users = set() users = set()
@ -259,7 +276,23 @@ def related_data_access(instance):
return instances_related_data_access(instance, related_instances) return instances_related_data_access(instance, related_instances)
def instances_related_data_access(instance, related_instances): def instances_related_data_access(instance, related_instances):
# related_instances = instance.related_instances()
related_ids = [ri.id for ri in related_instances] related_ids = [ri.id for ri in related_instances]
related_ids.append(instance.id) related_ids.append(instance.id)
return DataAccess.objects.filter(model_id__in=related_ids) return DataAccess.objects.filter(model_id__in=related_ids)
def evaluate_if_user_should_sync(user):
should_synchronize = False
if user.devices.count() > 1:
should_synchronize = True
elif DataAccess.objects.filter(
models.Q(shared_with=user) |
models.Q(related_user=user)
).count() > 0:
should_synchronize = True
print(f'should_synchronize = {should_synchronize}')
with transaction.atomic():
user.should_synchronize = should_synchronize
# if we go from True to False we might want to delete ModelLog once the last device has synchronized
user.save()

@ -160,7 +160,7 @@ class SynchronizationApi(HierarchyApiView):
except Exception as e: except Exception as e:
response_status = status.HTTP_400_BAD_REQUEST response_status = status.HTTP_400_BAD_REQUEST
print(f'other exception: {str(e)}') print(f'other exception: {str(e)}, type: {type(e)}, args: {e.args}, traceback: {e.__traceback__}')
message = str(e) message = str(e)
results.append({ results.append({

@ -22,7 +22,7 @@ class CustomUserAdmin(UserAdmin):
'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods',
'summons_display_format', 'summons_display_entry_fee', 'summons_use_full_custom_message', 'summons_display_format', 'summons_display_entry_fee', 'summons_use_full_custom_message',
'match_formats_default_duration', 'bracket_match_format_preference', 'group_stage_match_format_preference', 'match_formats_default_duration', 'bracket_match_format_preference', 'group_stage_match_format_preference',
'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'groups', 'origin', 'agents' 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'groups', 'origin', 'agents', 'should_synchronize'
]}), ]}),
] ]
add_fieldsets = [ add_fieldsets = [

@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2025-02-14 16:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tournaments', '0110_remove_failedapicall_creation_date_and_more'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='should_synchronize',
field=models.BooleanField(default=False),
),
]

@ -37,6 +37,7 @@ class CustomUser(AbstractUser):
loser_bracket_mode = models.IntegerField(default=0) loser_bracket_mode = models.IntegerField(default=0)
origin = models.IntegerField(default=enums.UserOrigin.ADMIN, choices=enums.UserOrigin.choices, null=True, blank=True) origin = models.IntegerField(default=enums.UserOrigin.ADMIN, choices=enums.UserOrigin.choices, null=True, blank=True)
should_synchronize = models.BooleanField(default=False)
### ### ### ### ### ### ### ### ### ### ### WARNING ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### WARNING ### ### ### ### ### ### ### ### ### ###
### WARNING : Any added field MUST be inserted in the method below: fields_for_update() ### ### WARNING : Any added field MUST be inserted in the method below: fields_for_update() ###
@ -49,7 +50,7 @@ class CustomUser(AbstractUser):
'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods', 'summons_message_body', 'summons_message_signature', 'summons_available_payment_methods',
'summons_display_format', 'summons_display_entry_fee', 'summons_display_format', 'summons_display_entry_fee',
'summons_use_full_custom_message', 'match_formats_default_duration', 'bracket_match_format_preference', 'summons_use_full_custom_message', 'match_formats_default_duration', 'bracket_match_format_preference',
'group_stage_match_format_preference', 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'origin', 'agents'] 'group_stage_match_format_preference', 'loser_bracket_match_format_preference', 'device_id', 'loser_bracket_mode', 'origin', 'agents', 'should_synchronize']
def __str__(self): def __str__(self):
return self.username return self.username

Loading…
Cancel
Save