summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2006-05-02 01:31:56 +0000
committerAdrian Holovaty <adrian@holovaty.com>2006-05-02 01:31:56 +0000
commitf69cf70ed813a8cd7e1f963a14ae39103e8d5265 (patch)
treed3b32e84cd66573b3833ddf662af020f8ef2f7a8 /tests
parentd5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 (diff)
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/__init__.py (renamed from tests/testapp/__init__.py)0
-rw-r--r--tests/modeltests/basic/__init__.py0
-rw-r--r--tests/modeltests/basic/models.py339
-rw-r--r--tests/modeltests/choices/__init__.py0
-rw-r--r--tests/modeltests/choices/models.py39
-rw-r--r--tests/modeltests/custom_columns/__init__.py0
-rw-r--r--tests/modeltests/custom_columns/models.py (renamed from tests/testapp/models/custom_columns.py)22
-rw-r--r--tests/modeltests/custom_managers/__init__.py0
-rw-r--r--tests/modeltests/custom_managers/models.py100
-rw-r--r--tests/modeltests/custom_methods/__init__.py0
-rw-r--r--tests/modeltests/custom_methods/models.py58
-rw-r--r--tests/modeltests/custom_pk/__init__.py0
-rw-r--r--tests/modeltests/custom_pk/models.py90
-rw-r--r--tests/modeltests/field_defaults/__init__.py0
-rw-r--r--tests/modeltests/field_defaults/models.py48
-rw-r--r--tests/modeltests/get_latest/__init__.py0
-rw-r--r--tests/modeltests/get_latest/models.py79
-rw-r--r--tests/modeltests/invalid_models/__init__.py1
-rw-r--r--tests/modeltests/invalid_models/models.py117
-rw-r--r--tests/modeltests/lookup/__init__.py0
-rw-r--r--tests/modeltests/lookup/models.py179
-rw-r--r--tests/modeltests/m2m_and_m2o/__init__.py0
-rw-r--r--tests/modeltests/m2m_and_m2o/models.py66
-rw-r--r--tests/modeltests/m2m_intermediary/__init__.py0
-rw-r--r--tests/modeltests/m2m_intermediary/models.py68
-rw-r--r--tests/modeltests/m2m_multiple/__init__.py0
-rw-r--r--tests/modeltests/m2m_multiple/models.py79
-rw-r--r--tests/modeltests/m2m_recursive/__init__.py0
-rw-r--r--tests/modeltests/m2m_recursive/models.py192
-rw-r--r--tests/modeltests/m2o_recursive/__init__.py0
-rw-r--r--tests/modeltests/m2o_recursive/models.py40
-rw-r--r--tests/modeltests/m2o_recursive2/__init__.py0
-rw-r--r--tests/modeltests/m2o_recursive2/models.py43
-rw-r--r--tests/modeltests/manipulators/__init__.py0
-rw-r--r--tests/modeltests/manipulators/models.py89
-rw-r--r--tests/modeltests/many_to_many/__init__.py0
-rw-r--r--tests/modeltests/many_to_many/models.py206
-rw-r--r--tests/modeltests/many_to_one/__init__.py0
-rw-r--r--tests/modeltests/many_to_one/models.py232
-rw-r--r--tests/modeltests/many_to_one_null/__init__.py0
-rw-r--r--tests/modeltests/many_to_one_null/models.py124
-rw-r--r--tests/modeltests/model_inheritance/__init__.py0
-rw-r--r--tests/modeltests/model_inheritance/models.py52
-rw-r--r--tests/modeltests/mutually_referential/__init__.py0
-rw-r--r--tests/modeltests/mutually_referential/models.py32
-rw-r--r--tests/modeltests/one_to_one/__init__.py0
-rw-r--r--tests/modeltests/one_to_one/models.py130
-rw-r--r--tests/modeltests/or_lookups/__init__.py0
-rw-r--r--tests/modeltests/or_lookups/models.py89
-rw-r--r--tests/modeltests/ordering/__init__.py0
-rw-r--r--tests/modeltests/ordering/models.py (renamed from tests/testapp/models/ordering.py)36
-rw-r--r--tests/modeltests/pagination/__init__.py0
-rw-r--r--tests/modeltests/pagination/models.py59
-rw-r--r--tests/modeltests/properties/__init__.py0
-rw-r--r--tests/modeltests/properties/models.py26
-rw-r--r--tests/modeltests/repr/__init__.py0
-rw-r--r--tests/modeltests/repr/models.py (renamed from tests/testapp/models/repr.py)10
-rw-r--r--tests/modeltests/reserved_names/__init__.py0
-rw-r--r--tests/modeltests/reserved_names/models.py56
-rw-r--r--tests/modeltests/reverse_lookup/__init__.py0
-rw-r--r--tests/modeltests/reverse_lookup/models.py56
-rw-r--r--tests/modeltests/save_delete_hooks/__init__.py0
-rw-r--r--tests/modeltests/save_delete_hooks/models.py42
-rw-r--r--tests/modeltests/transactions/__init__.py0
-rw-r--r--tests/modeltests/transactions/models.py92
-rw-r--r--tests/modeltests/validation/__init__.py0
-rw-r--r--tests/modeltests/validation/models.py147
-rw-r--r--tests/othertests/dateformat.py25
-rw-r--r--tests/othertests/db_typecasts.py4
-rw-r--r--tests/othertests/defaultfilters.py60
-rw-r--r--tests/othertests/httpwrappers.py2
-rw-r--r--tests/othertests/markup.py2
-rw-r--r--tests/othertests/templates.py221
-rwxr-xr-xtests/runtests.py102
-rw-r--r--tests/testapp/models/__init__.py5
-rw-r--r--tests/testapp/models/basic.py204
-rw-r--r--tests/testapp/models/custom_methods.py72
-rw-r--r--tests/testapp/models/custom_pk.py69
-rw-r--r--tests/testapp/models/get_latest.py43
-rw-r--r--tests/testapp/models/lookup.py153
-rw-r--r--tests/testapp/models/m2m_intermediary.py68
-rw-r--r--tests/testapp/models/m2m_multiple.py99
-rw-r--r--tests/testapp/models/m2o_recursive.py44
-rw-r--r--tests/testapp/models/m2o_recursive2.py43
-rw-r--r--tests/testapp/models/many_to_many.py82
-rw-r--r--tests/testapp/models/many_to_one.py98
-rw-r--r--tests/testapp/models/many_to_one_null.py78
-rw-r--r--tests/testapp/models/one_to_one.py80
-rw-r--r--tests/testapp/models/or_lookups.py57
-rw-r--r--tests/testapp/models/reserved_names.py47
-rw-r--r--tests/testapp/models/save_delete_hooks.py49
-rw-r--r--tests/testapp/models/subclassing.py180
92 files changed, 3288 insertions, 1637 deletions
diff --git a/tests/testapp/__init__.py b/tests/modeltests/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/testapp/__init__.py
+++ b/tests/modeltests/__init__.py
diff --git a/tests/modeltests/basic/__init__.py b/tests/modeltests/basic/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/basic/__init__.py
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
new file mode 100644
index 0000000000..c96cd993ad
--- /dev/null
+++ b/tests/modeltests/basic/models.py
@@ -0,0 +1,339 @@
+"""
+1. Bare-bones model
+
+This is a basic model with only two non-primary-key fields.
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __repr__(self):
+ return self.headline
+API_TESTS = """
+
+# No articles are in the system yet.
+>>> Article.objects.all()
+[]
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+
+# Save it into the database. You have to call save() explicitly.
+>>> a.save()
+
+# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
+>>> a.id
+1L
+
+# Access database columns via Python attributes.
+>>> a.headline
+'Area man programs in Python'
+>>> a.pub_date
+datetime.datetime(2005, 7, 28, 0, 0)
+
+# Change values by changing the attributes, then calling save().
+>>> a.headline = 'Area woman programs in Python'
+>>> a.save()
+
+# Article.objects.all() returns all the articles in the database.
+>>> Article.objects.all()
+[Area woman programs in Python]
+
+# Django provides a rich database lookup API.
+>>> Article.objects.get(id__exact=1)
+Area woman programs in Python
+>>> Article.objects.get(headline__startswith='Area woman')
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005)
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=7)
+Area woman programs in Python
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28)
+Area woman programs in Python
+
+# The "__exact" lookup type can be omitted, as a shortcut.
+>>> Article.objects.get(id=1)
+Area woman programs in Python
+>>> Article.objects.get(headline='Area woman programs in Python')
+Area woman programs in Python
+
+>>> Article.objects.filter(pub_date__year=2005)
+[Area woman programs in Python]
+>>> Article.objects.filter(pub_date__year=2004)
+[]
+>>> Article.objects.filter(pub_date__year=2005, pub_date__month=7)
+[Area woman programs in Python]
+
+# Django raises an Article.DoesNotExist exception for get() if the parameters
+# don't match any object.
+>>> Article.objects.get(id__exact=2)
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for {'id__exact': 2}
+
+>>> Article.objects.get(pub_date__year=2005, pub_date__month=8)
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+
+# Lookup by a primary key is the most common case, so Django provides a
+# shortcut for primary-key exact lookups.
+# The following is identical to articles.get(id=1).
+>>> Article.objects.get(pk=1)
+Area woman programs in Python
+
+# Model instances of the same type and same ID are considered equal.
+>>> a = Article.objects.get(pk=1)
+>>> b = Article.objects.get(pk=1)
+>>> a == b
+True
+
+# You can initialize a model instance using positional arguments, which should
+# match the field order as defined in the model.
+>>> a2 = Article(None, 'Second article', datetime(2005, 7, 29))
+>>> a2.save()
+>>> a2.id
+2L
+>>> a2.headline
+'Second article'
+>>> a2.pub_date
+datetime.datetime(2005, 7, 29, 0, 0)
+
+# ...or, you can use keyword arguments.
+>>> a3 = Article(id=None, headline='Third article', pub_date=datetime(2005, 7, 30))
+>>> a3.save()
+>>> a3.id
+3L
+>>> a3.headline
+'Third article'
+>>> a3.pub_date
+datetime.datetime(2005, 7, 30, 0, 0)
+
+# You can also mix and match position and keyword arguments, but be sure not to
+# duplicate field information.
+>>> a4 = Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31))
+>>> a4.save()
+>>> a4.headline
+'Fourth article'
+
+# Don't use invalid keyword arguments.
+>>> a5 = Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar')
+Traceback (most recent call last):
+ ...
+TypeError: 'foo' is an invalid keyword argument for this function
+
+# You can leave off the value for an AutoField when creating an object, because
+# it'll get filled in automatically when you save().
+>>> a5 = Article(headline='Article 6', pub_date=datetime(2005, 7, 31))
+>>> a5.save()
+>>> a5.id
+5L
+>>> a5.headline
+'Article 6'
+
+# If you leave off a field with "default" set, Django will use the default.
+>>> a6 = Article(pub_date=datetime(2005, 7, 31))
+>>> a6.save()
+>>> a6.headline
+'Default headline'
+
+# For DateTimeFields, Django saves as much precision (in seconds) as you
+# give it.
+>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30))
+>>> a7.save()
+>>> Article.objects.get(id__exact=7).pub_date
+datetime.datetime(2005, 7, 31, 12, 30)
+
+>>> a8 = Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45))
+>>> a8.save()
+>>> Article.objects.get(id__exact=8).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45)
+>>> a8.id
+8L
+
+# Saving an object again doesn't create a new object -- it just saves the old one.
+>>> a8.save()
+>>> a8.id
+8L
+>>> a8.headline = 'Updated article 8'
+>>> a8.save()
+>>> a8.id
+8L
+
+>>> a7 == a8
+False
+>>> a8 == Article.objects.get(id__exact=8)
+True
+>>> a7 != a8
+True
+>>> Article.objects.get(id__exact=8) != Article.objects.get(id__exact=7)
+True
+>>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7)
+False
+
+# dates() returns a list of available dates of the given scope for the given field.
+>>> Article.objects.dates('pub_date', 'year')
+[datetime.datetime(2005, 1, 1, 0, 0)]
+>>> Article.objects.dates('pub_date', 'month')
+[datetime.datetime(2005, 7, 1, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day')
+[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day', order='ASC')
+[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)]
+>>> Article.objects.dates('pub_date', 'day', order='DESC')
+[datetime.datetime(2005, 7, 31, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 28, 0, 0)]
+
+# dates() requires valid arguments.
+
+>>> Article.objects.dates()
+Traceback (most recent call last):
+ ...
+TypeError: dates() takes at least 3 arguments (1 given)
+
+>>> Article.objects.dates('invalid_field', 'year')
+Traceback (most recent call last):
+ ...
+FieldDoesNotExist: name=invalid_field
+
+>>> Article.objects.dates('pub_date', 'bad_kind')
+Traceback (most recent call last):
+ ...
+AssertionError: 'kind' must be one of 'year', 'month' or 'day'.
+
+>>> Article.objects.dates('pub_date', 'year', order='bad order')
+Traceback (most recent call last):
+ ...
+AssertionError: 'order' must be either 'ASC' or 'DESC'.
+
+# Use iterator() with dates() to return a generator that lazily requests each
+# result one at a time, to save memory.
+>>> for a in Article.objects.dates('pub_date', 'day', order='DESC').iterator():
+... print repr(a)
+datetime.datetime(2005, 7, 31, 0, 0)
+datetime.datetime(2005, 7, 30, 0, 0)
+datetime.datetime(2005, 7, 29, 0, 0)
+datetime.datetime(2005, 7, 28, 0, 0)
+
+# You can combine queries with & and |.
+>>> s1 = Article.objects.filter(id__exact=1)
+>>> s2 = Article.objects.filter(id__exact=2)
+>>> s1 | s2
+[Area woman programs in Python, Second article]
+>>> s1 & s2
+[]
+
+# You can get the number of objects like this:
+>>> len(Article.objects.filter(id__exact=1))
+1
+
+# You can get items using index and slice notation.
+>>> Article.objects.all()[0]
+Area woman programs in Python
+>>> Article.objects.all()[1:3]
+[Second article, Third article]
+>>> s3 = Article.objects.filter(id__exact=3)
+>>> (s1 | s2 | s3)[::2]
+[Area woman programs in Python, Third article]
+
+# Slices (without step) are lazy:
+>>> Article.objects.all()[0:5].filter()
+[Area woman programs in Python, Second article, Third article, Fourth article, Article 6]
+
+# Slicing again works:
+>>> Article.objects.all()[0:5][0:2]
+[Area woman programs in Python, Second article]
+>>> Article.objects.all()[0:5][:2]
+[Area woman programs in Python, Second article]
+>>> Article.objects.all()[0:5][4:]
+[Article 6]
+>>> Article.objects.all()[0:5][5:]
+[]
+
+# Some more tests!
+>>> Article.objects.all()[2:][0:2]
+[Third article, Fourth article]
+>>> Article.objects.all()[2:][:2]
+[Third article, Fourth article]
+>>> Article.objects.all()[2:][2:3]
+[Article 6]
+
+# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work:
+>>> Article.objects.all()[2:]
+Traceback (most recent call last):
+ ...
+AssertionError: 'offset' is not allowed without 'limit'
+
+# Also, once you have sliced you can't filter, re-order or combine
+>>> Article.objects.all()[0:5].filter(id=1)
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot filter a query once a slice has been taken.
+
+>>> Article.objects.all()[0:5].order_by('id')
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot reorder a query once a slice has been taken.
+
+>>> Article.objects.all()[0:1] & Article.objects.all()[4:5]
+Traceback (most recent call last):
+ ...
+AssertionError: Cannot combine queries once a slice has been taken.
+
+
+# An Article instance doesn't have access to the "objects" attribute.
+# That's only available on the class.
+>>> a7.objects.all()
+Traceback (most recent call last):
+ ...
+AttributeError: Manager isn't accessible via Article instances
+
+>>> a7.objects
+Traceback (most recent call last):
+ ...
+AttributeError: Manager isn't accessible via Article instances
+
+# Bulk delete test: How many objects before and after the delete?
+>>> Article.objects.all()
+[Area woman programs in Python, Second article, Third article, Fourth article, Article 6, Default headline, Article 7, Updated article 8]
+>>> Article.objects.filter(id__lte=4).delete()
+>>> Article.objects.all()
+[Article 6, Default headline, Article 7, Updated article 8]
+
+"""
+
+from django.conf import settings
+
+building_docs = getattr(settings, 'BUILDING_DOCS', False)
+
+if building_docs or settings.DATABASE_ENGINE == 'postgresql':
+ API_TESTS += """
+# In PostgreSQL, microsecond-level precision is available.
+>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
+>>> a9.save()
+>>> Article.objects.get(id__exact=9).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
+"""
+
+if building_docs or settings.DATABASE_ENGINE == 'mysql':
+ API_TESTS += """
+# In MySQL, microsecond-level precision isn't available. You'll lose
+# microsecond-level precision once the data is saved.
+>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
+>>> a9.save()
+>>> Article.objects.get(id__exact=9).pub_date
+datetime.datetime(2005, 7, 31, 12, 30, 45)
+"""
+
+API_TESTS += """
+
+# You can manually specify the primary key when creating a new object.
+>>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
+>>> a101.save()
+>>> a101 = Article.objects.get(pk=101)
+>>> a101.headline
+'Article 101'
+"""
diff --git a/tests/modeltests/choices/__init__.py b/tests/modeltests/choices/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/choices/__init__.py
diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py
new file mode 100644
index 0000000000..38dcb934c5
--- /dev/null
+++ b/tests/modeltests/choices/models.py
@@ -0,0 +1,39 @@
+"""
+21. Specifying 'choices' for a field
+
+Most fields take a ``choices`` parameter, which should be a tuple of tuples
+specifying which are the valid values for that field.
+
+For each field that has ``choices``, a model instance gets a
+``get_fieldname_display()`` method, where ``fieldname`` is the name of the
+field. This method returns the "human-readable" value of the field.
+"""
+
+from django.db import models
+
+GENDER_CHOICES = (
+ ('M', 'Male'),
+ ('F', 'Female'),
+)
+
+class Person(models.Model):
+ name = models.CharField(maxlength=20)
+ gender = models.CharField(maxlength=1, choices=GENDER_CHOICES)
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> a = Person(name='Adrian', gender='M')
+>>> a.save()
+>>> s = Person(name='Sara', gender='F')
+>>> s.save()
+>>> a.gender
+'M'
+>>> s.gender
+'F'
+>>> a.get_gender_display()
+'Male'
+>>> s.get_gender_display()
+'Female'
+"""
diff --git a/tests/modeltests/custom_columns/__init__.py b/tests/modeltests/custom_columns/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_columns/__init__.py
diff --git a/tests/testapp/models/custom_columns.py b/tests/modeltests/custom_columns/models.py
index c900091b64..4958517e69 100644
--- a/tests/testapp/models/custom_columns.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -6,38 +6,38 @@ If your database column name is different than your model attribute, use the
name, in API usage.
"""
-from django.core import meta
+from django.db import models
-class Person(meta.Model):
- first_name = meta.CharField(maxlength=30, db_column='firstname')
- last_name = meta.CharField(maxlength=30, db_column='last')
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30, db_column='firstname')
+ last_name = models.CharField(maxlength=30, db_column='last')
def __repr__(self):
return '%s %s' % (self.first_name, self.last_name)
API_TESTS = """
# Create a Person.
->>> p = persons.Person(first_name='John', last_name='Smith')
+>>> p = Person(first_name='John', last_name='Smith')
>>> p.save()
>>> p.id
1
->>> persons.get_list()
+>>> Person.objects.all()
[John Smith]
->>> persons.get_list(first_name__exact='John')
+>>> Person.objects.filter(first_name__exact='John')
[John Smith]
->>> persons.get_object(first_name__exact='John')
+>>> Person.objects.get(first_name__exact='John')
John Smith
->>> persons.get_list(firstname__exact='John')
+>>> Person.objects.filter(firstname__exact='John')
Traceback (most recent call last):
...
-TypeError: got unexpected keyword argument 'firstname__exact'
+TypeError: Cannot resolve keyword 'firstname' into field
->>> p = persons.get_object(last_name__exact='Smith')
+>>> p = Person.objects.get(last_name__exact='Smith')
>>> p.first_name
'John'
>>> p.last_name
diff --git a/tests/modeltests/custom_managers/__init__.py b/tests/modeltests/custom_managers/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_managers/__init__.py
diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py
new file mode 100644
index 0000000000..a6ae80029a
--- /dev/null
+++ b/tests/modeltests/custom_managers/models.py
@@ -0,0 +1,100 @@
+"""
+23. Giving models a custom manager
+"""
+
+from django.db import models
+
+# An example of a custom manager called "objects".
+
+class PersonManager(models.Manager):
+ def get_fun_people(self):
+ return self.filter(fun=True)
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ fun = models.BooleanField()
+ objects = PersonManager()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+# An example of a custom manager that sets a core_filter on its lookups.
+
+class PublishedBookManager(models.Manager):
+ def get_query_set(self):
+ return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
+
+class Book(models.Model):
+ title = models.CharField(maxlength=50)
+ author = models.CharField(maxlength=30)
+ is_published = models.BooleanField()
+ published_objects = PublishedBookManager()
+ authors = models.ManyToManyField(Person, related_name='books')
+
+ def __repr__(self):
+ return self.title
+
+# An example of providing multiple custom managers.
+
+class FastCarManager(models.Manager):
+ def get_query_set(self):
+ return super(FastCarManager, self).get_query_set().filter(top_speed__gt=150)
+
+class Car(models.Model):
+ name = models.CharField(maxlength=10)
+ mileage = models.IntegerField()
+ top_speed = models.IntegerField(help_text="In miles per hour.")
+ cars = models.Manager()
+ fast_cars = FastCarManager()
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True)
+>>> p1.save()
+>>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False)
+>>> p2.save()
+>>> Person.objects.get_fun_people()
+[Bugs Bunny]
+
+# The RelatedManager used on the 'books' descriptor extends the default manager
+>>> from modeltests.custom_managers.models import PublishedBookManager
+>>> isinstance(p2.books, PublishedBookManager)
+True
+
+>>> b1 = Book(title='How to program', author='Rodney Dangerfield', is_published=True)
+>>> b1.save()
+>>> b2 = Book(title='How to be smart', author='Albert Einstein', is_published=False)
+>>> b2.save()
+
+# The default manager, "objects", doesn't exist,
+# because a custom one was provided.
+>>> Book.objects
+Traceback (most recent call last):
+ ...
+AttributeError: type object 'Book' has no attribute 'objects'
+
+# The RelatedManager used on the 'authors' descriptor extends the default manager
+>>> from modeltests.custom_managers.models import PersonManager
+>>> isinstance(b2.authors, PersonManager)
+True
+
+>>> Book.published_objects.all()
+[How to program]
+
+>>> c1 = Car(name='Corvette', mileage=21, top_speed=180)
+>>> c1.save()
+>>> c2 = Car(name='Neon', mileage=31, top_speed=100)
+>>> c2.save()
+>>> Car.cars.order_by('name')
+[Corvette, Neon]
+>>> Car.fast_cars.all()
+[Corvette]
+
+# Each model class gets a "_default_manager" attribute, which is a reference
+# to the first manager defined in the class. In this case, it's "cars".
+>>> Car._default_manager.order_by('name')
+[Corvette, Neon]
+"""
diff --git a/tests/modeltests/custom_methods/__init__.py b/tests/modeltests/custom_methods/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_methods/__init__.py
diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py
new file mode 100644
index 0000000000..3fdefca6bf
--- /dev/null
+++ b/tests/modeltests/custom_methods/models.py
@@ -0,0 +1,58 @@
+"""
+3. Giving models custom methods and custom managers
+
+Any method you add to a model will be available to instances.
+"""
+
+from django.db import models
+import datetime
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+
+ def __repr__(self):
+ return self.headline
+
+ def was_published_today(self):
+ return self.pub_date == datetime.date.today()
+
+ def get_articles_from_same_day_1(self):
+ return Article.objects.filter(pub_date=self.pub_date).exclude(id=self.id)
+
+ def get_articles_from_same_day_2(self):
+ """
+ Verbose version of get_articles_from_same_day_1, which does a custom
+ database query for the sake of demonstration.
+ """
+ from django.db import connection
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT id, headline, pub_date
+ FROM custom_methods_article
+ WHERE pub_date = %s
+ AND id != %s""", [str(self.pub_date), self.id])
+ # The asterisk in "(*row)" tells Python to expand the list into
+ # positional arguments to Article().
+ return [self.__class__(*row) for row in cursor.fetchall()]
+
+API_TESTS = """
+# Create a couple of Articles.
+>>> from datetime import date
+>>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
+>>> a.save()
+>>> b = Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27))
+>>> b.save()
+
+# Test the custom methods.
+>>> a.was_published_today()
+False
+>>> a.get_articles_from_same_day_1()
+[Beatles reunite]
+>>> a.get_articles_from_same_day_2()
+[Beatles reunite]
+>>> b.get_articles_from_same_day_1()
+[Area man programs in Python]
+>>> b.get_articles_from_same_day_2()
+[Area man programs in Python]
+"""
diff --git a/tests/modeltests/custom_pk/__init__.py b/tests/modeltests/custom_pk/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/custom_pk/__init__.py
diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py
new file mode 100644
index 0000000000..df127ba3a8
--- /dev/null
+++ b/tests/modeltests/custom_pk/models.py
@@ -0,0 +1,90 @@
+"""
+14. Using a custom primary key
+
+By default, Django adds an ``"id"`` field to each model. But you can override
+this behavior by explicitly adding ``primary_key=True`` to a field.
+"""
+
+from django.db import models
+
+class Employee(models.Model):
+ employee_code = models.CharField(maxlength=10, primary_key=True)
+ first_name = models.CharField(maxlength=20)
+ last_name = models.CharField(maxlength=20)
+ class Meta:
+ ordering = ('last_name', 'first_name')
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Business(models.Model):
+ name = models.CharField(maxlength=20, primary_key=True)
+ employees = models.ManyToManyField(Employee)
+ class Meta:
+ verbose_name_plural = 'businesses'
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
+>>> dan.save()
+>>> Employee.objects.all()
+[Dan Jones]
+
+>>> fran = Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones')
+>>> fran.save()
+>>> Employee.objects.all()
+[Fran Bones, Dan Jones]
+
+>>> Employee.objects.get(pk='ABC123')
+Dan Jones
+>>> Employee.objects.get(pk='XYZ456')
+Fran Bones
+>>> Employee.objects.get(pk='foo')
+Traceback (most recent call last):
+ ...
+DoesNotExist: Employee does not exist for {'pk': 'foo'}
+
+# Use the name of the primary key, rather than pk.
+>>> Employee.objects.get(employee_code__exact='ABC123')
+Dan Jones
+
+# Fran got married and changed her last name.
+>>> fran = Employee.objects.get(pk='XYZ456')
+>>> fran.last_name = 'Jones'
+>>> fran.save()
+>>> Employee.objects.filter(last_name__exact='Jones')
+[Dan Jones, Fran Jones]
+>>> Employee.objects.in_bulk(['ABC123', 'XYZ456'])
+{'XYZ456': Fran Jones, 'ABC123': Dan Jones}
+
+>>> b = Business(name='Sears')
+>>> b.save()
+>>> b.employees.add(dan, fran)
+>>> b.employees.all()
+[Dan Jones, Fran Jones]
+>>> fran.business_set.all()
+[Sears]
+>>> Business.objects.in_bulk(['Sears'])
+{'Sears': Sears}
+
+>>> Business.objects.filter(name__exact='Sears')
+[Sears]
+>>> Business.objects.filter(pk='Sears')
+[Sears]
+
+# Queries across tables, involving primary key
+>>> Employee.objects.filter(business__name__exact='Sears')
+[Dan Jones, Fran Jones]
+>>> Employee.objects.filter(business__pk='Sears')
+[Dan Jones, Fran Jones]
+
+>>> Business.objects.filter(employees__employee_code__exact='ABC123')
+[Sears]
+>>> Business.objects.filter(employees__pk='ABC123')
+[Sears]
+>>> Business.objects.filter(employees__first_name__startswith='Fran')
+[Sears]
+
+"""
diff --git a/tests/modeltests/field_defaults/__init__.py b/tests/modeltests/field_defaults/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/field_defaults/__init__.py
diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py
new file mode 100644
index 0000000000..e5b7fd8e6d
--- /dev/null
+++ b/tests/modeltests/field_defaults/models.py
@@ -0,0 +1,48 @@
+"""
+XXX. Callable defaults
+
+???
+"""
+
+from django.db import models
+from datetime import datetime
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField(default = datetime.now)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+
+# No articles are in the system yet.
+>>> Article.objects.all()
+[]
+
+# Create an Article.
+>>> a = Article(id=None)
+
+# Grab the current datetime it should be very close to the default that just
+# got saved as a.pub_date
+>>> now = datetime.now()
+
+# Save it into the database. You have to call save() explicitly.
+>>> a.save()
+
+# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
+>>> a.id
+1L
+
+# Access database columns via Python attributes.
+>>> a.headline
+'Default headline'
+
+# make sure the two dates are sufficiently close
+>>> d = now - a.pub_date
+>>> d.seconds < 5
+True
+
+
+"""
diff --git a/tests/modeltests/get_latest/__init__.py b/tests/modeltests/get_latest/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/get_latest/__init__.py
diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py
new file mode 100644
index 0000000000..4b81dc837c
--- /dev/null
+++ b/tests/modeltests/get_latest/models.py
@@ -0,0 +1,79 @@
+"""
+8. get_latest_by
+
+Models can have a ``get_latest_by`` attribute, which should be set to the name
+of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's
+module will get a ``get_latest()`` function, which will return the latest
+object in the database according to that field. "Latest" means "having the
+date farthest into the future."
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ expire_date = models.DateField()
+ class Meta:
+ get_latest_by = 'pub_date'
+
+ def __repr__(self):
+ return self.headline
+
+class Person(models.Model):
+ name = models.CharField(maxlength=30)
+ birthday = models.DateField()
+
+ # Note that this model doesn't have "get_latest_by" set.
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
+>>> Article.objects.latest()
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+
+# Create a couple of Articles.
+>>> from datetime import datetime
+>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), expire_date=datetime(2005, 9, 1))
+>>> a1.save()
+>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 7, 28))
+>>> a2.save()
+>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 8, 27))
+>>> a3.save()
+>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), expire_date=datetime(2005, 7, 30))
+>>> a4.save()
+
+# Get the latest Article.
+>>> Article.objects.latest()
+Article 4
+
+# Get the latest Article that matches certain filters.
+>>> Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest()
+Article 1
+
+# Pass a custom field name to latest() to change the field that's used to
+# determine the latest object.
+>>> Article.objects.latest('expire_date')
+Article 1
+
+>>> Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date')
+Article 3
+
+# You can still use latest() with a model that doesn't have "get_latest_by"
+# set -- just pass in the field name manually.
+>>> p1 = Person(name='Ralph', birthday=datetime(1950, 1, 1))
+>>> p1.save()
+>>> p2 = Person(name='Stephanie', birthday=datetime(1960, 2, 3))
+>>> p2.save()
+>>> Person.objects.latest()
+Traceback (most recent call last):
+ ...
+AssertionError: latest() requires either a field_name parameter or 'get_latest_by' in the model
+
+>>> Person.objects.latest('birthday')
+Stephanie
+"""
diff --git a/tests/modeltests/invalid_models/__init__.py b/tests/modeltests/invalid_models/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/tests/modeltests/invalid_models/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py
new file mode 100644
index 0000000000..0b3ffcf073
--- /dev/null
+++ b/tests/modeltests/invalid_models/models.py
@@ -0,0 +1,117 @@
+"""
+26. A test to check that the model validator works can correctly identify errors in a model.
+"""
+
+from django.db import models
+
+class FieldErrors(models.Model):
+ charfield = models.CharField()
+ floatfield = models.FloatField()
+ filefield = models.FileField()
+ prepopulate = models.CharField(maxlength=10, prepopulate_from='bad')
+ choices = models.CharField(maxlength=10, choices='bad')
+ choices2 = models.CharField(maxlength=10, choices=[(1,2,3),(1,2,3)])
+ index = models.CharField(maxlength=10, db_index='bad')
+
+class Target(models.Model):
+ tgt_safe = models.CharField(maxlength=10)
+
+ clash1_set = models.CharField(maxlength=10)
+
+class Clash1(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ foreign = models.ForeignKey(Target)
+ m2m = models.ManyToManyField(Target)
+
+class Clash2(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ foreign_1 = models.ForeignKey(Target, related_name='id')
+ foreign_2 = models.ForeignKey(Target, related_name='src_safe')
+
+ m2m_1 = models.ManyToManyField(Target, related_name='id')
+ m2m_2 = models.ManyToManyField(Target, related_name='src_safe')
+
+class Target2(models.Model):
+ foreign_tgt = models.ForeignKey(Target)
+ clashforeign_set = models.ForeignKey(Target)
+
+ m2m_tgt = models.ManyToManyField(Target)
+ clashm2m_set = models.ManyToManyField(Target)
+
+class Clash3(models.Model):
+ foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt')
+ foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt')
+
+ m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt')
+ m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt')
+
+class ClashForeign(models.Model):
+ foreign = models.ForeignKey(Target2)
+
+class ClashM2M(models.Model):
+ m2m = models.ManyToManyField(Target2)
+
+class SelfClashForeign(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ selfclashforeign_set = models.ForeignKey("SelfClashForeign")
+ foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id')
+ foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe')
+
+class SelfClashM2M(models.Model):
+ src_safe = models.CharField(maxlength=10)
+
+ selfclashm2m_set = models.ManyToManyField("SelfClashM2M")
+ m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id')
+ m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe')
+
+error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute.
+invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute.
+invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute.
+invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
+invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple.
+invalid_models.fielderrors: "choices": "choices" should be either a tuple or list.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
+invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another related field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash2: 'foreign_2' accessor name 'Target.src_safe' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash2: 'm2m_2' m2m accessor name 'Target.src_safe' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'.
+invalid_models.clashforeign: 'foreign' accessor name 'Target2.clashforeign_set' clashes with another field. Add a related_name argument to the definition for 'foreign'.
+invalid_models.clashm2m: 'm2m' m2m accessor name 'Target2.clashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'm2m'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'foreign_tgt'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashforeign_set'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'.
+invalid_models.selfclashforeign: 'selfclashforeign_set' accessor name 'SelfClashForeign.selfclashforeign_set' clashes with another field. Add a related_name argument to the definition for 'selfclashforeign_set'.
+invalid_models.selfclashforeign: 'foreign_1' accessor name 'SelfClashForeign.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'.
+invalid_models.selfclashforeign: 'foreign_2' accessor name 'SelfClashForeign.src_safe' clashes with another field. Add a related_name argument to the definition for 'foreign_2'.
+invalid_models.selfclashm2m: 'selfclashm2m_set' m2m accessor name 'SelfClashM2M.selfclashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'selfclashm2m_set'.
+invalid_models.selfclashm2m: 'm2m_1' m2m accessor name 'SelfClashM2M.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'.
+invalid_models.selfclashm2m: 'm2m_2' m2m accessor name 'SelfClashM2M.src_safe' clashes with another field. Add a related_name argument to the definition for 'm2m_2'.
+"""
diff --git a/tests/modeltests/lookup/__init__.py b/tests/modeltests/lookup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/lookup/__init__.py
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
new file mode 100644
index 0000000000..605c05adbe
--- /dev/null
+++ b/tests/modeltests/lookup/models.py
@@ -0,0 +1,179 @@
+"""
+7. The lookup API
+
+This demonstrates features of the database API.
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+# 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()
+
+# 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
+
+# in_bulk() takes a list of IDs and returns a dictionary mapping IDs
+# to objects.
+>>> Article.objects.in_bulk([1, 2])
+{1: Article 1, 2: Article 2}
+>>> Article.objects.in_bulk([3])
+{3: 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': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': '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', 'Article 5'), ('id', 5)]
+[('headline', 'Article 6'), ('id', 6)]
+[('headline', 'Article 4'), ('id', 4)]
+[('headline', 'Article 2'), ('id', 2)]
+[('headline', 'Article 3'), ('id', 3)]
+[('headline', 'Article 7'), ('id', 7)]
+[('headline', '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', 'Article 5'), ('id', 5)]
+[('headline', 'Article 6'), ('id', 6)]
+[('headline', 'Article 4'), ('id', 4)]
+[('headline', 'Article 2'), ('id', 2)]
+[('headline', 'Article 3'), ('id', 3)]
+[('headline', 'Article 7'), ('id', 7)]
+[('headline', 'Article 1'), ('id', 1)]
+
+# if you don't specify which fields, all are returned
+>>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}]
+True
+
+# 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 2
+>>> a2.get_next_by_pub_date()
+Article 3
+>>> a3.get_next_by_pub_date()
+Article 7
+>>> a4.get_next_by_pub_date()
+Article 6
+>>> a5.get_next_by_pub_date()
+Traceback (most recent call last):
+ ...
+DoesNotExist: Article does not exist for ...
+>>> a6.get_next_by_pub_date()
+Article 5
+>>> a7.get_next_by_pub_date()
+Article 4
+
+>>> a7.get_previous_by_pub_date()
+Article 3
+>>> a6.get_previous_by_pub_date()
+Article 4
+>>> a5.get_previous_by_pub_date()
+Article 6
+>>> a4.get_previous_by_pub_date()
+Article 7
+>>> a3.get_previous_by_pub_date()
+Article 2
+>>> a2.get_previous_by_pub_date()
+Article 1
+
+# Underscores and percent signs have special meaning in the underlying
+# database library, 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_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.filter(headline__startswith='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% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.filter(headline__startswith='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 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.exclude(headline__startswith="Article_")
+[Article% with percent sign, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
+>>> Article.objects.exclude(headline="Article 7")
+[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 1]
+"""
diff --git a/tests/modeltests/m2m_and_m2o/__init__.py b/tests/modeltests/m2m_and_m2o/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_and_m2o/__init__.py
diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py
new file mode 100644
index 0000000000..29631e5779
--- /dev/null
+++ b/tests/modeltests/m2m_and_m2o/models.py
@@ -0,0 +1,66 @@
+"""
+27. Many-to-many and many-to-one relationships to the same table.
+
+This is a response to bug #1535
+
+"""
+
+from django.db import models
+
+class User(models.Model):
+ username = models.CharField(maxlength=20)
+
+class Issue(models.Model):
+ num = models.IntegerField()
+ cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc')
+ client = models.ForeignKey(User, related_name='test_issue_client')
+ def __repr__(self):
+ return "<Issue %d>" % (self.num,)
+
+ class Meta:
+ ordering = ('num',)
+
+
+API_TESTS = """
+>>> Issue.objects.all()
+[]
+>>> r = User(username='russell')
+>>> r.save()
+>>> g = User(username='gustav')
+>>> g.save()
+>>> i = Issue(num=1)
+>>> i.client = r
+>>> i.validate()
+{}
+>>> i.save()
+>>> i2 = Issue(num=2)
+>>> i2.client = r
+>>> i2.validate()
+{}
+>>> i2.save()
+>>> i2.cc.add(r)
+>>> i3 = Issue(num=3)
+>>> i3.client = g
+>>> i3.validate()
+{}
+>>> i3.save()
+>>> i3.cc.add(r)
+>>> from django.db.models.query import Q
+>>> Issue.objects.filter(client=r.id)
+[<Issue 1>, <Issue 2>]
+>>> Issue.objects.filter(client=g.id)
+[<Issue 3>]
+>>> Issue.objects.filter(cc__id__exact=g.id)
+[]
+>>> Issue.objects.filter(cc__id__exact=r.id)
+[<Issue 2>, <Issue 3>]
+
+# Queries that combine results from the m2m and the m2o relationship.
+# 3 ways of saying the same thing:
+>>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id))
+[<Issue 1>, <Issue 2>, <Issue 3>]
+>>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id)
+[<Issue 1>, <Issue 2>, <Issue 3>]
+>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id))
+[<Issue 1>, <Issue 2>, <Issue 3>]
+"""
diff --git a/tests/modeltests/m2m_intermediary/__init__.py b/tests/modeltests/m2m_intermediary/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_intermediary/__init__.py
diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py
new file mode 100644
index 0000000000..869b188521
--- /dev/null
+++ b/tests/modeltests/m2m_intermediary/models.py
@@ -0,0 +1,68 @@
+"""
+9. Many-to-many relationships via an intermediary table
+
+For many-to-many relationships that need extra fields on the intermediary
+table, use an intermediary model.
+
+In this example, an ``Article`` can have multiple ``Reporter``s, and each
+``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field,
+which specifies the ``Reporter``'s position for the given article (e.g. "Staff
+writer").
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+
+ def __repr__(self):
+ return self.headline
+
+class Writer(models.Model):
+ reporter = models.ForeignKey(Reporter)
+ article = models.ForeignKey(Article)
+ position = models.CharField(maxlength=100)
+
+ def __repr__(self):
+ return '%r (%s)' % (self.reporter, self.position)
+
+API_TESTS = """
+# Create a few Reporters.
+>>> r1 = Reporter(first_name='John', last_name='Smith')
+>>> r1.save()
+>>> r2 = Reporter(first_name='Jane', last_name='Doe')
+>>> r2.save()
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
+>>> a.save()
+
+# Create a few Writers.
+>>> w1 = Writer(reporter=r1, article=a, position='Main writer')
+>>> w1.save()
+>>> w2 = Writer(reporter=r2, article=a, position='Contributor')
+>>> w2.save()
+
+# Play around with the API.
+>>> a.writer_set.select_related().order_by('-position')
+[John Smith (Main writer), Jane Doe (Contributor)]
+>>> w1.reporter
+John Smith
+>>> w2.reporter
+Jane Doe
+>>> w1.article
+This is a test
+>>> w2.article
+This is a test
+>>> r1.writer_set.all()
+[John Smith (Main writer)]
+"""
diff --git a/tests/modeltests/m2m_multiple/__init__.py b/tests/modeltests/m2m_multiple/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_multiple/__init__.py
diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py
new file mode 100644
index 0000000000..3fec427c1d
--- /dev/null
+++ b/tests/modeltests/m2m_multiple/models.py
@@ -0,0 +1,79 @@
+"""
+20. Multiple many-to-many relationships between the same two tables
+
+In this example, an Article can have many Categories (as "primary") and many
+Categories (as "secondary").
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(maxlength=20)
+ class Meta:
+ ordering = ('name',)
+
+ def __repr__(self):
+ return self.name
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=50)
+ pub_date = models.DateTimeField()
+ primary_categories = models.ManyToManyField(Category, related_name='primary_article_set')
+ secondary_categories = models.ManyToManyField(Category, related_name='secondary_article_set')
+ class Meta:
+ ordering = ('pub_date',)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+
+>>> c1 = Category(name='Sports')
+>>> c1.save()
+>>> c2 = Category(name='News')
+>>> c2.save()
+>>> c3 = Category(name='Crime')
+>>> c3.save()
+>>> c4 = Category(name='Life')
+>>> c4.save()
+
+>>> a1 = Article(headline='Area man steals', pub_date=datetime(2005, 11, 27))
+>>> a1.save()
+>>> a1.primary_categories.add(c2, c3)
+>>> a1.secondary_categories.add(c4)
+
+>>> a2 = Article(headline='Area man runs', pub_date=datetime(2005, 11, 28))
+>>> a2.save()
+>>> a2.primary_categories.add(c1, c2)
+>>> a2.secondary_categories.add(c4)
+
+>>> a1.primary_categories.all()
+[Crime, News]
+
+>>> a2.primary_categories.all()
+[News, Sports]
+
+>>> a1.secondary_categories.all()
+[Life]
+
+
+>>> c1.primary_article_set.all()
+[Area man runs]
+>>> c1.secondary_article_set.all()
+[]
+>>> c2.primary_article_set.all()
+[Area man steals, Area man runs]
+>>> c2.secondary_article_set.all()
+[]
+>>> c3.primary_article_set.all()
+[Area man steals]
+>>> c3.secondary_article_set.all()
+[]
+>>> c4.primary_article_set.all()
+[]
+>>> c4.secondary_article_set.all()
+[Area man steals, Area man runs]
+"""
diff --git a/tests/modeltests/m2m_recursive/__init__.py b/tests/modeltests/m2m_recursive/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2m_recursive/__init__.py
diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py
new file mode 100644
index 0000000000..ff8a5a8f47
--- /dev/null
+++ b/tests/modeltests/m2m_recursive/models.py
@@ -0,0 +1,192 @@
+"""
+26. Many-to-many relationships between the same two tables
+
+In this example, A Person can have many friends, who are also people. Friendship is a
+symmetrical relationshiup - if I am your friend, you are my friend.
+
+A person can also have many idols - but while I may idolize you, you may not think
+the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive
+m2m fields may be non-symmetrical, and they are symmetrical by default.
+
+This test validates that the m2m table will create a mangled name for the m2m table if
+there will be a clash, and tests that symmetry is preserved where appropriate.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ name = models.CharField(maxlength=20)
+ friends = models.ManyToManyField('self')
+ idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers')
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> a = Person(name='Anne')
+>>> a.save()
+>>> b = Person(name='Bill')
+>>> b.save()
+>>> c = Person(name='Chuck')
+>>> c.save()
+>>> d = Person(name='David')
+>>> d.save()
+
+# Add some friends in the direction of field definition
+# Anne is friends with Bill and Chuck
+>>> a.friends.add(b,c)
+
+# David is friends with Anne and Chuck - add in reverse direction
+>>> d.friends.add(a,c)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Bill, Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[Anne]
+
+# Who is friends with Chuck?
+>>> c.friends.all()
+[Anne, David]
+
+# Who is friends with David?
+>>> d.friends.all()
+[Anne, Chuck]
+
+# Bill is already friends with Anne - add Anne again, but in the reverse direction
+>>> b.friends.add(a)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Bill, Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[Anne]
+
+# Remove Anne from Bill's friends
+>>> b.friends.remove(a)
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[Chuck, David]
+
+# Who is friends with Bill?
+>>> b.friends.all()
+[]
+
+# Clear Anne's group of friends
+>>> a.friends.clear()
+
+# Who is friends with Anne?
+>>> a.friends.all()
+[]
+
+# Reverse relationships should also be gone
+# Who is friends with Chuck?
+>>> c.friends.all()
+[David]
+
+# Who is friends with David?
+>>> d.friends.all()
+[Chuck]
+
+
+# Add some idols in the direction of field definition
+# Anne idolizes Bill and Chuck
+>>> a.idols.add(b,c)
+
+# Bill idolizes Anne right back
+>>> b.idols.add(a)
+
+# David is idolized by Anne and Chuck - add in reverse direction
+>>> d.stalkers.add(a,c)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Bill, Chuck, David]
+
+# Who is stalking Anne?
+>>> a.stalkers.all()
+[Bill]
+
+# Who are Bill's idols?
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[Anne]
+
+# Who are Chuck's idols?
+>>> c.idols.all()
+[David]
+
+# Who is stalking Chuck?
+>>> c.stalkers.all()
+[Anne]
+
+# Who are David's idols?
+>>> d.idols.all()
+[]
+
+# Who is stalking David
+>>> d.stalkers.all()
+[Anne, Chuck]
+
+# Bill is already being stalked by Anne - add Anne again, but in the reverse direction
+>>> b.stalkers.add(a)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Bill, Chuck, David]
+
+# Who is stalking Anne?
+[Bill]
+
+# Who are Bill's idols
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[Anne]
+
+# Remove Anne from Bill's list of stalkers
+>>> b.stalkers.remove(a)
+
+# Who are Anne's idols?
+>>> a.idols.all()
+[Chuck, David]
+
+# Who is stalking Anne?
+>>> a.stalkers.all()
+[Bill]
+
+# Who are Bill's idols?
+>>> b.idols.all()
+[Anne]
+
+# Who is stalking Bill?
+>>> b.stalkers.all()
+[]
+
+# Clear Anne's group of idols
+>>> a.idols.clear()
+
+# Who are Anne's idols
+>>> a.idols.all()
+[]
+
+# Reverse relationships should also be gone
+# Who is stalking Chuck?
+>>> c.stalkers.all()
+[]
+
+# Who is friends with David?
+>>> d.stalkers.all()
+[Chuck]
+
+"""
diff --git a/tests/modeltests/m2o_recursive/__init__.py b/tests/modeltests/m2o_recursive/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2o_recursive/__init__.py
diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py
new file mode 100644
index 0000000000..e7996bc15f
--- /dev/null
+++ b/tests/modeltests/m2o_recursive/models.py
@@ -0,0 +1,40 @@
+"""
+11. Relating an object to itself, many-to-one
+
+To define a many-to-one relationship between a model and itself, use
+``ForeignKey('self')``.
+
+In this example, a ``Category`` is related to itself. That is, each
+``Category`` has a parent ``Category``.
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(maxlength=20)
+ parent = models.ForeignKey('self', null=True, related_name='child_set')
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+# Create a few Category objects.
+>>> r = Category(id=None, name='Root category', parent=None)
+>>> r.save()
+>>> c = Category(id=None, name='Child category', parent=r)
+>>> c.save()
+
+>>> r.child_set.all()
+[Child category]
+>>> r.child_set.get(name__startswith='Child')
+Child category
+>>> print r.parent
+None
+
+>>> c.child_set.all()
+[]
+>>> c.parent
+Root category
+"""
diff --git a/tests/modeltests/m2o_recursive2/__init__.py b/tests/modeltests/m2o_recursive2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/m2o_recursive2/__init__.py
diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py
new file mode 100644
index 0000000000..40b842dc92
--- /dev/null
+++ b/tests/modeltests/m2o_recursive2/models.py
@@ -0,0 +1,43 @@
+"""
+12. Relating a model to another model more than once
+
+In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
+which are other ``Person`` objects.
+
+Set ``related_name`` to designate what the reverse relationship is called.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ full_name = models.CharField(maxlength=20)
+ mother = models.ForeignKey('self', null=True, related_name='mothers_child_set')
+ father = models.ForeignKey('self', null=True, related_name='fathers_child_set')
+
+ def __repr__(self):
+ return self.full_name
+
+API_TESTS = """
+# Create two Person objects -- the mom and dad in our family.
+>>> dad = Person(full_name='John Smith Senior', mother=None, father=None)
+>>> dad.save()
+>>> mom = Person(full_name='Jane Smith', mother=None, father=None)
+>>> mom.save()
+
+# Give mom and dad a kid.
+>>> kid = Person(full_name='John Smith Junior', mother=mom, father=dad)
+>>> kid.save()
+
+>>> kid.mother
+Jane Smith
+>>> kid.father
+John Smith Senior
+>>> dad.fathers_child_set.all()
+[John Smith Junior]
+>>> mom.mothers_child_set.all()
+[John Smith Junior]
+>>> kid.mothers_child_set.all()
+[]
+>>> kid.fathers_child_set.all()
+[]
+"""
diff --git a/tests/modeltests/manipulators/__init__.py b/tests/modeltests/manipulators/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/manipulators/__init__.py
diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py
new file mode 100644
index 0000000000..da47c0afd5
--- /dev/null
+++ b/tests/modeltests/manipulators/models.py
@@ -0,0 +1,89 @@
+"""
+25. Default manipulators
+"""
+
+from django.db import models
+
+class Musician(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Album(models.Model):
+ name = models.CharField(maxlength=100)
+ musician = models.ForeignKey(Musician)
+ release_date = models.DateField(blank=True, null=True)
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+>>> from django.utils.datastructures import MultiValueDict
+
+# Create a Musician object via the default AddManipulator.
+>>> man = Musician.AddManipulator()
+>>> data = MultiValueDict({'first_name': ['Ella'], 'last_name': ['Fitzgerald']})
+
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> m1 = man.save(data)
+
+# Verify it worked.
+>>> Musician.objects.all()
+[Ella Fitzgerald]
+>>> [m1] == list(Musician.objects.all())
+True
+
+# Attempt to add a Musician without a first_name.
+>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']}))
+{'first_name': ['This field is required.']}
+
+# Attempt to add a Musician without a first_name and last_name.
+>>> man.get_validation_errors(MultiValueDict({}))
+{'first_name': ['This field is required.'], 'last_name': ['This field is required.']}
+
+# Attempt to create an Album without a name or musician.
+>>> man = Album.AddManipulator()
+>>> man.get_validation_errors(MultiValueDict({}))
+{'musician': ['This field is required.'], 'name': ['This field is required.']}
+
+# Attempt to create an Album with an invalid musician.
+>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']}))
+{'musician': ["Select a valid choice; 'foo' is not in ['', '1']."]}
+
+# Attempt to create an Album with an invalid release_date.
+>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'}))
+{'release_date': ['Enter a valid date in YYYY-MM-DD format.']}
+
+# Create an Album without a release_date (because it's optional).
+>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']})
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> a1 = man.save(data)
+
+# Verify it worked.
+>>> Album.objects.all()
+[Ella and Basie]
+>>> Album.objects.get().musician
+Ella Fitzgerald
+
+# Create an Album with a release_date.
+>>> data = MultiValueDict({'name': ['Ultimate Ella'], 'musician': ['1'], 'release_date': ['2005-02-13']})
+>>> man.get_validation_errors(data)
+{}
+>>> man.do_html2python(data)
+>>> a2 = man.save(data)
+
+# Verify it worked.
+>>> Album.objects.order_by('name')
+[Ella and Basie, Ultimate Ella]
+>>> a2 = Album.objects.get(pk=2)
+>>> a2
+Ultimate Ella
+>>> a2.release_date
+datetime.date(2005, 2, 13)
+"""
diff --git a/tests/modeltests/many_to_many/__init__.py b/tests/modeltests/many_to_many/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_many/__init__.py
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
new file mode 100644
index 0000000000..dc69f9a49f
--- /dev/null
+++ b/tests/modeltests/many_to_many/models.py
@@ -0,0 +1,206 @@
+"""
+5. Many-to-many relationships
+
+To define a many-to-many relationship, use ManyToManyField().
+
+In this example, an article can be published in multiple publications,
+and a publication has multiple articles.
+"""
+
+from django.db import models
+
+class Publication(models.Model):
+ title = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return self.title
+
+ class Meta:
+ ordering = ('title',)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ publications = models.ManyToManyField(Publication)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a couple of Publications.
+>>> p1 = Publication(id=None, title='The Python Journal')
+>>> p1.save()
+>>> p2 = Publication(id=None, title='Science News')
+>>> p2.save()
+>>> p3 = Publication(id=None, title='Science Weekly')
+>>> p3.save()
+
+# Create an Article.
+>>> a1 = Article(id=None, headline='Django lets you build Web apps easily')
+>>> a1.save()
+
+# Associate the Article with a Publication.
+>>> a1.publications.add(p1)
+
+# Create another Article, and set it to appear in both Publications.
+>>> a2 = Article(id=None, headline='NASA uses Python')
+>>> a2.save()
+>>> a2.publications.add(p1, p2)
+>>> a2.publications.add(p3)
+
+# Adding a second time is OK
+>>> a2.publications.add(p3)
+
+# Add a Publication directly via publications.add by using keyword arguments.
+>>> new_publication = a2.publications.create(title='Highlights for Children')
+
+# Article objects have access to their related Publication objects.
+>>> a1.publications.all()
+[The Python Journal]
+>>> a2.publications.all()
+[Highlights for Children, Science News, Science Weekly, The Python Journal]
+
+# Publication objects have access to their related Article objects.
+>>> p2.article_set.all()
+[NASA uses Python]
+>>> p1.article_set.all()
+[Django lets you build Web apps easily, NASA uses Python]
+>>> Publication.objects.get(id=4).article_set.all()
+[NASA uses Python]
+
+# We can perform kwarg queries across m2m relationships
+>>> Article.objects.filter(publications__id__exact=1)
+[Django lets you build Web apps easily, NASA uses Python]
+>>> Article.objects.filter(publications__pk=1)
+[Django lets you build Web apps easily, NASA uses Python]
+
+>>> Article.objects.filter(publications__title__startswith="Science")
+[NASA uses Python, NASA uses Python]
+
+>>> Article.objects.filter(publications__title__startswith="Science").distinct()
+[NASA uses Python]
+
+# Reverse m2m queries are supported (i.e., starting at the table that doesn't
+# have a ManyToManyField).
+>>> Publication.objects.filter(id__exact=1)
+[The Python Journal]
+>>> Publication.objects.filter(pk=1)
+[The Python Journal]
+
+>>> Publication.objects.filter(article__headline__startswith="NASA")
+[Highlights for Children, Science News, Science Weekly, The Python Journal]
+
+>>> Publication.objects.filter(article__id__exact=1)
+[The Python Journal]
+
+>>> Publication.objects.filter(article__pk=1)
+[The Python Journal]
+
+# If we delete a Publication, its Articles won't be able to access it.
+>>> p1.delete()
+>>> Publication.objects.all()
+[Highlights for Children, Science News, Science Weekly]
+>>> a1 = Article.objects.get(pk=1)
+>>> a1.publications.all()
+[]
+
+# If we delete an Article, its Publications won't be able to access it.
+>>> a2.delete()
+>>> Article.objects.all()
+[Django lets you build Web apps easily]
+>>> p1.article_set.all()
+[Django lets you build Web apps easily]
+
+# Adding via the 'other' end of an m2m
+>>> a4 = Article(headline='NASA finds intelligent life on Earth')
+>>> a4.save()
+>>> p2.article_set.add(a4)
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth]
+>>> a4.publications.all()
+[Science News]
+
+# Adding via the other end using keywords
+>>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders')
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a5 = p2.article_set.all()[1]
+>>> a5.publications.all()
+[Science News]
+
+# Removing publication from an article:
+>>> a4.publications.remove(p2)
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[]
+
+# And from the other end
+>>> p2.article_set.remove(a5)
+>>> p2.article_set.all()
+[]
+>>> a5.publications.all()
+[]
+
+# Relation sets can be assigned. Assignment clears any existing set members
+>>> p2.article_set = [a4, a5]
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science News]
+>>> a4.publications = [p3]
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science Weekly]
+
+# Relation sets can be cleared:
+>>> p2.article_set.clear()
+>>> p2.article_set.all()
+[]
+>>> a4.publications.all()
+[Science Weekly]
+
+# And you can clear from the other end
+>>> p2.article_set.add(a4, a5)
+>>> p2.article_set.all()
+[NASA finds intelligent life on Earth, Oxygen-free diet works wonders]
+>>> a4.publications.all()
+[Science News, Science Weekly]
+>>> a4.publications.clear()
+>>> a4.publications.all()
+[]
+>>> p2.article_set.all()
+[Oxygen-free diet works wonders]
+
+# Recreate the article and Publication we just deleted.
+>>> p1 = Publication(id=None, title='The Python Journal')
+>>> p1.save()
+>>> a2 = Article(id=None, headline='NASA uses Python')
+>>> a2.save()
+>>> a2.publications.add(p1, p2, p3)
+
+# Bulk delete some Publications - references to deleted publications should go
+>>> Publication.objects.filter(title__startswith='Science').delete()
+>>> Publication.objects.all()
+[Highlights for Children, The Python Journal]
+>>> Article.objects.all()
+[Django lets you build Web apps easily, NASA finds intelligent life on Earth, NASA uses Python, Oxygen-free diet works wonders]
+>>> a2.publications.all()
+[The Python Journal]
+
+# Bulk delete some articles - references to deleted objects should go
+>>> q = Article.objects.filter(headline__startswith='Django')
+>>> print q
+[Django lets you build Web apps easily]
+>>> q.delete()
+
+# After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone
+>>> print q
+[]
+>>> p1.article_set.all()
+[NASA uses Python]
+
+"""
diff --git a/tests/modeltests/many_to_one/__init__.py b/tests/modeltests/many_to_one/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_one/__init__.py
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
new file mode 100644
index 0000000000..165a36c91c
--- /dev/null
+++ b/tests/modeltests/many_to_one/models.py
@@ -0,0 +1,232 @@
+"""
+4. Many-to-one relationships
+
+To define a many-to-one relationship, use ``ForeignKey()`` .
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ email = models.EmailField()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateField()
+ reporter = models.ForeignKey(Reporter)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a few Reporters.
+>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com')
+>>> r.save()
+
+>>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com')
+>>> r2.save()
+
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
+>>> a.save()
+
+>>> a.reporter.id
+1
+
+>>> a.reporter
+John Smith
+
+# Article objects have access to their related Reporter objects.
+>>> r = a.reporter
+>>> r.first_name, r.last_name
+('John', 'Smith')
+
+# Create an Article via the Reporter object.
+>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29))
+>>> new_article
+John's second story
+>>> new_article.reporter.id
+1
+
+# Create a new article, and add it to the article set.
+>>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17))
+>>> r.article_set.add(new_article2)
+>>> new_article2.reporter.id
+1
+>>> r.article_set.all()
+[John's second story, Paul's story, This is a test]
+
+# Add the same article to a different article set - check that it moves.
+>>> r2.article_set.add(new_article2)
+>>> new_article2.reporter.id
+2
+>>> r.article_set.all()
+[John's second story, This is a test]
+>>> r2.article_set.all()
+[Paul's story]
+
+# Assign the article to the reporter directly using the descriptor
+>>> new_article2.reporter = r
+>>> new_article2.save()
+>>> new_article2.reporter
+John Smith
+>>> new_article2.reporter.id
+1
+>>> r.article_set.all()
+[John's second story, Paul's story, This is a test]
+>>> r2.article_set.all()
+[]
+
+# Set the article back again using set descriptor.
+>>> r2.article_set = [new_article, new_article2]
+>>> r.article_set.all()
+[This is a test]
+>>> r2.article_set.all()
+[John's second story, Paul's story]
+
+# Funny case - assignment notation can only go so far; because the
+# ForeignKey cannot be null, existing members of the set must remain
+>>> r.article_set = [new_article]
+>>> r.article_set.all()
+[John's second story, This is a test]
+>>> r2.article_set.all()
+[Paul's story]
+
+# Reporter cannot be null - there should not be a clear or remove method
+>>> hasattr(r2.article_set, 'remove')
+False
+>>> hasattr(r2.article_set, 'clear')
+False
+
+# Reporter objects have access to their related Article objects.
+>>> r.article_set.all()
+[John's second story, This is a test]
+
+>>> r.article_set.filter(headline__startswith='This')
+[This is a test]
+
+>>> r.article_set.count()
+2
+
+>>> r2.article_set.count()
+1
+
+# Get articles by id
+>>> Article.objects.filter(id__exact=1)
+[This is a test]
+>>> Article.objects.filter(pk=1)
+[This is a test]
+
+# Query on an article property
+>>> Article.objects.filter(headline__startswith='This')
+[This is a test]
+
+# The API automatically follows relationships as far as you need.
+# Use double underscores to separate relationships.
+# This works as many levels deep as you want. There's no limit.
+# Find all Articles for any Reporter whose first name is "John".
+>>> Article.objects.filter(reporter__first_name__exact='John')
+[John's second story, This is a test]
+
+# Query twice over the related field.
+>>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith')
+[John's second story, This is a test]
+
+# The underlying query only makes one join when a related table is referenced twice.
+>>> query = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith')
+>>> null, sql, null = query._get_sql_clause()
+>>> sql.count('INNER JOIN')
+1
+
+# The automatically joined table has a predictable name.
+>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"])
+[John's second story, This is a test]
+
+# Find all Articles for the Reporter whose ID is 1.
+>>> Article.objects.filter(reporter__id__exact=1)
+[John's second story, This is a test]
+>>> Article.objects.filter(reporter__pk=1)
+[John's second story, 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):
+ ...
+TypeError: Cannot resolve keyword 'reporter_id' into field
+
+# You need to specify a comparison clause
+>>> Article.objects.filter(reporter_id=1)
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'reporter_id' into field
+
+# "pk" shortcut syntax works in a related context, too.
+>>> Article.objects.filter(reporter__pk=1)
+[John's second story, This is a test]
+
+# You can also instantiate an Article by passing
+# the Reporter's ID instead of a Reporter object.
+>>> a3 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
+>>> a3.save()
+>>> a3.reporter.id
+1
+>>> a3.reporter
+John Smith
+
+# Similarly, the reporter ID can be a string.
+>>> a4 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
+>>> a4.save()
+>>> a4.reporter
+John Smith
+
+# Reporters can be queried
+>>> Reporter.objects.filter(id__exact=1)
+[John Smith]
+>>> Reporter.objects.filter(pk=1)
+[John Smith]
+>>> Reporter.objects.filter(first_name__startswith='John')
+[John Smith]
+
+# Reporters can query in opposite direction of ForeignKey definition
+>>> Reporter.objects.filter(article__id__exact=1)
+[John Smith]
+>>> Reporter.objects.filter(article__pk=1)
+[John Smith]
+>>> Reporter.objects.filter(article__headline__startswith='This')
+[John Smith, John Smith, John Smith]
+>>> Reporter.objects.filter(article__headline__startswith='This').distinct()
+[John Smith]
+
+# Queries can go round in circles.
+>>> Reporter.objects.filter(article__reporter__first_name__startswith='John')
+[John Smith, John Smith, John Smith, John Smith]
+>>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct()
+[John Smith]
+
+# If you delete a reporter, his articles will be deleted.
+>>> Article.objects.all()
+[John's second story, Paul's story, This is a test, This is a test, This is a test]
+>>> Reporter.objects.order_by('first_name')
+[John Smith, Paul Jones]
+>>> r2.delete()
+>>> Article.objects.all()
+[John's second story, This is a test, This is a test, This is a test]
+>>> Reporter.objects.order_by('first_name')
+[John Smith]
+
+# Deletes using a join in the query
+>>> Reporter.objects.filter(article__headline__startswith='This').delete()
+>>> Reporter.objects.all()
+[]
+>>> Article.objects.all()
+[]
+
+"""
diff --git a/tests/modeltests/many_to_one_null/__init__.py b/tests/modeltests/many_to_one_null/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/many_to_one_null/__init__.py
diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py
new file mode 100644
index 0000000000..6818493ee3
--- /dev/null
+++ b/tests/modeltests/many_to_one_null/models.py
@@ -0,0 +1,124 @@
+"""
+16. Many-to-one relationships that can be null
+
+To define a many-to-one relationship that can have a null foreign key, use
+``ForeignKey()`` with ``null=True`` .
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ name = models.CharField(maxlength=30)
+
+ def __repr__(self):
+ return self.name
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ reporter = models.ForeignKey(Reporter, null=True)
+
+ def __repr__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
+API_TESTS = """
+# Create a Reporter.
+>>> r = Reporter(name='John Smith')
+>>> r.save()
+
+# Create an Article.
+>>> a = Article(headline="First", reporter=r)
+>>> a.save()
+
+>>> a.reporter.id
+1
+
+>>> a.reporter
+John Smith
+
+# Article objects have access to their related Reporter objects.
+>>> r = a.reporter
+
+# Create an Article via the Reporter object.
+>>> a2 = r.article_set.create(headline="Second")
+>>> a2
+Second
+>>> a2.reporter.id
+1
+
+# Reporter objects have access to their related Article objects.
+>>> r.article_set.all()
+[First, Second]
+>>> r.article_set.filter(headline__startswith='Fir')
+[First]
+>>> r.article_set.count()
+2
+
+# Create an Article with no Reporter by passing "reporter=None".
+>>> a3 = Article(headline="Third", reporter=None)
+>>> a3.save()
+>>> a3.id
+3
+>>> print a3.reporter
+None
+
+# Need to reget a3 to refresh the cache
+>>> a3 = Article.objects.get(pk=3)
+>>> print a3.reporter.id
+Traceback (most recent call last):
+ ...
+AttributeError: 'NoneType' object has no attribute 'id'
+
+# Accessing an article's 'reporter' attribute returns None
+# if the reporter is set to None.
+>>> print a3.reporter
+None
+
+# To retrieve the articles with no reporters set, use "reporter__isnull=True".
+>>> Article.objects.filter(reporter__isnull=True)
+[Third]
+
+# Set the reporter for the Third article
+>>> r.article_set.add(a3)
+>>> r.article_set.all()
+[First, Second, Third]
+
+# Remove an article from the set, and check that it was removed.
+>>> r.article_set.remove(a3)
+>>> r.article_set.all()
+[First, Second]
+>>> Article.objects.filter(reporter__isnull=True)
+[Third]
+
+# Create another article and reporter
+>>> r2 = Reporter(name='Paul Jones')
+>>> r2.save()
+>>> a4 = r2.article_set.create(headline='Fourth')
+>>> r2.article_set.all()
+[Fourth]
+
+# Try to remove a4 from a set it does not belong to
+>>> r.article_set.remove(a4)
+Traceback (most recent call last):
+...
+DoesNotExist: 'Fourth' is not related to 'John Smith'.
+
+>>> r2.article_set.all()
+[Fourth]
+
+# Use descriptor assignment to allocate ForeignKey. Null is legal, so
+# existing members of set that are not in the assignment set are set null
+>>> r2.article_set = [a2, a3]
+>>> r2.article_set.all()
+[Second, Third]
+
+# Clear the rest of the set
+>>> r.article_set.clear()
+>>> r.article_set.all()
+[]
+>>> Article.objects.filter(reporter__isnull=True)
+[First, Fourth]
+
+"""
diff --git a/tests/modeltests/model_inheritance/__init__.py b/tests/modeltests/model_inheritance/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/model_inheritance/__init__.py
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
new file mode 100644
index 0000000000..cdc4b4e2ac
--- /dev/null
+++ b/tests/modeltests/model_inheritance/models.py
@@ -0,0 +1,52 @@
+"""
+XX. Model inheritance
+
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(maxlength=50)
+ address = models.CharField(maxlength=80)
+
+ def __repr__(self):
+ return "%s the place" % self.name
+
+class Restaurant(Place):
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the restaurant" % self.name
+
+class ItalianRestaurant(Restaurant):
+ serves_gnocchi = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the italian restaurant" % self.name
+
+API_TESTS = """
+# Make sure Restaurant has the right fields in the right order.
+>>> [f.name for f in Restaurant._meta.fields]
+['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza']
+
+# Make sure ItalianRestaurant has the right fields in the right order.
+>>> [f.name for f in ItalianRestaurant._meta.fields]
+['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza', 'serves_gnocchi']
+
+# Create a couple of Places.
+>>> p1 = Place(name='Master Shakes', address='666 W. Jersey')
+>>> p1.save()
+>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
+>>> p2.save()
+
+# Test constructor for Restaurant.
+>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False)
+>>> r.save()
+
+# Test the constructor for ItalianRestaurant.
+>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Elm', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True)
+>>> ir.save()
+
+
+"""
diff --git a/tests/modeltests/mutually_referential/__init__.py b/tests/modeltests/mutually_referential/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/mutually_referential/__init__.py
diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py
new file mode 100644
index 0000000000..07b52effbc
--- /dev/null
+++ b/tests/modeltests/mutually_referential/models.py
@@ -0,0 +1,32 @@
+"""
+24. Mutually referential many-to-one relationships
+
+To define a many-to-one relationship, use ``ForeignKey()`` .
+"""
+
+from django.db.models import *
+
+class Parent(Model):
+ name = CharField(maxlength=100, core=True)
+ bestchild = ForeignKey("Child", null=True, related_name="favoured_by")
+
+class Child(Model):
+ name = CharField(maxlength=100)
+ parent = ForeignKey(Parent)
+
+API_TESTS = """
+# Create a Parent
+>>> q = Parent(name='Elizabeth')
+>>> q.save()
+
+# Create some children
+>>> c = q.child_set.create(name='Charles')
+>>> e = q.child_set.create(name='Edward')
+
+# Set the best child
+>>> q.bestchild = c
+>>> q.save()
+
+>>> q.delete()
+
+""" \ No newline at end of file
diff --git a/tests/modeltests/one_to_one/__init__.py b/tests/modeltests/one_to_one/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/one_to_one/__init__.py
diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py
new file mode 100644
index 0000000000..ef8a166afb
--- /dev/null
+++ b/tests/modeltests/one_to_one/models.py
@@ -0,0 +1,130 @@
+"""
+10. One-to-one relationships
+
+To define a one-to-one relationship, use ``OneToOneField()``.
+
+In this example, a ``Place`` optionally can be a ``Restaurant``.
+"""
+
+from django.db import models
+
+class Place(models.Model):
+ name = models.CharField(maxlength=50)
+ address = models.CharField(maxlength=80)
+
+ def __repr__(self):
+ return "%s the place" % self.name
+
+class Restaurant(models.Model):
+ place = models.OneToOneField(Place)
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __repr__(self):
+ return "%s the restaurant" % self.place.name
+
+class Waiter(models.Model):
+ restaurant = models.ForeignKey(Restaurant)
+ name = models.CharField(maxlength=50)
+
+ def __repr__(self):
+ return "%s the waiter at %r" % (self.name, self.restaurant)
+
+API_TESTS = """
+# Create a couple of Places.
+>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
+>>> p1.save()
+>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland')
+>>> p2.save()
+
+# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
+>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
+>>> r.save()
+
+# A Restaurant can access its place.
+>>> r.place
+Demon Dogs the place
+
+# A Place can access its restaurant, if available.
+>>> p1.restaurant
+Demon Dogs the restaurant
+
+# p2 doesn't have an associated restaurant.
+>>> p2.restaurant
+Traceback (most recent call last):
+ ...
+DoesNotExist: Restaurant does not exist for {'place__pk': ...}
+
+# Set the place using assignment notation. Because place is the primary key on Restaurant,
+# the save will create a new restaurant
+>>> r.place = p2
+>>> r.save()
+>>> p2.restaurant
+Ace Hardware the restaurant
+>>> r.place
+Ace Hardware the place
+
+# Set the place back again, using assignment in the reverse direction
+# Need to reget restaurant object first, because the reverse set
+# can't update the existing restaurant instance
+>>> p1.restaurant = r
+>>> r.save()
+>>> p1.restaurant
+Demon Dogs the restaurant
+
+>>> r = Restaurant.objects.get(pk=1)
+>>> r.place
+Demon Dogs the place
+
+# Restaurant.objects.all() just returns the Restaurants, not the Places.
+# Note that there are two restaurants - Ace Hardware the Restaurant was created
+# in the call to r.place = p2. This means there are multiple restaurants referencing
+# a single place...
+>>> Restaurant.objects.all()
+[Demon Dogs the restaurant, Ace Hardware the restaurant]
+
+# Place.objects.all() returns all Places, regardless of whether they have
+# Restaurants.
+>>> Place.objects.order_by('name')
+[Ace Hardware the place, Demon Dogs the place]
+
+>>> Restaurant.objects.get(place__id__exact=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(pk=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__exact=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__pk=1)
+Demon Dogs the restaurant
+>>> Restaurant.objects.get(place__name__startswith="Demon")
+Demon Dogs the restaurant
+
+>>> Place.objects.get(id__exact=1)
+Demon Dogs the place
+>>> Place.objects.get(pk=1)
+Demon Dogs the place
+>>> Place.objects.get(restaurant__place__exact=1)
+Demon Dogs the place
+>>> Place.objects.get(restaurant__pk=1)
+Demon Dogs the place
+
+# Add a Waiter to the Restaurant.
+>>> w = r.waiter_set.create(name='Joe')
+>>> w.save()
+>>> w
+Joe the waiter at Demon Dogs the restaurant
+
+# Query the waiters
+>>> Waiter.objects.filter(restaurant__place__exact=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(restaurant__pk=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(id__exact=1)
+[Joe the waiter at Demon Dogs the restaurant]
+>>> Waiter.objects.filter(pk=1)
+[Joe the waiter at Demon Dogs the restaurant]
+
+# Delete the restaurant; the waiter should also be removed
+>>> r = Restaurant.objects.get(pk=1)
+>>> r.delete()
+"""
diff --git a/tests/modeltests/or_lookups/__init__.py b/tests/modeltests/or_lookups/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/or_lookups/__init__.py
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
new file mode 100644
index 0000000000..9d62a1266c
--- /dev/null
+++ b/tests/modeltests/or_lookups/models.py
@@ -0,0 +1,89 @@
+"""
+19. OR lookups
+
+To perform an OR lookup, or a lookup that combines ANDs and ORs,
+combine QuerySet objects using & and | operators.
+
+Alternatively, use positional arguments, and pass one or more expressions
+of clauses using the variable ``django.db.models.Q`` (or any object with
+a get_sql method).
+
+
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=50)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('pub_date',)
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+>>> from datetime import datetime
+>>> from django.db.models import Q
+
+>>> a1 = Article(headline='Hello', pub_date=datetime(2005, 11, 27))
+>>> a1.save()
+
+>>> a2 = Article(headline='Goodbye', pub_date=datetime(2005, 11, 28))
+>>> a2.save()
+
+>>> a3 = Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29))
+>>> a3.save()
+
+>>> Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye'))
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye'))
+[]
+
+>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__startswith='Goodbye')
+[]
+
+>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__contains='bye')
+[Hello and goodbye]
+
+>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello')
+[Hello and goodbye]
+
+>>> Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood')
+[Hello, Goodbye, Hello and goodbye]
+
+>>> Article.objects.filter(Q(pk=1) | Q(pk=2))
+[Hello, Goodbye]
+
+>>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))
+[Hello, Goodbye, Hello and goodbye]
+
+# Q arg objects are ANDed
+>>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye'))
+[Hello and goodbye]
+
+# Q arg AND order is irrelevant
+>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello')
+[Hello and goodbye]
+
+# Try some arg queries with operations other than get_list
+>>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye'))
+Hello and goodbye
+
+>>> 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': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}]
+
+>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2])
+{1: Hello}
+
+"""
diff --git a/tests/modeltests/ordering/__init__.py b/tests/modeltests/ordering/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/ordering/__init__.py
diff --git a/tests/testapp/models/ordering.py b/tests/modeltests/ordering/models.py
index 6256e4daf7..de08a75755 100644
--- a/tests/testapp/models/ordering.py
+++ b/tests/modeltests/ordering/models.py
@@ -13,12 +13,12 @@ The ordering attribute is not required. If you leave it off, ordering will be
undefined -- not random, just undefined.
"""
-from django.core import meta
+from django.db import models
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+ class Meta:
ordering = ('-pub_date', 'headline')
def __repr__(self):
@@ -27,37 +27,37 @@ class Article(meta.Model):
API_TESTS = """
# Create a couple of Articles.
>>> from datetime import datetime
->>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
+>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
>>> a1.save()
->>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
+>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
>>> a2.save()
->>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
+>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
>>> a3.save()
->>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
+>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
>>> a4.save()
-# By default, articles.get_list() orders by pub_date descending, then
+# By default, Article.objects.all() orders by pub_date descending, then
# headline ascending.
->>> articles.get_list()
+>>> Article.objects.all()
[Article 4, Article 2, Article 3, Article 1]
# Override ordering with order_by, which is in the same format as the ordering
# attribute in models.
->>> articles.get_list(order_by=['headline'])
+>>> Article.objects.order_by('headline')
[Article 1, Article 2, Article 3, Article 4]
->>> articles.get_list(order_by=['pub_date', '-headline'])
+>>> Article.objects.order_by('pub_date', '-headline')
[Article 1, Article 3, Article 2, Article 4]
-# Use the "limit" parameter to limit the results.
->>> articles.get_list(order_by=['headline'], limit=2)
+# Use the 'stop' part of slicing notation to limit the results.
+>>> Article.objects.order_by('headline')[:2]
[Article 1, Article 2]
-# Use the "offset" parameter with "limit" to offset the result list.
->>> articles.get_list(order_by=['headline'], offset=1, limit=2)
+# Use the 'stop' and 'start' parts of slicing notation to offset the result list.
+>>> Article.objects.order_by('headline')[1:3]
[Article 2, Article 3]
# Use '?' to order randomly. (We're using [...] in the output to indicate we
# don't know what order the output will be in.
->>> articles.get_list(order_by=['?'])
+>>> Article.objects.order_by('?')
[...]
"""
diff --git a/tests/modeltests/pagination/__init__.py b/tests/modeltests/pagination/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/pagination/__init__.py
diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py
new file mode 100644
index 0000000000..6525168b97
--- /dev/null
+++ b/tests/modeltests/pagination/models.py
@@ -0,0 +1,59 @@
+"""
+20. Object Pagination
+
+Django provides a framework for paginating a list of objects in a few.
+This is often useful for dividing search results or long lists of objects
+in to easily readable pages.
+
+
+"""
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100, default='Default headline')
+ pub_date = models.DateTimeField()
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+# prepare a list of objects for pagination
+>>> from datetime import datetime
+>>> for x in range(1, 10):
+... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
+... a.save()
+
+# create a basic paginator, 5 articles per page
+>>> from django.core.paginator import ObjectPaginator, InvalidPage
+>>> paginator = ObjectPaginator(Article.objects.all(), 5)
+
+# the paginator knows how many hits and pages it contains
+>>> paginator.hits
+9
+
+>>> paginator.pages
+2
+
+# get the first page (zero-based)
+>>> paginator.get_page(0)
+[Article 1, Article 2, Article 3, Article 4, Article 5]
+
+# get the second page
+>>> paginator.get_page(1)
+[Article 6, Article 7, Article 8, Article 9]
+
+# does the first page have a next or previous page?
+>>> paginator.has_next_page(0)
+True
+
+>>> paginator.has_previous_page(0)
+False
+
+# check the second page
+>>> paginator.has_next_page(1)
+False
+
+>>> paginator.has_previous_page(1)
+True
+
+"""
diff --git a/tests/modeltests/properties/__init__.py b/tests/modeltests/properties/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/properties/__init__.py
diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py
new file mode 100644
index 0000000000..2c2190e989
--- /dev/null
+++ b/tests/modeltests/properties/models.py
@@ -0,0 +1,26 @@
+"""
+22. Using properties on models
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ def _get_full_name(self):
+ return "%s %s" % (self.first_name, self.last_name)
+ full_name = property(_get_full_name)
+
+API_TESTS = """
+>>> a = Person(first_name='John', last_name='Lennon')
+>>> a.save()
+>>> a.full_name
+'John Lennon'
+
+# The "full_name" property hasn't provided a "set" method.
+>>> a.full_name = 'Paul McCartney'
+Traceback (most recent call last):
+ ...
+AttributeError: can't set attribute
+"""
diff --git a/tests/modeltests/repr/__init__.py b/tests/modeltests/repr/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/repr/__init__.py
diff --git a/tests/testapp/models/repr.py b/tests/modeltests/repr/models.py
index 3d4daf22c9..7e5b98c4a5 100644
--- a/tests/testapp/models/repr.py
+++ b/tests/modeltests/repr/models.py
@@ -8,11 +8,11 @@ because objects' representations are used throughout Django's
automatically-generated admin.
"""
-from django.core import meta
+from django.db import models
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
def __repr__(self):
return self.headline
@@ -20,7 +20,7 @@ class Article(meta.Model):
API_TESTS = """
# Create an Article.
>>> from datetime import datetime
->>> a = articles.Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
>>> a.save()
>>> repr(a)
diff --git a/tests/modeltests/reserved_names/__init__.py b/tests/modeltests/reserved_names/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/reserved_names/__init__.py
diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py
new file mode 100644
index 0000000000..51d32e22c5
--- /dev/null
+++ b/tests/modeltests/reserved_names/models.py
@@ -0,0 +1,56 @@
+"""
+18. Using SQL reserved names
+
+Need to use a reserved SQL name as a column name or table name? Need to include
+a hyphen in a column or table name? No problem. Django quotes names
+appropriately behind the scenes, so your database won't complain about
+reserved-name usage.
+"""
+
+from django.db import models
+
+class Thing(models.Model):
+ when = models.CharField(maxlength=1, primary_key=True)
+ join = models.CharField(maxlength=1)
+ like = models.CharField(maxlength=1)
+ drop = models.CharField(maxlength=1)
+ alter = models.CharField(maxlength=1)
+ having = models.CharField(maxlength=1)
+ where = models.DateField(maxlength=1)
+ has_hyphen = models.CharField(maxlength=1, db_column='has-hyphen')
+ class Meta:
+ db_table = 'select'
+
+ def __repr__(self):
+ return self.when
+
+API_TESTS = """
+>>> import datetime
+>>> day1 = datetime.date(2005, 1, 1)
+>>> day2 = datetime.date(2006, 2, 2)
+>>> t = Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where=day1, has_hyphen='h')
+>>> t.save()
+>>> print t.when
+a
+
+>>> u = Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where=day2)
+>>> u.save()
+>>> print u.when
+h
+
+>>> Thing.objects.order_by('when')
+[a, h]
+>>> v = Thing.objects.get(pk='a')
+>>> print v.join
+b
+>>> print v.where
+2005-01-01
+>>> Thing.objects.order_by('select.when')
+[a, h]
+
+>>> Thing.objects.dates('where', 'year')
+[datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)]
+
+>>> Thing.objects.filter(where__month=1)
+[a]
+"""
diff --git a/tests/modeltests/reverse_lookup/__init__.py b/tests/modeltests/reverse_lookup/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/reverse_lookup/__init__.py
diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py
new file mode 100644
index 0000000000..2fe77bdbeb
--- /dev/null
+++ b/tests/modeltests/reverse_lookup/models.py
@@ -0,0 +1,56 @@
+"""
+25. Reverse lookups
+
+This demonstrates the reverse lookup features of the database API.
+"""
+
+from django.db import models
+
+class User(models.Model):
+ name = models.CharField(maxlength=200)
+ def __repr__(self):
+ return self.name
+
+class Poll(models.Model):
+ question = models.CharField(maxlength=200)
+ creator = models.ForeignKey(User)
+ def __repr__(self):
+ return self.question
+
+class Choice(models.Model):
+ name = models.CharField(maxlength=100)
+ poll = models.ForeignKey(Poll, related_name="poll_choice")
+ related_poll = models.ForeignKey(Poll, related_name="related_choice")
+ def __repr(self):
+ return self.name
+
+API_TESTS = """
+>>> john = User(name="John Doe")
+>>> john.save()
+>>> jim = User(name="Jim Bo")
+>>> jim.save()
+>>> first_poll = Poll(question="What's the first question?", creator=john)
+>>> first_poll.save()
+>>> second_poll = Poll(question="What's the second question?", creator=jim)
+>>> second_poll.save()
+>>> new_choice = Choice(poll=first_poll, related_poll=second_poll, name="This is the answer.")
+>>> new_choice.save()
+
+>>> # Reverse lookups by field name:
+>>> User.objects.get(poll__question__exact="What's the first question?")
+John Doe
+>>> User.objects.get(poll__question__exact="What's the second question?")
+Jim Bo
+
+>>> # Reverse lookups by related_name:
+>>> Poll.objects.get(poll_choice__name__exact="This is the answer.")
+What's the first question?
+>>> Poll.objects.get(related_choice__name__exact="This is the answer.")
+What's the second question?
+
+>>> # If a related_name is given you can't use the field name instead:
+>>> Poll.objects.get(choice__name__exact="This is the answer")
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'choice' into field
+"""
diff --git a/tests/modeltests/save_delete_hooks/__init__.py b/tests/modeltests/save_delete_hooks/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/save_delete_hooks/__init__.py
diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py
new file mode 100644
index 0000000000..47748082da
--- /dev/null
+++ b/tests/modeltests/save_delete_hooks/models.py
@@ -0,0 +1,42 @@
+"""
+13. Adding hooks before/after saving and deleting
+
+To execute arbitrary code around ``save()`` and ``delete()``, just subclass
+the methods.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ first_name = models.CharField(maxlength=20)
+ last_name = models.CharField(maxlength=20)
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+ def save(self):
+ print "Before save"
+ super(Person, self).save() # Call the "real" save() method
+ print "After save"
+
+ def delete(self):
+ print "Before deletion"
+ super(Person, self).delete() # Call the "real" delete() method
+ print "After deletion"
+
+API_TESTS = """
+>>> p1 = Person(first_name='John', last_name='Smith')
+>>> p1.save()
+Before save
+After save
+
+>>> Person.objects.all()
+[John Smith]
+
+>>> p1.delete()
+Before deletion
+After deletion
+
+>>> Person.objects.all()
+[]
+"""
diff --git a/tests/modeltests/transactions/__init__.py b/tests/modeltests/transactions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/transactions/__init__.py
diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py
new file mode 100644
index 0000000000..22f38f7a0c
--- /dev/null
+++ b/tests/modeltests/transactions/models.py
@@ -0,0 +1,92 @@
+"""
+XXX. Transactions
+
+Django handles transactions in three different ways. The default is to commit
+each transaction upon a write, but you can decorate a function to get
+commit-on-success behavior. Alternatively, you can manage the transaction
+manually.
+"""
+
+from django.db import models
+
+class Reporter(models.Model):
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ email = models.EmailField()
+
+ def __repr__(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+API_TESTS = """
+>>> from django.db import connection, transaction
+
+# the default behavior is to autocommit after each save() action
+>>> def create_a_reporter_then_fail(first, last):
+... a = Reporter(first_name=first, last_name=last)
+... a.save()
+... raise Exception("I meant to do that")
+...
+>>> create_a_reporter_then_fail("Alice", "Smith")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# The object created before the exception still exists
+>>> Reporter.objects.all()
+[Alice Smith]
+
+# the autocommit decorator works exactly the same as the default behavior
+>>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail)
+>>> autocomitted_create_then_fail("Ben", "Jones")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# Same behavior as before
+>>> Reporter.objects.all()
+[Alice Smith, Ben Jones]
+
+# With the commit_on_success decorator, the transaction is only comitted if the
+# function doesn't throw an exception
+>>> committed_on_success = transaction.commit_on_success(create_a_reporter_then_fail)
+>>> committed_on_success("Carol", "Doe")
+Traceback (most recent call last):
+ ...
+Exception: I meant to do that
+
+# This time the object never got saved
+>>> Reporter.objects.all()
+[Alice Smith, Ben Jones]
+
+# If there aren't any exceptions, the data will get saved
+>>> def remove_a_reporter():
+... r = Reporter.objects.get(first_name="Alice")
+... r.delete()
+...
+>>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter)
+>>> remove_comitted_on_success()
+>>> Reporter.objects.all()
+[Ben Jones]
+
+# You can manually manage transactions if you really want to, but you
+# have to remember to commit/rollback
+>>> def manually_managed():
+... r = Reporter(first_name="Carol", last_name="Doe")
+... r.save()
+... transaction.commit()
+>>> manually_managed = transaction.commit_manually(manually_managed)
+>>> manually_managed()
+>>> Reporter.objects.all()
+[Ben Jones, Carol Doe]
+
+# If you forget, you'll get bad errors
+>>> def manually_managed_mistake():
+... r = Reporter(first_name="David", last_name="Davidson")
+... r.save()
+... # oops, I forgot to commit/rollback!
+>>> manually_managed_mistake = transaction.commit_manually(manually_managed_mistake)
+>>> manually_managed_mistake()
+Traceback (most recent call last):
+ ...
+TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK
+""" \ No newline at end of file
diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/modeltests/validation/__init__.py
diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
new file mode 100644
index 0000000000..d03fffea25
--- /dev/null
+++ b/tests/modeltests/validation/models.py
@@ -0,0 +1,147 @@
+"""
+XX. Validation
+
+Each model instance has a validate() method that returns a dictionary of
+validation errors in the instance's fields. This method has a side effect
+of converting each field to its appropriate Python data type.
+"""
+
+from django.db import models
+
+class Person(models.Model):
+ is_child = models.BooleanField()
+ name = models.CharField(maxlength=20)
+ birthdate = models.DateField()
+ favorite_moment = models.DateTimeField()
+ email = models.EmailField()
+
+ def __repr__(self):
+ return self.name
+
+API_TESTS = """
+
+>>> import datetime
+>>> valid_params = {
+... 'is_child': True,
+... 'name': 'John',
+... 'birthdate': datetime.date(2000, 5, 3),
+... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23),
+... 'email': 'john@example.com'
+... }
+>>> p = Person(**valid_params)
+>>> p.validate()
+{}
+
+>>> p = Person(**dict(valid_params, id='23'))
+>>> p.validate()
+{}
+>>> p.id
+23
+
+>>> p = Person(**dict(valid_params, id='foo'))
+>>> p.validate()
+{'id': ['This value must be an integer.']}
+
+>>> p = Person(**dict(valid_params, id=None))
+>>> p.validate()
+{}
+>>> repr(p.id)
+'None'
+
+>>> p = Person(**dict(valid_params, is_child='t'))
+>>> p.validate()
+{}
+>>> p.is_child
+True
+
+>>> p = Person(**dict(valid_params, is_child='f'))
+>>> p.validate()
+{}
+>>> p.is_child
+False
+
+>>> p = Person(**dict(valid_params, is_child=True))
+>>> p.validate()
+{}
+>>> p.is_child
+True
+
+>>> p = Person(**dict(valid_params, is_child=False))
+>>> p.validate()
+{}
+>>> p.is_child
+False
+
+>>> p = Person(**dict(valid_params, is_child='foo'))
+>>> p.validate()
+{'is_child': ['This value must be either True or False.']}
+
+>>> p = Person(**dict(valid_params, name=u'Jose'))
+>>> p.validate()
+{}
+>>> p.name
+u'Jose'
+
+>>> p = Person(**dict(valid_params, name=227))
+>>> p.validate()
+{}
+>>> p.name
+'227'
+
+>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3)))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3)))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='2000-05-03'))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='2000-5-3'))
+>>> p.validate()
+{}
+>>> p.birthdate
+datetime.date(2000, 5, 3)
+
+>>> p = Person(**dict(valid_params, birthdate='foo'))
+>>> p.validate()
+{'birthdate': ['Enter a valid date in YYYY-MM-DD format.']}
+
+>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23)))
+>>> p.validate()
+{}
+>>> p.favorite_moment
+datetime.datetime(2002, 4, 3, 13, 23)
+
+>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3)))
+>>> p.validate()
+{}
+>>> p.favorite_moment
+datetime.datetime(2002, 4, 3, 0, 0)
+
+>>> p = Person(**dict(valid_params, email='john@example.com'))
+>>> p.validate()
+{}
+>>> p.email
+'john@example.com'
+
+>>> p = Person(**dict(valid_params, email=u'john@example.com'))
+>>> p.validate()
+{}
+>>> p.email
+u'john@example.com'
+
+>>> p = Person(**dict(valid_params, email=22))
+>>> p.validate()
+{'email': ['Enter a valid e-mail address.']}
+
+"""
diff --git a/tests/othertests/dateformat.py b/tests/othertests/dateformat.py
index 3350a1f8ab..0287587b4a 100644
--- a/tests/othertests/dateformat.py
+++ b/tests/othertests/dateformat.py
@@ -1,14 +1,16 @@
-"""
+r"""
>>> format(my_birthday, '')
''
>>> format(my_birthday, 'a')
'p.m.'
>>> format(my_birthday, 'A')
'PM'
+>>> format(my_birthday, 'd')
+'08'
>>> format(my_birthday, 'j')
-'7'
+'8'
>>> format(my_birthday, 'l')
-'Saturday'
+'Sunday'
>>> format(my_birthday, 'L')
'False'
>>> format(my_birthday, 'm')
@@ -24,7 +26,7 @@
>>> format(my_birthday, 'P')
'10 p.m.'
>>> format(my_birthday, 'r')
-'Sat, 7 Jul 1979 22:00:00 +0100'
+'Sun, 8 Jul 1979 22:00:00 +0100'
>>> format(my_birthday, 's')
'00'
>>> format(my_birthday, 'S')
@@ -34,9 +36,9 @@
>>> format(my_birthday, 'T')
'CET'
>>> format(my_birthday, 'U')
-'300445200'
+'300531600'
>>> format(my_birthday, 'w')
-'6'
+'0'
>>> format(my_birthday, 'W')
'27'
>>> format(my_birthday, 'y')
@@ -44,7 +46,7 @@
>>> format(my_birthday, 'Y')
'1979'
>>> format(my_birthday, 'z')
-'188'
+'189'
>>> format(my_birthday, 'Z')
'3600'
@@ -57,8 +59,11 @@
>>> format(wintertime, 'O')
'+0100'
->>> format(my_birthday, 'Y z \\C\\E\\T')
-'1979 188 CET'
+>>> format(my_birthday, r'Y z \C\E\T')
+'1979 189 CET'
+
+>>> format(my_birthday, r'jS o\f F')
+'8th of July'
"""
from django.utils import dateformat, translation
@@ -70,6 +75,6 @@ translation.activate('en-us')
time.tzset()
-my_birthday = datetime.datetime(1979, 7, 7, 22, 00)
+my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
summertime = datetime.datetime(2005, 10, 30, 1, 00)
wintertime = datetime.datetime(2005, 10, 30, 4, 00)
diff --git a/tests/othertests/db_typecasts.py b/tests/othertests/db_typecasts.py
index 52cab666de..ffc9b34aec 100644
--- a/tests/othertests/db_typecasts.py
+++ b/tests/othertests/db_typecasts.py
@@ -1,6 +1,6 @@
-# Unit tests for django.core.db.typecasts
+# Unit tests for typecast functions in django.db.backends.util
-from django.core.db import typecasts
+from django.db.backends import util as typecasts
import datetime
TEST_CASES = {
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
index d0d5d21e58..46f2519285 100644
--- a/tests/othertests/defaultfilters.py
+++ b/tests/othertests/defaultfilters.py
@@ -1,4 +1,4 @@
-"""
+r"""
>>> floatformat(7.7)
'7.7'
>>> floatformat(7.0)
@@ -12,8 +12,8 @@
>>> floatformat(0.0)
'0'
->>> addslashes('"double quotes" and \\'single quotes\\'')
-'\\\\"double quotes\\\\" and \\\\\\'single quotes\\\\\\''
+>>> addslashes('"double quotes" and \'single quotes\'')
+'\\"double quotes\\" and \\\'single quotes\\\''
>>> capfirst('hello world')
'Hello world'
@@ -21,17 +21,17 @@
>>> fix_ampersands('Jack & Jill & Jeroboam')
'Jack &amp; Jill &amp; Jeroboam'
->>> linenumbers('line 1\\nline 2')
-'1. line 1\\n2. line 2'
+>>> linenumbers('line 1\nline 2')
+'1. line 1\n2. line 2'
->>> linenumbers('\\n'.join(['x'] * 10))
-'01. x\\n02. x\\n03. x\\n04. x\\n05. x\\n06. x\\n07. x\\n08. x\\n09. x\\n10. x'
+>>> linenumbers('\n'.join(['x'] * 10))
+'01. x\n02. x\n03. x\n04. x\n05. x\n06. x\n07. x\n08. x\n09. x\n10. x'
>>> lower('TEST')
'test'
->>> lower(u'\\xcb') # uppercase E umlaut
-u'\\xeb'
+>>> lower(u'\xcb') # uppercase E umlaut
+u'\xeb'
>>> make_list('abc')
['a', 'b', 'c']
@@ -48,7 +48,7 @@ u'\\xeb'
>>> stringformat(1, 'z')
''
->>> title('a nice title, isn\\'t it?')
+>>> title('a nice title, isn\'t it?')
"A Nice Title, Isn't It?"
@@ -68,8 +68,8 @@ u'\\xeb'
>>> upper('Mixed case input')
'MIXED CASE INPUT'
->>> upper(u'\\xeb') # lowercase e umlaut
-u'\\xcb'
+>>> upper(u'\xeb') # lowercase e umlaut
+u'\xcb'
>>> urlencode('jack & jill')
@@ -91,8 +91,8 @@ u'\\xcb'
>>> wordcount('lots of words')
3
->>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\\'m afraid', 14)
-"this is a long\\nparagraph of\\ntext that\\nreally needs\\nto be wrapped\\nI'm afraid"
+>>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14)
+"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid"
>>> ljust('test', 10)
'test '
@@ -124,7 +124,7 @@ u'\\xcb'
>>> linebreaks('line 1')
'<p>line 1</p>'
->>> linebreaks('line 1\\nline 2')
+>>> linebreaks('line 1\nline 2')
'<p>line 1<br />line 2</p>'
>>> removetags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags', 'script img')
@@ -133,19 +133,15 @@ u'\\xcb'
>>> striptags('some <b>html</b> with <script>alert("You smell")</script> disallowed <img /> tags')
'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'}]
+>>> 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'}]
->>> 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'}]
+>>> 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'}]
>>> first([0,1,2])
0
@@ -196,13 +192,13 @@ False
'aceg'
>>> unordered_list(['item 1', []])
-'\\t<li>item 1</li>'
+'\t<li>item 1</li>'
>>> unordered_list(['item 1', [['item 1.1', []]]])
-'\\t<li>item 1\\n\\t<ul>\\n\\t\\t<li>item 1.1</li>\\n\\t</ul>\\n\\t</li>'
+'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t</ul>\n\t</li>'
>>> unordered_list(['item 1', [['item 1.1', []], ['item 1.2', []]]])
-'\\t<li>item 1\\n\\t<ul>\\n\\t\\t<li>item 1.1</li>\\n\\t\\t<li>item 1.2</li>\\n\\t</ul>\\n\\t</li>'
+'\t<li>item 1\n\t<ul>\n\t\t<li>item 1.1</li>\n\t\t<li>item 1.2</li>\n\t</ul>\n\t</li>'
>>> add('1', '2')
3
@@ -228,6 +224,8 @@ False
# real testing of date() is in dateformat.py
>>> date(datetime.datetime(2005, 12, 29), "d F Y")
'29 December 2005'
+>>> date(datetime.datetime(2005, 12, 29), r'jS o\f F')
+'29th of December'
# real testing of time() is done in dateformat.py
>>> time(datetime.time(13), "h")
@@ -322,7 +320,7 @@ False
"""
-from django.core.template.defaultfilters import *
+from django.template.defaultfilters import *
import datetime
if __name__ == '__main__':
diff --git a/tests/othertests/httpwrappers.py b/tests/othertests/httpwrappers.py
index e3aa7c4e42..385c3048d9 100644
--- a/tests/othertests/httpwrappers.py
+++ b/tests/othertests/httpwrappers.py
@@ -351,7 +351,7 @@ AttributeError: This QueryDict instance is immutable
"""
-from django.utils.httpwrappers import QueryDict
+from django.http import QueryDict
if __name__ == "__main__":
import doctest
diff --git a/tests/othertests/markup.py b/tests/othertests/markup.py
index 5a8f9e1cdc..3fa5a3a883 100644
--- a/tests/othertests/markup.py
+++ b/tests/othertests/markup.py
@@ -1,6 +1,6 @@
# Quick tests for the markup templatetags (django.contrib.markup)
-from django.core.template import Template, Context, add_to_builtins
+from django.template import Template, Context, add_to_builtins
add_to_builtins('django.contrib.markup.templatetags.markup')
diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py
index bfb62c7bd1..6d8487c67a 100644
--- a/tests/othertests/templates.py
+++ b/tests/othertests/templates.py
@@ -1,11 +1,10 @@
from django.conf import settings
-# Turn TEMPLATE_DEBUG off, because tests assume that.
-settings.TEMPLATE_DEBUG = False
-from django.core import template
-from django.core.template import loader
+from django import template
+from django.template import loader
from django.utils.translation import activate, deactivate, install
+from datetime import datetime
import traceback
#################################
@@ -32,6 +31,12 @@ template.libraries['django.templatetags.testtags'] = register
# Helper objects for template tests #
#####################################
+class SomeException(Exception):
+ silent_variable_failure = True
+
+class SomeOtherException(Exception):
+ pass
+
class SomeClass:
def __init__(self):
self.otherclass = OtherClass()
@@ -42,6 +47,12 @@ class SomeClass:
def method2(self, o):
return o
+ def method3(self):
+ raise SomeException
+
+ def method4(self):
+ raise SomeOtherException
+
class OtherClass:
def method(self):
return "OtherClass.method"
@@ -133,12 +144,20 @@ TEMPLATE_TESTS = {
'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
# Default argument testing
- 'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
+ 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
- ### IF TAG ################################################################
- 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
- 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
- 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+ # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
+ 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"),
+
+ # In methods that raise an exception without a "silent_variable_attribute" set to True,
+ # the exception propogates
+ 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+
+ # Escaped backslash in argument
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+
+ # Escaped backslash using known escape char
+ 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
### COMMENT TAG ###########################################################
'comment-tag01': ("{% comment %}this is hidden{% endcomment %}hello", {}, "hello"),
@@ -149,6 +168,45 @@ TEMPLATE_TESTS = {
'comment-tag04': ("foo{% comment %} {% endblock %} {% endcomment %}", {}, "foo"),
'comment-tag05': ("foo{% comment %} {% somerandomtag %} {% endcomment %}", {}, "foo"),
+ ### CYCLE TAG #############################################################
+ #'cycleXX': ('', {}, ''),
+ 'cycle01': ('{% cycle a, %}', {}, 'a'),
+ 'cycle02': ('{% cycle a,b,c as abc %}{% cycle abc %}', {}, 'ab'),
+ 'cycle03': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}', {}, 'abc'),
+ 'cycle04': ('{% cycle a,b,c as abc %}{% cycle abc %}{% cycle abc %}{% cycle abc %}', {}, 'abca'),
+ 'cycle05': ('{% cycle %}', {}, template.TemplateSyntaxError),
+ 'cycle06': ('{% cycle a %}', {}, template.TemplateSyntaxError),
+ 'cycle07': ('{% cycle a,b,c as foo %}{% cycle bar %}', {}, template.TemplateSyntaxError),
+
+ ### EXCEPTIONS ############################################################
+
+ # Raise exception for invalid template name
+ 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for invalid template name (in variable)
+ 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for extra {% extends %} tags
+ 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
+
+ # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
+ 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
+
+ ### FILTER TAG ############################################################
+ #'filterXX': ('', {}, ''),
+ 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
+ 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
+ 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+
+ ### FIRSTOF TAG ###########################################################
+ #'firstofXX': ('', {}, ''),
+ 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
+ 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
+ 'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
+ 'firstof04': ('{% firstof a b c %}', {'a':0,'b':0,'c':3}, '3'),
+ 'firstof05': ('{% firstof a b c %}', {'a':1,'b':2,'c':3}, '1'),
+ 'firstof06': ('{% firstof %}', {}, template.TemplateSyntaxError),
+
### FOR TAG ###############################################################
'for-tag01': ("{% for val in values %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "123"),
'for-tag02': ("{% for val in values reversed %}{{ val }}{% endfor %}", {"values": [1, 2, 3]}, "321"),
@@ -157,6 +215,17 @@ TEMPLATE_TESTS = {
'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+ ### IF TAG ################################################################
+ 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
+ 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
+ 'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+
+ ### IFCHANGED TAG #########################################################
+ #'ifchangedXX': ('', {}, ''),
+ 'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
+ 'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
+ 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
+
### IFEQUAL TAG ###########################################################
'ifequal01': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 2}, ""),
'ifequal02': ("{% ifequal a b %}yes{% endifequal %}", {"a": 1, "b": 1}, "yes"),
@@ -169,20 +238,6 @@ TEMPLATE_TESTS = {
'ifequal09': ('{% ifequal a "test" %}yes{% else %}no{% endifequal %}', {}, "no"),
'ifequal10': ('{% ifequal a b %}yes{% else %}no{% endifequal %}', {}, "yes"),
- # Integers
- 'ifequal11': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal12': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {"a": 1}, "yes"),
- 'ifequal13': ('{% ifequal a 1 %}yes{% else %}no{% endifequal %}', {"a": "1"}, "no"),
- 'ifequal14': ('{% ifequal a "1" %}yes{% else %}no{% endifequal %}', {"a": 1}, "no"),
- 'ifequal15': ('{% ifequal a "1" %}yes{% else %}no{% endifequal %}', {"a": "1"}, "yes"),
-
- # Floats
- 'ifequal16': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {}, "no"),
- 'ifequal17': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {"a": 1.2}, "yes"),
- 'ifequal18': ('{% ifequal a 1.2 %}yes{% else %}no{% endifequal %}', {"a": "1.2"}, "no"),
- 'ifequal19': ('{% ifequal a "1.2" %}yes{% else %}no{% endifequal %}', {"a": 1.2}, "no"),
- 'ifequal20': ('{% ifequal a "1.2" %}yes{% else %}no{% endifequal %}', {"a": "1.2"}, "yes"),
-
### IFNOTEQUAL TAG ########################################################
'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
@@ -266,37 +321,7 @@ TEMPLATE_TESTS = {
# Three-level inheritance with {{ block.super }} from parent and grandparent
'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
- ### EXCEPTIONS ############################################################
-
- # Raise exception for invalid template name
- 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for invalid template name (in variable)
- 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for extra {% extends %} tags
- 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
-
- # Raise exception for custom tags used in child with {% load %} tag in parent, not in child
- 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
-
- 'multiline01': ("""
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """,
- {},
- """
- Hello,
- boys.
- How
- are
- you
- gentlemen.
- """),
+ ### I18N ##################################################################
# {% spaceless %} tag
'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
@@ -341,6 +366,89 @@ TEMPLATE_TESTS = {
# translation of a constant string
'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
+
+ ### MULTILINE #############################################################
+
+ 'multiline01': ("""
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """,
+ {},
+ """
+ Hello,
+ boys.
+ How
+ are
+ you
+ gentlemen.
+ """),
+
+ ### REGROUP TAG ###########################################################
+ #'regroupXX': ('', {}, ''),
+ 'regroup01': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {'data': [ {'foo':'c', 'bar':1},
+ {'foo':'d', 'bar':1},
+ {'foo':'a', 'bar':2},
+ {'foo':'b', 'bar':2},
+ {'foo':'x', 'bar':3} ]},
+ '1:cd,2:ab,3:x,'),
+
+ # Test for silent failure when target variable isn't found
+ 'regroup02': ('{% regroup data by bar as grouped %}' + \
+ '{% for group in grouped %}' + \
+ '{{ group.grouper }}:' + \
+ '{% for item in group.list %}' + \
+ '{{ item.foo }}' + \
+ '{% endfor %},' + \
+ '{% endfor %}',
+ {}, ''),
+
+ ### TEMPLATETAG TAG #######################################################
+ #'templatetagXX': ('', {}, ''),
+ 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
+ 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
+ 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
+ 'templatetag04': ('{% templatetag closevariable %}', {}, '}}'),
+ 'templatetag05': ('{% templatetag %}', {}, template.TemplateSyntaxError),
+ 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
+
+ ### WIDTHRATIO TAG ########################################################
+ #'widthratioXX': ('', {}, ''),
+ 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
+ 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
+ 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
+ 'widthratio04': ('{% widthratio a b 100 %}', {'a':50,'b':100}, '50'),
+ 'widthratio05': ('{% widthratio a b 100 %}', {'a':100,'b':100}, '100'),
+
+ # 62.5 should round to 63
+ 'widthratio06': ('{% widthratio a b 100 %}', {'a':50,'b':80}, '63'),
+
+ # 71.4 should round to 71
+ 'widthratio07': ('{% widthratio a b 100 %}', {'a':50,'b':70}, '71'),
+
+ # Raise exception if we don't have 3 args, last one an integer
+ 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
+ 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+
+ ### NOW TAG ########################################################
+ # Simple case
+ 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
+
+ # Check parsing of escaped and special characters
+ 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
+# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
+# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
}
def test_template_loader(template_name, template_dirs=None):
@@ -358,6 +466,9 @@ def run_tests(verbosity=0, standalone=False):
failed_tests = []
tests = TEMPLATE_TESTS.items()
tests.sort()
+
+ # Turn TEMPLATE_DEBUG off, because tests assume that.
+ old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
for name, vals in tests:
install()
if 'LANGUAGE_CODE' in vals[1]:
@@ -387,6 +498,8 @@ def run_tests(verbosity=0, standalone=False):
failed_tests.append(name)
loader.template_source_loaders = old_template_loaders
deactivate()
+ settings.TEMPLATE_DEBUG = old_td
+
if failed_tests and not standalone:
msg = "Template tests %s failed." % failed_tests
if not verbosity:
diff --git a/tests/runtests.py b/tests/runtests.py
index 9226361090..3f8eda6ab4 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -7,7 +7,7 @@ import os, re, sys, time, traceback
# and Django aims to work with Python 2.3+.
import doctest
-APP_NAME = 'testapp'
+MODEL_TESTS_DIR_NAME = 'modeltests'
OTHER_TESTS_DIR = "othertests"
TEST_DATABASE_NAME = 'django_test_db'
@@ -18,10 +18,10 @@ def log_error(model_name, title, description):
'description': description,
})
-MODEL_DIR = os.path.join(os.path.dirname(__file__), APP_NAME, 'models')
+MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
def get_test_models():
- return [f[:-3] for f in os.listdir(MODEL_DIR) if f.endswith('.py') and not f.startswith('__init__')]
+ return [f for f in os.listdir(MODEL_TEST_DIR) if not f.startswith('__init__') and not f.startswith('.')]
class DjangoDoctestRunner(doctest.DocTestRunner):
def __init__(self, verbosity_level, *args, **kwargs):
@@ -39,9 +39,13 @@ class DjangoDoctestRunner(doctest.DocTestRunner):
"Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
def report_unexpected_exception(self, out, test, example, exc_info):
+ from django.db import transaction
tb = ''.join(traceback.format_exception(*exc_info)[1:])
log_error(test.name, "API test raised an exception",
"Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
+ # Rollback, in case of database errors. Otherwise they'd have
+ # side effects on other tests.
+ transaction.rollback_unless_managed()
normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
@@ -68,14 +72,27 @@ class TestRunner:
def run_tests(self):
from django.conf import settings
- from django.core.db import db
- from django.core import management, meta
- # Manually set INSTALLED_APPS to point to the test app.
- settings.INSTALLED_APPS = (APP_NAME,)
+ # Manually set INSTALLED_APPS to point to the test models.
+ settings.INSTALLED_APPS = [MODEL_TESTS_DIR_NAME + '.' + a for a in get_test_models()]
+
+ # Manually set DEBUG = False.
+ settings.DEBUG = False
+
+ from django.db import connection
+ from django.core import management
+ import django.db.models
# Determine which models we're going to test.
test_models = get_test_models()
+ if 'othertests' in self.which_tests:
+ self.which_tests.remove('othertests')
+ run_othertests = True
+ if not self.which_tests:
+ test_models = []
+ else:
+ run_othertests = not self.which_tests
+
if self.which_tests:
# Only run the specified tests.
bad_models = [m for m in self.which_tests if m not in test_models]
@@ -96,9 +113,9 @@ class TestRunner:
# Create the test database and connect to it. We need autocommit()
# because PostgreSQL doesn't allow CREATE DATABASE statements
# within transactions.
- cursor = db.cursor()
+ cursor = connection.cursor()
try:
- db.connection.autocommit(1)
+ connection.connection.autocommit(1)
except AttributeError:
pass
self.output(1, "Creating test database")
@@ -113,43 +130,62 @@ class TestRunner:
else:
print "Tests cancelled."
return
- db.close()
+ connection.close()
old_database_name = settings.DATABASE_NAME
settings.DATABASE_NAME = TEST_DATABASE_NAME
# Initialize the test database.
- cursor = db.cursor()
- self.output(1, "Initializing test database")
- management.init()
+ cursor = connection.cursor()
# Run the tests for each test model.
self.output(1, "Running app tests")
for model_name in test_models:
self.output(1, "%s model: Importing" % model_name)
try:
- mod = meta.get_app(model_name)
+ # TODO: Abstract this into a meta.get_app() replacement?
+ mod = __import__(MODEL_TESTS_DIR_NAME + '.' + model_name + '.models', '', '', [''])
except Exception, e:
log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
continue
- self.output(1, "%s model: Installing" % model_name)
- management.install(mod)
- # Run the API tests.
- p = doctest.DocTestParser()
- test_namespace = dict([(m._meta.module_name, getattr(mod, m._meta.module_name)) for m in mod._MODELS])
- dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
- # Manually set verbose=False, because "-v" command-line parameter
- # has side effects on doctest TestRunner class.
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
- self.output(1, "%s model: Running tests" % model_name)
- try:
+ if not getattr(mod, 'error_log', None):
+ # Model is not marked as an invalid model
+ self.output(1, "%s model: Installing" % model_name)
+ management.install(mod)
+
+ # Run the API tests.
+ p = doctest.DocTestParser()
+ test_namespace = dict([(m._meta.object_name, m) \
+ for m in django.db.models.get_models(mod)])
+ dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
+ # Manually set verbose=False, because "-v" command-line parameter
+ # has side effects on doctest TestRunner class.
+ runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
+ self.output(1, "%s model: Running tests" % model_name)
runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- finally:
- # Rollback, in case of database errors. Otherwise they'd have
- # side effects on other tests.
- db.rollback()
+ else:
+ # Check that model known to be invalid is invalid for the right reasons.
+ self.output(1, "%s model: Validating" % model_name)
+
+ from cStringIO import StringIO
+ s = StringIO()
+ count = management.get_validation_errors(s, mod)
+ s.seek(0)
+ error_log = s.read()
+ actual = error_log.split('\n')
+ expected = mod.error_log.split('\n')
+
+ unexpected = [err for err in actual if err not in expected]
+ missing = [err for err in expected if err not in actual]
+
+ if unexpected or missing:
+ unexpected_log = '\n'.join(unexpected)
+ missing_log = '\n'.join(missing)
+ log_error(model_name,
+ "Validator found %d validation errors, %d expected" % (count, len(expected) - 1),
+ "Missing errors:\n%s\n\nUnexpected errors:\n%s" % (missing_log, unexpected_log))
- if not self.which_tests:
+ if run_othertests:
# Run the non-model tests in the other tests dir
self.output(1, "Running other tests")
other_tests_dir = os.path.join(os.path.dirname(__file__), OTHER_TESTS_DIR)
@@ -180,12 +216,12 @@ class TestRunner:
# to do so, because it's not allowed to delete a database while being
# connected to it.
if settings.DATABASE_ENGINE != "sqlite3":
- db.close()
+ connection.close()
settings.DATABASE_NAME = old_database_name
- cursor = db.cursor()
+ cursor = connection.cursor()
self.output(1, "Deleting test database")
try:
- db.connection.autocommit(1)
+ connection.connection.autocommit(1)
except AttributeError:
pass
else:
diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py
deleted file mode 100644
index a5a41035d6..0000000000
--- a/tests/testapp/models/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-__all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many',
- 'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one',
- 'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk',
- 'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names',
- 'or_lookups', 'm2m_multiple']
diff --git a/tests/testapp/models/basic.py b/tests/testapp/models/basic.py
deleted file mode 100644
index 7261b8783f..0000000000
--- a/tests/testapp/models/basic.py
+++ /dev/null
@@ -1,204 +0,0 @@
-"""
-1. Bare-bones model
-
-This is a basic model with only two non-primary-key fields.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100, default='Default headline')
- pub_date = meta.DateTimeField()
-
-API_TESTS = """
-# No articles are in the system yet.
->>> articles.get_list()
-[]
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(id=None, headline='Area man programs in Python',
-... pub_date=datetime(2005, 7, 28))
-
-# Save it into the database. You have to call save() explicitly.
->>> a.save()
-
-# Now it has an ID. Note it's a long integer, as designated by the trailing "L".
->>> a.id
-1L
-
-# Access database columns via Python attributes.
->>> a.headline
-'Area man programs in Python'
->>> a.pub_date
-datetime.datetime(2005, 7, 28, 0, 0)
-
-# Change values by changing the attributes, then calling save().
->>> a.headline = 'Area woman programs in Python'
->>> a.save()
-
-# get_list() displays all the articles in the database. Note that the article
-# is represented by "<Article object>", because we haven't given the Article
-# model a __repr__() method.
->>> articles.get_list()
-[<Article object>]
-
-# Django provides a rich database lookup API that's entirely driven by
-# keyword arguments.
->>> articles.get_object(id__exact=1)
-<Article object>
->>> articles.get_object(headline__startswith='Area woman')
-<Article object>
->>> articles.get_object(pub_date__year=2005)
-<Article object>
->>> articles.get_object(pub_date__year=2005, pub_date__month=7)
-<Article object>
->>> articles.get_object(pub_date__year=2005, pub_date__month=7, pub_date__day=28)
-<Article object>
-
->>> articles.get_list(pub_date__year=2005)
-[<Article object>]
->>> articles.get_list(pub_date__year=2004)
-[]
->>> articles.get_list(pub_date__year=2005, pub_date__month=7)
-[<Article object>]
-
-# Django raises an ArticleDoesNotExist exception for get_object()
->>> articles.get_object(id__exact=2)
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for {'order_by': (), 'id__exact': 2}
-
->>> articles.get_object(pub_date__year=2005, pub_date__month=8)
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for ...
-
-# Lookup by a primary key is the most common case, so Django provides a
-# shortcut for primary-key exact lookups.
-# The following is identical to articles.get_object(id__exact=1).
->>> articles.get_object(pk=1)
-<Article object>
-
-# Model instances of the same type and same ID are considered equal.
->>> a = articles.get_object(pk=1)
->>> b = articles.get_object(pk=1)
->>> a == b
-True
-
-# You can initialize a model instance using positional arguments, which should
-# match the field order as defined in the model...
->>> a2 = articles.Article(None, 'Second article', datetime(2005, 7, 29))
->>> a2.save()
->>> a2.id
-2L
->>> a2.headline
-'Second article'
->>> a2.pub_date
-datetime.datetime(2005, 7, 29, 0, 0)
-
-# ...or, you can use keyword arguments.
->>> a3 = articles.Article(id=None, headline='Third article',
-... pub_date=datetime(2005, 7, 30))
->>> a3.save()
->>> a3.id
-3L
->>> a3.headline
-'Third article'
->>> a3.pub_date
-datetime.datetime(2005, 7, 30, 0, 0)
-
-# You can also mix and match position and keyword arguments, but be sure not to
-# duplicate field information.
->>> a4 = articles.Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31))
->>> a4.save()
->>> a4.headline
-'Fourth article'
-
-# Don't use invalid keyword arguments.
->>> a5 = articles.Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar')
-Traceback (most recent call last):
- ...
-TypeError: 'foo' is an invalid keyword argument for this function
-
-# You can leave off the ID.
->>> a5 = articles.Article(headline='Article 6', pub_date=datetime(2005, 7, 31))
->>> a5.save()
->>> a5.id
-5L
->>> a5.headline
-'Article 6'
-
-# If you leave off a field with "default" set, Django will use the default.
->>> a6 = articles.Article(pub_date=datetime(2005, 7, 31))
->>> a6.save()
->>> a6.headline
-'Default headline'
-
-# For DateTimeFields, Django saves as much precision (in seconds) as you
-# give it.
->>> a7 = articles.Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30))
->>> a7.save()
->>> articles.get_object(id__exact=7).pub_date
-datetime.datetime(2005, 7, 31, 12, 30)
-
->>> a8 = articles.Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45))
->>> a8.save()
->>> articles.get_object(id__exact=8).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
->>> a8.id
-8L
-
-# Saving an object again shouldn't create a new object -- it just saves the old one.
->>> a8.save()
->>> a8.id
-8L
->>> a8.headline = 'Updated article 8'
->>> a8.save()
->>> a8.id
-8L
-
->>> a7 == a8
-False
->>> a8 == articles.get_object(id__exact=8)
-True
->>> a7 != a8
-True
->>> articles.get_object(id__exact=8) != articles.get_object(id__exact=7)
-True
->>> articles.get_object(id__exact=8) == articles.get_object(id__exact=7)
-False
-"""
-
-from django.conf import settings
-
-building_docs = getattr(settings, 'BUILDING_DOCS', False)
-
-if building_docs or settings.DATABASE_ENGINE == 'postgresql':
- API_TESTS += """
-# In PostgreSQL, microsecond-level precision is available.
->>> a9 = articles.Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
->>> a9.save()
->>> articles.get_object(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45, 180)
-"""
-
-if building_docs or settings.DATABASE_ENGINE == 'mysql':
- API_TESTS += """
-# In MySQL, microsecond-level precision isn't available. You'll lose
-# microsecond-level precision once the data is saved.
->>> a9 = articles.Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180))
->>> a9.save()
->>> articles.get_object(id__exact=9).pub_date
-datetime.datetime(2005, 7, 31, 12, 30, 45)
-"""
-
-API_TESTS += """
-
-# You can manually specify the primary key when creating a new object
->>> a101 = articles.Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45))
->>> a101.save()
->>> a101 = articles.get_object(pk=101)
->>> a101.headline
-'Article 101'
-"""
diff --git a/tests/testapp/models/custom_methods.py b/tests/testapp/models/custom_methods.py
deleted file mode 100644
index 4f175752b4..0000000000
--- a/tests/testapp/models/custom_methods.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-3. Giving models custom methods and custom module-level functions
-
-Any method you add to a model will be available to instances.
-
-Custom methods have the same namespace as if the model class were defined
-in the dynamically-generated module. That is, methods can access
-``get_list()``, ``get_object()``, ``AddManipulator``, and all other
-module-level objects.
-
-Also, custom methods have access to a few commonly-used objects for
-convenience:
-
- * The ``datetime`` module from Python's standard library.
- * The ``db`` object from ``django.core.db``. This represents the database
- connection, so you can do custom queries via a cursor object.
-
-If your model method starts with "_module_", it'll be a module-level function
-instead of a method. Otherwise, custom module-level functions have the same
-namespace as custom methods.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
-
- def __repr__(self):
- return self.headline
-
- def was_published_today(self):
- return self.pub_date == datetime.date.today()
-
- def get_articles_from_same_day_1(self):
- return get_list(id__ne=self.id, pub_date__exact=self.pub_date)
-
- def get_articles_from_same_day_2(self):
- """
- Verbose version of get_articles_from_same_day_1, which does a custom
- database query for the sake of demonstration.
- """
- cursor = db.cursor()
- cursor.execute("""
- SELECT id, headline, pub_date
- FROM custom_methods_articles
- WHERE pub_date = %s
- AND id != %s""", [str(self.pub_date), self.id])
- # The asterisk in "Article(*row)" tells Python to expand the list into
- # positional arguments to Article().
- return [Article(*row) for row in cursor.fetchall()]
-
-API_TESTS = """
-# Create a couple of Articles.
->>> from datetime import date
->>> a = articles.Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27))
->>> a.save()
->>> b = articles.Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27))
->>> b.save()
-
-# Test the custom methods.
->>> a.was_published_today()
-False
->>> a.get_articles_from_same_day_1()
-[Beatles reunite]
->>> a.get_articles_from_same_day_2()
-[Beatles reunite]
->>> b.get_articles_from_same_day_1()
-[Area man programs in Python]
->>> b.get_articles_from_same_day_2()
-[Area man programs in Python]
-"""
diff --git a/tests/testapp/models/custom_pk.py b/tests/testapp/models/custom_pk.py
deleted file mode 100644
index 24041d64cd..0000000000
--- a/tests/testapp/models/custom_pk.py
+++ /dev/null
@@ -1,69 +0,0 @@
-"""
-14. Using a custom primary key
-
-By default, Django adds an ``"id"`` field to each model. But you can override
-this behavior by explicitly adding ``primary_key=True`` to a field.
-"""
-
-from django.core import meta
-
-class Employee(meta.Model):
- employee_code = meta.CharField(maxlength=10, primary_key=True)
- first_name = meta.CharField(maxlength=20)
- last_name = meta.CharField(maxlength=20)
- class META:
- ordering = ('last_name', 'first_name')
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Business(meta.Model):
- name = meta.CharField(maxlength=20, primary_key=True)
- employees = meta.ManyToManyField(Employee)
- class META:
- verbose_name_plural = 'businesses'
- module_name = 'businesses'
-
- def __repr__(self):
- return self.name
-
-API_TESTS = """
->>> dan = employees.Employee(employee_code='ABC123', first_name='Dan', last_name='Jones')
->>> dan.save()
->>> employees.get_list()
-[Dan Jones]
-
->>> fran = employees.Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones')
->>> fran.save()
->>> employees.get_list()
-[Fran Bones, Dan Jones]
-
->>> employees.get_object(pk='ABC123')
-Dan Jones
->>> employees.get_object(pk='XYZ456')
-Fran Bones
->>> employees.get_object(pk='foo')
-Traceback (most recent call last):
- ...
-EmployeeDoesNotExist: Employee does not exist for {'pk': 'foo', 'order_by': ()}
-
-# Fran got married and changed her last name.
->>> fran = employees.get_object(pk='XYZ456')
->>> fran.last_name = 'Jones'
->>> fran.save()
->>> employees.get_list(last_name__exact='Jones')
-[Dan Jones, Fran Jones]
->>> employees.get_in_bulk(['ABC123', 'XYZ456'])
-{'XYZ456': Fran Jones, 'ABC123': Dan Jones}
-
->>> b = businesses.Business(name='Sears')
->>> b.save()
->>> b.set_employees([dan.employee_code, fran.employee_code])
-True
->>> b.get_employee_list()
-[Dan Jones, Fran Jones]
->>> fran.get_business_list()
-[Sears]
->>> businesses.get_in_bulk(['Sears'])
-{'Sears': Sears}
-"""
diff --git a/tests/testapp/models/get_latest.py b/tests/testapp/models/get_latest.py
deleted file mode 100644
index 86697e85a7..0000000000
--- a/tests/testapp/models/get_latest.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-8. get_latest_by
-
-Models can have a ``get_latest_by`` attribute, which should be set to the name
-of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's
-module will get a ``get_latest()`` function, which will return the latest
-object in the database according to that field. "Latest" means "having the
-date farthest into the future."
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
- get_latest_by = 'pub_date'
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist.
->>> articles.get_latest()
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for {'order_by': ('-pub_date',), 'limit': 1}
-
-# Create a couple of Articles.
->>> from datetime import datetime
->>> a1 = articles.Article(id=None, headline='Article 1', pub_date=datetime(2005, 7, 26))
->>> a1.save()
->>> a2 = articles.Article(id=None, headline='Article 2', pub_date=datetime(2005, 7, 27))
->>> a2.save()
->>> a3 = articles.Article(id=None, headline='Article 3', pub_date=datetime(2005, 7, 27))
->>> a3.save()
->>> a4 = articles.Article(id=None, headline='Article 4', pub_date=datetime(2005, 7, 28))
->>> a4.save()
-
-# Get the latest Article.
->>> articles.get_latest()
-Article 4
-"""
diff --git a/tests/testapp/models/lookup.py b/tests/testapp/models/lookup.py
deleted file mode 100644
index 03f5c7ff71..0000000000
--- a/tests/testapp/models/lookup.py
+++ /dev/null
@@ -1,153 +0,0 @@
-"""
-7. The lookup API
-
-This demonstrates features of the database API.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateTimeField()
- class META:
- ordering = ('-pub_date', 'headline')
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a couple of Articles.
->>> from datetime import datetime
->>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26))
->>> a1.save()
->>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27))
->>> a2.save()
->>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27))
->>> a3.save()
->>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28))
->>> a4.save()
->>> a5 = articles.Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0))
->>> a5.save()
->>> a6 = articles.Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0))
->>> a6.save()
->>> a7 = articles.Article(headline='Article 7', pub_date=datetime(2005, 7, 27))
->>> a7.save()
-
-# get_iterator() is just like get_list(), but it's a generator.
->>> for a in articles.get_iterator():
-... print a.headline
-Article 5
-Article 6
-Article 4
-Article 2
-Article 3
-Article 7
-Article 1
-
-# get_iterator() takes the same lookup arguments as get_list().
->>> for a in articles.get_iterator(headline__endswith='4'):
-... print a.headline
-Article 4
-
-# get_count() returns the number of objects matching search criteria.
->>> articles.get_count()
-7L
->>> articles.get_count(pub_date__exact=datetime(2005, 7, 27))
-3L
->>> articles.get_count(headline__startswith='Blah blah')
-0L
-
-# get_in_bulk() takes a list of IDs and returns a dictionary mapping IDs
-# to objects.
->>> articles.get_in_bulk([1, 2])
-{1: Article 1, 2: Article 2}
->>> articles.get_in_bulk([3])
-{3: Article 3}
->>> articles.get_in_bulk([1000])
-{}
->>> articles.get_in_bulk([])
-Traceback (most recent call last):
- ...
-AssertionError: get_in_bulk() cannot be passed an empty list.
-
-# get_values() is just like get_list(), except it returns a list of
-# dictionaries instead of object instances -- and you can specify which fields
-# you want to retrieve.
->>> articles.get_values(fields=['headline'])
-[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}]
->>> articles.get_values(pub_date__exact=datetime(2005, 7, 27), fields=['id'])
-[{'id': 2}, {'id': 3}, {'id': 7}]
->>> articles.get_values(fields=['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
-
-# get_values_iterator() is just like get_values(), but it's a generator.
->>> for d in articles.get_values_iterator(fields=['id', 'headline']):
-... i = d.items()
-... i.sort()
-... i
-[('headline', 'Article 5'), ('id', 5)]
-[('headline', 'Article 6'), ('id', 6)]
-[('headline', 'Article 4'), ('id', 4)]
-[('headline', 'Article 2'), ('id', 2)]
-[('headline', 'Article 3'), ('id', 3)]
-[('headline', 'Article 7'), ('id', 7)]
-[('headline', 'Article 1'), ('id', 1)]
-
-# 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 2
->>> a2.get_next_by_pub_date()
-Article 3
->>> a3.get_next_by_pub_date()
-Article 7
->>> a4.get_next_by_pub_date()
-Article 6
->>> a5.get_next_by_pub_date()
-Traceback (most recent call last):
- ...
-ArticleDoesNotExist: Article does not exist for ...
->>> a6.get_next_by_pub_date()
-Article 5
->>> a7.get_next_by_pub_date()
-Article 4
-
->>> a7.get_previous_by_pub_date()
-Article 3
->>> a6.get_previous_by_pub_date()
-Article 4
->>> a5.get_previous_by_pub_date()
-Article 6
->>> a4.get_previous_by_pub_date()
-Article 7
->>> a3.get_previous_by_pub_date()
-Article 2
->>> a2.get_previous_by_pub_date()
-Article 1
-
-# Every DateField and DateTimeField give their model module a get_FOO_list
-# function.
->>> articles.get_pub_date_list('year')
-[datetime.datetime(2005, 1, 1, 0, 0)]
->>> articles.get_pub_date_list('month')
-[datetime.datetime(2005, 7, 1, 0, 0), datetime.datetime(2005, 8, 1, 0, 0)]
->>> articles.get_pub_date_list('day')
-[datetime.datetime(2005, 7, 26, 0, 0), datetime.datetime(2005, 7, 27, 0, 0), datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 8, 1, 0, 0)]
-
-# Underscores and percent signs have special meaning in the underlying
-# database library, but Django handles the quoting of them automatically.
->>> a8 = articles.Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20))
->>> a8.save()
->>> articles.get_list(headline__startswith='Article')
-[Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
->>> articles.get_list(headline__startswith='Article_')
-[Article_ with underscore]
->>> a9 = articles.Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21))
->>> a9.save()
->>> articles.get_list(headline__startswith='Article')
-[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1]
->>> articles.get_list(headline__startswith='Article%')
-[Article% with percent sign]
-"""
diff --git a/tests/testapp/models/m2m_intermediary.py b/tests/testapp/models/m2m_intermediary.py
deleted file mode 100644
index 2a20072e03..0000000000
--- a/tests/testapp/models/m2m_intermediary.py
+++ /dev/null
@@ -1,68 +0,0 @@
-"""
-9. Many-to-many relationships via an intermediary table
-
-For many-to-many relationships that need extra fields on the intermediary
-table, use an intermediary model.
-
-In this example, an ``Article`` can have multiple ``Reporter``s, and each
-``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field,
-which specifies the ``Reporter``'s position for the given article (e.g. "Staff
-writer").
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- first_name = meta.CharField(maxlength=30)
- last_name = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
-
- def __repr__(self):
- return self.headline
-
-class Writer(meta.Model):
- reporter = meta.ForeignKey(Reporter)
- article = meta.ForeignKey(Article)
- position = meta.CharField(maxlength=100)
-
- def __repr__(self):
- return '%r (%s)' % (self.get_reporter(), self.position)
-
-API_TESTS = """
-# Create a few Reporters.
->>> r1 = reporters.Reporter(first_name='John', last_name='Smith')
->>> r1.save()
->>> r2 = reporters.Reporter(first_name='Jane', last_name='Doe')
->>> r2.save()
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(headline='This is a test', pub_date=datetime(2005, 7, 27))
->>> a.save()
-
-# Create a few Writers.
->>> w1 = writers.Writer(reporter=r1, article=a, position='Main writer')
->>> w1.save()
->>> w2 = writers.Writer(reporter=r2, article=a, position='Contributor')
->>> w2.save()
-
-# Play around with the API.
->>> a.get_writer_list(order_by=['-position'], select_related=True)
-[John Smith (Main writer), Jane Doe (Contributor)]
->>> w1.get_reporter()
-John Smith
->>> w2.get_reporter()
-Jane Doe
->>> w1.get_article()
-This is a test
->>> w2.get_article()
-This is a test
->>> r1.get_writer_list()
-[John Smith (Main writer)]
-"""
diff --git a/tests/testapp/models/m2m_multiple.py b/tests/testapp/models/m2m_multiple.py
deleted file mode 100644
index d8793acb73..0000000000
--- a/tests/testapp/models/m2m_multiple.py
+++ /dev/null
@@ -1,99 +0,0 @@
-"""
-20. Multiple many-to-many relationships between the same two tables
-
-In this example, an Article can have many Categories (as "primary") and many
-Categories (as "secondary").
-
-Set ``related_name`` to designate what the reverse relationship is called.
-
-Set ``singular`` to designate what the category object is called. This is
-required if a model has multiple ``ManyToManyFields`` to the same object.
-"""
-
-from django.core import meta
-
-class Category(meta.Model):
- name = meta.CharField(maxlength=20)
- class META:
- module_name = 'categories'
- ordering = ('name',)
-
- def __repr__(self):
- return self.name
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=50)
- pub_date = meta.DateTimeField()
- primary_categories = meta.ManyToManyField(Category,
- singular='primary_category', related_name='primary_article')
- secondary_categories = meta.ManyToManyField(Category,
- singular='secondary_category', related_name='secondary_article')
- class META:
- ordering = ('pub_date',)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
->>> from datetime import datetime
-
->>> c1 = categories.Category(name='Sports')
->>> c1.save()
->>> c2 = categories.Category(name='News')
->>> c2.save()
->>> c3 = categories.Category(name='Crime')
->>> c3.save()
->>> c4 = categories.Category(name='Life')
->>> c4.save()
-
->>> a1 = articles.Article(headline='Area man steals', pub_date=datetime(2005, 11, 27))
->>> a1.save()
->>> a1.set_primary_categories([c2.id, c3.id])
-True
->>> a1.set_secondary_categories([c4.id])
-True
-
->>> a2 = articles.Article(headline='Area man runs', pub_date=datetime(2005, 11, 28))
->>> a2.save()
->>> a2.set_primary_categories([c1.id, c2.id])
-True
->>> a2.set_secondary_categories([c4.id])
-True
-
-# The "primary_category" here comes from the "singular" parameter. If we hadn't
-# specified the "singular" parameter, Django would just use "category", which
-# would cause a conflict because the "primary_categories" and
-# "secondary_categories" fields both relate to Category.
->>> a1.get_primary_category_list()
-[Crime, News]
-
-# Ditto for the "primary_category" here.
->>> a2.get_primary_category_list()
-[News, Sports]
-
-# Ditto for the "secondary_category" here.
->>> a1.get_secondary_category_list()
-[Life]
-
-# Ditto for the "secondary_category" here.
->>> a2.get_secondary_category_list()
-[Life]
-
-
->>> c1.get_primary_article_list()
-[Area man runs]
->>> c1.get_secondary_article_list()
-[]
->>> c2.get_primary_article_list()
-[Area man steals, Area man runs]
->>> c2.get_secondary_article_list()
-[]
->>> c3.get_primary_article_list()
-[Area man steals]
->>> c3.get_secondary_article_list()
-[]
->>> c4.get_primary_article_list()
-[]
->>> c4.get_secondary_article_list()
-[Area man steals, Area man runs]
-"""
diff --git a/tests/testapp/models/m2o_recursive.py b/tests/testapp/models/m2o_recursive.py
deleted file mode 100644
index 27d13b4e7e..0000000000
--- a/tests/testapp/models/m2o_recursive.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-11. Relating an object to itself, many-to-one
-
-To define a many-to-one relationship between a model and itself, use
-``ForeignKey('self')``.
-
-In this example, a ``Category`` is related to itself. That is, each
-``Category`` has a parent ``Category``.
-
-Set ``related_name`` to designate what the reverse relationship is called.
-"""
-
-from django.core import meta
-
-class Category(meta.Model):
- name = meta.CharField(maxlength=20)
- parent = meta.ForeignKey('self', null=True, related_name='child')
- class META:
- module_name = 'categories'
-
- def __repr__(self):
- return self.name
-
-API_TESTS = """
-# Create a few Category objects.
->>> r = categories.Category(id=None, name='Root category', parent=None)
->>> r.save()
->>> c = categories.Category(id=None, name='Child category', parent=r)
->>> c.save()
-
->>> r.get_child_list()
-[Child category]
->>> r.get_child(name__startswith='Child')
-Child category
->>> r.get_parent()
-Traceback (most recent call last):
- ...
-CategoryDoesNotExist
-
->>> c.get_child_list()
-[]
->>> c.get_parent()
-Root category
-"""
diff --git a/tests/testapp/models/m2o_recursive2.py b/tests/testapp/models/m2o_recursive2.py
deleted file mode 100644
index 52aa0f8b69..0000000000
--- a/tests/testapp/models/m2o_recursive2.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-12. Relating a model to another model more than once
-
-In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of
-which are other ``Person`` objects.
-
-Set ``related_name`` to designate what the reverse relationship is called.
-"""
-
-from django.core import meta
-
-class Person(meta.Model):
- full_name = meta.CharField(maxlength=20)
- mother = meta.ForeignKey('self', null=True, related_name='mothers_child')
- father = meta.ForeignKey('self', null=True, related_name='fathers_child')
-
- def __repr__(self):
- return self.full_name
-
-API_TESTS = """
-# Create two Person objects -- the mom and dad in our family.
->>> dad = persons.Person(full_name='John Smith Senior', mother=None, father=None)
->>> dad.save()
->>> mom = persons.Person(full_name='Jane Smith', mother=None, father=None)
->>> mom.save()
-
-# Give mom and dad a kid.
->>> kid = persons.Person(full_name='John Smith Junior', mother=mom, father=dad)
->>> kid.save()
-
->>> kid.get_mother()
-Jane Smith
->>> kid.get_father()
-John Smith Senior
->>> dad.get_fathers_child_list()
-[John Smith Junior]
->>> mom.get_mothers_child_list()
-[John Smith Junior]
->>> kid.get_mothers_child_list()
-[]
->>> kid.get_fathers_child_list()
-[]
-"""
diff --git a/tests/testapp/models/many_to_many.py b/tests/testapp/models/many_to_many.py
deleted file mode 100644
index 91addafe9b..0000000000
--- a/tests/testapp/models/many_to_many.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""
-5. Many-to-many relationships
-
-To define a many-to-many relationship, use ManyToManyField().
-
-In this example, an article can be published in multiple publications,
-and a publication has multiple articles.
-"""
-
-from django.core import meta
-
-class Publication(meta.Model):
- title = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return self.title
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- publications = meta.ManyToManyField(Publication)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a couple of Publications.
->>> p1 = publications.Publication(id=None, title='The Python Journal')
->>> p1.save()
->>> p2 = publications.Publication(id=None, title='Science News')
->>> p2.save()
-
-# Create an Article.
->>> a1 = articles.Article(id=None, headline='Django lets you build Web apps easily')
->>> a1.save()
-
-# Associate the Article with one Publication. set_publications() returns a
-# boolean, representing whether any records were added or deleted.
->>> a1.set_publications([p1.id])
-True
-
-# If we set it again, it'll return False, because the list of Publications
-# hasn't changed.
->>> a1.set_publications([p1.id])
-False
-
-# Create another Article, and set it to appear in both Publications.
->>> a2 = articles.Article(id=None, headline='NASA uses Python')
->>> a2.save()
->>> a2.set_publications([p1.id, p2.id])
-True
->>> a2.set_publications([p1.id])
-True
->>> a2.set_publications([p1.id, p2.id])
-True
-
-# Article objects have access to their related Publication objects.
->>> a1.get_publication_list()
-[The Python Journal]
->>> a2.get_publication_list()
-[The Python Journal, Science News]
-
-# Publication objects have access to their related Article objects.
->>> p2.get_article_list()
-[NASA uses Python]
->>> p1.get_article_list(order_by=['headline'])
-[Django lets you build Web apps easily, NASA uses Python]
-
-# If we delete a Publication, its Articles won't be able to access it.
->>> p1.delete()
->>> publications.get_list()
-[Science News]
->>> a1 = articles.get_object(pk=1)
->>> a1.get_publication_list()
-[]
-
-# If we delete an Article, its Publications won't be able to access it.
->>> a2.delete()
->>> articles.get_list()
-[Django lets you build Web apps easily]
->>> p1.get_article_list(order_by=['headline'])
-[Django lets you build Web apps easily]
-"""
diff --git a/tests/testapp/models/many_to_one.py b/tests/testapp/models/many_to_one.py
deleted file mode 100644
index 37828b6d82..0000000000
--- a/tests/testapp/models/many_to_one.py
+++ /dev/null
@@ -1,98 +0,0 @@
-"""
-4. Many-to-one relationships
-
-To define a many-to-one relationship, use ``ForeignKey()`` .
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- first_name = meta.CharField(maxlength=30)
- last_name = meta.CharField(maxlength=30)
- email = meta.EmailField()
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- pub_date = meta.DateField()
- reporter = meta.ForeignKey(Reporter)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a Reporter.
->>> r = reporters.Reporter(first_name='John', last_name='Smith', email='john@example.com')
->>> r.save()
-
-# Create an Article.
->>> from datetime import datetime
->>> a = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r)
->>> a.save()
-
->>> a.reporter_id
-1
-
->>> a.get_reporter()
-John Smith
-
-# Article objects have access to their related Reporter objects.
->>> r = a.get_reporter()
->>> r.first_name, r.last_name
-('John', 'Smith')
-
-# Create an Article via the Reporter object.
->>> new_article = r.add_article(headline="John's second story", pub_date=datetime(2005, 7, 29))
->>> new_article
-John's second story
->>> new_article.reporter_id
-1
-
-# Reporter objects have access to their related Article objects.
->>> r.get_article_list(order_by=['pub_date'])
-[This is a test, John's second story]
-
->>> r.get_article(headline__startswith='This')
-This is a test
-
->>> r.get_article_count()
-2
-
-# The API automatically follows relationships as far as you need.
-# Use double underscores to separate relationships.
-# This works as many levels deep as you want. There's no limit.
-# Find all Articles for any Reporter whose first name is "John".
->>> articles.get_list(reporter__first_name__exact='John', order_by=['pub_date'])
-[This is a test, John's second story]
-
-# Find all Articles for the Reporter whose ID is 1.
->>> articles.get_list(reporter__id__exact=1, order_by=['pub_date'])
-[This is a test, John's second story]
-
-# Note you need two underscores between "reporter" and "id" -- not one.
->>> articles.get_list(reporter_id__exact=1)
-Traceback (most recent call last):
- ...
-TypeError: got unexpected keyword argument 'reporter_id__exact'
-
-# "pk" shortcut syntax works in a related context, too.
->>> articles.get_list(reporter__pk=1, order_by=['pub_date'])
-[This is a test, John's second story]
-
-# You can also instantiate an Article by passing
-# the Reporter's ID instead of a Reporter object.
->>> a3 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id)
->>> a3.save()
->>> a3.reporter_id
-1
->>> a3.get_reporter()
-John Smith
-
-# Similarly, the reporter ID can be a string.
->>> a4 = articles.Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1")
->>> a4.save()
->>> a4.get_reporter()
-John Smith
-"""
diff --git a/tests/testapp/models/many_to_one_null.py b/tests/testapp/models/many_to_one_null.py
deleted file mode 100644
index c3c92601f7..0000000000
--- a/tests/testapp/models/many_to_one_null.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-16. Many-to-one relationships that can be null
-
-To define a many-to-one relationship that can have a null foreign key, use
-``ForeignKey()`` with ``null=True`` .
-"""
-
-from django.core import meta
-
-class Reporter(meta.Model):
- name = meta.CharField(maxlength=30)
-
- def __repr__(self):
- return self.name
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=100)
- reporter = meta.ForeignKey(Reporter, null=True)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
-# Create a Reporter.
->>> r = reporters.Reporter(name='John Smith')
->>> r.save()
-
-# Create an Article.
->>> a = articles.Article(headline="First", reporter=r)
->>> a.save()
-
->>> a.reporter_id
-1
-
->>> a.get_reporter()
-John Smith
-
-# Article objects have access to their related Reporter objects.
->>> r = a.get_reporter()
-
-# Create an Article via the Reporter object.
->>> a2 = r.add_article(headline="Second")
->>> a2
-Second
->>> a2.reporter_id
-1
-
-# Reporter objects have access to their related Article objects.
->>> r.get_article_list(order_by=['headline'])
-[First, Second]
->>> r.get_article(headline__startswith='Fir')
-First
->>> r.get_article_count()
-2
-
-# Create an Article with no Reporter by passing "reporter=None".
->>> a3 = articles.Article(headline="Third", reporter=None)
->>> a3.save()
->>> a3.id
-3
->>> a3.reporter_id
->>> print a3.reporter_id
-None
->>> a3 = articles.get_object(pk=3)
->>> print a3.reporter_id
-None
-
-# An article's get_reporter() method throws ReporterDoesNotExist
-# if the reporter is set to None.
->>> a3.get_reporter()
-Traceback (most recent call last):
- ...
-ReporterDoesNotExist
-
-# To retrieve the articles with no reporters set, use "reporter__isnull=True".
->>> articles.get_list(reporter__isnull=True)
-[Third]
-"""
diff --git a/tests/testapp/models/one_to_one.py b/tests/testapp/models/one_to_one.py
deleted file mode 100644
index b5d749c25d..0000000000
--- a/tests/testapp/models/one_to_one.py
+++ /dev/null
@@ -1,80 +0,0 @@
-"""
-10. One-to-one relationships
-
-To define a one-to-one relationship, use ``OneToOneField()``.
-
-In this example, a ``Place`` optionally can be a ``Restaurant``.
-"""
-
-from django.core import meta
-
-class Place(meta.Model):
- name = meta.CharField(maxlength=50)
- address = meta.CharField(maxlength=80)
-
- def __repr__(self):
- return "%s the place" % self.name
-
-class Restaurant(meta.Model):
- place = meta.OneToOneField(Place)
- serves_hot_dogs = meta.BooleanField()
- serves_pizza = meta.BooleanField()
-
- def __repr__(self):
- return "%s the restaurant" % self.get_place().name
-
-class Waiter(meta.Model):
- restaurant = meta.ForeignKey(Restaurant)
- name = meta.CharField(maxlength=50)
-
- def __repr__(self):
- return "%s the waiter at %r" % (self.name, self.get_restaurant())
-
-API_TESTS = """
-# Create a couple of Places.
->>> p1 = places.Place(name='Demon Dogs', address='944 W. Fullerton')
->>> p1.save()
->>> p2 = places.Place(name='Ace Hardware', address='1013 N. Ashland')
->>> p2.save()
-
-# Create a Restaurant. Pass the ID of the "parent" object as this object's ID.
->>> r = restaurants.Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False)
->>> r.save()
-
-# A Restaurant can access its place.
->>> r.get_place()
-Demon Dogs the place
-
-# A Place can access its restaurant, if available.
->>> p1.get_restaurant()
-Demon Dogs the restaurant
-
-# p2 doesn't have an associated restaurant.
->>> p2.get_restaurant()
-Traceback (most recent call last):
- ...
-RestaurantDoesNotExist: Restaurant does not exist for {'order_by': (), 'place__id__exact': ...}
-
-# restaurants.get_list() just returns the Restaurants, not the Places.
->>> restaurants.get_list()
-[Demon Dogs the restaurant]
-
-# places.get_list() returns all Places, regardless of whether they have
-# Restaurants.
->>> places.get_list(order_by=['name'])
-[Ace Hardware the place, Demon Dogs the place]
-
->>> restaurants.get_object(place__id__exact=1)
-Demon Dogs the restaurant
->>> restaurants.get_object(pk=1)
-Demon Dogs the restaurant
-
-# Add a Waiter to the Restaurant.
->>> w = r.add_waiter(name='Joe')
->>> w.save()
->>> w
-Joe the waiter at Demon Dogs the restaurant
-
->>> r = restaurants.get_object(pk=1)
->>> r.delete()
-"""
diff --git a/tests/testapp/models/or_lookups.py b/tests/testapp/models/or_lookups.py
deleted file mode 100644
index 0bf554e408..0000000000
--- a/tests/testapp/models/or_lookups.py
+++ /dev/null
@@ -1,57 +0,0 @@
-"""
-19. OR lookups
-
-To perform an OR lookup, or a lookup that combines ANDs and ORs, use the
-``complex`` keyword argument, and pass it an expression of clauses using the
-variable ``django.core.meta.Q``.
-"""
-
-from django.core import meta
-
-class Article(meta.Model):
- headline = meta.CharField(maxlength=50)
- pub_date = meta.DateTimeField()
- class META:
- ordering = ('pub_date',)
-
- def __repr__(self):
- return self.headline
-
-API_TESTS = """
->>> from datetime import datetime
->>> from django.core.meta import Q
-
->>> a1 = articles.Article(headline='Hello', pub_date=datetime(2005, 11, 27))
->>> a1.save()
-
->>> a2 = articles.Article(headline='Goodbye', pub_date=datetime(2005, 11, 28))
->>> a2.save()
-
->>> a3 = articles.Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29))
->>> a3.save()
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')))
-[]
-
->>> articles.get_list(complex=(Q(headline__startswith='Hello') & Q(headline__contains='bye')))
-[Hello and goodbye]
-
->>> articles.get_list(headline__startswith='Hello', complex=Q(headline__contains='bye'))
-[Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__contains='Hello') | Q(headline__contains='bye')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(headline__iexact='Hello') | Q(headline__contains='ood')))
-[Hello, Goodbye, Hello and goodbye]
-
->>> articles.get_list(complex=(Q(pk=1) | Q(pk=2)))
-[Hello, Goodbye]
-
->>> articles.get_list(complex=(Q(pk=1) | Q(pk=2) | Q(pk=3)))
-[Hello, Goodbye, Hello and goodbye]
-
-"""
diff --git a/tests/testapp/models/reserved_names.py b/tests/testapp/models/reserved_names.py
deleted file mode 100644
index eabe41e5bd..0000000000
--- a/tests/testapp/models/reserved_names.py
+++ /dev/null
@@ -1,47 +0,0 @@
-"""
-18. Using SQL reserved names
-
-Need to use a reserved SQL name as a column name or table name? Need to include
-a hyphen in a column or table name? No problem. Django quotes names
-appropriately behind the scenes, so your database won't complain about
-reserved-name usage.
-"""
-
-from django.core import meta
-
-class Thing(meta.Model):
- when = meta.CharField(maxlength=1, primary_key=True)
- join = meta.CharField(maxlength=1)
- like = meta.CharField(maxlength=1)
- drop = meta.CharField(maxlength=1)
- alter = meta.CharField(maxlength=1)
- having = meta.CharField(maxlength=1)
- where = meta.CharField(maxlength=1)
- has_hyphen = meta.CharField(maxlength=1, db_column='has-hyphen')
- class META:
- db_table = 'select'
-
- def __repr__(self):
- return self.when
-
-API_TESTS = """
->>> t = things.Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where='g', has_hyphen='h')
->>> t.save()
->>> print t.when
-a
-
->>> u = things.Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where='n')
->>> u.save()
->>> print u.when
-h
-
->>> things.get_list(order_by=['when'])
-[a, h]
->>> v = things.get_object(pk='a')
->>> print v.join
-b
->>> print v.where
-g
->>> things.get_list(order_by=['select.when'])
-[a, h]
-"""
diff --git a/tests/testapp/models/save_delete_hooks.py b/tests/testapp/models/save_delete_hooks.py
deleted file mode 100644
index f0fa836f71..0000000000
--- a/tests/testapp/models/save_delete_hooks.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""
-13. Adding hooks before/after saving and deleting
-
-Django provides hooks for executing arbitrary code around ``save()`` and
-``delete()``. Just add any of the following methods to your model:
-
- * ``_pre_save()`` is called before an object is saved.
- * ``_post_save()`` is called after an object is saved.
- * ``_pre_delete()`` is called before an object is deleted.
- * ``_post_delete()`` is called after an object is deleted.
-"""
-
-from django.core import meta
-
-class Person(meta.Model):
- first_name = meta.CharField(maxlength=20)
- last_name = meta.CharField(maxlength=20)
-
- def __repr__(self):
- return "%s %s" % (self.first_name, self.last_name)
-
- def _pre_save(self):
- print "Before save"
-
- def _post_save(self):
- print "After save"
-
- def _pre_delete(self):
- print "Before deletion"
-
- def _post_delete(self):
- print "After deletion"
-
-API_TESTS = """
->>> p1 = persons.Person(first_name='John', last_name='Smith')
->>> p1.save()
-Before save
-After save
-
->>> persons.get_list()
-[John Smith]
-
->>> p1.delete()
-Before deletion
-After deletion
-
->>> persons.get_list()
-[]
-"""
diff --git a/tests/testapp/models/subclassing.py b/tests/testapp/models/subclassing.py
deleted file mode 100644
index e0dad26acb..0000000000
--- a/tests/testapp/models/subclassing.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""
-15. Subclassing models
-
-You can subclass another model to create a copy of it that behaves slightly
-differently.
-"""
-
-from django.core import meta
-
-# From the "Bare-bones model" example
-from django.models.basic import Article
-
-# From the "Adding __repr__()" example
-from django.models.repr import Article as ArticleWithRepr
-
-# From the "Specifying ordering" example
-from django.models.ordering import Article as ArticleWithOrdering
-
-# This uses all fields and metadata from Article and
-# adds a "section" field.
-class ArticleWithSection(Article):
- section = meta.CharField(maxlength=30)
- class META:
- module_name = 'subarticles1'
-
-# This uses all fields and metadata from Article but
-# removes the "pub_date" field.
-class ArticleWithoutPubDate(Article):
- class META:
- module_name = 'subarticles2'
- remove_fields = ('pub_date',)
-
-# This uses all fields and metadata from Article but
-# overrides the "pub_date" field.
-class ArticleWithFieldOverride(Article):
- pub_date = meta.DateField() # overrides the old field, a DateTimeField
- class META:
- module_name = 'subarticles3'
- # No need to add remove_fields = ('pub_date',)
-
-# This uses all fields and metadata from ArticleWithRepr and
-# makes a few additions/changes.
-class ArticleWithManyChanges(ArticleWithRepr):
- section = meta.CharField(maxlength=30)
- is_popular = meta.BooleanField()
- pub_date = meta.DateField() # overrides the old field, a DateTimeField
- class META:
- module_name = 'subarticles4'
-
-# This uses all fields from ArticleWithOrdering but
-# changes the ordering parameter.
-class ArticleWithChangedMeta(ArticleWithOrdering):
- class META:
- module_name = 'subarticles5'
- ordering = ('headline', 'pub_date')
-
-# These two models don't define a module_name.
-class NoModuleNameFirst(Article):
- section = meta.CharField(maxlength=30)
-
-class NoModuleNameSecond(Article):
- section = meta.CharField(maxlength=30)
-
-API_TESTS = """
-# No data is in the system yet.
->>> subarticles1.get_list()
-[]
->>> subarticles2.get_list()
-[]
->>> subarticles3.get_list()
-[]
-
-# Create an ArticleWithSection.
->>> from datetime import date, datetime
->>> a1 = subarticles1.ArticleWithSection(headline='First', pub_date=datetime(2005, 8, 22), section='News')
->>> a1.save()
->>> a1
-<ArticleWithSection object>
->>> a1.id
-1
->>> a1.headline
-'First'
->>> a1.pub_date
-datetime.datetime(2005, 8, 22, 0, 0)
-
-# Retrieve it again, to prove the fields have been saved.
->>> a1 = subarticles1.get_object(pk=1)
->>> a1.headline
-'First'
->>> a1.pub_date
-datetime.datetime(2005, 8, 22, 0, 0)
->>> a1.section
-'News'
-
-# Create an ArticleWithoutPubDate.
->>> a2 = subarticles2.ArticleWithoutPubDate(headline='Second')
->>> a2.save()
->>> a2
-<ArticleWithoutPubDate object>
->>> a2.id
-1
->>> a2.pub_date
-Traceback (most recent call last):
- ...
-AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
-
-# Retrieve it again, to prove the fields have been saved.
->>> a2 = subarticles2.get_object(pk=1)
->>> a2.headline
-'Second'
->>> a2.pub_date
-Traceback (most recent call last):
- ...
-AttributeError: 'ArticleWithoutPubDate' object has no attribute 'pub_date'
-
-# Create an ArticleWithFieldOverride.
->>> a3 = subarticles3.ArticleWithFieldOverride(headline='Third', pub_date=date(2005, 8, 22))
->>> a3.save()
->>> a3
-<ArticleWithFieldOverride object>
->>> a3.id
-1
->>> a3.pub_date
-datetime.date(2005, 8, 22)
-
-# Retrieve it again, to prove the fields have been saved.
->>> a3 = subarticles3.get_object(pk=1)
->>> a3.headline
-'Third'
->>> a3.pub_date
-datetime.date(2005, 8, 22)
-
-# Create an ArticleWithManyChanges.
->>> a4 = subarticles4.ArticleWithManyChanges(headline='Fourth', section='Arts',
-... is_popular=True, pub_date=date(2005, 8, 22))
->>> a4.save()
-
-# a4 inherits __repr__() from its parent model (ArticleWithRepr).
->>> a4
-Fourth
-
-# Retrieve it again, to prove the fields have been saved.
->>> a4 = subarticles4.get_object(pk=1)
->>> a4.headline
-'Fourth'
->>> a4.section
-'Arts'
->>> a4.is_popular == True
-True
->>> a4.pub_date
-datetime.date(2005, 8, 22)
-
-# Test get_list().
->>> subarticles1.get_list()
-[<ArticleWithSection object>]
->>> subarticles2.get_list()
-[<ArticleWithoutPubDate object>]
->>> subarticles3.get_list()
-[<ArticleWithFieldOverride object>]
->>> subarticles4.get_list()
-[Fourth]
-
-# Create a couple of ArticleWithChangedMeta objects.
->>> a5 = subarticles5.ArticleWithChangedMeta(headline='A', pub_date=datetime(2005, 3, 1))
->>> a5.save()
->>> a6 = subarticles5.ArticleWithChangedMeta(headline='B', pub_date=datetime(2005, 4, 1))
->>> a6.save()
->>> a7 = subarticles5.ArticleWithChangedMeta(headline='C', pub_date=datetime(2005, 5, 1))
->>> a7.save()
-
-# Ordering has been overridden, so objects are ordered
-# by headline ASC instead of pub_date DESC.
->>> subarticles5.get_list()
-[A, B, C]
-
->>> nomodulenamefirsts.get_list()
-[]
->>> nomodulenameseconds.get_list()
-[]
-"""