Making a custom template for CreateView and UpdateView with placeholders and error validation

I'm making a generic blog while learning Django.

I have an ArticleCreateView and ArticleUpdateView, and I am trying to make a custom template that both views would share. From what I understand, CreateView and UpdateView use the same template by default (article_form.html), which is the template I'm trying to modify.

I have the following in my models.py:

class Article(models.Model):
    title = models.CharField(max_length=100)
    body = models.TextField()
    # def __str__ ...
    # def get_absolute_url ...

In my views.py:

class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body']      
    template_name = 'article_form.html'


class ArticleCreateView(CreateView):
    model = Article
    fields = ['title', 'body']
    template_name = 'article_form.html'

Having the following in my template article_form.html works:

<form method='post'>
  {{ form.as_p }}
  <button>
    Publish
  </button>
</form>

I want to make it more fancy though, with loads of CSS, and a simplified version is:

<form method='post'>
  {% csrf_token %}
  <fieldset class="fieldset-class">
    {{ form.title.errors }}
    <input
      class="form-class"
      type="text"
      placeholder="Article Title"
      name="{{ form.title.name }}"
      value="{{ form.title.value }}"
    />
  </fieldset>
  <fieldset class="form-group">
    {{ form.body.errors }}
    <textarea
      class="form-control"
      rows="8"
      placeholder="Article Body"
      name="{{ form.body.name }}"
    >{{ form.body.value }}</textarea>
  </fieldset>
</form>

What I want is a form that:

  • has placeholders in empty fields (instead of labels)
  • has model-based error validation (max_length is respected and both fields are required without me having to specify it)
  • doesn't erase the entered values upon submitting

The above html does the following:

  • submitting with empty fields will raise the errors, and any entered values will be persisted (good)
  • empty fields have None instead of placeholders (issue)

Substituting object.title and object.body to form.title.value and form.body.value in the above html does the following:

  • submitting with empty fields will raise the errors, but also erase the entered values (issue)
  • empty fields have the right placeholders (good)

I have read the documentation's Built-in template tags and filters, Rendering fields manually, Rendering form error messages, Looping over the form’s fields, and Attributes of BoundField, looked for similar code on SO and GitHub, read the source rendered by {{ form.as_p }}, but I find no simple solution despite having a supposedly common usecase.

The only thing I can think of is to shove {% if %} statements everywhere to choose between placeholder and value, or to get some third-party lib like crispy-forms, but it feels like a more pythonic solution should exist.

As always, I found the answer immediately after posting the question on SO.

The built-in template filter default_if_none solves the issue:

    <input
      class="form-class"
      type="text"
      placeholder="Article Title"
      name="{{ form.title.name }}"
      value="{{ form.title.value|default_if_none:'' }}"
    />
Back to Top