===== Creación de aplicaciones web ===== ===== Django ===== Django ((https://www.djangoproject.com)) es un framework open source para el desarrollo web escrito en lenguaje Python que sigue la arquitectura Model-View-Controller (MVC), que es un patrón de diseño de arquitectura para la construcción de aplicaciones aplicable con independencia de la plataforma para la que se desarrolla. La principal característica de este framework es la rapidez y facilidad con la que se pueden crear las aplicaciones web, incluso si éstas trabajan con Bases de Datos. Está totalmente orientado al desarrollo fácil y rápido de forma que el programador no tenga que repetir las tareas una y otra vez, lo que se conoce como DRY //(Don't Repeat Yourself)//.
{{ mvc.jpg }} Arquitectura MVC (Model-View-Controller)
===== Instalación del framework Django ===== Antes de comenzar con la instalación del propio framework, necesitaremos instalar //Python// en el equipo puesto que //Django// está escrito en este lenguaje. ==== Instalación de Python ==== {{ python.png }} La instalación de Python es muy sencilla. Simplemente accedemos a la sección [[https://www.python.org/downloads/|Descargas]] de su web y descargamos el paquete que corresponda con nuestro Sistema Operativo y la versión que queramos (en nuestro caso la 3.6.x). Una vez instalado podremos disponer del intérprete del lenguaje directamente desde nuestra consola de comandos. Si ejecutamos el comando python (o python3) se cargará y esperará que introduzcamos alguna orden. santi@zenbook:$ python Python 3.5.3 (default, Jan 19 2017, 14:11:04) [GCC 6.3.0 20170118] on linux Type "help", "copyright", "credits" or "license" for more information. >>> | Para salir del intérprete bastará con ejecuta la orden ''quit()'' > Hay una [[apuntes:python|guía de Python]] en la sección //Anexos// a la que habrá que echarle un vistazo antes de seguir, puesto que a partir de ahora tendremos que programar en este lenguaje. ==== Instalación de Django ==== Para la instalación de Django necesitamos la utilidad //pip// (gestor de de instalación de paquetes para Python) pero viene de serie en las versiones de Python >= 3.4 por lo que no será necesario hacer nada. Directamente podremos usarla para que se encargue de instalarnos el nuevo framework con el siguiente comando: santi@zenbook:$ pip install django==1.11.8 Para comprobar que todo está funcionando correctamente podemos ejecutar la consola de Python y comprobar que versión del framework se ha instalado santi@zenbook:$ python Python 3.5.3 (default, Jan 19 2017, 14:11:04) [GCC 6.3.0 20170118] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import django >>> print(django.get_version()) 1.11.4 ===== Introducción ===== Como se ha dicho anteriormente, el desarrollo en Django se realiza siguiendo una arquitectura MVC, aunque más concretamente en este caso se trabaja bajo lo que se conoce como arquitectura MVT (Model-View-Template) que sigue, en el caso de Django, al siguiente esquema:
{{ django_mtv.png }} Arquitectura MVT (Model-View-Template)
En el esquema se pueden distinguir los diferentes componentes que formarán cualquier aplicación web desarrollada con este framework y que son las siguientes: * **Routing (urls)**: Es un fichero que contendrá las * **View**: * **Template**: * **Model**: Representa qué forma tienen la información que gestiona nuestra aplicación. Serán las clases Python que definen la estructura de la Base de Datos donde se almacena dicha información ===== Crear un proyecto y la primera aplicación ===== ==== Crear un proyecto ==== Django no sólo es una conjunto de librerías de código escrito en Python sino que además dispone de una serie de comandos que pueden hacer trabajo por nosotros. En el caso de que tengamos que iniciar un nuevo proyecto desde cero, disponemos de un comando que nos creará todas la estructura y ficheros necesarios (con el código correspondiente) para poder empezar a trabajar sin que tengamos que realizar esa tarea manualmente y de forma repetitiva cada vez que queramos empezar cada proyecto. Asi, con el siguiente comando del framework, lanzándolo directamente desde la consola de comandos, se creará una carpeta 'peliculas' con la estructura inicial de un proyecto de Django santi@zenbook:$ django-admin startproject peliculas
{{ django_project.png }} Estructura inicial de un proyecto Django
En la imagen anterior se puede observar como queda la estructura del proyecto inicial. A partir de ahora podemos decidir trabajar con el editor de código que más nos guste. En este caso la captura corresponde al editor que usaremos nosotros que es //PyCharm//. Entre los ficheros que ya aparecen creados podemos destacar los siguientes: * **__init__.py**: Este fichero le indica a Python que considere a este directorio como un paquete * **settings.py**: Contiene todos los parámetros de configuración del proyecto * **urls.py**: En este fichero se declaran todas las urls. Más adelante veremos como trabajar con él * **wsgi.py**: Aqui configuraremos nuestro proyecto para que pueda desplegarse en servidores compatibles con //WSGI//, como puede ser //Apache// Además, se debe respetar la jerarquía de directorios que se observa en la imagen, donde se puede ver un directorio **peliculas** que contiene a otro con el mismo nombre, siendo posible renombrar sin ningún problema el directorio más externo ya que sólo se usa como contenedor del proyecto. El más interno es el que hace de paquete Python y el que usaremos más adelante durante el desarrollo para referirnos al mismo. Una vez creado entonces el proyecto inicial, podemos comprobar que todo funciona correctamente lanzando el servidor de pruebas que Django incorpora para la fase de desarrollo, utilizando el siguiente comando santi@zenbook:$ python3 manage.py runserver Performing system checks... System check identified no issues (0 silenced). August 31, 2017 - 13:42:42 Django version 1.11.4, using settings 'peliculas.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. Y si todo funciona correctamente veremos un mensaje parecido al que se muestra justo encima. Hay que tener en cuenta que se trata de un servidor de pruebas, para que podamos ir probando el proyecto durante el desarrollo del mismo. El despliegue de una aplicación es a veces un proceso largo y realizar dicho despliegue cada vez que se quiere probar una nueva funcionalidad puede resultar una pérdida de tiempo. De esta manera podemos probar rapidamente nuestra aplicación pero deberemos desplegar la misma de la forma correcta utilizando algún servidor más robusto si la queremos poner en marcha para producción. ==== Añadir aplicaciones a un proyecto ==== santi@zenbook:$ python3 manage.py startapp mispeliculas Por último, antes de comenzar el desarrollo de nuestra aplicación web, echaremos un vistazo al fichero ''settings.py'' donde encontraremos la opción ''INSTALLED_APPS'' con la siguiente información ya configurada: . . . INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] . . . Cada línea indica una aplicación que Django tiene ya preinstalada y que podremos usar más adelante. Siempre podremos eliminar aquellas que sepamos con certeza que no vamos a usar. Lo que sí tendremos que hacer antes de continuar es indicarle a Django que queremos que inicialize dichas aplicaciones y lo haremos con el siguiente comando, que basicamente lo que hace es crear las Bases de Datos que requieran las aplicaciones instaladas en el proyecto. Así, ejecutaremos el siguiente comando antes de continuar, obteniendo la correspondiente salida por pantalla: santi@zenbook:$ python3 manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying sessions.0001_initial... OK Ahora ya estamos listos para empezar a trabajar en la aplicación web cuya estructura se ha generado anteriormente. ===== Definir el modelo y configurar la Base de Datos ===== Antes de definir el modelo mediante clases Python, tenemos que configurar el proyecto para que pueda hacer uso de algún motor de Bases de Datos. Para este ejemplo dejaremos la configuración como está, de forma que se utilizará el motor de SQLite tal y como muesra el parámetro ''ENGINE'' en la opción ''DATABASES'' del fragmento del fichero de configuración ''settings.py'' que se muestra a continuación. Allí también se indica el nombre del fichero que tendrá en este caso la Bases de Datos. Si empleamos algún otro motor como MySQL o PostgreSQL tendremos que indicar los parámetros que corresponda en estos casos (como host, usuario, contraseña, . . .). Además, tenemos que añadir nuestra aplicación ''mispeliculas'' a la lista de aplicaciones disponibles en este proyecto, tal y como aparece en la opción ''INSTALLED_APPS''. . . . INSTALLED_APPS = [ 'mispeliculas.apps.MispeliculasConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'mispeliculas', ] . . . DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } . . . Y siguiendo la filosofía, como ya decíamos anteriormente, DRY (Don't Repear Yourself), Django nos facilita de nuevo el trabajo a la hora de trabajar con Bases de Datos. Así, simplemente tenemos que definir nuestro modelo de datos implementando las clases que consideremos oportunas y Django hará el resto (creará la Bases de Datos, el SQL para crear las tablas, . . .) con un par de simples comandos como veremos más adelante. Lo primero que debemos hacer es definir el Modelo de Datos que necesitamos para nuestra aplicación. En este caso hemos decidido crear una aplicación web para la gestión de nuestras peliculas y series por lo que definimos dos clases ''Pelicula'' y ''Serie'' con los atributos que consideramos. Como se puede observar en el siguiente fragmento de código podemos indicar para cada uno de ellos el tipo y las restricciones necesarias directamente en la definición de la clase. from django.db import models class Pelicula(models.Model): titulo = models.CharField(max_length=200) genero = models.CharField(max_length=200) director = models.CharField(max_length=200, blank=True) fecha = models.DateField descargada = models.BooleanField(default=False, blank=True) def __str__(self): return self.titulo class Serie(models.Model): titulo = models.CharField(max_length=200) genero = models.CharField(max_length=200) fecha = models.DateField temporadas = models.IntegerField(default=1) completada = models.BooleanField(default=False) descargada = models.BooleanField(default=False) def __str__(self): return self.titulo class Capitulo(models.Model): numero = models.IntegerField(default=0) temporada = models.IntegerField(default=0) serie = models.ForeignKey(Serie, on_delete=models.CASCADE) fecha_emision = models.DateField def __str__(self): return self.serie.titulo + " " + self.temporada + "x" + self.numero Una vez escritas las clases ejecutaremos el siguiente comando para indicarle que se han producido cambios en nuestro modelo (en este caso se ha creado por primera vez) y que prepare los cambios necesarios sobre la Base de Datos (todavía no los ejecuta). santi@zenbook:$ python3 manage.py makemigrations mispeliculas Migrations for 'mispeliculas': mispeliculas/migrations/0001_initial.py: - Create model Pelicula - Create model Serie - Create model Capitulo - Add field serie to capitulo Y para lanzar esos cambios sobre la Base de Datos tenemos que lanzar el comando ''migrate'' que veremos más adelante. Antes, conviene saber que siempre que queramos podremos ejecutar un comando si queremos revisar el código ''SQL'' que Django ha preparado. Este comando no tiene efecto ninguno ni realiza cambios en la Base de Datos, simplemente imprime por pantalla el código SQL del cambio que hemos dejado listo con el comando anterior. santi@zenbook:$ python3 manage.py sqlmigrate mispeliculas 0001 BEGIN; -- -- Create model Serie -- CREATE TABLE "mispeliculas_serie" ( "id" serial NOT NULL PRIMARY KEY, "titulo" varchar(200) NOT NULL, "genero" varchar(200) NOT NULL, . . . . . . ); -- -- Create model Pelicula -- CREATE TABLE "mispeliculas_pelicula" ( "id" serial NOT NULL PRIMARY KEY, "titulo" varchar(200) NOT NULL, . . . . . . ); . . . . . . Hay que tener en cuenta también que este paso no es obligatorio llevarlo a cabo. Siempre podremos ejecutar el comando ''makemigrations'' y ''migrate'' seguidos //confiando// en que Django haga correctamente su trabajo. santi@zenbook:$ python3 manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, mispeliculas, sessions Running migrations: Rendering model states... DONE Applying mispeliculas.0001_initial... OK Además, si queremos trabajar con otro SGBD, como por ejemplo MySQL, simplemente tendremos que cambiar la configuración de conexión con la Base de Datos y crear (''makemigrations'') y lanzar (''migrate'') los cambios para que éstos sean efectivos. . . . DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'nombre', 'USER': 'usuario', 'PASSWORD': 'contraseña', 'HOST': 'localhost', 'PORT': '3306', } } . . . ==== Realizar cambios en el modelo durante el desarrollo ==== Y siguiendo con lo que contabamos en el apartado anterior a la hora de crear nuestro modelo por primera vez, cada vez que realicemos algún cambio bastará con ejecutar los comandos ''makemigrations'' y ''migrate'' y los cambios se prepararán y ejecutarán sobre la Base de Datos, respectivamente. santi@zenbook:$ python3 manage.py makemigrations mispeliculas . . . . . . santi@zenbook:$ python3 manage.py migrate . . . . . . ==== Probar nuestro modelo ==== En cualquier momento podemos lanzar, desde la termina, una consola interactiva de Python sobre nuestra aplicación web. santi@zenbook:$ python3 manage.py shell Una vez iniciada la consola, disponemos de los modelos definidos en nuestra aplicación web Django para interactuar con ellos y comprobar rapidamente si todo funciona como esperamos que lo haga. Podemos registrar objetos en la Base de Datos, consultarlos, eliminarnos, modificarlos, . . . >>> from peliculas.models import Pelicula . . . >>> # Registra una pelicula en la Base de Datos >>> pelicula = Pelicula(titulo='Rocky', director='Silvester Stallone') >>> pelicula.save() >>> # Lista todas las peliculas >>> lista_peliculas = Pelicula.objects.all() >>> print(lista_peliculas) . . . >>> # Obtiene una pelicula concreta por titulo >>> pelicula = Pelicula.objects.get(titulo='Rocky') >>> print(pelicula.titulo) . . . >>> # Elimina una pelicula de la Base de Datos >>> pelicula = Pelicula.objects.get(titulo='Rocky') >>> pelicula.delete() . . . >>> # Modifica datos de una pelicula >>> pelicula = Pelicula.objects.get(titulo='Rocky') >>> pelicula.director = 'Otro' >>> pelicula.save() . . . >>> # Obtiene las peliculas ordenadas por un campo >>> peliculas = Pelicula.objects.order_by('titulo') >>> print(peliculas) . . . Django dispone de una API muy completa en relación al [[https://docs.djangoproject.com/en/2.0/ref/models/querysets/|API de consultas]] donde se pueden ver numerosos ejemplos de las diferentes vías de las disponemos para consultar y trabajar con la Base de Datos en nuestras aplicaciones web. ==== El panel de administración de Django ==== Por defecto //Django// proporciona un panel de administración para nuestra aplicación web y éste viene preparado para ser instalado en el fichero ''setttings.py''. Simplemente tenemos que crear el primer usuario administrador con el siguiente comando: santi@zenbook:$ python3 manage.py createsuperuser Lanzar el servidor de pruebas: santi@zenbook:$ python3 manage.py runserver Y podemos acceder al mismo a través de la URL ''http://localhost:8000/admin''. Por defecto tendremos acceso a la gestión de usuarios y grupos de dicho panel. Si queremos además poder gestionar los diferentes objetos que hemos definidos como modelos de nuestra Base de Datos, tendremos que indicarlo como tal en el fichero ''admin.py'' que podemos encontrar en la carpeta de nuestra aplicación. from django.contrib import admin from .models import Pelicula, Serie, Capitulo admin.site.register(Pelicula) admin.site.register(Serie) admin.site.register(Capitulo) Y de esa manera podremos realizar las operaciones ''CRUD'' sobre los tres objetos definidos (en este caso) desde un panel ya creado por el propio framework. ===== Creación de vistas y templates ===== Para la creación de las vistas de nuestra aplicación utilizaremos directamente el sistema de plantillas (//templates//) que incluye el framework. Lo primero que haremos será definir una plantilla que será donde escribiremos el código HTML que defina la estructura de la vista. Además, podremos acceder a la información que pasemos desde el código Python a dicha vista. En el siguiente ejemplo hemos preparado una plantilla donde se listan todas las películas de la Base de Datos. Se espera una variable ''lista_peliculas'' con toda la información, que habremos pasado al cargar la plantilla. En este caso además se trata de la página que hará de portada de nuestra aplicación (''index.html'') por lo que la asociaremos con la vista index que luego más adelante tendremos que registrar en la aplicación para definir a través de que URL podremos acceder. Antes de seguir tendremos que crear la carpeta ''templates'' dentro de la carpeta de nuestra aplicación y a continuación otra carpeta dentro de éste con el nombre de la misma (''mispeliculas''), quedando la ruta ''templates/mispeliculas'' (en este caso). Será allí donde almacenaremos todas las plantillas de la aplicación. Ahora definimos la plantilla en la que aparecerá el listado de peliculas mostrando el título para cada una de ellas y creándose un hipervínculo a través del ''id'' de la misma a otra ''view'' donde podríamos hacer que se mostraran los detalles de cada una:

Mis películas

+ {% if lista_peliculas %} {% else %}

No hay películas disponibles

{% endif %}
Ahora, en el fichero ''views.py'' de nuestra aplicación tendremos que definir la vista ''index'' que cargará la plantilla que acabamos de crear y le pasará los parámetros necesarios, en este caso la lista de películas. from django.shortcuts import render . . . def index(request): lista_peliculas = Pelicula.objects.all() contexto = {'lista_peliculas': lista_peliculas} return render(request, 'mispeliculas/index.html', contexto) Por último, tenemos que registrar la URL que le queramos definir a esta vista. Para eso, creamos el fichero ''urls.py'' en nuestra aplicación (por ser la primera vista que hemos creado, a partir de aquí será añadir sobre este mismo fichero) y definimos la URL para acceder a la portada: from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index') ] Y a continuación añadimos el contenido de este fichero al fichero ''urls.py'' del proyecto. A estas alturas, y puesto que ya deberíamos tener el panel de administración activado, este fichero debería quedar de la siguiente manera: from django.conf.urls import url,include from django.contrib import admin urlpatterns = [ url(r'^peliculas/', include('mispeliculas.urls')), url(r'^admin/', admin.site.urls), ] Así, si accedemos a la URL ''http://localhost:8000/peliculas'' se cargará la vista definida como ''index'' utilizando la plantilla que hemos escrito. Y a continuación, podemos preparar la view y plantilla para ver el detalle de cada una de las películas. La view quedaría de la siguiente manera, definida en el fichero ''views.py'' . . . def get_pelicula(request, pelicula_id): try: pelicula = Pelicula.objects.get(pk=pelicula_id) except Pelicula.DoesNotExist: raise Http404("La pelicula no existe") contexto = {'pelicula': pelicula} return render(request, 'mispeliculas/pelicula.html', contexto) . . . Y la plantilla quedaría asi:

{{ pelicula.titulo }}

{{ pelicula.director }}

{{ pelicula.genero }}

Y por último, añadir la url en el fichero ''urls.py'' de la aplicación ''mispeliculas'': . . . url(r'^peliculas/(?P[0-9]+)/$', views.get_pelicula, name='peliculas'), . . . Y ahora, podríamos modificar la plantilla ''index.html'' para que el título de cada película sea un enlace a esta nueva ''view'' donde podamos ver los detalles de la misma:

Mis películas

+ {% if lista_peliculas %} {% else %}

No hay películas disponibles

{% endif %}
==== Trabajar con contenido estático ==== Se considera contenido estático en una aplicación web Django a todos los recursos que utilicemos para su construcción a excepción de las plantillas HTML y el código Python: Hojas de estilo, ficheros de Javascript, imágenes, . . . De forma similar a como hicimos con las plantillas, crearemos primero una carpeta llamada ''static'' directamente dentro de nuestra aplicación (en este caso se llama ''peliculas'') y a continuación crearemos otra carpeta ''peliculas'' dentro de ''static''. A partir de allí podremos organizar el contenido estático de nuestra aplicación web (css, js, img, . . ., por ejemplo). Para poder hacer referencia a cualquier contenido estático, tendremos que hacer uso de la etiqueta ''static'' que previamente tendremos que cargar al inicio de la plantilla desde donde queramos acceder con la orden ''load''. A continuación se muestran algunos ejemplos de uso de contenido estático desde una plantilla de nuestra aplicación web. {% load static %} . . . . . . . . . ==== Incluir código HTML en una plantilla ===== También es posible formar una plantilla a partir del código HTML incluido en otras, como en el siguiente ejemplo que se adjunta donde se suponen una serie de documentos HTML que forman cabecera y pie de las plantillas de la aplicación web. Además, si el caso lo requiere es posible parametrizarlas pasando parámetros a las plantillas con la orden ''with'' de forma que éstos podrán ser utilizados en la plantilla destino (con la notación ''{{ parametro }}''). {% include 'peliculas/cabecera.html' %} {% include 'peliculas/navbar.html' with titulo='Portada' %} . . .

Esta es la página principal de mi aplicación web

. . . {% include 'peliculas/pie.html' %}
==== Creación de formularios ==== Primero definimos el formulario en Django (crearemos el fichero ''forms.py'' por ser el primero formulario que realizamos. A partir de ahora añadiremos clases aqui para ir registrando más formularios): from django import forms from .models import Pelicula . . . class PeliculaForm(forms.ModelForm): class Meta: fields = ('titulo', 'director', 'genero') model = Pelicula . . . Y ahora tendremos que crear la correspondiente //view// y //url// para la plantilla que acabamos de crear: . . . def nueva_pelicula(request): form = PeliculaForm() context = {'form': form} return render(request, 'mispeliculas/nueva_pelicula.html', context) . . . . . . url(r'^nueva_pelicula/$', views.nueva_pelicula, name='nueva_pelicula'), . . . A continuación, definimos la plantilla con el código HTML del mismo, incluyendo el token ''csrf_token'' para evitar el ataque //Cross-Site Request Forgery//. Para cada caja de texto (por cada etiqueta ''input'') que queramos representar usaremos el campo definido en el formulario (''form.titulo'', por ejemplo) y podremos añadir (incluyendo el paquete ''django-widgets-tweaks'' y cargándolo siempre que sea necesario) un estilo si queremos modificar su apariencia con la orden ''add_class'' e incluso añadir algún atributo más con ''attr'', pudiendo indicarse cualquier atributo que aceptara una etiqueta ''input'' de HTML. Para poder utilizar el módulo ''django-widgets-tweaks'' habrá que incluir como una aplicación de nuestro proyecto en el fichero ''settings.py'': . . . INSTALLED_APPS = [ . . . , . . . , 'widget_tweaks' ] . . . Además, podemos hacer que muestre justo debajo los errores (cuando los haya) asociados con cada uno de los campos. Esto tendrá efecto cuando, al rellenar y validar el formulario, éste nos venga de vuelta por algún fallo de validación. . . .
{% csrf_token %} . . . {{ form.titulo | add_class:'form-control' | attr:'size:20'}} {{ form.titulo.errors }} . . .
. . .
También es posible, utilizando el tag ''render_field'', renderizar los elementos del formulario de otra manera más cercana al estilo HTML . . . {% load widget_tweaks %} . . . {% render_field form.titulo class="form-control" placeholder=form.titulo.label size="40" %} {{ form.titulo.errors }} . . . A continuación creamos la ''view'' y ''url'' que permitan recoger los datos del formulario y registrar la pelicula en la Base de Datos: . . . from django.shortcuts import render, redirect from django.contrib import messages from .forms import PeliculaForm . . . def anadir_pelicula(request): if request.method == 'POST': form = PeliculaForm(request.POST, request.FILES) if form.is_valid(): pelicula = Pelicula() pelicula.titulo = form.cleaned_data['titulo'] pelicula.director = form.cleaned_data['director'] pelicula.genero = form.cleaned_data['genero'] pelicula.save() else: return render(request, 'mispeliculas/nueva_pelicula.html', {'form':form}) return redirect('nueva_pelicula') . . . . . . url(r'^anadir_pelicula/$', views.anadir_pelicula, name='anadir_pelicula'), . . . === Envio de mensajes === Django proporciona un framework de paso de mensajes por el que podemos enviar mensajes de texto después de procesar un formulario, por ejemplo. Resultaría útil, en este caso, para el ejemplo anterior donde volvemos a la plantilla de ''nueva_pelicula'' cuando los datos se han introducido en la Base de Datos correctamente. Quizás podría interesarnos colocar allí algún mensaje de confirmación o uno de error en alguna otra situación. Veremos ahora un ejemplo sobre cómo dejar mensajes en una //view// y que puedan ser procesador luego por una //plantilla//. Para este caso, vamos a dejar un mensaje justo antes de hacer la llamada a la función ''redirect'' del ejemplo anterior. . . . from django.contrib import messages . . . messages.success(request, 'Pelicula registrada correctamente') return redirect('nueva_pelicula') . . . Y ahora, en la plantilla que carga la //view// ''nueva_pelicula'' (''nueva.pelicula.html'') podremos leer ese mensaje para utilizarlo siempre y cuando no haya ningún problema al procesar el formulario: . . .

{% if messages %} {% for message in messages %} {% endfor %} {% endif %} . . .
{{ message.png }} Mensaje confirmación
=== Subir imágenes en un formulario === En el caso de que queramos subir imágenes en un formulario, tendremos que añadir algo de código adicional a lo que ya hemos comentado. A parte de definir el formulario para que soporte la subida de ficheros binarios, a la hora de definir el modelo, los campos que contenga imágenes tendrán que definirse de la siguiente forma: . . .
. . .
. . .
El campo imagen en el modelo quedaría como sigue (hay que tener que en cuenta que tendremos que instalar el paquete ''Pillow'' si no lo tenemos ya instalado. Lo podemos hacer desde la ventana donde instalamos el paquete para ''Django'' en su día) . . . imagen = models.ImageField(upload_to='img', default='') . . . Hará falta también modificar la clase del formulario para añadir el nuevo campo ''imagen'', de forma que quedaría de esta forma: from django import forms from .models import Pelicula . . . class PeliculaForm(forms.ModelForm): class Meta: fields = ('titulo', 'director', 'genero', 'imagen') model = Pelicula . . . Y, por supuesto, hay que asignar el campo ''imagen'' del formulario al atributo ''imagen'' del objeto a la hora de crearlo y almacenarlo en la Base de Datos. Eso lo haremos, como hacemos con cualquier formulario, en el fichero ''views.py'' en la view que corresponda: . . . def anadir_pelicula(request): . . . pelicula.imagen = form.cleaned_data['imagen'] . . . . . . La carpeta ''img'' tendrá que existir dentro de la carpeta destinada al contenido estático del proyecto y será donde las imágenes se suban cuando se procese el formulario. Y en el campo ''imagen'' (en este caso) quedará almacenada la ruta relativa. Asi, si queremos cargar esa imagen más adelante podemos hacerlo de la siguiente manera: . . . . . . Por último, tendremos que definir el valor de la variable ''MEDIA_ROOT'' en el fichero ''settings.py'' de nuestro proyecto indicando la ruta (desde la aplicación web) donde se encuentra la carpeta ''img'' donde se guardan las imágenes. . . . MEDIA_ROOT = 'peliculas/static/peliculas' . . . ==== Llamar a views de Django utilizando jQuery ==== Supongamos que tenemos un listado de peliculas como el que hemos implementando un poco más arriba y queremos añadir un enlace con forma de botón que permite eliminar dichas peliculas de la Base de Datos. Y además queremos realizar dicha acción de forma asíncrona utilizando Ajax, puesto que asi eliminamos la necesidad de recargar la página al volver de ejecutar la vista Django en el servidor. Añadiremos un enlace con forma de botón (con el símbolo X) para eliminar cada película, indicando el ''id'' de dicha pelicula como atributo de la etiqueta del enlace (para luego recogerlo desde Javascript):

Mis películas

+ {% if lista_peliculas %} {% else %}

No hay películas disponibles

{% endif %}
Y ahora, en el mismo fichero, añadimos el código jQuery que permitirán capturar la acción ''click'' sobre el enlace con forma de botón (identificándolos a través de su etiqueta y estilo ''a.btn''): . . . . . . Faltaría crear la vista Django que se encargue de realizar la operación de eliminar la pelicula, que en este caso variará ligeramente de las que hemos ido haciendo hasta ahora. En este caso tiene que devolver un resultado en forma de ''JSON'' (en este caso vacío) tras realizar la operación de borrado: . . . import json . . . def eliminar_pelicula(request, pelicula_id): pelicula = Pelicula.objects.get(pk=pelicula_id) pelicula.delete() return HttpResponse(json.dumps({}), content_type='application/json') Y su correspondiente //URL//: . . . url(r'^eliminar_pelicula/(?P[0-9]+)/$', views.eliminar_pelicula, name="eliminar_pelicula"), . . . ==== Implementar una caja de texto con autocompletado con Django y jQuery ==== Definimos una caja de texto a la que queremos añadir soporte para autocompletado a medida que el usuario escriba: . . . . . . Con jQuery (y la librería [[https://jqueryui.com|jQquery UI]]) añadimos soporte para el autocompletado a dicha caja de texto indicando la URL que se debe de ejecutar y, en este caso (no es obligatorio), incluso indicamos como renderizar la información recibida desde el servidor: . . . $('#director').autocomplete({ source: "{% url 'autocompleta_director' %}", minLength: 2, }).autocomplete("instance")._renderItem = function( ul, item ) { return $("
  • ") .append("
    " + item.nombre + "
    " + item.apellidos + "
    ") .appendTo(ul); }; . . .
    {{ autocompletar.png }} Caja de texto con autocompletado
    En la vista Django, recibimos el texto del elemento ''input'' por el protocolo ''GET'' y realizamos una búsqueda en la Base de Datos. Con los resultados formamos un documento ''JSON'' y lo mandamos de vuelta para que sea renderizado como una lista de autocompletado en el formulario inicial. Hay que tener en cuenta que si no se implementa con jQuery la opción de personalizar el HTML de renderizado de la lista de autocompletado, aparecerá directamente aquel dato que quede almacenado con la clave ''label'' en el documento ''JSON'' que Django devuelve. . . . def autocompleta_director(request): datos = request.GET contenido = datos.get('term') if contenido: directores = Director.objects.filter(nombre__contains=contenido) else: directores = Director.objects.all() resultados = [] for director in directores: item_json = {} # Valor por defecto si no se implementa la opción de renderizado en jQuery item_json['label'] = director.nombre item_json['nombre'] = director.nombre resultados.append(item_json) datos = json.dumps(resultados) mimetype = 'application/json' return HttpResponse(datos, mimetype) . . . Y su correspondiente ''url'' para poder invocarlo: . . . url(r'^autocompleta_director/$', views.autocompleta_director, name='autocompleta_director'), . . . ===== Paginación de resultados ===== Para la paginación de resultados en Django existe un objeto llamado ''Paginator'' que permite repartir los resultados obtenidos directamente de la Base de Datos indicando la página que se quiere mostrar. Será este objeto quién se encargue de calcular qué filas toca representar en cada una de las páginas. Lo primero que tendremos que hacer, en la configuración de nuestro proyecto, es fijar el número de resultados por página estableciendo una constante con el valor que nos interese (en este caso se muestra 5 resultados por página): . . . PELICULAS_POR_PAGINA = 5 . . . Ahora, siempre que queramos paginar, en la vista que extraiga los resultados de la Base de Datos, tendremos que hacer uso del objeto ''Paginator'' para que los resultados se repartan en páginas y sólo obtengamos y carguemos en la plantilla los que correspondan en función de la página donde estemos: . . . from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from Peliculas.settings import PELICULAS_POR_PAGINA . . . def peliculas(request): todas_las_peliculas = Pelicula.objects.order_by('titulo') pagina = request.GET.get('pagina', 1) paginator = Paginator(todas_las_peliculas, PELICULAS_POR_PAGINA) try: peliculas = paginator.page(pagina) except PageNotAnInteger: peliculas = paginator.page(1) except EmptyPage: peliculas = paginator.page(paginator.num_pages) context = {'peliculas': peliculas} return render(request, 'peliculas/peliculas.html', context) . . . A continuación, se muestra como quedaría la template para la view anterior, donde muestran botones para acceder a la página anterior y la siguiente (en caso de que haya) y la página actual sobre el total. . . .
      {% for pelicula in peliculas %}
    • {{ pelicula.titulo }}
    • {% endfor %}

    Mostrando {{ peliculas.number }} de {{ peliculas.paginator.num_pages }}

    . . .
    Sólo faltaría añadir la url en ''urls.py'' para esta view y su template. > Se puede encontrar más información sobre cómo paginar resultados con Django en la página que dedican en el manual de este framework sobre [[https://docs.djangoproject.com/en/1.10/topics/pagination/|Paginación en Django]] ---- {{ ejercicio.png?75}} === Ejercicios === - Pagina los resultados de una lista con botones para ir las páginas anterior y siguiente, y para ir a cada una de ellas (1, 2, 3, . . .). Puedes utilizar alguno de los diseños que Bootstrap ofrece, como el que se acompaña a este ejercicio: {{ paginar.png }} ---- ===== Autenticación de usuarios ===== Supongamos ahora que queremos añadir un sistema de gestión de usuarios a nuestra aplicación web. Necesitamos una tabla donde almacenar los usuarios y sus datos y por otro lado, de alguna manera, tendremos que permitir que éstos se registren, inicien sesión y otra serie de operaciones que podamos necesitar. Puesto que Django ya incorpora un sistema de gestión de usuarios con nuestra aplicación web, podemos aprovecharnos del mismo y podemos registrar los usuarios directamente haciendo uso del mismo. Podemos crear un formulario para permitir el registro de usuarios y también podemos registrarlos directamente desde la consola interactiva directamente. Lo haremos ahora de esta última forma para centrarnos en como gestionar el inicio de sesión. Más adelante podríamos crear otro formulario que permitiera el registro de nuevos usuarios ya que el código Python sería el mismo pero asociado a una ''view'' que recibiera el formulario de registro con los datos del usuario. Además, en [[https://docs.djangoproject.com/en/1.10/ref/contrib/auth/#django.contrib.auth.models.User|la documentación de la API para la clase User]] podemos consultar todos los datos con los que podemos contar a la hora de utilizar el sistema de usuarios incorporado por Django. Para este primer caso, creamos desde la consola interactiva un usuario e indicamos su nombre de usuario, e-mail y contraseña. santi@zenbook:$ python3 manager.py shell >>> from django.contrib.auth.models import User >>> user = User.objects.create_user('santi', 'santi@codeandcoke.com', 'mipassword') >>> user.save() ==== Inicio de sesión ==== A continuación, crearíamos un formulario para realizar el inicio de sesión y añadiríamos la correspondiente ''view'' y ''url'' como siempre para hacerlo funcionar. En este caso, además habría que preparar la view ''login_view'' que es donde se procesará dicho formulario para llevar a cabo la autenticación del usuario, que es en lo que nos centraremos en este apartado. . . . {% if message %} {% endif %}
    {{ login.png }} Formulario de inicio de sesión
    Así, suponiendo que se han creado las views y urls para el formulario, pasamos a ver como quedaría la view para la autenticación del usuario ''login_view'': . . . from django.contrib.auth import authenticate, login, logout . . . def login_view(request): username = request.POST['username'] password = request.POST['password'] user = authenticate(request, username=username, password=password) if user is not None: login(request, user) return redirect('index') context = {'message': 'Invalid Username/Password'} return render(request, 'peliculas/signin.html', context) . . . En la vista anterior, se recogen los datos del formulario y se comprueba si el usuario existe (función ''authenticate''). Si el usuario es válido (''is not None'') se procede a iniciar la sesión (función ''login''). En caso contrario volvemos a mandar al usuario al formulario de inicio de sesión con un mensaje de error. ==== Mantener la sesión del usuario ==== Una vez iniciada la sesión ya hemos visto que enviamos al usuario a la portada de la web. También podríamos haberlo enviado a la portada del panel de administración o cualquier otra acción que nos interese. Lo importante es saber cómo podemos saber que un usuario ha iniciado sesión y que ésta sigue activa (no ha salido de la misma). En este caso, suponemos que sólo queremos permitir el acceso a la portada de nuestra web a usuarios autenticados: . . . def index(request): if request.user.is_authenticated: return render(request, 'peliculas/index.html') else: return redirect('signin') . . . Hemos añadido en la view de la página de inicio un control para comprobar si el usuario está autenticado y asi permitirle visitar la página. En caso contrario lo enviamos directamente al formulario de inicio de sesión. Además, tenemos que tener en cuenta que podemos acceder a los datos del usuario desde las plantillas de nuestra aplicación web accediendo directamente al objeto ''request''. . . .

    Bienvenido {{ request.user.username }}

    . . .
    ==== Finalizar la sesión del usuario ==== Cuando queramos finalizar la sesión del usuario, simplemente tendremos que crear una view con el siguiente código: . . . from django.contrib.auth import authenticate, login, logout . . . def logout_view(request): logout(request) return HttpResponseRedirect(reverse('index')) . . . > Se puede encontrar más información sobre la gestión de la autenticación con Django en la página que dedican en el manual de este framework sobre [[https://docs.djangoproject.com/en/1.10/topics/auth/default/|El sistema de autenticación en Django]] ---- {{ ejercicio.png?75}} === Ejercicios === - Añade a la aplicación web un formulario para registrar nuevos usuarios - Añade a la aplicación web una zona donde cada usuario pueda modificar los datos de su perfil ---- ===== Informes ===== Para la creación de informes con Django utilizaremos la librería [[http://www.reportlab.com|reportlab]] que permite generar ficheros PDF con lenguaje Python. El primer paso será instalar el paquete ''reportlab'' con la herramienta de instalación de paquetes de Python o bien desde el IDE PyCharm directamente. santi@zenbook:$ pip3 install reportlab A continuación se muestran algunos ejemplos de uso de esta librería para la generación de informes. Conviene tener en cuenta que en éstos, se muestra solamente el código corresponde a la función que permite crear la ''view'' que lanzará el informe al usuario. Faltaría registrar la correspondiente url en el fichero ''urls.py'' de nuestra aplicación web y también añadir un enlace o botón en la plantilla donde queramos que el usuario tenga que pinchar para visualizar este informe. En este primer ejemplo se creará un informe (en PDF) donde se mostrará un título y a continuación un listado con los títulos de todas las películas de la Base de Datos. En este caso utilizaremos el objeto ''Canvas'' que nos permite ubicar los elementos del informe utilizando las coordenadas x e y para situarlos. Hay que tener en cuenta que la esquina inferior izquierda se corresponde con los valores ''x=0'' e ''y=0''. . . . def informe_peliculas(request): response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="peliculas.pdf"' buffer = BytesIO() can = canvas.Canvas(buffer) can.drawString(200, 800, "Peliculas") peliculas = Pelicula.objects.all() y = 700 for pelicula in peliculas: can.drawString(50, y, pelicula.titulo) y -= 20 can.showPage() can.save() pdf = buffer.getvalue() buffer.close() response.write(pdf) return response . . . En este segundo ejemplo, utilizando en este caso el objeto ''SimpleDocTemplate'', que permite añadir contenido al documento del informe si necesidad de indicar las coordenadas explicitamente. En este caso se genera una tabla (con borde) con el listado de las películas y cierta información sobre las mismas. . . . def informe_peliculas(request): response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="peliculas.pdf"' buffer = BytesIO() documento = SimpleDocTemplate(buffer, pagesize=A4, rigthMargin=40, leftMargin=40, topMargin=60, bootomMargin=18) contenido = [] estilos = getSampleStyleSheet() cabecera = Paragraph("Listado de Películas", estilos['Heading1']) contenido.append(cabecera) cabecera_tabla = ('Título', 'Género', 'Director') datos = [(pelicula.titulo, pelicula.genero, pelicula.director) for pelicula in Pelicula.objects.all()] tabla = Table([cabecera_tabla] + datos) tabla.setStyle(TableStyle([('GRID', (0, 0), (-1, -1), 1, colors.black),])) contenido.append(tabla) documento.build(contenido) response.write(buffer.getvalue()) buffer.close() return response . . . Podéis encontrar más información en esta [[https://www.reportlab.com/docs/reportlab-userguide.pdf|Guía de reportlab]] ===== Internacionalización ====== Desde el directorio raíz de la aplicación (no del proyecto), primero creamos la carpeta donde se almacenarán las traducciones para cada uno de los idiomas para los que queramos dar soporte santi@zenbook:Projects/peliculas/mispeliculas$ mkdir locale Si el texto va en una //template// HTML: {% load i18n %} . . .

    {% trans "Añadir pelicula" %}

    . . .
    Si estamos utilizando Python como lenguaje (en una vista, por ejemplo): from django.utils.translation import ugettext as _ . . . mensaje = _("Añadir pelicula") . . . Cuando queramos crear la estructura para el soporte de un idioma determinado (dentro de la carpeta de la aplicación): santi@zenbook:$ django-admin makemessages -l es processing locale es santi@zenbook:$ django-admin makemessages -l en processing locale en {{ django_i18n.png }} . . . msgid "Añadir pelicula" msgstr "Add movie" . . . Y si queremos reexaminar todos los ficheros de idiomas que se hayan creado porque se han realizado cambios o se han añadido nuevos mensajes (dentro de la carpeta de la aplicación): santi@zenbook:$ django-admin makemessages -a Además, es posible convertir estos ficheros de texto ''.po'' en ficheros binarios ''.mo'' más optimizados para usar con ''gettext'': santi@zenbook:$ django-admin compilemessages ===== Despliegue de aplicaciones ===== ==== Utilizando WSGI con Apache ==== En este apartado veremos como desplegar una aplicación web Django con Apache usando el módulo WSGI. Para ello tendremos que realizar una serie de ajustes, instalación y configuraciones: - Antes de comenzar, tendremos que ajustar algunos parámetros de configuración de nuestro proyecto - Más adelante instalaremos las aplicaciones y módulos necesarios para su despliegue con Apache y crearemos el host virtual - Para terminar tendremos que ajustar algunos permisos para que Apache pueda acceder al contenido de la aplicación web sin ningún problema. === Ajustar parámetros en el proyecto Django === Antes de comenzar, tenemos que comprobar que tenemos instalado el intérprete de Python y todos los paquetes necesarios en la máquina servidor. En nuestro caso al menos los paquetes ''django'' (en nuestro caso la versión 1.11.8), ''django-widget-tweaks'' y ''Pillow'' a través del comando ''pip3 install'' santi@zenbook:$ sudo apt-get install python3 . . . santi@zenbook:$ sudo pip3 install django==1.11.8 . . . santi@zenbook:$ sudo pip3 install django-widget-tweaks . . . santi@zenbook:$ sudo pip3 install Pillow . . . Lo primero que haremos será reunir todos los ficheros estáticos de la aplicación con ayuda del siguiente comando, lo que generará una carpeta ''static'' en el directorio raíz de nuestro proyecto con todo el contenido estático del mismo. santi@zenbook:$ python3 manage.py collectstatic . . . . . . 543 static files copied to '/home/santi/mispeliculas.com/static' Ahora, en el fichero ''settings.py'' de nuestro proyecto, fijaremos la nueva ruta donde se ha almacenado el contenido estático y también incluiremos la ruta raíz para el contenido que se suba con los modelos (imágenes asociadas, por ejemplo, a través de un formulario). Si además queremos que la aplicación web pueda ser accedida remotamente tendremos que definir cuáles son los hosts permitidos. Y por último, si suponemos que la aplicación web se encuentra en un servidor de producción, podríamos desactivar el modo ''DEBUG'' . . . # Directorio con el contenido estático STATIC_ROOT = os.path.join(BASE_DIR, 'static/') # Directorio a partir del cual se almacenarán los ficheros que se suban con algún modelo (en ''items'' en este caso) MEDIA_ROOT = '/home/santi/mispeliculas.com/static/mispeliculas' # Define los hosts que tienen permitido acceder a la aplicación web ALLOWED_HOSTS = ['mispeliculas.com'] # Desactiva el modo DEBUG DEBUG = False . . . En el fichero ''/home/santi/mispeliculas.com/peliculas/urls.py'' podemos modificar la url raíz para acceder directamente sobre el dominio de la aplicación web, dejando el fichero asi: . . . url(r'^', include('mispeliculas.urls') . . . De esta manera, una vez desplegada correctamente la aplicación, podremos acceder directamente a través de la url ''http://mispeliculas.com'' en vez de ''http://mispeliculas.com/peliculas'' o algo similar como teníamos desde un principio. === Instalación y configuración de Apache === Suponiendo que ya tenemos instalado Apache, tendremos que instalar el módulo WSGI para Python 3 (en nuestro caso) santi@zenbook:$ sudo apt-get install libapache2-mod-wsgi-py3 A continuación, activamos el módulo: santi@zenbook:$ sudo a2enmod wsgi Enabling module wsgi. To activate the new configuration, you need to run: systemctl restart apache2 Finalmente tendremos que crear un host virtual con las directivas habituales para un sitio alojado por Apache, al que añadiremos algunas específicas por tratarse de una aplicación web hecha con Python ServerName mispeliculas.com ServerAdmin santi@codeandcoke.com Alias /static /home/santi/mispeliculas.com/static Require all granted Require all granted WSGIDaemonProcess peliculas python-path=/home/santi/mispeliculas.com WSGIProcessGroup peliculas WSGIScriptAlias / /home/santi/mispeliculas.com/peliculas/wsgi.py ErrorLog ${APACHE_LOG_DIR}/mispeliculas-error.log CustomLog ${APACHE_LOG_DIR}/mispeliculas-access.log combined Ahora ya podemos reiniciar Apache y comprobar que todo está funcionando correctamente santi@zenbook:$ sudo service apache2 restart === Ajustes de permisos === Siempre dependerá de cómo se tenga configurado en cada caso, pero conviene echar un vistazo a los permisos asignados al fichero y carpeta donde se encuentra la Base de Datos (en este caso estamos utilizando SQLite), y también a la carpeta donde se encuentra el proyecto. santi@zenbook:$ chown :www-data /home/santi/mispeliculas.com/db.sqlite3 santi@zenbook:$ chmod 664 /home/santi/mispeliculas.com/db.sqlite3 santi@zenbook:$ chown :www-data /home/santi/mispeliculas.com santi@zenbook:$ chmod g+w /home/santi/mispeliculas.com También es posible que necesitemos asignar permisos de escritura en la carpeta donde se sube el contenido a través de los formularios (donde indica la variable ''MEDIA_ROOT'') santi@zenbook:$ chown -R :www-data /home/santi/mispeliculas.com/static santi@zenbook:$ chmod -R g+w /home/santi/mispeliculas.com/static Y ahora podrás visitar tu aplicación web Django accediendo directamente a ''http://mispeliculas.com'' ==== Desplegar la aplicación utilizando entornos virtuales ==== santi@zenbook:$ pip3 install virtualenv santi@zenbook:$ virtualenv env santi@zenbook:$ source env/bin/activate . . . santi@zenbook:$ pip install django santi@zenbook:$ pip install Pillow . . . santi@zenbook:$ deactivate . . . WSGIDaemonProcess peliculas python-path=/home/santi/mispeliculas.com:/home/santi/mispeliculas.com/env/lib/python3.5/site-packages . . . ===== Ejercicios ===== - Tomando como referencia el ejemplo del Blog de Bootstrap, preparar una aplicación web con Django que tenga las siguientes funcionalidades: * En la portada se mostraran los posts que haya escritos y de cada uno de ellos se mostrará la siguiente información: * Titulo del post * Nombre del autor * Fecha * Texto * En otra zona de la página (por ahora pública) hay que crear un formulario que permita escribir y registrar nuevos posts con toda esa información \\ \\ {{ blog.png }} \\ \\ - Sobre el ejercicio anterior del blog, añadir las siguientes secciones o funcionalidades: * Crear una zona privada protegida por usuario/contraseña desde donde se creen nuevas entradas en el blog * Listar (en forma de tabla) en esa zona privada las entradas de forma que puedan ser eliminadas. Como diseño se puede tomar cualquier de los que aparecen en la documentación de Bootstrap en [[https://getbootstrap.com/docs/4.0/content/tables/|Content-Tables]], añadiéndole algún botón para que los posts puedan ser directamente eliminados desde la tabla * Paginar los resultados de la portada fijando un número máximo de post visualizados por página \\ \\ - Realizar una página web que liste en forma de tabla los equipos de fútbol de primera división, mostrando para cada uno de ellos el nombre, ciudad de origen, nombre y apellidos del entrenador y el número de jugadores * El sitio web contará con una portada en la que habrá un botón para acceder a la página donde se mostrará el listado de equipos * Paginar los resultados en bloques de 5 equipos ---- ===== Proyectos de Ejemplo ===== * [[https://bitbucket.org/sfaci/servidor-ejercicios/src/2f9746ced2d43148194887d206e89d2eb17c8fdc/Peliculas/?at=master|Peliculas]] Proyecto de gestión de peliculas con Django ---- ===== Prácticas ===== ---- (c) 2018 Santiago Faci