Reputation: 126
I would like to get all sub related instances of all classes that have reference to the given instance. I have tried using related_name
, but it doesn't serve the purpose as it only gets all instances of one particular class, not all instances of all related classes.
How could I achieve this?
Model
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)
Given the one object of AlertTypeMaster
, I should be able to get all objects from both MachineAlertZone
and ActivityRecording
.
Please suggest any suitable approaches!
Upvotes: 0
Views: 282
Reputation: 1100
You always have to keep in mind the DB structure under Django models. Appending two querysets of two different models is impossible, because the resulting "queryset" would loose it's capabilities. What would you do on update? What fields would you be able to filter on?
One option is to fetch both querysets, evaluate them as lists and merge the two lists, which is essentially what bruno suggests (the generator approach is more memory efficent than simple list merge).
Better solution would be to rethink your models. Think this for example:
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'
)
]
You can call:
alert_type_master.alerts.all().select_related('zone', 'activity')
This way you fetch all the zones and activities of your alert_type_master in a single query. The Meta defined constraint makes sure that only activity or only zone is present on a given alert.
Setting an alert to active=True for a fiven master becomes as easy as:
alert_type_master.alerts.all().update(active=True)
A detail I would also rethink in your code is calling ForeignKey fields as "id". It is not really just id, in the database it is, but not in Django.
MachineAlertZone.object.first().machine_id # this returns an object, not an id
Upvotes: 1
Reputation: 77892
You can't mix instances from different models in a same queryset (the reasons seem obvious enough when you remember that models and querysets are just thin layers over a relational database).
Now nothing prevents you from providing your own iterable. At the simplest, you just chain together both querysets in a generator ie:
import itertools
def iter_subs(self):
yield from itertools.chain(self.machinealertzone_set.all(), self.activityrecording_set.all())
or anything more elaborate (sorting 'sub' objects by some criteria etc) depending on your needs.
This being said, I don't really see the point... Given your models definitions (few common fields and no common operations), this will be a very heterogenous collection and you will probably have to typecheck each item during iteration to know what to do with it, which kind of defeats the whole point of retrieveing them all at once.
EDIT
I need to retrieve them all at once because when one AlertTypeMaster object active field is set to True, all the sub object active field need to be set to True also. In case my approach is not efficient, could you suggest a better way?
This is denormalization. Do you really need it ? If the MachineAlertZone and ActivityRecording status
is supposed to reflect their AlertTypeMaster, then the proper design (theoretically, according to relational model normal forms) is to just get the status directly from the 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()
But if you still want to keep an individual flag for your "submodels", you still don't need to "retrieve them all at once" to update their status - just issue two update queries in a transaction in your AlertTypeMaster.save()
method:
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)
Upvotes: 1