summaryrefslogtreecommitdiff
path: root/tests/modeltests
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/modeltests
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/modeltests')
-rw-r--r--tests/modeltests/__init__.py0
-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.py53
-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.py63
-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.py31
-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
67 files changed, 3117 insertions, 0 deletions
diff --git a/tests/modeltests/__init__.py b/tests/modeltests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ 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/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
new file mode 100644
index 0000000000..4958517e69
--- /dev/null
+++ b/tests/modeltests/custom_columns/models.py
@@ -0,0 +1,53 @@
+"""
+17. Custom column names
+
+If your database column name is different than your model attribute, use the
+``db_column`` parameter. Note that you'll use the field's name, not its column
+name, in API usage.
+"""
+
+from django.db import models
+
+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 = Person(first_name='John', last_name='Smith')
+>>> p.save()
+
+>>> p.id
+1
+
+>>> Person.objects.all()
+[John Smith]
+
+>>> Person.objects.filter(first_name__exact='John')
+[John Smith]
+
+>>> Person.objects.get(first_name__exact='John')
+John Smith
+
+>>> Person.objects.filter(firstname__exact='John')
+Traceback (most recent call last):
+ ...
+TypeError: Cannot resolve keyword 'firstname' into field
+
+>>> p = Person.objects.get(last_name__exact='Smith')
+>>> p.first_name
+'John'
+>>> p.last_name
+'Smith'
+>>> p.firstname
+Traceback (most recent call last):
+ ...
+AttributeError: 'Person' object has no attribute 'firstname'
+>>> p.last
+Traceback (most recent call last):
+ ...
+AttributeError: 'Person' object has no attribute 'last'
+"""
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/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py
new file mode 100644
index 0000000000..de08a75755
--- /dev/null
+++ b/tests/modeltests/ordering/models.py
@@ -0,0 +1,63 @@
+"""
+6. Specifying ordering
+
+Specify default ordering for a model using the ``ordering`` attribute, which
+should be a list or tuple of field names. This tells Django how to order the
+results of ``get_list()`` and other similar functions.
+
+If a field name in ``ordering`` starts with a hyphen, that field will be
+ordered in descending order. Otherwise, it'll be ordered in ascending order.
+The special-case field name ``"?"`` specifies random order.
+
+The ordering attribute is not required. If you leave it off, ordering will be
+undefined -- not random, just undefined.
+"""
+
+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()
+
+# By default, Article.objects.all() orders by pub_date descending, then
+# headline ascending.
+>>> 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.
+>>> Article.objects.order_by('headline')
+[Article 1, Article 2, Article 3, Article 4]
+>>> Article.objects.order_by('pub_date', '-headline')
+[Article 1, Article 3, Article 2, Article 4]
+
+# Use the 'stop' part of slicing notation to limit the results.
+>>> Article.objects.order_by('headline')[:2]
+[Article 1, Article 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.
+>>> 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/modeltests/repr/models.py b/tests/modeltests/repr/models.py
new file mode 100644
index 0000000000..7e5b98c4a5
--- /dev/null
+++ b/tests/modeltests/repr/models.py
@@ -0,0 +1,31 @@
+"""
+2. Adding __repr__() to models
+
+Although it's not a strict requirement, each model should have a ``__repr__()``
+method to return a "human-readable" representation of the object. Do this not
+only for your own sanity when dealing with the interactive prompt, but also
+because objects' representations are used throughout Django's
+automatically-generated admin.
+"""
+
+from django.db import models
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ pub_date = models.DateTimeField()
+
+ def __repr__(self):
+ return self.headline
+
+API_TESTS = """
+# Create an Article.
+>>> from datetime import datetime
+>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28))
+>>> a.save()
+
+>>> repr(a)
+'Area man programs in Python'
+
+>>> a
+Area man programs in Python
+"""
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.']}
+
+"""