summaryrefslogtreecommitdiff
path: root/tests/modeltests
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/modeltests
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/modeltests')
-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
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'}]
+
+"""
+}