diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2006-05-02 01:31:56 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2006-05-02 01:31:56 +0000 |
| commit | f69cf70ed813a8cd7e1f963a14ae39103e8d5265 (patch) | |
| tree | d3b32e84cd66573b3833ddf662af020f8ef2f7a8 /tests/modeltests | |
| parent | d5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 (diff) | |
MERGED MAGIC-REMOVAL BRANCH TO TRUNK. This change is highly backwards-incompatible. Please read http://code.djangoproject.com/wiki/RemovingTheMagic for upgrade instructions.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@2809 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/modeltests')
67 files changed, 3117 insertions, 0 deletions
diff --git a/tests/modeltests/__init__.py b/tests/modeltests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/__init__.py diff --git a/tests/modeltests/basic/__init__.py b/tests/modeltests/basic/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/basic/__init__.py diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py new file mode 100644 index 0000000000..c96cd993ad --- /dev/null +++ b/tests/modeltests/basic/models.py @@ -0,0 +1,339 @@ +""" +1. Bare-bones model + +This is a basic model with only two non-primary-key fields. +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100, default='Default headline') + pub_date = models.DateTimeField() + + def __repr__(self): + return self.headline +API_TESTS = """ + +# No articles are in the system yet. +>>> Article.objects.all() +[] + +# Create an Article. +>>> from datetime import datetime +>>> a = Article(id=None, headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) + +# Save it into the database. You have to call save() explicitly. +>>> a.save() + +# Now it has an ID. Note it's a long integer, as designated by the trailing "L". +>>> a.id +1L + +# Access database columns via Python attributes. +>>> a.headline +'Area man programs in Python' +>>> a.pub_date +datetime.datetime(2005, 7, 28, 0, 0) + +# Change values by changing the attributes, then calling save(). +>>> a.headline = 'Area woman programs in Python' +>>> a.save() + +# Article.objects.all() returns all the articles in the database. +>>> Article.objects.all() +[Area woman programs in Python] + +# Django provides a rich database lookup API. +>>> Article.objects.get(id__exact=1) +Area woman programs in Python +>>> Article.objects.get(headline__startswith='Area woman') +Area woman programs in Python +>>> Article.objects.get(pub_date__year=2005) +Area woman programs in Python +>>> Article.objects.get(pub_date__year=2005, pub_date__month=7) +Area woman programs in Python +>>> Article.objects.get(pub_date__year=2005, pub_date__month=7, pub_date__day=28) +Area woman programs in Python + +# The "__exact" lookup type can be omitted, as a shortcut. +>>> Article.objects.get(id=1) +Area woman programs in Python +>>> Article.objects.get(headline='Area woman programs in Python') +Area woman programs in Python + +>>> Article.objects.filter(pub_date__year=2005) +[Area woman programs in Python] +>>> Article.objects.filter(pub_date__year=2004) +[] +>>> Article.objects.filter(pub_date__year=2005, pub_date__month=7) +[Area woman programs in Python] + +# Django raises an Article.DoesNotExist exception for get() if the parameters +# don't match any object. +>>> Article.objects.get(id__exact=2) +Traceback (most recent call last): + ... +DoesNotExist: Article does not exist for {'id__exact': 2} + +>>> Article.objects.get(pub_date__year=2005, pub_date__month=8) +Traceback (most recent call last): + ... +DoesNotExist: Article does not exist for ... + +# Lookup by a primary key is the most common case, so Django provides a +# shortcut for primary-key exact lookups. +# The following is identical to articles.get(id=1). +>>> Article.objects.get(pk=1) +Area woman programs in Python + +# Model instances of the same type and same ID are considered equal. +>>> a = Article.objects.get(pk=1) +>>> b = Article.objects.get(pk=1) +>>> a == b +True + +# You can initialize a model instance using positional arguments, which should +# match the field order as defined in the model. +>>> a2 = Article(None, 'Second article', datetime(2005, 7, 29)) +>>> a2.save() +>>> a2.id +2L +>>> a2.headline +'Second article' +>>> a2.pub_date +datetime.datetime(2005, 7, 29, 0, 0) + +# ...or, you can use keyword arguments. +>>> a3 = Article(id=None, headline='Third article', pub_date=datetime(2005, 7, 30)) +>>> a3.save() +>>> a3.id +3L +>>> a3.headline +'Third article' +>>> a3.pub_date +datetime.datetime(2005, 7, 30, 0, 0) + +# You can also mix and match position and keyword arguments, but be sure not to +# duplicate field information. +>>> a4 = Article(None, 'Fourth article', pub_date=datetime(2005, 7, 31)) +>>> a4.save() +>>> a4.headline +'Fourth article' + +# Don't use invalid keyword arguments. +>>> a5 = Article(id=None, headline='Invalid', pub_date=datetime(2005, 7, 31), foo='bar') +Traceback (most recent call last): + ... +TypeError: 'foo' is an invalid keyword argument for this function + +# You can leave off the value for an AutoField when creating an object, because +# it'll get filled in automatically when you save(). +>>> a5 = Article(headline='Article 6', pub_date=datetime(2005, 7, 31)) +>>> a5.save() +>>> a5.id +5L +>>> a5.headline +'Article 6' + +# If you leave off a field with "default" set, Django will use the default. +>>> a6 = Article(pub_date=datetime(2005, 7, 31)) +>>> a6.save() +>>> a6.headline +'Default headline' + +# For DateTimeFields, Django saves as much precision (in seconds) as you +# give it. +>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 31, 12, 30)) +>>> a7.save() +>>> Article.objects.get(id__exact=7).pub_date +datetime.datetime(2005, 7, 31, 12, 30) + +>>> a8 = Article(headline='Article 8', pub_date=datetime(2005, 7, 31, 12, 30, 45)) +>>> a8.save() +>>> Article.objects.get(id__exact=8).pub_date +datetime.datetime(2005, 7, 31, 12, 30, 45) +>>> a8.id +8L + +# Saving an object again doesn't create a new object -- it just saves the old one. +>>> a8.save() +>>> a8.id +8L +>>> a8.headline = 'Updated article 8' +>>> a8.save() +>>> a8.id +8L + +>>> a7 == a8 +False +>>> a8 == Article.objects.get(id__exact=8) +True +>>> a7 != a8 +True +>>> Article.objects.get(id__exact=8) != Article.objects.get(id__exact=7) +True +>>> Article.objects.get(id__exact=8) == Article.objects.get(id__exact=7) +False + +# dates() returns a list of available dates of the given scope for the given field. +>>> Article.objects.dates('pub_date', 'year') +[datetime.datetime(2005, 1, 1, 0, 0)] +>>> Article.objects.dates('pub_date', 'month') +[datetime.datetime(2005, 7, 1, 0, 0)] +>>> Article.objects.dates('pub_date', 'day') +[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)] +>>> Article.objects.dates('pub_date', 'day', order='ASC') +[datetime.datetime(2005, 7, 28, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 31, 0, 0)] +>>> Article.objects.dates('pub_date', 'day', order='DESC') +[datetime.datetime(2005, 7, 31, 0, 0), datetime.datetime(2005, 7, 30, 0, 0), datetime.datetime(2005, 7, 29, 0, 0), datetime.datetime(2005, 7, 28, 0, 0)] + +# dates() requires valid arguments. + +>>> Article.objects.dates() +Traceback (most recent call last): + ... +TypeError: dates() takes at least 3 arguments (1 given) + +>>> Article.objects.dates('invalid_field', 'year') +Traceback (most recent call last): + ... +FieldDoesNotExist: name=invalid_field + +>>> Article.objects.dates('pub_date', 'bad_kind') +Traceback (most recent call last): + ... +AssertionError: 'kind' must be one of 'year', 'month' or 'day'. + +>>> Article.objects.dates('pub_date', 'year', order='bad order') +Traceback (most recent call last): + ... +AssertionError: 'order' must be either 'ASC' or 'DESC'. + +# Use iterator() with dates() to return a generator that lazily requests each +# result one at a time, to save memory. +>>> for a in Article.objects.dates('pub_date', 'day', order='DESC').iterator(): +... print repr(a) +datetime.datetime(2005, 7, 31, 0, 0) +datetime.datetime(2005, 7, 30, 0, 0) +datetime.datetime(2005, 7, 29, 0, 0) +datetime.datetime(2005, 7, 28, 0, 0) + +# You can combine queries with & and |. +>>> s1 = Article.objects.filter(id__exact=1) +>>> s2 = Article.objects.filter(id__exact=2) +>>> s1 | s2 +[Area woman programs in Python, Second article] +>>> s1 & s2 +[] + +# You can get the number of objects like this: +>>> len(Article.objects.filter(id__exact=1)) +1 + +# You can get items using index and slice notation. +>>> Article.objects.all()[0] +Area woman programs in Python +>>> Article.objects.all()[1:3] +[Second article, Third article] +>>> s3 = Article.objects.filter(id__exact=3) +>>> (s1 | s2 | s3)[::2] +[Area woman programs in Python, Third article] + +# Slices (without step) are lazy: +>>> Article.objects.all()[0:5].filter() +[Area woman programs in Python, Second article, Third article, Fourth article, Article 6] + +# Slicing again works: +>>> Article.objects.all()[0:5][0:2] +[Area woman programs in Python, Second article] +>>> Article.objects.all()[0:5][:2] +[Area woman programs in Python, Second article] +>>> Article.objects.all()[0:5][4:] +[Article 6] +>>> Article.objects.all()[0:5][5:] +[] + +# Some more tests! +>>> Article.objects.all()[2:][0:2] +[Third article, Fourth article] +>>> Article.objects.all()[2:][:2] +[Third article, Fourth article] +>>> Article.objects.all()[2:][2:3] +[Article 6] + +# Note that you can't use 'offset' without 'limit' (on some dbs), so this doesn't work: +>>> Article.objects.all()[2:] +Traceback (most recent call last): + ... +AssertionError: 'offset' is not allowed without 'limit' + +# Also, once you have sliced you can't filter, re-order or combine +>>> Article.objects.all()[0:5].filter(id=1) +Traceback (most recent call last): + ... +AssertionError: Cannot filter a query once a slice has been taken. + +>>> Article.objects.all()[0:5].order_by('id') +Traceback (most recent call last): + ... +AssertionError: Cannot reorder a query once a slice has been taken. + +>>> Article.objects.all()[0:1] & Article.objects.all()[4:5] +Traceback (most recent call last): + ... +AssertionError: Cannot combine queries once a slice has been taken. + + +# An Article instance doesn't have access to the "objects" attribute. +# That's only available on the class. +>>> a7.objects.all() +Traceback (most recent call last): + ... +AttributeError: Manager isn't accessible via Article instances + +>>> a7.objects +Traceback (most recent call last): + ... +AttributeError: Manager isn't accessible via Article instances + +# Bulk delete test: How many objects before and after the delete? +>>> Article.objects.all() +[Area woman programs in Python, Second article, Third article, Fourth article, Article 6, Default headline, Article 7, Updated article 8] +>>> Article.objects.filter(id__lte=4).delete() +>>> Article.objects.all() +[Article 6, Default headline, Article 7, Updated article 8] + +""" + +from django.conf import settings + +building_docs = getattr(settings, 'BUILDING_DOCS', False) + +if building_docs or settings.DATABASE_ENGINE == 'postgresql': + API_TESTS += """ +# In PostgreSQL, microsecond-level precision is available. +>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180)) +>>> a9.save() +>>> Article.objects.get(id__exact=9).pub_date +datetime.datetime(2005, 7, 31, 12, 30, 45, 180) +""" + +if building_docs or settings.DATABASE_ENGINE == 'mysql': + API_TESTS += """ +# In MySQL, microsecond-level precision isn't available. You'll lose +# microsecond-level precision once the data is saved. +>>> a9 = Article(headline='Article 9', pub_date=datetime(2005, 7, 31, 12, 30, 45, 180)) +>>> a9.save() +>>> Article.objects.get(id__exact=9).pub_date +datetime.datetime(2005, 7, 31, 12, 30, 45) +""" + +API_TESTS += """ + +# You can manually specify the primary key when creating a new object. +>>> a101 = Article(id=101, headline='Article 101', pub_date=datetime(2005, 7, 31, 12, 30, 45)) +>>> a101.save() +>>> a101 = Article.objects.get(pk=101) +>>> a101.headline +'Article 101' +""" diff --git a/tests/modeltests/choices/__init__.py b/tests/modeltests/choices/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/choices/__init__.py diff --git a/tests/modeltests/choices/models.py b/tests/modeltests/choices/models.py new file mode 100644 index 0000000000..38dcb934c5 --- /dev/null +++ b/tests/modeltests/choices/models.py @@ -0,0 +1,39 @@ +""" +21. Specifying 'choices' for a field + +Most fields take a ``choices`` parameter, which should be a tuple of tuples +specifying which are the valid values for that field. + +For each field that has ``choices``, a model instance gets a +``get_fieldname_display()`` method, where ``fieldname`` is the name of the +field. This method returns the "human-readable" value of the field. +""" + +from django.db import models + +GENDER_CHOICES = ( + ('M', 'Male'), + ('F', 'Female'), +) + +class Person(models.Model): + name = models.CharField(maxlength=20) + gender = models.CharField(maxlength=1, choices=GENDER_CHOICES) + + def __repr__(self): + return self.name + +API_TESTS = """ +>>> a = Person(name='Adrian', gender='M') +>>> a.save() +>>> s = Person(name='Sara', gender='F') +>>> s.save() +>>> a.gender +'M' +>>> s.gender +'F' +>>> a.get_gender_display() +'Male' +>>> s.get_gender_display() +'Female' +""" diff --git a/tests/modeltests/custom_columns/__init__.py b/tests/modeltests/custom_columns/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/custom_columns/__init__.py diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py new file mode 100644 index 0000000000..4958517e69 --- /dev/null +++ b/tests/modeltests/custom_columns/models.py @@ -0,0 +1,53 @@ +""" +17. Custom column names + +If your database column name is different than your model attribute, use the +``db_column`` parameter. Note that you'll use the field's name, not its column +name, in API usage. +""" + +from django.db import models + +class Person(models.Model): + first_name = models.CharField(maxlength=30, db_column='firstname') + last_name = models.CharField(maxlength=30, db_column='last') + + def __repr__(self): + return '%s %s' % (self.first_name, self.last_name) + +API_TESTS = """ +# Create a Person. +>>> p = Person(first_name='John', last_name='Smith') +>>> p.save() + +>>> p.id +1 + +>>> Person.objects.all() +[John Smith] + +>>> Person.objects.filter(first_name__exact='John') +[John Smith] + +>>> Person.objects.get(first_name__exact='John') +John Smith + +>>> Person.objects.filter(firstname__exact='John') +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'firstname' into field + +>>> p = Person.objects.get(last_name__exact='Smith') +>>> p.first_name +'John' +>>> p.last_name +'Smith' +>>> p.firstname +Traceback (most recent call last): + ... +AttributeError: 'Person' object has no attribute 'firstname' +>>> p.last +Traceback (most recent call last): + ... +AttributeError: 'Person' object has no attribute 'last' +""" diff --git a/tests/modeltests/custom_managers/__init__.py b/tests/modeltests/custom_managers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/custom_managers/__init__.py diff --git a/tests/modeltests/custom_managers/models.py b/tests/modeltests/custom_managers/models.py new file mode 100644 index 0000000000..a6ae80029a --- /dev/null +++ b/tests/modeltests/custom_managers/models.py @@ -0,0 +1,100 @@ +""" +23. Giving models a custom manager +""" + +from django.db import models + +# An example of a custom manager called "objects". + +class PersonManager(models.Manager): + def get_fun_people(self): + return self.filter(fun=True) + +class Person(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + fun = models.BooleanField() + objects = PersonManager() + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +# An example of a custom manager that sets a core_filter on its lookups. + +class PublishedBookManager(models.Manager): + def get_query_set(self): + return super(PublishedBookManager, self).get_query_set().filter(is_published=True) + +class Book(models.Model): + title = models.CharField(maxlength=50) + author = models.CharField(maxlength=30) + is_published = models.BooleanField() + published_objects = PublishedBookManager() + authors = models.ManyToManyField(Person, related_name='books') + + def __repr__(self): + return self.title + +# An example of providing multiple custom managers. + +class FastCarManager(models.Manager): + def get_query_set(self): + return super(FastCarManager, self).get_query_set().filter(top_speed__gt=150) + +class Car(models.Model): + name = models.CharField(maxlength=10) + mileage = models.IntegerField() + top_speed = models.IntegerField(help_text="In miles per hour.") + cars = models.Manager() + fast_cars = FastCarManager() + + def __repr__(self): + return self.name + +API_TESTS = """ +>>> p1 = Person(first_name='Bugs', last_name='Bunny', fun=True) +>>> p1.save() +>>> p2 = Person(first_name='Droopy', last_name='Dog', fun=False) +>>> p2.save() +>>> Person.objects.get_fun_people() +[Bugs Bunny] + +# The RelatedManager used on the 'books' descriptor extends the default manager +>>> from modeltests.custom_managers.models import PublishedBookManager +>>> isinstance(p2.books, PublishedBookManager) +True + +>>> b1 = Book(title='How to program', author='Rodney Dangerfield', is_published=True) +>>> b1.save() +>>> b2 = Book(title='How to be smart', author='Albert Einstein', is_published=False) +>>> b2.save() + +# The default manager, "objects", doesn't exist, +# because a custom one was provided. +>>> Book.objects +Traceback (most recent call last): + ... +AttributeError: type object 'Book' has no attribute 'objects' + +# The RelatedManager used on the 'authors' descriptor extends the default manager +>>> from modeltests.custom_managers.models import PersonManager +>>> isinstance(b2.authors, PersonManager) +True + +>>> Book.published_objects.all() +[How to program] + +>>> c1 = Car(name='Corvette', mileage=21, top_speed=180) +>>> c1.save() +>>> c2 = Car(name='Neon', mileage=31, top_speed=100) +>>> c2.save() +>>> Car.cars.order_by('name') +[Corvette, Neon] +>>> Car.fast_cars.all() +[Corvette] + +# Each model class gets a "_default_manager" attribute, which is a reference +# to the first manager defined in the class. In this case, it's "cars". +>>> Car._default_manager.order_by('name') +[Corvette, Neon] +""" diff --git a/tests/modeltests/custom_methods/__init__.py b/tests/modeltests/custom_methods/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/custom_methods/__init__.py diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py new file mode 100644 index 0000000000..3fdefca6bf --- /dev/null +++ b/tests/modeltests/custom_methods/models.py @@ -0,0 +1,58 @@ +""" +3. Giving models custom methods and custom managers + +Any method you add to a model will be available to instances. +""" + +from django.db import models +import datetime + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateField() + + def __repr__(self): + return self.headline + + def was_published_today(self): + return self.pub_date == datetime.date.today() + + def get_articles_from_same_day_1(self): + return Article.objects.filter(pub_date=self.pub_date).exclude(id=self.id) + + def get_articles_from_same_day_2(self): + """ + Verbose version of get_articles_from_same_day_1, which does a custom + database query for the sake of demonstration. + """ + from django.db import connection + cursor = connection.cursor() + cursor.execute(""" + SELECT id, headline, pub_date + FROM custom_methods_article + WHERE pub_date = %s + AND id != %s""", [str(self.pub_date), self.id]) + # The asterisk in "(*row)" tells Python to expand the list into + # positional arguments to Article(). + return [self.__class__(*row) for row in cursor.fetchall()] + +API_TESTS = """ +# Create a couple of Articles. +>>> from datetime import date +>>> a = Article(id=None, headline='Area man programs in Python', pub_date=date(2005, 7, 27)) +>>> a.save() +>>> b = Article(id=None, headline='Beatles reunite', pub_date=date(2005, 7, 27)) +>>> b.save() + +# Test the custom methods. +>>> a.was_published_today() +False +>>> a.get_articles_from_same_day_1() +[Beatles reunite] +>>> a.get_articles_from_same_day_2() +[Beatles reunite] +>>> b.get_articles_from_same_day_1() +[Area man programs in Python] +>>> b.get_articles_from_same_day_2() +[Area man programs in Python] +""" diff --git a/tests/modeltests/custom_pk/__init__.py b/tests/modeltests/custom_pk/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/custom_pk/__init__.py diff --git a/tests/modeltests/custom_pk/models.py b/tests/modeltests/custom_pk/models.py new file mode 100644 index 0000000000..df127ba3a8 --- /dev/null +++ b/tests/modeltests/custom_pk/models.py @@ -0,0 +1,90 @@ +""" +14. Using a custom primary key + +By default, Django adds an ``"id"`` field to each model. But you can override +this behavior by explicitly adding ``primary_key=True`` to a field. +""" + +from django.db import models + +class Employee(models.Model): + employee_code = models.CharField(maxlength=10, primary_key=True) + first_name = models.CharField(maxlength=20) + last_name = models.CharField(maxlength=20) + class Meta: + ordering = ('last_name', 'first_name') + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +class Business(models.Model): + name = models.CharField(maxlength=20, primary_key=True) + employees = models.ManyToManyField(Employee) + class Meta: + verbose_name_plural = 'businesses' + + def __repr__(self): + return self.name + +API_TESTS = """ +>>> dan = Employee(employee_code='ABC123', first_name='Dan', last_name='Jones') +>>> dan.save() +>>> Employee.objects.all() +[Dan Jones] + +>>> fran = Employee(employee_code='XYZ456', first_name='Fran', last_name='Bones') +>>> fran.save() +>>> Employee.objects.all() +[Fran Bones, Dan Jones] + +>>> Employee.objects.get(pk='ABC123') +Dan Jones +>>> Employee.objects.get(pk='XYZ456') +Fran Bones +>>> Employee.objects.get(pk='foo') +Traceback (most recent call last): + ... +DoesNotExist: Employee does not exist for {'pk': 'foo'} + +# Use the name of the primary key, rather than pk. +>>> Employee.objects.get(employee_code__exact='ABC123') +Dan Jones + +# Fran got married and changed her last name. +>>> fran = Employee.objects.get(pk='XYZ456') +>>> fran.last_name = 'Jones' +>>> fran.save() +>>> Employee.objects.filter(last_name__exact='Jones') +[Dan Jones, Fran Jones] +>>> Employee.objects.in_bulk(['ABC123', 'XYZ456']) +{'XYZ456': Fran Jones, 'ABC123': Dan Jones} + +>>> b = Business(name='Sears') +>>> b.save() +>>> b.employees.add(dan, fran) +>>> b.employees.all() +[Dan Jones, Fran Jones] +>>> fran.business_set.all() +[Sears] +>>> Business.objects.in_bulk(['Sears']) +{'Sears': Sears} + +>>> Business.objects.filter(name__exact='Sears') +[Sears] +>>> Business.objects.filter(pk='Sears') +[Sears] + +# Queries across tables, involving primary key +>>> Employee.objects.filter(business__name__exact='Sears') +[Dan Jones, Fran Jones] +>>> Employee.objects.filter(business__pk='Sears') +[Dan Jones, Fran Jones] + +>>> Business.objects.filter(employees__employee_code__exact='ABC123') +[Sears] +>>> Business.objects.filter(employees__pk='ABC123') +[Sears] +>>> Business.objects.filter(employees__first_name__startswith='Fran') +[Sears] + +""" diff --git a/tests/modeltests/field_defaults/__init__.py b/tests/modeltests/field_defaults/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/field_defaults/__init__.py diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py new file mode 100644 index 0000000000..e5b7fd8e6d --- /dev/null +++ b/tests/modeltests/field_defaults/models.py @@ -0,0 +1,48 @@ +""" +XXX. Callable defaults + +??? +""" + +from django.db import models +from datetime import datetime + +class Article(models.Model): + headline = models.CharField(maxlength=100, default='Default headline') + pub_date = models.DateTimeField(default = datetime.now) + + def __repr__(self): + return self.headline + +API_TESTS = """ +>>> from datetime import datetime + +# No articles are in the system yet. +>>> Article.objects.all() +[] + +# Create an Article. +>>> a = Article(id=None) + +# Grab the current datetime it should be very close to the default that just +# got saved as a.pub_date +>>> now = datetime.now() + +# Save it into the database. You have to call save() explicitly. +>>> a.save() + +# Now it has an ID. Note it's a long integer, as designated by the trailing "L". +>>> a.id +1L + +# Access database columns via Python attributes. +>>> a.headline +'Default headline' + +# make sure the two dates are sufficiently close +>>> d = now - a.pub_date +>>> d.seconds < 5 +True + + +""" diff --git a/tests/modeltests/get_latest/__init__.py b/tests/modeltests/get_latest/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/get_latest/__init__.py diff --git a/tests/modeltests/get_latest/models.py b/tests/modeltests/get_latest/models.py new file mode 100644 index 0000000000..4b81dc837c --- /dev/null +++ b/tests/modeltests/get_latest/models.py @@ -0,0 +1,79 @@ +""" +8. get_latest_by + +Models can have a ``get_latest_by`` attribute, which should be set to the name +of a DateField or DateTimeField. If ``get_latest_by`` exists, the model's +module will get a ``get_latest()`` function, which will return the latest +object in the database according to that field. "Latest" means "having the +date farthest into the future." +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateField() + expire_date = models.DateField() + class Meta: + get_latest_by = 'pub_date' + + def __repr__(self): + return self.headline + +class Person(models.Model): + name = models.CharField(maxlength=30) + birthday = models.DateField() + + # Note that this model doesn't have "get_latest_by" set. + + def __repr__(self): + return self.name + +API_TESTS = """ +# Because no Articles exist yet, get_latest() raises ArticleDoesNotExist. +>>> Article.objects.latest() +Traceback (most recent call last): + ... +DoesNotExist: Article does not exist for ... + +# Create a couple of Articles. +>>> from datetime import datetime +>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26), expire_date=datetime(2005, 9, 1)) +>>> a1.save() +>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 7, 28)) +>>> a2.save() +>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27), expire_date=datetime(2005, 8, 27)) +>>> a3.save() +>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28), expire_date=datetime(2005, 7, 30)) +>>> a4.save() + +# Get the latest Article. +>>> Article.objects.latest() +Article 4 + +# Get the latest Article that matches certain filters. +>>> Article.objects.filter(pub_date__lt=datetime(2005, 7, 27)).latest() +Article 1 + +# Pass a custom field name to latest() to change the field that's used to +# determine the latest object. +>>> Article.objects.latest('expire_date') +Article 1 + +>>> Article.objects.filter(pub_date__gt=datetime(2005, 7, 26)).latest('expire_date') +Article 3 + +# You can still use latest() with a model that doesn't have "get_latest_by" +# set -- just pass in the field name manually. +>>> p1 = Person(name='Ralph', birthday=datetime(1950, 1, 1)) +>>> p1.save() +>>> p2 = Person(name='Stephanie', birthday=datetime(1960, 2, 3)) +>>> p2.save() +>>> Person.objects.latest() +Traceback (most recent call last): + ... +AssertionError: latest() requires either a field_name parameter or 'get_latest_by' in the model + +>>> Person.objects.latest('birthday') +Stephanie +""" diff --git a/tests/modeltests/invalid_models/__init__.py b/tests/modeltests/invalid_models/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/modeltests/invalid_models/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/modeltests/invalid_models/models.py b/tests/modeltests/invalid_models/models.py new file mode 100644 index 0000000000..0b3ffcf073 --- /dev/null +++ b/tests/modeltests/invalid_models/models.py @@ -0,0 +1,117 @@ +""" +26. A test to check that the model validator works can correctly identify errors in a model. +""" + +from django.db import models + +class FieldErrors(models.Model): + charfield = models.CharField() + floatfield = models.FloatField() + filefield = models.FileField() + prepopulate = models.CharField(maxlength=10, prepopulate_from='bad') + choices = models.CharField(maxlength=10, choices='bad') + choices2 = models.CharField(maxlength=10, choices=[(1,2,3),(1,2,3)]) + index = models.CharField(maxlength=10, db_index='bad') + +class Target(models.Model): + tgt_safe = models.CharField(maxlength=10) + + clash1_set = models.CharField(maxlength=10) + +class Clash1(models.Model): + src_safe = models.CharField(maxlength=10) + + foreign = models.ForeignKey(Target) + m2m = models.ManyToManyField(Target) + +class Clash2(models.Model): + src_safe = models.CharField(maxlength=10) + + foreign_1 = models.ForeignKey(Target, related_name='id') + foreign_2 = models.ForeignKey(Target, related_name='src_safe') + + m2m_1 = models.ManyToManyField(Target, related_name='id') + m2m_2 = models.ManyToManyField(Target, related_name='src_safe') + +class Target2(models.Model): + foreign_tgt = models.ForeignKey(Target) + clashforeign_set = models.ForeignKey(Target) + + m2m_tgt = models.ManyToManyField(Target) + clashm2m_set = models.ManyToManyField(Target) + +class Clash3(models.Model): + foreign_1 = models.ForeignKey(Target2, related_name='foreign_tgt') + foreign_2 = models.ForeignKey(Target2, related_name='m2m_tgt') + + m2m_1 = models.ManyToManyField(Target2, related_name='foreign_tgt') + m2m_2 = models.ManyToManyField(Target2, related_name='m2m_tgt') + +class ClashForeign(models.Model): + foreign = models.ForeignKey(Target2) + +class ClashM2M(models.Model): + m2m = models.ManyToManyField(Target2) + +class SelfClashForeign(models.Model): + src_safe = models.CharField(maxlength=10) + + selfclashforeign_set = models.ForeignKey("SelfClashForeign") + foreign_1 = models.ForeignKey("SelfClashForeign", related_name='id') + foreign_2 = models.ForeignKey("SelfClashForeign", related_name='src_safe') + +class SelfClashM2M(models.Model): + src_safe = models.CharField(maxlength=10) + + selfclashm2m_set = models.ManyToManyField("SelfClashM2M") + m2m_1 = models.ManyToManyField("SelfClashM2M", related_name='id') + m2m_2 = models.ManyToManyField("SelfClashM2M", related_name='src_safe') + +error_log = """invalid_models.fielderrors: "charfield": CharFields require a "maxlength" attribute. +invalid_models.fielderrors: "floatfield": FloatFields require a "decimal_places" attribute. +invalid_models.fielderrors: "floatfield": FloatFields require a "max_digits" attribute. +invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. +invalid_models.fielderrors: "prepopulate": prepopulate_from should be a list or tuple. +invalid_models.fielderrors: "choices": "choices" should be either a tuple or list. +invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. +invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples. +invalid_models.fielderrors: "index": "db_index" should be either None, True or False. +invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: 'foreign' accessor name 'Target.clash1_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign'. +invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another field. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash1: 'm2m' m2m accessor name 'Target.clash1_set' clashes with another related field. Add a related_name argument to the definition for 'm2m'. +invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: 'foreign_1' accessor name 'Target.id' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash2: 'foreign_2' accessor name 'Target.src_safe' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: 'm2m_1' m2m accessor name 'Target.id' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash2: 'm2m_2' m2m accessor name 'Target.src_safe' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: 'foreign_1' accessor name 'Target2.foreign_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: 'foreign_2' accessor name 'Target2.m2m_tgt' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: 'm2m_1' m2m accessor name 'Target2.foreign_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with a m2m field. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clash3: 'm2m_2' m2m accessor name 'Target2.m2m_tgt' clashes with another related field. Add a related_name argument to the definition for 'm2m_2'. +invalid_models.clashforeign: 'foreign' accessor name 'Target2.clashforeign_set' clashes with another field. Add a related_name argument to the definition for 'foreign'. +invalid_models.clashm2m: 'm2m' m2m accessor name 'Target2.clashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'm2m'. +invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: 'foreign_tgt' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'foreign_tgt'. +invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: 'clashforeign_set' accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashforeign_set'. +invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: 'm2m_tgt' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'm2m_tgt'. +invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with a related m2m field. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.target2: 'clashm2m_set' m2m accessor name 'Target.target2_set' clashes with another related field. Add a related_name argument to the definition for 'clashm2m_set'. +invalid_models.selfclashforeign: 'selfclashforeign_set' accessor name 'SelfClashForeign.selfclashforeign_set' clashes with another field. Add a related_name argument to the definition for 'selfclashforeign_set'. +invalid_models.selfclashforeign: 'foreign_1' accessor name 'SelfClashForeign.id' clashes with another field. Add a related_name argument to the definition for 'foreign_1'. +invalid_models.selfclashforeign: 'foreign_2' accessor name 'SelfClashForeign.src_safe' clashes with another field. Add a related_name argument to the definition for 'foreign_2'. +invalid_models.selfclashm2m: 'selfclashm2m_set' m2m accessor name 'SelfClashM2M.selfclashm2m_set' clashes with a m2m field. Add a related_name argument to the definition for 'selfclashm2m_set'. +invalid_models.selfclashm2m: 'm2m_1' m2m accessor name 'SelfClashM2M.id' clashes with another field. Add a related_name argument to the definition for 'm2m_1'. +invalid_models.selfclashm2m: 'm2m_2' m2m accessor name 'SelfClashM2M.src_safe' clashes with another field. Add a related_name argument to the definition for 'm2m_2'. +""" diff --git a/tests/modeltests/lookup/__init__.py b/tests/modeltests/lookup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/lookup/__init__.py diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py new file mode 100644 index 0000000000..605c05adbe --- /dev/null +++ b/tests/modeltests/lookup/models.py @@ -0,0 +1,179 @@ +""" +7. The lookup API + +This demonstrates features of the database API. +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateTimeField() + class Meta: + ordering = ('-pub_date', 'headline') + + def __repr__(self): + return self.headline + +API_TESTS = """ +# Create a couple of Articles. +>>> from datetime import datetime +>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) +>>> a1.save() +>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) +>>> a2.save() +>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) +>>> a3.save() +>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) +>>> a4.save() +>>> a5 = Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0)) +>>> a5.save() +>>> a6 = Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0)) +>>> a6.save() +>>> a7 = Article(headline='Article 7', pub_date=datetime(2005, 7, 27)) +>>> a7.save() + +# Each QuerySet gets iterator(), which is a generator that "lazily" returns +# results using database-level iteration. +>>> for a in Article.objects.iterator(): +... print a.headline +Article 5 +Article 6 +Article 4 +Article 2 +Article 3 +Article 7 +Article 1 + +# iterator() can be used on any QuerySet. +>>> for a in Article.objects.filter(headline__endswith='4').iterator(): +... print a.headline +Article 4 + +# count() returns the number of objects matching search criteria. +>>> Article.objects.count() +7L +>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).count() +3L +>>> Article.objects.filter(headline__startswith='Blah blah').count() +0L + +# in_bulk() takes a list of IDs and returns a dictionary mapping IDs +# to objects. +>>> Article.objects.in_bulk([1, 2]) +{1: Article 1, 2: Article 2} +>>> Article.objects.in_bulk([3]) +{3: Article 3} +>>> Article.objects.in_bulk([1000]) +{} +>>> Article.objects.in_bulk([]) +{} +>>> Article.objects.in_bulk('foo') +Traceback (most recent call last): + ... +AssertionError: in_bulk() must be provided with a list of IDs. +>>> Article.objects.in_bulk() +Traceback (most recent call last): + ... +TypeError: in_bulk() takes exactly 2 arguments (1 given) +>>> Article.objects.in_bulk(headline__startswith='Blah') +Traceback (most recent call last): + ... +TypeError: in_bulk() got an unexpected keyword argument 'headline__startswith' + +# values() returns a list of dictionaries instead of object instances -- and +# you can specify which fields you want to retrieve. +>>> Article.objects.values('headline') +[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 7'}, {'headline': 'Article 1'}] +>>> Article.objects.filter(pub_date__exact=datetime(2005, 7, 27)).values('id') +[{'id': 2}, {'id': 3}, {'id': 7}] +>>> list(Article.objects.values('id', 'headline')) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 7, 'headline': 'Article 7'}, {'id': 1, 'headline': 'Article 1'}] +True + +>>> for d in Article.objects.values('id', 'headline'): +... i = d.items() +... i.sort() +... i +[('headline', 'Article 5'), ('id', 5)] +[('headline', 'Article 6'), ('id', 6)] +[('headline', 'Article 4'), ('id', 4)] +[('headline', 'Article 2'), ('id', 2)] +[('headline', 'Article 3'), ('id', 3)] +[('headline', 'Article 7'), ('id', 7)] +[('headline', 'Article 1'), ('id', 1)] + +# You can use values() with iterator() for memory savings, because iterator() +# uses database-level iteration. +>>> for d in Article.objects.values('id', 'headline').iterator(): +... i = d.items() +... i.sort() +... i +[('headline', 'Article 5'), ('id', 5)] +[('headline', 'Article 6'), ('id', 6)] +[('headline', 'Article 4'), ('id', 4)] +[('headline', 'Article 2'), ('id', 2)] +[('headline', 'Article 3'), ('id', 3)] +[('headline', 'Article 7'), ('id', 7)] +[('headline', 'Article 1'), ('id', 1)] + +# if you don't specify which fields, all are returned +>>> list(Article.objects.filter(id=5).values()) == [{'id': 5, 'headline': 'Article 5', 'pub_date': datetime(2005, 8, 1, 9, 0)}] +True + +# Every DateField and DateTimeField creates get_next_by_FOO() and +# get_previous_by_FOO() methods. +# In the case of identical date values, these methods will use the ID as a +# fallback check. This guarantees that no records are skipped or duplicated. +>>> a1.get_next_by_pub_date() +Article 2 +>>> a2.get_next_by_pub_date() +Article 3 +>>> a3.get_next_by_pub_date() +Article 7 +>>> a4.get_next_by_pub_date() +Article 6 +>>> a5.get_next_by_pub_date() +Traceback (most recent call last): + ... +DoesNotExist: Article does not exist for ... +>>> a6.get_next_by_pub_date() +Article 5 +>>> a7.get_next_by_pub_date() +Article 4 + +>>> a7.get_previous_by_pub_date() +Article 3 +>>> a6.get_previous_by_pub_date() +Article 4 +>>> a5.get_previous_by_pub_date() +Article 6 +>>> a4.get_previous_by_pub_date() +Article 7 +>>> a3.get_previous_by_pub_date() +Article 2 +>>> a2.get_previous_by_pub_date() +Article 1 + +# Underscores and percent signs have special meaning in the underlying +# database library, but Django handles the quoting of them automatically. +>>> a8 = Article(headline='Article_ with underscore', pub_date=datetime(2005, 11, 20)) +>>> a8.save() +>>> Article.objects.filter(headline__startswith='Article') +[Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.filter(headline__startswith='Article_') +[Article_ with underscore] +>>> a9 = Article(headline='Article% with percent sign', pub_date=datetime(2005, 11, 21)) +>>> a9.save() +>>> Article.objects.filter(headline__startswith='Article') +[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.filter(headline__startswith='Article%') +[Article% with percent sign] + +# exclude() is the opposite of filter() when doing lookups: +>>> Article.objects.filter(headline__contains='Article').exclude(headline__contains='with') +[Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.exclude(headline__startswith="Article_") +[Article% with percent sign, Article 5, Article 6, Article 4, Article 2, Article 3, Article 7, Article 1] +>>> Article.objects.exclude(headline="Article 7") +[Article% with percent sign, Article_ with underscore, Article 5, Article 6, Article 4, Article 2, Article 3, Article 1] +""" diff --git a/tests/modeltests/m2m_and_m2o/__init__.py b/tests/modeltests/m2m_and_m2o/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2m_and_m2o/__init__.py diff --git a/tests/modeltests/m2m_and_m2o/models.py b/tests/modeltests/m2m_and_m2o/models.py new file mode 100644 index 0000000000..29631e5779 --- /dev/null +++ b/tests/modeltests/m2m_and_m2o/models.py @@ -0,0 +1,66 @@ +""" +27. Many-to-many and many-to-one relationships to the same table. + +This is a response to bug #1535 + +""" + +from django.db import models + +class User(models.Model): + username = models.CharField(maxlength=20) + +class Issue(models.Model): + num = models.IntegerField() + cc = models.ManyToManyField(User, blank=True, related_name='test_issue_cc') + client = models.ForeignKey(User, related_name='test_issue_client') + def __repr__(self): + return "<Issue %d>" % (self.num,) + + class Meta: + ordering = ('num',) + + +API_TESTS = """ +>>> Issue.objects.all() +[] +>>> r = User(username='russell') +>>> r.save() +>>> g = User(username='gustav') +>>> g.save() +>>> i = Issue(num=1) +>>> i.client = r +>>> i.validate() +{} +>>> i.save() +>>> i2 = Issue(num=2) +>>> i2.client = r +>>> i2.validate() +{} +>>> i2.save() +>>> i2.cc.add(r) +>>> i3 = Issue(num=3) +>>> i3.client = g +>>> i3.validate() +{} +>>> i3.save() +>>> i3.cc.add(r) +>>> from django.db.models.query import Q +>>> Issue.objects.filter(client=r.id) +[<Issue 1>, <Issue 2>] +>>> Issue.objects.filter(client=g.id) +[<Issue 3>] +>>> Issue.objects.filter(cc__id__exact=g.id) +[] +>>> Issue.objects.filter(cc__id__exact=r.id) +[<Issue 2>, <Issue 3>] + +# Queries that combine results from the m2m and the m2o relationship. +# 3 ways of saying the same thing: +>>> Issue.objects.filter(Q(cc__id__exact=r.id) | Q(client=r.id)) +[<Issue 1>, <Issue 2>, <Issue 3>] +>>> Issue.objects.filter(cc__id__exact=r.id) | Issue.objects.filter(client=r.id) +[<Issue 1>, <Issue 2>, <Issue 3>] +>>> Issue.objects.filter(Q(client=r.id) | Q(cc__id__exact=r.id)) +[<Issue 1>, <Issue 2>, <Issue 3>] +""" diff --git a/tests/modeltests/m2m_intermediary/__init__.py b/tests/modeltests/m2m_intermediary/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2m_intermediary/__init__.py diff --git a/tests/modeltests/m2m_intermediary/models.py b/tests/modeltests/m2m_intermediary/models.py new file mode 100644 index 0000000000..869b188521 --- /dev/null +++ b/tests/modeltests/m2m_intermediary/models.py @@ -0,0 +1,68 @@ +""" +9. Many-to-many relationships via an intermediary table + +For many-to-many relationships that need extra fields on the intermediary +table, use an intermediary model. + +In this example, an ``Article`` can have multiple ``Reporter``s, and each +``Article``-``Reporter`` combination (a ``Writer``) has a ``position`` field, +which specifies the ``Reporter``'s position for the given article (e.g. "Staff +writer"). +""" + +from django.db import models + +class Reporter(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateField() + + def __repr__(self): + return self.headline + +class Writer(models.Model): + reporter = models.ForeignKey(Reporter) + article = models.ForeignKey(Article) + position = models.CharField(maxlength=100) + + def __repr__(self): + return '%r (%s)' % (self.reporter, self.position) + +API_TESTS = """ +# Create a few Reporters. +>>> r1 = Reporter(first_name='John', last_name='Smith') +>>> r1.save() +>>> r2 = Reporter(first_name='Jane', last_name='Doe') +>>> r2.save() + +# Create an Article. +>>> from datetime import datetime +>>> a = Article(headline='This is a test', pub_date=datetime(2005, 7, 27)) +>>> a.save() + +# Create a few Writers. +>>> w1 = Writer(reporter=r1, article=a, position='Main writer') +>>> w1.save() +>>> w2 = Writer(reporter=r2, article=a, position='Contributor') +>>> w2.save() + +# Play around with the API. +>>> a.writer_set.select_related().order_by('-position') +[John Smith (Main writer), Jane Doe (Contributor)] +>>> w1.reporter +John Smith +>>> w2.reporter +Jane Doe +>>> w1.article +This is a test +>>> w2.article +This is a test +>>> r1.writer_set.all() +[John Smith (Main writer)] +""" diff --git a/tests/modeltests/m2m_multiple/__init__.py b/tests/modeltests/m2m_multiple/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2m_multiple/__init__.py diff --git a/tests/modeltests/m2m_multiple/models.py b/tests/modeltests/m2m_multiple/models.py new file mode 100644 index 0000000000..3fec427c1d --- /dev/null +++ b/tests/modeltests/m2m_multiple/models.py @@ -0,0 +1,79 @@ +""" +20. Multiple many-to-many relationships between the same two tables + +In this example, an Article can have many Categories (as "primary") and many +Categories (as "secondary"). + +Set ``related_name`` to designate what the reverse relationship is called. +""" + +from django.db import models + +class Category(models.Model): + name = models.CharField(maxlength=20) + class Meta: + ordering = ('name',) + + def __repr__(self): + return self.name + +class Article(models.Model): + headline = models.CharField(maxlength=50) + pub_date = models.DateTimeField() + primary_categories = models.ManyToManyField(Category, related_name='primary_article_set') + secondary_categories = models.ManyToManyField(Category, related_name='secondary_article_set') + class Meta: + ordering = ('pub_date',) + + def __repr__(self): + return self.headline + +API_TESTS = """ +>>> from datetime import datetime + +>>> c1 = Category(name='Sports') +>>> c1.save() +>>> c2 = Category(name='News') +>>> c2.save() +>>> c3 = Category(name='Crime') +>>> c3.save() +>>> c4 = Category(name='Life') +>>> c4.save() + +>>> a1 = Article(headline='Area man steals', pub_date=datetime(2005, 11, 27)) +>>> a1.save() +>>> a1.primary_categories.add(c2, c3) +>>> a1.secondary_categories.add(c4) + +>>> a2 = Article(headline='Area man runs', pub_date=datetime(2005, 11, 28)) +>>> a2.save() +>>> a2.primary_categories.add(c1, c2) +>>> a2.secondary_categories.add(c4) + +>>> a1.primary_categories.all() +[Crime, News] + +>>> a2.primary_categories.all() +[News, Sports] + +>>> a1.secondary_categories.all() +[Life] + + +>>> c1.primary_article_set.all() +[Area man runs] +>>> c1.secondary_article_set.all() +[] +>>> c2.primary_article_set.all() +[Area man steals, Area man runs] +>>> c2.secondary_article_set.all() +[] +>>> c3.primary_article_set.all() +[Area man steals] +>>> c3.secondary_article_set.all() +[] +>>> c4.primary_article_set.all() +[] +>>> c4.secondary_article_set.all() +[Area man steals, Area man runs] +""" diff --git a/tests/modeltests/m2m_recursive/__init__.py b/tests/modeltests/m2m_recursive/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2m_recursive/__init__.py diff --git a/tests/modeltests/m2m_recursive/models.py b/tests/modeltests/m2m_recursive/models.py new file mode 100644 index 0000000000..ff8a5a8f47 --- /dev/null +++ b/tests/modeltests/m2m_recursive/models.py @@ -0,0 +1,192 @@ +""" +26. Many-to-many relationships between the same two tables + +In this example, A Person can have many friends, who are also people. Friendship is a +symmetrical relationshiup - if I am your friend, you are my friend. + +A person can also have many idols - but while I may idolize you, you may not think +the same of me. 'Idols' is an example of a non-symmetrical m2m field. Only recursive +m2m fields may be non-symmetrical, and they are symmetrical by default. + +This test validates that the m2m table will create a mangled name for the m2m table if +there will be a clash, and tests that symmetry is preserved where appropriate. +""" + +from django.db import models + +class Person(models.Model): + name = models.CharField(maxlength=20) + friends = models.ManyToManyField('self') + idols = models.ManyToManyField('self', symmetrical=False, related_name='stalkers') + + def __repr__(self): + return self.name + +API_TESTS = """ +>>> a = Person(name='Anne') +>>> a.save() +>>> b = Person(name='Bill') +>>> b.save() +>>> c = Person(name='Chuck') +>>> c.save() +>>> d = Person(name='David') +>>> d.save() + +# Add some friends in the direction of field definition +# Anne is friends with Bill and Chuck +>>> a.friends.add(b,c) + +# David is friends with Anne and Chuck - add in reverse direction +>>> d.friends.add(a,c) + +# Who is friends with Anne? +>>> a.friends.all() +[Bill, Chuck, David] + +# Who is friends with Bill? +>>> b.friends.all() +[Anne] + +# Who is friends with Chuck? +>>> c.friends.all() +[Anne, David] + +# Who is friends with David? +>>> d.friends.all() +[Anne, Chuck] + +# Bill is already friends with Anne - add Anne again, but in the reverse direction +>>> b.friends.add(a) + +# Who is friends with Anne? +>>> a.friends.all() +[Bill, Chuck, David] + +# Who is friends with Bill? +>>> b.friends.all() +[Anne] + +# Remove Anne from Bill's friends +>>> b.friends.remove(a) + +# Who is friends with Anne? +>>> a.friends.all() +[Chuck, David] + +# Who is friends with Bill? +>>> b.friends.all() +[] + +# Clear Anne's group of friends +>>> a.friends.clear() + +# Who is friends with Anne? +>>> a.friends.all() +[] + +# Reverse relationships should also be gone +# Who is friends with Chuck? +>>> c.friends.all() +[David] + +# Who is friends with David? +>>> d.friends.all() +[Chuck] + + +# Add some idols in the direction of field definition +# Anne idolizes Bill and Chuck +>>> a.idols.add(b,c) + +# Bill idolizes Anne right back +>>> b.idols.add(a) + +# David is idolized by Anne and Chuck - add in reverse direction +>>> d.stalkers.add(a,c) + +# Who are Anne's idols? +>>> a.idols.all() +[Bill, Chuck, David] + +# Who is stalking Anne? +>>> a.stalkers.all() +[Bill] + +# Who are Bill's idols? +>>> b.idols.all() +[Anne] + +# Who is stalking Bill? +>>> b.stalkers.all() +[Anne] + +# Who are Chuck's idols? +>>> c.idols.all() +[David] + +# Who is stalking Chuck? +>>> c.stalkers.all() +[Anne] + +# Who are David's idols? +>>> d.idols.all() +[] + +# Who is stalking David +>>> d.stalkers.all() +[Anne, Chuck] + +# Bill is already being stalked by Anne - add Anne again, but in the reverse direction +>>> b.stalkers.add(a) + +# Who are Anne's idols? +>>> a.idols.all() +[Bill, Chuck, David] + +# Who is stalking Anne? +[Bill] + +# Who are Bill's idols +>>> b.idols.all() +[Anne] + +# Who is stalking Bill? +>>> b.stalkers.all() +[Anne] + +# Remove Anne from Bill's list of stalkers +>>> b.stalkers.remove(a) + +# Who are Anne's idols? +>>> a.idols.all() +[Chuck, David] + +# Who is stalking Anne? +>>> a.stalkers.all() +[Bill] + +# Who are Bill's idols? +>>> b.idols.all() +[Anne] + +# Who is stalking Bill? +>>> b.stalkers.all() +[] + +# Clear Anne's group of idols +>>> a.idols.clear() + +# Who are Anne's idols +>>> a.idols.all() +[] + +# Reverse relationships should also be gone +# Who is stalking Chuck? +>>> c.stalkers.all() +[] + +# Who is friends with David? +>>> d.stalkers.all() +[Chuck] + +""" diff --git a/tests/modeltests/m2o_recursive/__init__.py b/tests/modeltests/m2o_recursive/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2o_recursive/__init__.py diff --git a/tests/modeltests/m2o_recursive/models.py b/tests/modeltests/m2o_recursive/models.py new file mode 100644 index 0000000000..e7996bc15f --- /dev/null +++ b/tests/modeltests/m2o_recursive/models.py @@ -0,0 +1,40 @@ +""" +11. Relating an object to itself, many-to-one + +To define a many-to-one relationship between a model and itself, use +``ForeignKey('self')``. + +In this example, a ``Category`` is related to itself. That is, each +``Category`` has a parent ``Category``. + +Set ``related_name`` to designate what the reverse relationship is called. +""" + +from django.db import models + +class Category(models.Model): + name = models.CharField(maxlength=20) + parent = models.ForeignKey('self', null=True, related_name='child_set') + + def __repr__(self): + return self.name + +API_TESTS = """ +# Create a few Category objects. +>>> r = Category(id=None, name='Root category', parent=None) +>>> r.save() +>>> c = Category(id=None, name='Child category', parent=r) +>>> c.save() + +>>> r.child_set.all() +[Child category] +>>> r.child_set.get(name__startswith='Child') +Child category +>>> print r.parent +None + +>>> c.child_set.all() +[] +>>> c.parent +Root category +""" diff --git a/tests/modeltests/m2o_recursive2/__init__.py b/tests/modeltests/m2o_recursive2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/m2o_recursive2/__init__.py diff --git a/tests/modeltests/m2o_recursive2/models.py b/tests/modeltests/m2o_recursive2/models.py new file mode 100644 index 0000000000..40b842dc92 --- /dev/null +++ b/tests/modeltests/m2o_recursive2/models.py @@ -0,0 +1,43 @@ +""" +12. Relating a model to another model more than once + +In this example, a ``Person`` can have a ``mother`` and ``father`` -- both of +which are other ``Person`` objects. + +Set ``related_name`` to designate what the reverse relationship is called. +""" + +from django.db import models + +class Person(models.Model): + full_name = models.CharField(maxlength=20) + mother = models.ForeignKey('self', null=True, related_name='mothers_child_set') + father = models.ForeignKey('self', null=True, related_name='fathers_child_set') + + def __repr__(self): + return self.full_name + +API_TESTS = """ +# Create two Person objects -- the mom and dad in our family. +>>> dad = Person(full_name='John Smith Senior', mother=None, father=None) +>>> dad.save() +>>> mom = Person(full_name='Jane Smith', mother=None, father=None) +>>> mom.save() + +# Give mom and dad a kid. +>>> kid = Person(full_name='John Smith Junior', mother=mom, father=dad) +>>> kid.save() + +>>> kid.mother +Jane Smith +>>> kid.father +John Smith Senior +>>> dad.fathers_child_set.all() +[John Smith Junior] +>>> mom.mothers_child_set.all() +[John Smith Junior] +>>> kid.mothers_child_set.all() +[] +>>> kid.fathers_child_set.all() +[] +""" diff --git a/tests/modeltests/manipulators/__init__.py b/tests/modeltests/manipulators/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/manipulators/__init__.py diff --git a/tests/modeltests/manipulators/models.py b/tests/modeltests/manipulators/models.py new file mode 100644 index 0000000000..da47c0afd5 --- /dev/null +++ b/tests/modeltests/manipulators/models.py @@ -0,0 +1,89 @@ +""" +25. Default manipulators +""" + +from django.db import models + +class Musician(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +class Album(models.Model): + name = models.CharField(maxlength=100) + musician = models.ForeignKey(Musician) + release_date = models.DateField(blank=True, null=True) + + def __repr__(self): + return self.name + +API_TESTS = """ +>>> from django.utils.datastructures import MultiValueDict + +# Create a Musician object via the default AddManipulator. +>>> man = Musician.AddManipulator() +>>> data = MultiValueDict({'first_name': ['Ella'], 'last_name': ['Fitzgerald']}) + +>>> man.get_validation_errors(data) +{} +>>> man.do_html2python(data) +>>> m1 = man.save(data) + +# Verify it worked. +>>> Musician.objects.all() +[Ella Fitzgerald] +>>> [m1] == list(Musician.objects.all()) +True + +# Attempt to add a Musician without a first_name. +>>> man.get_validation_errors(MultiValueDict({'last_name': ['Blakey']})) +{'first_name': ['This field is required.']} + +# Attempt to add a Musician without a first_name and last_name. +>>> man.get_validation_errors(MultiValueDict({})) +{'first_name': ['This field is required.'], 'last_name': ['This field is required.']} + +# Attempt to create an Album without a name or musician. +>>> man = Album.AddManipulator() +>>> man.get_validation_errors(MultiValueDict({})) +{'musician': ['This field is required.'], 'name': ['This field is required.']} + +# Attempt to create an Album with an invalid musician. +>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['foo']})) +{'musician': ["Select a valid choice; 'foo' is not in ['', '1']."]} + +# Attempt to create an Album with an invalid release_date. +>>> man.get_validation_errors(MultiValueDict({'name': ['Sallies Fforth'], 'musician': ['1'], 'release_date': 'today'})) +{'release_date': ['Enter a valid date in YYYY-MM-DD format.']} + +# Create an Album without a release_date (because it's optional). +>>> data = MultiValueDict({'name': ['Ella and Basie'], 'musician': ['1']}) +>>> man.get_validation_errors(data) +{} +>>> man.do_html2python(data) +>>> a1 = man.save(data) + +# Verify it worked. +>>> Album.objects.all() +[Ella and Basie] +>>> Album.objects.get().musician +Ella Fitzgerald + +# Create an Album with a release_date. +>>> data = MultiValueDict({'name': ['Ultimate Ella'], 'musician': ['1'], 'release_date': ['2005-02-13']}) +>>> man.get_validation_errors(data) +{} +>>> man.do_html2python(data) +>>> a2 = man.save(data) + +# Verify it worked. +>>> Album.objects.order_by('name') +[Ella and Basie, Ultimate Ella] +>>> a2 = Album.objects.get(pk=2) +>>> a2 +Ultimate Ella +>>> a2.release_date +datetime.date(2005, 2, 13) +""" diff --git a/tests/modeltests/many_to_many/__init__.py b/tests/modeltests/many_to_many/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/many_to_many/__init__.py diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py new file mode 100644 index 0000000000..dc69f9a49f --- /dev/null +++ b/tests/modeltests/many_to_many/models.py @@ -0,0 +1,206 @@ +""" +5. Many-to-many relationships + +To define a many-to-many relationship, use ManyToManyField(). + +In this example, an article can be published in multiple publications, +and a publication has multiple articles. +""" + +from django.db import models + +class Publication(models.Model): + title = models.CharField(maxlength=30) + + def __repr__(self): + return self.title + + class Meta: + ordering = ('title',) + +class Article(models.Model): + headline = models.CharField(maxlength=100) + publications = models.ManyToManyField(Publication) + + def __repr__(self): + return self.headline + + class Meta: + ordering = ('headline',) + +API_TESTS = """ +# Create a couple of Publications. +>>> p1 = Publication(id=None, title='The Python Journal') +>>> p1.save() +>>> p2 = Publication(id=None, title='Science News') +>>> p2.save() +>>> p3 = Publication(id=None, title='Science Weekly') +>>> p3.save() + +# Create an Article. +>>> a1 = Article(id=None, headline='Django lets you build Web apps easily') +>>> a1.save() + +# Associate the Article with a Publication. +>>> a1.publications.add(p1) + +# Create another Article, and set it to appear in both Publications. +>>> a2 = Article(id=None, headline='NASA uses Python') +>>> a2.save() +>>> a2.publications.add(p1, p2) +>>> a2.publications.add(p3) + +# Adding a second time is OK +>>> a2.publications.add(p3) + +# Add a Publication directly via publications.add by using keyword arguments. +>>> new_publication = a2.publications.create(title='Highlights for Children') + +# Article objects have access to their related Publication objects. +>>> a1.publications.all() +[The Python Journal] +>>> a2.publications.all() +[Highlights for Children, Science News, Science Weekly, The Python Journal] + +# Publication objects have access to their related Article objects. +>>> p2.article_set.all() +[NASA uses Python] +>>> p1.article_set.all() +[Django lets you build Web apps easily, NASA uses Python] +>>> Publication.objects.get(id=4).article_set.all() +[NASA uses Python] + +# We can perform kwarg queries across m2m relationships +>>> Article.objects.filter(publications__id__exact=1) +[Django lets you build Web apps easily, NASA uses Python] +>>> Article.objects.filter(publications__pk=1) +[Django lets you build Web apps easily, NASA uses Python] + +>>> Article.objects.filter(publications__title__startswith="Science") +[NASA uses Python, NASA uses Python] + +>>> Article.objects.filter(publications__title__startswith="Science").distinct() +[NASA uses Python] + +# Reverse m2m queries are supported (i.e., starting at the table that doesn't +# have a ManyToManyField). +>>> Publication.objects.filter(id__exact=1) +[The Python Journal] +>>> Publication.objects.filter(pk=1) +[The Python Journal] + +>>> Publication.objects.filter(article__headline__startswith="NASA") +[Highlights for Children, Science News, Science Weekly, The Python Journal] + +>>> Publication.objects.filter(article__id__exact=1) +[The Python Journal] + +>>> Publication.objects.filter(article__pk=1) +[The Python Journal] + +# If we delete a Publication, its Articles won't be able to access it. +>>> p1.delete() +>>> Publication.objects.all() +[Highlights for Children, Science News, Science Weekly] +>>> a1 = Article.objects.get(pk=1) +>>> a1.publications.all() +[] + +# If we delete an Article, its Publications won't be able to access it. +>>> a2.delete() +>>> Article.objects.all() +[Django lets you build Web apps easily] +>>> p1.article_set.all() +[Django lets you build Web apps easily] + +# Adding via the 'other' end of an m2m +>>> a4 = Article(headline='NASA finds intelligent life on Earth') +>>> a4.save() +>>> p2.article_set.add(a4) +>>> p2.article_set.all() +[NASA finds intelligent life on Earth] +>>> a4.publications.all() +[Science News] + +# Adding via the other end using keywords +>>> new_article = p2.article_set.create(headline='Oxygen-free diet works wonders') +>>> p2.article_set.all() +[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +>>> a5 = p2.article_set.all()[1] +>>> a5.publications.all() +[Science News] + +# Removing publication from an article: +>>> a4.publications.remove(p2) +>>> p2.article_set.all() +[Oxygen-free diet works wonders] +>>> a4.publications.all() +[] + +# And from the other end +>>> p2.article_set.remove(a5) +>>> p2.article_set.all() +[] +>>> a5.publications.all() +[] + +# Relation sets can be assigned. Assignment clears any existing set members +>>> p2.article_set = [a4, a5] +>>> p2.article_set.all() +[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +>>> a4.publications.all() +[Science News] +>>> a4.publications = [p3] +>>> p2.article_set.all() +[Oxygen-free diet works wonders] +>>> a4.publications.all() +[Science Weekly] + +# Relation sets can be cleared: +>>> p2.article_set.clear() +>>> p2.article_set.all() +[] +>>> a4.publications.all() +[Science Weekly] + +# And you can clear from the other end +>>> p2.article_set.add(a4, a5) +>>> p2.article_set.all() +[NASA finds intelligent life on Earth, Oxygen-free diet works wonders] +>>> a4.publications.all() +[Science News, Science Weekly] +>>> a4.publications.clear() +>>> a4.publications.all() +[] +>>> p2.article_set.all() +[Oxygen-free diet works wonders] + +# Recreate the article and Publication we just deleted. +>>> p1 = Publication(id=None, title='The Python Journal') +>>> p1.save() +>>> a2 = Article(id=None, headline='NASA uses Python') +>>> a2.save() +>>> a2.publications.add(p1, p2, p3) + +# Bulk delete some Publications - references to deleted publications should go +>>> Publication.objects.filter(title__startswith='Science').delete() +>>> Publication.objects.all() +[Highlights for Children, The Python Journal] +>>> Article.objects.all() +[Django lets you build Web apps easily, NASA finds intelligent life on Earth, NASA uses Python, Oxygen-free diet works wonders] +>>> a2.publications.all() +[The Python Journal] + +# Bulk delete some articles - references to deleted objects should go +>>> q = Article.objects.filter(headline__startswith='Django') +>>> print q +[Django lets you build Web apps easily] +>>> q.delete() + +# After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone +>>> print q +[] +>>> p1.article_set.all() +[NASA uses Python] + +""" diff --git a/tests/modeltests/many_to_one/__init__.py b/tests/modeltests/many_to_one/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/many_to_one/__init__.py diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py new file mode 100644 index 0000000000..165a36c91c --- /dev/null +++ b/tests/modeltests/many_to_one/models.py @@ -0,0 +1,232 @@ +""" +4. Many-to-one relationships + +To define a many-to-one relationship, use ``ForeignKey()`` . +""" + +from django.db import models + +class Reporter(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + email = models.EmailField() + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateField() + reporter = models.ForeignKey(Reporter) + + def __repr__(self): + return self.headline + + class Meta: + ordering = ('headline',) + +API_TESTS = """ +# Create a few Reporters. +>>> r = Reporter(first_name='John', last_name='Smith', email='john@example.com') +>>> r.save() + +>>> r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') +>>> r2.save() + +# Create an Article. +>>> from datetime import datetime +>>> a = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter=r) +>>> a.save() + +>>> a.reporter.id +1 + +>>> a.reporter +John Smith + +# Article objects have access to their related Reporter objects. +>>> r = a.reporter +>>> r.first_name, r.last_name +('John', 'Smith') + +# Create an Article via the Reporter object. +>>> new_article = r.article_set.create(headline="John's second story", pub_date=datetime(2005, 7, 29)) +>>> new_article +John's second story +>>> new_article.reporter.id +1 + +# Create a new article, and add it to the article set. +>>> new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) +>>> r.article_set.add(new_article2) +>>> new_article2.reporter.id +1 +>>> r.article_set.all() +[John's second story, Paul's story, This is a test] + +# Add the same article to a different article set - check that it moves. +>>> r2.article_set.add(new_article2) +>>> new_article2.reporter.id +2 +>>> r.article_set.all() +[John's second story, This is a test] +>>> r2.article_set.all() +[Paul's story] + +# Assign the article to the reporter directly using the descriptor +>>> new_article2.reporter = r +>>> new_article2.save() +>>> new_article2.reporter +John Smith +>>> new_article2.reporter.id +1 +>>> r.article_set.all() +[John's second story, Paul's story, This is a test] +>>> r2.article_set.all() +[] + +# Set the article back again using set descriptor. +>>> r2.article_set = [new_article, new_article2] +>>> r.article_set.all() +[This is a test] +>>> r2.article_set.all() +[John's second story, Paul's story] + +# Funny case - assignment notation can only go so far; because the +# ForeignKey cannot be null, existing members of the set must remain +>>> r.article_set = [new_article] +>>> r.article_set.all() +[John's second story, This is a test] +>>> r2.article_set.all() +[Paul's story] + +# Reporter cannot be null - there should not be a clear or remove method +>>> hasattr(r2.article_set, 'remove') +False +>>> hasattr(r2.article_set, 'clear') +False + +# Reporter objects have access to their related Article objects. +>>> r.article_set.all() +[John's second story, This is a test] + +>>> r.article_set.filter(headline__startswith='This') +[This is a test] + +>>> r.article_set.count() +2 + +>>> r2.article_set.count() +1 + +# Get articles by id +>>> Article.objects.filter(id__exact=1) +[This is a test] +>>> Article.objects.filter(pk=1) +[This is a test] + +# Query on an article property +>>> Article.objects.filter(headline__startswith='This') +[This is a test] + +# The API automatically follows relationships as far as you need. +# Use double underscores to separate relationships. +# This works as many levels deep as you want. There's no limit. +# Find all Articles for any Reporter whose first name is "John". +>>> Article.objects.filter(reporter__first_name__exact='John') +[John's second story, This is a test] + +# Query twice over the related field. +>>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') +[John's second story, This is a test] + +# The underlying query only makes one join when a related table is referenced twice. +>>> query = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') +>>> null, sql, null = query._get_sql_clause() +>>> sql.count('INNER JOIN') +1 + +# The automatically joined table has a predictable name. +>>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_article__reporter.last_name='Smith'"]) +[John's second story, This is a test] + +# Find all Articles for the Reporter whose ID is 1. +>>> Article.objects.filter(reporter__id__exact=1) +[John's second story, This is a test] +>>> Article.objects.filter(reporter__pk=1) +[John's second story, This is a test] + +# You need two underscores between "reporter" and "id" -- not one. +>>> Article.objects.filter(reporter_id__exact=1) +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'reporter_id' into field + +# You need to specify a comparison clause +>>> Article.objects.filter(reporter_id=1) +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'reporter_id' into field + +# "pk" shortcut syntax works in a related context, too. +>>> Article.objects.filter(reporter__pk=1) +[John's second story, This is a test] + +# You can also instantiate an Article by passing +# the Reporter's ID instead of a Reporter object. +>>> a3 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id=r.id) +>>> a3.save() +>>> a3.reporter.id +1 +>>> a3.reporter +John Smith + +# Similarly, the reporter ID can be a string. +>>> a4 = Article(id=None, headline="This is a test", pub_date=datetime(2005, 7, 27), reporter_id="1") +>>> a4.save() +>>> a4.reporter +John Smith + +# Reporters can be queried +>>> Reporter.objects.filter(id__exact=1) +[John Smith] +>>> Reporter.objects.filter(pk=1) +[John Smith] +>>> Reporter.objects.filter(first_name__startswith='John') +[John Smith] + +# Reporters can query in opposite direction of ForeignKey definition +>>> Reporter.objects.filter(article__id__exact=1) +[John Smith] +>>> Reporter.objects.filter(article__pk=1) +[John Smith] +>>> Reporter.objects.filter(article__headline__startswith='This') +[John Smith, John Smith, John Smith] +>>> Reporter.objects.filter(article__headline__startswith='This').distinct() +[John Smith] + +# Queries can go round in circles. +>>> Reporter.objects.filter(article__reporter__first_name__startswith='John') +[John Smith, John Smith, John Smith, John Smith] +>>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() +[John Smith] + +# If you delete a reporter, his articles will be deleted. +>>> Article.objects.all() +[John's second story, Paul's story, This is a test, This is a test, This is a test] +>>> Reporter.objects.order_by('first_name') +[John Smith, Paul Jones] +>>> r2.delete() +>>> Article.objects.all() +[John's second story, This is a test, This is a test, This is a test] +>>> Reporter.objects.order_by('first_name') +[John Smith] + +# Deletes using a join in the query +>>> Reporter.objects.filter(article__headline__startswith='This').delete() +>>> Reporter.objects.all() +[] +>>> Article.objects.all() +[] + +""" diff --git a/tests/modeltests/many_to_one_null/__init__.py b/tests/modeltests/many_to_one_null/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/many_to_one_null/__init__.py diff --git a/tests/modeltests/many_to_one_null/models.py b/tests/modeltests/many_to_one_null/models.py new file mode 100644 index 0000000000..6818493ee3 --- /dev/null +++ b/tests/modeltests/many_to_one_null/models.py @@ -0,0 +1,124 @@ +""" +16. Many-to-one relationships that can be null + +To define a many-to-one relationship that can have a null foreign key, use +``ForeignKey()`` with ``null=True`` . +""" + +from django.db import models + +class Reporter(models.Model): + name = models.CharField(maxlength=30) + + def __repr__(self): + return self.name + +class Article(models.Model): + headline = models.CharField(maxlength=100) + reporter = models.ForeignKey(Reporter, null=True) + + def __repr__(self): + return self.headline + + class Meta: + ordering = ('headline',) + +API_TESTS = """ +# Create a Reporter. +>>> r = Reporter(name='John Smith') +>>> r.save() + +# Create an Article. +>>> a = Article(headline="First", reporter=r) +>>> a.save() + +>>> a.reporter.id +1 + +>>> a.reporter +John Smith + +# Article objects have access to their related Reporter objects. +>>> r = a.reporter + +# Create an Article via the Reporter object. +>>> a2 = r.article_set.create(headline="Second") +>>> a2 +Second +>>> a2.reporter.id +1 + +# Reporter objects have access to their related Article objects. +>>> r.article_set.all() +[First, Second] +>>> r.article_set.filter(headline__startswith='Fir') +[First] +>>> r.article_set.count() +2 + +# Create an Article with no Reporter by passing "reporter=None". +>>> a3 = Article(headline="Third", reporter=None) +>>> a3.save() +>>> a3.id +3 +>>> print a3.reporter +None + +# Need to reget a3 to refresh the cache +>>> a3 = Article.objects.get(pk=3) +>>> print a3.reporter.id +Traceback (most recent call last): + ... +AttributeError: 'NoneType' object has no attribute 'id' + +# Accessing an article's 'reporter' attribute returns None +# if the reporter is set to None. +>>> print a3.reporter +None + +# To retrieve the articles with no reporters set, use "reporter__isnull=True". +>>> Article.objects.filter(reporter__isnull=True) +[Third] + +# Set the reporter for the Third article +>>> r.article_set.add(a3) +>>> r.article_set.all() +[First, Second, Third] + +# Remove an article from the set, and check that it was removed. +>>> r.article_set.remove(a3) +>>> r.article_set.all() +[First, Second] +>>> Article.objects.filter(reporter__isnull=True) +[Third] + +# Create another article and reporter +>>> r2 = Reporter(name='Paul Jones') +>>> r2.save() +>>> a4 = r2.article_set.create(headline='Fourth') +>>> r2.article_set.all() +[Fourth] + +# Try to remove a4 from a set it does not belong to +>>> r.article_set.remove(a4) +Traceback (most recent call last): +... +DoesNotExist: 'Fourth' is not related to 'John Smith'. + +>>> r2.article_set.all() +[Fourth] + +# Use descriptor assignment to allocate ForeignKey. Null is legal, so +# existing members of set that are not in the assignment set are set null +>>> r2.article_set = [a2, a3] +>>> r2.article_set.all() +[Second, Third] + +# Clear the rest of the set +>>> r.article_set.clear() +>>> r.article_set.all() +[] +>>> Article.objects.filter(reporter__isnull=True) +[First, Fourth] + +""" diff --git a/tests/modeltests/model_inheritance/__init__.py b/tests/modeltests/model_inheritance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/model_inheritance/__init__.py diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py new file mode 100644 index 0000000000..cdc4b4e2ac --- /dev/null +++ b/tests/modeltests/model_inheritance/models.py @@ -0,0 +1,52 @@ +""" +XX. Model inheritance + +""" + +from django.db import models + +class Place(models.Model): + name = models.CharField(maxlength=50) + address = models.CharField(maxlength=80) + + def __repr__(self): + return "%s the place" % self.name + +class Restaurant(Place): + serves_hot_dogs = models.BooleanField() + serves_pizza = models.BooleanField() + + def __repr__(self): + return "%s the restaurant" % self.name + +class ItalianRestaurant(Restaurant): + serves_gnocchi = models.BooleanField() + + def __repr__(self): + return "%s the italian restaurant" % self.name + +API_TESTS = """ +# Make sure Restaurant has the right fields in the right order. +>>> [f.name for f in Restaurant._meta.fields] +['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza'] + +# Make sure ItalianRestaurant has the right fields in the right order. +>>> [f.name for f in ItalianRestaurant._meta.fields] +['id', 'name', 'address', 'serves_hot_dogs', 'serves_pizza', 'serves_gnocchi'] + +# Create a couple of Places. +>>> p1 = Place(name='Master Shakes', address='666 W. Jersey') +>>> p1.save() +>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') +>>> p2.save() + +# Test constructor for Restaurant. +>>> r = Restaurant(name='Demon Dogs', address='944 W. Fullerton', serves_hot_dogs=True, serves_pizza=False) +>>> r.save() + +# Test the constructor for ItalianRestaurant. +>>> ir = ItalianRestaurant(name='Ristorante Miron', address='1234 W. Elm', serves_hot_dogs=False, serves_pizza=False, serves_gnocchi=True) +>>> ir.save() + + +""" diff --git a/tests/modeltests/mutually_referential/__init__.py b/tests/modeltests/mutually_referential/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/mutually_referential/__init__.py diff --git a/tests/modeltests/mutually_referential/models.py b/tests/modeltests/mutually_referential/models.py new file mode 100644 index 0000000000..07b52effbc --- /dev/null +++ b/tests/modeltests/mutually_referential/models.py @@ -0,0 +1,32 @@ +""" +24. Mutually referential many-to-one relationships + +To define a many-to-one relationship, use ``ForeignKey()`` . +""" + +from django.db.models import * + +class Parent(Model): + name = CharField(maxlength=100, core=True) + bestchild = ForeignKey("Child", null=True, related_name="favoured_by") + +class Child(Model): + name = CharField(maxlength=100) + parent = ForeignKey(Parent) + +API_TESTS = """ +# Create a Parent +>>> q = Parent(name='Elizabeth') +>>> q.save() + +# Create some children +>>> c = q.child_set.create(name='Charles') +>>> e = q.child_set.create(name='Edward') + +# Set the best child +>>> q.bestchild = c +>>> q.save() + +>>> q.delete() + +"""
\ No newline at end of file diff --git a/tests/modeltests/one_to_one/__init__.py b/tests/modeltests/one_to_one/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/one_to_one/__init__.py diff --git a/tests/modeltests/one_to_one/models.py b/tests/modeltests/one_to_one/models.py new file mode 100644 index 0000000000..ef8a166afb --- /dev/null +++ b/tests/modeltests/one_to_one/models.py @@ -0,0 +1,130 @@ +""" +10. One-to-one relationships + +To define a one-to-one relationship, use ``OneToOneField()``. + +In this example, a ``Place`` optionally can be a ``Restaurant``. +""" + +from django.db import models + +class Place(models.Model): + name = models.CharField(maxlength=50) + address = models.CharField(maxlength=80) + + def __repr__(self): + return "%s the place" % self.name + +class Restaurant(models.Model): + place = models.OneToOneField(Place) + serves_hot_dogs = models.BooleanField() + serves_pizza = models.BooleanField() + + def __repr__(self): + return "%s the restaurant" % self.place.name + +class Waiter(models.Model): + restaurant = models.ForeignKey(Restaurant) + name = models.CharField(maxlength=50) + + def __repr__(self): + return "%s the waiter at %r" % (self.name, self.restaurant) + +API_TESTS = """ +# Create a couple of Places. +>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton') +>>> p1.save() +>>> p2 = Place(name='Ace Hardware', address='1013 N. Ashland') +>>> p2.save() + +# Create a Restaurant. Pass the ID of the "parent" object as this object's ID. +>>> r = Restaurant(place=p1, serves_hot_dogs=True, serves_pizza=False) +>>> r.save() + +# A Restaurant can access its place. +>>> r.place +Demon Dogs the place + +# A Place can access its restaurant, if available. +>>> p1.restaurant +Demon Dogs the restaurant + +# p2 doesn't have an associated restaurant. +>>> p2.restaurant +Traceback (most recent call last): + ... +DoesNotExist: Restaurant does not exist for {'place__pk': ...} + +# Set the place using assignment notation. Because place is the primary key on Restaurant, +# the save will create a new restaurant +>>> r.place = p2 +>>> r.save() +>>> p2.restaurant +Ace Hardware the restaurant +>>> r.place +Ace Hardware the place + +# Set the place back again, using assignment in the reverse direction +# Need to reget restaurant object first, because the reverse set +# can't update the existing restaurant instance +>>> p1.restaurant = r +>>> r.save() +>>> p1.restaurant +Demon Dogs the restaurant + +>>> r = Restaurant.objects.get(pk=1) +>>> r.place +Demon Dogs the place + +# Restaurant.objects.all() just returns the Restaurants, not the Places. +# Note that there are two restaurants - Ace Hardware the Restaurant was created +# in the call to r.place = p2. This means there are multiple restaurants referencing +# a single place... +>>> Restaurant.objects.all() +[Demon Dogs the restaurant, Ace Hardware the restaurant] + +# Place.objects.all() returns all Places, regardless of whether they have +# Restaurants. +>>> Place.objects.order_by('name') +[Ace Hardware the place, Demon Dogs the place] + +>>> Restaurant.objects.get(place__id__exact=1) +Demon Dogs the restaurant +>>> Restaurant.objects.get(pk=1) +Demon Dogs the restaurant +>>> Restaurant.objects.get(place__exact=1) +Demon Dogs the restaurant +>>> Restaurant.objects.get(place__pk=1) +Demon Dogs the restaurant +>>> Restaurant.objects.get(place__name__startswith="Demon") +Demon Dogs the restaurant + +>>> Place.objects.get(id__exact=1) +Demon Dogs the place +>>> Place.objects.get(pk=1) +Demon Dogs the place +>>> Place.objects.get(restaurant__place__exact=1) +Demon Dogs the place +>>> Place.objects.get(restaurant__pk=1) +Demon Dogs the place + +# Add a Waiter to the Restaurant. +>>> w = r.waiter_set.create(name='Joe') +>>> w.save() +>>> w +Joe the waiter at Demon Dogs the restaurant + +# Query the waiters +>>> Waiter.objects.filter(restaurant__place__exact=1) +[Joe the waiter at Demon Dogs the restaurant] +>>> Waiter.objects.filter(restaurant__pk=1) +[Joe the waiter at Demon Dogs the restaurant] +>>> Waiter.objects.filter(id__exact=1) +[Joe the waiter at Demon Dogs the restaurant] +>>> Waiter.objects.filter(pk=1) +[Joe the waiter at Demon Dogs the restaurant] + +# Delete the restaurant; the waiter should also be removed +>>> r = Restaurant.objects.get(pk=1) +>>> r.delete() +""" diff --git a/tests/modeltests/or_lookups/__init__.py b/tests/modeltests/or_lookups/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/or_lookups/__init__.py diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py new file mode 100644 index 0000000000..9d62a1266c --- /dev/null +++ b/tests/modeltests/or_lookups/models.py @@ -0,0 +1,89 @@ +""" +19. OR lookups + +To perform an OR lookup, or a lookup that combines ANDs and ORs, +combine QuerySet objects using & and | operators. + +Alternatively, use positional arguments, and pass one or more expressions +of clauses using the variable ``django.db.models.Q`` (or any object with +a get_sql method). + + +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=50) + pub_date = models.DateTimeField() + class Meta: + ordering = ('pub_date',) + + def __repr__(self): + return self.headline + +API_TESTS = """ +>>> from datetime import datetime +>>> from django.db.models import Q + +>>> a1 = Article(headline='Hello', pub_date=datetime(2005, 11, 27)) +>>> a1.save() + +>>> a2 = Article(headline='Goodbye', pub_date=datetime(2005, 11, 28)) +>>> a2.save() + +>>> a3 = Article(headline='Hello and goodbye', pub_date=datetime(2005, 11, 29)) +>>> a3.save() + +>>> Article.objects.filter(headline__startswith='Hello') | Article.objects.filter(headline__startswith='Goodbye') +[Hello, Goodbye, Hello and goodbye] + +>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__startswith='Goodbye')) +[Hello, Goodbye, Hello and goodbye] + +>>> Article.objects.filter(Q(headline__startswith='Hello') & Q(headline__startswith='Goodbye')) +[] + +>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__startswith='Goodbye') +[] + +>>> Article.objects.filter(headline__startswith='Hello') & Article.objects.filter(headline__contains='bye') +[Hello and goodbye] + +>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') +[Hello and goodbye] + +>>> Article.objects.filter(headline__contains='Hello') | Article.objects.filter(headline__contains='bye') +[Hello, Goodbye, Hello and goodbye] + +>>> Article.objects.filter(headline__iexact='Hello') | Article.objects.filter(headline__contains='ood') +[Hello, Goodbye, Hello and goodbye] + +>>> Article.objects.filter(Q(pk=1) | Q(pk=2)) +[Hello, Goodbye] + +>>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3)) +[Hello, Goodbye, Hello and goodbye] + +# Q arg objects are ANDed +>>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')) +[Hello and goodbye] + +# Q arg AND order is irrelevant +>>> Article.objects.filter(Q(headline__contains='bye'), headline__startswith='Hello') +[Hello and goodbye] + +# Try some arg queries with operations other than get_list +>>> Article.objects.get(Q(headline__startswith='Hello'), Q(headline__contains='bye')) +Hello and goodbye + +>>> Article.objects.filter(Q(headline__startswith='Hello') | Q(headline__contains='bye')).count() +3 + +>>> list(Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye')).values()) +[{'headline': 'Hello and goodbye', 'pub_date': datetime.datetime(2005, 11, 29, 0, 0), 'id': 3}] + +>>> Article.objects.filter(Q(headline__startswith='Hello')).in_bulk([1,2]) +{1: Hello} + +""" diff --git a/tests/modeltests/ordering/__init__.py b/tests/modeltests/ordering/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/ordering/__init__.py diff --git a/tests/modeltests/ordering/models.py b/tests/modeltests/ordering/models.py new file mode 100644 index 0000000000..de08a75755 --- /dev/null +++ b/tests/modeltests/ordering/models.py @@ -0,0 +1,63 @@ +""" +6. Specifying ordering + +Specify default ordering for a model using the ``ordering`` attribute, which +should be a list or tuple of field names. This tells Django how to order the +results of ``get_list()`` and other similar functions. + +If a field name in ``ordering`` starts with a hyphen, that field will be +ordered in descending order. Otherwise, it'll be ordered in ascending order. +The special-case field name ``"?"`` specifies random order. + +The ordering attribute is not required. If you leave it off, ordering will be +undefined -- not random, just undefined. +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateTimeField() + class Meta: + ordering = ('-pub_date', 'headline') + + def __repr__(self): + return self.headline + +API_TESTS = """ +# Create a couple of Articles. +>>> from datetime import datetime +>>> a1 = Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) +>>> a1.save() +>>> a2 = Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) +>>> a2.save() +>>> a3 = Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) +>>> a3.save() +>>> a4 = Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) +>>> a4.save() + +# By default, Article.objects.all() orders by pub_date descending, then +# headline ascending. +>>> Article.objects.all() +[Article 4, Article 2, Article 3, Article 1] + +# Override ordering with order_by, which is in the same format as the ordering +# attribute in models. +>>> Article.objects.order_by('headline') +[Article 1, Article 2, Article 3, Article 4] +>>> Article.objects.order_by('pub_date', '-headline') +[Article 1, Article 3, Article 2, Article 4] + +# Use the 'stop' part of slicing notation to limit the results. +>>> Article.objects.order_by('headline')[:2] +[Article 1, Article 2] + +# Use the 'stop' and 'start' parts of slicing notation to offset the result list. +>>> Article.objects.order_by('headline')[1:3] +[Article 2, Article 3] + +# Use '?' to order randomly. (We're using [...] in the output to indicate we +# don't know what order the output will be in. +>>> Article.objects.order_by('?') +[...] +""" diff --git a/tests/modeltests/pagination/__init__.py b/tests/modeltests/pagination/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/pagination/__init__.py diff --git a/tests/modeltests/pagination/models.py b/tests/modeltests/pagination/models.py new file mode 100644 index 0000000000..6525168b97 --- /dev/null +++ b/tests/modeltests/pagination/models.py @@ -0,0 +1,59 @@ +""" +20. Object Pagination + +Django provides a framework for paginating a list of objects in a few. +This is often useful for dividing search results or long lists of objects +in to easily readable pages. + + +""" +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100, default='Default headline') + pub_date = models.DateTimeField() + + def __repr__(self): + return self.headline + +API_TESTS = """ +# prepare a list of objects for pagination +>>> from datetime import datetime +>>> for x in range(1, 10): +... a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29)) +... a.save() + +# create a basic paginator, 5 articles per page +>>> from django.core.paginator import ObjectPaginator, InvalidPage +>>> paginator = ObjectPaginator(Article.objects.all(), 5) + +# the paginator knows how many hits and pages it contains +>>> paginator.hits +9 + +>>> paginator.pages +2 + +# get the first page (zero-based) +>>> paginator.get_page(0) +[Article 1, Article 2, Article 3, Article 4, Article 5] + +# get the second page +>>> paginator.get_page(1) +[Article 6, Article 7, Article 8, Article 9] + +# does the first page have a next or previous page? +>>> paginator.has_next_page(0) +True + +>>> paginator.has_previous_page(0) +False + +# check the second page +>>> paginator.has_next_page(1) +False + +>>> paginator.has_previous_page(1) +True + +""" diff --git a/tests/modeltests/properties/__init__.py b/tests/modeltests/properties/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/properties/__init__.py diff --git a/tests/modeltests/properties/models.py b/tests/modeltests/properties/models.py new file mode 100644 index 0000000000..2c2190e989 --- /dev/null +++ b/tests/modeltests/properties/models.py @@ -0,0 +1,26 @@ +""" +22. Using properties on models +""" + +from django.db import models + +class Person(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + + def _get_full_name(self): + return "%s %s" % (self.first_name, self.last_name) + full_name = property(_get_full_name) + +API_TESTS = """ +>>> a = Person(first_name='John', last_name='Lennon') +>>> a.save() +>>> a.full_name +'John Lennon' + +# The "full_name" property hasn't provided a "set" method. +>>> a.full_name = 'Paul McCartney' +Traceback (most recent call last): + ... +AttributeError: can't set attribute +""" diff --git a/tests/modeltests/repr/__init__.py b/tests/modeltests/repr/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/repr/__init__.py diff --git a/tests/modeltests/repr/models.py b/tests/modeltests/repr/models.py new file mode 100644 index 0000000000..7e5b98c4a5 --- /dev/null +++ b/tests/modeltests/repr/models.py @@ -0,0 +1,31 @@ +""" +2. Adding __repr__() to models + +Although it's not a strict requirement, each model should have a ``__repr__()`` +method to return a "human-readable" representation of the object. Do this not +only for your own sanity when dealing with the interactive prompt, but also +because objects' representations are used throughout Django's +automatically-generated admin. +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100) + pub_date = models.DateTimeField() + + def __repr__(self): + return self.headline + +API_TESTS = """ +# Create an Article. +>>> from datetime import datetime +>>> a = Article(headline='Area man programs in Python', pub_date=datetime(2005, 7, 28)) +>>> a.save() + +>>> repr(a) +'Area man programs in Python' + +>>> a +Area man programs in Python +""" diff --git a/tests/modeltests/reserved_names/__init__.py b/tests/modeltests/reserved_names/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/reserved_names/__init__.py diff --git a/tests/modeltests/reserved_names/models.py b/tests/modeltests/reserved_names/models.py new file mode 100644 index 0000000000..51d32e22c5 --- /dev/null +++ b/tests/modeltests/reserved_names/models.py @@ -0,0 +1,56 @@ +""" +18. Using SQL reserved names + +Need to use a reserved SQL name as a column name or table name? Need to include +a hyphen in a column or table name? No problem. Django quotes names +appropriately behind the scenes, so your database won't complain about +reserved-name usage. +""" + +from django.db import models + +class Thing(models.Model): + when = models.CharField(maxlength=1, primary_key=True) + join = models.CharField(maxlength=1) + like = models.CharField(maxlength=1) + drop = models.CharField(maxlength=1) + alter = models.CharField(maxlength=1) + having = models.CharField(maxlength=1) + where = models.DateField(maxlength=1) + has_hyphen = models.CharField(maxlength=1, db_column='has-hyphen') + class Meta: + db_table = 'select' + + def __repr__(self): + return self.when + +API_TESTS = """ +>>> import datetime +>>> day1 = datetime.date(2005, 1, 1) +>>> day2 = datetime.date(2006, 2, 2) +>>> t = Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where=day1, has_hyphen='h') +>>> t.save() +>>> print t.when +a + +>>> u = Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where=day2) +>>> u.save() +>>> print u.when +h + +>>> Thing.objects.order_by('when') +[a, h] +>>> v = Thing.objects.get(pk='a') +>>> print v.join +b +>>> print v.where +2005-01-01 +>>> Thing.objects.order_by('select.when') +[a, h] + +>>> Thing.objects.dates('where', 'year') +[datetime.datetime(2005, 1, 1, 0, 0), datetime.datetime(2006, 1, 1, 0, 0)] + +>>> Thing.objects.filter(where__month=1) +[a] +""" diff --git a/tests/modeltests/reverse_lookup/__init__.py b/tests/modeltests/reverse_lookup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/reverse_lookup/__init__.py diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py new file mode 100644 index 0000000000..2fe77bdbeb --- /dev/null +++ b/tests/modeltests/reverse_lookup/models.py @@ -0,0 +1,56 @@ +""" +25. Reverse lookups + +This demonstrates the reverse lookup features of the database API. +""" + +from django.db import models + +class User(models.Model): + name = models.CharField(maxlength=200) + def __repr__(self): + return self.name + +class Poll(models.Model): + question = models.CharField(maxlength=200) + creator = models.ForeignKey(User) + def __repr__(self): + return self.question + +class Choice(models.Model): + name = models.CharField(maxlength=100) + poll = models.ForeignKey(Poll, related_name="poll_choice") + related_poll = models.ForeignKey(Poll, related_name="related_choice") + def __repr(self): + return self.name + +API_TESTS = """ +>>> john = User(name="John Doe") +>>> john.save() +>>> jim = User(name="Jim Bo") +>>> jim.save() +>>> first_poll = Poll(question="What's the first question?", creator=john) +>>> first_poll.save() +>>> second_poll = Poll(question="What's the second question?", creator=jim) +>>> second_poll.save() +>>> new_choice = Choice(poll=first_poll, related_poll=second_poll, name="This is the answer.") +>>> new_choice.save() + +>>> # Reverse lookups by field name: +>>> User.objects.get(poll__question__exact="What's the first question?") +John Doe +>>> User.objects.get(poll__question__exact="What's the second question?") +Jim Bo + +>>> # Reverse lookups by related_name: +>>> Poll.objects.get(poll_choice__name__exact="This is the answer.") +What's the first question? +>>> Poll.objects.get(related_choice__name__exact="This is the answer.") +What's the second question? + +>>> # If a related_name is given you can't use the field name instead: +>>> Poll.objects.get(choice__name__exact="This is the answer") +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'choice' into field +""" diff --git a/tests/modeltests/save_delete_hooks/__init__.py b/tests/modeltests/save_delete_hooks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/save_delete_hooks/__init__.py diff --git a/tests/modeltests/save_delete_hooks/models.py b/tests/modeltests/save_delete_hooks/models.py new file mode 100644 index 0000000000..47748082da --- /dev/null +++ b/tests/modeltests/save_delete_hooks/models.py @@ -0,0 +1,42 @@ +""" +13. Adding hooks before/after saving and deleting + +To execute arbitrary code around ``save()`` and ``delete()``, just subclass +the methods. +""" + +from django.db import models + +class Person(models.Model): + first_name = models.CharField(maxlength=20) + last_name = models.CharField(maxlength=20) + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + + def save(self): + print "Before save" + super(Person, self).save() # Call the "real" save() method + print "After save" + + def delete(self): + print "Before deletion" + super(Person, self).delete() # Call the "real" delete() method + print "After deletion" + +API_TESTS = """ +>>> p1 = Person(first_name='John', last_name='Smith') +>>> p1.save() +Before save +After save + +>>> Person.objects.all() +[John Smith] + +>>> p1.delete() +Before deletion +After deletion + +>>> Person.objects.all() +[] +""" diff --git a/tests/modeltests/transactions/__init__.py b/tests/modeltests/transactions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/transactions/__init__.py diff --git a/tests/modeltests/transactions/models.py b/tests/modeltests/transactions/models.py new file mode 100644 index 0000000000..22f38f7a0c --- /dev/null +++ b/tests/modeltests/transactions/models.py @@ -0,0 +1,92 @@ +""" +XXX. Transactions + +Django handles transactions in three different ways. The default is to commit +each transaction upon a write, but you can decorate a function to get +commit-on-success behavior. Alternatively, you can manage the transaction +manually. +""" + +from django.db import models + +class Reporter(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + email = models.EmailField() + + def __repr__(self): + return "%s %s" % (self.first_name, self.last_name) + +API_TESTS = """ +>>> from django.db import connection, transaction + +# the default behavior is to autocommit after each save() action +>>> def create_a_reporter_then_fail(first, last): +... a = Reporter(first_name=first, last_name=last) +... a.save() +... raise Exception("I meant to do that") +... +>>> create_a_reporter_then_fail("Alice", "Smith") +Traceback (most recent call last): + ... +Exception: I meant to do that + +# The object created before the exception still exists +>>> Reporter.objects.all() +[Alice Smith] + +# the autocommit decorator works exactly the same as the default behavior +>>> autocomitted_create_then_fail = transaction.autocommit(create_a_reporter_then_fail) +>>> autocomitted_create_then_fail("Ben", "Jones") +Traceback (most recent call last): + ... +Exception: I meant to do that + +# Same behavior as before +>>> Reporter.objects.all() +[Alice Smith, Ben Jones] + +# With the commit_on_success decorator, the transaction is only comitted if the +# function doesn't throw an exception +>>> committed_on_success = transaction.commit_on_success(create_a_reporter_then_fail) +>>> committed_on_success("Carol", "Doe") +Traceback (most recent call last): + ... +Exception: I meant to do that + +# This time the object never got saved +>>> Reporter.objects.all() +[Alice Smith, Ben Jones] + +# If there aren't any exceptions, the data will get saved +>>> def remove_a_reporter(): +... r = Reporter.objects.get(first_name="Alice") +... r.delete() +... +>>> remove_comitted_on_success = transaction.commit_on_success(remove_a_reporter) +>>> remove_comitted_on_success() +>>> Reporter.objects.all() +[Ben Jones] + +# You can manually manage transactions if you really want to, but you +# have to remember to commit/rollback +>>> def manually_managed(): +... r = Reporter(first_name="Carol", last_name="Doe") +... r.save() +... transaction.commit() +>>> manually_managed = transaction.commit_manually(manually_managed) +>>> manually_managed() +>>> Reporter.objects.all() +[Ben Jones, Carol Doe] + +# If you forget, you'll get bad errors +>>> def manually_managed_mistake(): +... r = Reporter(first_name="David", last_name="Davidson") +... r.save() +... # oops, I forgot to commit/rollback! +>>> manually_managed_mistake = transaction.commit_manually(manually_managed_mistake) +>>> manually_managed_mistake() +Traceback (most recent call last): + ... +TransactionManagementError: Transaction managed block ended with pending COMMIT/ROLLBACK +"""
\ No newline at end of file diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/validation/__init__.py diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py new file mode 100644 index 0000000000..d03fffea25 --- /dev/null +++ b/tests/modeltests/validation/models.py @@ -0,0 +1,147 @@ +""" +XX. Validation + +Each model instance has a validate() method that returns a dictionary of +validation errors in the instance's fields. This method has a side effect +of converting each field to its appropriate Python data type. +""" + +from django.db import models + +class Person(models.Model): + is_child = models.BooleanField() + name = models.CharField(maxlength=20) + birthdate = models.DateField() + favorite_moment = models.DateTimeField() + email = models.EmailField() + + def __repr__(self): + return self.name + +API_TESTS = """ + +>>> import datetime +>>> valid_params = { +... 'is_child': True, +... 'name': 'John', +... 'birthdate': datetime.date(2000, 5, 3), +... 'favorite_moment': datetime.datetime(2002, 4, 3, 13, 23), +... 'email': 'john@example.com' +... } +>>> p = Person(**valid_params) +>>> p.validate() +{} + +>>> p = Person(**dict(valid_params, id='23')) +>>> p.validate() +{} +>>> p.id +23 + +>>> p = Person(**dict(valid_params, id='foo')) +>>> p.validate() +{'id': ['This value must be an integer.']} + +>>> p = Person(**dict(valid_params, id=None)) +>>> p.validate() +{} +>>> repr(p.id) +'None' + +>>> p = Person(**dict(valid_params, is_child='t')) +>>> p.validate() +{} +>>> p.is_child +True + +>>> p = Person(**dict(valid_params, is_child='f')) +>>> p.validate() +{} +>>> p.is_child +False + +>>> p = Person(**dict(valid_params, is_child=True)) +>>> p.validate() +{} +>>> p.is_child +True + +>>> p = Person(**dict(valid_params, is_child=False)) +>>> p.validate() +{} +>>> p.is_child +False + +>>> p = Person(**dict(valid_params, is_child='foo')) +>>> p.validate() +{'is_child': ['This value must be either True or False.']} + +>>> p = Person(**dict(valid_params, name=u'Jose')) +>>> p.validate() +{} +>>> p.name +u'Jose' + +>>> p = Person(**dict(valid_params, name=227)) +>>> p.validate() +{} +>>> p.name +'227' + +>>> p = Person(**dict(valid_params, birthdate=datetime.date(2000, 5, 3))) +>>> p.validate() +{} +>>> p.birthdate +datetime.date(2000, 5, 3) + +>>> p = Person(**dict(valid_params, birthdate=datetime.datetime(2000, 5, 3))) +>>> p.validate() +{} +>>> p.birthdate +datetime.date(2000, 5, 3) + +>>> p = Person(**dict(valid_params, birthdate='2000-05-03')) +>>> p.validate() +{} +>>> p.birthdate +datetime.date(2000, 5, 3) + +>>> p = Person(**dict(valid_params, birthdate='2000-5-3')) +>>> p.validate() +{} +>>> p.birthdate +datetime.date(2000, 5, 3) + +>>> p = Person(**dict(valid_params, birthdate='foo')) +>>> p.validate() +{'birthdate': ['Enter a valid date in YYYY-MM-DD format.']} + +>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3, 13, 23))) +>>> p.validate() +{} +>>> p.favorite_moment +datetime.datetime(2002, 4, 3, 13, 23) + +>>> p = Person(**dict(valid_params, favorite_moment=datetime.datetime(2002, 4, 3))) +>>> p.validate() +{} +>>> p.favorite_moment +datetime.datetime(2002, 4, 3, 0, 0) + +>>> p = Person(**dict(valid_params, email='john@example.com')) +>>> p.validate() +{} +>>> p.email +'john@example.com' + +>>> p = Person(**dict(valid_params, email=u'john@example.com')) +>>> p.validate() +{} +>>> p.email +u'john@example.com' + +>>> p = Person(**dict(valid_params, email=22)) +>>> p.validate() +{'email': ['Enter a valid e-mail address.']} + +""" |
