django/tests/modeltests/lookup/models.py

400 lines
15 KiB
Python
Raw Normal View History

"""
7. The lookup API
This demonstrates features of the database API.
"""
from django.db import models
from django.conf import settings
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
class Meta:
ordering = ('-pub_date', 'headline')
def __unicode__(self):
return self.headline
__test__ = {'API_TESTS': r"""
# Create a couple of Articles.
>>> from datetime import datetime
>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
>>> a1.save()
>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
>>> a2.save()
>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
>>> a3.save()
>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
>>> a4.save()
>>> a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0))
>>> a5.save()
>>> a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0))
>>> a6.save()
>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
>>> a7.save()
"""}
if settings.DATABASE_ENGINE in ('postgresql', 'postgresql_pysycopg2'):
__test__['API_TESTS'] += r"""
# text matching tests for PostgreSQL 8.3
>>> Article.objects.filter(id__iexact='1')
[<Article: Article 1>]
>>> Article.objects.filter(pub_date__startswith='2005')
[<Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
"""
__test__['API_TESTS'] += r"""
# Each QuerySet gets iterator(), which is a generator that "lazily" returns
# results using database-level iteration.
>>> for a in Article.objects.iterator():
... print a.headline
Article 5
Article 6
Article 4
Article 2
Article 3
Article 7
Article 1
# iterator() can be used on any QuerySet.
>>> for a in Article.objects.filter(headline__endswith='4').iterator():
... print a.headline
Article 4
# count() returns the number of objects matching search criteria.
>>> Article.objects.count()
7L
>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count()
3L
>>> Article.objects.filter(headline__startswith='Blah blah').count()
0L
# count() should respect sliced query sets.
>>> articles = Article.objects.all()
>>> articles.count()
7L
>>> articles[:4].count()
4
>>> articles[1:100].count()
6L
>>> articles[10:100].count()
0
# Date and date/time lookups can also be done with strings.
>>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count()
3L
# in_bulk() takes a list of IDs and returns a dictionary mapping IDs
# to objects.
>>> arts = Article.objects.in_bulk([1, 2])
>>> arts[1]
<Article: Article 1>
>>> arts[2]
<Article: Article 2>
>>> Article.objects.in_bulk([3])
{3: <Article: Article 3>}
>>> Article.objects.in_bulk([1000])
{}
>>> Article.objects.in_bulk([])
{}
>>> Article.objects.in_bulk('foo')
Traceback (most recent call last):
...
AssertionError: in_bulk() must be provided with a list of IDs.
>>> Article.objects.in_bulk()
Traceback (most recent call last):
...
TypeError: in_bulk() takes exactly 2 arguments (1 given)
>>> Article.objects.in_bulk(headline__startswith='Blah')
Traceback (most recent call last):
...
TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith'
# values() returns a list of dictionaries instead of object instances -- and
# you can specify which fields you want to retrieve.
>>> Article.objects.values('headline')
[{'headline': u'Article 5'}, {'headline': u'Article 6'}, {'headline': u'Article 4'}, {'headline': u'Article 2'}, {'headline': u'Article 3'}, {'headline': u'Article 7'}, {'headline': u'Article 1'}]
>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id')
[{'id': 2}, {'id': 3}, {'id': 7}]
>>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}]
True
>>> for d in Article.objects.values('id', 'headline'):
... i = d.items()
... i.sort()
... i
[('headline', u'Article 5'), ('id', 5)]
[('headline', u'Article 6'), ('id', 6)]
[('headline', u'Article 4'), ('id', 4)]
[('headline', u'Article 2'), ('id', 2)]
[('headline', u'Article 3'), ('id', 3)]
[('headline', u'Article 7'), ('id', 7)]
[('headline', u'Article 1'), ('id', 1)]
# You can use values() with iterator() for memory savings, because iterator()
# uses database-level iteration.
>>> for d in Article.objects.values('id', 'headline').iterator():
... i = d.items()
... i.sort()
... i
[('headline', u'Article 5'), ('id', 5)]
[('headline', u'Article 6'), ('id', 6)]
[('headline', u'Article 4'), ('id', 4)]
[('headline', u'Article 2'), ('id', 2)]
[('headline', u'Article 3'), ('id', 3)]
[('headline', u'Article 7'), ('id', 7)]
[('headline', u'Article 1'), ('id', 1)]
# The values() method works with "extra" fields specified in extra(select).
>>> for d in Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_one'):
... i = d.items()
... i.sort()
... i
[('id', 5), ('id_plus_one', 6)]
[('id', 6), ('id_plus_one', 7)]
[('id', 4), ('id_plus_one', 5)]
[('id', 2), ('id_plus_one', 3)]
[('id', 3), ('id_plus_one', 4)]
[('id', 7), ('id_plus_one', 8)]
[('id', 1), ('id_plus_one', 2)]
>>> data = {'id_plus_one': 'id+1', 'id_plus_two': 'id+2', 'id_plus_three': 'id+3',
... 'id_plus_four': 'id+4', 'id_plus_five': 'id+5', 'id_plus_six': 'id+6',
... 'id_plus_seven': 'id+7', 'id_plus_eight': 'id+8'}
>>> result = list(Article.objects.filter(id=1).extra(select=data).values(*data.keys()))[0]
>>> result = result.items()
>>> result.sort()
>>> result
[('id_plus_eight', 9), ('id_plus_five', 6), ('id_plus_four', 5), ('id_plus_one', 2), ('id_plus_seven', 8), ('id_plus_six', 7), ('id_plus_three', 4), ('id_plus_two', 3)]
# However, an exception FieldDoesNotExist will be thrown if you specify a
# non-existent field name in values() (a field that is neither in the model
# nor in extra(select)).
>>> Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_two')
Traceback (most recent call last):
...
FieldError: Cannot resolve keyword 'id_plus_two' into field. Choices are: headline, id, id_plus_one, pub_date
# If you don't specify field names to values(), all are returned.
>>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}]
True
# values_list() is similar to values(), except that the results are returned as
# a list of tuples, rather than a list of dictionaries. Within each tuple, the
# order of the elemnts is the same as the order of fields in the values_list()
# call.
>>> Article.objects.values_list('headline')
[(u'Article 5',), (u'Article 6',), (u'Article 4',), (u'Article 2',), (u'Article 3',), (u'Article 7',), (u'Article 1',)]
>>> Article.objects.values_list('id').order_by('id')
[(1,), (2,), (3,), (4,), (5,), (6,), (7,)]
>>> Article.objects.values_list('id', flat=True).order_by('id')
[1, 2, 3, 4, 5, 6, 7]
>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id')
[(1,), (2,), (3,), (4,), (5,), (6,), (7,)]
>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id_plus_one', 'id')
[(2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7)]
>>> Article.objects.extra(select={'id_plus_one': 'id+1'}).order_by('id').values_list('id', 'id_plus_one')
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)]
>>> Article.objects.values_list('id', 'headline', flat=True)
Traceback (most recent call last):
...
TypeError: 'flat' is not valid when values_list is called with more than one field.
# Every DateField and DateTimeField creates get_next_by_FOO() and
# get_previous_by_FOO() methods.
# In the case of identical date values, these methods will use the ID as a
# fallback check. This guarantees that no records are skipped or duplicated.
>>> a1.get_next_by_pub_date()
<Article: Article 2>
>>> a2.get_next_by_pub_date()
<Article: Article 3>
>>> a2.get_next_by_pub_date(headline__endswith='6')
<Article: Article 6>
>>> a3.get_next_by_pub_date()
<Article: Article 7>
>>> a4.get_next_by_pub_date()
<Article: Article 6>
>>> a5.get_next_by_pub_date()
Traceback (most recent call last):
...
DoesNotExist: Article matching query does not exist.
>>> a6.get_next_by_pub_date()
<Article: Article 5>
>>> a7.get_next_by_pub_date()
<Article: Article 4>
>>> a7.get_previous_by_pub_date()
<Article: Article 3>
>>> a6.get_previous_by_pub_date()
<Article: Article 4>
>>> a5.get_previous_by_pub_date()
<Article: Article 6>
>>> a4.get_previous_by_pub_date()
<Article: Article 7>
>>> a3.get_previous_by_pub_date()
<Article: Article 2>
>>> a2.get_previous_by_pub_date()
<Article: Article 1>
# Underscores and percent signs have special meaning in the underlying
# SQL code, but Django handles the quoting of them automatically.
>>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20))
>>> a8.save()
>>> Article.objects.filter(headline__startswith='Article')
[<Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.filter(headline__startswith='Article_')
[<Article: Article_ with underscore>]
>>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21))
>>> a9.save()
>>> Article.objects.filter(headline__startswith='Article')
[<Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.filter(headline__startswith='Article%')
[<Article: Article% with percent sign>]
# exclude() is the opposite of filter() when doing lookups:
>>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with')
[<Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.exclude(headline__startswith="Article_")
[<Article: Article% with percent sign>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
>>> Article.objects.exclude(headline="Article 7")
[<Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 1>]
# Backslashes also have special meaning in the underlying SQL code, but Django
# automatically quotes them appropriately.
>>> a10 = Article(headline='Article with \\ backslash', pub_date=datetime(2005, 11, 22))
>>> a10.save()
>>> Article.objects.filter(headline__contains='\\')
[<Article: Article with \ backslash>]
# none() returns an EmptyQuerySet that behaves like any other QuerySet object
>>> Article.objects.none()
[]
>>> Article.objects.none().filter(headline__startswith='Article')
[]
>>> Article.objects.filter(headline__startswith='Article').none()
[]
>>> Article.objects.none().count()
0
>>> [article for article in Article.objects.none().iterator()]
[]
# using __in with an empty list should return an empty query set
>>> Article.objects.filter(id__in=[])
[]
>>> Article.objects.exclude(id__in=[])
[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
# Programming errors are pointed out with nice error messages
>>> Article.objects.filter(pub_date_year='2005').count()
Traceback (most recent call last):
...
FieldError: Cannot resolve keyword 'pub_date_year' into field. Choices are: headline, id, pub_date
>>> Article.objects.filter(headline__starts='Article')
Traceback (most recent call last):
...
FieldError: Join on field 'headline' not permitted. Did you misspell 'starts' for the lookup type?
# Create some articles with a bit more interesting headlines for testing field lookups:
>>> now = datetime.now()
>>> for a in Article.objects.all():
... a.delete()
>>> a1 = Article(pub_date=now, headline='f')
>>> a1.save()
>>> a2 = Article(pub_date=now, headline='fo')
>>> a2.save()
>>> a3 = Article(pub_date=now, headline='foo')
>>> a3.save()
>>> a4 = Article(pub_date=now, headline='fooo')
>>> a4.save()
>>> a5 = Article(pub_date=now, headline='hey-Foo')
>>> a5.save()
# zero-or-more
>>> Article.objects.filter(headline__regex=r'fo*')
[<Article: f>, <Article: fo>, <Article: foo>, <Article: fooo>]
>>> Article.objects.filter(headline__iregex=r'fo*')
[<Article: f>, <Article: fo>, <Article: foo>, <Article: fooo>, <Article: hey-Foo>]
# one-or-more
>>> Article.objects.filter(headline__regex=r'fo+')
[<Article: fo>, <Article: foo>, <Article: fooo>]
# wildcard
>>> Article.objects.filter(headline__regex=r'fooo?')
[<Article: foo>, <Article: fooo>]
# and some more:
>>> a6 = Article(pub_date=now, headline='bar')
>>> a6.save()
>>> a7 = Article(pub_date=now, headline='AbBa')
>>> a7.save()
>>> a8 = Article(pub_date=now, headline='baz')
>>> a8.save()
>>> a9 = Article(pub_date=now, headline='baxZ')
>>> a9.save()
# leading anchor
>>> Article.objects.filter(headline__regex=r'^b')
[<Article: bar>, <Article: baxZ>, <Article: baz>]
>>> Article.objects.filter(headline__iregex=r'^a')
[<Article: AbBa>]
# trailing anchor
>>> Article.objects.filter(headline__regex=r'z$')
[<Article: baz>]
>>> Article.objects.filter(headline__iregex=r'z$')
[<Article: baxZ>, <Article: baz>]
# character sets
>>> Article.objects.filter(headline__regex=r'ba[rz]')
[<Article: bar>, <Article: baz>]
>>> Article.objects.filter(headline__regex=r'ba.[RxZ]')
[<Article: baxZ>]
>>> Article.objects.filter(headline__iregex=r'ba[RxZ]')
[<Article: bar>, <Article: baxZ>, <Article: baz>]
# and yet more:
>>> a10 = Article(pub_date=now, headline='foobar')
>>> a10.save()
>>> a11 = Article(pub_date=now, headline='foobaz')
>>> a11.save()
>>> a12 = Article(pub_date=now, headline='ooF')
>>> a12.save()
>>> a13 = Article(pub_date=now, headline='foobarbaz')
>>> a13.save()
>>> a14 = Article(pub_date=now, headline='zoocarfaz')
>>> a14.save()
>>> a15 = Article(pub_date=now, headline='barfoobaz')
>>> a15.save()
>>> a16 = Article(pub_date=now, headline='bazbaRFOO')
>>> a16.save()
# alternation
>>> Article.objects.filter(headline__regex=r'oo(f|b)')
[<Article: barfoobaz>, <Article: foobar>, <Article: foobarbaz>, <Article: foobaz>]
>>> Article.objects.filter(headline__iregex=r'oo(f|b)')
[<Article: barfoobaz>, <Article: foobar>, <Article: foobarbaz>, <Article: foobaz>, <Article: ooF>]
>>> Article.objects.filter(headline__regex=r'^foo(f|b)')
[<Article: foobar>, <Article: foobarbaz>, <Article: foobaz>]
# greedy matching
>>> Article.objects.filter(headline__regex=r'b.*az')
[<Article: barfoobaz>, <Article: baz>, <Article: bazbaRFOO>, <Article: foobarbaz>, <Article: foobaz>]
>>> Article.objects.filter(headline__iregex=r'b.*ar')
[<Article: bar>, <Article: barfoobaz>, <Article: bazbaRFOO>, <Article: foobar>, <Article: foobarbaz>]
"""
if settings.DATABASE_ENGINE != 'mysql':
__test__['API_TESTS'] += r"""
# grouping and backreferences
>>> Article.objects.filter(headline__regex=r'b(.).*b\1')
[<Article: barfoobaz>, <Article: bazbaRFOO>, <Article: foobarbaz>]
"""