outposts: improve performance by running related check in worker, fix tokens being left over on outpost delete

This commit is contained in:
Jens Langhammer 2020-10-14 18:41:16 +02:00
parent 45699a1a69
commit 18da7565c2
2 changed files with 59 additions and 38 deletions

View File

@ -1,51 +1,28 @@
"""passbook outpost signals""" """passbook outpost signals"""
from django.db.models import Model 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 django.dispatch import receiver
from structlog import get_logger from structlog import get_logger
from passbook.lib.utils.reflection import class_to_path from passbook.lib.utils.reflection import class_to_path
from passbook.outposts.models import Outpost, OutpostModel from passbook.outposts.models import Outpost
from passbook.outposts.tasks import outpost_send_update from passbook.outposts.tasks import outpost_post_save
LOGGER = get_logger() 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) @receiver(post_save)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def post_save_update(sender, instance: Model, **_): 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""" we send a message down the relevant OutpostModels WS connection to trigger an update"""
if isinstance(instance, (OutpostModel, Outpost)): outpost_post_save.delay(class_to_path(instance.__class__), instance.pk)
LOGGER.debug(
"triggering outpost update from outpostmodel/outpost", instance=instance
)
outpost_send_update.delay(class_to_path(instance.__class__), instance.pk)
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" @receiver(pre_delete, sender=Outpost)
if not hasattr(instance, field_name): # pylint: disable=unused-argument
continue def pre_delete_cleanup(sender, instance: Outpost, **_):
"""Ensure that Outpost's user is deleted (which will delete the token through cascade)"""
LOGGER.debug("triggering outpost update from from field", field=field.name) instance.user.delete()
# 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)

View File

@ -3,6 +3,7 @@ from typing import Any
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer from channels.layers import get_channel_layer
from django.db.models.base import Model
from structlog import get_logger from structlog import get_logger
from passbook.lib.utils.reflection import path_to_class 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() @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 """Send outpost update to all registered outposts, irregardless to which passbook
instance they are connected""" instance they are connected"""
model = path_to_class(model_class)
model_instace = model.objects.get(pk=model_pk)
channel_layer = get_channel_layer() channel_layer = get_channel_layer()
if isinstance(model_instace, OutpostModel): if isinstance(model_instace, OutpostModel):
for outpost in model_instace.outpost_set.all(): for outpost in model_instace.outpost_set.all():