Django: Получить все связанные с этим экземпляры данного экземпляра. - PullRequest
0 голосов
/ 09 января 2020

Я хотел бы получить все подчиненные экземпляры всех классов, которые имеют ссылку на данный экземпляр. Я попытался использовать related_name, но он не служит цели, так как он получает только все экземпляры одного конкретного класса, а не все экземпляры всех связанных классов. Как я могу достичь этого?

Модель

class AlertTypeMaster():
    alert_type_name = models.CharField(max_length=255)
    description = models.CharField(max_length=255)
    active = models.BooleanField(default=True)

class MachineAlertZone():
    machine_id = models.ForeignKey(MachineMaster, on_delete=models.CASCADE)
    alert_type_id = models.ForeignKey(AlertTypeMaster, on_delete=models.CASCADE)
    radius_value = models.CharField(max_length=50)
    active = models.BooleanField(default=True)

class ActivityRecording():
    alert_type_id = models.ForeignKey(AlertTypeMaster, on_delete=models.CASCADE)
    working_area_id = models.ForeignKey(WorkingAreaMaster, on_delete=models.CASCADE)
    staff_id = models.ForeignKey(StaffMaster, on_delete=models.CASCADE)
    type_of_occur = models.CharField(max_length=255)
    active = models.BooleanField(default=True)

Учитывая один объект AlertTypeMaster, я должен иметь возможность получить все объекты из обоих MachineAlertZone и ActivityRecording.

Пожалуйста, предложите любые подходящие подходы!

Ответы [ 2 ]

1 голос
/ 09 января 2020

Вы всегда должны иметь в виду структуру БД в моделях Django. Добавление двух наборов запросов двух разных моделей невозможно, потому что результирующий «набор запросов» потеряет свои возможности. Что бы вы сделали при обновлении? По каким полям вы сможете фильтровать?

Один из вариантов - извлечь оба набора запросов, оценить их как списки и объединить два списка, что, по сути, и предлагает Bruno (генераторный подход более эффективен, чем простой). объединение списка).

Лучшим решением было бы переосмыслить ваши модели. Представьте себе, например, что:

class AlertTypeMaster():
    description = models.CharField(max_length=255)
    active = models.BooleanField(default=True)

class MachineAlertZone():
    machine_id = models.ForeignKey(MachineMaster, on_delete=models.CASCADE)
    alert_type_id = models.ForeignKey(AlertTypeMaster, on_delete=models.CASCADE)
    radius_value = models.CharField(max_length=50)

class ActivityRecording():
    working_area_id = models.ForeignKey(WorkingAreaMaster, on_delete=models.CASCADE)
    staff_id = models.ForeignKey(StaffMaster, on_delete=models.CASCADE)
    type_of_occur = models.CharField(max_length=255)

class Alert():
    alert_type_id = models.ForeignKey(AlertTypeMaster, on_delete=models.CASCADE, related_name='alerts')
    active = models.BooleanField(default=True)
    zone = model.ForeignKey(MachineAlertZone, on_delete=models.CASCADE)
    activity = model.ForeignKey(ActivityRecording, on_delete=models.CASCADE)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.Q(
                    zone__isnull=False,
                    activity__isnull=True) | 
                models.Q(
                    zone__isnull=True,
                    activity__isnull=False),
                name='activity_zone_xor'
            )
        ]

Вы можете позвонить:

alert_type_master.alerts.all().select_related('zone', 'activity')

Таким образом, вы получите все зоны и действия вашего alert_type_master в одном запросе. Мета-определенное ограничение гарантирует, что в данном предупреждении присутствует только активность или только зона.

Установка активного оповещения = Истина для ведущего устройства Fiven становится такой простой, как:

alert_type_master.alerts.all().update(active=True)

Деталь, которую я также переосмыслил бы в вашем коде, вызывает поля ForeignKey как "id". На самом деле это не просто идентификатор, он есть в базе данных, но не в Django.

MachineAlertZone.object.first().machine_id  # this returns an object, not an id
1 голос
/ 09 января 2020

Нельзя смешивать экземпляры из разных моделей в одном наборе запросов (причины кажутся достаточно очевидными, если вспомнить, что модели и наборы запросов - это просто тонкие слои в реляционной базе данных).

Теперь ничто не мешает вам предоставить собственную итерацию. В самом простом случае вы просто соединяете вместе оба набора запросов в генераторе ie:

import itertools

def iter_subs(self):
    yield from itertools.chain(self.machinealertzone_set.all(), self.activityrecording_set.all())

или что-то более сложное (сортировка «подчиненных» объектов по некоторым критериям и т. Д. c) в зависимости от ваших потребностей.

При этом я не вижу смысла ... Учитывая ваши определения моделей (мало общих полей и нет общих операций), это будет очень разнородная коллекция, и вам, вероятно, придется проверять каждый элемент по типу во время итерации, чтобы знать, что с ней делать, что побеждает весь смысл их извлечения сразу.

РЕДАКТИРОВАТЬ

Мне нужно получить все они сразу, потому что когда для одного активного поля объекта AlertTypeMaster установлено значение True, для всех активных полей подобъекта также должно быть установлено значение True. Если мой подход неэффективен, не могли бы вы предложить лучший способ?

Это денормализация. Тебе это действительно нужно? Если MachineAlertZone и ActivityRecording status должны отражать их AlertTypeMaster, то правильный дизайн (теоретически, согласно нормальным формам реляционной модели) состоит в том, чтобы просто получить статус непосредственно из AlertTypeMaster:

class StatusQueryset(models.Queryset):
    # allow to filter MachineAlertZone and ActivityRecording
    # by their parent AlertType active status
    def active(self):
        return self.filter(alert_type__active=True)

class MachineAlertZone():
    machine = models.ForeignKey(MachineMaster, on_delete=models.CASCADE)
on_delete=models.CASCADE)
    alert_type = models.ForeignKey(AlertTypeMaster, on_delete=models.CASCADE)
    radius_value = models.CharField(max_length=50)

    #active = models.BooleanField(default=True)
    @property
    def active(self):
        return self.alert_type.status

    objects = StatusQueryset.as_manager()

Но если вы все еще хотите сохранить отдельный флаг для ваших «подмоделей», вам все равно не нужно «извлекать их все сразу», чтобы обновить их статус - просто выполните два запроса на обновление в транзакции в вашем AlertTypeMaster.save() методе:

from django.db import transaction

class AlertTypeMaster(models.Model):
    def save(self, *args, **kw):
        with transaction.atomic():
            super(AlertTypeMaster, self).save(*args, **kw)
            self.machinealertzone_set.update(active=self.active)
            self.activityrecording_set.update(active=self.active)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...