|
|
|
|
@ -11,6 +11,8 @@ from .ws_sender import websocket_sender |
|
|
|
|
from .registry import device_registry, related_users_registry |
|
|
|
|
|
|
|
|
|
import logging |
|
|
|
|
import sys |
|
|
|
|
import traceback |
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
|
@ -21,31 +23,41 @@ User = get_user_model() |
|
|
|
|
@receiver([pre_save, pre_delete]) |
|
|
|
|
def presave_handler(sender, instance, **kwargs): |
|
|
|
|
|
|
|
|
|
# some other classes are excluded in settings_app.py: SYNC_APPS |
|
|
|
|
if not isinstance(instance, (BaseModel, User)): |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
signal = kwargs.get('signal') |
|
|
|
|
# avoid crash in manage.py createsuperuser + delete user in the admin |
|
|
|
|
if isinstance(instance, User) and (instance._state.db is None or signal == pre_delete): |
|
|
|
|
return |
|
|
|
|
try: |
|
|
|
|
# some other classes are excluded in settings_app.py: SYNC_APPS |
|
|
|
|
if not isinstance(instance, (BaseModel, User)) or isinstance(instance, DataAccess): |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
users = related_users(instance) |
|
|
|
|
signal = kwargs.get('signal') |
|
|
|
|
# avoid crash in manage.py createsuperuser + delete user in the admin |
|
|
|
|
if isinstance(instance, User) and (instance._state.db is None or signal == pre_delete): |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
related_users_registry.register(instance.id, users) |
|
|
|
|
# user_ids = [user.id for user in users] |
|
|
|
|
users = related_users(instance) |
|
|
|
|
|
|
|
|
|
related_users_registry.register(instance.id, users) |
|
|
|
|
|
|
|
|
|
if signal == pre_save: |
|
|
|
|
detect_foreign_key_changes_for_shared_instances(sender, instance) |
|
|
|
|
sig_type = 'pre_save' |
|
|
|
|
elif signal == pre_delete: |
|
|
|
|
# if hasattr(instance, 'id'): |
|
|
|
|
# try: |
|
|
|
|
# data_access_list = DataAccess.objects.filter(model_id=instance.id) |
|
|
|
|
# if data_access_list: |
|
|
|
|
# logger.info(f'>>> {instance.__class__.__name__} {instance.id} : delete {data_access_list.count()} DataAccess') |
|
|
|
|
# data_access_list.delete() |
|
|
|
|
# except Exception as e: |
|
|
|
|
# logger.info(f'*** ERRRRRRR: {e}') |
|
|
|
|
# logger.info(traceback.format_exc()) |
|
|
|
|
# raise |
|
|
|
|
|
|
|
|
|
sig_type = 'pre_delete' |
|
|
|
|
# logger.info(f'* {sig_type} : {instance.__class__.__name__} > impacted users = {users}') |
|
|
|
|
|
|
|
|
|
if signal == pre_save: |
|
|
|
|
detect_foreign_key_changes_for_shared_instances(sender, instance) |
|
|
|
|
sig_type = 'pre_save' |
|
|
|
|
elif signal == pre_delete: |
|
|
|
|
if hasattr(instance, 'id'): |
|
|
|
|
data_access_list = DataAccess.objects.filter(model_id=instance.id) |
|
|
|
|
logger.info(f'>>> delete {data_access_list.count()} DataAccess') |
|
|
|
|
data_access_list.delete() |
|
|
|
|
sig_type = 'pre_delete' |
|
|
|
|
# logger.info(f'* {sig_type} : {instance.__class__.__name__} > impacted users = {users}') |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR: {e}') |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
@receiver([post_save, post_delete]) |
|
|
|
|
def synchronization_notifications(sender, instance, created=False, **kwargs): |
|
|
|
|
@ -59,47 +71,31 @@ def synchronization_notifications(sender, instance, created=False, **kwargs): |
|
|
|
|
if not isinstance(instance, BaseModel) and not isinstance(instance, User): |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
process_foreign_key_changes(sender, instance, **kwargs) |
|
|
|
|
|
|
|
|
|
signal = kwargs.get('signal') |
|
|
|
|
save_model_log_if_possible(instance, signal, created) |
|
|
|
|
notify_impacted_users(instance) |
|
|
|
|
|
|
|
|
|
related_users_registry.unregister(instance.id) |
|
|
|
|
try: |
|
|
|
|
process_foreign_key_changes(sender, instance, **kwargs) |
|
|
|
|
signal = kwargs.get('signal') |
|
|
|
|
save_model_log_if_possible(instance, signal, created) |
|
|
|
|
notify_impacted_users(instance) |
|
|
|
|
related_users_registry.unregister(instance.id) |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR2: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
def notify_impacted_users(instance): |
|
|
|
|
# print(f'*** notify_impacted_users for instance: {instance}') |
|
|
|
|
# user_ids = set() |
|
|
|
|
# # add impacted users |
|
|
|
|
# if isinstance(instance, User): |
|
|
|
|
# user_ids.add(instance.id) |
|
|
|
|
# elif isinstance(instance, BaseModel): |
|
|
|
|
# owner = instance.last_updated_by |
|
|
|
|
# if owner: |
|
|
|
|
# user_ids.add(owner.id) |
|
|
|
|
|
|
|
|
|
# if isinstance(instance, BaseModel): |
|
|
|
|
# if hasattr(instance, '_users_to_notify'): |
|
|
|
|
# user_ids.update(instance._users_to_notify) |
|
|
|
|
# else: |
|
|
|
|
# print('no users to notify') |
|
|
|
|
|
|
|
|
|
device_id = device_registry.get_device_id(instance.id) |
|
|
|
|
users = related_users_registry.get_users(instance.id) |
|
|
|
|
|
|
|
|
|
if users: |
|
|
|
|
user_ids = [user.id for user in users] |
|
|
|
|
websocket_sender.send_message(user_ids, device_id) |
|
|
|
|
# print(f'notify device: {device_id}, users = {user_ids}') |
|
|
|
|
# for user_id in user_ids: |
|
|
|
|
# websocket_sender.send_user_message(user_id, device_id) |
|
|
|
|
|
|
|
|
|
device_registry.unregister(instance.id) |
|
|
|
|
|
|
|
|
|
def save_model_log_if_possible(instance, signal, created): |
|
|
|
|
|
|
|
|
|
users = related_users_registry.get_users(instance.id) |
|
|
|
|
logger.debug(f'*** save_model_log_if_possible >>> users from registry = {users}, instance = {instance}') |
|
|
|
|
# logger.info(f'*** save_model_log_if_possible >>> users from registry = {users}, instance = {instance}') |
|
|
|
|
|
|
|
|
|
if not users: |
|
|
|
|
logger.warning(f'!!! Registry returned empty users for instance {instance.id} ({instance.__class__.__name__})') |
|
|
|
|
@ -121,8 +117,8 @@ def save_model_log_if_possible(instance, signal, created): |
|
|
|
|
if isinstance(instance, SideStoreModel): |
|
|
|
|
store_id = instance.store_id |
|
|
|
|
|
|
|
|
|
if operation == ModelOperation.DELETE: # delete now unnecessary logs |
|
|
|
|
ModelLog.objects.filter(model_id=instance.id).delete() |
|
|
|
|
# if operation == ModelOperation.DELETE: # delete now unnecessary logs |
|
|
|
|
# ModelLog.objects.filter(model_id=instance.id).delete() |
|
|
|
|
|
|
|
|
|
# user_ids = [user.id for user in users] |
|
|
|
|
# # print(f'users to notify: {user_ids}') |
|
|
|
|
@ -133,16 +129,14 @@ def save_model_log_if_possible(instance, signal, created): |
|
|
|
|
logger.info(f'!!! Model Log could not be created because no linked user could be found: {instance.__class__.__name__} {instance}, {signal}') |
|
|
|
|
|
|
|
|
|
def save_model_log(users, model_operation, model_name, model_id, store_id): |
|
|
|
|
|
|
|
|
|
device_id = device_registry.get_device_id(model_id) |
|
|
|
|
|
|
|
|
|
logger.info(f'*** creating ModelLogs for: {model_operation} {model_name} : {users}') |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
with transaction.atomic(): |
|
|
|
|
created_logs = [] |
|
|
|
|
for user in users: |
|
|
|
|
# logger.info(f'Creating ModelLog for user {user.id} ({user.username})') |
|
|
|
|
# logger.info(f'Creating ModelLog for user {user.id} - user exists: {User.objects.filter(id=user.id).exists()}') |
|
|
|
|
model_log = ModelLog( |
|
|
|
|
user=user, |
|
|
|
|
operation=model_operation, |
|
|
|
|
@ -152,20 +146,19 @@ def save_model_log(users, model_operation, model_name, model_id, store_id): |
|
|
|
|
device_id=device_id |
|
|
|
|
) |
|
|
|
|
model_log.save() |
|
|
|
|
# logger.info(f'ModelLog saved with ID: {model_log.id}') |
|
|
|
|
created_logs.append(model_log.id) |
|
|
|
|
# logger.info(f'Successfully created ModelLog {model_log.id}') |
|
|
|
|
|
|
|
|
|
# logger.info(f'*** Successfully created {len(created_logs)} ModelLogs: {created_logs}') |
|
|
|
|
# Immediate verification within transaction |
|
|
|
|
immediate_count = ModelLog.objects.filter(id__in=created_logs).count() |
|
|
|
|
# logger.info(f'*** Within transaction: Created {len(created_logs)}, found {immediate_count}') |
|
|
|
|
|
|
|
|
|
# Verify ModelLogs were actually persisted |
|
|
|
|
persisted_count = ModelLog.objects.filter(id__in=created_logs).count() |
|
|
|
|
if persisted_count != len(created_logs): |
|
|
|
|
logger.error(f'*** PERSISTENCE VERIFICATION FAILED! Created {len(created_logs)} ModelLogs but only {persisted_count} were persisted to database') |
|
|
|
|
else: |
|
|
|
|
logger.info(f'*** PERSISTENCE VERIFIED: All {persisted_count} ModelLogs successfully persisted') |
|
|
|
|
# Verification after transaction commits |
|
|
|
|
persisted_count = ModelLog.objects.filter(id__in=created_logs).count() |
|
|
|
|
# logger.info(f'*** After transaction: Created {len(created_logs)}, persisted {persisted_count}') |
|
|
|
|
|
|
|
|
|
except Exception as e: |
|
|
|
|
logger.error(f'*** FAILED to create ModelLogs for: {model_operation} {model_name}, users: {[u.id for u in users]}, error: {e}', exc_info=True) |
|
|
|
|
logger.error(f'*** Exception during ModelLog creation: {e}', exc_info=True) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
# with transaction.atomic(): |
|
|
|
|
@ -209,7 +202,6 @@ def detect_foreign_key_changes_for_shared_instances(sender, instance): |
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
data_access_list = related_data_access(instance) |
|
|
|
|
# print(f'FK change > DA count = {len(data_access_list)}') |
|
|
|
|
if data_access_list: |
|
|
|
|
try: |
|
|
|
|
old_instance = sender.objects.get(pk=instance.pk) |
|
|
|
|
@ -278,12 +270,12 @@ def process_foreign_key_changes(sender, instance, **kwargs): |
|
|
|
|
|
|
|
|
|
### Data Access |
|
|
|
|
|
|
|
|
|
# @receiver(post_delete) |
|
|
|
|
# def delete_data_access_if_necessary(sender, instance, **kwargs): |
|
|
|
|
# if not isinstance(instance, BaseModel): |
|
|
|
|
# return |
|
|
|
|
# if hasattr(instance, 'id'): |
|
|
|
|
# DataAccess.objects.filter(model_id=instance.id).delete() |
|
|
|
|
@receiver(post_delete) |
|
|
|
|
def delete_data_access_if_necessary(sender, instance, **kwargs): |
|
|
|
|
if not isinstance(instance, BaseModel): |
|
|
|
|
return |
|
|
|
|
if hasattr(instance, 'id'): |
|
|
|
|
DataAccess.objects.filter(model_id=instance.id).delete() |
|
|
|
|
|
|
|
|
|
@receiver(m2m_changed, sender=DataAccess.shared_with.through) |
|
|
|
|
def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs): |
|
|
|
|
@ -309,26 +301,41 @@ def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs): |
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=DataAccess) |
|
|
|
|
def data_access_post_save(sender, instance, **kwargs): |
|
|
|
|
instance.add_references() # create DataAccess references on hierarchy |
|
|
|
|
try: |
|
|
|
|
instance.add_references() # create DataAccess references on hierarchy |
|
|
|
|
|
|
|
|
|
if instance.related_user: |
|
|
|
|
evaluate_if_user_should_sync(instance.related_user) |
|
|
|
|
if instance.related_user: |
|
|
|
|
evaluate_if_user_should_sync(instance.related_user) |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR3: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
@receiver(pre_delete, sender=DataAccess) |
|
|
|
|
def revoke_access_after_delete(sender, instance, **kwargs): |
|
|
|
|
instance.cleanup_references() |
|
|
|
|
instance.create_revoke_access_log() |
|
|
|
|
related_users_registry.register(instance.id, instance.shared_with.all()) |
|
|
|
|
try: |
|
|
|
|
instance.cleanup_references() |
|
|
|
|
instance.create_revoke_access_log() |
|
|
|
|
related_users_registry.register(instance.id, instance.shared_with.all()) |
|
|
|
|
|
|
|
|
|
instance._user = instance.related_user |
|
|
|
|
instance._user = instance.related_user |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR4: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
@receiver(post_delete, sender=DataAccess) |
|
|
|
|
def data_access_post_delete(sender, instance, **kwargs): |
|
|
|
|
notify_impacted_users(instance) |
|
|
|
|
try: |
|
|
|
|
notify_impacted_users(instance) |
|
|
|
|
|
|
|
|
|
if not hasattr(instance, '_user') or not instance._user: |
|
|
|
|
return |
|
|
|
|
evaluate_if_user_should_sync(instance._user) |
|
|
|
|
if not hasattr(instance, '_user') or not instance._user: |
|
|
|
|
return |
|
|
|
|
evaluate_if_user_should_sync(instance._user) |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR5: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
def related_users(instance): |
|
|
|
|
users = set() |
|
|
|
|
@ -373,9 +380,14 @@ def evaluate_if_user_should_sync(user): |
|
|
|
|
|
|
|
|
|
@receiver(post_save, sender=Device) |
|
|
|
|
def device_created(sender, instance, **kwargs): |
|
|
|
|
if not instance.user: |
|
|
|
|
return |
|
|
|
|
evaluate_if_user_should_sync(instance.user) |
|
|
|
|
try: |
|
|
|
|
if not instance.user: |
|
|
|
|
return |
|
|
|
|
evaluate_if_user_should_sync(instance.user) |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR6: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
@receiver(pre_delete, sender=Device) |
|
|
|
|
def device_pre_delete(sender, instance, **kwargs): |
|
|
|
|
@ -383,6 +395,11 @@ def device_pre_delete(sender, instance, **kwargs): |
|
|
|
|
|
|
|
|
|
@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) |
|
|
|
|
try: |
|
|
|
|
if not hasattr(instance, '_user') or not instance._user: |
|
|
|
|
return |
|
|
|
|
evaluate_if_user_should_sync(instance._user) |
|
|
|
|
except Exception as e: |
|
|
|
|
logger.info(f'*** ERROR7: {e}') |
|
|
|
|
logger.info(traceback.format_exc()) |
|
|
|
|
raise |
|
|
|
|
|