Save multiple objects with a unique attribute using Django formset

点点圈 提交于 2019-12-07 16:30:57

问题


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 Settings (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 Settings 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 Settings:

  • 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:

  1. set self._validate_unique = False in the ModelForm (it's set to True during clean() method)
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!