diff options
| author | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
|---|---|---|
| committer | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-03-09 17:43:46 +0000 |
| commit | 0b7dd14d1f87e2ecef7aacc39fe4189667ed4fdf (patch) | |
| tree | b77497f9de324d38a9c6341e54d2742633e20055 /tests/modeltests | |
| parent | e17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (diff) | |
boulder-oracle-sprint: Merged to trunk [4692].
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4695 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/modeltests')
19 files changed, 650 insertions, 25 deletions
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 2ebe857e7e..062f8b29ed 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -14,7 +14,7 @@ class Article(models.Model): class Meta: ordering = ('pub_date','headline') - + def __str__(self): return self.headline @@ -321,7 +321,6 @@ AttributeError: Manager isn't accessible via Article instances >>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.all() [<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>] - """} from django.conf import settings @@ -360,4 +359,11 @@ __test__['API_TESTS'] += """ >>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) >>> Article.objects.get(headline="Article 10") <Article: Article 10> + +# Edge-case test: A year lookup should retrieve all objects in the given +year, including Jan. 1 and Dec. 31. +>>> a11 = Article.objects.create(headline='Article 11', pub_date=datetime(2008, 1, 1)) +>>> a12 = Article.objects.create(headline='Article 12', pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999)) +>>> Article.objects.filter(pub_date__year=2008) +[<Article: Article 11>, <Article: Article 12>] """ diff --git a/tests/modeltests/fixtures/__init__.py b/tests/modeltests/fixtures/__init__.py new file mode 100644 index 0000000000..139597f9cb --- /dev/null +++ b/tests/modeltests/fixtures/__init__.py @@ -0,0 +1,2 @@ + + diff --git a/tests/modeltests/fixtures/fixtures/fixture1.json b/tests/modeltests/fixtures/fixtures/fixture1.json new file mode 100644 index 0000000000..cc11a3e926 --- /dev/null +++ b/tests/modeltests/fixtures/fixtures/fixture1.json @@ -0,0 +1,18 @@ +[ + { + "pk": "2", + "model": "fixtures.article", + "fields": { + "headline": "Poker has no place on ESPN", + "pub_date": "2006-06-16 12:00:00" + } + }, + { + "pk": "3", + "model": "fixtures.article", + "fields": { + "headline": "Time to reform copyright", + "pub_date": "2006-06-16 13:00:00" + } + } +]
\ No newline at end of file diff --git a/tests/modeltests/fixtures/fixtures/fixture2.json b/tests/modeltests/fixtures/fixtures/fixture2.json new file mode 100644 index 0000000000..01b40d7535 --- /dev/null +++ b/tests/modeltests/fixtures/fixtures/fixture2.json @@ -0,0 +1,18 @@ +[ + { + "pk": "3", + "model": "fixtures.article", + "fields": { + "headline": "Copyright is fine the way it is", + "pub_date": "2006-06-16 14:00:00" + } + }, + { + "pk": "4", + "model": "fixtures.article", + "fields": { + "headline": "Django conquers world!", + "pub_date": "2006-06-16 15:00:00" + } + } +]
\ No newline at end of file diff --git a/tests/modeltests/fixtures/fixtures/fixture2.xml b/tests/modeltests/fixtures/fixtures/fixture2.xml new file mode 100644 index 0000000000..9ced78162e --- /dev/null +++ b/tests/modeltests/fixtures/fixtures/fixture2.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="2" model="fixtures.article"> + <field type="CharField" name="headline">Poker on TV is great!</field> + <field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field> + </object> + <object pk="5" model="fixtures.article"> + <field type="CharField" name="headline">XML identified as leading cause of cancer</field> + <field type="DateTimeField" name="pub_date">2006-06-16 16:00:00</field> + </object> +</django-objects>
\ No newline at end of file diff --git a/tests/modeltests/fixtures/fixtures/fixture3.xml b/tests/modeltests/fixtures/fixtures/fixture3.xml new file mode 100644 index 0000000000..9ced78162e --- /dev/null +++ b/tests/modeltests/fixtures/fixtures/fixture3.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<django-objects version="1.0"> + <object pk="2" model="fixtures.article"> + <field type="CharField" name="headline">Poker on TV is great!</field> + <field type="DateTimeField" name="pub_date">2006-06-16 11:00:00</field> + </object> + <object pk="5" model="fixtures.article"> + <field type="CharField" name="headline">XML identified as leading cause of cancer</field> + <field type="DateTimeField" name="pub_date">2006-06-16 16:00:00</field> + </object> +</django-objects>
\ No newline at end of file diff --git a/tests/modeltests/fixtures/fixtures/initial_data.json b/tests/modeltests/fixtures/fixtures/initial_data.json new file mode 100644 index 0000000000..477d781dbc --- /dev/null +++ b/tests/modeltests/fixtures/fixtures/initial_data.json @@ -0,0 +1,10 @@ +[ + { + "pk": "1", + "model": "fixtures.article", + "fields": { + "headline": "Python program becomes self aware", + "pub_date": "2006-06-16 11:00:00" + } + } +]
\ No newline at end of file diff --git a/tests/modeltests/fixtures/models.py b/tests/modeltests/fixtures/models.py new file mode 100644 index 0000000000..d82886a6c4 --- /dev/null +++ b/tests/modeltests/fixtures/models.py @@ -0,0 +1,88 @@ +""" +39. Fixtures. + +Fixtures are a way of loading data into the database in bulk. Fixure data +can be stored in any serializable format (including JSON and XML). Fixtures +are identified by name, and are stored in either a directory named 'fixtures' +in the application directory, on in one of the directories named in the +FIXTURE_DIRS setting. +""" + +from django.db import models + +class Article(models.Model): + headline = models.CharField(maxlength=100, default='Default headline') + pub_date = models.DateTimeField() + + def __str__(self): + return self.headline + + class Meta: + ordering = ('-pub_date', 'headline') + +__test__ = {'API_TESTS': """ +>>> from django.core import management +>>> from django.db.models import get_app + +# Reset the database representation of this app. +# This will return the database to a clean initial state. +>>> management.flush(verbosity=0, interactive=False) + +# Syncdb introduces 1 initial data object from initial_data.json. +>>> Article.objects.all() +[<Article: Python program becomes self aware>] + +# Load fixture 1. Single JSON file, with two objects. +>>> management.load_data(['fixture1.json'], verbosity=0) +>>> Article.objects.all() +[<Article: Time to reform copyright>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>] + +# Load fixture 2. JSON file imported by default. Overwrites some existing objects +>>> management.load_data(['fixture2.json'], verbosity=0) +>>> Article.objects.all() +[<Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>] + +# Load fixture 3, XML format. +>>> management.load_data(['fixture3.xml'], verbosity=0) +>>> Article.objects.all() +[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>] + +# Load a fixture that doesn't exist +>>> management.load_data(['unknown.json'], verbosity=0) + +# object list is unaffected +>>> Article.objects.all() +[<Article: XML identified as leading cause of cancer>, <Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker on TV is great!>, <Article: Python program becomes self aware>] + +# Reset the database representation of this app. This will delete all data. +>>> management.flush(verbosity=0, interactive=False) +>>> Article.objects.all() +[<Article: Python program becomes self aware>] + +# Load fixture 1 again, using format discovery +>>> management.load_data(['fixture1'], verbosity=0) +>>> Article.objects.all() +[<Article: Time to reform copyright>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>] + +# Try to load fixture 2 using format discovery; this will fail +# because there are two fixture2's in the fixtures directory +>>> management.load_data(['fixture2'], verbosity=0) # doctest: +ELLIPSIS +Multiple fixtures named 'fixture2' in '.../fixtures'. Aborting. + +>>> Article.objects.all() +[<Article: Time to reform copyright>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>] + +# Dump the current contents of the database as a JSON fixture +>>> management.dump_data(['fixtures'], format='json') +[{"pk": "3", "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16 13:00:00"}}, {"pk": "2", "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16 12:00:00"}}, {"pk": "1", "model": "fixtures.article", "fields": {"headline": "Python program becomes self aware", "pub_date": "2006-06-16 11:00:00"}}] +"""} + +from django.test import TestCase + +class SampleTestCase(TestCase): + fixtures = ['fixture1.json', 'fixture2.json'] + + def testClassFixtures(self): + "Check that test case has installed 4 fixture objects" + self.assertEqual(Article.objects.count(), 4) + self.assertEquals(str(Article.objects.all()), "[<Article: Django conquers world!>, <Article: Copyright is fine the way it is>, <Article: Poker has no place on ESPN>, <Article: Python program becomes self aware>]") diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index aa903d1a64..106c97d3b4 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -58,6 +58,17 @@ Article 4 >>> Article.objects.filter(headline__startswith='Blah blah').count() 0L +# count() should respect sliced query sets. +>>> articles = Article.objects.all() +>>> articles.count() +7L +>>> articles[:4].count() +4 +>>> articles[1:100].count() +6L +>>> articles[10:100].count() +0 + # Date and date/time lookups can also be done with strings. >>> Article.objects.filter(pub_date__exact='2005-07-27 00:00:00').count() 3L @@ -198,6 +209,8 @@ DoesNotExist: Article matching query does not exist. [] >>> Article.objects.none().count() 0 +>>> [article for article in Article.objects.none().iterator()] +[] # using __in with an empty list should return an empty query set >>> Article.objects.filter(id__in=[]) @@ -206,4 +219,15 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.exclude(id__in=[]) [<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>] +# Programming errors are pointed out with nice error messages +>>> Article.objects.filter(pub_date_year='2005').count() +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'pub_date_year' into field + +>>> Article.objects.filter(headline__starts='Article') +Traceback (most recent call last): + ... +TypeError: Cannot resolve keyword 'headline__starts' into field + """} diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 657d506b33..e64174a23f 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -40,13 +40,27 @@ class Writer(models.Model): class Article(models.Model): headline = models.CharField(maxlength=50) pub_date = models.DateField() + created = models.DateField(editable=False) writer = models.ForeignKey(Writer) article = models.TextField() categories = models.ManyToManyField(Category, blank=True) + def save(self): + import datetime + if not self.id: + self.created = datetime.date.today() + return super(Article, self).save() + def __str__(self): return self.headline +class PhoneNumber(models.Model): + phone = models.PhoneNumberField() + description = models.CharField(maxlength=20) + + def __str__(self): + return self.phone + __test__ = {'API_TESTS': """ >>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField >>> import datetime @@ -281,4 +295,170 @@ existing Category instance. <Category: Third> >>> Category.objects.get(id=3) <Category: Third> + +Here, we demonstrate that choices for a ForeignKey ChoiceField are determined +at runtime, based on the data in the database when the form is displayed, not +the data in the database when the form is instantiated. +>>> ArticleForm = form_for_model(Article) +>>> f = ArticleForm(auto_id=False) +>>> print f.as_ul() +<li>Headline: <input type="text" name="headline" maxlength="50" /></li> +<li>Pub date: <input type="text" name="pub_date" /></li> +<li>Writer: <select name="writer"> +<option value="" selected="selected">---------</option> +<option value="1">Mike Royko</option> +<option value="2">Bob Woodward</option> +</select></li> +<li>Article: <textarea name="article"></textarea></li> +<li>Categories: <select multiple="multiple" name="categories"> +<option value="1">Entertainment</option> +<option value="2">It's a test</option> +<option value="3">Third</option> +</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> +>>> Category.objects.create(name='Fourth', url='4th') +<Category: Fourth> +>>> Writer.objects.create(name='Carl Bernstein') +<Writer: Carl Bernstein> +>>> print f.as_ul() +<li>Headline: <input type="text" name="headline" maxlength="50" /></li> +<li>Pub date: <input type="text" name="pub_date" /></li> +<li>Writer: <select name="writer"> +<option value="" selected="selected">---------</option> +<option value="1">Mike Royko</option> +<option value="2">Bob Woodward</option> +<option value="3">Carl Bernstein</option> +</select></li> +<li>Article: <textarea name="article"></textarea></li> +<li>Categories: <select multiple="multiple" name="categories"> +<option value="1">Entertainment</option> +<option value="2">It's a test</option> +<option value="3">Third</option> +<option value="4">Fourth</option> +</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li> + +# ModelChoiceField ############################################################ + +>>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField + +>>> f = ModelChoiceField(Category.objects.all()) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(0) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] +>>> f.clean(3) +<Category: Third> +>>> f.clean(2) +<Category: It's a test> + +# Add a Category object *after* the ModelChoiceField has already been +# instantiated. This proves clean() checks the database during clean() rather +# than caching it at time of instantiation. +>>> Category.objects.create(name='Fifth', url='5th') +<Category: Fifth> +>>> f.clean(5) +<Category: Fifth> + +# Delete a Category object *after* the ModelChoiceField has already been +# instantiated. This proves clean() checks the database during clean() rather +# than caching it at time of instantiation. +>>> Category.objects.get(url='5th').delete() +>>> f.clean(5) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +>>> f = ModelChoiceField(Category.objects.filter(pk=1), required=False) +>>> print f.clean('') +None +>>> f.clean('') +>>> f.clean('1') +<Category: Entertainment> +>>> f.clean('100') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +# ModelMultipleChoiceField #################################################### + +>>> f = ModelMultipleChoiceField(Category.objects.all()) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([]) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([1]) +[<Category: Entertainment>] +>>> f.clean([2]) +[<Category: It's a test>] +>>> f.clean(['1']) +[<Category: Entertainment>] +>>> f.clean(['1', '2']) +[<Category: Entertainment>, <Category: It's a test>] +>>> f.clean([1, '2']) +[<Category: Entertainment>, <Category: It's a test>] +>>> f.clean((1, '2')) +[<Category: Entertainment>, <Category: It's a test>] +>>> f.clean(['100']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 100 is not one of the available choices.'] +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] + +# Add a Category object *after* the ModelChoiceField has already been +# instantiated. This proves clean() checks the database during clean() rather +# than caching it at time of instantiation. +>>> Category.objects.create(id=6, name='Sixth', url='6th') +<Category: Sixth> +>>> f.clean([6]) +[<Category: Sixth>] + +# Delete a Category object *after* the ModelChoiceField has already been +# instantiated. This proves clean() checks the database during clean() rather +# than caching it at time of instantiation. +>>> Category.objects.get(url='6th').delete() +>>> f.clean([6]) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 6 is not one of the available choices.'] + +>>> f = ModelMultipleChoiceField(Category.objects.all(), required=False) +>>> f.clean([]) +[] +>>> f.clean(()) +[] +>>> f.clean(['10']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] +>>> f.clean(['3', '10']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] +>>> f.clean(['1', '10']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] + +# PhoneNumberField ############################################################ + +>>> PhoneNumberForm = form_for_model(PhoneNumber) +>>> f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'}) +>>> f.is_valid() +True +>>> f.clean_data +{'phone': u'312-555-1212', 'description': u'Assistance'} """} diff --git a/tests/modeltests/select_related/__init__.py b/tests/modeltests/select_related/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/modeltests/select_related/__init__.py diff --git a/tests/modeltests/select_related/models.py b/tests/modeltests/select_related/models.py new file mode 100644 index 0000000000..8a19267870 --- /dev/null +++ b/tests/modeltests/select_related/models.py @@ -0,0 +1,152 @@ +""" +XXX. Tests for ``select_related()`` + +``select_related()`` follows all relationships and pre-caches any foreign key +values so that complex trees can be fetched in a single query. However, this +isn't always a good idea, so the ``depth`` argument control how many "levels" +the select-related behavior will traverse. +""" + +from django.db import models + +# Who remembers high school biology? + +class Domain(models.Model): + name = models.CharField(maxlength=50) + def __str__(self): + return self.name + +class Kingdom(models.Model): + name = models.CharField(maxlength=50) + domain = models.ForeignKey(Domain) + def __str__(self): + return self.name + +class Phylum(models.Model): + name = models.CharField(maxlength=50) + kingdom = models.ForeignKey(Kingdom) + def __str__(self): + return self.name + +class Klass(models.Model): + name = models.CharField(maxlength=50) + phylum = models.ForeignKey(Phylum) + def __str__(self): + return self.name + +class Order(models.Model): + name = models.CharField(maxlength=50) + klass = models.ForeignKey(Klass) + def __str__(self): + return self.name + +class Family(models.Model): + name = models.CharField(maxlength=50) + order = models.ForeignKey(Order) + def __str__(self): + return self.name + +class Genus(models.Model): + name = models.CharField(maxlength=50) + family = models.ForeignKey(Family) + def __str__(self): + return self.name + +class Species(models.Model): + name = models.CharField(maxlength=50) + genus = models.ForeignKey(Genus) + def __str__(self): + return self.name + +def create_tree(stringtree): + """Helper to create a complete tree""" + names = stringtree.split() + models = [Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species] + assert len(names) == len(models), (names, models) + + parent = None + for name, model in zip(names, models): + try: + obj = model.objects.get(name=name) + except model.DoesNotExist: + obj = model(name=name) + if parent: + setattr(obj, parent.__class__.__name__.lower(), parent) + obj.save() + parent = obj + +__test__ = {'API_TESTS':""" + +# Set up. +# The test runner sets settings.DEBUG to False, but we want to gather queries +# so we'll set it to True here and reset it at the end of the test suite. +>>> from django.conf import settings +>>> settings.DEBUG = True + +>>> create_tree("Eukaryota Animalia Anthropoda Insecta Diptera Drosophilidae Drosophila melanogaster") +>>> create_tree("Eukaryota Animalia Chordata Mammalia Primates Hominidae Homo sapiens") +>>> create_tree("Eukaryota Plantae Magnoliophyta Magnoliopsida Fabales Fabaceae Pisum sativum") +>>> create_tree("Eukaryota Fungi Basidiomycota Homobasidiomycatae Agaricales Amanitacae Amanita muscaria") + +>>> from django import db + +# Normally, accessing FKs doesn't fill in related objects: +>>> db.reset_queries() +>>> fly = Species.objects.get(name="melanogaster") +>>> fly.genus.family.order.klass.phylum.kingdom.domain +<Domain: Eukaryota> +>>> len(db.connection.queries) +8 + +# However, a select_related() call will fill in those related objects without any extra queries: +>>> db.reset_queries() +>>> person = Species.objects.select_related().get(name="sapiens") +>>> person.genus.family.order.klass.phylum.kingdom.domain +<Domain: Eukaryota> +>>> len(db.connection.queries) +1 + +# select_related() also of course applies to entire lists, not just items. +# Without select_related() +>>> db.reset_queries() +>>> world = Species.objects.all() +>>> [o.genus.family for o in world] +[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] +>>> len(db.connection.queries) +9 + +# With select_related(): +>>> db.reset_queries() +>>> world = Species.objects.all().select_related() +>>> [o.genus.family for o in world] +[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] +>>> len(db.connection.queries) +1 + +# The "depth" argument to select_related() will stop the descent at a particular level: +>>> db.reset_queries() +>>> pea = Species.objects.select_related(depth=1).get(name="sativum") +>>> pea.genus.family.order.klass.phylum.kingdom.domain +<Domain: Eukaryota> + +# Notice: one few query than above because of depth=1 +>>> len(db.connection.queries) +7 + +>>> db.reset_queries() +>>> pea = Species.objects.select_related(depth=5).get(name="sativum") +>>> pea.genus.family.order.klass.phylum.kingdom.domain +<Domain: Eukaryota> +>>> len(db.connection.queries) +3 + +>>> db.reset_queries() +>>> world = Species.objects.all().select_related(depth=2) +>>> [o.genus.family.order for o in world] +[<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>] +>>> len(db.connection.queries) +5 + +# Reset DEBUG to where we found it. +>>> settings.DEBUG = False +"""} diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py index e24ff537c1..e86546c6fe 100644 --- a/tests/modeltests/serializers/models.py +++ b/tests/modeltests/serializers/models.py @@ -139,4 +139,24 @@ __test__ = {'API_TESTS':""" ... print obj <DeserializedObject: Profile of Joe> +# Objects ids can be referenced before they are defined in the serialization data +# However, the deserialization process will need to be contained within a transaction +>>> json = '[{"pk": "3", "model": "serializers.article", "fields": {"headline": "Forward references pose no problem", "pub_date": "2006-06-16 15:00:00", "categories": [4, 1], "author": 4}}, {"pk": "4", "model": "serializers.category", "fields": {"name": "Reference"}}, {"pk": "4", "model": "serializers.author", "fields": {"name": "Agnes"}}]' +>>> from django.db import transaction +>>> transaction.enter_transaction_management() +>>> transaction.managed(True) +>>> for obj in serializers.deserialize("json", json): +... obj.save() + +>>> transaction.commit() +>>> transaction.leave_transaction_management() + +>>> article = Article.objects.get(pk=3) +>>> article +<Article: Forward references pose no problem> +>>> article.categories.all() +[<Category: Reference>, <Category: Sports>] +>>> article.author +<Author: Agnes> + """} diff --git a/tests/modeltests/test_client/fixtures/testdata.json b/tests/modeltests/test_client/fixtures/testdata.json new file mode 100644 index 0000000000..5c9e415240 --- /dev/null +++ b/tests/modeltests/test_client/fixtures/testdata.json @@ -0,0 +1,20 @@ +[ + { + "pk": "1", + "model": "auth.user", + "fields": { + "username": "testclient", + "first_name": "Test", + "last_name": "Client", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2006-12-17 07:03:31", + "groups": [], + "user_permissions": [], + "password": "sha1$6efc0$f93efe9fd7542f25a7be94871ea45aa95de57161", + "email": "testclient@example.com", + "date_joined": "2006-12-17 07:03:31" + } + } +]
\ No newline at end of file diff --git a/tests/modeltests/test_client/management.py b/tests/modeltests/test_client/management.py deleted file mode 100644 index 9b5a5c498e..0000000000 --- a/tests/modeltests/test_client/management.py +++ /dev/null @@ -1,10 +0,0 @@ -from django.dispatch import dispatcher -from django.db.models import signals -import models as test_client_app -from django.contrib.auth.models import User - -def setup_test(app, created_models, verbosity): - # Create a user account for the login-based tests - User.objects.create_user('testclient','testclient@example.com', 'password') - -dispatcher.connect(setup_test, sender=test_client_app, signal=signals.post_syncdb) diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index c5b1a241ca..a3a9749162 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -19,10 +19,11 @@ testing against the contexts and templates produced by a view, rather than the HTML rendered to the end-user. """ -from django.test.client import Client -import unittest +from django.test import Client, TestCase -class ClientTest(unittest.TestCase): +class ClientTest(TestCase): + fixtures = ['testdata.json'] + def setUp(self): "Set up test environment" self.client = Client() @@ -43,7 +44,7 @@ class ClientTest(unittest.TestCase): # Check some response details self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, 'Empty POST Template') + self.assertEqual(response.template.name, 'Empty GET Template') def test_empty_post(self): "POST an empty dictionary to a view" @@ -53,7 +54,7 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') - def test_post_view(self): + def test_post(self): "POST some data to a view" post_data = { 'value': 37 @@ -66,6 +67,14 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.template.name, 'POST Template') self.failUnless('Data received' in response.content) + def test_raw_post(self): + test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>""" + response = self.client.post("/test_client/raw_post_view/", test_doc, + content_type="text/xml") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.template.name, "Book template") + self.assertEqual(response.content, "Blink - Malcolm Gladwell") + def test_redirect(self): "GET a URL that redirects elsewhere" response = self.client.get('/test_client/redirect_view/') @@ -89,7 +98,7 @@ class ClientTest(unittest.TestCase): # Request a page that requires a login response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') - self.assertTrue(response) + self.failUnless(response) self.assertEqual(response.status_code, 200) self.assertEqual(response.context['user'].username, 'testclient') self.assertEqual(response.template.name, 'Login Template') @@ -98,4 +107,30 @@ class ClientTest(unittest.TestCase): "Request a page that is protected with @login, but use bad credentials" response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword') - self.assertFalse(response) + self.failIf(response) + + def test_session_modifying_view(self): + "Request a page that modifies the session" + # Session value isn't set initially + try: + self.client.session['tobacconist'] + self.fail("Shouldn't have a session value") + except KeyError: + pass + + from django.contrib.sessions.models import Session + response = self.client.post('/test_client/session_view/') + + # Check that the session was modified + self.assertEquals(self.client.session['tobacconist'], 'hovercraft') + + def test_view_with_exception(self): + "Request a page that is known to throw an error" + self.assertRaises(KeyError, self.client.get, "/test_client/broken_view/") + + #Try the same assertion, a different way + try: + self.client.get('/test_client/broken_view/') + self.fail('Should raise an error') + except KeyError: + pass diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 09bba5c007..0bef1e9b71 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -4,6 +4,9 @@ import views urlpatterns = patterns('', (r'^get_view/$', views.get_view), (r'^post_view/$', views.post_view), + (r'^raw_post_view/$', views.raw_post_view), (r'^redirect_view/$', views.redirect_view), (r'^login_protected_view/$', views.login_protected_view), + (r'^session_view/$', views.session_view), + (r'^broken_view/$', views.broken_view) ) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 7acfc2db60..653e9a10e9 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,3 +1,4 @@ +from xml.dom.minidom import parseString from django.template import Context, Template from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required @@ -13,15 +14,34 @@ def post_view(request): """A view that expects a POST, and returns a different template depending on whether any POST data is available """ - if request.POST: - t = Template('Data received: {{ data }} is the value.', name='POST Template') - c = Context({'data': request.POST['value']}) + if request.method == 'POST': + if request.POST: + t = Template('Data received: {{ data }} is the value.', name='POST Template') + c = Context({'data': request.POST['value']}) + else: + t = Template('Viewing POST page.', name='Empty POST Template') + c = Context() else: - t = Template('Viewing POST page.', name='Empty POST Template') + t = Template('Viewing GET page.', name='Empty GET Template') c = Context() - + return HttpResponse(t.render(c)) +def raw_post_view(request): + """A view which expects raw XML to be posted and returns content extracted + from the XML""" + if request.method == 'POST': + root = parseString(request.raw_post_data) + first_book = root.firstChild.firstChild + title, author = [n.firstChild.nodeValue for n in first_book.childNodes] + t = Template("{{ title }} - {{ author }}", name="Book template") + c = Context({"title": title, "author": author}) + else: + t = Template("GET request.", name="Book GET template") + c = Context() + + return HttpResponse(t.render(c)) + def redirect_view(request): "A view that redirects all requests to the GET view" return HttpResponseRedirect('/test_client/get_view/') @@ -32,4 +52,17 @@ def login_protected_view(request): c = Context({'user': request.user}) return HttpResponse(t.render(c)) -login_protected_view = login_required(login_protected_view)
\ No newline at end of file +login_protected_view = login_required(login_protected_view) + +def session_view(request): + "A view that modifies the session" + request.session['tobacconist'] = 'hovercraft' + + t = Template('This is a view that modifies the session.', + name='Session Modifying View Template') + c = Context() + return HttpResponse(t.render(c)) + +def broken_view(request): + """A view which just raises an exception, simulating a broken view.""" + raise KeyError("Oops! Looks like you wrote some bad code.") diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py index a9a3d3f485..1066faca4f 100644 --- a/tests/modeltests/validation/models.py +++ b/tests/modeltests/validation/models.py @@ -146,4 +146,8 @@ u'john@example.com' >>> p.validate() {'email': ['Enter a valid e-mail address.']} +# Make sure that Date and DateTime return validation errors and don't raise Python errors. +>>> Person(name='John Doe', is_child=True, email='abc@def.com').validate() +{'favorite_moment': ['This field is required.'], 'birthdate': ['This field is required.']} + """} |
