summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/basic/models.py5
-rw-r--r--tests/modeltests/many_to_many/models.py8
-rw-r--r--tests/modeltests/many_to_one/models.py6
-rw-r--r--tests/modeltests/model_inheritance/models.py5
-rw-r--r--tests/modeltests/one_to_one/models.py5
-rw-r--r--tests/modeltests/or_lookups/models.py5
-rw-r--r--tests/modeltests/test_client/fixtures/testdata.json18
-rw-r--r--tests/modeltests/update/models.py7
-rw-r--r--tests/regressiontests/defaultfilters/tests.py18
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn1
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml7
-rw-r--r--tests/regressiontests/fixtures_regress/models.py23
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py46
-rw-r--r--tests/regressiontests/model_inheritance_regress/__init__.py0
-rw-r--r--tests/regressiontests/model_inheritance_regress/models.py120
-rw-r--r--tests/regressiontests/null_fk/__init__.py0
-rw-r--r--tests/regressiontests/null_fk/models.py55
-rw-r--r--tests/regressiontests/one_to_one_regress/models.py38
-rw-r--r--tests/regressiontests/queries/models.py11
-rw-r--r--tests/regressiontests/serializers_regress/models.py20
-rw-r--r--tests/regressiontests/serializers_regress/tests.py48
-rw-r--r--tests/regressiontests/test_client_regress/fixtures/testdata.json36
-rw-r--r--tests/regressiontests/test_client_regress/models.py29
-rw-r--r--tests/regressiontests/test_client_regress/urls.py1
-rw-r--r--tests/regressiontests/test_client_regress/views.py14
-rwxr-xr-xtests/runtests.py9
26 files changed, 506 insertions, 29 deletions
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index d7c27cb15b..c3ad38d661 100644
--- a/tests/modeltests/basic/models.py
+++ b/tests/modeltests/basic/models.py
@@ -401,8 +401,9 @@ True
# The 'select' argument to extra() supports names with dashes in them, as long
# as you use values().
->>> Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value')
-[{'headline': u'Article 11', 'dashed-value': 1}, {'headline': u'Article 12', 'dashed-value': 1}]
+>>> dicts = Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value')
+>>> [sorted(d.items()) for d in dicts]
+[[('dashed-value', 1), ('headline', u'Article 11')], [('dashed-value', 1), ('headline', u'Article 12')]]
# If you use 'select' with extra() and names containing dashes on a query
# that's *not* a values() query, those extra 'select' values will silently be
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index e09fd825f8..c2ab2897b6 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -39,6 +39,14 @@ __test__ = {'API_TESTS':"""
# Create an Article.
>>> a1 = Article(id=None, headline='Django lets you build Web apps easily')
+
+# You can't associate it with a Publication until it's been saved.
+>>> a1.publications.add(p1)
+Traceback (most recent call last):
+...
+ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used.
+
+# Save it!
>>> a1.save()
# Associate the Article with a Publication.
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index 53ad4466bb..dfb17b8344 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -175,6 +175,12 @@ False
>>> Article.objects.filter(reporter__in=[r,r2]).distinct()
[<Article: John's second story>, <Article: Paul's story>, <Article: This is a test>]
+# You can also use a queryset instead of a literal list of instances.
+# The queryset must be reduced to a list of values using values(),
+# then converted into a query
+>>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John').values('pk').query).distinct()
+[<Article: John's second story>, <Article: This is a test>]
+
# You need two underscores between "reporter" and "id" -- not one.
>>> Article.objects.filter(reporter_id__exact=1)
Traceback (most recent call last):
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index b1a751f5e8..7c737b6bd1 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -147,8 +147,13 @@ Test constructor for Restaurant.
>>> 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()
+>>> ItalianRestaurant.objects.filter(address='1234 W. Ash')
+[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
+
>>> ir.address = '1234 W. Elm'
>>> ir.save()
+>>> ItalianRestaurant.objects.filter(address='1234 W. Elm')
+[<ItalianRestaurant: Ristorante Miron the italian restaurant>]
# Make sure Restaurant and ItalianRestaurant have the right fields in the right
# order.
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
index 800ccddac2..6fa4dd8c18 100644
--- a/tests/modeltests/one_to_one/models.py
+++ b/tests/modeltests/one_to_one/models.py
@@ -80,11 +80,8 @@ 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
-# reload 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.
>>> p1.restaurant = r
->>> r.save()
>>> p1.restaurant
<Restaurant: Demon Dogs the restaurant>
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index c779e19e37..22bada07b1 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -110,8 +110,9 @@ __test__ = {'API_TESTS':"""
>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count()
3
->>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
-[{'headline': u'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
+>>> dicts = list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values())
+>>> [sorted(d.items()) for d in dicts]
+[[('headline', u'Hello and goodbye'), ('id', 3), ('pub_date', datetime.datetime(2005, 11, 29, 0, 0))]]
>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
{1: <Article: Hello>}
diff --git a/tests/modeltests/test_client/fixtures/testdata.json b/tests/modeltests/test_client/fixtures/testdata.json
index e9d3ebe9a8..0dcf625939 100644
--- a/tests/modeltests/test_client/fixtures/testdata.json
+++ b/tests/modeltests/test_client/fixtures/testdata.json
@@ -34,5 +34,23 @@
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
+ },
+ {
+ "pk": "3",
+ "model": "auth.user",
+ "fields": {
+ "username": "staff",
+ "first_name": "Staff",
+ "last_name": "Member",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": true,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
}
] \ No newline at end of file
diff --git a/tests/modeltests/update/models.py b/tests/modeltests/update/models.py
index 3b0f83389f..8a35b61a7c 100644
--- a/tests/modeltests/update/models.py
+++ b/tests/modeltests/update/models.py
@@ -63,5 +63,12 @@ a manager method.
>>> DataPoint.objects.values('value').distinct()
[{'value': u'thing'}]
+We do not support update on already sliced query sets.
+
+>>> DataPoint.objects.all()[:2].update(another_value='another thing')
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot update a query once a slice has been taken.
+
"""
}
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index 668ecb9d5a..4a8b68a897 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -226,15 +226,17 @@ u'some <b>html</b> with alert("You smell") disallowed tags'
>>> striptags(u'some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
u'some html with alert("You smell") disallowed tags'
->>> dictsort([{'age': 23, 'name': 'Barbara-Ann'},
-... {'age': 63, 'name': 'Ra Ra Rasputin'},
-... {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 18, 'name': 'Jonny B Goode'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 63, 'name': 'Ra Ra Rasputin'}]
+>>> sorted_dicts = dictsort([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+>>> [sorted(dict.items()) for dict in sorted_dicts]
+[[('age', 18), ('name', 'Jonny B Goode')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 63), ('name', 'Ra Ra Rasputin')]]
->>> dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
-... {'age': 63, 'name': 'Ra Ra Rasputin'},
-... {'name': 'Jonny B Goode', 'age': 18}], 'age')
-[{'age': 63, 'name': 'Ra Ra Rasputin'}, {'age': 23, 'name': 'Barbara-Ann'}, {'age': 18, 'name': 'Jonny B Goode'}]
+>>> sorted_dicts = dictsortreversed([{'age': 23, 'name': 'Barbara-Ann'},
+... {'age': 63, 'name': 'Ra Ra Rasputin'},
+... {'name': 'Jonny B Goode', 'age': 18}], 'age')
+>>> [sorted(dict.items()) for dict in sorted_dicts]
+[[('age', 63), ('name', 'Ra Ra Rasputin')], [('age', 23), ('name', 'Barbara-Ann')], [('age', 18), ('name', 'Jonny B Goode')]]
>>> first([0,1,2])
0
diff --git a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn b/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn
new file mode 100644
index 0000000000..a8b0a0c56c
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/bad_fixture1.unkn
@@ -0,0 +1 @@
+This data shouldn't load, as it's of an unknown file format. \ No newline at end of file
diff --git a/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml b/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml
new file mode 100644
index 0000000000..87b809fbc6
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/bad_fixture2.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<django-objcts version="1.0">
+ <objct pk="2" model="fixtures.article">
+ <field type="CharField" name="headline">Poker on TV is great!</field>
+ <field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field>
+ </objct>
+</django-objcts> \ No newline at end of file
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
index 144debe05a..59fc167d50 100644
--- a/tests/regressiontests/fixtures_regress/models.py
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -71,4 +71,27 @@ __test__ = {'API_TESTS':"""
>>> Absolute.load_count
1
+###############################################
+# Test for ticket #4371 -- fixture loading fails silently in testcases
+# Validate that error conditions are caught correctly
+
+# redirect stderr for the next few tests...
+>>> import sys
+>>> savestderr = sys.stderr
+>>> sys.stderr = sys.stdout
+
+# Loading data of an unknown format should fail
+>>> management.call_command('loaddata', 'bad_fixture1.unkn', verbosity=0)
+Problem installing fixture 'bad_fixture1': unkn is not a known serialization format.
+
+# Loading a fixture file with invalid data using explicit filename
+>>> management.call_command('loaddata', 'bad_fixture2.xml', verbosity=0)
+No fixture data found for 'bad_fixture2'. (File format may be invalid.)
+
+# Loading a fixture file with invalid data without file extension
+>>> management.call_command('loaddata', 'bad_fixture2', verbosity=0)
+No fixture data found for 'bad_fixture2'. (File format may be invalid.)
+
+>>> sys.stderr = savestderr
+
"""}
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 57bbcd8489..4e49df1555 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -1,3 +1,7 @@
+"""
+Regression tests for a few FK bugs: #1578, #6886
+"""
+
from django.db import models
# If ticket #1578 ever slips back in, these models will not be able to be
@@ -25,10 +29,48 @@ class Child(models.Model):
__test__ = {'API_TESTS':"""
->>> Third.AddManipulator().save(dict(id='3', name='An example', another=None))
+>>> Third.objects.create(id='3', name='An example')
<Third: Third object>
>>> parent = Parent(name = 'fred')
>>> parent.save()
->>> Child.AddManipulator().save(dict(name='bam-bam', parent=parent.id))
+>>> Child.objects.create(name='bam-bam', parent=parent)
<Child: Child object>
+
+#
+# Tests of ForeignKey assignment and the related-object cache (see #6886)
+#
+>>> p = Parent.objects.create(name="Parent")
+>>> c = Child.objects.create(name="Child", parent=p)
+
+# Look up the object again so that we get a "fresh" object
+>>> c = Child.objects.get(name="Child")
+>>> p = c.parent
+
+# Accessing the related object again returns the exactly same object
+>>> c.parent is p
+True
+
+# But if we kill the cache, we get a new object
+>>> del c._parent_cache
+>>> c.parent is p
+False
+
+# Assigning a new object results in that object getting cached immediately
+>>> p2 = Parent.objects.create(name="Parent 2")
+>>> c.parent = p2
+>>> c.parent is p2
+True
+
+# Assigning None fails: Child.parent is null=False
+>>> c.parent = None
+Traceback (most recent call last):
+ ...
+ValueError: Cannot assign None: "Child.parent" does not allow null values.
+
+# You also can't assign an object of the wrong type here
+>>> c.parent = First(id=1, second=1)
+Traceback (most recent call last):
+ ...
+ValueError: Cannot assign "<First: First object>": "Child.parent" must be a "Parent" instance.
+
"""}
diff --git a/tests/regressiontests/model_inheritance_regress/__init__.py b/tests/regressiontests/model_inheritance_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/model_inheritance_regress/__init__.py
diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
new file mode 100644
index 0000000000..8801715a0c
--- /dev/null
+++ b/tests/regressiontests/model_inheritance_regress/models.py
@@ -0,0 +1,120 @@
+"""
+Regression tests for Model inheritance behaviour.
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+ address = models.CharField(max_length=80)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __unicode__(self):
+ return u"%s the place" % self.name
+
+class Restaurant(Place):
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the restaurant" % self.name
+
+class ItalianRestaurant(Restaurant):
+ serves_gnocchi = models.BooleanField()
+
+ def __unicode__(self):
+ return u"%s the italian restaurant" % 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)
+ capacity = models.IntegerField()
+
+ def __unicode__(self):
+ return u"%s the parking lot" % self.name
+
+__test__ = {'API_TESTS':"""
+# Regression for #7350, #7202
+# Check that when you create a Parent object with a specific reference to an existent
+# child instance, saving the Parent doesn't duplicate the child.
+# This behaviour is only activated during a raw save - it is mostly relevant to
+# deserialization, but any sort of CORBA style 'narrow()' API would require a
+# similar approach.
+
+# Create a child-parent-grandparent chain
+>>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton')
+>>> place1.save_base(raw=True)
+>>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False)
+>>> restaurant.save_base(raw=True)
+>>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True)
+>>> italian_restaurant.save_base(raw=True)
+
+# Create a child-parent chain with an explicit parent link
+>>> place2 = Place(name='Main St', address='111 Main St')
+>>> place2.save_base(raw=True)
+>>> park = ParkingLot(parent=place2, capacity=100)
+>>> park.save_base(raw=True)
+
+# Check that no extra parent objects have been created.
+>>> Place.objects.all()
+[<Place: Guido's House of Pasta the place>, <Place: Main St the place>]
+
+>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
+>>> [sorted(d.items()) for d in dicts]
+[[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]]
+
+>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
+>>> [sorted(d.items()) for d in dicts]
+[[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]]
+
+>>> dicts = ParkingLot.objects.values('name','capacity')
+>>> [sorted(d.items()) for d in dicts]
+[[('capacity', 100), ('name', u'Main St')]]
+
+# You can also update objects when using a raw save.
+>>> place1.name = "Guido's All New House of Pasta"
+>>> place1.save_base(raw=True)
+
+>>> restaurant.serves_hot_dogs = False
+>>> restaurant.save_base(raw=True)
+
+>>> italian_restaurant.serves_gnocchi = False
+>>> italian_restaurant.save_base(raw=True)
+
+>>> place2.name='Derelict lot'
+>>> place2.save_base(raw=True)
+
+>>> park.capacity = 50
+>>> park.save_base(raw=True)
+
+# No extra parent objects after an update, either.
+>>> Place.objects.all()
+[<Place: Derelict lot the place>, <Place: Guido's All New House of Pasta the place>]
+
+>>> dicts = Restaurant.objects.values('name','serves_hot_dogs')
+>>> [sorted(d.items()) for d in dicts]
+[[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]]
+
+>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
+>>> [sorted(d.items()) for d in dicts]
+[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
+
+>>> dicts = ParkingLot.objects.values('name','capacity')
+>>> [sorted(d.items()) for d in dicts]
+[[('capacity', 50), ('name', u'Derelict lot')]]
+
+# If you try to raw_save a parent attribute onto a child object,
+# the attribute will be ignored.
+
+>>> italian_restaurant.name = "Lorenzo's Pasta Hut"
+>>> italian_restaurant.save_base(raw=True)
+
+# Note that the name has not changed
+# - name is an attribute of Place, not ItalianRestaurant
+>>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi')
+>>> [sorted(d.items()) for d in dicts]
+[[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]]
+
+"""}
diff --git a/tests/regressiontests/null_fk/__init__.py b/tests/regressiontests/null_fk/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/null_fk/__init__.py
diff --git a/tests/regressiontests/null_fk/models.py b/tests/regressiontests/null_fk/models.py
new file mode 100644
index 0000000000..1bc266c033
--- /dev/null
+++ b/tests/regressiontests/null_fk/models.py
@@ -0,0 +1,55 @@
+"""
+Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
+
+ * #7369: FK non-null after null relationship on select_related() generates an invalid query
+
+"""
+
+from django.db import models
+
+class SystemInfo(models.Model):
+ system_name = models.CharField(max_length=32)
+
+class Forum(models.Model):
+ system_info = models.ForeignKey(SystemInfo)
+ forum_name = models.CharField(max_length=32)
+
+class Post(models.Model):
+ forum = models.ForeignKey(Forum, null=True)
+ title = models.CharField(max_length=32)
+
+ def __unicode__(self):
+ return self.title
+
+class Comment(models.Model):
+ post = models.ForeignKey(Post, null=True)
+ comment_text = models.CharField(max_length=250)
+
+ def __unicode__(self):
+ return self.comment_text
+
+__test__ = {'API_TESTS':"""
+
+>>> s = SystemInfo.objects.create(system_name='First forum')
+>>> f = Forum.objects.create(system_info=s, forum_name='First forum')
+>>> p = Post.objects.create(forum=f, title='First Post')
+>>> c1 = Comment.objects.create(post=p, comment_text='My first comment')
+>>> c2 = Comment.objects.create(comment_text='My second comment')
+
+# Starting from comment, make sure that a .select_related(...) with a specified
+# set of fields will properly LEFT JOIN multiple levels of NULLs (and the things
+# that come after the NULLs, or else data that should exist won't).
+>>> c = Comment.objects.select_related().get(id=1)
+>>> c.post
+<Post: First Post>
+>>> c = Comment.objects.select_related().get(id=2)
+>>> print c.post
+None
+
+>>> comments = Comment.objects.select_related('post__forum__system_info').all()
+>>> [(c.id, c.post.id) for c in comments]
+[(1, 1), (2, None)]
+>>> [(c.comment_text, c.post.title) for c in comments]
+[(u'My first comment', u'First Post'), (u'My second comment', None)]
+
+"""}
diff --git a/tests/regressiontests/one_to_one_regress/models.py b/tests/regressiontests/one_to_one_regress/models.py
index c68fdfc780..99022882f2 100644
--- a/tests/regressiontests/one_to_one_regress/models.py
+++ b/tests/regressiontests/one_to_one_regress/models.py
@@ -50,4 +50,42 @@ __test__ = {'API_TESTS':"""
<Restaurant: Demon Dogs the restaurant>
>>> p1.bar
<Bar: Demon Dogs the bar>
+
+#
+# Regression test for #6886 (the related-object cache)
+#
+
+# Look up the objects again so that we get "fresh" objects
+>>> p = Place.objects.get(name="Demon Dogs")
+>>> r = p.restaurant
+
+# Accessing the related object again returns the exactly same object
+>>> p.restaurant is r
+True
+
+# But if we kill the cache, we get a new object
+>>> del p._restaurant_cache
+>>> p.restaurant is r
+False
+
+# Reassigning the Restaurant object results in an immediate cache update
+# We can't use a new Restaurant because that'll violate one-to-one, but
+# with a new *instance* the is test below will fail if #6886 regresses.
+>>> r2 = Restaurant.objects.get(pk=r.pk)
+>>> p.restaurant = r2
+>>> p.restaurant is r2
+True
+
+# Assigning None fails: Place.restaurant is null=False
+>>> p.restaurant = None
+Traceback (most recent call last):
+ ...
+ValueError: Cannot assign None: "Place.restaurant" does not allow null values.
+
+# You also can't assign an object of the wrong type here
+>>> p.restaurant = p
+Traceback (most recent call last):
+ ...
+ValueError: Cannot assign "<Place: Demon Dogs the place>": "Place.restaurant" must be a "Restaurant" instance.
+
"""}
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index 5beaf5fb09..aa78d6583a 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -503,8 +503,15 @@ True
# 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}]
+>>> dicts = qs.values('id', 'rank').order_by('id')
+>>> [sorted(d.items()) for d in dicts]
+[[('id', 1), ('rank', 2)], [('id', 2), ('rank', 1)], [('id', 3), ('rank', 3)]]
+
+Bug #7256
+# An empty values() call includes all aliases, including those from an extra()
+>>> dicts = qs.values().order_by('id')
+>>> [sorted(d.items()) for d in dicts]
+[[('author_id', 2), ('good', 0), ('id', 1), ('rank', 2)], [('author_id', 3), ('good', 0), ('id', 2), ('rank', 1)], [('author_id', 1), ('good', 1), ('id', 3), ('rank', 3)]]
Bugs #2874, #3002
>>> qs = Item.objects.select_related().order_by('note__note', 'name')
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index 593e61ecc7..7d3f9d3b1d 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -223,3 +223,23 @@ class ModifyingSaveData(models.Model):
"A save method that modifies the data in the object"
self.data = 666
super(ModifyingSaveData, self).save(raw)
+
+# Tests for serialization of models using inheritance.
+# Regression for #7202, #7350
+class AbstractBaseModel(models.Model):
+ parent_data = models.IntegerField()
+ class Meta:
+ abstract = True
+
+class InheritAbstractModel(AbstractBaseModel):
+ child_data = models.IntegerField()
+
+class BaseModel(models.Model):
+ parent_data = models.IntegerField()
+
+class InheritBaseModel(BaseModel):
+ child_data = models.IntegerField()
+
+class ExplicitInheritBaseModel(BaseModel):
+ parent = models.OneToOneField(BaseModel)
+ child_data = models.IntegerField()
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index db34f8cf77..9bc5eec1eb 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -32,7 +32,7 @@ def data_create(pk, klass, data):
instance = klass(id=pk)
instance.data = data
models.Model.save_base(instance, raw=True)
- return instance
+ return [instance]
def generic_create(pk, klass, data):
instance = klass(id=pk)
@@ -40,32 +40,45 @@ def generic_create(pk, klass, data):
models.Model.save_base(instance, raw=True)
for tag in data[1:]:
instance.tags.create(data=tag)
- return instance
+ return [instance]
def fk_create(pk, klass, data):
instance = klass(id=pk)
setattr(instance, 'data_id', data)
models.Model.save_base(instance, raw=True)
- return instance
+ return [instance]
def m2m_create(pk, klass, data):
instance = klass(id=pk)
models.Model.save_base(instance, raw=True)
instance.data = data
- return instance
+ return [instance]
def o2o_create(pk, klass, data):
instance = klass()
instance.data_id = data
models.Model.save_base(instance, raw=True)
- return instance
+ return [instance]
def pk_create(pk, klass, data):
instance = klass()
instance.data = data
models.Model.save_base(instance, raw=True)
- return instance
+ return [instance]
+def inherited_create(pk, klass, data):
+ instance = klass(id=pk,**data)
+ # This isn't a raw save because:
+ # 1) we're testing inheritance, not field behaviour, so none
+ # of the field values need to be protected.
+ # 2) saving the child class and having the parent created
+ # automatically is easier than manually creating both.
+ models.Model.save(instance)
+ created = [instance]
+ for klass,field in instance._meta.parents.items():
+ created.append(klass.objects.get(id=pk))
+ return created
+
# A set of functions that can be used to compare
# test data objects of various kinds
def data_compare(testcase, pk, klass, data):
@@ -94,6 +107,11 @@ def pk_compare(testcase, pk, klass, data):
instance = klass.objects.get(data=data)
testcase.assertEqual(data, instance.data)
+def inherited_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ for key,value in data.items():
+ testcase.assertEqual(value, getattr(instance,key))
+
# Define some data types. Each data type is
# actually a pair of functions; one to create
# and one to compare objects of that type
@@ -103,6 +121,7 @@ fk_obj = (fk_create, fk_compare)
m2m_obj = (m2m_create, m2m_compare)
o2o_obj = (o2o_create, o2o_compare)
pk_obj = (pk_create, pk_compare)
+inherited_obj = (inherited_create, inherited_compare)
test_data = [
# Format: (data type, PK value, Model Class, data)
@@ -255,6 +274,10 @@ The end."""),
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
(data_obj, 810, ModifyingSaveData, 42),
+
+ (inherited_obj, 900, InheritAbstractModel, {'child_data':37,'parent_data':42}),
+ (inherited_obj, 910, ExplicitInheritBaseModel, {'child_data':37,'parent_data':42}),
+ (inherited_obj, 920, InheritBaseModel, {'child_data':37,'parent_data':42}),
]
# Because Oracle treats the empty string as NULL, Oracle is expected to fail
@@ -277,13 +300,19 @@ def serializerTest(format, self):
# Create all the objects defined in the test data
objects = []
+ instance_count = {}
transaction.enter_transaction_management()
transaction.managed(True)
for (func, pk, klass, datum) in test_data:
- objects.append(func[0](pk, klass, datum))
+ objects.extend(func[0](pk, klass, datum))
+ instance_count[klass] = 0
transaction.commit()
transaction.leave_transaction_management()
+ # Get a count of the number of objects created for each class
+ for klass in instance_count:
+ instance_count[klass] = klass.objects.count()
+
# Add the generic tagged objects to the object list
objects.extend(Tag.objects.all())
@@ -304,6 +333,11 @@ def serializerTest(format, self):
for (func, pk, klass, datum) in test_data:
func[1](self, pk, klass, datum)
+ # Assert that the number of objects deserialized is the
+ # same as the number that was serialized.
+ for klass, count in instance_count.items():
+ self.assertEquals(count, klass.objects.count())
+
def fieldsTest(format, self):
# Clear the database first
management.call_command('flush', verbosity=0, interactive=False)
diff --git a/tests/regressiontests/test_client_regress/fixtures/testdata.json b/tests/regressiontests/test_client_regress/fixtures/testdata.json
index 5c9e415240..0dcf625939 100644
--- a/tests/regressiontests/test_client_regress/fixtures/testdata.json
+++ b/tests/regressiontests/test_client_regress/fixtures/testdata.json
@@ -16,5 +16,41 @@
"email": "testclient@example.com",
"date_joined": "2006-12-17 07:03:31"
}
+ },
+ {
+ "pk": "2",
+ "model": "auth.user",
+ "fields": {
+ "username": "inactive",
+ "first_name": "Inactive",
+ "last_name": "User",
+ "is_active": false,
+ "is_superuser": false,
+ "is_staff": false,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
+ },
+ {
+ "pk": "3",
+ "model": "auth.user",
+ "fields": {
+ "username": "staff",
+ "first_name": "Staff",
+ "last_name": "Member",
+ "is_active": true,
+ "is_superuser": false,
+ "is_staff": true,
+ "last_login": "2006-12-17 07:03:31",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161",
+ "email": "testclient@example.com",
+ "date_joined": "2006-12-17 07:03:31"
+ }
}
] \ No newline at end of file
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 305ccc9aa3..a204ec3e72 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -4,6 +4,7 @@ Regression tests for the Test Client, especially the customized assertions.
"""
from django.test import Client, TestCase
from django.core.urlresolvers import reverse
+from django.core.exceptions import SuspiciousOperation
import os
class AssertContainsTests(TestCase):
@@ -11,6 +12,7 @@ class AssertContainsTests(TestCase):
"Responses can be inspected for content, including counting repeated substrings"
response = self.client.get('/test_client_regress/no_template_view/')
+ self.assertNotContains(response, 'never')
self.assertContains(response, 'never', 0)
self.assertContains(response, 'once')
self.assertContains(response, 'once', 1)
@@ -18,6 +20,11 @@ class AssertContainsTests(TestCase):
self.assertContains(response, 'twice', 2)
try:
+ self.assertNotContains(response, 'once')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response should not contain 'once'")
+
+ try:
self.assertContains(response, 'never', 1)
except AssertionError, e:
self.assertEquals(str(e), "Found 0 instances of 'never' in response (expected 1)")
@@ -288,4 +295,26 @@ class URLEscapingTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'Hi, Arthur')
+class ExceptionTests(TestCase):
+ fixtures = ['testdata.json']
+
+ def test_exception_cleared(self):
+ "#5836 - A stale user exception isn't re-raised by the test client."
+
+ login = self.client.login(username='testclient',password='password')
+ self.failUnless(login, 'Could not log in')
+ try:
+ response = self.client.get("/test_client_regress/staff_only/")
+ self.fail("General users should not be able to visit this page")
+ except SuspiciousOperation:
+ pass
+ # At this point, an exception has been raised, and should be cleared.
+
+ # This next operation should be successful; if it isn't we have a problem.
+ login = self.client.login(username='staff', password='password')
+ self.failUnless(login, 'Could not log in')
+ try:
+ self.client.get("/test_client_regress/staff_only/")
+ except SuspiciousOperation:
+ self.fail("Staff should be able to visit this page")
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
index d3304caef0..dc26d1260a 100644
--- a/tests/regressiontests/test_client_regress/urls.py
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -4,6 +4,7 @@ import views
urlpatterns = patterns('',
(r'^no_template_view/$', views.no_template_view),
(r'^file_upload/$', views.file_upload_view),
+ (r'^staff_only/$', views.staff_only_view),
(r'^get_view/$', views.get_view),
url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
(r'^login_protected_redirect_view/$', views.login_protected_redirect_view)
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
index f44757dc10..9632c17284 100644
--- a/tests/regressiontests/test_client_regress/views.py
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -1,5 +1,8 @@
+import os
+
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseServerError
+from django.core.exceptions import SuspiciousOperation
def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template"
@@ -13,10 +16,21 @@ def file_upload_view(request):
form_data = request.POST.copy()
form_data.update(request.FILES)
if isinstance(form_data['file_field'], dict) and isinstance(form_data['name'], unicode):
+ # If a file is posted, the dummy client should only post the file name,
+ # not the full path.
+ if os.path.dirname(form_data['file_field']['filename']) != '':
+ return HttpResponseServerError()
return HttpResponse('')
else:
return HttpResponseServerError()
+def staff_only_view(request):
+ "A view that can only be visited by staff. Non staff members get an exception"
+ if request.user.is_staff:
+ return HttpResponse('')
+ else:
+ raise SuspiciousOperation()
+
def get_view(request):
"A simple login protected view"
return HttpResponse("Hello world")
diff --git a/tests/runtests.py b/tests/runtests.py
index 599916ab23..ee7b1a5cda 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -118,7 +118,6 @@ def django_tests(verbosity, interactive, test_labels):
get_apps()
# Load all the test model apps.
- test_models = []
for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name])
try:
@@ -142,7 +141,13 @@ def django_tests(verbosity, interactive, test_labels):
model_label = '.'.join([model_dir, model_name])
if not test_labels or model_name in test_labels:
extra_tests.append(InvalidModelTestCase(model_label))
-
+ try:
+ # Invalid models are not working apps, so we cannot pass them into
+ # the test runner with the other test_labels
+ test_labels.remove(model_name)
+ except ValueError:
+ pass
+
# Run the test suite, including the extra validation tests.
from django.test.simple import run_tests
failures = run_tests(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests)