diff options
Diffstat (limited to 'tests')
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) |
