Django Model Field Type: ManyToManyField

By: Dusty Arlia
Published on Sunday, March 23, 2014, 10:59 AM
Last Updated on Sunday, July 12, 2015 at 2:32 PM
Total Updates: 7

A ManyToManyField allows for a many-to-many relationship with another model. This field requires the class name of the related model to be the first argument. Here is an example of its syntax:

from django.db import models

class Car_Part(models.Model):
    # ...
    pass

class Car(models.Model):
    # ...
    car_parts = models.ManyToManyField(Car_Part)

The name of the ManyToManyField above is car_parts. It’s recommended that the name of the ManyToManyField be plural to describe the set of related model objects (like car_parts above). It doesn’t matter which model has the ManyToManyField, but you should only include it in one of your models, not both. The ManyToManyField field should go in the object that is going to be edited in the form. In the example above, car_parts is in Car, because it’s more natural to think about a car having car pars. In this exaple, the Car form would let users select the car_parts. It is less logical to have Car_Part have cars, though possible.

ManyToManyFields have many optional arguments that help define how the relationship should work.

Here is an example:

from django.db import models

class Employee(models.Model):
    name = models.CharField(max_length=128)

    # On Python 3: def __str__(self):
    def __unicode__(self):
        return self.name

class Company(models.Model):
    name = models.CharField(max_length=128)
    staff = models.ManyToManyField(Employee, through='Employee_Record')

    # On Python 3: def __str__(self):
    def __unicode__(self):
        return self.name

class Employee_Record(models.Model):
    employee = models.ForeignKey(Employee)
    company = models.ForeignKey(Company)
    hire_date = models.DateField()
    job_title = models.CharField(max_length=64)

You may need extra data with some many-to-many relationships. For example, if you had both a Company and a Employee model, then you might want to include hire_date field.

Django lets you specify a intermediary model to control the many-to-many relationship. The intermediate model is associated with the ManyToManyField using the through argument. When you set up the intermediary model, you explicitly specify the ForeignKeys to the models that are involved in the many-to-many relationship (like employee and company above). You can also put extra fields on this model.

Here are some restrictions for the intermediate model:

  • Intermediate models must contain one - and only one - ForeignKey to the target model (Employee in the above example). If you have more than one ForeignKey, a validation error will be raised.
  • Intermediate models must contain one - and only one - ForeignKey to the source model (Company in the above example). If you have more than one ForeignKey, a validation error will be raised.
  • The only exception to this is a model which has a many-to-many relationship with itself, through an intermediary model. In this case, two ForeignKeys to the same model are permitted. One key will be treated as the ForeignKey to the target model and the other key will be treated as the ForeignKey to the source model(both sides of the many-to-many relationship).
  • When using an intermediary model to define a many-to-many relationship from a model with itself, you must use symmetrical=False.

Here is an example of creating an instance of the intermediate model:

>>> jamie = Employee.objects.create(name="Jamie Foxx")
>>> leonardo = Employee.objects.create(name="Leonardo DiCaprio")
>>> actors_union = Company.objects.create(name="Actors Union")
>>> record1 = Employee_Record(employee=jamie, company=actors_union,
...     hire_date=date(2012, 12, 25),
...     job_title="Actor")
>>> record1.save()
>>> actors_union.staff.all()
[<Employee: Jamie Foxx>]

You can’t just create a relationship between an Employee and a Company. The relationship requires the Employee_Record model. When using the through argument with your many-to-many field, you can’t use add, create, or assignment to create relationships. add, create, and assignment don’t provide a way to specify this relationship. So they are disabled for many-to-many relationships that use an intermediate model. The only way to create this type of relationship is to create instances of the intermediate model. The remove() method is also disabled, but you can use the clear() method to remove all many-to-many relationships for an instance.

Here is an example:

# NOT work
>>> actors_union.staff.add(samuel)
# NOT work
>>> actors_union.staff.create(name="Samuel Jackson")
# NOT work
>>> actors_union.staff = [kerry, don, jonah, franco, walton]
# WILL work; Deletes the intermediate model instances
>>> actors_union.staff.clear()
>>> Employee_Record.objects.all()
[]

Once you have established the many-to-many relationships by creating instances of your intermediate model, you can perform queries. Just as with normal many-to-many relationships, you can query using the attributes of the many-to-many-related model:

# Find all the companies with a staff member whose name starts with 'Jamie'
>>> company.objects.filter(staff__name__startswith='Jamie')
[<Company: Actors Union>]

While using an intermediate model, you can also query on its attributes:

# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Employee.objects.filter(
...     company__name='Actors Union',
...     employee_record__hire_date__gt=date(2012,1,1))
[<Employee: Jamie Foxx>]

If you need to access a employee_record’s information you may do so by directly querying the Employee_Record model:

>>> jamies_employee_record = Membership.objects.get(group=beatles, person=ringo)
>>> jamies_employee_record.hire_date
datetime.date(2012, 12, 25)
>>> jamies_employee_record.job_title
u'Actor'

Another way to access the same information is by querying the many-to-many reverse relationship from a Employee object:

>>> jamies_employee_record = jamies.employee_record_set.get(company=actors_union)
>>> jamies_employee_record.hire_date
datetime.date(2012, 12, 25)
>>> jamies_employee_record.job_title
u'Actor'

You can create a recursive relationship with an object that has a many-to-many relationship with itself. And you can create relationships to models not yet defined.

Comments:

Ad:

Ad: