3

Is there a way to update a unique field in update view? I have a model that has a name and age field but when I try to update the age without even changing the value of the name, it returns an error that the name already exists in the database

models.py

class MyModel(models.Model)
  name = models.CharField(max_length=200, unique=True)
  age = models.IntegerField()

views.py

class MyModelUpdateView(UpdateView):
    def get(self):
        self.object = self.get_object()
        my_model = self.object

        form = MyModelForm(instance=my_model)

        return self.render_to_response(
            self.get_context_data(pk=my_model.pk, form=form)
        )

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()

        my_model = self.object

        form = MyModelForm(data=request.POST, instance=my_model)

        if form.is_valid():
            form.save()

            return some_url

        return self.render_to_response(
            self.get_context_data(pk=my_model.pk, form=form)
        )

forms.py

class MyModelForm(forms.ModelForm):

    class Meta:
        model = MyModel
        fields = (
            'name',
            'age',
        )

    def clean(self):
        cleaned_data = super().clean()

        if MyModel.objects.filter(
            active=True, name=cleaned_data.get('name')
        ).exists():
            raise forms.ValidationError('MyModel already exists.')

        return cleaned_data

What am I missing here? Thank you.

dahyunism
  • 55
  • 5

1 Answers1

3

Since you update a model, and you do not change the name, of course a record with that name already exists: that specific record. You thus should alter the checking code, to:

class MyModelForm(forms.ModelForm):

    def clean(self, *args, **kwargs):
        cleaned_data = super().clean(*args, **kwargs)
        if MyModel.objects.exclude(pk=self.instance.pk).filter(
            active=True, name=cleaned_data.get('name')
        ).exists():
            raise forms.ValidationError('MyModel already exists.')
        return cleaned_data

    class Meta:
        model = MyModel
        fields = ('name', 'age')

Please do not alter the boilerplate logic of the UpdateView, you can easily implement this with:

class MyModelUpdateView(UpdateView):
    form_class = MyModelForm
    success_url = 'some url'

That being said, since if you already have set the field to unique=True, then there is no need to implement the check yourself. It seems here, that you already have a unique=True constraint:

class MyModel(models.Model)
  name = models.CharField(max_length=200, unique=True)
  age = models.IntegerField()

In that case, you can simply let the ModelForm do the work, so then your form looks like:

class MyModelForm(forms.ModelForm):

    class Meta:
        model = MyModel
        fields = ('name', 'age')

It is only if you want a more sophisticated uniqness (like with active=True?), and you can not represent it (easily) you should do your own validation.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • ModelForms already handle uniqueness validation, there is no need to re-implement it – Iain Shelvington Aug 25 '19 at 18:27
  • @IainShelvington: the problem here is that the `active=True` constraint should hold. That will not help if `unique=True` is set. It is thus a "conditional" uniqness. – Willem Van Onsem Aug 25 '19 at 18:27
  • Ah yes but in that case a row that has `active=False` and the same name would still fail uniqueness validation since the constraint is on the column. It shouldn't be possible to have 2 rows with the same name but 1 active and the other not – Iain Shelvington Aug 25 '19 at 18:30
  • 1
    @IainShelvington: well the strange thing is that `active` is apparently not defined on the model. So the two somehow contradict each other. But it is correct that only in case you can not check uniqness with a `unique=True` (or `UniqueConstraint`, etc.) that you should implement the check. Updated the answer. – Willem Van Onsem Aug 25 '19 at 18:31