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