Django: Как каскадировать обновление через несколько моделей?
Я пишу приложение на основе Django для отслеживания объектов (Objekt) и задач по их обслуживанию. Объекты могут быть привязаны к местоположению.
<>> Место (0/1) --- (n) Объект (1) --- (n) Задача
<>>>Положение, объект и задача имеют поле статуса со следующими значениями:
RED = "red"
YELLOW = "yellow"
GREEN = "green"
STATUS = [
(RED, "Overdue tasks"),
(YELLOW, "Pending tasks"),
(GREEN, "All good"),
]
Я хочу, чтобы маркер карты местоположения менял свой цвет в зависимости от статуса связанных с ним объектов и в конечном итоге задач.
Я пытался следовать лучшим практикам джанго и создать толстую модель.
from django.db import models
from locationapp.models import Location
from taskapp.models import Task
from rules.contrib.models import RulesModel
class Objekt(RulesModel):
RED = "red"
YELLOW = "yellow"
GREEN = "green"
STATUS = [
(RED, "Overdue tasks"),
(YELLOW, "Pending tasks"),
(GREEN, "All good"),
]
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
location = models.ForeignKey(
Location, on_delete=models.SET_NULL, null=True, blank=True
)
status = models.CharField(max_length=6, choices=STATUS, default=GREEN)
def set_status(self):
if Task.objects.filter(objekt=self.id).filter(status=Task.RED).exists():
self.status = Objekt.RED
elif Task.objects.filter(objekt=self.id).filter(status=Task.YELLOW).exists():
self.status = Objekt.YELLOW
else:
self.status = Objekt.GREEN
Но я как-то не уверен в своей концепции... Как обновление Задачи может вызвать обновление связанного с ней Объекта. И как Objekt в дальнейшем вызовет обновление Location - если это вообще необходимо?
Потенциальным решением является использование сигналов. Я реализовал это следующим образом, пока что без сельдерея:
# objektapp/apps.py
from django.apps import AppConfig
class ObjektappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'objektapp'
def ready(self):
import objektapp.signals
# objektapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import transaction
from taskapp.models import Task
from objektapp.models import Objekt
@receiver(post_save, sender=Task)
def set_status(sender, instance, created, **kwargs):
# TODO: Use celery for async operation: https://docs.djangoproject.com/en/3.2/topics/db/transactions/
transaction.on_commit(lambda: objekt_update_status(instance))
def objekt_update_status(task_instance):
objekt = Objekt.objects.get(id=task_instance.objekt.id)
new_objekt_status = Objekt.GREEN
if Task.objects.filter(objekt=task_instance.objekt.id, status=Task.RED).exists():
new_objekt_status = Objekt.RED
elif Task.objects.filter(objekt=task_instance.objekt.id, status=Task.YELLOW).exists():
new_objekt_status = Objekt.YELLOW
if objekt.status != new_objekt_status:
objekt.status = new_objekt_status
objekt.save()
Похожую настройку я сделал на модели Location, которая также реагирует на сигнал post_save от Objekt. Я не уверен, что лучшее место для хранения функции objekt_update_status() в файле signals.py, но попытка поместить ее в models.py закончилась ошибкой циклического импорта.