1

I have a Django 3.0 Model and associated Admin Add Form. I would like to validate entered data against other data being entered at the same time, the way you can for normal Forms -- making sure two password fields match, for example, or making sure the email and backup email fields are different.

It seems like the best way to do this is to extend the clean() function for the Add page's Form, because that's the function you'd use for a normal Form. The documentation seems to give hints that this might be possible, but it doesn't give enough information to put it all together. Web searches for "Django extend Admin Form" return results only for extending the template.

How do I extend a Django Admin Add Form's clean() function?

Edit: For concreteness, here's an example of the Model and ModelAdmin the answer should apply to:

# models.py

class Questions(models.Model):
    question = models.CharField(
        help_text='Text of question'
    )

class Choices(models.Model):

    q = models.ForeignKey(
        Questions,
        on_delete=models.CASCADE
    )
    choice = models.CharField(
        help_text='Text of choice'
    )
# admin.py

class ChoicesInQuestions(admin.TabularInline):
    model = Choices
    extra = 4
    max_num = 4

@admin.register(Questions)
class QuestionAdmin(admin.ModelAdmin):

    inlines = [ChoicesInQuestions]

There are four Choices on the Question Add page. I'd like to simultaneously validate them against each other at the time of submission, just like I can do with any regular form using its clean() function.

Darien Marks
  • 446
  • 4
  • 18

1 Answers1

4

After some clarifications from author, here is a solution for the question: How to write custom validations on BaseInlineFormSetform. (previous question was How do I extend a Django admin form's clean() function?)

Solution

class InlineFormset(forms.models.BaseInlineFormSet):

    def clean(self):
        choice1 = self.cleaned_data[0].get('choice')
        choice2 = self.cleaned_data[1].get('choice')
        choice3 = self.cleaned_data[2].get('choice')
        choice4 = self.cleaned_data[3].get('choice')

        if "please_give_me_an_error" in choice1:
            raise ValidationError('Error!!!')


class ChoicesInQuestions(admin.TabularInline):
    model = Choices
    extra = 4
    max_num = 4
    formset = InlineFormset

@admin.register(Questions)
class QuestionAdmin(admin.ModelAdmin):
    inlines = [ChoicesInQuestions]

demo

enter image description here

------------------------------Initial response------------------------------

Keep in mind that there can exists two types of validations. You can validate on backend ( or maybe you MUST) and you can enforce UserInterface/UserExperience validating on frontend. There may be one or both

validating on django backend

Maybe you have not reached the specific documentation place (I put in example default data from documentation)

You must:

  1. Create a specific modelForm ( for email and email_backup)
class PersonForm(forms.ModelForm):

    def clean(self):
        cleaned_data = super().clean()
        email = cleaned_data.get("email")
        email_backup = cleaned_data.get("email_backup")

        if email and email_backup:
            # Only do something if both fields are valid so far.
            if email != email_backup:
                raise ValidationError(
                    "Email and email backup aren't equal "
                    "Verify."
                )

    class Meta:
        model = Person
        exclude = ['name']
  1. Overwrite specific form to ModelAdmin
from myapp.forms import PersonForm

class PersonAdmin(admin.ModelAdmin):
    form = PersonForm
  1. Go !

validating on frontend with javascript

This enforce the user experience and will reduce requests to the server... interesting...but it requires a lot of integration time.

Append js into modeladmin, and write some js!

class MyModelAdmin(admin.ModelAdmin):
    class Media:
        js = ('js/admin/mymodel.js',)

There exists some js kits for this needs like FormValidation, or Parsley. This last has django integration ( but a bit not mantained)

gilito
  • 354
  • 3
  • 11
  • Thank you for a thorough response! This isn't really what I'm asking, though. In your 'backend' example, I have to write a completely new form. That's a lot of work to duplicate something that already exists, and that I can only do a worse job of than the version that already exists. Is it not possible for me to grab the existing `ModelAdmin` form and use it, but with a change made to its `clean()` function? – Darien Marks Mar 13 '21 at 22:46
  • If you want, edit your question attaching the model that you have, and I will edit the answer. – gilito Mar 14 '21 at 06:11
  • I've added an example Model and ModelAdmin – Darien Marks Mar 14 '21 at 12:58
  • Thanks Darien! Try if given solutions is fine for your! In this case I would appreciate a successfully check click! – gilito Mar 14 '21 at 18:28
  • Forgive the delay, I had to spend a month rebuilding my databases, because Django, before I could get back to this. Thanks for your work, but please note that I didn't change my question. I want to know how to extend the existing `clean()` function. In this case, it looks like you're suggesting I define a new form with a new `clean()` function, which is not the same. – Darien Marks Apr 19 '21 at 13:02
  • Yes, new clean function from "forms.models.BaseInlineFormSet" class, not from admin.ModelForm ;) – gilito Apr 19 '21 at 13:27