Filling the FormMixin form

39 views Asked by At

Is it possible to populate FormMixin form data without passing the form variable to the template? I have a template, but I don't understand how to create a form with Django, so I just specify the name attribute in the template. The form is created, everything is fine, I override the method
get_initial() method, but the form is not filled out Is it possible to do this or do I need to pass the form variable?

class CommentEditView(View, FormMixin):
    form_class = EditCommentForm

    def get_initial(self):
        initial = super().get_initial()
        comment = get_object_or_404(Comment, pk=15)
        
        initial['review_text'] = comment.review_text
        initial['grade'] = comment.grade

        return initial

    def get(self, request, comment_id):
        return render(request, 'catalog/review-edit.html')
class EditCommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['review_text', 'grade']
<form method="POST">
                    {% csrf_token %}
                    <textarea class="product-detail-reviews-textarea" name="review_text" id="id_review_text"></textarea>
                    {% for error in form.review_text.errors %}
                        <p class="product-detail-reviews-error">* {{ error }} </p>
                    {% endfor %}
                    <div class="product-detail-reviews-row">
                        <div class="product-detail-reviews-stars">
                            <p>Stars:</p>
                            <div class="rating-area">
                                <input type="radio" id="id_grade_five" name="grade" value="5">
                                <label for="id_grade_five" title="Star «5»"></label>    
                                <input type="radio" id="id_grade_four" name="grade" value="4">
                                <label for="id_grade_four" title="Star «4»"></label>    
                                <input type="radio" id="id_grade_three" name="grade" value="3">
                                <label for="id_grade_three" title="Star «3»"></label>  
                                <input type="radio" id="id_grade_two" name="grade" value="2">
                                <label for="id_grade_two" title="Star «2»"></label>    
                                <input type="radio" id="id_grade_one" name="grade" value="1">
                                <label for="id_grade_one" title="Star «1»"></label>
                            </div>
                        </div>
                        <button type="submit" class="product-detail-reviews-link">Confirm</a>
                    </div>
                    {% for error in form.grade.errors %}
                        <p class="product-detail-reviews-error">*{{ error }} </p>
                    {% endfor %}
        </form>

Overridden the method get_initial()

3

There are 3 answers

0
willeM_ Van Onsem On BEST ANSWER

You don't pass the form to the context, that is the problem. Don't override .get(…): the FormMixin will add the item to a context yes, but by overriding the .get(…) method and just rendering a template, it will never even built the context.

You can let the TemplateView do the work for you: this will generate the context, and then render the template:

from django.views.generic import TemplateView


class CommentEditView(FormMixin, TemplateView):
    form_class = EditCommentForm
    template_name = 'catalog/review-edit.html'

    def get_initial(self):
        initial = super().get_initial()
        comment = get_object_or_404(Comment, pk=15)

        initial['review_text'] = comment.review_text
        initial['grade'] = comment.grade

        return initial

    # no def get(self, request, comment_id)

As for the template, you then load data from the form into the template, for example with:

<textarea class="product-detail-reviews-textarea" name="review_text" id="id_review_text">
    {{ form.review_text.widget.value }}
</textarea>
0
CoffeeBasedLifeform On

You're rendering the fields manually, so you also need to take care to include the initial values for those fields. For example for review_text

<textarea class="product-detail-reviews-textarea" name="review_text" id="id_review_text">{{ form.review_text.initial }}</textarea>

If you just want to add some CSS classes to your form elements, you're better off declaring them with the widgets as described here or in the form Meta described here. For example:

class EditCommentForm(ModelForm):
    class Meta:
        model = Comment
        fields = ["review_text", "grade"]
        widgets = {"review_text": Textarea(attrs={"class": "product-detail-reviews-textarea"})}

That way, you can let django do most of the rendering:

{{ form.review_text }}
{% for error in form.review_text.errors %}
  <p class="product-detail-reviews-error">* {{ error }} </p>
{% endfor %}

Also, consider including labels for your form controls. They offer more than just a description of the field:

  • The label text is not only visually associated with its corresponding text input; it is programmatically associated with it too. This means that, for example, a screen reader will read out the label when the user is focused on the form input, making it easier for an assistive technology user to understand what data should be entered.
  • When a user clicks or touches/taps a label, the browser passes the focus to its associated input (the resulting event is also raised for the input). That increased hit area for focusing the input provides an advantage to anyone trying to activate it — including those using a touch-screen device.

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label

0
Jarad On

If I had a hypothetical app named "myapp", I would:

  1. Point the root urls.py to myapp's urls.py using include:
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls'))
]
  1. Maybe model looks like this:
class Comment(models.Model):
    review_title = models.CharField(max_length=100)
    review_text = models.TextField(max_length=1000)
    grade = models.IntegerField(validators=[
        MinValueValidator(1), MaxValueValidator(5)
    ])

    def __str__(self):
        return str(self.review_title)

    def get_absolute_url(self):
        return reverse('myapp:comment-detail', kwargs={"pk": self.pk})

  1. I would have several url paths:
from django.urls import path
from myapp.views import (
    CommentListView, CommentCreateView,
    CommentUpdateView, CommentDetailView
)

app_name = "myapp"
urlpatterns = [
    path('', CommentListView.as_view(), name='comment-list'),
    path('create/', CommentCreateView.as_view(), name='comment-create'),
    path('edit/<int:pk>/', CommentUpdateView.as_view(), name='comment-edit'),
    path('comment/<int:pk>/', CommentDetailView.as_view(), name='comment-detail'),
]
  1. views.py would look like this:
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import (
    ListView, DetailView, CreateView, UpdateView
)

from myapp.forms import EditCommentForm
from myapp.models import Comment


class CommentListView(ListView):
    template_name = 'catalog/comments-list.html'
    model = Comment

class CommentCreateView(CreateView):
    template_name = 'catalog/comment-form.html'
    form_class = EditCommentForm

    def form_valid(self, form):
        self.object = form.save()
        return HttpResponseRedirect(self.object.get_absolute_url())

class CommentUpdateView(UpdateView):
    template_name = 'catalog/comment-form.html'
    form_class = EditCommentForm
    model = Comment

class CommentDetailView(DetailView):
    template_name = 'catalog/comment-detail.html'
    model = Comment
  1. Make Three Templates:

catalog/comments-list.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div>
    <div><a href="{% url 'myapp:comment-create' %}">Create</a></div>
    <h2>Comments</h2>
    {% for comment in object_list %}
      <div>
        <a href="{% url 'myapp:comment-detail' comment.pk %}">{{ comment.review_title }}</a>
      </div>
    {% endfor %}
  </div>
</body>
</html>

catalog/comment-form.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div style="margin: 100px auto; width: 400px;">
    <form method="POST">
      {% csrf_token %}
      {{ form.as_div }}
      <input type="submit" class="product-detail-reviews-link" value="Confirm">
    </form>
  </div>
</body>
</html>

catalog/comment-detail.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div>
    <h2>The Review</h2>
    <div>{{ object.review_title }}</div>
    <div>{{ object.review_text }}</div>
    <div>{{ object.grade }}</div>
    <a href="{% url 'myapp:comment-list' %}">back to list</a>
    <a href="{% url 'myapp:comment-edit' object.pk %}">edit comment</a>
  </div>
</body>
</html>

This should illustrate how forms work and how to create new instances and edit them. Hope it helps.