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