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

This tutorial begins where [Tutorial 2](/es/6.1/intro/tutorial02/) left off.
We’re continuing the web-poll application and will focus on creating the public
interface – «views.»

> **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.

## Información general

A view is a «type» of web page in your Django application that generally serves
a specific function and has a specific template. For example, in a blog
application, you might have the following views:

- La página de inicio del blog – muestra las últimas pocas entradas.
- Página de entrada de «detail» – página de enlace permanente para una entrada individual.
- Página de archivo basado en el año – muestra todos los meses con entradas en un año determinado.
- Página de archivo basado en el mes – muestra todos los días con las entradas en un mes determinado.
- Página de archivo basado en el día – muestra todas las entradas de un día determinado.
- Comentar – gestiona la publicación de comentarios en una entrada determinada.

En nuestra aplicación encuestas, vamos a tener las siguientes cuatro vistas:

- La página «índice» de preguntas – muestra las últimas preguntas.
- La página «detalle» de preguntas– muestra un texto de pregunta sin resultados, pero con un formulario para votar.
- La página «resultados» de preguntas – muestra los resultados de una pregunta en particular.
- Votar – gestiona la votación para una elección concreta en una pregunta específica.

In Django, web pages and other content are delivered by views. Each view is
represented by a Python function (or method, in the case of class-based views).
Django will choose a view by examining the URL that’s requested (to be precise,
the part of the URL after the domain name).

Now in your time on the web you may have come across such beauties as
`ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B`.
You will be pleased to know that Django allows us much more elegant *URL
patterns* than that.

A URL pattern is the general form of a URL - for example:
`/newsarchive/<year>/<month>/`.

Para llegar desde una URL a una vista Django usa lo que se conoce como “URLconfs”. Una URLconf mapea patrones de URL a vistas.

Este tutorial proporciona instrucción básica en el uso de URLconfs. Usted puede consultar [URL dispatcher](/es/6.1/topics/http/urls/) para más información.

## Escribiendo más vistas

A continuación vamos a agregar más vistas a `polls/views.py`. Estas vistas son un poco diferentes porque toman un argumento:

*`polls/views.py`*

```python
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)
```

