Mi primera experiencia con Django

Des de revisar el ejemplo oficial de Django, he notado que sigue una arquitectura MVC. Para quienes no estén familiarizados con este patrón, les recomiendo investigarlo antes de continuar.

La documentación oficial explica cómo crear un proyecto, incluyendo migraciones de base de datos. Utilicé Django 2.1 con Python 3.6 para implementar operaciones CRUD básicas.

Instalación de Django

Primero, instalamos Django:

pip install django

Creación del proyecto

Para crear un nuevo proyecto:

django-admin startproject mi_proyecto

La estructura de archivos generada se detalla en la documentación oficial.

Problemas encontrados

Configuración de la base de datos

Dado que mi servicio MySQL está en una máquina virtual local, la configuración en settings.py quedó así:


DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.mysql',
       'NAME': 'basededatos_django',
       'HOST': '192.168.175.130',
       'USER': 'root',
       'PASSWORD': '123456',
       'CHARSET': 'latin1',
   }
}
   

Definición del modelo

A continuación, definí mi modelo:


class Recursos(models.Model):
   id_recurso = models.IntegerField()
   enlace = models.CharField(max_length=255)
   id_categoria = models.SmallIntegerField()
   titulo = models.CharField(max_length=255)
   descripcion = models.TextField()

   def __str__(self):
       return self.titulo
   

Configuración de la aplicación

En apps.py:


class AdministracionConfig(AppConfig):
   name = 'administracion'
   

Y en settings.py, importé la aplicación:


INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'blog.apps.BlogConfig',
   'administracion.apps.AdministracionConfig',
]
   

Migraciones de base de datos

Ejecuté:

manage.py makemigrations administracion

Este comando genera el SQL para crear tablas basado en el modelo.

Luego ejecuté:

manage.py migrate

Problema con MySQLdb

Al ejecutar la migración, encontré que faltaba la librería MySQLdb. Intenté instalarla con:

pip install python-mysql

Obtuve varios errores. Después de revisar la documentación, descubrí que para Python 3.6 ya no existe este módulo. En su lugar, necesitamos instalar PyMySQL:

pip install pymysql

Luego, añadí esta línea a settings.py:


import os, pymysql
pymysql.install_as_MySQLdb()
   

Con esto, todo funcionó correctamente.

Vistas y plantillas

Plantilla index.html



<html lang="es">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Gestión de Recursos</title>
   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
   <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
   <style>
       .navegador-superior {
           background-color: aqua;
       }
       .navegador-izquierdo {
           background-color: #fff;
       }
       .contenido-principal {
           background-color: #f8f9fa;
       }
   </style>
</head>
<body>
<div class="container-fluid">
   <div class="row">
       <div class="col-md-12 navegador-superior">
           <p>Encabezado</p>
       </div>
       <div class="col-md-2 navegador-izquierdo">
           <p>Menú lateral</p>
       </div>
       <div class="col-md-10 contenido-principal">
           <div class="row">
               <button type="button" class="btn btn-primary btn-sm" data-toggle="modal" data-target="#modalAgregar">
                   AGREGAR
               </button>
           </div>
           
           <div class="modal fade" id="modalAgregar" tabindex="-1" role="dialog">
               <div class="modal-dialog">
                   <div class="modal-content">
                       <div class="modal-header">
                           <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
                           <h4 class="modal-title">Agregar Nuevo Recurso</h4>
                       </div>
                       <div class="modal-body">
                           <form id="formulario-recurso" class="form-horizontal" action="{% url 'administracion:agregarRecurso' %}" method="post" enctype="multipart/form-data">
                               <input type="hidden" name="id_recurso" value="1">
                               <input type="hidden" name="id_categoria" value="1">
                               {% csrf_token %}
                               <div class="form-group">
                                   <label class="col-sm-2 control-label">Título</label>
                                   <div class="col-sm-10">
                                       <input type="text" class="form-control" name="titulo">
                                   </div>
                               </div>
                               <div class="form-group">
                                   <label class="col-sm-2 control-label">Descripción</label>
                                   <div class="col-sm-10">
                                       <input type="text" class="form-control" name="descripcion">
                                   </div>
                               </div>
                               <div class="form-group">
                                   <label class="col-sm-2 control-label">Archivo</label>
                                   <div class="col-sm-10">
                                       <input type="file" class="form-control" name="archivo">
                                   </div>
                               </div>
                           </form>
                       </div>
                       <div class="modal-footer">
                           <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
                           <button type="button" class="btn btn-primary" id="enviar-formulario">Guardar</button>
                       </div>
                   </div>
               </div>
           </div>
           
           <div class="row">
               <table class="table table-hover">
                   <tr>
                       <th>ID Recurso</th>
                       <th>Título</th>
                       <th>ID Categoría</th>
                       <th>Descripción</th>
                       <th>Imagen</th>
                       <th>Acciones</th>
                   </tr>
                   {% for recurso in recursos %}
                   <tr>
                       <th>{{ recurso.id_recurso }}</th>
                       <th>{{ recurso.titulo }}</th>
                       <th>{{ recurso.id_categoria }}</th>
                       <th>{{ recurso.descripcion }}</th>
                       <th><img src="{{ recurso.enlace }}" style="width: 50px;" alt=""></th>
                       <th>
                           <button class="btn btn-success editar" data-id="{{ recurso.id }}" data-titulo="{{ recurso.titulo }}" data-descripcion="{{ recurso.descripcion }}">Editar</button>
                           <button class="btn btn-danger eliminar" data-id="{{ recurso.id }}">Eliminar</button>
                       </th>
                   </tr>
                   {% endfor %}
               </table>
           </div>
       </div>
   </div>
