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/tournaments/signals.py

149 lines
5.9 KiB

import random
import string
from django.db.models.signals import post_save, pre_delete, post_delete, m2m_changed
from django.db.transaction import DatabaseError
from django.dispatch import receiver
from django.conf import settings
from django.apps import apps
from django.utils import timezone
from django.db.models import Q
from .models import Club, FailedApiCall, CustomUser, Log, DataAccess, ModelLog, BaseModel
import requests
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
# Synchronization
@receiver([post_save, post_delete])
def synchronization_notifications(sender, instance, **kwargs):
"""
Signal handler that sends notifications through WebSocket channels when model instances are saved or deleted.
The function creates a WebSocket group name for each affected user and sends a sync update message
to all clients connected to that group.
"""
if sender in [FailedApiCall, Log, ModelLog]:
return
if kwargs.get('signal') == post_delete:
delete_data_access_if_necessary(instance.id)
print(f'*** signals {sender}')
user_ids = set()
# add impacted users
if isinstance(instance, CustomUser):
user_ids.add(instance.id)
elif isinstance(instance, BaseModel):
owner = instance.last_updated_by
if owner is not None:
user_ids.add(owner.id)
if isinstance(instance, BaseModel):
data_access_query = Q(model_id=instance.id)
if kwargs.get('signal') != post_delete:
# when deleting objects, accessing reference generates DoesNotExist exceptions
parent_model, data_access_reference_id = instance.get_parent_reference()
if data_access_reference_id is not None:
data_access_query |= Q(model_id=data_access_reference_id)
# look for users through data access objects
data_access_list = DataAccess.objects.filter(data_access_query)
for data_access in data_access_list:
user_ids.add(data_access.owner.id)
for shared_user in data_access.shared_with.all():
user_ids.add(shared_user.id)
for user_id in user_ids:
send_user_message(user_id)
def delete_data_access_if_necessary(model_id):
DataAccess.objects.filter(model_id=model_id).delete()
def send_user_message(user_id):
group_name = f"sync_{user_id}"
print(f">>> send to group {group_name}")
# Send to all clients in the sync group
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
group_name, {"type": "sync.update", "message": "hello"}
)
@receiver(m2m_changed, sender=DataAccess.shared_with.through)
def handle_shared_with_changes(sender, instance, action, pk_set, **kwargs):
if action == "post_add":
for user_id in pk_set:
user = CustomUser.objects.get(id=user_id)
instance.create_access_log(user, 'GRANT_ACCESS')
send_user_message(user_id)
elif action == "post_remove":
for user_id in pk_set:
user = CustomUser.objects.get(id=user_id)
instance.create_access_log(user, 'REVOKE_ACCESS')
send_user_message(user_id)
@receiver(pre_delete, sender=DataAccess)
def revoke_access_after_delete(sender, instance, **kwargs):
for user in instance.shared_with.all():
instance.create_access_log(user, 'REVOKE_ACCESS')
# # Store the users in a temporary attribute that we can access after deletion
# instance._users_to_revoke = list(instance.shared_with.all())
# @receiver(post_delete, sender=DataAccess)
# def revoke_access_after_delete(sender, instance, **kwargs):
# # Create revoke logs for all previously stored users
# if hasattr(instance, '_users_to_revoke'):
# for user in instance._users_to_revoke:
# instance.create_access_log(user, 'REVOKE_ACCESS')
# Others
def generate_unique_code():
characters = string.ascii_letters + string.digits
while True:
code = ''.join(random.sample(characters, 3))
if not Club.objects.filter(broadcast_code=code).exists():
return code
@receiver(post_save, sender=Club)
def assign_unique_code(sender, instance, created, **kwargs):
if created and not instance.broadcast_code:
instance.broadcast_code = generate_unique_code()
instance.save()
DISCORD_FAILED_CALLS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1248191778134163486/sSoTL6cULCElWr2YFwyllsg7IXxHcCx_YMDJA_cUHtVUU4WOfN-5M7drCJuwNBBfAk9a'
DISCORD_LOGS_WEBHOOK_URL = 'https://discord.com/api/webhooks/1257987637449588736/TtOUwzYgSlQH2d3Ps7SfIKRcFALQVa3hfkC-j9K4_UAcWtsfiw4v8NUPbnX2_ZPOYzuv'
@receiver(post_save, sender=FailedApiCall)
def notify_discord_on_create(sender, instance, created, **kwargs):
notify_object_creation_on_discord(created, instance, DISCORD_FAILED_CALLS_WEBHOOK_URL)
@receiver(post_save, sender=Log)
def notify_log_creation_on_discord(sender, instance, created, **kwargs):
notify_object_creation_on_discord(created, instance, DISCORD_LOGS_WEBHOOK_URL)
# WARNING: using this method requires the instance to have a discord_string method
def notify_object_creation_on_discord(created, instance, webhook_url):
if created:
default_db_engine = settings.DATABASES['default']['ENGINE']
if default_db_engine != 'django.db.backends.sqlite3':
if hasattr(instance, 'discord_string'):
message = f'New {instance.__class__.__name__} created: {instance.discord_string()}'
else:
message = "no message. Please configure 'discord_string' on your instance"
send_discord_message(webhook_url, message)
def send_discord_message(webhook_url, content):
data = {
"content": content
}
response = requests.post(webhook_url, json=data)
if response.status_code != 204:
raise ValueError(
f'Error sending message to Discord webhook: {response.status_code}, {response.text}'
)