diff --git a/passbook/outposts/signals.py b/passbook/outposts/signals.py index 8c16a3e4c..fe710b0b7 100644 --- a/passbook/outposts/signals.py +++ b/passbook/outposts/signals.py @@ -1,51 +1,28 @@ """passbook outpost signals""" from django.db.models import Model -from django.db.models.signals import post_save +from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from structlog import get_logger from passbook.lib.utils.reflection import class_to_path -from passbook.outposts.models import Outpost, OutpostModel -from passbook.outposts.tasks import outpost_send_update +from passbook.outposts.models import Outpost +from passbook.outposts.tasks import outpost_post_save LOGGER = get_logger() -@receiver(post_save, sender=Outpost) -# pylint: disable=unused-argument -def ensure_user_and_token(sender, instance: Model, **_): - """Ensure that token is created/updated on save""" - _ = instance.token - - @receiver(post_save) # pylint: disable=unused-argument def post_save_update(sender, instance: Model, **_): - """If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved, + """If an Outpost is saved, Ensure that token is created/updated + + If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved, we send a message down the relevant OutpostModels WS connection to trigger an update""" - if isinstance(instance, (OutpostModel, Outpost)): - LOGGER.debug( - "triggering outpost update from outpostmodel/outpost", instance=instance - ) - outpost_send_update.delay(class_to_path(instance.__class__), instance.pk) - return + outpost_post_save.delay(class_to_path(instance.__class__), instance.pk) - for field in instance._meta.get_fields(): - # Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms) - # are used, and if it has a value - if not hasattr(field, "related_model"): - continue - if not field.related_model: - continue - if not issubclass(field.related_model, OutpostModel): - continue - field_name = f"{field.name}_set" - if not hasattr(instance, field_name): - continue - - LOGGER.debug("triggering outpost update from from field", field=field.name) - # Because the Outpost Model has an M2M to Provider, - # we have to iterate over the entire QS - for reverse in getattr(instance, field_name).all(): - outpost_send_update(class_to_path(reverse.__class__), reverse.pk) +@receiver(pre_delete, sender=Outpost) +# pylint: disable=unused-argument +def pre_delete_cleanup(sender, instance: Outpost, **_): + """Ensure that Outpost's user is deleted (which will delete the token through cascade)""" + instance.user.delete() diff --git a/passbook/outposts/tasks.py b/passbook/outposts/tasks.py index 05d342e74..305759cae 100644 --- a/passbook/outposts/tasks.py +++ b/passbook/outposts/tasks.py @@ -3,6 +3,7 @@ from typing import Any from asgiref.sync import async_to_sync from channels.layers import get_channel_layer +from django.db.models.base import Model from structlog import get_logger from passbook.lib.utils.reflection import path_to_class @@ -42,11 +43,54 @@ def outpost_controller_single(outpost_pk: str, deployment_type: str, outpost_typ @CELERY_APP.task() -def outpost_send_update(model_class: str, model_pk: Any): +def outpost_post_save(model_class: str, model_pk: Any): + """If an Outpost is saved, Ensure that token is created/updated + + If an OutpostModel, or a model that is somehow connected to an OutpostModel is saved, + we send a message down the relevant OutpostModels WS connection to trigger an update""" + model: Model = path_to_class(model_class) + try: + instance = model.objects.get(pk=model_pk) + except model.DoesNotExist: + LOGGER.warning("Model does not exist", model=model, pk=model_pk) + return + + if isinstance(instance, Outpost): + LOGGER.debug("Ensuring token for outpost", instance=instance) + _ = instance.token + return + + if isinstance(instance, (OutpostModel, Outpost)): + LOGGER.debug( + "triggering outpost update from outpostmodel/outpost", instance=instance + ) + outpost_send_update(instance) + return + + for field in instance._meta.get_fields(): + # Each field is checked if it has a `related_model` attribute (when ForeginKeys or M2Ms) + # are used, and if it has a value + if not hasattr(field, "related_model"): + continue + if not field.related_model: + continue + if not issubclass(field.related_model, OutpostModel): + continue + + field_name = f"{field.name}_set" + if not hasattr(instance, field_name): + continue + + LOGGER.debug("triggering outpost update from from field", field=field.name) + # Because the Outpost Model has an M2M to Provider, + # we have to iterate over the entire QS + for reverse in getattr(instance, field_name).all(): + outpost_send_update(reverse) + + +def outpost_send_update(model_instace: Model): """Send outpost update to all registered outposts, irregardless to which passbook instance they are connected""" - model = path_to_class(model_class) - model_instace = model.objects.get(pk=model_pk) channel_layer = get_channel_layer() if isinstance(model_instace, OutpostModel): for outpost in model_instace.outpost_set.all():