diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index c6be981453..b4a0f377ab 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -24,290 +24,3 @@ class Article(models.Model): class Meta: ordering = ('headline',) - -__test__ = {'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 - - -# Article objects have access to their related Reporter objects. ->>> r = a.reporter - -# These are strings instead of unicode strings because that's what was used in -# the creation of this reporter (and we haven't refreshed the data from the -# database, which always returns unicode strings). ->>> 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 - ->>> 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() -[, , ] - -# Add the same article to a different article set - check that it moves. ->>> r2.article_set.add(new_article2) ->>> new_article2.reporter.id -2 - -# Adding an object of the wrong type raises TypeError ->>> r.article_set.add(r2) -Traceback (most recent call last): -... -TypeError: 'Article' instance expected - ->>> r.article_set.all() -[, ] ->>> r2.article_set.all() -[] - -# Assign the article to the reporter directly using the descriptor ->>> new_article2.reporter = r ->>> new_article2.save() ->>> new_article2.reporter - ->>> new_article2.reporter.id -1 ->>> r.article_set.all() -[, , ] ->>> r2.article_set.all() -[] - -# Set the article back again using set descriptor. ->>> r2.article_set = [new_article, new_article2] ->>> r.article_set.all() -[] ->>> r2.article_set.all() -[, ] - -# 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() -[, ] ->>> r2.article_set.all() -[] - -# 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() -[, ] - ->>> r.article_set.filter(headline__startswith='This') -[] - ->>> r.article_set.count() -2 - ->>> r2.article_set.count() -1 - -# Get articles by id ->>> Article.objects.filter(id__exact=1) -[] ->>> Article.objects.filter(pk=1) -[] - -# Query on an article property ->>> Article.objects.filter(headline__startswith='This') -[] - -# 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') -[, ] - -# Check that implied __exact also works ->>> Article.objects.filter(reporter__first_name='John') -[, ] - -# Query twice over the related field. ->>> Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') -[, ] - -# The underlying query only makes one join when a related table is referenced twice. ->>> queryset = Article.objects.filter(reporter__first_name__exact='John', reporter__last_name__exact='Smith') ->>> sql = queryset.query.get_compiler(queryset.db).as_sql()[0] ->>> 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_reporter.last_name='Smith'"]) -[, ] - -# And should work fine with the unicode that comes out of -# forms.Form.cleaned_data ->>> Article.objects.filter(reporter__first_name__exact='John').extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']) -[, ] - -# Find all Articles for the Reporter whose ID is 1. -# Use direct ID check, pk check, and object comparison ->>> Article.objects.filter(reporter__id__exact=1) -[, ] ->>> Article.objects.filter(reporter__pk=1) -[, ] ->>> Article.objects.filter(reporter=1) -[, ] ->>> Article.objects.filter(reporter=r) -[, ] - ->>> Article.objects.filter(reporter__in=[1,2]).distinct() -[, , ] ->>> Article.objects.filter(reporter__in=[r,r2]).distinct() -[, , ] - -# You can also use a queryset instead of a literal list of instances. -# The queryset must be reduced to a list of values using values(), -# then converted into a query ->>> Article.objects.filter(reporter__in=Reporter.objects.filter(first_name='John').values('pk').query).distinct() -[, ] - -# You need two underscores between "reporter" and "id" -- not one. ->>> Article.objects.filter(reporter_id__exact=1) -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter - -# You need to specify a comparison clause ->>> Article.objects.filter(reporter_id=1) -Traceback (most recent call last): - ... -FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headline, id, pub_date, reporter - -# 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 - - -# 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 - - -# Reporters can be queried ->>> Reporter.objects.filter(id__exact=1) -[] ->>> Reporter.objects.filter(pk=1) -[] ->>> Reporter.objects.filter(first_name__startswith='John') -[] - -# Reporters can query in opposite direction of ForeignKey definition ->>> Reporter.objects.filter(article__id__exact=1) -[] ->>> Reporter.objects.filter(article__pk=1) -[] ->>> Reporter.objects.filter(article=1) -[] ->>> Reporter.objects.filter(article=a) -[] - ->>> Reporter.objects.filter(article__in=[1,4]).distinct() -[] ->>> Reporter.objects.filter(article__in=[1,a3]).distinct() -[] ->>> Reporter.objects.filter(article__in=[a,a3]).distinct() -[] - ->>> Reporter.objects.filter(article__headline__startswith='This') -[, , ] ->>> Reporter.objects.filter(article__headline__startswith='This').distinct() -[] - -# Counting in the opposite direction works in conjunction with distinct() ->>> Reporter.objects.filter(article__headline__startswith='This').count() -3 ->>> Reporter.objects.filter(article__headline__startswith='This').distinct().count() -1 - -# Queries can go round in circles. ->>> Reporter.objects.filter(article__reporter__first_name__startswith='John') -[, , , ] ->>> Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct() -[] ->>> Reporter.objects.filter(article__reporter__exact=r).distinct() -[] - -# Regression for #12876 -- Model methods that include queries that -# recursive don't cause recursion depth problems under deepcopy. ->>> r.cached_query = Article.objects.filter(reporter=r) ->>> from copy import deepcopy ->>> deepcopy(r) - - -# Check that implied __exact also works. ->>> Reporter.objects.filter(article__reporter=r).distinct() -[] - -# It's possible to use values() calls across many-to-one relations. (Note, too, that we clear the ordering here so as not to drag the 'headline' field into the columns being used to determine uniqueness.) ->>> d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} ->>> list(Article.objects.filter(reporter=r).distinct().order_by().values('reporter__first_name', 'reporter__last_name')) == [d] -True - -# If you delete a reporter, his articles will be deleted. ->>> Article.objects.all() -[, , , , ] ->>> Reporter.objects.order_by('first_name') -[, ] ->>> r2.delete() ->>> Article.objects.all() -[, , , ] ->>> Reporter.objects.order_by('first_name') -[] - -# You can delete using a JOIN in the query. ->>> Reporter.objects.filter(article__headline__startswith='This').delete() ->>> Reporter.objects.all() -[] ->>> Article.objects.all() -[] - -# Check that Article.objects.select_related().dates() works properly when -# there are multiple Articles with the same date but different foreign-key -# objects (Reporters). ->>> r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') ->>> r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') ->>> a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) ->>> a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) ->>> Article.objects.select_related().dates('pub_date', 'day') -[datetime.datetime(1980, 4, 23, 0, 0)] ->>> Article.objects.select_related().dates('pub_date', 'month') -[datetime.datetime(1980, 4, 1, 0, 0)] ->>> Article.objects.select_related().dates('pub_date', 'year') -[datetime.datetime(1980, 1, 1, 0, 0)] -"""} diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py new file mode 100644 index 0000000000..2d474c0ddb --- /dev/null +++ b/tests/modeltests/many_to_one/tests.py @@ -0,0 +1,372 @@ +from datetime import datetime +from django.test import TestCase +from django.core.exceptions import FieldError +from models import Article, Reporter + +class ManyToOneTests(TestCase): + + def setUp(self): + # Create a few Reporters. + self.r = Reporter(first_name='John', last_name='Smith', email='john@example.com') + self.r.save() + self.r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') + self.r2.save() + # Create an Article. + self.a = Article(id=None, headline="This is a test", + pub_date=datetime(2005, 7, 27), reporter=self.r) + self.a.save() + + def test_get(self): + # Article objects have access to their related Reporter objects. + r = self.a.reporter + self.assertEqual(r.id, self.r.id) + # These are strings instead of unicode strings because that's what was used in + # the creation of this reporter (and we haven't refreshed the data from the + # database, which always returns unicode strings). + self.assertEqual((r.first_name, self.r.last_name), ('John', 'Smith')) + + def test_create(self): + # You can also instantiate an Article by passing the Reporter's ID + # instead of a Reporter object. + a3 = Article(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a3.save() + self.assertEqual(a3.reporter.id, self.r.id) + + # Similarly, the reporter ID can be a string. + a4 = Article(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + a4.save() + self.assertEqual(repr(a4.reporter), "") + + def test_add(self): + # Create an Article via the Reporter object. + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + self.assertEqual(repr(new_article), "") + self.assertEqual(new_article.reporter.id, self.r.id) + + # Create a new article, and add it to the article set. + new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) + self.r.article_set.add(new_article2) + self.assertEqual(new_article2.reporter.id, self.r.id) + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + "", + ]) + + # Add the same article to a different article set - check that it moves. + self.r2.article_set.add(new_article2) + self.assertEqual(new_article2.reporter.id, self.r2.id) + self.assertQuerysetEqual(self.r2.article_set.all(), [""]) + + # Adding an object of the wrong type raises TypeError. + self.assertRaises(TypeError, self.r.article_set.add, self.r2) + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + ]) + + def test_assign(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + # Assign the article to the reporter directly using the descriptor. + new_article2.reporter = self.r + new_article2.save() + self.assertEqual(repr(new_article2.reporter), "") + self.assertEqual(new_article2.reporter.id, self.r.id) + self.assertQuerysetEqual(self.r.article_set.all(), [ + "", + "", + "", + ]) + self.assertQuerysetEqual(self.r2.article_set.all(), []) + # Set the article back again using set descriptor. + self.r2.article_set = [new_article, new_article2] + self.assertQuerysetEqual(self.r.article_set.all(), [""]) + self.assertQuerysetEqual(self.r2.article_set.all(), + [ + "", + "", + ]) + + # Funny case - assignment notation can only go so far; because the + # ForeignKey cannot be null, existing members of the set must remain. + self.r.article_set = [new_article] + self.assertQuerysetEqual(self.r.article_set.all(), + [ + "", + "", + ]) + self.assertQuerysetEqual(self.r2.article_set.all(), [""]) + # Reporter cannot be null - there should not be a clear or remove method + self.assertFalse(hasattr(self.r2.article_set, 'remove')) + self.assertFalse(hasattr(self.r2.article_set, 'clear')) + + def test_selects(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + # Reporter objects have access to their related Article objects. + self.assertQuerysetEqual(self.r.article_set.all(), [ + "", + "", + ]) + self.assertQuerysetEqual(self.r.article_set.filter(headline__startswith='This'), + [""]) + self.assertEqual(self.r.article_set.count(), 2) + self.assertEqual(self.r2.article_set.count(), 1) + # Get articles by id + self.assertQuerysetEqual(Article.objects.filter(id__exact=self.a.id), + [""]) + self.assertQuerysetEqual(Article.objects.filter(pk=self.a.id), + [""]) + # Query on an article property + self.assertQuerysetEqual(Article.objects.filter(headline__startswith='This'), + [""]) + # 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". + self.assertQuerysetEqual(Article.objects.filter(reporter__first_name__exact='John'), + [ + "", + "", + ]) + # Check that implied __exact also works + self.assertQuerysetEqual(Article.objects.filter(reporter__first_name='John'), + [ + "", + "", + ]) + # Query twice over the related field. + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John', + reporter__last_name__exact='Smith'), + [ + "", + "", + ]) + # The underlying query only makes one join when a related table is referenced twice. + queryset = Article.objects.filter(reporter__first_name__exact='John', + reporter__last_name__exact='Smith') + self.assertNumQueries(1, list, queryset) + self.assertEqual(queryset.query.get_compiler(queryset.db).as_sql()[0].count('INNER JOIN'), 1) + + # The automatically joined table has a predictable name. + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John').extra( + where=["many_to_one_reporter.last_name='Smith'"]), + [ + "", + "", + ]) + # ... and should work fine with the unicode that comes out of forms.Form.cleaned_data + self.assertQuerysetEqual( + Article.objects.filter(reporter__first_name__exact='John' + ).extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']), + [ + "", + "", + ]) + # Find all Articles for a Reporter. + # Use direct ID check, pk check, and object comparison + self.assertQuerysetEqual( + Article.objects.filter(reporter__id__exact=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__pk=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter=self.r.id), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter=self.r), + [ + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__in=[self.r.id,self.r2.id]).distinct(), + [ + "", + "", + "", + ]) + self.assertQuerysetEqual( + Article.objects.filter(reporter__in=[self.r,self.r2]).distinct(), + [ + "", + "", + "", + ]) + # You can also use a queryset instead of a literal list of instances. + # The queryset must be reduced to a list of values using values(), + # then converted into a query + self.assertQuerysetEqual( + Article.objects.filter( + reporter__in=Reporter.objects.filter(first_name='John').values('pk').query + ).distinct(), + [ + "", + "", + ]) + # You need two underscores between "reporter" and "id" -- not one. + self.assertRaises(FieldError, Article.objects.filter, reporter_id__exact=self.r.id) + # You need to specify a comparison clause + self.assertRaises(FieldError, Article.objects.filter, reporter_id=self.r.id) + + def test_reverse_selects(self): + a3 = Article.objects.create(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a4 = Article.objects.create(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + # Reporters can be queried + self.assertQuerysetEqual(Reporter.objects.filter(id__exact=self.r.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(pk=self.r.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(first_name__startswith='John'), + [""]) + # Reporters can query in opposite direction of ForeignKey definition + self.assertQuerysetEqual(Reporter.objects.filter(article__id__exact=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article__pk=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article=self.a.id), + [""]) + self.assertQuerysetEqual(Reporter.objects.filter(article=self.a), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a.id,a3.id]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a.id,a3]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__in=[self.a,a3]).distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__headline__startswith='T'), + ["", ""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__headline__startswith='T').distinct(), + [""]) + + # Counting in the opposite direction works in conjunction with distinct() + self.assertEqual( + Reporter.objects.filter(article__headline__startswith='T').count(), 2) + self.assertEqual( + Reporter.objects.filter(article__headline__startswith='T').distinct().count(), 1) + + # Queries can go round in circles. + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__first_name__startswith='John'), + [ + "", + "", + "", + ]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(), + [""]) + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter__exact=self.r).distinct(), + [""]) + + # Check that implied __exact also works. + self.assertQuerysetEqual( + Reporter.objects.filter(article__reporter=self.r).distinct(), + [""]) + + # It's possible to use values() calls across many-to-one relations. + # (Note, too, that we clear the ordering here so as not to drag the + # 'headline' field into the columns being used to determine uniqueness) + d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} + self.assertEqual([d], + list(Article.objects.filter(reporter=self.r).distinct().order_by() + .values('reporter__first_name', 'reporter__last_name'))) + + def test_select_related(self): + # Check that Article.objects.select_related().dates() works properly when + # there are multiple Articles with the same date but different foreign-key + # objects (Reporters). + r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') + r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') + a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) + a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'day')), + [ + datetime(1980, 4, 23, 0, 0), + datetime(2005, 7, 27, 0, 0), + ]) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'month')), + [ + datetime(1980, 4, 1, 0, 0), + datetime(2005, 7, 1, 0, 0), + ]) + self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'year')), + [ + datetime(1980, 1, 1, 0, 0), + datetime(2005, 1, 1, 0, 0), + ]) + + def test_delete(self): + new_article = self.r.article_set.create(headline="John's second story", + pub_date=datetime(2005, 7, 29)) + new_article2 = self.r2.article_set.create(headline="Paul's story", + pub_date=datetime(2006, 1, 17)) + a3 = Article.objects.create(id=None, headline="Third article", + pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) + a4 = Article.objects.create(id=None, headline="Fourth article", + pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) + # If you delete a reporter, his articles will be deleted. + self.assertQuerysetEqual(Article.objects.all(), + [ + "", + "", + "", + "", + "", + ]) + self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), + [ + "", + "", + ]) + self.r2.delete() + self.assertQuerysetEqual(Article.objects.all(), + [ + "", + "", + "", + "", + ]) + self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), + [""]) + # You can delete using a JOIN in the query. + Reporter.objects.filter(article__headline__startswith='This').delete() + self.assertQuerysetEqual(Reporter.objects.all(), []) + self.assertQuerysetEqual(Article.objects.all(), []) + + def test_regression_12876(self): + # Regression for #12876 -- Model methods that include queries that + # recursive don't cause recursion depth problems under deepcopy. + self.r.cached_query = Article.objects.filter(reporter=self.r) + from copy import deepcopy + self.assertEqual(repr(deepcopy(self.r)), "")