Django model inheritance: how to retrieve records? - django

I have these Django models:
class Base(models.Model):
name = models.CharField(max_length=255)
class Restaurant(Base):
pass
class Hotel(Base)
pass
class Message(models.Model)
text = models.TextField()
parent = models.ForeignKey(Base, on_delete=models.CASCADE)
I would like to set up a query to retrieve all of the messages left about Hotels only. But how to do that?
Message.objects.filter(has_attr("Hotel"))
Obviously this doesn't work but something like that is what I am looking for.

An inherited model has an implicit OneToOneField from the child to the parent. This relation in reverse has, by default, as related name the name of the class, so hotel.
We thus can check if there exists a Hotel object for the given parent with:
Message.objects.filter(parent__hotel__isnull=False)

Related

Django - Can I use an abstract Model in another Model?

I'm new to Django (and it's been a few years since I played with databases), and might be dancing around something simple that I don't know what to google.
Essentially, I want to have a table of "Events" which can contain data of various Types. Say one event is an "alarm" and another is a "message." The "alarm" type needs different fields than the "message" so it seems to make sense to not store the data in one big table containing any sort of data that might be needed for any event. However, I want to be able to get a list of all events for a given "event_block" foreign key.
I have done some reading about abstract models and proxies, etc, but I haven't been able to figure out how one Model can refer to an abstract model (if that is in fact the path to take).
models.py:
class Event(models.Model):
#id auto-generated primary_key
event_block = models.ForeignKey(Block, on_delete=models.CASCADE)
event_data = BaseEvent # This is wrong, but what is right?
class BaseEvent(Models.model):
class Meta:
abstract=True
... common event info ...
class AlarmEvent(BaseEvent):
... event info for alarms ...
class MessageEvent(BaseEvent):
... event info for messages ...
Ideally I would be able to, in my template, create a drop down box with all available event_types and then click a "submit" button the form to create that event (with its necessary event data fields) within my Event model and let Django handle the details of how that is handled in the database tables.
What am I missing?
Firstly, This is appropriate with the Event model
class Event(models.Model):
event_block = models.ForeignKey(Block, on_delete=models.CASCADE)
event_data = models.ForeignKey(BaseEvent)
However... BaseEvent with abstract=True does not physically exist but its children AlarmEvent and MessageEvent does. So I am not sure you can point BaseEvent as a ForeignKey. This post about generic foreign key may help.
Secondly about the structure of models. IMO, things can be simpler without abstract mother class. How about a model with category field?
CATEGORY = ( ('field1', 'field name 1'), ('field2', 'field name 2'), )
class Event(models.Model):
category = models.CharField(max_length=10, choices=CATEGORY)
# SOME OTHER FIELDS...
you can see a form of the model via django admin in default.
What is the reason for those separate tables? Are you going to query them directly? Are these events supposed to share data?
If answers to these questions are "I'm not sure" (or even better "no") then how about (instead of creating separate tables) you keep all the data on Event model directly? Something like
class Event(models.Model):
#id auto-generated primary_key
event_block = models.ForeignKey(Block, on_delete=models.CASCADE)
event_data = models.TextField()
and keep the data in event_data field in serialized format (e.g. JSON). Or you can use some plugin, e.g. https://github.com/bradjasper/django-jsonfield for JSONField.
Some common fields can be separated, for example:
class Event(models.Model):
#id auto-generated primary_key
event_block = models.ForeignKey(Block, on_delete=models.CASCADE)
event_type = models.CharField(max_length=2)
event_data = models.TextField()

Filtering reverse forein key set in django

class item(models.Model):
name = models.CharField()
class itemTxns(models.Model):
item = models.ForeignKey(item)
txnDate = models.DateField()
txn = models.CharField()
Given an "item" object, how do I find the most recent itemTxn associated with that item?
I know I have access to item.itemTxns_set, but is it possible to query that without explicitly calling another get on the itemTxns class?
item.itemtxns_set.latest('txnDate')
I'm not sure how you'll get around calling it.
You could make it a property of the item class.
class item(models.Model):
#....
#property
def latest_itemtxns(self):
return self.itemtxns_set.latest('txndate')
By the way I recommend you capitalize your classes to differentiate between instances, variables, and classes.

How do I select the base class from the child object in Django (using model inheritance)

I have a base model and one that inherits from it, similar in setup to this:
class Archive(models.Model):
pub_date = models.DateField()
class ArchiveB(Archive):
another_date = models.DateField()
How do I access the base class from the child class? For example:
archiveb_instance = ArchiveB.objects.get(pk=5)
base_instance = archiveb_instance.archive #This doesn't work.
According to the documentation its just a one-to-one relationship automatically created in the child so I figured it would let me go backwards. The reason I want this is because I have a third non-Archive model that has a foreignkey to Archive. I want the foreignkey to Archive because this way the third model can relate to any Archive, not just a specific Archive type.
i think you might be looking for archiveb_instance.archive_ptr
They share the same PK, so just assign that to the field with _id appended.

Django: making relationships in memory without saving to DB

I have some models with relationships like this:
class Item(model.Model):
name = models.CharField()
class Group(models.Model):
item = models.ManyToManyField(Item)
class Serie(models.Model):
name = models.CharField()
chart = models.ForeignKey(Chart)
group = models.ForeignKey(Group)
class Chart(models.Model):
name = models.CharField()
I need to create a Chart object on the fly, without saving to the DB. But I can't do it because Django tries to use the objects primary keys when assigning the relationships.
I just want Group.add(Item()) to work without having to save the objects to the DB.
Is there any simple way around this?
Reviving here for the sake of future readers:
I've gotten around this use case by defining a private attribute that represents the relationship inside the classes and a property to inspect wether the object can be retrieved from the DB or resides in memory.
Here is a simple example:
class Parent(models.Model):
_children = []
name = models.CharField(max_length=100)
#property
def children(self):
if _children:
return self._children
else:
return self.children_set.all()
def set_virtual_children(self, value): # could use a setter for children
self._children = value # Expose _children to modification
def some_on_the_fly_operation(self):
print(','.join([c.name for c in self.children]))
class Children(models.Model):
parent = models.ForeignKey(Parent)
name = models.CharField(max_length=100)
This way, I can set the "virtual children" and use all the defined methods "on the fly"
EDIT: It seems that approach described here isn't enough for django to allow adding to the ManyToMany relationship.
Have you tried to add primary_key=True and unique=True to the name attribute of the Item model. Then doing Group.add(Item("item_name_here")) should work if you have the possibility to create the name on the fly.
I didn't test it, but I think your way failed because add() wants to use the primary-key which by default is the autoincrementing id that is assigned when it is saved to the database.

Django model inheritance, filtering models

Given the following models:(don't mind the TextFields there're just for illustration)
class Base(models.Model):
field1 = models.TextField()
class Meta:
abstract=True
class Child1(Base):
child1_field = models.TextField()
class Child2(Base):
child2_field = models.TextField()
class Content(models.Model):
aso_items = models.ManyToManyField('Base')
According to these definitions a Content object can be associated with more than one Base object, eg. an interview(=Content object) can be linked with a musician(=Child1 object), a filmdirector(=Child2), etc.
Now, for my question:
Is it possible to filter Content objects according to which model the aso_items field points to?
An example : Say I would like a Queryset containing all the Content objects that are associated with a specific object of Child1(eg. all the interviews associated with the musician Bob Dylan), how can I achieve this?
Further, what if I'd want a QuerySet containing all the Content objects that are associated with Child1 objects?(eg. all the interviews that associated with musicians)
How does this change the filtering?
Thanks in advance
ps: I'm experiencing some problems with white space in the preview, forgive me
You should check the section of the Django docs regarding using related_name for abstract base classes. http://docs.djangoproject.com/en/dev/topics/db/models/#be-careful-with-related-name
To quote the docs:
If you are using the related_name
attribute on a ForeignKey or
ManyToManyField, you must always
specify a unique reverse name for the
field. This would normally cause a
problem in abstract base classes,
since the fields on this class are
included into each of the child
classes, with exactly the same values
for the attributes (including
related_name) each time.
To work around this problem, when you
are using related_name in an abstract
base class (only), part of the name
should be the string %(class)s. This
is replaced by the lower-cased name of
the child class that the field is used
in. Since each class has a different
name, each related name will end up
being different.
Using this information I would recommend moving the m2m field into the Base class:
class Content(models.Model):
# Add remaining fields for Content
pass
class Base(models.Model):
field1 = models.TextField()
items = models.ManyToManyField(Content,related_name="%(class)s_related")
class Meta:
abstract=True
class Child1(Base):
child1_field = models.TextField()
class Child2(Base):
child2_field = models.TextField()
Apparently a ForeignKey relation(or ManyToMany for that matter) with a abstract class isn't allowed.
I get the following error : 'AssertionError: ForeignKey cannot define a relation with abstract class Artiest'.
A possible solution is to define the base class as non-abstract, however this implies that one could instantiate models of the base class. Which isn't the behavior I want.(after all it was an abstract class)
Has someone come accross the same problem how did you solve it? Any alternatives?
Have a look at http://www.djangoproject.com/documentation/models/generic_relations/ which goes through generic relations. Your Content model would match up to their TaggedItem model, and your Base model would match up to their Animal/Vegetable/Mineral model (with Child1 and Child2 extending).
Getting all of the Content objects for a single child would be (assuming you set the GenericRelation to contents inside Base):
child_contents = childObject.contents.all()
And to get all Content objects for a model:
ctype = ContentType.objects.get_for_model(Child1)
all_child_contents = Content.objects.filter(content_type=ctype)