Una estas nuevas vistas al módulo `polls.urls` añadiendo las siguientes llamadas [`path()`](/es/6.1/ref/urls/#django.urls.path):

*`polls/urls.py`*

```python
from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path("", views.index, name="index"),
    # ex: /polls/5/
    path("<int:question_id>/", views.detail, name="detail"),
    # ex: /polls/5/results/
    path("<int:question_id>/results/", views.results, name="results"),
    # ex: /polls/5/vote/
    path("<int:question_id>/vote/", views.vote, name="vote"),
]
```

Take a look in your browser, at «/polls/34/». It’ll run the `detail()`
function and display whatever ID you provide in the URL. Try
«/polls/34/results/» and «/polls/34/vote/» too – these will display the
placeholder results and voting pages.

Cuando se solicita una página a tu sitio web – digamos, «/polls/34/», Django cargará el módulo Python `mysite.urls` apuntado por el parámetro [`ROOT_URLCONF`](/es/6.1/ref/settings/#std-setting-ROOT_URLCONF). Recorre la lista de patrones `urlpatterns` en su orden. Al encontrar la coincidencia `'polls/'`, elimina el texto coincidente (`"polls/"`) y envía el texto restante – `"34/"` – a la URLconf “polls.urls” para su posterior procesamiento. Allí coincide con `'<int:question_id>/'`, lo que resulta en una llamada a la vista `detail()` como esta:

```pycon
detail(request=<HttpRequest object>, question_id=34)
```

La cadena `question_id=34` proviene de `<int:question_id>`. El texto del URL entre paréntesis angulares se «captura» y se envía como el argumento clave a la función de la vista. La parte `question_id` de la cadena define el nombre (campo) que se utilizará para identificar el patrón coincidente, y la parte `int` convierte el patrón a enteros y determinará coincidencias con esta parte del URL. Los dos puntos (`:`) separan el convertidor y el nombre del patrón.

## Escriba vistas que realmente hagan algo

Cada vista es responsable de hacer una de dos cosas: retornar un objeto [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) con el contenido de la página solicitada, o levantar una excepción como [`Http404`](/es/6.1/topics/http/views/#django.http.Http404). El resto depende de usted.

Su vista puede leer registros de una base de datos o no, puede usar un sistema de plantillas como el de Django o un sistema de plantillas Python de terceros o no. Puede generar un archivo PDF, salida XML, crear un archivo ZIP en la marcha, todo lo que quiera, utilizando cualquier librería de Python que desee.

Todo lo que Django quiere es ese objeto [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) o una excepción.

Debido a que es conveniente, vamos a utilizar la propia API de base de datos de Django, que estudiamos en el [Tutorial 2](/es/6.1/intro/tutorial02/). A continuación señalaremos una nueva vista `index()` que muestra en el sistema las 5 últimas preguntas de la encuesta, separadas por comas, según su fecha de publicación:

*`polls/views.py`*

```python
from django.http import HttpResponse

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    output = ", ".join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# Leave the rest of the views (detail, results, vote) unchanged
```

There’s a problem here, though: the page’s design is hardcoded in the view. If
you want to change the way the page looks, you’ll have to edit this Python
code. So let’s use Django’s template system to separate the design from Python
by creating a template that the view can use.

Primero, cree un directorio llamado `templates` en su directorio `polls`. Django buscará las plantillas allí.

La configuración de [`TEMPLATES`](/es/6.1/ref/settings/#std-setting-TEMPLATES) de su proyecto describe cómo Django cargará y creará las plantillas. El archivo de configuración predeterminada configura un backend `DjangoTemplates` cuya opción [`APP_DIRS`](/es/6.1/ref/settings/#std-setting-TEMPLATES-APP_DIRS) está configurada como `True`. Convencionalmente, `DjangoTemplates` busca un subdirectorio «templates» en cada una de las [`INSTALLED_APPS`](/es/6.1/ref/settings/#std-setting-INSTALLED_APPS).

Within the `templates` directory you have just created, create another
directory called `polls`, and within that create a file called
`index.html`. In other words, your template should be at
`polls/templates/polls/index.html`. Because of how the `app_directories`
template loader works as described above, you can refer to this template within
Django as `polls/index.html`.

> **Espacio de nombres de plantillas**
>
> Now we *might* be able to get away with putting our templates directly in
> `polls/templates` (rather than creating another `polls` subdirectory),
> but it would actually be a bad idea. Django will choose the first template
> it finds whose name matches, and if you had a template with the same name
> in a *different* application, Django would be unable to distinguish between
> them. We need to be able to point Django at the right one, and the best
> way to ensure this is by *namespacing* them. That is, by putting those
> templates inside *another* directory named for the application itself.

Introduzca el siguiente código en esa plantilla:

*`polls/templates/polls/index.html`*

```html+django
{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}
```

> **Note**
>
> To make the tutorial shorter, all template examples use incomplete HTML. In
> your own projects you should use [complete HTML documents](https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Getting_started#anatomy_of_an_html_document).

Ahora vamos a actualizar nuestra vista `index` en `polls/views.py` para usar la plantilla:

*`polls/views.py`*

```python
from django.http import HttpResponse
from django.template import loader

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    template = loader.get_template("polls/index.html")
    context = {"latest_question_list": latest_question_list}
    return HttpResponse(template.render(context, request))
```

Ese código carga la plantilla llamada `polls/index.html` y le pasa un contexto. El contexto es un diccionario que relaciona los nombres de variables de plantillas con objetos Python.

Cargue la página señalando su navegador en «/polls/», y usted debería ver una lista con viñetas que contiene la pregunta «¿Qué pasa?» del [Tutorial 2](/es/6.1/intro/tutorial02/). El enlace señala la página de detalles de la pregunta.

### Un atajo: [`render()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.render)

Es una práctica muy común cargar una plantilla, llenar un contexto y retornar un objeto [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) con el resultado de la plantilla creada. Django proporciona un atajo. A continuación la vista `index()` completa, reescrita:

*`polls/views.py`*

```python
from django.shortcuts import render

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by("-pub_date")[:5]
    context = {"latest_question_list": latest_question_list}
    return render(request, "polls/index.html", context)
```

Tenga en cuenta que una vez que hemos hecho esto en todas estas vistas, ya no necesitamos importar [`loader`](/es/6.1/topics/templates/#module-django.template.loader) y [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) (usted querrá continuar `HttpResponse` si usted todavía tiene los métodos stub para `detail`, `results` y `vote`).

La función [`render()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.render) toma el objeto de la solicitud como su primer argumento, un nombre de plantilla como su segundo argumento y un diccionario como su tercer argumento opcional. La función devuelve un objeto [`HttpResponse`](/es/6.1/ref/request-response/#django.http.HttpResponse) de la plantilla que se ha proporcionado representada con el contexto dado.

## Levantar un error 404

Ahora vamos a abordar la vista de detalle de la pregunta: la página que muestra el texto de pregunta para una encuesta determinada. Aquí está la vista:

*`polls/views.py`*

```python
from django.http import Http404
from django.shortcuts import render

from .models import Question

# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, "polls/detail.html", {"question": question})
```

El nuevo concepto aquí: La vista levanta la excepción [`Http404`](/es/6.1/topics/http/views/#django.http.Http404) si no existe una pregunta con la ID solicitada.

Hablaremos de lo que podría poner en esa plantilla `polls/detail.html` un poco más tarde, pero si desea empezar a trabajar con el ejemplo anterior, un archivo que contenga simplemente:

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

```html+django
{{ question }}
```

le ayudará a empezar por ahora.

### Un atajo [`get_object_or_404()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.get_object_or_404)

Es una práctica muy común utilizar [`get()`](/es/6.1/ref/models/querysets/#django.db.models.query.QuerySet.get) y levantar la excepción [`Http404`](/es/6.1/topics/http/views/#django.http.Http404) si no existe el objeto. Django proporciona un atajo. Aquí está la vista `detail()`, reescrita:

*`polls/views.py`*

```python
from django.shortcuts import get_object_or_404, render

from .models import Question

# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, "polls/detail.html", {"question": question})
```

La función [`get_object_or_404()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.get_object_or_404) toma un modelo Django como su primer argumento y un número arbitrario de argumentos de palabra clave que pasa a la función [`get()`](/es/6.1/ref/models/querysets/#django.db.models.query.QuerySet.get) del administrador del modelo. Levanta la excepción [`Http404`](/es/6.1/topics/http/views/#django.http.Http404) si no existe el objeto.

> **Filosofía**
>
> ¿Por qué utilizamos una función auxiliar [`get_object_or_404()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.get_object_or_404) en lugar de capturar las excepciones [`ObjectDoesNotExist`](/es/6.1/ref/exceptions/#django.core.exceptions.ObjectDoesNotExist) en un nivel superior, o hacer que la API del modelo levante la excepción [`Http404`](/es/6.1/topics/http/views/#django.http.Http404) en vez de la excepción [`ObjectDoesNotExist`](/es/6.1/ref/exceptions/#django.core.exceptions.ObjectDoesNotExist)?
>
> Porque eso acoplaría la capa de modelos con la capa de vistas. Uno de los objetivos de diseño más destacados de Django es mantener el bajo acoplamiento. Algo de acoplamiento controlado se introduce en el módulo [`django.shortcuts`](/es/6.1/topics/http/shortcuts/#module-django.shortcuts).

También hay una función [`get_list_or_404()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.get_list_or_404) , que funciona igual que [`get_object_or_404()`](/es/6.1/topics/http/shortcuts/#django.shortcuts.get_object_or_404) \- excepto usando ` filter()` en lugar de [`get()`](/es/6.1/ref/models/querysets/#django.db.models.query.QuerySet.get). La misma levanta la excepción [`Http404`](/es/6.1/topics/http/views/#django.http.Http404) si la lista está vacía.

## Utilice el sistema de plantillas

Vuelva a la vista `detail()` para nuestra aplicación encuesta. Teniendo en cuenta la variable de contexto `question`, así es como la plantilla `polls/detail.html` podría verse:

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

```html+django
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
```

El sistema de plantillas utiliza la sintaxis de búsqueda con puntos para acceder a los atributos de variables. En el ejemplo de `{{ question.question_text }}`, Django primero realiza una consulta en el diccionario sobre el objeto `question`. Si esto falla, intenta una búsqueda de atributos, que en este caso funciona. Si la búsqueda de atributos hubiera fallado, hubiera intentado una búsqueda de índice de lista.

La llamada del método ocurre en el bucle [`{% for %}`](/es/6.1/ref/templates/builtins/#std-templatetag-for): `question.choice_set.all` se interpreta como el código Python `question.choice_set.all()`, que devuelve un iterable de objetos `Choice` y es adecuado para usarse en la etiqueta [`{% for %}`](/es/6.1/ref/templates/builtins/#std-templatetag-for).

Consulte la [guía de plantillas](/es/6.1/topics/templates/) para más información sobre las plantillas.

## Quitar URLs codificadas de manera predeterminada en las plantillas

Recuerde que cuando escribimos el enlace para una pregunta en la plantilla `polls/index.html`, el enlace estaba parcialmente codificado de forma predeterminada como este:

```html+django
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
```

The problem with this hardcoded, tightly-coupled approach is that it becomes
challenging to change URLs on projects with a lot of templates. However, since
you defined the `name` argument in the [`path()`](/es/6.1/ref/urls/#django.urls.path) functions in
the `polls.urls` module, you can remove a reliance on specific URL paths
defined in your url configurations by using the `{% url %}` template tag:

```html+django
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
```

La forma como esto funciona es buscando la definición de la URL como se específica en el módulo `polls.urls`. Usted puede ver exactamente donde se define el nombre de la URL de “detail” a continuación:

```
...
# the 'name' value as called by the {% url %} template tag
path("<int:question_id>/", views.detail, name="detail"),
...
```

Si desea modificar la URL de la vista de detalle de las encuestas a algo diferente, quizás a algo como `polls/specifics/12/`, en lugar de hacerlo en la plantilla (o plantillas), usted la modificaría en `polls/urls.py`:

```
...
# added the word 'specifics'
path("specifics/<int:question_id>/", views.detail, name="detail"),
...
```

## Asignando los nombres de URLs

El proyecto tutorial solo tiene una aplicación; `polls`. En proyectos reales de Django, puede haber cinco, diez, veinte o más aplicaciones. ¿Cómo diferencia Django los nombres de las URLs entre ellos? Por ejemplo, la aplicación `polls` tiene una vista `detail`, como la podría tener también una aplicación en el mismo proyecto que es para un blog. ¿Cómo hacer para que Django sepa qué vista de aplicaciones crear para una URL cuando se utiliza la etiqueta de plantilla `{% url %}`?

La solución es añadir espacios de nombres a su URLconf. En el archivo `polls/urls.py`, añada un `app_name` para configurar el espacio de nombres de la aplicación:

*`polls/urls.py`*

```python
from django.urls import path

from . import views

app_name = "polls"
urlpatterns = [
    path("", views.index, name="index"),
    path("<int:question_id>/", views.detail, name="detail"),
    path("<int:question_id>/results/", views.results, name="results"),
    path("<int:question_id>/vote/", views.vote, name="vote"),
]
```

Ahora modifique su plantilla `polls/index.html` desde:

*`polls/templates/polls/index.html`*

```html+django
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
```

para señalar la vista de detalle con espacio de nombres:

*`polls/templates/polls/index.html`*

```html+django
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
```

When you’re comfortable with writing views, read [part 4 of this tutorial](/es/6.1/intro/tutorial04/) to learn the basics about form processing and generic
views.
