'Django tries to INSERT instead of UPDATE on save()
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.
Solution 1:[1]
You certainly have some issues with fields definition (maybe for some reason you should add unique=True). But if you still can't determine why UPDATE fails (INSERT happens if UPDATE returns "no rows updated"), then you can use .save(force_update=True) as a last resort.
Also you have some inconsistency in your code:
- field is named as
dev_idbut you are usinguuidin filtering (.get(uuid=uuid)). Are you sure that you have properly set a name for primary_key field? AutoFieldis assuming the use ofIntegerfield, so duringget_prep_valueit doesint(self.pk)which works without throwing any errors on instances ofuuid.UUID, But if internally db usesvarcharfield, then it will be converted back to str with a wrong value (e.g.str(int(uuid.UUID('579aea21-49a4-4819-90ec-a192014b4a44'))) == '116447198070669671892400511866020514372'). So that's what might be causing the problems. Instead of AutoField try usingUUIDFieldorCharField:
import uuid
class SomeModel(models.Model):
dev_id = models.UUIDField(primary_key=True, blank=True, default=uuid.uuid4)
# Or if your column is a varchar:
class SomeModel(models.Model):
def dev_id_default():
return uuid.uuid4().hex # or str(uuid.uuid4()) if you need values with `-`
dev_id = models.CharField(primary_key=True, blank=True, default=dev_id_default, max_length=33_OR_36)
Looks like you are using pre-existing tables in DB: try using code for models generated by python manage.py inspectdb.
Solution 2:[2]
In my case, I had a unique CharField at the top of my class. It seemed to me that Django would sometimes see it as the primary key, sometimes not -- with the consequence of sometimes not realizing it should be updating instead of inserting.
Adding an explicit id=AutoField(primary_key=True) fixed the problem for me.
Solution 3:[3]
One reason why this might occur is if your primary key field is defined with a default value. I don't see a default in your example, but I also don't see that the datatype is a uuid, so I thought I would provide this answer just in case.
If your model's pkey looks like this: models.UUIDField(primary_key=True, default=uuid.uuid4), calling save will always result in an INSERT rather than an UPDATE. I believe this is true even if you pass force_update=True to the save method. This was at one point submitted as a bug, but
is actually the intended behavior of Django 3 according to the docs.
There are a couple of ways you can handle this issue if you want to use a uuid as your primary key. The best way is probably to follow the recommendation of the docs and use either the update or update_or_create methods to update your model instances.
>>> MyModel.objects.update_or_create(pk=existing_pk, defaults={'name': 'new name'})
>>> MyModel.objects.filter(pk=existing_pk).update(name='new name')
Alternatively, you could also remove the default argument from your primary key definition and ensure that each instance has a uuid assigned on save by overriding your model's save method - for example:
def save(self, *args, **kwargs):
if not self.id:
self.id = uuid.uuid1()
super(DeviceAdmin, self).save(*args, **kwargs)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Jacopo |
| Solution 3 | Cyrus |
