Permalink
Browse files

Consolidated some many_to_one tests.

  • Loading branch information...
loic committed Sep 23, 2014
1 parent 2a1bdf5 commit 1fd6e13bf2db025dc93482184439cc1a900db276
@@ -30,3 +30,71 @@ def __str__(self):
class Meta:
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)
View
@@ -2,12 +2,13 @@
import datetime
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.utils import six
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):
@@ -374,12 +375,6 @@ def test_delete(self):
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)
- self.assertEqual(repr(deepcopy(self.r)), "<Reporter: John Smith>")
-
def test_explicit_fk(self):
# Create a new Article with get_or_create using an explicit value
# for a ForeignKey.
@@ -411,6 +406,12 @@ def test_explicit_fk(self):
repr(Article.objects.get(reporter_id=self.r2.id,
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):
r1 = Reporter.objects.create(first_name='Mike')
r2 = Reporter.objects.create(first_name='John')
@@ -443,3 +444,154 @@ def test_values_list_exception(self):
expected_message % ', '.join(['EXTRA'] + Article._meta.get_all_field_names()),
Article.objects.extra(select={'EXTRA': 'EXTRA_SELECT'}).values_list,
'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 Meta:
def __str__(self):
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 django.test import TestCase
-from .models import Reporter, Article
+from .models import Article, Car, Driver, Reporter
class ManyToOneNullTests(TestCase):
@@ -105,3 +105,10 @@ def test_clear_efficiency(self):
with self.assertNumQueries(1):
r.article_set.clear()
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()), [])
No changes.
@@ -1,69 +0,0 @@
-"""
-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')
Oops, something went wrong.

0 comments on commit 1fd6e13

Please sign in to comment.