I have the following Django model:
class MyModel(models.Model):
[...]
parent = models.ForeignKey('self', related_name="children", null=True, blank=True)
[...]
What I'm trying to do is count how many parents and grandparents a certain instance of the model has. That is, if instance_1 is the parent of instance_2, and instance_2 is the parent of instance_3, then instance_3 has a value of 2 (its parent, and the parent of its parent).
The trick is I'm trying to do this in a queryset as to do some filtering based on that value.
MyModel.objects.filter(??)
How can I go about doing that?
I thought about using annotate and Count and then filtering based on that, but again I'm not sure how to go about it.
Related
I have the following model:
class TopicModel(models.Model):
topic = models.ForeignKey('self', on_delete=models.CASCADE)
... some more not relevant fields...
I need to get a parent topic and its direct children in a queryset.
Currently I use it with this queryset:
TopicModel.objects.filter(
Q(pk=10) |
Q(topic__pk=10)
)
I was wondering whether I can do the same in a more simple query without using Q.
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)
Many to many (non-recursive)
class A(models.Model):
pass
class B(models.Model):
parents = models.ManyToManyField(A, related_name='children')
>>> A._meta.get_all_field_names()
['children', u'id']
>>> B._meta.get_all_field_names()
[u'id', 'parents']
I can get the sets of children and parents of model instances with a.children.all() and b.parents.all()
Foreign key (recursive)
class FK(models.Model):
parent = models.ForeignKey('self', related_name='child')
>>> FK._meta.get_all_field_names()
['child', u'id', 'parent']
Any instance of FK will now be able to get both its parent and its child with fk.parent and fk.child
Many to many (recursive)
class M2M(models.Model):
parents = models.ManyToManyField('self', related_name='children')
>>> M2M._meta.get_all_field_names()
[u'id', 'parents']
One would expect that, like I could access a.children and fk.child, I would also be able to access m2m.children. This seems to not be the case.
How do I access m2m.children?
I'm using Django 1.6.5.
For future reference
As Daniel Roseman's answer said, setting symmetrical=False solves the problem. In a Django ticket it is explained as:
In the case of parent/child, the relationship isn't symmetrical - if A is a child of B, it doesn't follow that A is a parent of B.
With symmetrical=False, the reverse relation specified in the related_name is created just like in the foreign key case:
class M2M(models.Model):
parents = models.ManyToManyField('self', related_name='children', symmetrical=False)
>>> M2M._meta.get_all_field_names()
[u'id', 'parents', children]
>>> parent.children.add(child)
>>> parent.children.all() # returns QuerySet containing the child
>>> child.parents.all() # returns QuerySet containing the parent
You need to set symmetrical=False. As the documentation for ManyToManyField says:
When Django processes this model, it identifies that it has a ManyToManyField on itself, and as a result, it doesn’t add a person_set attribute to the Person class. Instead, the ManyToManyField is assumed to be symmetrical – that is, if I am your friend, then you are my friend.
If you do not want symmetry in many-to-many relationships with self, set symmetrical to False. This will force Django to add the descriptor for the reverse relationship, allowing ManyToManyField relationships to be non-symmetrical.
Am trying to build a project and it seems that using model inheritance for my requirements can work the best, I have one base model in which I have several model uses it for inheritance. In the base model, I have a slug field which is unique across all (for integrity)
In my child model, sometimes I want to create a record but if a parent already exists, I want to create only the child and link to the parent directly. for example,
class Base(models.Model):
slug = models.SlugField(_('Slug'), unique=True)
#Other fiels
class ChildA(Base):
height = models.CharField(max_length=100, )
class ChildB(Base):
is_for_sale = models.BooleanField(_('Is active'), default=True, )
# when creating ChildA, it will automtically insert into base model as well
ChildA.objects.create(slug='bmw', height='11')
# now for childB, I want the newly created object to link to an existing record in Base where slug
# is the unique value, is it possible todo such a thing?
ChildB.objects.create(slug='bmw', is_for_sale=True)
I think you might have misunderstood inheritance at this point.
If I get it right, you say that slug must be unique. Therefore you restrict the amount of Classes with a certain slug to '1'
Your Classes ChildB and ChildA are not "linked" to BaseClass, but are descendants of BaseClass, therefore an instance of a child is also an instance of BaseClass.
To me it seems you a looking for a relation between a Slug Object and multiple other Objects with different properties.
Instead of trying to derive from Class Base I would suggest a relation like:
class ChildA(models.Model):
height = models.CharField(max_length=100, )
slug = ForeignKey(Base)
This will ensure that the right Slug Object will be used and not created again.
If you need to keep that line of inheritance, keep your base class, but extract the slug field into its own class like
class Slug(models.Model):
slug = models.SlugField(_('Slug'), unique=True)
class Base(models.Model):
# all your other fields
slug = ForeignKey(Slug)
class ChildA(Base):
height = models.CharField(max_length=100, )
class ChildB(Base):
is_for_sale = models.BooleanField(_('Is active'), default=True, )
Once you are here you can use the constructor of these classes to enforce further restrictions on Slug if they are not yet provided
If you just want a set of common attributes, you want to make your base model abstract, otherwise django will create a table for it.
If instead you are interested in a relation among all subclasses of Base, then you have to give up on inheritance and use a foreign key from every child to Base:
class ChildA(models.Model):
base = ForeignKey(Base)
height = models.CharField(max_length=100)
Say we have a model with two self-recursive relations:
class Article(Item): # Item in this case is an abstract class
date = models.DateField()
parent = models.OneToOneField('self', null=True, blank=True)
subatricles = models.ForeignKey('self', null=True, blank=True, related_name='subs')
Article acts here as a node - it can has many children (if supplied) and one parent (if any). However, when I register my model in Django's admin my subatricles are shown as "one-to-one' - in both cases there are choice boxes but in the latter multiple values cannot be selected, though.
How can I add children via the admin pane to this Article object and later list them?
What I would like to have is this:
instead of normal drop-down.
Thanks.
You only need one field parent with subarticles as related_name to provide the reverse lookup:
class Article(Item): # Item in this case is an abstract class
date = models.DateField()
parent = models.ForeignKey('self', null=True, blank=True, related_name='subarticles')
so if you have an article object and you want to get its parent, use:
article.parent
if you want to get its children, you use:
article.subarticles
In the admin interface to show the subarticles the easiest way is to use the InlineModelAdmin:
class ArticleInline(admin.StackedInline):
model = Article
class ArticleAdmin(admin.ModelAdmin):
inlines = [
ArticleInline,
]