1

I use a regular generic.edit.CreateView of Django to create an object according to user input. The MyModel has an UniqueConstrait so the creation would fail if the new object happens to match an existing one.

However, instead of telling users that creation fails due to duplicate records, I would like to simply return the existing object since this is what users want. I tried to override

    def save(self, commit=True):
        return MyModel.get_or_create(value=self.cleaned_data['value'])[0]

but this does not work because form._post_clean will call form.instance.full_clean with its default validate_constraints=True, so an error will return during form validation.

Essentially I am looking for a GetOrCreateView but I am not sure what is a clean way to achieve this (other than maybe overriding form._full_clean). Note that I am not looking for a UpdateView because we do not know in advance which existing object will be returned.

Edit: After struggling with the validation issue for a very long time, I finally abandoned CreateView and resorted to a simpler FormView. The FormView will simply get all the user input I need, before calling get_or_create(form.cleaned_data[...]) in form_valid(self, form).

2
  • What's wrong with .exist()? I think you can use it by doing: existing_object = MyModel.objects.get(id=pk).exist() then you can pass an error message. Commented May 24, 2023 at 22:09
  • I do not have a pk to get the existing_object. The scenario is that the user is creating an object by entering a name in a form backed by a CreateView, the name happens to clash with an existing object so the CreateView is supposed to return an existing object matching the user's input, instead of creating a new one. Commented May 26, 2023 at 15:51

1 Answer 1

0

To achieve the behavior you described, where instead of raising an error for duplicate records, you want to return the existing object, Here's an example of how you can implement it:

class CreateObjectOfMyModel(CreateView):
    model = MyModel
    fields = ['value']

    def get_form(self, form_class=None): 
        form = super().get_form(form_class) 
        value = form.data.get('value')
        form.fields['value'].queryset = MyModel.objects.filter(value=value)
        return form

The get_form method is overridden in this class base view, It calls the get_form method using super().get_form(form_class) to get the base form object. Then the value field of the form to filter out duplicate records, I have also added a line to retrieve the entered value from the form data using form.data.get('value'). I hope it works.

Sign up to request clarification or add additional context in comments.

6 Comments

I would argue that form_valid will never be called if there exists duplicate record, since a unique validation error will be raised in form validation and thus form_invalid gets called instead. Instead, one should override post method.
You're correct. The form_invalid method will be called instead of form_valid. Check my answer again.
This will not work either because is_valid() will call self.errors, self.full_clean(), self._post_clean(), self.instance.full_clean() and return False due to the constraint on the model. I think the root problem is that django (4.2) allows bypassing validate_unique, but does not allow bypassing validate_constraints of models.base.Model.full_clean(), so form validation will always fail in this particular example (unless _post_clean is overriden).
You're correct in your analysis. The code I provided will not work as intended due to the way Django's form validation Check my answer again, I hope it would work.
Thanks. Your solution might work for simple cases but in my real application user inputs from multiple fields need to go through a series of clean_ calls before they are used to create an object, so it is awkward to override get_form this way.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.