Este artículo detalla la implementación de un componente denominado Stark, diseñado para emular la funcionalidad del sistema de administración integrado de Django. Su arquitectura se estructura en cinco módulos fundamentales.
Diseño de URLs y Modelos de Datos
El punto de partida es definir los modelos que representarán las entidades del sistema. A continuación, se muestra un ejemplo con autores, editoriales y libros.
from django.db import models
class Autor(models.Model):
identificador = models.AutoField(primary_key=True)
nombre = models.CharField(max_length=32)
edad = models.IntegerField()
detalle = models.OneToOneField(to="DetalleAutor", on_delete=models.CASCADE)
def __str__(self):
return self.nombre
class DetalleAutor(models.Model):
identificador = models.AutoField(primary_key=True)
fecha_nacimiento = models.DateField()
telefono = models.BigIntegerField()
direccion = models.CharField(max_length=64)
def __str__(self):
return self.direccion
class Editorial(models.Model):
identificador = models.AutoField(primary_key=True)
nombre = models.CharField(max_length=32)
ciudad = models.CharField(max_length=32)
correo = models.EmailField()
def __str__(self):
return self.nombre
class Libro(models.Model):
identificador = models.AutoField(primary_key=True)
titulo = models.CharField(max_length=32)
fecha_publicacion = models.DateField()
precio = models.DecimalField(max_digits=5, decimal_places=2)
editorial = models.ForeignKey(to="Editorial", to_field="identificador", on_delete=models.CASCADE)
autores = models.ManyToManyField(to='Autor')
def __str__(self):
return self.titulo
En el archivo admin.py estándar de Django, estos modelos se registrarían. Nuestro componente Stark replicará este proceso de registro pero con una lógica personalizada para la generación de URLs y vistas.
Configuración y Registro Automático
El sistema debe escanear automáticamente los archivos de configuración específicos. Esto se logra mediante la configuración de la aplicación Django.
En apps.py dentro de la aplicación del componente (ej. whqstark):
from django.apps import AppConfig
from django.utils.module_loading import autodiscover_modules
class WhqstarkConfig(AppConfig):
name = 'whqstark'
def ready(self):
autodiscover_modules('stark')
Se debe añadir esta aplicación al array INSTALLED_APPS en el archivo settings.py del proyecto principal.
Implementación del Núcleo (CRUD)
La funcionalidad central se divide en la definición de una clase base ModelAdminStark y un sitio de registro singleton SitioAdmin.
Definición de la Clase Base para Configuración del Modelo
from django.conf.urls import url
from django.utils.safestring import mark_safe
from django.shortcuts import HttpResponse, render, redirect
from django.urls import reverse
class ModelAdminStark(object):
mostrar_lista = ["__str__", ]
enlaces_lista = []
def __init__(self, modelo, sitio):
self.modelo = modelo
self.sitio = sitio
# Métodos de vista
def agregar(self, solicitud):
return HttpResponse('Función Agregar')
def eliminar(self, solicitud, id_registro):
return HttpResponse('Función Eliminar')
def modificar(self, solicitud, id_registro):
return HttpResponse('Función Modificar')
# Funciones auxiliares para acciones de tabla
def checkbox(self, instancia):
return mark_safe('<input type="checkbox"></input>')
def accion_editar(self, instancia=None, es_encabezado=False):
if es_encabezado:
return 'Acción'
url_edicion = self.obtener_url_edicion(instancia)
return mark_safe(f'<a href="{url_edicion}">Editar</a>')
def accion_eliminar(self, instancia=None, es_encabezado=False):
if es_encabezado:
return 'Acción'
url_eliminacion = self.obtener_url_eliminacion(instancia)
return mark_safe(f'<a href="{url_eliminacion}">Borrar</a>')
def construir_columnas_tabla(self):
columnas = []
columnas.append(ModelAdminStark.checkbox)
columnas.extend(self.mostrar_lista)
if not self.enlaces_lista:
columnas.append(ModelAdminStark.accion_editar)
columnas.append(ModelAdminStark.accion_eliminar)
return columnas
# Generación de URLs
def obtener_url_agregar(self):
nombre_app = self.modelo._meta.app_label
nombre_modelo = self.modelo._meta.model_name
return reverse(f'{nombre_app}_{nombre_modelo}_agregar')
def obtener_url_eliminacion(self, instancia):
nombre_app = self.modelo._meta.app_label
nombre_modelo = self.modelo._meta.model_name
return reverse(f'{nombre_app}_{nombre_modelo}_eliminar', args=(instancia.pk,))
def obtener_url_edicion(self, instancia):
nombre_app = self.modelo._meta.app_label
nombre_modelo = self.modelo._meta.model_name
return reverse(f'{nombre_app}_{nombre_modelo}_modificar', args=(instancia.pk,))
def obtener_url_listado(self):
nombre_app = self.modelo._meta.app_label
nombre_modelo = self.modelo._meta.model_name
return reverse(f'{nombre_app}_{nombre_modelo}_listado')
def listar(self, solicitud):
listado_datos = self.modelo.objects.all()
gestor_listado = GestorListado(self, listado_datos)
contexto = {
'gestor': gestor_listado,
}
return render(solicitud, 'list_view.html', contexto)
def obtener_patrones_url(self):
lista_patrones = []
nombre_app = self.modelo._meta.app_label
nombre_modelo = self.modelo._meta.model_name
lista_patrones.append(url(r'agregar/', self.agregar, name=f'{nombre_app}_{nombre_modelo}_agregar'))
lista_patrones.append(url(r'(\d+)/eliminar/', self.eliminar, name=f'{nombre_app}_{nombre_modelo}_eliminar'))
lista_patrones.append(url(r'(\d+)/modificar/', self.modificar, name=f'{nombre_app}_{nombre_modelo}_modificar'))
lista_patrones.append(url(r'listado/', self.listar, name=f'{nombre_app}_{nombre_modelo}_listado'))
return lista_patrones
@property
def urls(self):
return (self.obtener_patrones_url(), None, None)
# Clase auxiliar para encapsular la lógica del listado
class GestorListado:
def __init__(self, configuracion, datos):
self.configuracion = configuracion
self.datos = datos
def obtener_encabezados(self):
encabezados = []
for columna in self.configuracion.construir_columnas_tabla():
if callable(columna):
valor = columna(self.configuracion, es_encabezado=True)
encabezados.append(valor)
else:
if columna == "__str__":
encabezados.append(self.configuracion.modelo._meta.model_name.upper())
else:
campo = self.configuracion.modelo._meta.get_field(columna)
encabezados.append(campo.verbose_name)
return encabezados
def obtener_cuerpo(self):
filas = []
for registro in self.datos:
fila = []
for columna in self.configuracion.construir_columnas_tabla():
if callable(columna):
valor = columna(self.configuracion, registro)
else:
valor = getattr(registro, columna)
fila.append(valor)
filas.append(fila)
return filas
# Singleton para el sitio de administración
class SitioAdmin(object):
def __init__(self):
self._registros = {}
def registrar(self, modelo, clase_config=None):
if not clase_config:
clase_config = ModelAdminStark
self._registros[modelo] = clase_config(modelo, self)
def obtener_urls(self):
lista_urls = []
for modelo, config_modelo in self._registros.items():
nombre_app = modelo._meta.app_label
nombre_modelo = modelo._meta.model_name
lista_urls.append(url(f'{nombre_app}/{nombre_modelo}', config_modelo.urls))
return lista_urls
@property
def urls(self):
return (self.obtener_urls(), None, None)
sitio = SitioAdmin()
Configuración en el Proyecto Principal y Registro de Modelos
En el archivo urls.py raíz del proyecto, se incluyen las URLs generadas por el singleton sitio.
from django.conf.urls import url
from whqstark.service.stark import sitio
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^stark/', sitio.urls),
]
Posteriormente, en un archivo stark.py dentro de cada aplicación (escaneado automáticamente), se registran los modelos con configuraciones opcionales.
from whqstark.service.stark import sitio, ModelAdminStark
from app01.models import Libro, Autor
class ConfigLibro(ModelAdminStark):
mostrar_lista = ['titulo', 'precio', 'editorial']
enlaces_lista = ['titulo']
sitio.registrar(Libro, ConfigLibro)
sitio.registrar(Autor)
Plantilla para la Vista de Listado
La plantilla list_view.html consume los datos preparados por GestorListado.
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Listado de Registros</title>
<!-- Enlaces a Bootstrap y otros estáticos -->
</head>
<body>
<div class="container">
<a href="agregar/" class="btn btn-primary">Agregar Nuevo</a>
<table class="table table-bordered">
<thead>
<tr>
{% for encabezado in gestor.obtener_encabezados %}
<th>{{ encabezado }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for fila in gestor.obtener_cuerpo %}
<tr>
{% for celda in fila %}
<td>{{ celda }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>
Funcionalidades Extendidas
El componente base puede ser extendido para soportar características avanzadas como la filtrado (filter), la paginación, la búsqueda, y acciones en lote. La lógica de filtrado se integraría dentro del método listar y en el GestorListado para aplicar consultas al queryset inicial basadas en parámetros de la URL o del formulario. La funcionalidad de POP (pop-up) se implementaría para permitir la creación rápida de objetos relacionados (como editoriales al crear un libro) en vantanas modales.