Dori's Python Life in Words Journal

Create a comment ModelForm


Django ModelForms is a helper class that allows you to create a Form class from an existing Django model.

A list of ModelForm.Meta class attributes:

    • model: Specifies the model that the form is tied to (mandatory attribute for ModelForm).
    • fields or exclude: Specify which fields to include or exclude from the form.
    • formfield_callback: A callable that customizes the form fields.
    • widgets: A dictionary to specify custom widgets for form fields.
    • localized_fields: A list of field names that should be localized.
    • labels: A dictionary of field labels.
    • help_texts: A dictionary of help texts for fields.
    • error_messages: A dictionary to specify custom error messages for fields.
    • field_classes: A dictionary to specify custom field classes for form fields.

A) The ModelForm:

# blog/forms.py

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ["user", "body"]

We have defined the model for which we want to create a form. The Meta class attribute model is mandatory. And we define the two attribute names of the Comment model: user and body. These two attributes will be used to create the form.

The ForeignKey user is represented as a django.form.MultipleChoiceField. You can select the user from a list of users in the form.

B) The ModelForm view:

Let's create the view. This view context contains the post that pk searched for, the form to create a comment, and the comment itself when submitted.

# blog/views.py

@require_POST
def post_comment(request, post_id):
    post = get_object_or_404(Post, pk=post_id, status=Post.Status.PUBLISHED)
    comment = None
    form = CommentForm(data=request.POST)
    if form.is_valid():
        comment = form.save(commit=False)
        comment.post = post
        comment.save()
    return render(
        request, "post/comment.html", {"post": post, "form": form, "comment": comment}
    )

Legend:

    • @require_POST: This decorator makes sure that the view will only accept requests from the POST method.
    • comment = form.save(commit=False): The save() method creates an instance of the model that the form is linked to and saves it to the database. If you call it using commit=False, the model instance is created but not saved to the database. This allows us to modify the form object before finally saving it.

C) Create the redirect template comment.html:

<!-- blog/templates/post/comment.html -->

{% extends "base.html" %}

{% block title %}Add a comment{% endblock %}

{% block content %}
  {% if comment %}
    <h2>Your comment has been added.</h2>
    <p><a href="{{ post.get_absolute_url }}">Back to the post</a></p>
  {% else %}
    {% include "post/includes/comment_form.html" %}
  {% endif %}
{% endblock %}

Legend:

    • {% include "post/includes/comment_form.html" %}: This template tag loads a template and renders it using the current context of the template it is in.

D) Integrate the ModelForm and Comments in detail template:

<!-- blog/template/post/detail.html -->

{% extends "base.html" %}

{% block title %} {{ post.title }} {% endblock %}

{% block content %}
  <h1>{{ post.title }}</h1>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|linebreaks }}
  <p>
    <a href="{% url 'blog:post_share' post.id %}">Share this post.</a>
  </p>
  {% with comments.count as total_comments %}
    <h2>
      {{ total_comments }} comment{{ total_comments|pluralize }}
    </h2>
  {% endwith %}
  {% for comment in comments %}
    <div class="comment">
      <p class="info">
        Comment {{ forloop.counter }} by <strong>{{ comment.user.username }}</strong> {{ comment.created }}
      </p>
      {{ comment.body|linebreaks }}
    </div>
  {% empty %}
    <p>There are no comments.</p>
  {% endfor %}
  {% include "post/includes/comment_form.html" %}
{% endblock %}

Legend:

    • {% with %}: The {% with %} template tag in Django is used to temporarily assign a value to a variable within a specific block of a template. You can reuse this value multiple times within the {% with %} block. The {% with %} template tag is useful when you want to avoid repetition, when you want to improve the readability of your template, and when you are working with complex values.
    • {% empty %}: The {% empty %} template tag is an optional tag that can be used if the given array is empty or could not be found.
    • |linebreaks: The template filter |linebreaks converts the entire text to a paragraph. If there is a single newline (\n) in this plain text string, it converts the newline to an HTML linebreak (<br>) tag. If you have two newline characters (\n\n) in a plain text string, this template filter will create two paragraphs.
    • {% include "post/includes/comment_form.html" %}: This template tag loads a template and renders it using the current context of the template it is in.

E) Create the ModelForm template:

We have included the template for rendering the ModelForm in the detail HTML template, and now we need to create the template for displaying the ModelForm.

There are several ways to display the ModelForm:

E.1) Simple ModelForm rendering:

<!-- blog/templates/post/includes/comment_form.html -->

<h2>Add a comment</h2>
<form action="{% url 'blog:post_comment' post.id %}" method="post">
  {% csrf_token %}
  {{ form.as_p }}
  <p><input type="submit" value="Add comment"></p>
</form>

Legend:

    • {{ form.as_p }}: With the built-in method as_p you display the ModelForm all in paragraphs.

E.2) Looping over the form fields:

<!-- blog/templates/post/includes/comment_form.html -->

<h2>Add a comment</h2>
<form action="{% url 'blog:post_comment' post.id %}" method="post">
  {% csrf_token %}
  {% for field in form %}
    <div>
      {{ field.errors }}
      {{ field.lable_tag }} {{ field }}
      <div>{{ field.help_text|safe }}</div>
    </div>
  {% endfor %}
</form>

Legend:

    • {{ field.errors }}: Displays all containing validation errors corresponding to this field.
    • {{ field.lable_tag }}: Displays the lable of the field.
    • {{ field }}: Displays the Field instance from the form class.
    • {{ field.help_text }}: Displays the help texts corresponding to the field.
    • {{ |safe }}: Marks a string as not requiring any HTML escaping before output. If autoescaping is switched off, this filter has no effect.

E.3) Reusable field group templates:

<!-- blog/templates/post/includes/comment_form.html -->

<h2>Add a comment</h2>
<form action="{% url 'blog:post_comment' post.id %}" method="post">
  {% csrf_token %}
  <div>{{ form.user.as_field_group }}</div>
  <div>{{ form.body.as_field_group }}</div>
  <p><input type="submit" value="Add comment"></p>
</form>

Legend:

    • {{ form.user.as_field_group }}: The as_field_group method renders the related elements of the field as a group. This group contains its label, widget, error and help text.


Designed by BootstrapMade and modified by DoriDoro