</div>
</body>
<script>
   $('#enviar-formulario').click(function () {
       $('#formulario-recurso').submit();
   })
   
   $('.eliminar').click(function () {
       var boton = $(this)
       var id_recurso = boton.attr('data-id')
       var token_csrf = jQuery("[name=csrfmiddlewaretoken]").val()
       
       $.ajax({
           beforeSend: function (xhr, settings) {
               xhr.setRequestHeader("X-CSRFToken", token_csrf);
           },
           url: "{% url 'administracion:eliminarRecurso' %}",
           data: {id: id_recurso},
           type: 'post',
           dataType: 'json',
           success: function (respuesta) {
               if (respuesta.codigo === 1){
                   alert('Eliminación exitosa')
               }else{
                   alert('Error al eliminar')
               }
               location.reload()
           }
       })
   })
</script>
</html>
   

Configuración de URLs


from django.urls import path
from . import vistas

app_name = 'administracion'
urlpatterns = [
   path('', vistas.inicio, name='inicio'),
   path('recursos/listar', vistas.listarRecursos, name='listarRecursos'),
   path('recursos/agregar', vistas.agregarRecurso, name='agregarRecurso'),
   path('recursos/eliminar', vistas.eliminarRecurso, name='eliminarRecurso')
]
   

Vistas


from django.shortcuts import render
from django.http import HttpResponse, Http404, HttpResponseRedirect, JsonResponse
from django.urls import reverse
from .models import Recursos
import os, uuid

def inicio(request):
   return HttpResponse('Hola mundo con Django')

def listarRecursos(request):
   recursos = Recursos.objects.all()
   contexto = {"recursos": recursos}
   return render(request, 'recursos/index.html', contexto)

def agregarRecurso(request):
   id_recurso = request.POST.get('id_recurso', None)
   id_categoria = request.POST.get('id_categoria', None)
   titulo = request.POST.get('titulo', None)
   descripcion = request.POST.get('descripcion', None)
   
   enlace = subirArchivo(request.FILES['archivo'])
   
   nuevo_recurso = Recursos(
       id_recurso=id_recurso, 
       id_categoria=id_categoria, 
       titulo=titulo, 
       descripcion=descripcion, 
       enlace=enlace
   )
   nuevo_recurso.save()
   
   if nuevo_recurso.id is None:
       raise Http404
   
   return HttpResponseRedirect(reverse('administracion:listarRecursos'))

def eliminarRecurso(request):
   id_recurso = request.POST.get('id', None)
   if id_recurso is None:
       return JsonResponse({'codigo': 0})
   
   try:
       recurso = Recursos.objects.get(pk=id_recurso)
       recurso.delete()
   except Exception as e:
       return JsonResponse({'codigo': 0})
   else:
       return JsonResponse({'codigo': 1})

def subirArchivo(archivo):
   ruta = '/recursos/'
   enlace_base = 'http://127.0.0.1:81/'
   
   if not os.path.exists(ruta):
       os.mkdir(ruta)
   
   nombre_archivo = str(uuid.uuid1()) + archivo.name
   ruta_guardado = ruta + nombre_archivo
   
   with open(ruta_guardado, 'wb+') as archivo_guardado:
       for fragmento in archivo.chunks():
           archivo_guardado.write(fragmento)
   
   return enlace_base + nombre_archivo
   

Problemas con codificación de caracteres

Al no especificar el conjunto de caracteres de la base de datos, encontré el error:

1366, "Incorrect string value: '\\xE6\\xB5\\x8B\\xE8\\xAF\\x95' for column 'titulo' at row 1"

Al revisar la configuración de MySQL:

mysql> show variables like '%char%';

Descubrí que muchos valores usaban latin1 por defecto. Mi tabla también estaba en Latin1. Busqué en la documentación de Django cómo configurar el charset a nivel de modelo, pero no encontré una solución directa.

Finalmente, opté por modificar la configuración de la base de datos. Para quienes enfrenten el mismo problema, pueden encontrar una guía aquí: https://www.cnblogs.com/HondaHsu/p/3640180.html

Después de estos cambios, la inserción de caracteres especiales funcionó correctamente.

Impresiones finales

Hasta este punto, siento que Django es un framwork completo pero algo pesado. Su curva de aprendizaje inicial parece considerable. Por esta razón, mi próximo proyecto será una exploración de Flask, un framework más ligero.

Etiquetas: Django Python MySQL MVC Desarrollo Web

Publicado el 6-14 19:57