Cómo desarrollar un proyecto web completo que permite, vía web, añadir, eliminar y mostrar registros de una base de datos MySQL/MariaDB (válido para otras bases de datos con muy pocos cambios). Como estilo visual CSS usaremos la biblioteca Boostrap instalada en Django. Se mostrarán los registros en una tabla y cada registro tendrá los botones Modificar (modificar el registro actual), Eliminar (eliminar el registro actual) y Detalle (consultar todos los datos del registro). Incluimos la descarga del proyecto completo en este enlace.
- Preparar el entorno Django Python, instalar Bootstrap para Django, crear proyecto y aplicación web Django.
- Crear modelo de datos Contactos en aplicación web Django y persistencia en MySQL/MariaDB.
- Crear las vistas views para la aplicación web Contactos de Django.
- Rutas de la aplicación web Agenda en Django.
- Plantillas (templates) para la aplicación web Contactos en Django.
- Configuración adicional en settings.py para mostrar mensajes y para ruta de fotos de los contactos.
- Probando la aplicación web Contactos en Django.
- Descarga del proyecto completo Agenda y Contactos en Django y Python.
Preparar el entorno Django Python, instalar Bootstrap para Django, crear proyecto y aplicación web Django
Antes de continuar, para los usuarios que no tengan un nivel medio de conocimientos de Django, conviene que revisen los siguientes artículos, donde explicamos, entre otras cosas, cómo instalar Django sobre Python, cómo crear un proyecto, cómo crear una aplicación, cómo crear un formulario web para insertar registros, cómo sincronizar el modelo Django con la base de datos MySQL, etc.:
- Instalar Django y primera aplicación web hola mundo en Python.
- Añadir formulario para insertar registro en tabla MySQL usando Framework Django y Python.
En nuestro caso trabajaremos con un equipo con Windows 10, con Python versión 3.9.0, Django versión 4.0.1, Bootstrap versión 5 y MariaDB versión 5.5. Instalaremos Python y Django como indicamos en los artículos anteriores.
Si queremos usar Bootstrap 5 en nuestro proyecto web de forma más o menos automatizada, podremos instalar el paquete para Django correspondiente. Si bien no es necesario porque en realidad lo único que hace este paquete es agregar una línea de código en el HTML haciendo referencia a la ubicación web del Javascript y el CSS de Bootstrap en Internet. Aún así, si queremos instalarlo, ejecutaremos, desde la línea de comandos (símbolo de sistema), el siguiente comando:
pip install django-bootstrap5
Reiteramos que lo anterior no es necesario, podremos usar en nuestros ficheros de plantillas html (templates) un enlace directo al CSS de Boostrap y a su fichero JavaScript, así como a sus componentes (pipe), como indicamos en pasos sucesivos de este artículo.
Si queremos usar un módulo para ayudar a generar formularios instalaremos «django-widget-tweaks». Aunque este módulo tampoco es necesario si establecemos el código HTML de los formularios de manera manual. Si queremos automatizar (en la medida de lo posible) la inserción de campos en formularios podremos instalar este componente con el comando:
pip install django-widget-tweaks
Crearemos el proyecto Django «proyectoa_agenda», para ello accederemos desde la línea de comandos a la carpeta donde queramos que se ubique la subcarpeta del proyecto (la creará el propio comando), en nuestro caso en la uniad D:
d:
Y en la carpeta ProyectoA_Python\Django:
cd ProyectoA_Python\Django
Y ejecutaremos el siguiente comando para crear un proyecto Django llamado «proyectoa_agenda«:
django-admin startproject proyectoa_agenda
Una vez creado el proyecto, accederemos a la carpeta del proyecto con:
cd proyectoa_agenda
Ahora crearemos una app (aplicación) dentro del proyecto, llamada «app_contactos«. Un proyecto puede contener varias aplicaciones. Para crear la aplicación ejecutaremos el comando:
python manage.py startapp app_contactos
Esta es la estructura de carpetas que se habrá creado:
Registraremos la app app_contactos en el fichero settings.py de la carpeta del proyecto proyectoa_agenda, para ello agregaremos una línea en INSTALLED_APPS:
Crear modelo de datos Contactos en aplicación web Django y persistencia en MySQL/MariaDB
A continuación crearemos el modelo de datos, el model. Una vez creado, desde Django, migraremos este modelo al motor de base de datos elegido para hacerlo persistente. Como mostraremos a continuación.
Para crear el modelo editaremos el fichero models.py ubicado en la carpeta de la aplicación app_agenda y agregaremos el siguiente código Python:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from django.db import models from django.utils import timezone # Clase "Contactos" que tendrá los campos que se usarán en la aplicación y que se crearán en la tabla la base de datos class Contactos(models.Model): nombre = models.CharField(max_length=150, null=False, verbose_name="Nombre", default="") telefono_movil = models.CharField(max_length=9, null=True, blank=True, verbose_name="Tlfno. móvil", default="") telefono_fijo = models.CharField(max_length=9, null=True, blank=True, verbose_name="Tlfno. fijo", default="+34") mail = models.EmailField(max_length=150, null=True, blank=True, verbose_name="EMail", default="") direccion = models.CharField(max_length=200, null=True, blank=True, verbose_name="Dirección postal", default="") #foto = models.FileField(upload_to ='fotos/', max_length=254, null=True, blank=True, verbose_name="Foto") foto = models.FileField(max_length=254, null=True, blank=True, verbose_name="Foto") empresa = models.CharField(max_length=150, default='Proyecto A') fecha_alta = models.DateTimeField(auto_now_add=True) fecha_actualizacion = models.DateTimeField(auto_now=True) class Meta: db_table = 'contactos' # Nombre que tendrá la tabla que se creará en la base de datos en la Base de Datos |
Para indicar a Django que use un servidor de MySQL/MariaDB específico (o cualquier otro motor de base de datos soportado por Django), editaremos el fichero settings.py de la carpeta del proyecto proyectoa_agenda y agregaremos las siguientes líneas en DATABASES:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'agenda', 'USER': 'usuario_pa', 'PASSWORD': 'contraseña', 'HOST': '192.168.1.5', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" } } } |
Indicando que se usará por defecto el motor de base de datos MySQL/MariaDB, del equipo con IP 192.168.1.5, con el usuario y contraseña especificados de acceso a MySQL. Como es lógico, cambiaremos estos datos para acceder a nuestro equipo con MySQL correspondiente.
Se podrá usar cualquier motor de BD soportado por Django: Oracle, PostgreSQL, MySQL, SQLite, MariaDB, etc.. Únicamente habrá que modificar el ENGINE y las posibles OPTIONS. Y tener instalada la librería de acceso al motor de base de datos correspondiente para Python, como indicamos para MySQL/MariaDB en este artículo:
Para que funcione correctamente el enlace entre nuestra aplicación Django y MySQL/MariaDB, editaremos el fichero:
1 |
__init__.py |
Ubicado en la carpeta del proyecto proyectoa_agenda, y agregaremos las siguientes líneas:
1 2 3 |
import pymysql pymysql.install_as_MySQLdb() |
También debemos tener creado el esquema (base de datos o catálogo) donde se realizará la persistencia. Será en esta base de datos donde se cree la tabla de la aplicación «contactos». En nuestro caso la base de datos se llamará «agenda». La podremos crear usando un cliente de gestión de MySQL/MariaDB o bien desde la línea de comandos con el comando «mysql»:
Cuando tengamos bien definido el modelo de datos (model), creado anteriormente, ejecutaremos los siguientes comandos para sincronizar los cambios en la base de datos «agenda» de MySQL/MariaDB. Se crearán las tablas correspondientes a cada clase del model (models.py) con sus campos. Para ello ejecuaremos el siguiente comando, siempre desde la carpeta del proyecto proyectoa_agenda:
python manage.py makemigrations app_contactos
Devolviendo:
Migrations for ‘app_contactos’:
app_contactos\migrations\0001_initial.py
– Create model Contactos
Y este otro comando para hacer la migración definitiva:
python manage.py migrate app_contactos
Que devolverá algo así:
Operations to perform:
Apply all migrations: app_contactos
Running migrations:
Applying app_contactos.0001_initial… OK
Si el proceso de sincronización se ha ejecutado correctamente podremos consultar, en el servidor de base de datos, que se ha creado la tabla contactos con todos los campos indicados en models.py, en la base de datos «agenda»:
Esta es la consulta SQL de creación de tabla que ha ejecutado Django:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
CREATE TABLE `contactos` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `nombre` varchar(150) NOT NULL, `telefono_movil` varchar(9) DEFAULT NULL, `telefono_fijo` varchar(9) DEFAULT NULL, `mail` varchar(150) DEFAULT NULL, `direccion` varchar(200) DEFAULT NULL, `foto` varchar(254) DEFAULT NULL, `empresa` varchar(150) NOT NULL, `fecha_alta` datetime(6) NOT NULL, `fecha_actualizacion` datetime(6) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1; |
Crear las vistas views para la aplicación web Contactos de Django
Agregaremos el siguiente código al fichero views.py de la carpeta de la aplicación app_contactos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
from django.shortcuts import render from django.views.generic import ListView, DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from .models import Contactos from django.urls import reverse from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin from django import forms #Para obtener todos los registros de la tabla Contactos class ContactoListar(ListView): model = Contactos #Para obtener todos los campos de un registro de la tabla Contactos class ContactoDetalle(DetailView): model = Contactos #Para insertar un nuevo contacto en la tabla Contactos class ContactoNuevo(SuccessMessageMixin, CreateView): model = Contactos form = Contactos fields = "__all__" # Mensaje que se mostrará cuando se inserte el registro success_message = 'Contacto añadido correctamente.' # Redirigimos a la página principal tras insertar el registro def get_success_url(self): return reverse('listar') #Para modificar un contacto existente de la tabla Contactos class ContactoActualizar(SuccessMessageMixin, UpdateView): model = Contactos form = Contactos fields = "__all__" # Mensaje que se mostrará cuando se actualice el registro success_message = 'Contacto actualizado correctamente.' # Redireccionamos a la página principal tras actualizar el registro def get_success_url(self): return reverse('listar') #Para eliminar un contacto de la tabla Contactos class ContactoEliminar(SuccessMessageMixin, DeleteView): model = Contactos form = Contactos fields = "__all__" #Redireccionamos a la página principal tras de eliminar el registro def get_success_url(self): # Mensaje que se mostrará cuando se elimine el registro success_message = 'Contacto eliminado correctamente.' messages.success (self.request, (success_message)) return reverse('listar') #Ejemplo de redirección a una página HTML existente def CerrarSesion(request): return render(request, 'logout.html') |
Rutas de la aplicación web Agenda en Django
Crearemos las rutas para cada vista en el fichero urls.py del proyecto proyectoa_agenda, agregando el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from django.contrib import admin from django.urls import path from app_contactos.views import ContactoListar, ContactoDetalle, ContactoNuevo, ContactoActualizar, ContactoEliminar, CerrarSesion from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), #Mostrar todos los registros en una tabla path('app_contactos/', ContactoListar.as_view(template_name = "app_contactos/index.html"), name='listar'), #Mostrar una página con el detalle del registro path('app_contactos/detalle/<int:pk>', ContactoDetalle.as_view(template_name = "app_contactos/detalles.html"), name='detalles'), #Mostrar formulario de alta de nuevo registro path('app_contactos/nuevo', ContactoNuevo.as_view(template_name = "app_contactos/crear.html"), name='nuevo'), #Mostrar formulario de modificación de registro path('app_contactos/editar/<int:pk>', ContactoActualizar.as_view(template_name = "app_contactos/actualizar.html"), name='actualizar'), #Eliminar un registro path('app_contactos/eliminar/<int:pk>', ContactoEliminar.as_view(), name='eliminar'), #Redirección a html Cerrar Sesión (logout.html) #Sólo ejemplo para mostrar una página HTML estática path('app_contactos/cerrarsesion', CerrarSesion, name='cerrarsesion'), ] |
Plantillas (templates) para la aplicación web Contactos en Django
Crearemos las plantillas html para cada vista de nuestra aplicación web Contactos. Para ello, crearemos la siguiente estructura de carpetas dentro de la carpeta de la aplicación web app_contactos:
Básicamente crearemos la carpeta templates dentro de la carpeta de la aplicación app_contactos y, dentro de la carpeta templates, crearemos la subcarpeta con el nombre de la aplicación app_contactos. Será en esta subcarpeta donde guardaremos los ficheros de las plantillas que crearemos a continuación: crear.html, detalles.html, index.html y actualizar.html:
Nos aseguraremos de que tenemos el siguiente parámetro de configuración en el fichero settings.py de la carpeta del proyecto proyectoa_agenda:
1 |
STATIC_URL = 'static/' |
A continuación mostramos el contenido de cada plantilla. Lo ideal y para una mayor eficiencia, sería usar encabezados y pies automatizados, cogidos de otra plantilla por directivas. Esto lo veremos en próximos artículos.
El código fuente completo del proyecto está disponible para su decarga (gratuita) en el final del artículo.
- index.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Agenda - Contactos - ProyectoA - Django</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> {% load static %} <!-- Cargar URL raíz --> </head> <body> <!--Menú y barra superior --> <div class="container-fluid"> <!-- Logo e iniciales --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <!--Botón que sustituye al menú en dispositivos móviles --> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menu_principal" aria-controls="menu_principal" aria-expanded="false" aria-label="Menú"> <span class="navbar-toggler-icon"></span> </button> <!-- Logotipo y siglas --> <a class="navbar-brand" href="index.php"> <img alt="Agenda - Proyecto A" src="{% static 'fotos/'%}logotipo.png" class="d-inline-block align-text-top"/> <span>PA</span> </a> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="/" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="glyphicon glyphicon-time"></i> Contactos</a> <!-- Iniciamos principio del grupo (menú) --> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="dropdown-item" href="{% url 'listar' %}"> <i class="glyphicon glyphicon-th-list"></i> Listado</a> </li> <li><a class="dropdown-item" href="{% url 'nuevo' %}"> <i class="glyphicon glyphicon-plus"></i> Nuevo</a> </li> </ul> <!-- Cierra la lista de submenús del grupo --> </li> <!-- Cierra el grupo de submenús del menú principal --> </ul> <!-- Cierra el menú principal con los menús y submenús --> </div> <!-- container para menús diferentes (principal con grupos y submenus) --> <!-- Menú derecha para usuario y cierre de sesión --> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="fas fa-user"> ProyectoA</i> </a> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="nav-link disabled"><i class="fa fa-asterisk"></i> administrador </a></li> <li><hr class="dropdown-divider"></li> <li><a class="nav-link disabled">Configuración</a></li> <li><a class="nav-link disabled">Perfil</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="{% url 'cerrarsesion' %}"><i class="fa fa-window-close"></i> Cerrar sesión</a></li> </ul> </li> </ul> </div> </div> <!-- del container fluid de los elementos del menú --> </nav> <!-- del menú principal --> </div> <!-- del container fluid de la barra superior del menú --> <div> {% if messages %} <ul class="messages list-group mb-3"> {% for message in messages %} <li{% if message.tags %} class="{{ message.tags }} list-group-item list-group-item-primary"{% endif %}>{{ message }}</li> {% endfor %} </ul> {% endif %} </div> <div> <table class="table table-striped table-hover"> <thead> <tr> <th width="35%">Nombre</th> <th>Tfno. móvil</th> <th>EMail</th> <th>Foto</th> <th>Empresa</th> </tr> </thead> <tbody> <!-- Recorremos los registros de la tabla 'contactos' y los mostramos --> {% for contacto in object_list %} <tr> <td>{{ contacto.nombre }}</td> <td>{{ contacto.telefono_movil }}</td> <td>{{ contacto.mail }}</td> <td><img src="{% static 'fotos/'%}{{contacto.foto}}" alt="{{contacto.nombre}}" class="img-fluid" width="7%"></td> <td> <!-- Formulario para eliminar un registro desde la misma tabla HTML --> <form method="POST" action="eliminar/{{contacto.id}}"> {% csrf_token %} <div class="btn-group"> <!-- Creamos 3 botones Detalle, Editar y Eliminar --> <a href="detalle/{{contacto.id}}" title="Detalle" type="button" class="btn btn-success">Ver </a> <a href="editar/{{contacto.id}}" title="Editar" type="button" class="btn btn-primary">Editar </a> <button class="btn btn-danger" onclick="return eliminar();" type="submit"> Eliminar </button> </div> </form> </td> </tr> {% endfor %} </tbody> </table> <div align="left" class="btn_crear mb-3"> <a href="nuevo" type="button" class="btn btn-primary">Añadir contacto</a> </div> </div> <script type="text/javascript"> function eliminar() { var x = confirm("¿Está seguro de que desea eliminar el contacto?"); if (x) return true; else return false; } </script> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> </body> </html> |
- crear.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Agenda - Contactos - ProyectoA - Django</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> {% load static %} <!-- Cargar URL raíz --> {% load widget_tweaks %} </head> <body> <!--Menú y barra superior --> <div class="container-fluid"> <!-- Logo e iniciales --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <!--Botón que sustituye al menú en dispositivos móviles --> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menu_principal" aria-controls="menu_principal" aria-expanded="false" aria-label="Menú"> <span class="navbar-toggler-icon"></span> </button> <!-- Logotipo y siglas --> <a class="navbar-brand" href="index.php"> <img alt="Agenda - Proyecto A" src="{% static 'fotos/'%}logotipo.png" class="d-inline-block align-text-top"/> <span>PA</span> </a> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="glyphicon glyphicon-time"></i> Contactos</a> <!-- Iniciamos principio del grupo (menú) --> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="dropdown-item" href="{% url 'listar' %}"> <i class="glyphicon glyphicon-th-list"></i> Listado</a> </li> <li><a class="dropdown-item" href="{% url 'nuevo' %"> <i class="glyphicon glyphicon-plus"></i> Nuevo</a> </li> </ul> <!-- Cierra la lista de submenús del grupo --> </li> <!-- Cierra el grupo de submenús del menú principal --> </ul> <!-- Cierra el menú principal con los menús y submenús --> </div> <!-- container para menús diferentes (principal con grupos y submenus) --> <!-- Menú derecha para usuario y cierre de sesión --> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="listar" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="fas fa-user"> ProyectoA</i> </a> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="nav-link disabled"><i class="fa fa-asterisk"></i> administrador </a></li> <li><hr class="dropdown-divider"></li> <li><a class="nav-link disabled">Configuración</a></li> <li><a class="nav-link disabled">Perfil</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="app_contactos/cerrar"><i class="fa fa-window-close"></i> Cerrar sesión</a></li> </ul> </li> </ul> </div> </div> <!-- del container fluid de los elementos del menú --> </nav> <!-- del menú principal --> </div> <!-- del container fluid de la barra superior del menú --> <div> <form method="post" enctype="multipart/form-data"> <!-- Token de seguridad para poder crear un nuevo registro --> {% csrf_token %} <div class="form-group"> <label for="nombre" class="txt_negrita">Nombre</label> {{ form.nombre|add_class:"form-control" }} </div> <div class="form-group"> <label for="telefono_movil" class="txt_negrita">Tlfno. móvil</label> {{ form.telefono_movil|add_class:"form-control" }} </div> <div class="form-group"> <label for="telefono_fijo" class="txt_negrita">Tlfno. fijo</label> {{ form.telefono_fijo|add_class:"form-control" }} </div> <div class="form-group"> <label for="mail" class="txt_negrita">E-Mail</label> {{ form.mail|add_class:"form-control" }} </div> <div class="form-group"> <label for="direccion" class="txt_negrita">Dirección</label> {{ form.direccion|add_class:"form-control" }} </div> <div class="form-group"> <label for="empresa" class="txt_negrita">Empresa</label> {{ form.empresa|add_class:"form-control" }} </div> <div class="form-group"> <label for="foto" class="txt_negrita">Foto</label> {{ form.foto|add_class:"form-control mb-3" }} </div> <button type="submit" class="btn btn-primary">Guardar contacto</button> <a href="./" type="submit" class="btn btn-primary">Cancelar</a> </form> </div> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> </body> </html> |
- actualizar.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Agenda - Contactos - ProyectoA - Django</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> {% load static %} <!-- Cargar URL raíz --> {% load widget_tweaks %} </head> <body> <!--Menú y barra superior --> <div class="container-fluid"> <!-- Logo e iniciales --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <!--Botón que sustituye al menú en dispositivos móviles --> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menu_principal" aria-controls="menu_principal" aria-expanded="false" aria-label="Menú"> <span class="navbar-toggler-icon"></span> </button> <!-- Logotipo y siglas --> <a class="navbar-brand" href="index.php"> <img alt="Agenda - Proyecto A" src="{% static 'fotos/'%}logotipo.png" class="d-inline-block align-text-top"/> <span>PA</span> </a> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="glyphicon glyphicon-time"></i> Contactos</a> <!-- Iniciamos principio del grupo (menú) --> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="dropdown-item" href="{% url 'listar' %}"> <i class="glyphicon glyphicon-th-list"></i> Listado</a> </li> <li><a class="dropdown-item" href="{% url 'nuevo' %}"> <i class="glyphicon glyphicon-plus"></i> Nuevo</a> </li> </ul> <!-- Cierra la lista de submenús del grupo --> </li> <!-- Cierra el grupo de submenús del menú principal --> </ul> <!-- Cierra el menú principal con los menús y submenús --> </div> <!-- container para menús diferentes (principal con grupos y submenus) --> <!-- Menú derecha para usuario y cierre de sesión --> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="fas fa-user"> ProyectoA</i> </a> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="nav-link disabled"><i class="fa fa-asterisk"></i> administrador </a></li> <li><hr class="dropdown-divider"></li> <li><a class="nav-link disabled">Configuración</a></li> <li><a class="nav-link disabled">Perfil</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="app_contactos/cerrar"><i class="fa fa-window-close"></i> Cerrar sesión</a></li> </ul> </li> </ul> </div> </div> <!-- del container fluid de los elementos del menú --> </nav> <!-- del menú principal --> </div> <!-- del container fluid de la barra superior del menú --> <div> <form method="post" enctype="multipart/form-data"> <!-- Token de seguridad para poder crear un nuevo registro --> {% csrf_token %} <!-- {{ form.as_p }} --> <div class="form-group"> <label for="nombre" class="txt_negrita">Nombre</label> {{ form.nombre|add_class:"form-control" }} </div> <div class="form-group"> <label for="telefono_movil" class="txt_negrita">Tlfno. móvil</label> {{ form.telefono_movil|add_class:"form-control" }} </div> <div class="form-group"> <label for="telefono_fijo" class="txt_negrita">Tlfno. fijo</label> {{ form.telefono_fijo|add_class:"form-control" }} </div> <div class="form-group"> <label for="direccion" class="txt_negrita">Dirección</label> {{ form.direccion|add_class:"form-control" }} </div> <div class="form-group"> <label for="empresa" class="txt_negrita">Empresa</label> {{ form.empresa|add_class:"form-control" }} </div> <div class="form-group"> <label for="foto" class="txt_negrita">Foto</label> {{ form.foto|add_class:"form-control mb-3" }} </div> <button type="submit" class="btn btn-primary">Guardar</button> <a href="{% url 'listar' %}" type="submit" class="btn btn-primary">Cancelar</a> </form> </div> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> </body> </html> |
- detalles.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Agenda - Contactos - ProyectoA - Django</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> {% load static %} <!-- Cargar URL raíz --> {% load widget_tweaks %} </head> <body> <!--Menú y barra superior --> <div class="container-fluid"> <!-- Logo e iniciales --> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <!--Botón que sustituye al menú en dispositivos móviles --> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menu_principal" aria-controls="menu_principal" aria-expanded="false" aria-label="Menú"> <span class="navbar-toggler-icon"></span> </button> <!-- Logotipo y siglas --> <a class="navbar-brand" href="index.php"> <img alt="Agenda - Proyecto A" src="{% static 'fotos/'%}logotipo.png" class="d-inline-block align-text-top"/> <span>PA</span> </a> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="glyphicon glyphicon-time"></i> Contactos</a> <!-- Iniciamos principio del grupo (menú) --> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="dropdown-item" href="{% url 'listar' %}"> <i class="glyphicon glyphicon-th-list"></i> Listado</a> </li> <li><a class="dropdown-item" href="{% url 'nuevo' %}"> <i class="glyphicon glyphicon-plus"></i> Nuevo</a> </li> </ul> <!-- Cierra la lista de submenús del grupo --> </li> <!-- Cierra el grupo de submenús del menú principal --> </ul> <!-- Cierra el menú principal con los menús y submenús --> </div> <!-- container para menús diferentes (principal con grupos y submenus) --> <!-- Menú derecha para usuario y cierre de sesión --> <div class="collapse navbar-collapse" id="menu_principal"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="submenu_principal" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <i class="fas fa-user"> ProyectoA</i> </a> <ul class="dropdown-menu" aria-labelledby="submenu_principal"> <li><a class="nav-link disabled"><i class="fa fa-asterisk"></i> administrador </a></li> <li><hr class="dropdown-divider"></li> <li><a class="nav-link disabled">Configuración</a></li> <li><a class="nav-link disabled">Perfil</a></li> <li><hr class="dropdown-divider"></li> <li><a class="dropdown-item" href="app_contactos/cerrar"><i class="fa fa-window-close"></i> Cerrar sesión</a></li> </ul> </li> </ul> </div> </div> <!-- del container fluid de los elementos del menú --> </nav> <!-- del menú principal --> </div> <!-- del container fluid de la barra superior del menú --> <div> <p><span class="txt_negrita">Nombre:</span> <br> {{object.nombre}}</p> <p><span class="txt_negrita">Tlfno. móvil:</span> <br> {{object.telefono_movil}}</p> <p><span class="txt_negrita">Tlfno. fijo:</span> <br> {{object.telefono_fijo}}</p> <p><span class="txt_negrita">Dirección:</span> <br> {{object.direccion}}</p> <p><span class="txt_negrita">Empresa:</span> <br> {{object.empresa}}</p> <p><span class="txt_negrita">Foto:</span> <br> <img src="{% static 'fotos/'%}{{object.foto}}" alt="{{object.nombre}}" class="img-fluid"> </p> <p><span class="txt_negrita">Fecha alta:</span> <br> {{object.fecha_alta}}</p> <p><span class="txt_negrita">Fecha modificación:</span> <br> {{object.fecha_actualizacion}}</p> <!-- Botón para volver a la vista principal (Home) --> <a href="../" type="submit" class="btn btn-primary">Volver</a> </div> <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> </body> </html> |
- logout.html:
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>Agenda - Contactos - ProyectoA - Django</title> </head> <body> <p>Ha salido de la aplicación web Django Agenda (Contactos).</p> <p>Pulse <a href="{% url 'listar' %}">aquí</a> para volver a acceder.</p> </body> </html> |
Configuración adicional en settings.py para mostrar mensajes y para ruta de fotos de los contactos
Para mostrar mensajes al usuario de finalización existosa de las acciones de alta de contacto, modificación de contacto y eliminación de contacto:
En el fichero de views.py de la aplicación web «app_contactos» habremos añadido el import:
1 |
from django.contrib import messages |
Y en el fichero de configuración settings.py del proyecto proyectoa_agenda, en INSTALLED_APPS, tendremos la línea:
1 |
'django.contrib.messages', |
Y tendremos también en este fichero settings.py la línea:
1 2 |
# Activar 'CookieStorage' para enviar los mensajes de respuesta al Crear, Eliminar y Actualizar un registro MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' |
Por último, para establecer la ruta donde se guardarán las imágenes (fotos) asociadas a cada contacto de la agenda, en el fichero settings.py del proyecto agregaremos las líneas:
1 2 3 |
# Ruta para las imágenes de cada registro MEDIA_URL = '/app_contactos/' MEDIA_ROOT = os.path.join(BASE_DIR, 'app_contactos/static/fotos') |
Para que funcione el componente de creación de formularios django-widget-tweaks tendremos la línea siguiente en INSTALLED_APPS del fichero settings.py:
1 |
'widget_tweaks', |
Y si hemos optado por usar el paquete Bootstrap 5 para Django, también tendremos la línea siguiente INSTALLED_APPS del fichero settings.py:
1 |
'django_bootstrap5' |
Puesto que hemos hecho varias modificaciones en el fichero settings.py, a continuación mostramos su contenido completo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
from pathlib import Path import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'django-insecure-tkc=dyd5%tu7jh9' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_bootstrap5', 'widget_tweaks', 'app_contactos', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'proyectoa_agenda.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'proyectoa_agenda.wsgi.application' # Database DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'agenda', 'USER': 'root', 'PASSWORD': 'aaaaaaaa', 'HOST': '192.168.1.5', 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" } } } # Password validation AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) STATIC_URL = 'static/' # Ruta para las imágenes de cada registro MEDIA_URL = '/app_contactos/' MEDIA_ROOT = os.path.join(BASE_DIR, 'app_contactos/static/fotos') # Activar 'CookieStorage' para enviar los mensajes de respuesta al Crear, Eliminar y Actualizar un registro MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage' # Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' |
Probando la aplicación web Contactos en Django
Tras establecer todos los ficheros de configuración y creación de la aplicación, podremos realizar las pruebas oportunas de su correcto funcionamiento. Para ello iniciaremos el servidor web de Django, ejecutando, desde la carpeta del proyecto proyectoa_agenda, el siguiente comando:
python manage.py runserver
Si hay algún error de sintaxis o similar nos lo mostrará en la línea de comandos. En caso de que todo sea correcto, nos mostrará el mensaje:
Watching for file changes with StatReloader
Performing system checks…
System check identified no issues (0 silenced).
January 17, 2022 – 21:58:47
Django version 4.0.1, using settings ‘proyectoa_agenda.settings’
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
El servidor Django quedará iniciado a la escucha de peticiones por el puerto 8000 (el defecto si no se le indica otro).
Ahora, desde un navegador web, accedermos a la URL:
http://localhost:8000/app_contactos
Nos mostrará la web Contactos, con su menú, la tabla de contactos y el botón «Añadir contacto»:
La aplicación web es adaptativa y responsiva, si la ejecutáramos desde un dispositivo móvil, se adaptaría y mostraría el menú contraído:
Si pulsamos en el menú «Contactos» y en «Nuevo» (o bien directamente en el botón «Añadir contacto», nos abrirá el formulario para alta de un nuevo contacto. Rellaremos los datos, incluida la foto (pulsando en «Examinar» y eligiendo una foto del disco duro), y pulsaremos en «Guardar contacto»:
El contacto quedará guardado, nos lo indicará con el mensaje «Contacto añadido correctamente» y nos llevará a la página inicial, donde listará el contacto creado:
Podremos añadir todos los contactos que queramos y también podremos editar (modificar) un contacto pulsando en el botón «Editar» que tiene a su derecha y realizando la modificación correspondiente en el formulario de edición, pulsando en «Guardar»:
Podremos consultar todos los datos de un cotacto pulsando en «Ver». Nos mostrará la ventana de detalle del contacto, con todos sus datos, incluida la foto, sólo para consulta:
También podremos eliminar un contacto, pulsando en el botón «Eliminar» que tendrá a su derecha. Nos pedirá confirmación para eliminarlo (por seguridad):
Si pulsamos «Aceptar» en el mensaje de confirmación, la aplicación eliminará el contacto de la base de datos y volverá al listado (a la página inicial), actualizada sin ese contacto.
En el listado de contactos nos mostrará también la foto asignada a cada contacto (en miniatura):
Si pulsamos en el menú «ProyectoA» – «Cerrar sesión»:
Nos llevará a la página «logut.html»:
Descarga del proyecto completo Agenda y Contactos en Django y Python
A continuación dejamos el enlace a la descarga del proyecto completo desarrollado con Django y Python: