summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorRussell Keith-Magee <russell@keith-magee.com>2008-07-29 12:41:08 +0000
committerRussell Keith-Magee <russell@keith-magee.com>2008-07-29 12:41:08 +0000
commit174641b9b35022b80ea1d1a917a9d95b37a551d7 (patch)
tree3f3acdcb470dc8b0b5b32a49905196b77e3ed8de /docs
parentf752f692386faf328c5e091d71d50024ac5b09d0 (diff)
Fixed #6095 -- Added the ability to specify the model to use to manage a ManyToManyField. Thanks to Eric Florenzano for his excellent work on this patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@8136 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'docs')
-rw-r--r--docs/admin.txt55
-rw-r--r--docs/model-api.txt129
2 files changed, 182 insertions, 2 deletions
diff --git a/docs/admin.txt b/docs/admin.txt
index 76f61a1d42..8c9ef71898 100644
--- a/docs/admin.txt
+++ b/docs/admin.txt
@@ -617,6 +617,61 @@ automatically::
FriendshipInline,
]
+Working with Many-to-Many Intermediary Models
+----------------------------------------------
+
+By default, admin widgets for many-to-many relations will be displayed inline
+on whichever model contains the actual reference to the `ManyToManyField`.
+However, when you specify an intermediary model using the ``through``
+argument to a ``ManyToManyField``, the admin will not display a widget by
+default. This is because each instance of that intermediary model requires
+more information than could be displayed in a single widget, and the layout
+required for multiple widgets will vary depending on the intermediate model.
+
+However, we still want to be able to edit that information inline. Fortunately,
+this is easy to do with inline admin models. Suppose we have the following
+models::
+
+ class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ class Group(models.Model):
+ name = models.CharField(max_length=128)
+ members = models.ManyToManyField(Person, through='Membership')
+
+ class Membership(models.Model):
+ person = models.ForeignKey(Person)
+ group = models.ForeignKey(Group)
+ date_joined = models.DateField()
+ invite_reason = models.CharField(max_length=64)
+
+The first step in displaying this intermediate model in the admin is to
+define an inline model for the Membership table::
+
+ class MembershipInline(admin.TabularInline):
+ model = Membership
+ extra = 1
+
+This simple example uses the defaults inline form for the Membership model,
+and shows 1 extra line. This could be customized using any of the options
+available to inline models.
+
+Now create admin views for the ``Person`` and ``Group`` models::
+
+ class PersonAdmin(admin.ModelAdmin):
+ inlines = (MembershipInline,)
+
+ class GroupAdmin(admin.ModelAdmin):
+ inlines = (MembershipInline,)
+
+Finally, register your ``Person`` and ``Group`` models with the admin site::
+
+ admin.site.register(Person, PersonAdmin)
+ admin.site.register(Group, GroupAdmin)
+
+Now your admin site is set up to edit ``Membership`` objects inline from either
+the ``Person`` or the ``Group`` detail pages.
+
``AdminSite`` objects
=====================
diff --git a/docs/model-api.txt b/docs/model-api.txt
index e20319ae22..616cd1d242 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -655,7 +655,7 @@ Note that this value is *not* HTML-escaped when it's displayed in the admin
interface. This lets you include HTML in ``help_text`` if you so desire. For
example::
- help_text="Please use the following format: <em>YYYY-MM-DD</em>."
+ help_text="Please use the following format: <em>YYYY-MM-DD</em>."
Alternatively you can use plain text and
``django.utils.html.escape()`` to escape any HTML special characters.
@@ -944,6 +944,131 @@ the relationship should work. All are optional:
======================= ============================================================
+Extra fields on many-to-many relationships
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+When you're only dealing with simple many-to-many relationships such as
+mixing and matching pizzas and toppings, a standard ``ManyToManyField``
+is all you need. However, sometimes you may need to associate data with the
+relationship between two models.
+
+For example, consider the case of an application tracking the musical groups
+which musicians belong to. There is a many-to-many relationship between a person
+and the groups of which they are a member, so you could use a ManyToManyField
+to represent this relationship. However, there is a lot of detail about the
+membership that you might want to collect, such as the date at which the person
+joined the group.
+
+For these situations, Django allows you to specify the model that will be used
+to govern the many-to-many relationship. You can then put extra fields on the
+intermediate model. The intermediate model is associated with the
+``ManyToManyField`` using the ``through`` argument to point to the model
+that will act as an intermediary. For our musician example, the code would look
+something like this::
+
+ class Person(models.Model):
+ name = models.CharField(max_length=128)
+
+ def __unicode__(self):
+ return self.name
+
+ class Group(models.Model):
+ name = models.CharField(max_length=128)
+ members = models.ManyToManyField(Person, through='Membership')
+
+ def __unicode__(self):
+ return self.name
+
+ class Membership(models.Model):
+ person = models.ForeignKey(Person)
+ group = models.ForeignKey(Group)
+ date_joined = models.DateField()
+ invite_reason = models.CharField(max_length=64)
+
+When you set up the intermediary model, you explicitly specify foreign
+keys to the models that are involved in the ManyToMany relation. This
+explicit declaration defines how the two models are related.
+
+There are a few restrictions on the intermediate model:
+
+ * Your intermediate model must contain one - and *only* one - foreign key
+ on the target model (this would be ``Person`` in our example). If you
+ have more than one foreign key, a validation error will be raised.
+
+ * Your intermediate model must contain one - and *only* one - foreign key
+ on the source model (this would be ``Group`` in our example). If you
+ have more than one foreign key, a validation error will be raised.
+
+ * If the many-to-many relation is a relation on itself, the relationship
+ must be non-symmetric.
+
+Now that you have set up your ``ManyToManyField`` to use your intermediary
+model (Membership, in this case), you're ready to start creating some
+many-to-many relationships. You do this by creating instances of the
+intermediate model::
+
+ >>> ringo = Person.objects.create(name="Ringo Starr")
+ >>> paul = Person.objects.create(name="Paul McCartney")
+ >>> beatles = Group.objects.create(name="The Beatles")
+ >>> m1 = Membership(person=ringo, group=beatles,
+ ... date_joined=date(1962, 8, 16),
+ ... invite_reason= "Needed a new drummer.")
+ >>> m1.save()
+ >>> beatles.members.all()
+ [<Person: Ringo Starr>]
+ >>> ringo.group_set.all()
+ [<Group: The Beatles>]
+ >>> m2 = Membership.objects.create(person=paul, group=beatles,
+ ... date_joined=date(1960, 8, 1),
+ ... invite_reason= "Wanted to form a band.")
+ >>> beatles.members.all()
+ [<Person: Ringo Starr>, <Person: Paul McCartney>]
+
+Unlike normal many-to-many fields, you *can't* use ``add``, ``create``,
+or assignment (i.e., ``beatles.members = [...]``) to create relationships::
+
+ # THIS WILL NOT WORK
+ >>> beatles.members.add(john)
+ # NEITHER WILL THIS
+ >>> beatles.members.create(name="George Harrison")
+ # AND NEITHER WILL THIS
+ >>> beatles.members = [john, paul, ringo, george]
+
+Why? You can't just create a relationship between a Person and a Group - you
+need to specify all the detail for the relationship required by the
+Membership table. The simple ``add``, ``create`` and assignment calls
+don't provide a way to specify this extra detail. As a result, they are
+disabled for many-to-many relationships that use an intermediate model.
+The only way to create a many-to-many relationship with an intermediate table
+is to create instances of the intermediate model.
+
+The ``remove`` method is disabled for similar reasons. However, the
+``clear()`` method can be used to remove all many-to-many relationships
+for an instance::
+
+ # Beatles have broken up
+ >>> beatles.members.clear()
+
+Once you have established the many-to-many relationships by creating instances
+of your intermediate model, you can issue 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 groups with a member whose name starts with 'Paul'
+ >>> Groups.objects.filter(person__name__startswith='Paul')
+ [<Group: The Beatles>]
+
+As you are using an intermediate table, you can also query on the attributes
+of the intermediate model::
+
+ # Find all the members of the Beatles that joined after 1 Jan 1961
+ >>> Person.objects.filter(
+ ... group__name='The Beatles',
+ ... membership__date_joined__gt=date(1961,1,1))
+ [<Person: Ringo Starr]
+
One-to-one relationships
~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1145,7 +1270,7 @@ any parent classes in ``unique_together``.
For convenience, unique_together can be a single list when dealing
with a single set of fields::
- unique_together = ("driver", "restaurant")
+ unique_together = ("driver", "restaurant")
``verbose_name``
----------------