Search code examples
djangodjango-modelsdjango-formsdjango-validation

Enforce form field validation & display error without render?


I'm a django newbie so a verbose answer will be greatly appreciated. I'm enforcing a capacity limit on any newly created Bottle objects in my model, like so:

class Bottle(models.Model):
    name = models.CharField(max_length=150, blank=False, default="")
    brand = models.ForeignKey(Brand, on_delete=models.CASCADE, related_name="bottles")
    vintage = models.IntegerField('vintage', choices=YEAR_CHOICES, default=datetime.datetime.now().year)
    capacity = models.IntegerField(default=750,
                                   validators=[MaxValueValidator(2000, message="Must be less than 2000")
                                    ,MinValueValidator(50, message="Must be more than 50")])

My BottleForm looks like so:

class BottleForm(ModelForm):

    class Meta:
        model = Bottle
        fields = '__all__'

My view (with form validation logic based on this answer):

def index(request):
    args = {}

    user = request.user
    object = Bottle.objects.filter(brand__business__owner_id=user.id).all(). \
    values('brand__name', 'name', 'capacity', 'vintage').annotate(Count('brand')).order_by('brand__count')

    args['object'] = object

    if request.method == "POST":
        form = BottleForm(request.POST)

        if form.is_valid():
            bottle = form.save(commit=False)
            bottle.save()
            return redirect('index')

    else:
        form = BottleForm()
    args['form'] = form
    return render(request, template_name="index.pug", context=args)

And my template (in pug format), like so:

            form(class="form-horizontal")(method="post" action=".")
                | {% csrf_token %}
                for field in da_form
                    div(class="form-group")
                        label(class="col-lg-3 col-md-3 col-sm-3 control-label") {{field.label_tag}}
                        div(class="col-lg-9 col-md-9 col-sm-9")
                            | {{ field|add_class:"form-control" }}
                input(class="btn btn-primary")(type="submit" value="submit")

After a few hours of messing with my code and browsing SO, I managed to display the error by adding {{ form.errors }} to my template, but that only shows after the page has already been reloaded and in a very ugly form: see here.

What I'd like is to utilize django's built-in popover error messages without reloading page (see example on default non-empty field), which is so much better from a UX standpoint.


Solution

  • That is not a Django message. That is an HTML5 validation message, which is enforced directly by your browser. Django simply outputs the input field as type number with a max attribute:

    <input type="number" name="capacity" max="750">
    

    I'm not sure if your (horrible) pug templating thing is getting in the way, or whether it's just that Django doesn't pass on these arguments when you use validators. You may need to redefine the field in the form, specifying the max and min values:

    class BottleForm(ModelForm):
        capacity = forms.IntegerField(initial=750, max_value=2000, min_value=250)
    

    (Note, doing {{ field.errors }} alongside each field gives a much better display than just doing {{ form.errors }} at the top, anyway.)