summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2008-04-27 17:17:04 +0000
committerJustin Bronn <jbronn@gmail.com>2008-04-27 17:17:04 +0000
commite973fea91c4e5924f1d0d709b9c8f0d069380709 (patch)
tree135c266171c5492bbb095e333e9737eb87c32665 /tests
parent5456919782e5cd0b885dd383d57e187a06148307 (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')
-rw-r--r--tests/modeltests/basic/models.py8
-rw-r--r--tests/modeltests/custom_columns/models.py6
-rw-r--r--tests/modeltests/field_subclassing/models.py5
-rw-r--r--tests/modeltests/lookup/models.py32
-rw-r--r--tests/modeltests/many_to_many/models.py5
-rw-r--r--tests/modeltests/many_to_one/models.py17
-rw-r--r--tests/modeltests/many_to_one_null/models.py5
-rw-r--r--tests/modeltests/model_inheritance/models.py237
-rw-r--r--tests/modeltests/one_to_one/models.py43
-rw-r--r--tests/modeltests/or_lookups/models.py21
-rw-r--r--tests/modeltests/order_with_respect_to/__init__.py0
-rw-r--r--tests/modeltests/order_with_respect_to/models.py78
-rw-r--r--tests/modeltests/ordering/models.py13
-rw-r--r--tests/modeltests/reserved_names/models.py2
-rw-r--r--tests/modeltests/reverse_lookup/models.py2
-rw-r--r--tests/modeltests/select_related/models.py47
-rw-r--r--tests/modeltests/serializers/models.py29
-rw-r--r--tests/modeltests/signals/models.py3
-rw-r--r--tests/modeltests/transactions/models.py2
-rw-r--r--tests/modeltests/update/__init__.py0
-rw-r--r--tests/modeltests/update/models.py67
-rw-r--r--tests/regressiontests/null_queries/models.py15
-rw-r--r--tests/regressiontests/queries/__init__.py0
-rw-r--r--tests/regressiontests/queries/models.py658
-rw-r--r--tests/regressiontests/serializers_regress/models.py27
-rw-r--r--tests/regressiontests/serializers_regress/tests.py16
26 files changed, 1243 insertions, 95 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'}]
+
+"""
+}
diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py
index 2aa36b2c1a..df04f14eba 100644
--- a/tests/regressiontests/null_queries/models.py
+++ b/tests/regressiontests/null_queries/models.py
@@ -14,7 +14,7 @@ class Choice(models.Model):
return u"Choice: %s in poll %s" % (self.choice, self.poll)
__test__ = {'API_TESTS':"""
-# Regression test for the use of None as a query value. None is interpreted as
+# Regression test for the use of None as a query value. None is interpreted as
# an SQL NULL, but only in __exact queries.
# Set up some initial polls and choices
>>> p1 = Poll(question='Why?')
@@ -24,15 +24,20 @@ __test__ = {'API_TESTS':"""
>>> c2 = Choice(poll=p1, choice='Why Not?')
>>> c2.save()
-# Exact query with value None returns nothing (=NULL in sql)
->>> Choice.objects.filter(id__exact=None)
+# Exact query with value None returns nothing ("is NULL" in sql, but every 'id'
+# field has a value).
+>>> Choice.objects.filter(choice__exact=None)
[]
+Excluding the previous result returns everything.
+>>> Choice.objects.exclude(choice=None).order_by('id')
+[<Choice: Choice: Because. in poll Q: Why? >, <Choice: Choice: Why Not? in poll Q: Why? >]
+
# Valid query, but fails because foo isn't a keyword
->>> Choice.objects.filter(foo__exact=None)
+>>> Choice.objects.filter(foo__exact=None)
Traceback (most recent call last):
...
-TypeError: Cannot resolve keyword 'foo' into field. Choices are: id, poll, choice
+FieldError: Cannot resolve keyword 'foo' into field. Choices are: choice, id, poll
# Can't use None on anything other than __exact
>>> Choice.objects.filter(id__gt=None)
diff --git a/tests/regressiontests/queries/__init__.py b/tests/regressiontests/queries/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/queries/__init__.py
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
new file mode 100644
index 0000000000..483aa7218c
--- /dev/null
+++ b/tests/regressiontests/queries/models.py
@@ -0,0 +1,658 @@
+"""
+Various complex queries that have been problematic in the past.
+"""
+
+import datetime
+
+from django.db import models
+from django.db.models.query import Q
+
+class Tag(models.Model):
+ name = models.CharField(max_length=10)
+ parent = models.ForeignKey('self', blank=True, null=True)
+
+ def __unicode__(self):
+ return self.name
+
+class Note(models.Model):
+ note = models.CharField(max_length=100)
+ misc = models.CharField(max_length=10)
+
+ class Meta:
+ ordering = ['note']
+
+ def __unicode__(self):
+ return self.note
+
+class ExtraInfo(models.Model):
+ info = models.CharField(max_length=100)
+ note = models.ForeignKey(Note)
+
+ class Meta:
+ ordering = ['info']
+
+ def __unicode__(self):
+ return self.info
+
+class Author(models.Model):
+ name = models.CharField(max_length=10)
+ num = models.IntegerField(unique=True)
+ extra = models.ForeignKey(ExtraInfo)
+
+ def __unicode__(self):
+ return self.name
+
+class Item(models.Model):
+ name = models.CharField(max_length=10)
+ created = models.DateTimeField()
+ tags = models.ManyToManyField(Tag, blank=True, null=True)
+ creator = models.ForeignKey(Author)
+ note = models.ForeignKey(Note)
+
+ class Meta:
+ ordering = ['-note', 'name']
+
+ def __unicode__(self):
+ return self.name
+
+class Report(models.Model):
+ name = models.CharField(max_length=10)
+ creator = models.ForeignKey(Author, to_field='num')
+
+ def __unicode__(self):
+ return self.name
+
+class Ranking(models.Model):
+ rank = models.IntegerField()
+ author = models.ForeignKey(Author)
+
+ class Meta:
+ # A complex ordering specification. Should stress the system a bit.
+ ordering = ('author__extra__note', 'author__name', 'rank')
+
+ def __unicode__(self):
+ return '%d: %s' % (self.rank, self.author.name)
+
+class Cover(models.Model):
+ title = models.CharField(max_length=50)
+ item = models.ForeignKey(Item)
+
+ class Meta:
+ ordering = ['item']
+
+ def __unicode__(self):
+ return self.title
+
+class Number(models.Model):
+ num = models.IntegerField()
+
+ def __unicode__(self):
+ return unicode(self.num)
+
+# Some funky cross-linked models for testing a couple of infinite recursion
+# cases.
+class X(models.Model):
+ y = models.ForeignKey('Y')
+
+class Y(models.Model):
+ x1 = models.ForeignKey(X, related_name='y1')
+
+# Some models with a cycle in the default ordering. This would be bad if we
+# didn't catch the infinite loop.
+class LoopX(models.Model):
+ y = models.ForeignKey('LoopY')
+
+ class Meta:
+ ordering = ['y']
+
+class LoopY(models.Model):
+ x = models.ForeignKey(LoopX)
+
+ class Meta:
+ ordering = ['x']
+
+class LoopZ(models.Model):
+ z = models.ForeignKey('self')
+
+ class Meta:
+ ordering = ['z']
+
+__test__ = {'API_TESTS':"""
+>>> t1 = Tag(name='t1')
+>>> t1.save()
+>>> t2 = Tag(name='t2', parent=t1)
+>>> t2.save()
+>>> t3 = Tag(name='t3', parent=t1)
+>>> t3.save()
+>>> t4 = Tag(name='t4', parent=t3)
+>>> t4.save()
+>>> t5 = Tag(name='t5', parent=t3)
+>>> t5.save()
+
+>>> n1 = Note(note='n1', misc='foo')
+>>> n1.save()
+>>> n2 = Note(note='n2', misc='bar')
+>>> n2.save()
+>>> n3 = Note(note='n3', misc='foo')
+>>> n3.save()
+
+Create these out of order so that sorting by 'id' will be different to sorting
+by 'info'. Helps detect some problems later.
+>>> e2 = ExtraInfo(info='e2', note=n2)
+>>> e2.save()
+>>> e1 = ExtraInfo(info='e1', note=n1)
+>>> e1.save()
+
+>>> a1 = Author(name='a1', num=1001, extra=e1)
+>>> a1.save()
+>>> a2 = Author(name='a2', num=2002, extra=e1)
+>>> a2.save()
+>>> a3 = Author(name='a3', num=3003, extra=e2)
+>>> a3.save()
+>>> a4 = Author(name='a4', num=4004, extra=e2)
+>>> a4.save()
+
+>>> time1 = datetime.datetime(2007, 12, 19, 22, 25, 0)
+>>> time2 = datetime.datetime(2007, 12, 19, 21, 0, 0)
+>>> time3 = datetime.datetime(2007, 12, 20, 22, 25, 0)
+>>> time4 = datetime.datetime(2007, 12, 20, 21, 0, 0)
+>>> i1 = Item(name='one', created=time1, creator=a1, note=n3)
+>>> i1.save()
+>>> i1.tags = [t1, t2]
+>>> i2 = Item(name='two', created=time2, creator=a2, note=n2)
+>>> i2.save()
+>>> i2.tags = [t1, t3]
+>>> i3 = Item(name='three', created=time3, creator=a2, note=n3)
+>>> i3.save()
+>>> i4 = Item(name='four', created=time4, creator=a4, note=n3)
+>>> i4.save()
+>>> i4.tags = [t4]
+
+>>> r1 = Report(name='r1', creator=a1)
+>>> r1.save()
+>>> r2 = Report(name='r2', creator=a3)
+>>> r2.save()
+
+Ordering by 'rank' gives us rank2, rank1, rank3. Ordering by the Meta.ordering
+will be rank3, rank2, rank1.
+>>> rank1 = Ranking(rank=2, author=a2)
+>>> rank1.save()
+>>> rank2 = Ranking(rank=1, author=a3)
+>>> rank2.save()
+>>> rank3 = Ranking(rank=3, author=a1)
+>>> rank3.save()
+
+>>> c1 = Cover(title="first", item=i4)
+>>> c1.save()
+>>> c2 = Cover(title="second", item=i2)
+>>> c2.save()
+
+>>> n1 = Number(num=4)
+>>> n1.save()
+>>> n2 = Number(num=8)
+>>> n2.save()
+>>> n3 = Number(num=12)
+>>> n3.save()
+
+Bug #1050
+>>> Item.objects.filter(tags__isnull=True)
+[<Item: three>]
+>>> Item.objects.filter(tags__id__isnull=True)
+[<Item: three>]
+
+Bug #1801
+>>> Author.objects.filter(item=i2)
+[<Author: a2>]
+>>> Author.objects.filter(item=i3)
+[<Author: a2>]
+>>> Author.objects.filter(item=i2) & Author.objects.filter(item=i3)
+[<Author: a2>]
+
+Bug #2306
+Checking that no join types are "left outer" joins.
+>>> query = Item.objects.filter(tags=t2).query
+>>> query.LOUTER not in [x[2] for x in query.alias_map.values()]
+True
+
+>>> Item.objects.filter(Q(tags=t1)).order_by('name')
+[<Item: one>, <Item: two>]
+>>> Item.objects.filter(Q(tags=t1)).filter(Q(tags=t2))
+[<Item: one>]
+>>> Item.objects.filter(Q(tags=t1)).filter(Q(creator__name='fred')|Q(tags=t2))
+[<Item: one>]
+
+Each filter call is processed "at once" against a single table, so this is
+different from the previous example as it tries to find tags that are two
+things at once (rather than two tags).
+>>> Item.objects.filter(Q(tags=t1) & Q(tags=t2))
+[]
+>>> Item.objects.filter(Q(tags=t1), Q(creator__name='fred')|Q(tags=t2))
+[]
+
+>>> qs = Author.objects.filter(ranking__rank=2, ranking__id=rank1.id)
+>>> list(qs)
+[<Author: a2>]
+>>> qs.query.count_active_tables()
+2
+>>> qs = Author.objects.filter(ranking__rank=2).filter(ranking__id=rank1.id)
+>>> qs.query.count_active_tables()
+3
+
+Bug #4464
+>>> Item.objects.filter(tags=t1).filter(tags=t2)
+[<Item: one>]
+>>> Item.objects.filter(tags__in=[t1, t2]).distinct().order_by('name')
+[<Item: one>, <Item: two>]
+>>> Item.objects.filter(tags__in=[t1, t2]).filter(tags=t3)
+[<Item: two>]
+
+Bug #2080, #3592
+>>> Author.objects.filter(item__name='one') | Author.objects.filter(name='a3')
+[<Author: a1>, <Author: a3>]
+>>> Author.objects.filter(Q(item__name='one') | Q(name='a3'))
+[<Author: a1>, <Author: a3>]
+>>> Author.objects.filter(Q(name='a3') | Q(item__name='one'))
+[<Author: a1>, <Author: a3>]
+>>> Author.objects.filter(Q(item__name='three') | Q(report__name='r3'))
+[<Author: a2>]
+
+Bug #4289
+A slight variation on the above theme: restricting the choices by the lookup
+constraints.
+>>> Number.objects.filter(num__lt=4)
+[]
+>>> Number.objects.filter(num__gt=8, num__lt=12)
+[]
+>>> Number.objects.filter(num__gt=8, num__lt=13)
+[<Number: 12>]
+>>> Number.objects.filter(Q(num__lt=4) | Q(num__gt=8, num__lt=12))
+[]
+>>> Number.objects.filter(Q(num__gt=8, num__lt=12) | Q(num__lt=4))
+[]
+>>> Number.objects.filter(Q(num__gt=8) & Q(num__lt=12) | Q(num__lt=4))
+[]
+>>> Number.objects.filter(Q(num__gt=7) & Q(num__lt=12) | Q(num__lt=4))
+[<Number: 8>]
+
+Bug #6074
+Merging two empty result sets shouldn't leave a queryset with no constraints
+(which would match everything).
+>>> Author.objects.filter(Q(id__in=[]))
+[]
+>>> Author.objects.filter(Q(id__in=[])|Q(id__in=[]))
+[]
+
+Bug #1878, #2939
+>>> Item.objects.values('creator').distinct().count()
+3
+
+# Create something with a duplicate 'name' so that we can test multi-column
+# cases (which require some tricky SQL transformations under the covers).
+>>> xx = Item(name='four', created=time1, creator=a2, note=n1)
+>>> xx.save()
+>>> Item.objects.exclude(name='two').values('creator', 'name').distinct().count()
+4
+>>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name', 'foo').distinct().count()
+4
+>>> Item.objects.exclude(name='two').extra(select={'foo': '%s'}, select_params=(1,)).values('creator', 'name').distinct().count()
+4
+>>> xx.delete()
+
+Bug #2253
+>>> q1 = Item.objects.order_by('name')
+>>> q2 = Item.objects.filter(id=i1.id)
+>>> q1
+[<Item: four>, <Item: one>, <Item: three>, <Item: two>]
+>>> q2
+[<Item: one>]
+>>> (q1 | q2).order_by('name')
+[<Item: four>, <Item: one>, <Item: three>, <Item: two>]
+>>> (q1 & q2).order_by('name')
+[<Item: one>]
+
+# FIXME: This is difficult to fix and very much an edge case, so punt for now.
+# # This is related to the order_by() tests, below, but the old bug exhibited
+# # itself here (q2 was pulling too many tables into the combined query with the
+# # new ordering, but only because we have evaluated q2 already).
+# >>> len((q1 & q2).order_by('name').query.tables)
+# 1
+
+>>> q1 = Item.objects.filter(tags=t1)
+>>> q2 = Item.objects.filter(note=n3, tags=t2)
+>>> q3 = Item.objects.filter(creator=a4)
+>>> ((q1 & q2) | q3).order_by('name')
+[<Item: four>, <Item: one>]
+
+Bugs #4088, #4306
+>>> Report.objects.filter(creator=1001)
+[<Report: r1>]
+>>> Report.objects.filter(creator__num=1001)
+[<Report: r1>]
+>>> Report.objects.filter(creator__id=1001)
+[]
+>>> Report.objects.filter(creator__id=a1.id)
+[<Report: r1>]
+>>> Report.objects.filter(creator__name='a1')
+[<Report: r1>]
+
+Bug #4510
+>>> Author.objects.filter(report__name='r1')
+[<Author: a1>]
+
+Bug #5324, #6704
+>>> Item.objects.filter(tags__name='t4')
+[<Item: four>]
+>>> Item.objects.exclude(tags__name='t4').order_by('name').distinct()
+[<Item: one>, <Item: three>, <Item: two>]
+>>> Item.objects.exclude(tags__name='t4').order_by('name').distinct().reverse()
+[<Item: two>, <Item: three>, <Item: one>]
+>>> Author.objects.exclude(item__name='one').distinct().order_by('name')
+[<Author: a2>, <Author: a3>, <Author: a4>]
+
+
+# Excluding across a m2m relation when there is more than one related object
+# associated was problematic.
+>>> Item.objects.exclude(tags__name='t1').order_by('name')
+[<Item: four>, <Item: three>]
+>>> Item.objects.exclude(tags__name='t1').exclude(tags__name='t4')
+[<Item: three>]
+
+# Excluding from a relation that cannot be NULL should not use outer joins.
+>>> query = Item.objects.exclude(creator__in=[a1, a2]).query
+>>> query.LOUTER not in [x[2] for x in query.alias_map.values()]
+True
+
+Similarly, when one of the joins cannot possibly, ever, involve NULL values (Author -> ExtraInfo, in the following), it should never be promoted to a left outer join. So hte following query should only involve one "left outer" join (Author -> Item is 0-to-many).
+>>> qs = Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3))
+>>> len([x[2] for x in qs.query.alias_map.values() if x[2] == query.LOUTER])
+1
+
+The previous changes shouldn't affect nullable foreign key joins.
+>>> Tag.objects.filter(parent__isnull=True).order_by('name')
+[<Tag: t1>]
+>>> Tag.objects.exclude(parent__isnull=True).order_by('name')
+[<Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>]
+>>> Tag.objects.exclude(Q(parent__name='t1') | Q(parent__isnull=True)).order_by('name')
+[<Tag: t4>, <Tag: t5>]
+>>> Tag.objects.exclude(Q(parent__isnull=True) | Q(parent__name='t1')).order_by('name')
+[<Tag: t4>, <Tag: t5>]
+>>> Tag.objects.exclude(Q(parent__parent__isnull=True)).order_by('name')
+[<Tag: t4>, <Tag: t5>]
+>>> Tag.objects.filter(~Q(parent__parent__isnull=True)).order_by('name')
+[<Tag: t4>, <Tag: t5>]
+
+Bug #2091
+>>> t = Tag.objects.get(name='t4')
+>>> Item.objects.filter(tags__in=[t])
+[<Item: four>]
+
+Combining querysets built on different models should behave in a well-defined
+fashion. We raise an error.
+>>> Author.objects.all() & Tag.objects.all()
+Traceback (most recent call last):
+...
+AssertionError: Cannot combine queries on two different base models.
+>>> Author.objects.all() | Tag.objects.all()
+Traceback (most recent call last):
+...
+AssertionError: Cannot combine queries on two different base models.
+
+Bug #3141
+>>> Author.objects.extra(select={'foo': '1'}).count()
+4
+>>> Author.objects.extra(select={'foo': '%s'}, select_params=(1,)).count()
+4
+
+Bug #2400
+>>> Author.objects.filter(item__isnull=True)
+[<Author: a3>]
+>>> Tag.objects.filter(item__isnull=True)
+[<Tag: t5>]
+
+Bug #2496
+>>> Item.objects.extra(tables=['queries_author']).select_related().order_by('name')[:1]
+[<Item: four>]
+
+Bug #2076
+# Ordering on related tables should be possible, even if the table is not
+# otherwise involved.
+>>> Item.objects.order_by('note__note', 'name')
+[<Item: two>, <Item: four>, <Item: one>, <Item: three>]
+
+# Ordering on a related field should use the remote model's default ordering as
+# a final step.
+>>> Author.objects.order_by('extra', '-name')
+[<Author: a2>, <Author: a1>, <Author: a4>, <Author: a3>]
+
+# Using remote model default ordering can span multiple models (in this case,
+# Cover is ordered by Item's default, which uses Note's default).
+>>> Cover.objects.all()
+[<Cover: first>, <Cover: second>]
+
+# If you're not careful, it's possible to introduce infinite loops via default
+# ordering on foreign keys in a cycle. We detect that.
+>>> LoopX.objects.all()
+Traceback (most recent call last):
+...
+FieldError: Infinite loop caused by ordering.
+
+>>> LoopZ.objects.all()
+Traceback (most recent call last):
+...
+FieldError: Infinite loop caused by ordering.
+
+# ... but you can still order in a non-recursive fashion amongst linked fields
+# (the previous test failed because the default ordering was recursive).
+>>> LoopX.objects.all().order_by('y__x__y__x__id')
+[]
+
+# If the remote model does not have a default ordering, we order by its 'id'
+# field.
+>>> Item.objects.order_by('creator', 'name')
+[<Item: one>, <Item: three>, <Item: two>, <Item: four>]
+
+# Cross model ordering is possible in Meta, too.
+>>> Ranking.objects.all()
+[<Ranking: 3: a1>, <Ranking: 2: a2>, <Ranking: 1: a3>]
+>>> Ranking.objects.all().order_by('rank')
+[<Ranking: 1: a3>, <Ranking: 2: a2>, <Ranking: 3: a1>]
+
+# Ordering by a many-valued attribute (e.g. a many-to-many or reverse
+# ForeignKey) is legal, but the results might not make sense. That isn't
+# Django's problem. Garbage in, garbage out.
+>>> Item.objects.all().order_by('tags', 'id')
+[<Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>]
+
+# If we replace the default ordering, Django adjusts the required tables
+# automatically. Item normally requires a join with Note to do the default
+# ordering, but that isn't needed here.
+>>> qs = Item.objects.order_by('name')
+>>> qs
+[<Item: four>, <Item: one>, <Item: three>, <Item: two>]
+>>> len(qs.query.tables)
+1
+
+# Ordering of extra() pieces is possible, too and you can mix extra fields and
+# model fields in the ordering.
+>>> Ranking.objects.extra(tables=['django_site'], order_by=['-django_site.id', 'rank'])
+[<Ranking: 1: a3>, <Ranking: 2: a2>, <Ranking: 3: a1>]
+
+>>> qs = Ranking.objects.extra(select={'good': 'case when rank > 2 then 1 else 0 end'})
+>>> [o.good for o in qs.extra(order_by=('-good',))] == [True, False, False]
+True
+>>> qs.extra(order_by=('-good', 'id'))
+[<Ranking: 3: a1>, <Ranking: 2: a2>, <Ranking: 1: a3>]
+
+# Despite having some extra aliases in the query, we can still omit them in a
+# values() query.
+>>> qs.values('id', 'rank').order_by('id')
+[{'id': 1, 'rank': 2}, {'id': 2, 'rank': 1}, {'id': 3, 'rank': 3}]
+
+Bugs #2874, #3002
+>>> qs = Item.objects.select_related().order_by('note__note', 'name')
+>>> list(qs)
+[<Item: two>, <Item: four>, <Item: one>, <Item: three>]
+
+# This is also a good select_related() test because there are multiple Note
+# entries in the SQL. The two Note items should be different.
+>>> qs[0].note, qs[0].creator.extra.note
+(<Note: n2>, <Note: n1>)
+
+Bug #3037
+>>> Item.objects.filter(Q(creator__name='a3', name='two')|Q(creator__name='a4', name='four'))
+[<Item: four>]
+
+Bug #5321, #7070
+
+Ordering columns must be included in the output columns. Note that this means
+results that might otherwise be distinct are not (if there are multiple values
+in the ordering cols), as in this example. This isn't a bug; it's a warning to
+be careful with the selection of ordering columns.
+
+>>> Note.objects.values('misc').distinct().order_by('note', '-misc')
+[{'misc': u'foo'}, {'misc': u'bar'}, {'misc': u'foo'}]
+
+Bug #4358
+If you don't pass any fields to values(), relation fields are returned as
+"foo_id" keys, not "foo". For consistency, you should be able to pass "foo_id"
+in the fields list and have it work, too. We actually allow both "foo" and
+"foo_id".
+
+# The *_id version is returned by default.
+>>> 'note_id' in ExtraInfo.objects.values()[0]
+True
+
+# You can also pass it in explicitly.
+>>> ExtraInfo.objects.values('note_id')
+[{'note_id': 1}, {'note_id': 2}]
+
+# ...or use the field name.
+>>> ExtraInfo.objects.values('note')
+[{'note': 1}, {'note': 2}]
+
+Bug #5261
+>>> Note.objects.exclude(Q())
+[<Note: n1>, <Note: n2>, <Note: n3>]
+
+Bug #3045, #3288
+Once upon a time, select_related() with circular relations would loop
+infinitely if you forgot to specify "depth". Now we set an arbitrary default
+upper bound.
+>>> X.objects.all()
+[]
+>>> X.objects.select_related()
+[]
+
+Bug #3739
+The all() method on querysets returns a copy of the queryset.
+>>> q1 = Item.objects.order_by('name')
+>>> id(q1) == id(q1.all())
+False
+
+Bug #2902
+Parameters can be given to extra_select, *if* you use a SortedDict.
+
+(First we need to know which order the keys fall in "naturally" on your system,
+so we can put things in the wrong way around from normal. A normal dict would
+thus fail.)
+>>> from django.utils.datastructures import SortedDict
+>>> s = [('a', '%s'), ('b', '%s')]
+>>> params = ['one', 'two']
+>>> if {'a': 1, 'b': 2}.keys() == ['a', 'b']:
+... s.reverse()
+... params.reverse()
+
+# This slightly odd comparison works aorund the fact that PostgreSQL will
+# return 'one' and 'two' as strings, not Unicode objects. It's a side-effect of
+# using constants here and not a real concern.
+>>> d = Item.objects.extra(select=SortedDict(s), select_params=params).values('a', 'b')[0]
+>>> d == {'a': u'one', 'b': u'two'}
+True
+
+# Order by the number of tags attached to an item.
+>>> l = Item.objects.extra(select={'count': 'select count(*) from queries_item_tags where queries_item_tags.item_id = queries_item.id'}).order_by('-count')
+>>> [o.count for o in l]
+[2, 2, 1, 0]
+
+Bug #6154
+Multiple filter statements are joined using "AND" all the time.
+
+>>> Author.objects.filter(id=a1.id).filter(Q(extra__note=n1)|Q(item__note=n3))
+[<Author: a1>]
+>>> Author.objects.filter(Q(extra__note=n1)|Q(item__note=n3)).filter(id=a1.id)
+[<Author: a1>]
+
+Bug #6981
+>>> Tag.objects.select_related('parent').order_by('name')
+[<Tag: t1>, <Tag: t2>, <Tag: t3>, <Tag: t4>, <Tag: t5>]
+
+Bug #6180, #6203 -- dates with limits and/or counts
+>>> Item.objects.count()
+4
+>>> Item.objects.dates('created', 'month').count()
+1
+>>> Item.objects.dates('created', 'day').count()
+2
+>>> len(Item.objects.dates('created', 'day'))
+2
+>>> Item.objects.dates('created', 'day')[0]
+datetime.datetime(2007, 12, 19, 0, 0)
+
+Bug #7087 -- dates with extra select columns
+>>> Item.objects.dates('created', 'day').extra(select={'a': 1})
+[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
+
+Test that parallel iterators work.
+
+>>> qs = Tag.objects.all()
+>>> i1, i2 = iter(qs), iter(qs)
+>>> i1.next(), i1.next()
+(<Tag: t1>, <Tag: t2>)
+>>> i2.next(), i2.next(), i2.next()
+(<Tag: t1>, <Tag: t2>, <Tag: t3>)
+>>> i1.next()
+<Tag: t3>
+
+>>> qs = X.objects.all()
+>>> bool(qs)
+False
+>>> bool(qs)
+False
+
+We can do slicing beyond what is currently in the result cache, too.
+
+## FIXME!! This next test causes really weird PostgreSQL behaviour, but it's
+## only apparent much later when the full test suite runs. I don't understand
+## what's going on here yet.
+##
+## # We need to mess with the implemenation internals a bit here to decrease the
+## # cache fill size so that we don't read all the results at once.
+## >>> from django.db.models import query
+## >>> query.ITER_CHUNK_SIZE = 2
+## >>> qs = Tag.objects.all()
+##
+## # Fill the cache with the first chunk.
+## >>> bool(qs)
+## True
+## >>> len(qs._result_cache)
+## 2
+##
+## # Query beyond the end of the cache and check that it is filled out as required.
+## >>> qs[4]
+## <Tag: t5>
+## >>> len(qs._result_cache)
+## 5
+##
+## # But querying beyond the end of the result set will fail.
+## >>> qs[100]
+## Traceback (most recent call last):
+## ...
+## IndexError: ...
+
+Bug #7045 -- extra tables used to crash SQL construction on the second use.
+>>> qs = Ranking.objects.extra(tables=['django_site'])
+>>> s = qs.query.as_sql()
+>>> s = qs.query.as_sql() # test passes if this doesn't raise an exception.
+
+"""}
+
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index e9df508822..593e61ecc7 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -77,7 +77,7 @@ class USStateData(models.Model):
class XMLData(models.Model):
data = models.XMLField(null=True)
-
+
class Tag(models.Model):
"""A tag on an item."""
data = models.SlugField()
@@ -93,40 +93,39 @@ class GenericData(models.Model):
data = models.CharField(max_length=30)
tags = generic.GenericRelation(Tag)
-
+
# The following test classes are all for validation
# of related objects; in particular, forward, backward,
# and self references.
-
+
class Anchor(models.Model):
- """This is a model that can be used as
+ """This is a model that can be used as
something for other models to point at"""
-
+
data = models.CharField(max_length=30)
class UniqueAnchor(models.Model):
- """This is a model that can be used as
+ """This is a model that can be used as
something for other models to point at"""
data = models.CharField(unique=True, max_length=30)
-
+
class FKData(models.Model):
data = models.ForeignKey(Anchor, null=True)
-
+
class M2MData(models.Model):
data = models.ManyToManyField(Anchor, null=True)
-
+
class O2OData(models.Model):
- # One to one field can't be null, since it is a PK.
- data = models.OneToOneField(Anchor)
+ # One to one field can't be null here, since it is a PK.
+ data = models.OneToOneField(Anchor, primary_key=True)
class FKSelfData(models.Model):
data = models.ForeignKey('self', null=True)
-
+
class M2MSelfData(models.Model):
data = models.ManyToManyField('self', null=True, symmetrical=False)
-
class FKDataToField(models.Model):
data = models.ForeignKey(UniqueAnchor, null=True, to_field='data')
@@ -142,7 +141,7 @@ class FKDataToO2O(models.Model):
class BooleanPKData(models.Model):
data = models.BooleanField(primary_key=True)
-
+
class CharPKData(models.Model):
data = models.CharField(max_length=30, primary_key=True)
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index 24111308d7..db34f8cf77 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -31,13 +31,13 @@ except ImportError:
def data_create(pk, klass, data):
instance = klass(id=pk)
instance.data = data
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
return instance
def generic_create(pk, klass, data):
instance = klass(id=pk)
instance.data = data[0]
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
for tag in data[1:]:
instance.tags.create(data=tag)
return instance
@@ -45,25 +45,25 @@ def generic_create(pk, klass, data):
def fk_create(pk, klass, data):
instance = klass(id=pk)
setattr(instance, 'data_id', data)
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
return instance
def m2m_create(pk, klass, data):
instance = klass(id=pk)
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
instance.data = data
return instance
def o2o_create(pk, klass, data):
instance = klass()
instance.data_id = data
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
return instance
def pk_create(pk, klass, data):
instance = klass()
instance.data = data
- models.Model.save(instance, raw=True)
+ models.Model.save_base(instance, raw=True)
return instance
# A set of functions that can be used to compare
@@ -309,7 +309,7 @@ def fieldsTest(format, self):
management.call_command('flush', verbosity=0, interactive=False)
obj = ComplexModel(field1='first',field2='second',field3='third')
- obj.save(raw=True)
+ obj.save_base(raw=True)
# Serialize then deserialize the test database
serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3'))
@@ -325,7 +325,7 @@ def streamTest(format, self):
management.call_command('flush', verbosity=0, interactive=False)
obj = ComplexModel(field1='first',field2='second',field3='third')
- obj.save(raw=True)
+ obj.save_base(raw=True)
# Serialize the test database to a stream
stream = StringIO()