Introducción
Este tutorial comienza donde dejó Creating the Public Interface Views. Continuaremos con la aplicación de encuestas web y nos centraremos en el procesamiento de formularios y en reducir nuestro código.
💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí
Este tutorial comienza donde dejó Creating the Public Interface Views. Continuaremos con la aplicación de encuestas web y nos centraremos en el procesamiento de formularios y en reducir nuestro código.
Actualicemos la plantilla de detalle de encuesta (polls/detail.html
) del último tutorial para que contenga un elemento HTML <form>
:
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
Un repaso rápido:
value
de cada botón de radio es el ID de la opción de pregunta asociada. El name
de cada botón de radio es "choice"
. Eso significa que, cuando alguien selecciona uno de los botones de radio y envía el formulario, enviará los datos POST choice=#
donde ## es el ID de la opción seleccionada. Este es el concepto básico de los formularios HTML.action
del formulario en {% url 'polls:vote' question.id %}
y establecemos method="post"
. Usar method="post"
(en lugar de method="get"
) es muy importante, porque la acción de enviar este formulario modificará los datos en el servidor. Siempre que crees un formulario que modifique los datos en el servidor, utiliza method="post"
. Este consejo no es específico de Django; es una buena práctica de desarrollo web en general.forloop.counter
indica cuántas veces la etiqueta for
ha pasado por su bucle{% csrf_token %}<csrf_token>
.Ahora, creemos una vista de Django que maneje los datos enviados y haga algo con ellos. Recuerda, en **Creating the Public Interface Views**
, creamos una URLconf para la aplicación de encuestas que incluye esta línea:
path("<int:question_id>/vote/", views.vote, name="vote"),
También creamos una implementación dummy de la función vote()
. Vamos a crear una versión real. Agrega lo siguiente a polls/views.py
:
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from.models import Choice, Question
#...
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
try:
selected_choice = question.choice_set.get(pk=request.POST["choice"])
except (KeyError, Choice.DoesNotExist):
## Vuelva a mostrar el formulario de votación de la pregunta.
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
## Siempre devuelve un HttpResponseRedirect después de manejar
## correctamente los datos POST. Esto evita que los datos se envíen
## dos veces si un usuario presiona el botón Atrás.
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
Este código incluye algunas cosas que aún no hemos cubierto en este tutorial:
request.POST <django.http.HttpRequest.POST>
es un objeto similar a un diccionario que te permite acceder a los datos enviados por nombre de clave. En este caso, request.POST['choice']
devuelve el ID de la opción seleccionada, como una cadena. Los valores de request.POST <django.http.HttpRequest.POST>
siempre son cadenas.
Tenga en cuenta que Django también proporciona request.GET <django.http.HttpRequest.GET>
para acceder a los datos GET de la misma manera, pero estamos usando explícitamente request.POST <django.http.HttpRequest.POST>
en nuestro código para asegurarnos de que los datos solo se modifiquen a través de una llamada POST.
request.POST['choice']
generará un KeyError
si choice
no se proporcionó en los datos POST. El código anterior verifica KeyError
y vuelve a mostrar el formulario de pregunta con un mensaje de error si no se da choice
.
Después de incrementar el recuento de opciones, el código devuelve un ~django.http.HttpResponseRedirect
en lugar de un ~django.http.HttpResponse
normal. ~django.http.HttpResponseRedirect
toma un solo argumento: la URL a la que se redirigirá el usuario (vea el siguiente punto para ver cómo construimos la URL en este caso).
Como señala el comentario de Python arriba, siempre deberías devolver un ~django.http.HttpResponseRedirect
después de manejar correctamente los datos POST. Este consejo no es específico de Django; es una buena práctica de desarrollo web en general.
Estamos usando la función ~django.urls.reverse
en el constructor de ~django.http.HttpResponseRedirect
en este ejemplo. Esta función ayuda a evitar tener que codificar una URL en el función de vista. Se le da el nombre de la vista a la que queremos pasar el control y la parte variable del patrón de URL que apunta a esa vista. En este caso, usando la URLconf que configuramos en **Creating the Public Interface Views**
, esta llamada a ~django.urls.reverse
devolverá una cadena como:
"/polls/3/results/"
donde el 3
es el valor de question.id
. Esta URL redirigida luego llamará a la vista 'results'
para mostrar la página final.
Como se mencionó en **Creating the Public Interface Views**
, request
es un objeto ~django.http.HttpRequest
. Para obtener más información sobre los objetos ~django.http.HttpRequest
, consulte la documentación de solicitud y respuesta </ref/request-response>
.
Después de que alguien vota en una pregunta, la vista vote()
redirige a la página de resultados de la pregunta. Escribamos esa vista:
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
Esto es casi exactamente lo mismo que la vista detail()
de Creating the Public Interface Views. La única diferencia es el nombre de la plantilla. Corregiremos esta redundancia más adelante.
Ahora, cree una plantilla polls/results.html
:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
Ahora, vaya a /polls/1/
en su navegador y vote en la pregunta. Debería ver una página de resultados que se actualiza cada vez que vota. Si envía el formulario sin haber elegido una opción, debería ver el mensaje de error.
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080
Nota:
El código de nuestra vista vote()
tiene un pequeño problema. Primero obtiene el objeto selected_choice
de la base de datos, luego calcula el nuevo valor de votes
y luego lo guarda de nuevo en la base de datos. Si dos usuarios de su sitio web intentan votar exactamente al mismo tiempo, esto puede salir mal: El mismo valor, digamos 42, se recuperará para votes
. Luego, para ambos usuarios se calcula y guarda el nuevo valor de 43, pero 44 sería el valor esperado.
Esto se llama una condición de carrera. Si estás interesado, puedes leer avoiding-race-conditions-using-f
para aprender cómo puedes resolver este problema.
Las vistas detail()
(de **Creating the Public Interface Views**
) y results()
son muy cortas y, como se mencionó anteriormente, redundantes. La vista index()
, que muestra una lista de encuestas, es similar.
Estas vistas representan un caso común en el desarrollo web básico: obtener datos de la base de datos de acuerdo con un parámetro pasado en la URL, cargar una plantilla y devolver la plantilla renderizada. Debido a que esto es muy común, Django proporciona un atajo llamado el sistema de "vistas genéricas".
Las vistas genéricas abstraen patrones comunes al punto de que ni siquiera necesitas escribir código de Python para escribir una aplicación.
Convertamos nuestra aplicación de encuestas para usar el sistema de vistas genéricas, para que podamos eliminar un montón de nuestro propio código. Tendremos que dar algunos pasos para hacer la conversión. Vamos a:
Sigue leyendo para obtener detalles.
¿Por qué el reordenamiento de código?
En general, al escribir una aplicación de Django, evaluarás si las vistas genéricas son adecuadas para tu problema y las usarás desde el principio, en lugar de refactorizar tu código en mitad del proceso. Pero este tutorial intencionalmente se ha centrado en escribir las vistas "de manera difícil" hasta ahora, para centrarse en los conceptos básicos.
Debes conocer las matemáticas básicas antes de comenzar a usar una calculadora.
Primero, abre la URLconf polls/urls.py
y cámbiala de la siguiente manera:
from django.urls import path
from. import views
app_name = "polls"
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("<int:pk>/", views.DetailView.as_view(), name="detail"),
path("<int:pk>/results/", views.ResultsView.as_view(), name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
]
Observa que el nombre del patrón coincidente en las cadenas de ruta de los segundos y tercer patrones ha cambiado de <question_id>
a <pk>
.
A continuación, vamos a eliminar nuestras viejas vistas index
, detail
y results
y usar en su lugar las vistas genéricas de Django. Para hacer eso, abre el archivo polls/views.py
y cámbialo de la siguiente manera:
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from.models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Devuelve las últimas cinco preguntas publicadas."""
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
def vote(request, question_id):
... ## es el mismo que arriba, no se necesitan cambios.
Estamos usando dos vistas genéricas aquí: ~django.views.generic.list.ListView
y ~django.views.generic.detail.DetailView
. Respectivamente, esas dos vistas abstraen los conceptos de "mostrar una lista de objetos" y "mostrar una página de detalles para un tipo particular de objeto".
model
.~django.views.generic.detail.DetailView
espera que el valor de la clave primaria capturado de la URL se llame "pk"
, por lo que hemos cambiado question_id
a pk
para las vistas genéricas.Por defecto, la vista genérica ~django.views.generic.detail.DetailView
utiliza una plantilla llamada <nombre de la aplicación>/<nombre del modelo>_detail.html
. En nuestro caso, usaría la plantilla "polls/question_detail.html"
. El atributo template_name
se utiliza para decirle a Django que use un nombre de plantilla específico en lugar del nombre de plantilla predeterminado automático. También especificamos el template_name
para la vista de lista de results
; esto garantiza que la vista de resultados y la vista de detalles tengan una apariencia diferente cuando se renderizan, aunque en realidad ambas son una ~django.views.generic.detail.DetailView
detrás de escena.
Del mismo modo, la vista genérica ~django.views.generic.list.ListView
utiliza una plantilla predeterminada llamada <nombre de la aplicación>/<nombre del modelo>_list.html
; usamos template_name
para decirle a ~django.views.generic.list.ListView
que use nuestra plantilla existente "polls/index.html"
.
En partes anteriores del tutorial, las plantillas se han proporcionado con un contexto que contiene las variables de contexto question
y latest_question_list
. Para DetailView
, la variable question
se proporciona automáticamente, ya que estamos usando un modelo de Django (Question
), Django es capaz de determinar un nombre adecuado para la variable de contexto. Sin embargo, para ListView, la variable de contexto generada automáticamente es question_list
. Para anular esto, proporcionamos el atributo context_object_name
, especificando que queremos usar latest_question_list
en lugar de eso. Como enfoque alternativo, podrías cambiar tus plantillas para que coincidan con las nuevas variables de contexto predeterminadas, pero es mucho más fácil decirle a Django que use la variable que quieres.
Ejecuta el servidor y utiliza tu nueva aplicación de encuestas basada en vistas genéricas.
¡Felicidades! Has completado el laboratorio de Procesamiento de Formularios y Reducción de Nuestro Código. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.