Hyper Customizing Django Inputs and Forms with Widgets

I’ve been using Django recently to develop some applications. During this process, I’ve spent quite a bit of time working with Django forms. Forms are a powerful part of the framework, providing automatic rendering, form validation, and even mapping to database objects. This makes it straightforward to build and set up an app.

That said, I like to have full control over the styling of my components and Django’s forms don’t make this very easy by default. We’re not just talking about adding a class to the input element. The form elements I built required being able to wrap the input element in divs and other tags to gain control that exceeds a single element styling. After searching through documentation and other blogs I found out about Django widgets. This is the best way I’ve found to achieve custom styling and templating within Django forms.

What is a Django Widget?

Django Widgets is how Django represents HTML input elements. When you create a form you’re already using widgets, these are just the defaults that come with Django. Take the following example:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    description = forms.CharField(widget=forms.Textarea)

Here, name has been assigned a field of CharField which has a default widget of TextInput. Description in this case is also a CharField but we want it to be represented by the Textarea widget which renders out a textarea HTML element.

We’re going to take advantage of this widgeting system to apply custom styling to our input fields and allow us greater control over what HTML gets rendered.

Creating a Django Widget

In our example we’re creating an application that does a morgage calculation. We need to be able to capture dollar values and percentage rates in our form. I would like to have the forms be pretty to represent what type of numbers the user is inputing. The way we’re going to do that is format our input widgets to represent a currency or another type of number.

1. Setting Up Our Django Application

We’ll first set up our Model, Form and Template for the form that we want to be rendered. In this example we’ll be working with ModelForms which map straigt to our database models. The syntax is a bit different but you can apply the same concept to regular Django forms.

model.py

class MorgageCalculator(models.Model):
    sale_price = models.DecimalField(decimal_places=2, max_digits=15)
    down_payment = models.DecimalField(decimal_places=2, max_digits=15)
    anual_interest_rate = models.DecimalField(decimal_places=2, max_digits=15)
    life_of_loan_years = models.IntegerField()

forms.py

from django.forms import Form, ModelForm
from .models import MorgageCalculator

class MorgageCalculatorForm(ModelForm):
    class Meta:
        model = MorgageCalculator
        fields = ["sale_price", "down_payment", "anual_interest_rate", "life_of_loan_years"]

django_template.html

{% block content %}
<div class="form_container">
  <form class="form_styler" action="/myurl" method="post">
    {% csrf_token %} {% for field in form %}
    <div>
      <label class="sweet_label">{{ field.label }}</label>
      {{ field }}
    </div>
    {% endfor %}
    <input class="submit_button" type="submit" value="Next" />
  </form>
</div>
{% endblock %}

2. Define a Template for our Widget

This is where things get spicy. Every time when we call our input to get rendered, there is a template that get’s used by the widget to render out the HTML. We’re going to overide the default and create our own template. Note that we would inport a stylesheet in our regular component template that contains style definitions for each class defined within this template.

Note that there are some defaults that I borrowed from the NumberInput class. You’ll need to dig into the Django source code and see the template that this widget is using.

moneyInput.html

<div class="input_wrapper">
  <div class="dollarContainer"><span class="dollarSpan">$</span></div>
  <input
    aria-describedby="{{ widget.name }}-currency"
    class="inputter"
    name="{{ widget.name }}"
    placeholder="0.00"
    type="number"
    {% if widget.value != None %}
      value="{{ widget.value }}"
    {% endif %}
    {% include "django/forms/widgets/attrs.html" %}
  />
  <div class="currency_denote_container">
    <span class="currency_denoter" id="{{ widget.name }}-currency">USD</span>
  </div>
</div>
<style>
</style>

3. Define our Widget

Now that we have our template, lets define our widget and use it. In this case, we just have it inherit from another widget: NumberInput. Add this to our forms.py file.

from django.forms import widgets
class MoneyWidget(widgets.NumberInput):
    template_name = 'formComponents/moneyInput.html'

4. Hooking it All Together

Now we need to hook up our widget to our form. It’s pretty simple, we assign each field a widget in widgets attribute of our form class. In this case we have 2 values in our forms that represent dollar values. Those will use our new MoneyWidget/

forms.py

from django.forms import Form, ModelForm, widgets
from .models import MorgageCalculator

class MoneyWidget(widgets.NumberInput):
    template_name = 'formComponents/moneyInput.html'

class MorgageCalculatorForm(ModelForm):
    class Meta:
        model = MorgageCalculator
        fields = ["sale_price", "down_payment", "anual_interest_rate", "life_of_loan_years"]
        widgets = {
            'sale_price': MoneyWidget(attrs={'placeholder': '0.00'}),
            'down_payment': MoneyWidget(attrs={'placeholder': '0.00'}),
        }

Trying it Out

Lets take a look and see what happened. I went ahead and rendered my form template. We can verify that our method works by inspecting the DOM tree and seeing that our widget template is being used. Here’s a snippet of my DOM:

<form
  class="form_styler"
  action="/myurl"
  method="post"
>
  <div>
    <label class="sweet_label" for="id_sale_price">Sale price</label>
    <div class="input_wrapper">
      <div class="dollarContainer"><span class="dollarSpan">$</span></div>
      <input
        aria-describedby="sale_price-currency"
        class="inputter"
        name="sale_price"
        placeholder="0.00"
        type="number"
        step="0.01"
        required=""
        id="id_sale_price"
      />
      <div class="currency_denote_container">
        <span class="WKVAEIJLPDQASNL" id="sale_price-currency">USD</span>
      </div>
    </div>
  </div>
    <!-- ...rest of form -->
</form>

What’s next?

Using widgets we can create UI’s that are much harder to do without losing the value of Django’s form management. There are Some cavets to be aware of. Be careful as now templating is somewhat fragmented and complexity will increase. Also if Django downstream makes a change or breaks a widget by adding or removing attributes, you’ll need to update your widgets as well.

If you want that crisp styling that is only achieved through wrapping with div tags, this is the way to go. In later post’s we’ll dive into more Django concepts especially around production deployment. See how to create and Django image to start your production journey.

More sweet content?

Stay up to date!

I don't sell your data. Read my privacy policy .

Related Articles