diff options
| author | Justin Bronn <jbronn@gmail.com> | 2008-04-27 17:17:04 +0000 |
|---|---|---|
| committer | Justin Bronn <jbronn@gmail.com> | 2008-04-27 17:17:04 +0000 |
| commit | e973fea91c4e5924f1d0d709b9c8f0d069380709 (patch) | |
| tree | 135c266171c5492bbb095e333e9737eb87c32665 /tests/modeltests | |
| parent | 5456919782e5cd0b885dd383d57e187a06148307 (diff) | |
gis: Merged revisions 7458,7471-7473,7476-7478,7480 via svnmerge from trunk.
This includes all necessary patches for compatibility with queryset-refactor.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7482 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/modeltests')
21 files changed, 554 insertions, 68 deletions
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 557331a36e..51de8a50f8 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -292,11 +292,9 @@ datetime.datetime(2005, 7, 28, 0, 0) >>> Article.objects.all()[2:][2:3] [<Article: Default headline>] -# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: ->>> Article.objects.all()[2:] -Traceback (most recent call last): - ... -AssertionError: 'offset' is not allowed without 'limit' +# Using an offset without a limit is also possible. +>>> Article.objects.all()[5:] +[<Article: Fourth article>, <Article: Article 7>, <Article: Updated article 8>] # Also, once you have sliced you can't filter, re-order or combine >>> Article.objects.all()[0:5].filter(id=1) diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py index e1d0bc6e94..a0800299a7 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/modeltests/custom_columns/models.py @@ -55,8 +55,8 @@ __test__ = {'API_TESTS':""" >>> art.save() >>> art.authors = [a, a2] -# Although the table and column names on Author have been set to -# custom values, nothing about using the Author model has changed... +# Although the table and column names on Author have been set to custom values, +# nothing about using the Author model has changed... # Query the available authors >>> Author.objects.all() @@ -71,7 +71,7 @@ __test__ = {'API_TESTS':""" >>> Author.objects.filter(firstname__exact='John') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article, id, first_name, last_name +FieldError: Cannot resolve keyword 'firstname' into field. Choices are: article, first_name, id, last_name >>> a = Author.objects.get(last_name__exact='Smith') >>> a.first_name diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py index 97804f5cd5..baf07a072f 100644 --- a/tests/modeltests/field_subclassing/models.py +++ b/tests/modeltests/field_subclassing/models.py @@ -5,6 +5,7 @@ Tests for field subclassing. from django.db import models from django.utils.encoding import force_unicode from django.core import serializers +from django.core.exceptions import FieldError class Small(object): """ @@ -50,7 +51,7 @@ class SmallField(models.Field): return [force_unicode(v) for v in value] if lookup_type == 'isnull': return [] - raise TypeError('Invalid lookup type: %r' % lookup_type) + raise FieldError('Invalid lookup type: %r' % lookup_type) def flatten_data(self, follow, obj=None): return {self.attname: force_unicode(self._get_val_from_obj(obj))} @@ -94,7 +95,7 @@ True >>> MyModel.objects.filter(data__lt=s) Traceback (most recent call last): ... -TypeError: Invalid lookup type: 'lt' +FieldError: Invalid lookup type: 'lt' # Serialization works, too. >>> stream = serializers.serialize("json", MyModel.objects.all()) diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index f31857e0fe..7a53e93aec 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -162,12 +162,36 @@ True >>> Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_two') Traceback (most recent call last): ... -FieldDoesNotExist: Article has no field named 'id_plus_two' +FieldError: Cannot resolve keyword 'id_plus_two' into field. Choices are: headline, id, id_plus_one, pub_date # If you don't specify field names to values(), all are returned. >>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}] True +# values_list() is similar to values(), except that the results are returned as +# a list of tuples, rather than a list of dictionaries. Within each tuple, the +# order of the elemnts is the same as the order of fields in the values_list() +# call. +>>> Article.objects.values_list('headline') +[(u'Article 5',), (u'Article 6',), (u'Article 4',), (u'Article 2',), (u'Article 3',), (u'Article 7',), (u'Article 1',)] + +>>> Article.objects.values_list('id').order_by('id') +[(1,), (2,), (3,), (4,), (5,), (6,), (7,)] +>>> Article.objects.values_list('id', flat=True).order_by('id') +[1, 2, 3, 4, 5, 6, 7] + +>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id') +[(1,), (2,), (3,), (4,), (5,), (6,), (7,)] +>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id_plus_one', 'id') +[(2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7)] +>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id', 'id_plus_one') +[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)] + +>>> Article.objects.values_list('id', 'headline', flat=True) +Traceback (most recent call last): +... +TypeError: 'flat' is not valid when values_list is called with more than one field. + # Every DateField and DateTimeField creates get_next_by_FOO() and # get_previous_by_FOO() methods. # In the case of identical date values, these methods will use the ID as a @@ -240,6 +264,8 @@ DoesNotExist: Article matching query does not exist. [] >>> Article.objects.none().filter(headline__startswith='Article') [] +>>> Article.objects.filter(headline__startswith='Article').none() +[] >>> Article.objects.none().count() 0 >>> [article for article in Article.objects.none().iterator()] @@ -256,12 +282,12 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.filter(pub_date_year='2005').count() Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, headline, pub_date +FieldError: Cannot resolve keyword 'pub_date_year' into field. Choices are: headline, id, pub_date >>> Article.objects.filter(headline__starts='Article') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date +FieldError: Join on field 'headline' not permitted. # Create some articles with a bit more interesting headlines for testing field lookups: >>> now = datetime.now() diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py index 198c95c4d5..e09fd825f8 100644 --- a/tests/modeltests/many_to_many/models.py +++ b/tests/modeltests/many_to_many/models.py @@ -126,6 +126,11 @@ __test__ = {'API_TESTS':""" >>> Publication.objects.filter(article__in=[a1,a2]).distinct() [<Publication: Highlights for Children>, <Publication: Science News>, <Publication: Science Weekly>, <Publication: The Python Journal>] +# Excluding a related item works as you would expect, too (although the SQL +# involved is a little complex). +>>> Article.objects.exclude(publications=p2) +[<Article: Django lets you build Web apps easily>] + # If we delete a Publication, its Articles won't be able to access it. >>> p1.delete() >>> Publication.objects.all() diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index d5d07a1e21..6616f8b55e 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -145,18 +145,18 @@ False [<Article: John's second story>, <Article: This is a test>] # The underlying query only makes one join when a related table is referenced twice. ->>> query = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') ->>> null, sql, null = query._get_sql_clause() +>>> queryset = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') +>>> sql = queryset.query.as_sql()[0] >>> sql.count('INNER JOIN') 1 # The automatically joined table has a predictable name. ->>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"]) +>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='Smith'"]) [<Article: John's second story>, <Article: This is a test>] # And should work fine with the unicode that comes out of # newforms.Form.cleaned_data ->>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='%s'" % u'Smith']) +>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']) [<Article: John's second story>, <Article: This is a test>] # Find all Articles for the Reporter whose ID is 1. @@ -179,13 +179,13 @@ False >>> Article.objects.filter(reporter_id__exact=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter +FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter # You need to specify a comparison clause >>> Article.objects.filter(reporter_id=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter +FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter # You can also instantiate an Article by passing # the Reporter's ID instead of a Reporter object. @@ -250,6 +250,11 @@ TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, hea >>> Reporter.objects.filter(article__reporter=r).distinct() [<Reporter: John Smith>] +# It's possible to use values() calls across many-to-one relations. (Note, too, that we clear the ordering here so as not to drag the 'headline' field into the columns being used to determine uniqueness.) +>>> d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} +>>> list(Article.objects.filter(reporter=r).distinct().order_by().values('reporter__first_name', 'reporter__last_name')) == [d] +True + # If you delete a reporter, his articles will be deleted. >>> Article.objects.all() [<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>, <Article: This is a test>, <Article: This is a test>] diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py index 60c5888371..cee0e21a72 100644 --- a/tests/modeltests/many_to_one_null/models.py +++ b/tests/modeltests/many_to_one_null/models.py @@ -80,6 +80,11 @@ None >>> Article.objects.filter(reporter__isnull=True) [<Article: Third>] +# We can achieve the same thing by filtering for the case where the reporter is +# None. +>>> Article.objects.filter(reporter=None) +[<Article: Third>] + # Set the reporter for the Third article >>> r.article_set.add(a3) >>> r.article_set.all() diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py index ca00e96418..b1a751f5e8 100644 --- a/tests/modeltests/model_inheritance/models.py +++ b/tests/modeltests/model_inheritance/models.py @@ -1,11 +1,53 @@ """ XX. Model inheritance -Model inheritance isn't yet supported. +Model inheritance exists in two varieties: + - abstract base classes which are a way of specifying common + information inherited by the subclasses. They don't exist as a separate + model. + - non-abstract base classes (the default), which are models in their own + right with their own database tables and everything. Their subclasses + have references back to them, created automatically. + +Both styles are demonstrated here. """ from django.db import models +# +# Abstract base classes +# + +class CommonInfo(models.Model): + name = models.CharField(max_length=50) + age = models.PositiveIntegerField() + + class Meta: + abstract = True + ordering = ['name'] + + def __unicode__(self): + return u'%s %s' % (self.__class__.__name__, self.name) + +class Worker(CommonInfo): + job = models.CharField(max_length=50) + +class Student(CommonInfo): + school_class = models.CharField(max_length=10) + + class Meta: + pass + +# +# Multi-table inheritance +# + +class Chef(models.Model): + name = models.CharField(max_length=50) + + def __unicode__(self): + return u"%s the chef" % self.name + class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) @@ -13,9 +55,20 @@ class Place(models.Model): def __unicode__(self): return u"%s the place" % self.name -class Restaurant(Place): +class Rating(models.Model): + rating = models.IntegerField(null=True, blank=True) + + class Meta: + abstract = True + ordering = ['-rating'] + +class Restaurant(Place, Rating): serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField() + chef = models.ForeignKey(Chef, null=True, blank=True) + + class Meta(Rating.Meta): + db_table = 'my_restaurant' def __unicode__(self): return u"%s the restaurant" % self.name @@ -26,14 +79,58 @@ class ItalianRestaurant(Restaurant): def __unicode__(self): return u"%s the italian restaurant" % self.name +class Supplier(Place): + customers = models.ManyToManyField(Restaurant, related_name='provider') + + def __unicode__(self): + return u"%s the supplier" % self.name + +class ParkingLot(Place): + # An explicit link to the parent (we can control the attribute name). + parent = models.OneToOneField(Place, primary_key=True, parent_link=True) + main_site = models.ForeignKey(Place, related_name='lot') + + def __unicode__(self): + return u"%s the parking lot" % self.name + __test__ = {'API_TESTS':""" -# Make sure Restaurant has the right fields in the right order. ->>> [f.name for f in Restaurant._meta.fields] -['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza'] +# The Student and Worker models both have 'name' and 'age' fields on them and +# inherit the __unicode__() method, just as with normal Python subclassing. +# This is useful if you want to factor out common information for programming +# purposes, but still completely independent separate models at the database +# level. -# Make sure ItalianRestaurant has the right fields in the right order. ->>> [f.name for f in ItalianRestaurant._meta.fields] -['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza', 'serves_gnocchi'] +>>> w = Worker(name='Fred', age=35, job='Quarry worker') +>>> w.save() +>>> w2 = Worker(name='Barney', age=34, job='Quarry worker') +>>> w2.save() +>>> s = Student(name='Pebbles', age=5, school_class='1B') +>>> s.save() +>>> unicode(w) +u'Worker Fred' +>>> unicode(s) +u'Student Pebbles' + +# The children inherit the Meta class of their parents (if they don't specify +# their own). +>>> Worker.objects.values('name') +[{'name': u'Barney'}, {'name': u'Fred'}] + +# Since Student does not subclass CommonInfo's Meta, it has the effect of +# completely overriding it. So ordering by name doesn't take place for Students. +>>> Student._meta.ordering +[] + +# However, the CommonInfo class cannot be used as a normal model (it doesn't +# exist as a model). +>>> CommonInfo.objects.all() +Traceback (most recent call last): + ... +AttributeError: type object 'CommonInfo' has no attribute 'objects' + +# The Place/Restaurant/ItalianRestaurant models, on the other hand, all exist +# as independent models. However, the subclasses also have transparent access +# to the fields of their ancestors. # Create a couple of Places. >>> p1 = Place(name='Master Shakes', address='666 W. Jersey') @@ -41,13 +138,131 @@ __test__ = {'API_TESTS':""" >>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') >>> p2.save() -# Test constructor for Restaurant. ->>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False) +Test constructor for Restaurant. +>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton',serves_hot_dogs=True, serves_pizza=False, rating=2) >>> r.save() # Test the constructor for ItalianRestaurant. ->>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Elm', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True) +>>> c = Chef(name="Albert") +>>> c.save() +>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Ash', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True, rating=4, chef=c) +>>> ir.save() +>>> ir.address = '1234 W. Elm' >>> ir.save() +# Make sure Restaurant and ItalianRestaurant have the right fields in the right +# order. +>>> [f.name for f in Restaurant._meta.fields] +['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef'] +>>> [f.name for f in ItalianRestaurant._meta.fields] +['id', 'name', 'address', 'place_ptr', 'rating', 'serves_hot_dogs', 'serves_pizza', 'chef', 'restaurant_ptr', 'serves_gnocchi'] +>>> Restaurant._meta.ordering +['-rating'] + +# Even though p.supplier for a Place 'p' (a parent of a Supplier), a Restaurant +# object cannot access that reverse relation, since it's not part of the +# Place-Supplier Hierarchy. +>>> Place.objects.filter(supplier__name='foo') +[] +>>> Restaurant.objects.filter(supplier__name='foo') +Traceback (most recent call last): + ... +FieldError: Cannot resolve keyword 'supplier' into field. Choices are: address, chef, id, italianrestaurant, lot, name, place_ptr, provider, rating, serves_hot_dogs, serves_pizza + +# Parent fields can be used directly in filters on the child model. +>>> Restaurant.objects.filter(name='Demon Dogs') +[<Restaurant: Demon Dogs the restaurant>] +>>> ItalianRestaurant.objects.filter(address='1234 W. Elm') +[<ItalianRestaurant: Ristorante Miron the italian restaurant>] + +# Filters against the parent model return objects of the parent's type. +>>> Place.objects.filter(name='Demon Dogs') +[<Place: Demon Dogs the place>] + +# Since the parent and child are linked by an automatically created +# OneToOneField, you can get from the parent to the child by using the child's +# name. +>>> place = Place.objects.get(name='Demon Dogs') +>>> place.restaurant +<Restaurant: Demon Dogs the restaurant> + +>>> Place.objects.get(name='Ristorante Miron').restaurant.italianrestaurant +<ItalianRestaurant: Ristorante Miron the italian restaurant> +>>> Restaurant.objects.get(name='Ristorante Miron').italianrestaurant +<ItalianRestaurant: Ristorante Miron the italian restaurant> + +# This won't work because the Demon Dogs restaurant is not an Italian +# restaurant. +>>> place.restaurant.italianrestaurant +Traceback (most recent call last): + ... +DoesNotExist: ItalianRestaurant matching query does not exist. + +# Related objects work just as they normally do. + +>>> s1 = Supplier(name="Joe's Chickens", address='123 Sesame St') +>>> s1.save() +>>> s1.customers = [r, ir] +>>> s2 = Supplier(name="Luigi's Pasta", address='456 Sesame St') +>>> s2.save() +>>> s2.customers = [ir] + +# This won't work because the Place we select is not a Restaurant (it's a +# Supplier). +>>> p = Place.objects.get(name="Joe's Chickens") +>>> p.restaurant +Traceback (most recent call last): + ... +DoesNotExist: Restaurant matching query does not exist. + +# But we can descend from p to the Supplier child, as expected. +>>> p.supplier +<Supplier: Joe's Chickens the supplier> + +>>> ir.provider.order_by('-name') +[<Supplier: Luigi's Pasta the supplier>, <Supplier: Joe's Chickens the supplier>] + +>>> Restaurant.objects.filter(provider__name__contains="Chickens") +[<Restaurant: Ristorante Miron the restaurant>, <Restaurant: Demon Dogs the restaurant>] +>>> ItalianRestaurant.objects.filter(provider__name__contains="Chickens") +[<ItalianRestaurant: Ristorante Miron the italian restaurant>] + +>>> park1 = ParkingLot(name='Main St', address='111 Main St', main_site=s1) +>>> park1.save() +>>> park2 = ParkingLot(name='Well Lit', address='124 Sesame St', main_site=ir) +>>> park2.save() + +>>> Restaurant.objects.get(lot__name='Well Lit') +<Restaurant: Ristorante Miron the restaurant> + +# The update() command can update fields in parent and child classes at once +# (although it executed multiple SQL queries to do so). +>>> Restaurant.objects.filter(serves_hot_dogs=True, name__contains='D').update(name='Demon Puppies', serves_hot_dogs=False) +>>> r1 = Restaurant.objects.get(pk=r.pk) +>>> r1.serves_hot_dogs == False +True +>>> r1.name +u'Demon Puppies' + +# The values() command also works on fields from parent models. +>>> d = {'rating': 4, 'name': u'Ristorante Miron'} +>>> list(ItalianRestaurant.objects.values('name', 'rating')) == [d] +True + +# select_related works with fields from the parent object as if they were a +# normal part of the model. +>>> from django import db +>>> from django.conf import settings +>>> settings.DEBUG = True +>>> db.reset_queries() +>>> ItalianRestaurant.objects.all()[0].chef +<Chef: Albert the chef> +>>> len(db.connection.queries) +2 +>>> ItalianRestaurant.objects.select_related('chef')[0].chef +<Chef: Albert the chef> +>>> len(db.connection.queries) +3 +>>> settings.DEBUG = False """} diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py index 2f3f22e628..800ccddac2 100644 --- a/tests/modeltests/one_to_one/models.py +++ b/tests/modeltests/one_to_one/models.py @@ -6,7 +6,7 @@ To define a one-to-one relationship, use ``OneToOneField()``. In this example, a ``Place`` optionally can be a ``Restaurant``. """ -from django.db import models +from django.db import models, connection class Place(models.Model): name = models.CharField(max_length=50) @@ -16,7 +16,7 @@ class Place(models.Model): return u"%s the place" % self.name class Restaurant(models.Model): - place = models.OneToOneField(Place) + place = models.OneToOneField(Place, primary_key=True) serves_hot_dogs = models.BooleanField() serves_pizza = models.BooleanField() @@ -38,6 +38,14 @@ class RelatedModel(models.Model): link = models.OneToOneField(ManualPrimaryKey) name = models.CharField(max_length = 50) +class MultiModel(models.Model): + link1 = models.OneToOneField(Place) + link2 = models.OneToOneField(ManualPrimaryKey) + name = models.CharField(max_length=50) + + def __unicode__(self): + return u"Multimodel %s" % self.name + __test__ = {'API_TESTS':""" # Create a couple of Places. >>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') @@ -63,8 +71,8 @@ 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 +# 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 @@ -72,9 +80,9 @@ DoesNotExist: Restaurant matching query does not exist. >>> r.place <Place: Ace Hardware the place> -# Set the place back again, using assignment in the reverse direction -# Need to reget restaurant object first, because the reverse set -# can't update the existing restaurant instance +# Set the place back again, using assignment in the reverse direction. Need to +# reload restaurant object first, because the reverse set can't update the +# existing restaurant instance >>> p1.restaurant = r >>> r.save() >>> p1.restaurant @@ -86,8 +94,7 @@ DoesNotExist: Restaurant matching query does not exist. # 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. This means there are multiple restaurants referencing -# a single place... +# in the call to r.place = p2. >>> Restaurant.objects.all() [<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ace Hardware the restaurant>] @@ -165,4 +172,22 @@ DoesNotExist: Restaurant matching query does not exist. >>> o1.save() >>> o2 = RelatedModel(link=o1, name="secondary") >>> o2.save() + +# You can have multiple one-to-one fields on a model, too. +>>> x1 = MultiModel(link1=p1, link2=o1, name="x1") +>>> x1.save() +>>> o1.multimodel +<MultiModel: Multimodel x1> + +# This will fail because each one-to-one field must be unique (and link2=o1 was +# used for x1, above). +>>> MultiModel(link1=p2, link2=o1, name="x1").save() +Traceback (most recent call last): + ... +IntegrityError: ... + +# Because the unittests all use a single connection, we need to force a +# reconnect here to ensure the connection is clean (after the previous +# IntegrityError). +>>> connection.close() """} diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py index 44dda0dc16..c779e19e37 100644 --- a/tests/modeltests/or_lookups/models.py +++ b/tests/modeltests/or_lookups/models.py @@ -4,11 +4,9 @@ To perform an OR lookup, or a lookup that combines ANDs and ORs, combine QuerySet objects using & and | operators. -Alternatively, use positional arguments, and pass one or more expressions -of clauses using the variable ``django.db.models.Q`` (or any object with -a get_sql method). - - +Alternatively, use positional arguments, and pass one or more expressions of +clauses using the variable ``django.db.models.Q`` (or any object with an +add_to_query method). """ from django.db import models @@ -72,6 +70,8 @@ __test__ = {'API_TESTS':""" # You could also use "in" to accomplish the same as above. >>> Article.objects.filter(pk__in=[1,2,3]) [<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>] +>>> Article.objects.filter(pk__in=(1,2,3)) +[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>] >>> Article.objects.filter(pk__in=[1,2,3,4]) [<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>] @@ -92,6 +92,17 @@ __test__ = {'API_TESTS':""" >>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') [<Article: Hello and goodbye>] +# Q objects can be negated +>>> Article.objects.filter(Q(pk=1) | ~Q(pk=2)) +[<Article: Hello>, <Article: Hello and goodbye>] +>>> Article.objects.filter(~Q(pk=1) & ~Q(pk=2)) +[<Article: Hello and goodbye>] + +# This allows for more complex queries than filter() and exclude() alone would +# allow +>>> Article.objects.filter(Q(pk=1) & (~Q(pk=2) | Q(pk=3))) +[<Article: Hello>] + # Try some arg queries with operations other than filter. >>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye')) <Article: Hello and goodbye> diff --git a/tests/modeltests/order_with_respect_to/__init__.py b/tests/modeltests/order_with_respect_to/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/order_with_respect_to/__init__.py diff --git a/tests/modeltests/order_with_respect_to/models.py b/tests/modeltests/order_with_respect_to/models.py new file mode 100644 index 0000000000..9c8917f59b --- /dev/null +++ b/tests/modeltests/order_with_respect_to/models.py @@ -0,0 +1,78 @@ +""" +Tests for the order_with_respect_to Meta attribute. +""" + +from django.db import models + +class Question(models.Model): + text = models.CharField(max_length=200) + +class Answer(models.Model): + text = models.CharField(max_length=200) + question = models.ForeignKey(Question) + + class Meta: + order_with_respect_to = 'question' + + def __unicode__(self): + return unicode(self.text) + +__test__ = {'API_TESTS': """ +>>> q1 = Question(text="Which Beatle starts with the letter 'R'?") +>>> q1.save() +>>> q2 = Question(text="What is your name?") +>>> q2.save() +>>> Answer(text="John", question=q1).save() +>>> Answer(text="Jonno",question=q2).save() +>>> Answer(text="Paul", question=q1).save() +>>> Answer(text="Paulo", question=q2).save() +>>> Answer(text="George", question=q1).save() +>>> Answer(text="Ringo", question=q1).save() + +The answers will always be ordered in the order they were inserted. + +>>> q1.answer_set.all() +[<Answer: John>, <Answer: Paul>, <Answer: George>, <Answer: Ringo>] + +We can retrieve the answers related to a particular object, in the order +they were created, once we have a particular object. + +>>> a1 = Answer.objects.filter(question=q1)[0] +>>> a1 +<Answer: John> +>>> a2 = a1.get_next_in_order() +>>> a2 +<Answer: Paul> +>>> a4 = list(Answer.objects.filter(question=q1))[-1] +>>> a4 +<Answer: Ringo> +>>> a4.get_previous_in_order() +<Answer: George> + +Determining (and setting) the ordering for a particular item is also possible. + +>>> id_list = [o.pk for o in q1.answer_set.all()] +>>> a2.question.get_answer_order() == id_list +True + +>>> a5 = Answer(text="Number five", question=q1) +>>> a5.save() + +It doesn't matter which answer we use to check the order, it will always be the same. + +>>> a2.question.get_answer_order() == a5.question.get_answer_order() +True + +The ordering can be altered: + +>>> id_list = [o.pk for o in q1.answer_set.all()] +>>> x = id_list.pop() +>>> id_list.insert(-1, x) +>>> a5.question.get_answer_order == id_list +False +>>> a5.question.set_answer_order(id_list) +>>> q1.answer_set.all() +[<Answer: John>, <Answer: Paul>, <Answer: George>, <Answer: Number five>, <Answer: Ringo>] + +""" +} diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py index 9b342a9265..d7435f6f74 100644 --- a/tests/modeltests/ordering/models.py +++ b/tests/modeltests/ordering/models.py @@ -48,6 +48,13 @@ __test__ = {'API_TESTS':""" >>> Article.objects.order_by('pub_date', '-headline') [<Article: Article 1>, <Article: Article 3>, <Article: Article 2>, <Article: Article 4>] +# Only the last order_by has any effect (since they each override any previous +# ordering). +>>> Article.objects.order_by('id') +[<Article: Article 1>, <Article: Article 2>, <Article: Article 3>, <Article: Article 4>] +>>> Article.objects.order_by('id').order_by('-headline') +[<Article: Article 4>, <Article: Article 3>, <Article: Article 2>, <Article: Article 1>] + # Use the 'stop' part of slicing notation to limit the results. >>> Article.objects.order_by('headline')[:2] [<Article: Article 1>, <Article: Article 2>] @@ -64,4 +71,10 @@ __test__ = {'API_TESTS':""" # don't know what order the output will be in. >>> Article.objects.order_by('?') [...] + +# Ordering can be reversed using the reverse() method on a queryset. This +# allows you to extract things like "the last two items" (reverse and then +# take the first two). +>>> Article.objects.all().reverse()[:2] +[<Article: Article 1>, <Article: Article 3>] """} diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py index a11b8d9f88..f698b5bc49 100644 --- a/tests/modeltests/reserved_names/models.py +++ b/tests/modeltests/reserved_names/models.py @@ -45,8 +45,6 @@ h b >>> print v.where 2005-01-01 ->>> Thing.objects.order_by('select.when') -[<Thing: a>, <Thing: h>] >>> Thing.objects.dates('where', 'year') [datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)] diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index 80408ad761..ef385b4b18 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -55,5 +55,5 @@ __test__ = {'API_TESTS':""" >>> Poll.objects.get(choice__name__exact="This is the answer") Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'choice' into field. Choices are: poll_choice, related_choice, id, question, creator +FieldError: Cannot resolve keyword 'choice' into field. Choices are: creator, id, poll_choice, question, related_choice """} diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py index f0fd121665..9d64cf24c6 100644 --- a/tests/modeltests/select_related/models.py +++ b/tests/modeltests/select_related/models.py @@ -27,13 +27,13 @@ class Phylum(models.Model): kingdom = models.ForeignKey(Kingdom) def __unicode__(self): return self.name - + class Klass(models.Model): name = models.CharField(max_length=50) phylum = models.ForeignKey(Phylum) def __unicode__(self): return self.name - + class Order(models.Model): name = models.CharField(max_length=50) klass = models.ForeignKey(Klass) @@ -63,7 +63,7 @@ def create_tree(stringtree): names = stringtree.split() models = [Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species] assert len(names) == len(models), (names, models) - + parent = None for name, model in zip(names, models): try: @@ -100,7 +100,7 @@ __test__ = {'API_TESTS':""" # However, a select_related() call will fill in those related objects without any extra queries: >>> db.reset_queries() ->>> person = Species.objects.select_related().get(name="sapiens") +>>> person = Species.objects.select_related(depth=10).get(name="sapiens") >>> person.genus.family.order.klass.phylum.kingdom.domain <Domain: Eukaryota> >>> len(db.connection.queries) @@ -129,7 +129,7 @@ __test__ = {'API_TESTS':""" >>> pea.genus.family.order.klass.phylum.kingdom.domain <Domain: Eukaryota> -# Notice: one few query than above because of depth=1 +# Notice: one fewer queries than above because of depth=1 >>> len(db.connection.queries) 7 @@ -147,6 +147,43 @@ __test__ = {'API_TESTS':""" >>> len(db.connection.queries) 5 +>>> s = Species.objects.all().select_related(depth=1).extra(select={'a': 'select_related_species.id + 10'})[0] +>>> s.id + 10 == s.a +True + +# The optional fields passed to select_related() control which related models +# we pull in. This allows for smaller queries and can act as an alternative +# (or, in addition to) the depth parameter. + +# In the next two cases, we explicitly say to select the 'genus' and +# 'genus.family' models, leading to the same number of queries as before. +>>> db.reset_queries() +>>> world = Species.objects.select_related('genus__family') +>>> [o.genus.family for o in world] +[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] +>>> len(db.connection.queries) +1 + +>>> db.reset_queries() +>>> world = Species.objects.filter(genus__name='Amanita').select_related('genus__family') +>>> [o.genus.family.order for o in world] +[<Order: Agaricales>] +>>> len(db.connection.queries) +2 + +>>> db.reset_queries() +>>> Species.objects.all().select_related('genus__family__order').order_by('id')[0:1].get().genus.family.order.name +u'Diptera' +>>> len(db.connection.queries) +1 + +# Specifying both "depth" and fields is an error. +>>> Species.objects.select_related('genus__family__order', depth=4) +Traceback (most recent call last): +... +TypeError: Cannot pass both "depth" and fields to select_related() + # Reset DEBUG to where we found it. >>> settings.DEBUG = False """} + diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index 0ccc19f895..f9dd8f288b 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -22,7 +22,7 @@ class Author(models.Model): class Meta: ordering = ('name',) - + def __unicode__(self): return self.name @@ -39,21 +39,21 @@ class Article(models.Model): return self.headline class AuthorProfile(models.Model): - author = models.OneToOneField(Author) + author = models.OneToOneField(Author, primary_key=True) date_of_birth = models.DateField() - + def __unicode__(self): return u"Profile of %s" % self.author - + class Actor(models.Model): name = models.CharField(max_length=20, primary_key=True) class Meta: ordering = ('name',) - + def __unicode__(self): return self.name - + class Movie(models.Model): actor = models.ForeignKey(Actor) title = models.CharField(max_length=50) @@ -63,7 +63,7 @@ class Movie(models.Model): def __unicode__(self): return self.title - + class Score(models.Model): score = models.FloatField() @@ -100,7 +100,7 @@ __test__ = {'API_TESTS':""" >>> dom = minidom.parseString(xml) # Deserializing has a similar interface, except that special DeserializedObject -# instances are returned. This is because data might have changed in the +# instances are returned. This is because data might have changed in the # database since the data was serialized (we'll simulate that below). >>> for obj in serializers.deserialize("xml", xml): ... print obj @@ -148,7 +148,7 @@ __test__ = {'API_TESTS':""" >>> Article.objects.all() [<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>] -# If you use your own primary key field (such as a OneToOneField), +# If you use your own primary key field (such as a OneToOneField), # it doesn't appear in the serialized field list - it replaces the # pk identifier. >>> profile = AuthorProfile(author=joe, date_of_birth=datetime(1970,1,1)) @@ -186,7 +186,7 @@ __test__ = {'API_TESTS':""" >>> print serializers.serialize("json", Article.objects.all(), fields=('headline','pub_date')) [{"pk": 1, "model": "serializers.article", "fields": {"headline": "Just kidding; I love TV poker", "pub_date": "2006-06-16 11:00:00"}}, {"pk": 2, "model": "serializers.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:11"}}, {"pk": 3, "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00"}}] -# Every string is serialized as a unicode object, also primary key +# Every string is serialized as a unicode object, also primary key # which is 'varchar' >>> ac = Actor(name="Zażółć") >>> mv = Movie(title="Gęślą jaźń", actor=ac) @@ -247,12 +247,13 @@ try: pk: 2 <BLANKLINE> ->>> obs = list(serializers.deserialize("yaml", serialized)) ->>> for i in obs: +>>> obs = list(serializers.deserialize("yaml", serialized)) +>>> for i in obs: ... print i <DeserializedObject: Just kidding; I love TV poker> <DeserializedObject: Time to reform copyright> """ -except ImportError: pass - +except ImportError: + pass + diff --git a/tests/modeltests/signals/models.py b/tests/modeltests/signals/models.py index 5b0f5f2285..fc58d90a14 100644 --- a/tests/modeltests/signals/models.py +++ b/tests/modeltests/signals/models.py @@ -66,7 +66,8 @@ post_save_nokwargs signal post_save signal, Tom Smith Is updated ->>> p1.save(raw=True) +# Calling an internal method purely so that we can trigger a "raw" save. +>>> p1.save_base(raw=True) pre_save_nokwargs signal pre_save signal, Tom Smith Is raw diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py index 06d21bbdd4..a3222cd511 100644 --- a/tests/modeltests/transactions/models.py +++ b/tests/modeltests/transactions/models.py @@ -25,7 +25,7 @@ from django.conf import settings building_docs = getattr(settings, 'BUILDING_DOCS', False) -if building_docs or settings.DATABASE_ENGINE != 'mysql': +if building_docs or settings.DATABASE_ENGINE not in ('mysql', 'mysql_old'): __test__['API_TESTS'] += """ # the default behavior is to autocommit after each save() action >>> def create_a_reporter_then_fail(first, last): diff --git a/tests/modeltests/update/__init__.py b/tests/modeltests/update/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/update/__init__.py diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py new file mode 100644 index 0000000000..3b0f83389f --- /dev/null +++ b/tests/modeltests/update/models.py @@ -0,0 +1,67 @@ +""" +Tests for the update() queryset method that allows in-place, multi-object +updates. +""" + +from django.db import models + +class DataPoint(models.Model): + name = models.CharField(max_length=20) + value = models.CharField(max_length=20) + another_value = models.CharField(max_length=20, blank=True) + + def __unicode__(self): + return unicode(self.name) + +class RelatedPoint(models.Model): + name = models.CharField(max_length=20) + data = models.ForeignKey(DataPoint) + + def __unicode__(self): + return unicode(self.name) + + +__test__ = {'API_TESTS': """ +>>> DataPoint(name="d0", value="apple").save() +>>> DataPoint(name="d2", value="banana").save() +>>> d3 = DataPoint(name="d3", value="banana") +>>> d3.save() +>>> RelatedPoint(name="r1", data=d3).save() + +Objects are updated by first filtering the candidates into a queryset and then +calling the update() method. It executes immediately and returns nothing. + +>>> DataPoint.objects.filter(value="apple").update(name="d1") +>>> DataPoint.objects.filter(value="apple") +[<DataPoint: d1>] + +We can update multiple objects at once. + +>>> DataPoint.objects.filter(value="banana").update(value="pineapple") +>>> DataPoint.objects.get(name="d2").value +u'pineapple' + +Foreign key fields can also be updated, although you can only update the object +referred to, not anything inside the related object. + +>>> d = DataPoint.objects.get(name="d1") +>>> RelatedPoint.objects.filter(name="r1").update(data=d) +>>> RelatedPoint.objects.filter(data__name="d1") +[<RelatedPoint: r1>] + +Multiple fields can be updated at once + +>>> DataPoint.objects.filter(value="pineapple").update(value="fruit", another_value="peaches") +>>> d = DataPoint.objects.get(name="d2") +>>> d.value, d.another_value +(u'fruit', u'peaches') + +In the rare case you want to update every instance of a model, update() is also +a manager method. + +>>> DataPoint.objects.update(value='thing') +>>> DataPoint.objects.values('value').distinct() +[{'value': u'thing'}] + +""" +} |
