---
title: "Escribiendo su primera aplicación en Django, parte 4"
version: 6.1
locale: es
source: https://docs.djangoproject.com/es/6.1/intro/tutorial04/
canonical: https://djangodocs.dev/es/6.1/intro/tutorial04/
---
# Escribiendo su primera aplicación en Django, parte 4

This tutorial begins where [Tutorial 3](/es/6.1/intro/tutorial03/) left off.
We’re continuing the web-poll application and will focus on form processing and
cutting down our code.

> **Dónde obtener ayuda:**
>
> Si tiene problemas para seguir este tutorial, diríjase a la sección [Obteniendo ayuda](/es/6.1/faq/help/) del FAQ.

## Escriba un formulario sencillo

Vamos a actualizar nuestra plantilla de detalles de encuestas («polls/detail.html») a partir del último tutorial, de modo que la plantilla contenga un elemento HTML `<form>`:

*`polls/templates/polls/detail.html`*

```html+django
<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 resumen rápido:

- La plantilla anterior muestra un botón de opción para cada pregunta de selección. El `value` de cada botón de opción es la ID de la pregunta de selección asociada. El `name` de cada botón de opción es `"choice"`. Esto significa que cuando alguien selecciona uno de los botones de opción y envía el formulario, enviará los datos POST `choice=#` donde # es la ID de la opción seleccionada. Este es el concepto básico de los formularios HTML.
- We set the form’s `action` to `{% url 'polls:vote' question.id %}`, and
  we set `method="post"`. Using `method="post"` (as opposed to
  `method="get"`) is very important, because the act of submitting this
  form will alter data server-side. Whenever you create a form that alters
  data server-side, use `method="post"`. This tip isn’t specific to
  Django; it’s good web development practice in general.
- \`\` forloop.counter\`\` indica cuántas veces la etiqueta [`for`](/es/6.1/ref/templates/builtins/#std-templatetag-for) ha pasado por el bucle
- Since we’re creating a POST form (which can have the effect of modifying
  data), we need to worry about Cross Site Request Forgeries.
  Thankfully, you don’t have to worry too hard, because Django comes with a
  helpful system for protecting against it. In short, all POST forms that are
  targeted at internal URLs should use the [`{% csrf_token %}`](/es/6.1/ref/templates/builtins/#std-templatetag-csrf_token)
  template tag.

A continuación, vamos a crear una vista Django que maneje los datos enviados y haga algo con ello. Recuerde que en el [Tutorial 3](/es/6.1/intro/tutorial03/), creamos un URLconf para la aplicación encuestas que incluye esta línea:

*`polls/urls.py`*

```python
path("<int:question_id>/vote/", views.vote, name="vote"),
```

También creamos una implementación simulada de la función `vote()`. Vamos a crear una versión real. Agregue lo siguiente a `polls/views.py`:

*`polls/views.py`*

```python
from django.db.models import F
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):
        # Redisplay the question voting form.
        return render(
            request,
            "polls/detail.html",
            {
                "question": question,
                "error_message": "You didn't select a choice.",
            },
        )
    else:
        selected_choice.votes = F("votes") + 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
