1

Explanation:

I have a model that has two foreign keys:

class Book(models.Model):
    name = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=CASCADE)
    created_by = models.ForeignKey(User, blank=True, null=True, on_delete=CASCADE)

I have a modelform for it:

class BookForm(modelForm):
    class Meta:
        model = Book
        exclude = ('author', 'created_by',)

In my business logic, I want to save a new author and a book with it in the same view, and the created_by could be null in some cases. So, before saving an instance of a BookForm, I will create an author first. So, suppose that I have created an author, then I will do the below to save a book:

f = BookForm(post_data)
if form.is_valid():
    instance = f.save(commit=False)
    instance.author = author
    instance.created_by = user
    instance.save()

What I did so far is working fine, but the foreign key fields are not part of the validation. As you can see from the model class, author field is mandatory, but created_by field is optional. So, if I want to add some validations for them, I have to add them explicitly by customizing the is_valid() method, or by adding some if conditions before instance.author = author.

Question:

The bigger picture is that I have a model with much more foreign keys; some of them are mandatory, and the others are not. So, using the same approach I did above, I have to add a custom validation for each FK explicitly, which I consider it duplication and unclean as those FK fields validation is defined in my model class.

So, what is the best practice to validate automatically those multiple foreign keys while saving by checking the models validation directly and without mentioning each FK one by one?

1 Answer 1

1

Since the foreign key fields are not included in your form, there won't be any form validation on them. What you need is a model validation.

Django provides the model.full_clean() method, which performs all model validation steps.

You will need to call the method manually, since save() method does not invoke any model clean method.

See documentation here.

For example,

f = BookForm(post_data)
if form.is_valid():
    instance = f.save(commit=False)
    instance.author = author
    instance.created_by = user
    try:
        instance.full_clean()
    except ValidationError as e:
        # Do something based on the errors contained in e.message_dict.
        # Display them to a user, or handle them programmatically.
        pass
    instance.save()

If you want to display the error messages to the user, simply pass e to the context and loop through the messages in your template, displaying them wherever you want. This seems the cleaner approach than adding errors to the form, because they are model validation errors not form errors.

In your view:

    except ValidationError as e:
        context = {'e':e} # add other relevant data to context
        return render(request, 'your_template', context)

In your template:

{% for key, values in e.message_dict %}
{{key}}: {{value}}
{% endfor %}

If you want the errors added to the form, then use form.add_error:

except ValidationError as e:
    for key, value in e.items():
        # this adds none-field errors to the form for each model error
        form.add_error(None, f'{key}: {value}')
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, it is kind of helpful, but it still doesn't serve my request completely. Using full_clean is enough to validate the instance for me, but how can I add this to my form errors to be parsed with form errors while reloading the form?
@YasserMohsen, Not sure I fully understand. Do you want to display the validation error to the user on the page where the book form is rendered?
Yes, I want to add this validation error to the form errors. I know this can be done through form._errors, but is it better to be done through my custom form.save function, or should I handle this validation error totally out of the form errors?
@YasserMohsen - OK, updated the answer. It seems cleaner to handle the errors outside form errors, but you can add them to form errors, too, if needed.
I am thinking of following another strategy; adding those parameters to my post_data through updated_data=post_data.copy() then updated_data.update({'author': author, 'created_by': user}), then I can pass the updated_data to the form to get out of the validation headache and leave it to the form. What do you think? And I am wondering why people don't do that and they are always adding the foreign key value after saving the model? Am I am missing something here or are there some disadvantages about this?

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.