I have a model
class DeviceAdmin(models.Model):
dev_id = models.AutoField(db_column='dev_id', primary_key=True)
...
A view locates an object:
device = DeviceAdmin.objects.get(uuid=uuid)
then it makes some changes (or maybe not)
if (...):
device.os_type = 'windows'
...
then it tries to save any changes:
device.save()
Here's where Django attempts to insert anther row instead of updating, causing a DB error
django.db.utils.IntegrityError: duplicate key value violates unique constraint
There's a kind of similar question with solution:
Once I set the primary_key field to an AutoField, the issue went away.
However, my primary key is already an AutoField.
So I stepped through django code with debugger, and fund this in file ...site-packages\django\models\base.py
:
# If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
if pk_set and not force_insert:
base_qs = cls._base_manager.using(using)
values = [(f, None, (getattr(self, f.attname) if raw else f.pre_save(self, False)))
for f in non_pks]
forced_update = update_fields or force_update
updated = self._do_update(base_qs, using, pk_val, values, update_fields,
forced_update)
if force_update and not updated:
raise DatabaseError("Forced update did not affect any rows.")
if update_fields and not updated:
raise DatabaseError("Save with update_fields did not affect any rows.")
if not updated:
if meta.order_with_respect_to:
# If this is a model with an order_with_respect_to
# autopopulate the _order field
field = meta.order_with_respect_to
filter_args = field.get_filter_kwargs_for_object(self)
order_value = cls._base_manager.using(using).filter(**filter_args).count()
self._order = order_value
fields = meta.local_concrete_fields
if not pk_set:
fields = [f for f in fields if f is not meta.auto_field]
update_pk = meta.auto_field and not pk_set
result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
if update_pk:
setattr(self, meta.pk.attname, result)
return updated
It seems that if there are no changes (and thus update doesn't do anything), then it will try to insert a duplicate record.
I tested this with adding a save right after get
:
device = DeviceAdmin.objects.get(uuid=uuid)
device.save()
and it's trying to insert a duplicate.
Does this mean, my code needs to keep track if an object has to be saved to DB or not?
What's the simplest way to get around this problem?
UPDATE:
As a test, I put a save
immediately after finding an object with get
, without any changes:
device = DeviceAdmin.objects.get(uuid=uuid)
device.save()
Django should know that the object is an existing one. And yet, it tries to insert a duplicate.
Similarly, creating a new object and calling save
twice:
device = DeviceAdmin(...) # create a new object (it has null `dev_id`)
device.save() # insert the new object into DB; get 'dev_id'
# for the newly inserted record
device.save() # This shouldn't do anything!
This second save
attempts to insert a duplicate - as I can see if I step through the django code with debugger.