```

Este código incluye algunas cosas que no hemos cubierto todavía en este tutorial:

- [`request.POST`](/es/6.1/ref/request-response/#django.http.HttpRequest.POST) es un objeto tipo diccionario que le permite acceder a los datos presentados mediante el nombre clave. En este caso, `request.POST['choice']` retorna la ID de la opción seleccionada como una cadena. Los valores [`request.POST`](/es/6.1/ref/request-response/#django.http.HttpRequest.POST) son siempre cadenas.

  Tenga en cuenta que Django también proporciona [`request.GET`](/es/6.1/ref/request-response/#django.http.HttpRequest.GET) para acceder a datos GET de la misma manera, pero estamos usando de manera explícita [`request.POST`](/es/6.1/ref/request-response/#django.http.HttpRequest.POST) en nuestro código, para garantizar que los datos sólo se modifiquen a través de una llamada POST.
- `request.POST['choice']` levantará un excepción [`KeyError`](https://docs.python.org/3/library/exceptions.html#KeyError) si no se proporcionó `choice` en los datos POST. El código anterior busca la excepción [`KeyError`](https://docs.python.org/3/library/exceptions.html#KeyError) y muestra de nuevo el formulario de pregunta con un mensaje de error si no se ha proporcionado `choice`.
- `F("votes") + 1` [instructs the database](/es/6.1/ref/models/expressions/#avoiding-race-conditions-using-f) to increase the vote count by 1.
- Después de incrementar el conteo de la elección, el código retorna una [`HttpResponseRedirect`](/es/6.1/ref/request-response/#django.http.HttpResponseRedirect) en lugar de una [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) normal [`HttpResponseRedirect`](/es/6.1/ref/request-response/#django.http.HttpResponseRedirect) toma un único argumento: La URL a la que el usuario será redirigido (vea el siguiente aspecto de cómo construimos la URL en este caso).

  As the Python comment above points out, you should always return an
  [`HttpResponseRedirect`](/es/6.1/ref/request-response/#django.http.HttpResponseRedirect) after successfully dealing with
  POST data. This tip isn’t specific to Django; it’s good web development
  practice in general.
- Estamos utilizando la función [`reverse()`](/es/6.1/ref/urlresolvers/#django.urls.reverse) en el constructor [`HttpResponseRedirect`](/es/6.1/ref/request-response/#django.http.HttpResponseRedirect) en este ejemplo. Esta función ayuda a evitar tener que codificar una URL en la función de vista. Se proporciona el nombre de la vista a la que queremos pasar el control y la parte de la variable del patrón de URL que señala esa vista. En este caso, utilizando la URLconf que configuramos en el [Tutorial 3](/es/6.1/intro/tutorial03/), esta llamada [`reverse()`](/es/6.1/ref/urlresolvers/#django.urls.reverse) retornará una cadena como

  ```
  "/polls/3/results/"
  ```

  donde el `3` es el valor de `question.id`. Esta URL redirigida entonces llamará a la vista `'results'` para mostrar la página final.

Como se mencionó en el [Tutorial 3](/es/6.1/intro/tutorial03/), `request` es un objeto [`HttpRequest`](/es/6.1/ref/request-response/#django.http.HttpRequest) . Para más información sobre los objetos [`HttpRequest`](/es/6.1/ref/request-response/#django.http.HttpRequest), consulte la [documentación de petición y respuesta](/es/6.1/ref/request-response/).

Después de que alguien vota en una pregunta, la vista `vote()` remite a la página de resultados de la pregunta. Vamos a escribir dicha vista:

*`polls/views.py`*

```python
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()` del [Tutorial 3](/es/6.1/intro/tutorial03/). La única diferencia es el nombre de la plantilla. Solucionaremos esta redundancia más tarde.

Ahora, cree una plantilla `polls/results.html`:

*`polls/templates/polls/results.html`*

```html+django
<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>
```

A continuación, 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 usted vota. Si usted envía el formulario sin haber seleccionado una opción, usted debería ver el mensaje de error.

## Utilice vistas genéricas: menos código es mejor

The `detail()` (from [Tutorial 3](/es/6.1/intro/tutorial03/)) and `results()`
views are very short – and, as mentioned above, redundant. The `index()`
view, which displays a list of polls, is similar.

These views represent a common case of basic web development: getting data from
the database according to a parameter passed in the URL, loading a template and
returning the rendered template. Because this is so common, Django provides a
shortcut, called the «generic views» system.

Generic views abstract common patterns to the point where you don’t even need
to write Python code to write an app. For example, the
[`ListView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.list.ListView) and
[`DetailView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView) generic views abstract the
concepts of «display a list of objects» and «display a detail page for a
particular type of object» respectively.

Let’s convert our poll app to use the generic views system, so we can delete a
bunch of our own code. We’ll have to take a few steps to make the conversion.
We will:

1. Convertir el URLconf.
2. Elimina algunas de las vistas viejas e innecesarias.
3. Introduce nuevas vistas basadas en las vistas genéricas de Django.

Siga leyendo para conocer más detalles.

> **¿Por qué el intercambio de código?**
>
> Generalmente, cuando se escribe una aplicación de Django, usted podrá evaluar si las vistas genéricas son una buena opción para su problema y ​​las utilizará desde el principio en lugar de reestructurar su código a medio camino. Sin embargo, este tutorial intencionadamente se ha centrado hasta ahora en escribir las vistas «de la manera difícil» para centrarse en los conceptos esenciales.
>
> Debe saber matemáticas básicas antes de comenzar a utilizar una calculadora.

### Modifique el URLconf

Primero, abra el URLconf `polls/urls.py` y modifíquelo de la siguiente manera:

*`polls/urls.py`*

```python
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"),
]
```

Note that the name of the matched pattern in the path strings of the second and
third patterns has changed from `<question_id>` to `<pk>`. This is
necessary because we’ll use the
[`DetailView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView) generic view to replace our
`detail()` and `results()` views, and it expects the primary key value
captured from the URL to be called `"pk"`.

