Opened 14 years ago
Closed 5 years ago
#14071 closed Bug (wontfix)
Row duplicated when modifying PK
Reported by: | mnbayazit | Owned by: | nobody |
---|---|---|---|
Component: | contrib.admin | Version: | 1.2 |
Severity: | Normal | Keywords: | |
Cc: | jedie | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Create a model and add a field with primary_key=True
Go into the django admin panel and change this primary key.
A new entry is added with the new PK, but the old one remains as well.
Sample model:
class FlatPage(Model):
title = CharField(max_length=50)
key = SlugField(max_length=50, primary_key=True, help_text="Do not change.")
content = TextField()
created = DateTimeField(auto_now_add=True)
updated = DateTimeField(auto_now=True)
def unicode(self):
return self.title
class Meta:
app_label = 'app'
Change History (4)
comment:1 by , 14 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
comment:2 by , 5 years ago
Easy pickings: | unset |
---|---|
Resolution: | wontfix |
Severity: | → Normal |
Status: | closed → new |
Type: | → Bug |
UI/UX: | unset |
I just had to deal with the original Problem: Row duplicated when modifying PK
By changing the primary key in admin you can also get another problem: If the model has another unique field, then you get the admin error that the field value is not unique. (This is because django tries to create a duplicated entry).
The problem is: django.forms.models.BaseModelForm._post_clean()
will do this:
self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
And construct_instance()
will just change the primary key and this object will be saved in the end and results in duplication. This is a known behaviour as the docs says at: https://docs.djangoproject.com/en/1.11/ref/models/fields/#django.db.models.Field.primary_key
If you change the value of the primary key on an existing object and then save it, a new object will be created alongside the old one.
One way to change the primary key is:
FooBarModel.objects.filter(id=old_id).update(id=new_id)
My current work-a-round it to use a own model form:
class FooBarAdminForm(forms.ModelForm): def clean_id(self): old_id = self.instance.id new_id = self.cleaned_data['id'] if old_id == new_id: # ID not changed -> nothing to do return old_id if FooBarModel.objects.filter(id=new_id).exists(): raise forms.ValidationError('ID already exists') # Change the primary key without make a new object: FooBarModel.objects.filter(id=old_id).update(id=new_id) # replace new created instance, made in self._post_clean() via construct_instance(): self.instance = FooBarModel.objects.get(id=new_id) return new_id
But that's not a perfect solution:
- It's not universal. (Primary key must be
id
) - Has potential for race-contitions
- Admin continue will redirect to
request.path
that will contain the old ID (But can be fixed via overwritingresponse_change()
)
comment:3 by , 5 years ago
Cc: | added |
---|
comment:4 by , 5 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
Replying to mnbayazit:
The primary key is exactly how the decision is made as to whether update or create a new row.
Altering this behavior would be a fundamentally backwards incompatible change.