问题
TL;DR: How do you save/validate multiple objects with a unique attribute using a formset?
Let's say I have a Machine
which has multiple Setting
s (see models below). These settings should have a unique ordering within the machine. This can be accomplished by defining a unique_together
attribute in the Meta
class of the Setting
model:
unique_together = (('order', 'machine'), )
However, I'm using an inline formset to update all the Setting
s of a Machine
. Assume I have the following info in my formset:
- Setting 1: machine=1; order=1
- Setting 2: machine=1; order=2
If I were to switch the order on the two Setting
s:
- Setting 1: machine=1; order=2
- Setting 2: machine=1; order=1
and save the form using the following piece of code:
self.object = machine_form.save()
settings = setting_formset.save(commit=False)
for setting in settings:
setting.machine = self.object
setting.save()
Then I get a Unique Constraint Error since I'm trying to save Setting 1 with order=2, but Setting 2 still has that order. I can't seem to find a clean solution for this problem. I would like to keep the unique_together
constraint to ensure the correctness of my data. I know how to solve this in the frontend using Javascript, but I want a check in my models/forms. Looping over the formset rows seems possible, but ugly?
class Machine(models.Model):
name = models.CharField(max_length=255)
class Setting(models.Model):
name = models.CharField(max_length=255)
machine = models.ForeignKey(Machine, on_delete=models.CASCADE)
order = models.PositiveSmallIntegerField()
回答1:
It seems not possible to constraint the order on the database level. I'm not saying it's not possible, but I don't know how.
For now, I added a check in my formset. I'll leave it here in case someone has a similar problem.
class BaseSettingFormSet(BaseInlineFormSet):
'''FormSet to update the order of Settings in a Machine'''
def clean(self):
'''Checks if there are no two Settings with the same order.'''
if any(self.errors):
return
orders = []
for form in self.forms:
if form.cleaned_data:
order = form.cleaned_data['order']
if order in orders:
form.add_error('order', _('The order of each setting must be unique'))
else:
orders.append(order)
回答2:
I was able to achieve it using deferred constraint (works for Postgres DB only)
Check this answer on how to set it up: https://stackoverflow.com/a/56644496
And afterwards you want to:
- set
self._validate_unique = False
in the ModelForm (it's set toTrue
duringclean()
method) - Wrap saving of the formset in
transaction.atomic()
block (this will trigger the deferred constraint)
来源:https://stackoverflow.com/questions/46484564/save-multiple-objects-with-a-unique-attribute-using-django-formset