### Modifique las vistas

A continuación, vamos a eliminar nuestras viejas vistas `index`, `detail` y `results` y en su lugar vamos a usar las vistas genéricas de Django. Para ello, abra el archivo `polls/views.py` y modifíquelo de la siguiente manera:

*`polls/views.py`*

```python
from django.db.models import F
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):
        """Return the last five published questions."""
        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):
    # same as above, no changes needed.
    ...
```

Each generic view needs to know what model it will be acting upon. This is
provided using either the `model` attribute (in this example, `model =
Question` for  `DetailView` and `ResultsView`) or by defining the
[`get_queryset()`](/es/6.1/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin.get_queryset) method (as
shown in `IndexView`).

Por defecto, la vista genérica [`DetailView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.detail.DetailView) utiliza una plantilla llamada `<app name>/<model name>_detail.html`. En nuestro caso, utilizaría la plantilla `"polls/question_detail.html"`. El atributo `template_name` se utiliza para indicarle a Django que utilice un nombre de plantilla específico en vez del nombre de plantilla generado de forma automática. También especificamos el atributo `template_name` para la vista de lista `results`, esto garantiza que la vista de resultados y la vista detalle tengan un aspecto diferente cuando sean creadas, a pesar de que las dos son una vista genérica `DetailView` en segundo plano.

Del mismo modo, la vista genérica [`ListView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.list.ListView) utiliza una plantilla predeterminada llamada `<app name>/<model name>_list.html`; utilizamos el atributo `template_name` para indicarle a [`ListView`](/es/6.1/ref/class-based-views/generic-display/#django.views.generic.list.ListView) que utilice nuestra plantilla `"polls/index.html"` existente.

In previous parts of the tutorial, the templates have been provided
with a context that contains the `question` and `latest_question_list`
context variables. For `DetailView` the `question` variable is provided
automatically – since we’re using a Django model (`Question`), Django
is able to determine an appropriate name for the context variable.
However, for ListView, the automatically generated context variable is
`question_list`. To override this we provide the `context_object_name`
attribute, specifying that we want to use `latest_question_list` instead.
As an alternative approach, you could change your templates to match
the new default context variables – but it’s a lot easier to tell Django to
use the variable you want.

Ejecute el servidor y utilice su nueva aplicación encuesta basada en las vistas genéricas.

Para más detalles sobre las vistas genéricas, consulte la [documentación de las vistas genéricas](/es/6.1/topics/class-based-views/).

Cuando esté familiarizado con los formularios y las vistas genéricas, lea la [parte 5 de este tutorial](/es/6.1/intro/tutorial05/) para aprender sobre cómo probar nuestra aplicación encuestas.
