El procesamiento de formularios generalmente tiene 3 vías:
GET inicial (formulario vacío o prepoblado)
POST con datos inválidos (normalmente muestra el formulario con errores)
POST con datos válidos (procesa los datos y normalmente redirige)
Implementando esto tú mismo a menudo resulta en una gran cantidad de código boilerplate repetido (consultar Usando un formulario en una vista). Para ayudar a evitar esto, Django proporciona una colección de vistas basadas en clases genéricas para el procesamiento de formularios.
Dado un formulario de contacto:
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
La vista se puede construir utilizando una 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)
Notas:
FormView hereda TemplateResponseMixin por lo que template_name se puede utilizar aquí.
La implementación predeterminada para form_valid() simplemente redirige a la success_url.
Vistas genericas realmente brillan cuando se trabajan con modelos. Estas vistas genericas crearán automáticamente un ModelForm, siempre y cuando puedan determinar qué clase de modelo utilizar.
Si se proporciona la atributo model, se utilizará esa clase de modelo.
Si get_object() devuelve un objeto, se utilizará la clase de ese objeto.
Si se proporciona un queryset, se utilizará el modelo para ese conjunto de datos.
Vistas de formulario de modelo proporcionan una implementación de form_valid() que guarda el modelo automáticamente. Puedes sobrescribir esto si tienes alguna necesidad especial; consulta los ejemplos a continuación.
No es necesario proporcionar un success_url para CreateView o UpdateView, ya que utilizarán el método get_absolute_url() del objeto modelo si está disponible.
Si deseas utilizar una clase de formulario personalizada (ModelForm, por ejemplo, para agregar validación adicional), establece el atributo form_class en tu vista.
Nota
A la hora de especificar una clase de formulario personalizada, debes seguir especificando el modelo, incluso aunque la form_class pueda ser un ModelForm.
Primero debemos agregar get_absolute_url() a nuestra clase Author.
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})
Luego podemos utilizar CreateView y amigos para realizar el trabajo real. Nota cómo estamos configurando solo las vistas basadas en clases genéricas aquí; no tenemos que escribir lógica nosotros mismos:
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")
Nota
Tenemos que usar reverse_lazy() en lugar de reverse(), ya que las URL todavía no están cargadas cuando se importa el archivo.
El atributo fields funciona de la misma manera que el atributo fields en la clase interna Meta en ModelForm. A menos que definas la clase de formulario de otra forma, el atributo es obligatorio y la vista lanzará una excepción ImproperlyConfigured si no se especifica.
Si especificas tanto los atributos fields como form_class, se levantará una excepción ImproperlyConfigured.
Finalmente, conectamos estas nuevas vistas en el archivo urls.py:
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"),
]
Nota
Estas vistas heredan de SingleObjectTemplateResponseMixin que utiliza template_name_suffix para construir la template_name basado en el modelo.
En este ejemplo:
CreateView y UpdateView utilizan myapp/author_form.html
DeleteView utiliza myapp/author_confirm_delete.html
Si deseas tener plantillas separadas para CreateView y UpdateView, puedes establecer ya sea template_name o template_name_suffix en tu clase de vista.
request.user¶Para seguir el usuario que creó un objeto utilizando una CreateView, puedes utilizar una ModelForm personalizada para ello. Primero, agrega la relación clave-foránea al modelo:
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)
# ...
En la vista, asegúrate de no incluir created_by en la lista de campos a editar, y sobreescribe form_valid() para agregar el usuario:
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 impide que los usuarios no conectados accedan al formulario. Si omites eso, deberás manejar a los usuarios no autorizados en form_valid().
Aquí tienes un ejemplo mostrando cómo podrías implementar un formulario que funcione con un flujo de trabajo basado en API así como “formularios POST normales”:
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"]
El ejemplo anterior asume que si el cliente admite text/html, ellos preferirían eso. Sin embargo, esto no siempre es cierto. Cuando se solicita un archivo .css, muchos navegadores envían la cabecera Accept: text/css,*/*;q=0.1, indicando que prefieren CSS, pero cualquier otra cosa está bien. Esto significa que request.accepts("text/html") será True.
Para determinar el formato correcto, teniendo en cuenta la preferencia del cliente, utiliza 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)
El método HttpRequest.get_preferred_type() se agregó.
may 31, 2026