Opened 7 years ago

Closed 6 years ago

Last modified 6 years ago

#28742 closed Bug (fixed)

get/set on a OneToOneField produces AttributeError

Reported by: Faidon Liambotis Owned by: Paulo
Component: Database layer (models, ORM) Version: 1.11
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

On a sample empty project (mysite) with a sample empty app (myapp) with these models:

from django.db import models
 
class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=80)
 
class Restaurant(models.Model):
    place = models.OneToOneField(Place)
    serves_hot_dogs = models.BooleanField(default=False)
    serves_pizza = models.BooleanField(default=False)

Executing this, works just fine:

p = Place()
p.restaurant = None

But this:

p = Place()
r = getattr(p, 'restaurant', None)
p.restaurant = None

…raises an AttributeError:

AttributeError                            Traceback (most recent call last)
<ipython-input-4-c4df23702cac> in <module>()
----> 1 p.restaurant = None
 
.../lib/python3.5/site-packages/django/db/models/fields/related_descriptors.py in __set__(self, instance, value)
    435             else:
    436                 delattr(instance, self.cache_name)
--> 437                 setattr(rel_obj, self.related.field.name, None)
    438         elif not isinstance(value, self.related.related_model):
    439             # An object must be an instance of the related class.
 
AttributeError: 'NoneType' object has no attribute 'place'

The above is with Python 3.5.3 and Django 1.11.6, although user "knbk" on #django said that they reproduced it in master.

Change History (8)

comment:1 by Tim Graham, 7 years ago

Triage Stage: UnreviewedAccepted

comment:2 by Josh Schneier, 6 years ago

Another way of reproducing this is with

    def test_getattr_getter(self):
        place = Place()
        try:
            place.restaurant
        except Restaurant.DoesNotExist:
            place.restaurant = None

The problem is that we are caching the return of Restaurant.DoesNotExist. Removing the caching triggers a regression test for #17439. I saw that Aymeric closed that one quite a while ago so I'd be interested on some additional thoughts.

comment:3 by Paulo, 6 years ago

Owner: changed from nobody to Paulo
Status: newassigned

comment:5 by Tim Graham, 6 years ago

Has patch: set

The test in the PR is failing as far back as I tested (Django 1.7, note: you have to update the models according to 7fe554b2a3d72d0142e5c9e97efbd0a672c2d790 there).

Are you seeing a regression from an older version of Django?

comment:6 by Paulo, 6 years ago

I didn't test that far back.
Only traced the AttributeError to the commit above, before that I assume a ValueError was raised because setting None was just not allowed.
So is not really a regression.

comment:7 by Tim Graham <timograham@…>, 6 years ago

Resolution: fixed
Status: assignedclosed

In fcfcf8a:

Fixed #28742 -- Fixed AttributeError crash when assigning None to cached reverse relations.

comment:8 by Tim Graham <timograham@…>, 6 years ago

In f2d5417d:

[2.0.x] Fixed #28742 -- Fixed AttributeError crash when assigning None to cached reverse relations.

Backport of fcfcf8aae470d893b0d2ef176434461edf9e9c4d from master

Note: See TracTickets for help on using tickets.
Back to Top