#32420 closed Bug (fixed)
build_instance in (de)serializers uses model's primary key field name instead of attname to detect if data contains a primary key
Reported by: | trybik | Owned by: | trybik |
---|---|---|---|
Component: | Core (Serialization) | Version: | dev |
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 (last modified by )
The bug was introduced in #28385 fix, in commit dcd1025. Currently in master, the django.core.serializers.base.build_instance
function uses pk = data.get(Model._meta.pk.name)
instead of pk = data.get(Model._meta.pk.attname)
:
def build_instance(Model, data, db): """ Build a model instance. If the model instance doesn't have a primary key and the model supports natural keys, try to retrieve it from the database. """ default_manager = Model._meta.default_manager pk = data.get(Model._meta.pk.name) if (pk is None and hasattr(default_manager, 'get_by_natural_key') and hasattr(Model, 'natural_key')): natural_key = Model(**data).natural_key() try: data[Model._meta.pk.attname] = Model._meta.pk.to_python( default_manager.db_manager(db).get_by_natural_key(*natural_key).pk ) except Model.DoesNotExist: pass return Model(**data)
Why it should use attname
? Because it may be different than name
and the following corresponding assignments use data[Model._meta.pk.attname] = ...
:
- proceeding
django.core.serializers.python.Deserializer
which callsbuild_instance
:def Deserializer(...): ... for d in object_list: ... data = {} if 'pk' in d: try: data[Model._meta.pk.attname] = Model._meta.pk.to_python(d.get('pk')) except Exception as e: ... obj = base.build_instance(Model, data, using) yield ...
- following 5 lines further assignment
data[Model._meta.pk.attname] = ...
usesattname
.
The marginal error case is when primary key is also a foreign key (then attname
has "_id"
suffix). Moreover, to actually make it an error you have to create a bit pathological situation where you have to have natural key but it does not work, e.g.:
class Author(models.Model): name = models.CharField(max_length=20) class GhostWriterManager(models.Manager): def get_by_natural_key(self, *args, **kwargs): raise NotImplementedError("Don't get by natural key") class GhostWriter(models.Model): author_ptr = models.ForeignKey(Author, on_delete=models.CASCADE, primary_key=True) objects = GhostWriterManager() def natural_key(self): raise NotImplementedError("There is no natural key")
This rare situation actually can come up in an arguably normal situation, when using django-polymorphic non-abstract subclasses - when loading the subclass part of JSON list, and when the subclass uses a natural key that refers to fields from base class. The natural key will work perfectly fine, just not when loading the subclass part of JSON.
Change History (6)
comment:1 by , 4 years ago
comment:2 by , 4 years ago
Description: | modified (diff) |
---|
comment:3 by , 4 years ago
Has patch: | set |
---|---|
Triage Stage: | Unreviewed → Accepted |
Thanks for the detailed report and the PR.
comment:4 by , 4 years ago
Description: | modified (diff) |
---|
PR with a fix: https://github.com/django/django/pull/13975