Ticket #11811: 11811-patch-rev-5.diff

File 11811-patch-rev-5.diff, 7.2 KB (added by ashearer, 14 years ago)

Patch (with error on save instead of on assignment)

  • django/db/models/base.py

     
    638638        return getattr(self, cachename)
    639639
    640640    def prepare_database_save(self, unused):
     641        if self.pk is None:
     642            # This is reached if this instance is missing a primary key value
     643            # but is passed as a parameter to a queryset's update() method,
     644            # which needs some value to use as a foreign key.
     645            raise ValueError('Cannot assign the instance "%r" to a foreign key'
     646                ' field, because its primary key is not available.'
     647                ' (For autonumbered keys, the instance would need to be saved'
     648                ' first.)' % self)
    641649        return self.pk
    642650
    643651    def clean(self):
  • django/db/models/fields/related.py

     
    807807        if isinstance(field_default, self.rel.to):
    808808            return getattr(field_default, self.rel.get_related_field().attname)
    809809        return field_default
     810   
     811    def pre_save(self, model_instance, add):
     812        value = super(ForeignKey, self).pre_save(model_instance, add)
     813        if (value is None and self.null and
     814            getattr(model_instance, self.get_cache_name(), None) is not None):
     815            # Error: we don't have a related key to save but do have a cached
     816            # related instance. This indicates that a reference to the related
     817            # instance was intended to make it into the database, but the lack
     818            # of a related key value at the time of assignment makes this
     819            # impossible.
     820            # The situation is typically caused by assigning a new, unsaved
     821            # instance to a foreign key field, leaving its autonumbered
     822            # primary key empty.
     823            # Non-null fields will raise an exception in this case anyway
     824            # (an IntegrityError) so we only worry about nullable fields,
     825            # to preserve previous behavior.
     826            raise ValueError('Cannot save a foreign key referring to the'
     827                ' instance "%r" into the field "%s.%s", because the key value'
     828                ' for the instance was not available at the time of assignment'
     829                ' to the field. (For autonumbered keys, the related instance'
     830                ' may need to be saved prior to the assignment.)' %
     831                (getattr(model_instance, self.get_cache_name()),
     832                 model_instance._meta.object_name, self.name))
     833        return value
    810834
    811835    def get_db_prep_save(self, value, connection):
    812836        if value == '' or value == None:
  • tests/regressiontests/model_regress/tests.py

     
    1 from models import Worker
     1from models import Department, Worker, Site, Flatpage
     2from django.core.exceptions import ObjectDoesNotExist
    23from django.test import TestCase
    34
    45class RelatedModelOrderedLookupTest(TestCase):
     
    1415
    1516    def test_related_lte_lookup(self):
    1617        Worker.objects.filter(department__lte=0)
     18
     19class AssignmentWithMissingForeignKeyTest(TestCase):
     20    """
     21    Regression test for #11811: setting a foreign key field to a model
     22    instance with a null pk (i.e. an unsaved instance) should raise an
     23    exception, which occurs when the model is saved to the database.
     24    """
     25    def test_attr_assignment_nullable(self):
     26        s = Site(name='My Unsaved Site')
     27        f = Flatpage(id=1, text='I will try to specify that site')
     28        f.site = s
     29        self.assertRaises(ValueError, f.save)
     30        self.assertRaises(ObjectDoesNotExist, lambda: Flatpage.objects.get(
     31            id=1))
     32
     33    def test_model_initializer_nullable(self):
     34        s = Site(name='My Unsaved Site')
     35        f = Flatpage(id=2, text='I will also try to specify that site', site=s)
     36        self.assertRaises(ValueError, f.save)
     37        self.assertRaises(ObjectDoesNotExist, lambda: Flatpage.objects.get(
     38            id=2))
     39   
     40    def test_manager_create_nullable(self):
     41        s = Site(name='My Unsaved Site')
     42        self.assertRaises(ValueError, lambda: Flatpage.objects.create(id=3,
     43            text='Never mind, I will try to specify that site', site=s))
     44        self.assertRaises(ObjectDoesNotExist, lambda: Flatpage.objects.get(
     45            id=3))
     46   
     47    def test_queryset_update_nullable(self):
     48        s = Site(name='My Unsaved Site')
     49        Flatpage.objects.create(id=4, text='Not specifying a site')
     50        self.assertRaises(ValueError, lambda: Flatpage.objects.filter(
     51            id=4).update(site=s))
     52   
     53    def test_attr_assignment_nonnull(self):
     54        d = Department(name='An Unsaved Department')
     55        w = Worker(id=1, name='Milton Waddams')
     56        w.department = d
     57        # the DB backend will define its own IntegrityError, so
     58        # catch Exception to be backend-neutral
     59        self.assertRaises(Exception, w.save)
     60        self.assertRaises(ObjectDoesNotExist, lambda: Worker.objects.get(
     61            id=1))
     62
     63    def test_model_initializer_nonnull(self):
     64        d = Department(name='An Unsaved Department')
     65        w = Worker(id=2, name='Milton Waddams', department=d)
     66        # the DB backend will define its own IntegrityError, so
     67        # catch Exception to be backend-neutral
     68        self.assertRaises(Exception, w.save)
     69        self.assertRaises(ObjectDoesNotExist, lambda: Worker.objects.get(
     70            id=2))
     71   
     72    def test_manager_create_nonnull(self):
     73        d = Department(name='An Unsaved Department')
     74        # the DB backend will define its own IntegrityError, so
     75        # catch Exception to be backend-neutral
     76        self.assertRaises(Exception, lambda: Worker.objects.create(id=3,
     77            name='Milton Waddams', department=d))
     78        self.assertRaises(ObjectDoesNotExist, lambda: Worker.objects.get(
     79            id=3))
     80   
     81    def test_queryset_update_nonnull(self):
     82        d1 = Department(id=1, name='A Real Department')
     83        d1.save()
     84        d2 = Department(name='An Unsaved Department')
     85        Worker.objects.create(id=4, name='Milton Waddams', department=d1)
     86        self.assertRaises(ValueError, lambda: Worker.objects.filter(
     87            id=4).update(department=d2))
     88
  • tests/regressiontests/model_regress/models.py

     
    5050    def __unicode__(self):
    5151        return self.name
    5252
     53# two models with a nullable foreign key relationship for testing #11811
     54class Site(models.Model):
     55    name = models.CharField(max_length=200)
     56
     57    def __unicode__(self):
     58        return self.name
     59
     60class Flatpage(models.Model):
     61    text = models.TextField()
     62    site = models.ForeignKey(Site, null=True)
     63
    5364class BrokenUnicodeMethod(models.Model):
    5465    name = models.CharField(max_length=7)
    5566
Back to Top