Menangani formulir dengan tampilan berdasarkan-kelas

Pengolahan formulir umumnya mempunyai 3 jalur:

  • Inisial GET (kosong atau formulir diisi dimuka)

  • POST dengan data tidak sah (khususnya memperlihatkan kembali formulir dengan kesalahan)

  • POST dengan data sah (pengolahan data dan khususnya pengalihan)

Menerapkan ini anda sendiri sering menghasilkan banyak perulangan kode boilerplate (lihat Using a form in a view). Untuk menghindai ini, Django menyediakan kumpulan dari tampilan berdasarkan-kelas umum untuk pengolahan formulir.

Formulir dasar

Diberikan formulir kontak:

forms.py
from django import forms


class ContactForm(forms.Form):
    name = forms.CharField()
    message = forms.CharField(widget=forms.Textarea)

    def send_email(self):
        # send email using the self.cleaned_data dictionary
        pass

Tampilan dapat dibangun menggunakan FormView:

views.py
from myapp.forms import ContactForm
from django.views.generic.edit import FormView


class ContactFormView(FormView):
    template_name = "contact.html"
    form_class = ContactForm
    success_url = "/thanks/"

    def form_valid(self, form):
        # This method is called when valid form data has been POSTed.
        # It should return an HttpResponse.
        form.send_email()
        return super().form_valid(form)

Catatan:

Formulir model

Generic views really shine when working with models. These generic views will automatically create a ModelForm, so long as they can work out which model class to use:

  • Jika atribut model diberikan, kelas model itu akan digunakan.

  • If get_object() returns an object, the class of that object will be used.

  • Jika sebuah queryset diberikan, model untuk queryset itu akan digunakan.

Model form views provide a form_valid() implementation that saves the model automatically. You can override this if you have any special requirements; see below for examples.

You don't even need to provide a success_url for CreateView or UpdateView - they will use get_absolute_url() on the model object if available.

Jika anda ingin menggunakan penyesuaian ModelForm (misalnya untuk menambahkan validasi ekstra), atur form_class pada tampilan anda.

Catatan

Ketika menentukan sebuah penyesuaian kelas formulir, anda harus masih menentukan model, meskipun form_class mungkin berupa sebuah ModelForm.

First we need to add get_absolute_url() to our Author class:

models.py
from django.db import models
from django.urls import reverse


class Author(models.Model):
    name = models.CharField(max_length=200)

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

Kemudian kami dapat menggunakan CreateView dan teman-teman untuk melakukan pekerjaan sebenarnya. Perhatikan bagaimana kami hanya mengkonfigurasi tampilan berdasarkan-kelas umum disini; kami tidak harus menulis logika apapun kami sendiri:

views.py
from django.urls import reverse_lazy
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from myapp.models import Author


class AuthorCreateView(CreateView):
    model = Author
    fields = ["name"]


class AuthorUpdateView(UpdateView):
    model = Author
    fields = ["name"]


class AuthorDeleteView(DeleteView):
    model = Author
    success_url = reverse_lazy("author-list")

Catatan

Kami harus menggunakan reverse_lazy() daripada reverse(), ketika url tidak dimuat ketika berkas diimpor.

Atribut fields bekerja cara sama seperti atribut fields pada kelas Meta sebelah dalam pada ModelForm. Meskipun anda menentukan kelas formulir di cara lain, atribut dibutuhkan dan tampilan akan munculkan sebuah pengecualian ImproperlyConfigured jika itu tidak.

Jika anda menentukan kedua atribut fields dan form_class, sebuah pengecualian ImproperlyConfigured akan dimunculkan.

Akhirnya, kami mengaitkan tampilan baru ini kedalam URLconf:

urls.py
from django.urls import path
from myapp.views import AuthorCreateView, AuthorDeleteView, AuthorUpdateView

