diff --git a/docs/topics/db/examples/index.txt b/docs/topics/db/examples/index.txt new file mode 100644 index 00000000000..321e539e4a3 --- /dev/null +++ b/docs/topics/db/examples/index.txt @@ -0,0 +1,10 @@ +======================================== +Examples of model relationship API usage +======================================== + +.. toctree:: + :maxdepth: 1 + + many_to_many + many_to_one + one_to_one diff --git a/docs/topics/db/examples/many_to_many.txt b/docs/topics/db/examples/many_to_many.txt new file mode 100644 index 00000000000..1ad89e71bf8 --- /dev/null +++ b/docs/topics/db/examples/many_to_many.txt @@ -0,0 +1,289 @@ +########################## +Many-to-many relationships +########################## + +.. highlight:: pycon + +To define a many-to-many relationship, use :ref:`ref-manytomany`. + +In this example, an ``Article`` can be published in multiple ``Publication`` +objects, and a ``Publication`` has multiple ``Article`` objects: + +.. code-block:: python + + from django.db import models + + class Publication(models.Model): + title = models.CharField(max_length=30) + + def __unicode__(self): + return self.title + + class Meta: + ordering = ('title',) + + class Article(models.Model): + headline = models.CharField(max_length=100) + publications = models.ManyToManyField(Publication) + + def __unicode__(self): + return self.headline + + class Meta: + ordering = ('headline',) + +What follows are examples of operations that can be performed using the Python +API facilities. + +Create a couple of Publications:: + + >>> p1 = Publication(title='The Python Journal') + >>> p1.save() + >>> p2 = Publication(title='Science News') + >>> p2.save() + >>> p3 = Publication(title='Science Weekly') + >>> p3.save() + +Create an Article:: + + >>> a1 = Article(headline='Django lets you build Web apps easily') + +You can't associate it with a Publication until it's been saved:: + + >>> a1.publications.add(p1) + Traceback (most recent call last): + ... + ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used. + +Save it! +:: + + >>> a1.save() + +Associate the Article with a Publication:: + + >>> a1.publications.add(p1) + +Create another Article, and set it to appear in both Publications:: + + >>> a2 = Article(headline='NASA uses Python') + >>> a2.save() + >>> a2.publications.add(p1, p2) + >>> a2.publications.add(p3) + +Adding a second time is OK:: + + >>> a2.publications.add(p3) + +Adding an object of the wrong type raises TypeError:: + + >>> a2.publications.add(a1) + Traceback (most recent call last): + ... + TypeError: 'Publication' instance expected + +Add a Publication directly via publications.add by using keyword arguments:: + + >>> new_publication = a2.publications.create(title='Highlights for Children') + +Article objects have access to their related Publication objects:: + + >>> a1.publications.all() + [] + >>> a2.publications.all() + [, , , ] + +Publication objects have access to their related Article objects:: + + >>> p2.article_set.all() + [] + >>> p1.article_set.all() + [, ] + >>> Publication.objects.get(id=4).article_set.all() + [] + +Many-to-many relationships can be queried using :ref:`lookups across relationships `:: + + >>> Article.objects.filter(publications__id__exact=1) + [, ] + >>> Article.objects.filter(publications__pk=1) + [, ] + >>> Article.objects.filter(publications=1) + [, ] + >>> Article.objects.filter(publications=p1) + [, ] + + >>> Article.objects.filter(publications__title__startswith="Science") + [, ] + + >>> Article.objects.filter(publications__title__startswith="Science").distinct() + [] + +The count() function respects distinct() as well:: + + >>> Article.objects.filter(publications__title__startswith="Science").count() + 2 + + >>> Article.objects.filter(publications__title__startswith="Science").distinct().count() + 1 + + >>> Article.objects.filter(publications__in=[1,2]).distinct() + [, ] + >>> Article.objects.filter(publications__in=[p1,p2]).distinct() + [, ] + +Reverse m2m queries are supported (i.e., starting at the table that doesn't have +a ManyToManyField):: + + >>> Publication.objects.filter(id__exact=1) + [] + >>> Publication.objects.filter(pk=1) + [] + + >>> Publication.objects.filter(article__headline__startswith="NASA") + [, , , ] + + >>> Publication.objects.filter(article__id__exact=1) + [] + >>> Publication.objects.filter(article__pk=1) + [] + >>> Publication.objects.filter(article=1) + [] + >>> Publication.objects.filter(article=a1) + [] + + >>> Publication.objects.filter(article__in=[1,2]).distinct() + [, , , ] + >>> Publication.objects.filter(article__in=[a1,a2]).distinct() + [, , , ] + +Excluding a related item works as you would expect, too (although the SQL +involved is a little complex):: + + >>> Article.objects.exclude(publications=p2) + [] + +If we delete a Publication, its Articles won't be able to access it:: + + >>> p1.delete() + >>> Publication.objects.all() + [, , ] + >>> a1 = Article.objects.get(pk=1) + >>> a1.publications.all() + [] + +If we delete an Article, its Publications won't be able to access it:: + + >>> a2.delete() + >>> Article.objects.all() + [] + >>> p2.article_set.all() + [] + +Adding via the 'other' end of an m2m:: + + >>> a4 = Article(headline='NASA finds intelligent life on Earth') + >>> a4.save() + >>> p2.article_set.add(a4) + >>> p2.article_set.all() + [] + >>> a4.publications.all() + [] + +Adding via the other end using keywords:: + + >>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders') + >>> p2.article_set.all() + [, ] + >>> a5 = p2.article_set.all()[1] + >>> a5.publications.all() + [] + +Removing publication from an article:: + + >>> a4.publications.remove(p2) + >>> p2.article_set.all() + [] + >>> a4.publications.all() + [] + +And from the other end:: + + >>> p2.article_set.remove(a5) + >>> p2.article_set.all() + [] + >>> a5.publications.all() + [] + +Relation sets can be assigned. Assignment clears any existing set members:: + + >>> a4.publications.all() + [] + >>> a4.publications = [p3] + >>> a4.publications.all() + [] + +Relation sets can be cleared:: + + >>> p2.article_set.clear() + >>> p2.article_set.all() + [] + +And you can clear from the other end:: + + >>> p2.article_set.add(a4, a5) + >>> p2.article_set.all() + [, ] + >>> a4.publications.all() + [, ] + >>> a4.publications.clear() + >>> a4.publications.all() + [] + >>> p2.article_set.all() + [] + +Recreate the article and Publication we have deleted:: + + >>> p1 = Publication(title='The Python Journal') + >>> p1.save() + >>> a2 = Article(headline='NASA uses Python') + >>> a2.save() + >>> a2.publications.add(p1, p2, p3) + +Bulk delete some Publications - references to deleted publications should go:: + + >>> Publication.objects.filter(title__startswith='Science').delete() + >>> Publication.objects.all() + [, ] + >>> Article.objects.all() + [, , , ] + >>> a2.publications.all() + [] + +Bulk delete some articles - references to deleted objects should go:: + + >>> q = Article.objects.filter(headline__startswith='Django') + >>> print q + [] + >>> q.delete() + +After the delete, the QuerySet cache needs to be cleared, and the referenced +objects should be gone:: + + >>> print q + [] + >>> p1.article_set.all() + [] + +An alternate to calling clear() is to assign the empty set:: + + >>> p1.article_set = [] + >>> p1.article_set.all() + [] + + >>> a2.publications = [p1, new_publication] + >>> a2.publications.all() + [, ] + >>> a2.publications = [] + >>> a2.publications.all() + [] diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt new file mode 100644 index 00000000000..0a9978b8d1f --- /dev/null +++ b/docs/topics/db/examples/many_to_one.txt @@ -0,0 +1,208 @@ +######################### +Many-to-one relationships +######################### + +.. highlight:: pycon + +To define a many-to-one relationship, use :class:`~django.db.models.ForeignKey`. + +.. code-block:: python + + from django.db import models + + class Reporter(models.Model): + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) + email = models.EmailField() + + def __unicode__(self): + return u"%s %s" % (self.first_name, self.last_name) + + class Article(models.Model): + headline = models.CharField(max_length=100) + pub_date = models.DateField() + reporter = models.ForeignKey(Reporter) + + def __unicode__(self): + return self.headline + + class Meta: + ordering = ('headline',) + +What follows are examples of operations that can be performed using the Python +API facilities. + +Create a few Reporters:: + + >>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com') + >>> r.save() + + >>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') + >>> r2.save() + +Create an Article:: + + >>> from datetime import datetime + >>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r) + >>> a.save() + + >>> a.reporter.id + 1 + + >>> a.reporter + + +Article objects have access to their related Reporter objects:: + + >>> r = a.reporter + +These are strings instead of unicode strings because that's what was used in +the creation of this reporter (and we haven't refreshed the data from the +database, which always returns unicode strings):: + + >>> r.first_name, r.last_name + ('John', 'Smith') + +Create an Article via the Reporter object:: + + >>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) + >>> new_article + + >>> new_article.reporter + + >>> new_article.reporter.id + 1 + +Create a new article, and add it to the article set:: + + >>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) + >>> r.article_set.add(new_article2) + >>> new_article2.reporter + + >>> new_article2.reporter.id + 1 + >>> r.article_set.all() + [, , ] + +Add the same article to a different article set - check that it moves:: + + >>> r2.article_set.add(new_article2) + >>> new_article2.reporter.id + 2 + >>> new_article2.reporter + + +Adding an object of the wrong type raises TypeError:: + + >>> r.article_set.add(r2) + Traceback (most recent call last): + ... + TypeError: 'Article' instance expected + + >>> r.article_set.all() + [, ] + >>> r2.article_set.all() + [] + + >>> r.article_set.count() + 2 + + >>> r2.article_set.count() + 1 + +Note that in the last example the article has moved from John to Paul. + +Related managers support field lookups as well. +The API automatically follows relationships as far as you need. +Use double underscores to separate relationships. +This works as many levels deep as you want. There's no limit. For example:: + + >>> r.article_set.filter(headline__startswith='This') + [] + + # Find all Articles for any Reporter whose first name is "John". + >>> Article.objects.filter(reporter__first_name__exact='John') + [, ] + +Exact match is implied here:: + + >>> Article.objects.filter(reporter__first_name='John') + [, ] + +Query twice over the related field. This translates to an AND condition in the +WHERE clause:: + + >>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') + [, ] + +For the related lookup you can supply a primary key value or pass the related +object explicitly:: + + >>> Article.objects.filter(reporter__pk=1) + [, ] + >>> Article.objects.filter(reporter=1) + [, ] + >>> Article.objects.filter(reporter=r) + [, ] + + >>> Article.objects.filter(reporter__in=[1,2]).distinct() + [, , ] + >>> Article.objects.filter(reporter__in=[r,r2]).distinct() + [, , ] + +You can also use a queryset instead of a literal list of instances:: + + >>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John')).distinct() + [, ] + +Querying in the opposite direction:: + + >>> Reporter.objects.filter(article__pk=1) + [] + >>> Reporter.objects.filter(article=1) + [] + >>> Reporter.objects.filter(article=a) + [] + + >>> Reporter.objects.filter(article__headline__startswith='This') + [, , ] + >>> Reporter.objects.filter(article__headline__startswith='This').distinct() + [] + +Counting in the opposite direction works in conjunction with distinct():: + + >>> Reporter.objects.filter(article__headline__startswith='This').count() + 3 + >>> Reporter.objects.filter(article__headline__startswith='This').distinct().count() + 1 + +Queries can go round in circles:: + + >>> Reporter.objects.filter(article__reporter__first_name__startswith='John') + [, , , ] + >>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() + [] + >>> Reporter.objects.filter(article__reporter__exact=r).distinct() + [] + +If you delete a reporter, his articles will be deleted (assuming that the +ForeignKey was defined with :attr:`django.db.models.ForeignKey.on_delete` set to +``CASCADE``, which is the default):: + + >>> Article.objects.all() + [, , ] + >>> Reporter.objects.order_by('first_name') + [, ] + >>> r2.delete() + >>> Article.objects.all() + [, ] + >>> Reporter.objects.order_by('first_name') + [] + +You can delete using a JOIN in the query:: + + >>> Reporter.objects.filter(article__headline__startswith='This').delete() + >>> Reporter.objects.all() + [] + >>> Article.objects.all() + [] diff --git a/docs/topics/db/examples/one_to_one.txt b/docs/topics/db/examples/one_to_one.txt new file mode 100644 index 00000000000..4c8e0ecfcc4 --- /dev/null +++ b/docs/topics/db/examples/one_to_one.txt @@ -0,0 +1,132 @@ +######################## +One-to-one relationships +######################## + +.. highlight:: pycon + +To define a one-to-one relationship, use :ref:`ref-onetoone`. + +In this example, a ``Place`` optionally can be a ``Restaurant``: + +.. code-block:: python + + from django.db import models, transaction, IntegrityError + + class Place(models.Model): + name = models.CharField(max_length=50) + address = models.CharField(max_length=80) + + def __unicode__(self): + return u"%s the place" % self.name + + class Restaurant(models.Model): + place = models.OneToOneField(Place, primary_key=True) + serves_hot_dogs = models.BooleanField() + serves_pizza = models.BooleanField() + + def __unicode__(self): + return u"%s the restaurant" % self.place.name + + class Waiter(models.Model): + restaurant = models.ForeignKey(Restaurant) + name = models.CharField(max_length=50) + + def __unicode__(self): + return u"%s the waiter at %s" % (self.name, self.restaurant) + +What follows are examples of operations that can be performed using the Python +API facilities. + +Create a couple of Places:: + + >>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') + >>> p1.save() + >>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') + >>> p2.save() + +Create a Restaurant. Pass the ID of the "parent" object as this object's ID:: + + >>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False) + >>> r.save() + +A Restaurant can access its place:: + + >>> r.place + + +A Place can access its restaurant, if available:: + + >>> p1.restaurant + + +p2 doesn't have an associated restaurant:: + + >>> p2.restaurant + Traceback (most recent call last): + ... + DoesNotExist: Restaurant matching query does not exist. + +Set the place using assignment notation. Because place is the primary key on +Restaurant, the save will create a new restaurant:: + + >>> r.place = p2 + >>> r.save() + >>> p2.restaurant + + >>> r.place + + +Set the place back again, using assignment in the reverse direction:: + + >>> p1.restaurant = r + >>> p1.restaurant + + +Restaurant.objects.all() just returns the Restaurants, not the Places. Note +that there are two restaurants - Ace Hardware the Restaurant was created in the +call to r.place = p2:: + + >>> Restaurant.objects.all() + [, ] + +Place.objects.all() returns all Places, regardless of whether they have +Restaurants:: + + >>> Place.objects.order_by('name') + [, ] + +You can query the models using :ref:`lookups across relationships `:: + + >>> Restaurant.objects.get(place=p1) + + >>> Restaurant.objects.get(place__pk=1) + + >>> Restaurant.objects.filter(place__name__startswith="Demon") + [] + >>> Restaurant.objects.exclude(place__address__contains="Ashland") + [] + +This of course works in reverse:: + + >>> Place.objects.get(pk=1) + + >>> Place.objects.get(restaurant__place__exact=p1) + + >>> Place.objects.get(restaurant=r) + + >>> Place.objects.get(restaurant__place__name__startswith="Demon") + + +Add a Waiter to the Restaurant:: + + >>> w = r.waiter_set.create(name='Joe') + >>> w.save() + >>> w + + +Query the waiters:: + + >>> Waiter.objects.filter(restaurant__place=p1) + [] + >>> Waiter.objects.filter(restaurant__place__name__startswith="Demon") + [] diff --git a/docs/topics/db/index.txt b/docs/topics/db/index.txt index 0e0fc8adb60..5b45e36e7c7 100644 --- a/docs/topics/db/index.txt +++ b/docs/topics/db/index.txt @@ -19,3 +19,4 @@ model maps to a single database table. multi-db tablespaces optimization + examples/index diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 9a4010d953a..9b29e1ef3af 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -18,13 +18,6 @@ The basics: * With all of this, Django gives you an automatically-generated database-access API; see :doc:`/topics/db/queries`. -.. seealso:: - - A companion to this document is the `official repository of model - examples`_. (In the Django source distribution, these examples are in the - ``tests/modeltests`` directory.) - - .. _official repository of model examples: https://code.djangoproject.com/browser/django/trunk/tests/modeltests Quick example ============= @@ -326,9 +319,6 @@ whatever you want. For example:: For details on accessing backwards-related objects, see the :ref:`Following relationships backward example `. - For sample code, see the `Many-to-one relationship model tests`_. - - .. _Many-to-one relationship model tests: https://code.djangoproject.com/browser/django/trunk/tests/modeltests/many_to_one Many-to-many relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -376,10 +366,6 @@ form would let users select the toppings. .. seealso:: - See the `Many-to-many relationship model example`_ for a full example. - -.. _Many-to-many relationship model example: https://code.djangoproject.com/browser/django/trunk/tests/modeltests/many_to_many/models.py - :class:`~django.db.models.ManyToManyField` fields also accept a number of extra arguments which are explained in :ref:`the model field reference `. These options help define how the relationship should @@ -569,10 +555,6 @@ can be made; see :ref:`the model field reference ` for details. .. seealso:: - See the `One-to-one relationship model example`_ for a full example. - -.. _One-to-one relationship model example: https://code.djangoproject.com/browser/django/trunk/tests/modeltests/one_to_one/models.py - :class:`~django.db.models.OneToOneField` fields also accept one optional argument described in the :ref:`model field reference `. diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 351a1fd8e67..0a67b9b486b 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -483,6 +483,8 @@ probably use: Again, this only scratches the surface. A complete reference can be found in the :ref:`field lookup reference `. +.. _lookups-that-span-relationships: + Lookups that span relationships -------------------------------