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.
Related
There's this model:
class User(AbstractUser):
followers = models.ManyToManyField('self', symmetrical=False, related_name='followings', null=True, blank=True)
Say I have a user object called 'A'. In a view, I want to filter User objects which have 'A' as their follower. How can I do that?
You can query with:
A.followings.all()
The related_name=… [Django-doc] is the name of the relation in reverse, so it is a QuerySet of Users that have A as follower.
If you do not specify a related_name=… it will take the name of the model in lowercase followed by the …_set suffix, so user_set in this case.
If you only have the primary key of A, then you can query with:
User.objects.filter(followers__id=a_id)
Note: Using null=True [Django-doc] for a ManyToManyField [Django-doc] makes no sense: one can not enforce by the database that a ManyToManyField should be non-empty, therefore as the documentation says: "null has no effect since there is no way to require a relationship at the database level."
models.py :
class Employee(models.Model):
name = models.CharField(max_length=100)
class Department(models.Model):
name = models.CharField(max_length=100)
employee = models.ManyToManyField(Employee, null=True, blank=True)
I need to save employee ids (instead of employee object) in 'employee' ManyToManyField of 'Department' model. How to do that?
views.py:
dept = Department(name=name)
dept.save()
employee_ids = [1,2]
We can use method add (Django Docs):
Adds the specified model objects to the related object set.
dept = Department.objects.create(name=name)
dept.employee.add(*[1, 2])
Or method set(Django Docs):
Replace the set of related objects
dept.employee.set([1, 2])
Note that add(), create(), remove(), clear(), and set() all
apply database changes immediately for all types of related fields. In
other words, there is no need to call save() on either end of the
relationship.
I think this question is unclear what exactly are you trying to do ?
If you want to create a relation between department and employee on the database level django does that for you
on your current structure the relation and is like
id|department_id|user_id
--|-------------|-------
1| 3 | 2
class Product( models.Model ):
name = models.CharField(verbose_name="Name", max_length=255, null=True, blank=True)
the_products_inside_combo = models.ManyToManyField('self', verbose_name="Products Inside Combo", help_text="Only for Combo Products", blank=True)
However, I got this error when I tried to put the duplicate values:
From_product-to_product relationship with this From product and To
product already exists.
Screencap of the error.
Each pair (Product, Product) must be unique. This is why you get already exists error.
Behind the scenes, Django creates an intermediary join table to
represent the many-to-many relationship.
What do you want to do is to have many-to-many relationship between two models (nevermind that they are the same) with additional information stored - quantity (so you would have ProductA = 2x ProductB + ....
In order to model this relationship you will have to create intermediary model and use through option. Documentation explains it very well, so have a look:
https://docs.djangoproject.com/en/dev/topics/db/models/#intermediary-manytomany
Update
Here is minimal working example:
class Product(models.Model):
name = models.CharField(verbose_name='Name', max_length=255, null=True, blank=True)
products = models.ManyToManyField('self', through='ProductGroup', symmetrical=False)
def __str__(self):
return self.name
class ProductGroup(models.Model):
parent = models.ForeignKey('Product', related_name='+')
child = models.ForeignKey('Product', related_name='+')
and admin models:
class ProductGroupInline(admin.TabularInline):
model = Product.products.through
fk_name = 'parent'
class ProductAdmin(admin.ModelAdmin):
inlines = [
ProductGroupInline,
]
admin.site.register(Product, ProductAdmin)
admin.site.register(ProductGroup)
As you can see recursive Product-Product relation is modeled with ProductGroup (through parameter). Couple of notes:
Many-to-many fields with intermediate tables must not be symmetrical, hence symmetrical=False. Details.
Reverse accessors for ProductGroup are disabled ('+') (in general you can just rename them, however, you don't want to work with ProductGroup directly). Otherwise we would get Reverse accessor for 'ProductGroup.child' clashes with reverse accessor for 'ProductGroup.parent'..
In order to have a nice display of ManyToMany in admin we have to use inline models (ProductGroupInline). Read about them in documentation. Please note, however, fk_name field. We have to specify this because ProductGroup itself is ambiguous - both fields are foreign keys to the same model.
Be cautious with recurrency. If you would define, for example, __str__ on Product as: return self.products having ProductGroup with the same parent as the child you would loop infinitely.
As you can see in the screencap pairs can be duplicated now. Alternatively you would just add quantity field to ProductGroup and check for duplication when creating objects.
I have a Django model that is doing way too much. Here's an abbreviated example of the model. Basically, it can represent four different Entity types, and there are recursive ForeignKey and ManyToMany relationships that point to other entities.
This project is currently using Django 1.8.x and Python 2.7.x, but I can upgrade those if the solution requires it.
class Entity(models.Model):
"""
Films, People, Companies, Terms & Techniques
"""
class Meta:
ordering = ['name']
verbose_name_plural = 'entities'
# Types:
FILM = 'FILM'
PERSON = 'PERS'
COMPANY = 'COMP'
TERM = 'TERM'
TYPE_CHOICES = (
(FILM, 'Film'),
(PERSON, 'Person'),
(COMPANY, 'Company'),
(TERM, 'Term/Technique'),
)
created = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
type = models.CharField(max_length=4, choices=TYPE_CHOICES, default=FILM)
slug = models.SlugField(blank=True, unique=True, help_text="Automatically generated")
name = models.CharField(max_length=256, blank=True)
redirect = models.ForeignKey('Entity', related_name='entity_redirect', blank=True, null=True, help_text="If this is an alias (see), set Redirect to the primary entry.")
cross_references = models.ManyToManyField('Entity', related_name='entity_cross_reference', blank=True, help_text="This is a 'see also' — 'see' should be performed with a redirect.")
[... and more fields, some of them type-specific]
I realize this is rather messy, and I'd like to remove 'type' and make an EntityBase class that abstracts out all of the common fields, and create new Film, Person, Company, and Term models that inherit from the EntityBase abstract base class.
Once I create the new models, I think I understand how to write the data migration to move all of the field data over to the new models (iterate over objects from Entity, filtered via type, create new objects in the appropriate new model)... except the ForeignKey and ManyToMany relationships. Maybe I'm thinking about this the wrong way, but how can I transfer those relationships when, during the migration, the new object that the relationship points to may not exist yet?
I suspect this may mean a multi-step migration, but I haven't quite worked out the right way to do it.
There is nothing magical about m2m and fk fields. This is the procedure that I would follow... It might be a bit blunt, but will get the job done:
Make a BACKKKUPPPPPPppp of the database!!
Make another backup!
Create the new model and migration
Write a new data migration that will manually iterate over existing models and update the new model, one-by-one. Don't be afraid of the for loop here, unless you have millions of entries in db.
Delete redundant models and/or fields, make migration for this.
Run those migrations :)
In practice, this means a lot of restoring from the "BACKKKUPPPPPPppp" until the migrations are just right.
One little thing to take care of:
M2m fields cannot get any value if model is not yet saved (because model gets its ID on first save). I would do something like, in the manual migration:
new_instance = NewModel()
new_instance.somefield = "whatever"
new_instance.meaning = 42
....
new_instance.save()
new_instance.that_m2m_field.add(some_related_obj)
Of course, make sure you read the docs in detail, especially that bit about importing the model class - you can't just import it from myapp.models import MyModel, instead do:
MyModel = apps.get_model("myapp", "MyModel")
One possible tripping stone might be the model inheritance that you plan to introduce. Generally, you will want to operate on the child model, and access the parent from there as / if needed. Parent can be accessed via the implicit ptr attribute - in your example it would be entitybase_ptr or something similar (that is just a OneToOne field). Going in the other direction, however, (from parent to unknown child) is not as straightforward, because parent doesn't a priori know what is the class of its child.
I Have a model AB that holds two foreign keys A_id and B_id.
class AB(models.Model):
A_id = models.ForeignKey('A')
B_id = models.ForeignKey('B')
field_1 = models.CharField(max_length=200, blank=True)
field_2 = models.CharField(max_length=200, blank=True)
When editing A or B, AB items are edited inlines, what I want to achieve is that when editing let's say B I want to keep the selected AB items and set the foreign key B_id to null instead of deleting them.
thanks for any hint
I wound up here because I had the same question. I think the previous answer misses the issue here -- the use case here is the user checks the "delete" checkbox on an InlineModelAdmin, not that they delete the model linked by the foreign key.
I think you can simplify the original problem, consider just that model B has a nullable foreign key to model A:
class A(models.Model):
pass
class B(models.Model):
linked_a = models.ForeignKey(A, null=True)
Then the admin lists each B linked to an A using an inline:
class BInline(TabularInline):
model = B
class AModelAdmin(ModelAdmin):
inlines = [BInline]
The question is, is there a way to make the "delete" checkbox on BInline result in B.linked_a = None rather than deleting the instance of B?
The reason this seems like a logical operation is that if you used a ManyToManyField to join these two objects, that's what would happen -- it wouldn't delete B, it would just "unlink" it.
Unfortunately, the answer as far as I can tell is that you can't do this easily. In both cases the inline is showing a database row, but while the inline for a ForeignKey is showing the related object itself, the inline for a ManyToManyField is showing a row from the join table (eg. the relationship). So in terms of database operations the "delete" action is the same, it's just that in one case you delete the related object, in the other case you just delete the relationship.
If I understand this correctly, what you want is protection against cascade deletion.
If this is the case, you need to specify what django should do on deletion of an A or B model.
From the docs:
When an object referenced by a ForeignKey is deleted, Django by default emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey. This behavior can be overridden by specifying the on_delete argument. For example, if you have a nullable ForeignKey and you want it to be set null when the referenced object is deleted:
In order to set the ForeignKey null, you can do it like this:
A_id = models.ForeignKey('A', null=True, on_delete=models.SET_NULL)
B_id = models.ForeignKey('B', null=True, on_delete=models.SET_NULL)
Good luck and hope this helps.
You can use a custom inline form set and override the delete_existing method which is available in django 1.11+.
from django.forms.models import BaseInlineFormSet
from django.db import models
from django.contrib import admin
class Publisher(models.Model):
pass
class Book(models.Model):
publisher = models.ForeignKey(Publisher, null=True)
class CustomInlineFormSet(BaseInlineFormSet):
def delete_existing(self, obj, commit=True):
"""Unhook a model instead of deleting it."""
if commit:
obj.publisher = None
obj.save()
class BooktInline(admin.TabularInline):
formset = CustomInlineFormSet
This changes it so that the 'delete' action on admin inline formsets will unhook the inline model instead of deleting it.