urlpatterns = [
    # ...
    path("author/add/", AuthorCreateView.as_view(), name="author-add"),
    path("author/<int:pk>/", AuthorUpdateView.as_view(), name="author-update"),
    path("author/<int:pk>/delete/", AuthorDeleteView.as_view(), name="author-delete"),
]

Catatan

Tampilan ini mewarisi class:~django.views.generic.detail.SingleObjectTemplateResponseMixin yang menggunakan template_name_suffix untuk membangun template_name berdasarkan pada model.

Di contoh ini:

Jika anda berharap untuk mempunyai cetakan terpisah untuk CreateView dan UpdateView, anda dapat menyetel antara template_name atau template_name_suffix pada kelas tampilan anda.

Models dan request.user

Untuk melacak pengguna yang membuat sebuah obyek menggunakan sebuah CreateView, anda dapat menggunakan sebuah penyesuaian ModelForm untuk melakukan ini. Pertama, tambah hubungan foreign key pada model:

models.py
from django.contrib.auth.models import User
from django.db import models


class Author(models.Model):
    name = models.CharField(max_length=200)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE)

    # ...

In the view, ensure that you don't include created_by in the list of fields to edit, and override form_valid() to add the user:

views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author


class AuthorCreateView(LoginRequiredMixin, CreateView):
    model = Author
    fields = ["name"]

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

LoginRequiredMixin prevents users who aren't logged in from accessing the form. If you omit that, you'll need to handle unauthorized users in form_valid().

Contoh negosiasi isi

Here is an example showing how you might go about implementing a form that works with an API-based workflow as well as 'normal' form POSTs:

from django.http import JsonResponse
from django.views.generic.edit import CreateView
from myapp.models import Author


class JsonableResponseMixin:
    """
    Mixin to add JSON support to a form.
    Must be used with an object-based FormView (e.g. CreateView)
    """

    def form_invalid(self, form):
        response = super().form_invalid(form)
        if self.request.accepts("text/html"):
            return response
        else:
            return JsonResponse(form.errors, status=400)

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        if self.request.accepts("text/html"):
            return response
        else:
            data = {
                "pk": self.object.pk,
            }
            return JsonResponse(data)


class AuthorCreateView(JsonableResponseMixin, CreateView):
    model = Author
    fields = ["name"]

The above example assumes that if the client supports text/html, that they would prefer it. However, this may not always be true. When requesting a .css file, many browsers will send the header Accept: text/css,*/*;q=0.1, indicating that they would prefer CSS, but anything else is fine. This means request.accepts("text/html") will be True.

To determine the correct format, taking into consideration the client's preference, use django.http.HttpRequest.get_preferred_type():

class JsonableResponseMixin:
    """
    Mixin to add JSON support to a form.
    Must be used with an object-based FormView (e.g. CreateView).
    """

    accepted_media_types = ["text/html", "application/json"]

    def dispatch(self, request, *args, **kwargs):
        if request.get_preferred_type(self.accepted_media_types) is None:
            # No format in common.
            return HttpResponse(
                status_code=406, headers={"Accept": ",".join(self.accepted_media_types)}
            )

        return super().dispatch(request, *args, **kwargs)

    def form_invalid(self, form):
        response = super().form_invalid(form)
        accepted_type = self.request.get_preferred_type(self.accepted_media_types)
        if accepted_type == "text/html":
            return response
        elif accepted_type == "application/json":
            return JsonResponse(form.errors, status=400)

    def form_valid(self, form):
        # We make sure to call the parent's form_valid() method because
        # it might do some processing (in the case of CreateView, it will
        # call form.save() for example).
        response = super().form_valid(form)
        accepted_type = self.request.get_preferred_type(self.accepted_media_types)
        if accepted_type == "text/html":
            return response
        elif accepted_type == "application/json":
            data = {
                "pk": self.object.pk,
            }
            return JsonResponse(data)
Changed in Django 5.2:

The HttpRequest.get_preferred_type() method was added.