Kaydet (Commit) 1fd6e13b authored tarafından Loic Bistuer's avatar Loic Bistuer

Consolidated some many_to_one tests.

üst 2a1bdf5c
...@@ -30,3 +30,71 @@ class Article(models.Model): ...@@ -30,3 +30,71 @@ class Article(models.Model):
class Meta: class Meta:
ordering = ('headline',) ordering = ('headline',)
# If ticket #1578 ever slips back in, these models will not be able to be
# created (the field names being lower-cased versions of their opposite
# classes is important here).
class First(models.Model):
second = models.IntegerField()
class Second(models.Model):
first = models.ForeignKey(First, related_name='the_first')
# Protect against repetition of #1839, #2415 and #2536.
class Third(models.Model):
name = models.CharField(max_length=20)
third = models.ForeignKey('self', null=True, related_name='child_set')
class Parent(models.Model):
name = models.CharField(max_length=20, unique=True)
bestchild = models.ForeignKey('Child', null=True, related_name='favored_by')
class Child(models.Model):
name = models.CharField(max_length=20)
parent = models.ForeignKey(Parent)
class ToFieldChild(models.Model):
parent = models.ForeignKey(Parent, to_field='name')
# Multiple paths to the same model (#7110, #7125)
@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Record(models.Model):
category = models.ForeignKey(Category)
@python_2_unicode_compatible
class Relation(models.Model):
left = models.ForeignKey(Record, related_name='left_set')
right = models.ForeignKey(Record, related_name='right_set')
def __str__(self):
return "%s - %s" % (self.left.category.name, self.right.category.name)
# Test related objects visibility.
class SchoolManager(models.Manager):
def get_queryset(self):
return super(SchoolManager, self).get_queryset().filter(is_public=True)
class School(models.Model):
is_public = models.BooleanField(default=False)
objects = SchoolManager()
class Student(models.Model):
school = models.ForeignKey(School)
...@@ -2,12 +2,13 @@ from copy import deepcopy ...@@ -2,12 +2,13 @@ from copy import deepcopy
import datetime import datetime
from django.core.exceptions import MultipleObjectsReturned, FieldError from django.core.exceptions import MultipleObjectsReturned, FieldError
from django.db import transaction from django.db import models, transaction
from django.test import TestCase from django.test import TestCase
from django.utils import six from django.utils import six
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from .models import Article, Reporter from .models import (Article, Reporter, First, Third, Parent, Child,
ToFieldChild, Category, Record, Relation, School, Student)
class ManyToOneTests(TestCase): class ManyToOneTests(TestCase):
...@@ -374,12 +375,6 @@ class ManyToOneTests(TestCase): ...@@ -374,12 +375,6 @@ class ManyToOneTests(TestCase):
self.assertQuerysetEqual(Reporter.objects.all(), []) self.assertQuerysetEqual(Reporter.objects.all(), [])
self.assertQuerysetEqual(Article.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)
self.assertEqual(repr(deepcopy(self.r)), "<Reporter: John Smith>")
def test_explicit_fk(self): def test_explicit_fk(self):
# Create a new Article with get_or_create using an explicit value # Create a new Article with get_or_create using an explicit value
# for a ForeignKey. # for a ForeignKey.
...@@ -411,6 +406,12 @@ class ManyToOneTests(TestCase): ...@@ -411,6 +406,12 @@ class ManyToOneTests(TestCase):
repr(Article.objects.get(reporter_id=self.r2.id, repr(Article.objects.get(reporter_id=self.r2.id,
pub_date=datetime.date(2011, 5, 7)))) pub_date=datetime.date(2011, 5, 7))))
def test_deepcopy_and_circular_references(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)
self.assertEqual(repr(deepcopy(self.r)), "<Reporter: John Smith>")
def test_manager_class_caching(self): def test_manager_class_caching(self):
r1 = Reporter.objects.create(first_name='Mike') r1 = Reporter.objects.create(first_name='Mike')
r2 = Reporter.objects.create(first_name='John') r2 = Reporter.objects.create(first_name='John')
...@@ -443,3 +444,154 @@ class ManyToOneTests(TestCase): ...@@ -443,3 +444,154 @@ class ManyToOneTests(TestCase):
expected_message % ', '.join(['EXTRA'] + Article._meta.get_all_field_names()), expected_message % ', '.join(['EXTRA'] + Article._meta.get_all_field_names()),
Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list, Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list,
'notafield') 'notafield')
def test_fk_assignment_and_related_object_cache(self):
# Tests of ForeignKey assignment and the related-object cache (see #6886).
p = Parent.objects.create(name="Parent")
c = Child.objects.create(name="Child", parent=p)
# Look up the object again so that we get a "fresh" object.
c = Child.objects.get(name="Child")
p = c.parent
# Accessing the related object again returns the exactly same object.
self.assertTrue(c.parent is p)
# But if we kill the cache, we get a new object.
del c._parent_cache
self.assertFalse(c.parent is p)
# Assigning a new object results in that object getting cached immediately.
p2 = Parent.objects.create(name="Parent 2")
c.parent = p2
self.assertTrue(c.parent is p2)
# Assigning None succeeds if field is null=True.
p.bestchild = None
self.assertTrue(p.bestchild is None)
# bestchild should still be None after saving.
p.save()
self.assertTrue(p.bestchild is None)
# bestchild should still be None after fetching the object again.
p = Parent.objects.get(name="Parent")
self.assertTrue(p.bestchild is None)
# Assigning None fails: Child.parent is null=False.
self.assertRaises(ValueError, setattr, c, "parent", None)
# You also can't assign an object of the wrong type here
self.assertRaises(ValueError, setattr, c, "parent", First(id=1, second=1))
# Nor can you explicitly assign None to Child.parent during object
# creation (regression for #9649).
self.assertRaises(ValueError, Child, name='xyzzy', parent=None)
self.assertRaises(ValueError, Child.objects.create, name='xyzzy', parent=None)
# Creation using keyword argument should cache the related object.
p = Parent.objects.get(name="Parent")
c = Child(parent=p)
self.assertTrue(c.parent is p)
# Creation using keyword argument and unsaved related instance (#8070).
p = Parent()
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (p, Child.parent.field.rel.to._meta.object_name)):
Child(parent=p)
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (p, Child.parent.field.rel.to._meta.object_name)):
ToFieldChild(parent=p)
# Creation using attname keyword argument and an id will cause the
# related object to be fetched.
p = Parent.objects.get(name="Parent")
c = Child(parent_id=p.id)
self.assertFalse(c.parent is p)
self.assertEqual(c.parent, p)
def test_multiple_foreignkeys(self):
# Test of multiple ForeignKeys to the same model (bug #7125).
c1 = Category.objects.create(name='First')
c2 = Category.objects.create(name='Second')
c3 = Category.objects.create(name='Third')
r1 = Record.objects.create(category=c1)
r2 = Record.objects.create(category=c1)
r3 = Record.objects.create(category=c2)
r4 = Record.objects.create(category=c2)
r5 = Record.objects.create(category=c3)
Relation.objects.create(left=r1, right=r2)
Relation.objects.create(left=r3, right=r4)
Relation.objects.create(left=r1, right=r3)
Relation.objects.create(left=r5, right=r2)
Relation.objects.create(left=r3, right=r2)
q1 = Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second'])
self.assertQuerysetEqual(q1, ["<Relation: First - Second>"])
q2 = Category.objects.filter(record__left_set__right__category__name='Second').order_by('name')
self.assertQuerysetEqual(q2, ["<Category: First>", "<Category: Second>"])
p = Parent.objects.create(name="Parent")
c = Child.objects.create(name="Child", parent=p)
self.assertRaises(ValueError, Child.objects.create, name="Grandchild", parent=c)
def test_fk_instantiation_outside_model(self):
# Regression for #12190 -- Should be able to instantiate a FK outside
# of a model, and interrogate its related field.
cat = models.ForeignKey(Category)
self.assertEqual('id', cat.rel.get_related_field().name)
def test_relation_unsaved(self):
# Test that the <field>_set manager does not join on Null value fields (#17541)
Third.objects.create(name='Third 1')
Third.objects.create(name='Third 2')
th = Third(name="testing")
# The object isn't saved an thus the relation field is null - we won't even
# execute a query in this case.
with self.assertNumQueries(0):
self.assertEqual(th.child_set.count(), 0)
th.save()
# Now the model is saved, so we will need to execute an query.
with self.assertNumQueries(1):
self.assertEqual(th.child_set.count(), 0)
def test_related_object(self):
public_school = School.objects.create(is_public=True)
public_student = Student.objects.create(school=public_school)
private_school = School.objects.create(is_public=False)
private_student = Student.objects.create(school=private_school)
# Only one school is available via all() due to the custom default manager.
self.assertQuerysetEqual(
School.objects.all(),
["<School: School object>"]
)
self.assertEqual(public_student.school, public_school)
# Make sure the base manager is used so that an student can still access
# its related school even if the default manager doesn't normally
# allow it.
self.assertEqual(private_student.school, private_school)
# If the manager is marked "use_for_related_fields", it'll get used instead
# of the "bare" queryset. Usually you'd define this as a property on the class,
# but this approximates that in a way that's easier in tests.
School.objects.use_for_related_fields = True
try:
private_student = Student.objects.get(pk=private_student.pk)
self.assertRaises(School.DoesNotExist, lambda: private_student.school)
finally:
School.objects.use_for_related_fields = False
def test_hasattr_related_object(self):
# The exception raised on attribute access when a related object
# doesn't exist should be an instance of a subclass of `AttributeError`
# refs #21563
self.assertFalse(hasattr(Article(), 'reporter'))
...@@ -27,3 +27,11 @@ class Article(models.Model): ...@@ -27,3 +27,11 @@ class Article(models.Model):
def __str__(self): def __str__(self):
return self.headline return self.headline
class Car(models.Model):
make = models.CharField(max_length=100, null=True, unique=True)
class Driver(models.Model):
car = models.ForeignKey(Car, to_field='make', null=True, related_name='drivers')
...@@ -2,7 +2,7 @@ from __future__ import unicode_literals ...@@ -2,7 +2,7 @@ from __future__ import unicode_literals
from django.test import TestCase from django.test import TestCase
from .models import Reporter, Article from .models import Article, Car, Driver, Reporter
class ManyToOneNullTests(TestCase): class ManyToOneNullTests(TestCase):
...@@ -105,3 +105,10 @@ class ManyToOneNullTests(TestCase): ...@@ -105,3 +105,10 @@ class ManyToOneNullTests(TestCase):
with self.assertNumQueries(1): with self.assertNumQueries(1):
r.article_set.clear() r.article_set.clear()
self.assertEqual(r.article_set.count(), 0) self.assertEqual(r.article_set.count(), 0)
def test_related_null_to_field(self):
c1 = Car.objects.create()
d1 = Driver.objects.create()
self.assertIs(d1.car, None)
with self.assertNumQueries(0):
self.assertEqual(list(c1.drivers.all()), [])
"""
Regression tests for a few ForeignKey bugs.
"""
from __future__ import unicode_literals
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
# If ticket #1578 ever slips back in, these models will not be able to be
# created (the field names being lower-cased versions of their opposite
# classes is important here).
class First(models.Model):
second = models.IntegerField()
class Second(models.Model):
first = models.ForeignKey(First, related_name='the_first')
# Protect against repetition of #1839, #2415 and #2536.
class Third(models.Model):
name = models.CharField(max_length=20)
third = models.ForeignKey('self', null=True, related_name='child_set')
class Parent(models.Model):
name = models.CharField(max_length=20, unique=True)
bestchild = models.ForeignKey('Child', null=True, related_name='favored_by')
class Child(models.Model):
name = models.CharField(max_length=20)
parent = models.ForeignKey(Parent)
class ToFieldChild(models.Model):
parent = models.ForeignKey(Parent, to_field='name')
# Multiple paths to the same model (#7110, #7125)
@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Record(models.Model):
category = models.ForeignKey(Category)
@python_2_unicode_compatible
class Relation(models.Model):
left = models.ForeignKey(Record, related_name='left_set')
right = models.ForeignKey(Record, related_name='right_set')
def __str__(self):
return "%s - %s" % (self.left.category.name, self.right.category.name)
class Car(models.Model):
make = models.CharField(max_length=100, null=True, unique=True)
class Driver(models.Model):
car = models.ForeignKey(Car, to_field='make', null=True, related_name='drivers')
from __future__ import unicode_literals
from django.db import models
from django.test import TestCase
from .models import (
First, Third, Parent, Child, ToFieldChild, Category, Record, Relation, Car, Driver)
class ManyToOneRegressionTests(TestCase):
def test_object_creation(self):
Third.objects.create(id='3', name='An example')
parent = Parent(name='fred')
parent.save()
Child.objects.create(name='bam-bam', parent=parent)
def test_fk_assignment_and_related_object_cache(self):
# Tests of ForeignKey assignment and the related-object cache (see #6886).
p = Parent.objects.create(name="Parent")
c = Child.objects.create(name="Child", parent=p)
# Look up the object again so that we get a "fresh" object.
c = Child.objects.get(name="Child")
p = c.parent
# Accessing the related object again returns the exactly same object.
self.assertTrue(c.parent is p)
# But if we kill the cache, we get a new object.
del c._parent_cache
self.assertFalse(c.parent is p)
# Assigning a new object results in that object getting cached immediately.
p2 = Parent.objects.create(name="Parent 2")
c.parent = p2
self.assertTrue(c.parent is p2)
# Assigning None succeeds if field is null=True.
p.bestchild = None
self.assertTrue(p.bestchild is None)
# bestchild should still be None after saving.
p.save()
self.assertTrue(p.bestchild is None)
# bestchild should still be None after fetching the object again.
p = Parent.objects.get(name="Parent")
self.assertTrue(p.bestchild is None)
# Assigning None fails: Child.parent is null=False.
self.assertRaises(ValueError, setattr, c, "parent", None)
# You also can't assign an object of the wrong type here
self.assertRaises(ValueError, setattr, c, "parent", First(id=1, second=1))
# Nor can you explicitly assign None to Child.parent during object
# creation (regression for #9649).
self.assertRaises(ValueError, Child, name='xyzzy', parent=None)
self.assertRaises(ValueError, Child.objects.create, name='xyzzy', parent=None)
# Creation using keyword argument should cache the related object.
p = Parent.objects.get(name="Parent")
c = Child(parent=p)
self.assertTrue(c.parent is p)
# Creation using keyword argument and unsaved related instance (#8070).
p = Parent()
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (p, Child.parent.field.rel.to._meta.object_name)):
Child(parent=p)
with self.assertRaisesMessage(ValueError,
'Cannot assign "%r": "%s" instance isn\'t saved in the database.'
% (p, Child.parent.field.rel.to._meta.object_name)):
ToFieldChild(parent=p)
# Creation using attname keyword argument and an id will cause the
# related object to be fetched.
p = Parent.objects.get(name="Parent")
c = Child(parent_id=p.id)
self.assertFalse(c.parent is p)
self.assertEqual(c.parent, p)
def test_multiple_foreignkeys(self):
# Test of multiple ForeignKeys to the same model (bug #7125).
c1 = Category.objects.create(name='First')
c2 = Category.objects.create(name='Second')
c3 = Category.objects.create(name='Third')
r1 = Record.objects.create(category=c1)
r2 = Record.objects.create(category=c1)
r3 = Record.objects.create(category=c2)
r4 = Record.objects.create(category=c2)
r5 = Record.objects.create(category=c3)
Relation.objects.create(left=r1, right=r2)
Relation.objects.create(left=r3, right=r4)
Relation.objects.create(left=r1, right=r3)
Relation.objects.create(left=r5, right=r2)
Relation.objects.create(left=r3, right=r2)
q1 = Relation.objects.filter(left__category__name__in=['First'], right__category__name__in=['Second'])
self.assertQuerysetEqual(q1, ["<Relation: First - Second>"])
q2 = Category.objects.filter(record__left_set__right__category__name='Second').order_by('name')
self.assertQuerysetEqual(q2, ["<Category: First>", "<Category: Second>"])
p = Parent.objects.create(name="Parent")
c = Child.objects.create(name="Child", parent=p)
self.assertRaises(ValueError, Child.objects.create, name="Grandchild", parent=c)
def test_fk_instantiation_outside_model(self):
# Regression for #12190 -- Should be able to instantiate a FK outside
# of a model, and interrogate its related field.
cat = models.ForeignKey(Category)
self.assertEqual('id', cat.rel.get_related_field().name)
def test_relation_unsaved(self):
# Test that the <field>_set manager does not join on Null value fields (#17541)
Third.objects.create(name='Third 1')
Third.objects.create(name='Third 2')
th = Third(name="testing")
# The object isn't saved an thus the relation field is null - we won't even
# execute a query in this case.
with self.assertNumQueries(0):
self.assertEqual(th.child_set.count(), 0)
th.save()
# Now the model is saved, so we will need to execute an query.
with self.assertNumQueries(1):
self.assertEqual(th.child_set.count(), 0)
def test_related_null_to_field(self):
c1 = Car.objects.create()
d1 = Driver.objects.create()
self.assertIs(d1.car, None)
with self.assertNumQueries(0):
self.assertEqual(list(c1.drivers.all()), [])
from django.db import models
class SourceManager(models.Manager):
def get_queryset(self):
return super(SourceManager, self).get_queryset().filter(is_public=True)
class Source(models.Model):
is_public = models.BooleanField(default=False)
objects = SourceManager()
class Item(models.Model):
source = models.ForeignKey(Source)
from django.test import TestCase
from .models import Source, Item
class ReverseSingleRelatedTests(TestCase):
"""
Regression tests for an object that cannot access a single related
object due to a restrictive default manager.
"""
def test_reverse_single_related(self):
public_source = Source.objects.create(is_public=True)
public_item = Item.objects.create(source=public_source)
private_source = Source.objects.create(is_public=False)
private_item = Item.objects.create(source=private_source)
# Only one source is available via all() due to the custom default manager.
self.assertQuerysetEqual(
Source.objects.all(),
["<Source: Source object>"]
)
self.assertEqual(public_item.source, public_source)
# Make sure that an item can still access its related source even if the default
# manager doesn't normally allow it.
self.assertEqual(private_item.source, private_source)
# If the manager is marked "use_for_related_fields", it'll get used instead
# of the "bare" queryset. Usually you'd define this as a property on the class,
# but this approximates that in a way that's easier in tests.
Source.objects.use_for_related_fields = True
try:
private_item = Item.objects.get(pk=private_item.pk)
self.assertRaises(Source.DoesNotExist, lambda: private_item.source)
finally:
Source.objects.use_for_related_fields = False
def test_hasattr_single_related(self):
# The exception raised on attribute access when a related object
# doesn't exist should be an instance of a subclass of `AttributeError`
# refs #21563
self.assertFalse(hasattr(Item(), 'source'))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment