#28856: GenericForeignKey attributes create new instances on every access
-------------------------------------+-------------------------------------
Reporter: Morgan Wahl | Owner: nobody
Type: Bug | Status: new
Component: | Version: 1.10
contrib.contenttypes |
Severity: Normal | Resolution:
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Morgan Wahl:
Old description:
> Given these models:
>
> {{{
> class OtherSuper(models.Model):
> pass
>
> class OtherSub(OtherSuper):
> pass
>
> class Ref(models.Model):
> obj_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
> obj_id = models.CharField(max_length=255)
> obj = GenericForeignKey('obj_type', 'obj_id')
> }}}
>
> I get this behavior:
>
> {{{
> In [1]: ref = Ref.objects.create(obj=OtherSub.objects.create())
>
> In [2]: id(ref.obj) == id(ref.obj)
> Out[2]: True
>
> In [3]: ref.refresh_from_db()
>
> In [4]: id(ref.obj) == id(ref.obj)
> Out[4]: False
> }}}
>
> Each time `ref.obj` is accessed, a new instance is created for its value.
> This is a problem, since doing something like `ref.obj.field = 1;
> ref.obj.save()` won't actually update the field in the database. This
> only happens when the referenced object is an instance of a model that
> subclasses another model. (So, it wouldn't happen if referencing
> `OtherSuper` in the models above.)
>
> I've written a regression test against stable/1.10.x . I'll attach a
> patch.
>
> I discovered this because I have code that does the above (changes a
> field on the related model and calls save). I call this a regression
> because it works correctly on 1.9.
>
> I'm not sure what the underly bug is; I looked at the diff in
> `contenttypes` between 1.9 and 1.10, and there are more than a few
> changes. Hopefully someone who understands the `GenericForeignKey`
> implementation can figure this out.
New description:
Given these models:
{{{
class OtherSuper(models.Model):
pass
class OtherSub(OtherSuper):
pass
class Ref(models.Model):
obj_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
obj_id = models.CharField(max_length=255)
obj = GenericForeignKey('obj_type', 'obj_id')
}}}
I get this behavior:
{{{
In [1]: ref = Ref.objects.create(obj=OtherSub.objects.create())
In [2]: id(ref.obj) == id(ref.obj)
Out[2]: True
In [3]: ref.refresh_from_db()
In [4]: id(ref.obj) == id(ref.obj)
Out[4]: False
}}}
Each time `ref.obj` is accessed, a new instance is created for its value.
This is a problem, since doing something like `ref.obj.field = 1;
ref.obj.save()` won't actually update the field in the database. This only
happens when the referenced object is an instance of a model that
subclasses another model. (So, it wouldn't happen if referencing
`OtherSuper` in the models above.) The `refresh_from_db()` call is also
necessary to reproduce this in a simple test like the above; it happens
with any instance created from an existing DB record.
I've written a regression test against stable/1.10.x . I'll attach a
patch.
I discovered this because I have code that does the above (changes a field
on the related model and calls save). I call this a regression because it
works correctly on 1.9.
I'm not sure what the underly bug is; I looked at the diff in
`contenttypes` between 1.9 and 1.10, and there are more than a few
changes. Hopefully someone who understands the `GenericForeignKey`
implementation can figure this out.
--
--
Ticket URL: <https://code.djangoproject.com/ticket/28856#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/068.70b9bbf65b8902a0a7e16b4227cdef16%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.