📚 Unidad ISS-13 · Django Admin — capa 🧠 APRENDER
🛠 Construir este bloque → · ✅ Condiciones de cierre (GATE) · 📝 Evaluación
Capa Página Para qué 🧠 Aprender esta página comprender, explicar y relacionar 🛠 Construir Django Admin ejecutar, programar y verificar ✅ GATE Cierre de la unidad condición para pasar al bloque siguiente
Mapa de correspondencias. Cada fila enlaza el mismo tema en las dos capas; los enlaces apuntan a secciones reales del material.
| Tema | 🧠 Aprender (esta página) | 🛠 Construir (ISS técnico) |
|---|---|---|
| Objetivo | A. Objetivos y Requisitos de ISS-13 | Objetivo |
| Recorrido | C. Estructura de Archivos y Paquetes | Construcción |
| Cierre | I. Criterios de Aceptación (AC-13-01 al AC-13-05) | Criterios de aceptación · GATE |
| Evaluación | K. Cuestionario de Preguntas de Defensa Oral Técnica | GATE |
🖥 Presentación del ISS
Presentación de la unidad. Diapositivas de ISS-13 — Django Admin (8 diapositivas). Se visualiza aquí, dentro del sitio.
Infografía
Mapa conceptual
- Autenticación y Superusuario
- Comando CLI: python manage.py createsuperuser
- Validadores de contraseñas: rechazan claves comunes o numéricas
- StoreUserManager: create_superuser fuerza status ACTIVE
- Acceso al panel: requiere is_active=True derivado del estado
- Gestión de Clientes
- Archivo afectado: apps/client/admin.py
- Clase de registro: ClientAdmin
- Columnas list_display: name, email, phone, address, status, created_at
- Filtros y búsqueda: list_filter con status y search_fields
- Edición directa list_editable: status
- Agrupación fieldsets: formulario sin campos de contraseña
- Catálogo y Autocompletado
- Archivo afectado: apps/product/admin.py
- ProductTypeAdmin: search_fields en name y description
- ProductAdmin list_editable: price, stock y status
- Buscador autocomplete_fields: product_type
- Optimización get_queryset: select_related product_type
- Ventas e Inlines Inmutables
- Archivo afectado: apps/sale/admin.py
- ProductSaleInline: TabularInline de solo lectura
- Configuración inline: extra=0 y can_delete=False
- Navegación show_change_link: True para la ficha
- Bloqueo de creación: has_add_permission=False
- Protección de Integridad y Reglas de Negocio
- SaleAdmin list_display: id, client, sale_date, subtotals y total
- Jerarquía temporal date_hierarchy: sale_date
- Optimización SaleAdmin: select_related client
- Inmutabilidad operativa: has_add_permission=False
- Protección contra borrado: has_delete_permission=False
- Preservación transaccional: evita alteración fuera de la API
- Verificación y GATE
- Verificación: python manage.py check
- Evidencia EVI-13-01: System check identified no issues
- Criterios AC-13-01 al AC-13-05: Estado PASS
Guía de Estudio Profunda: Configuración e Inmutabilidad de Django Admin en StoreLab (ISS-13)
A. Objetivos y Requisitos de ISS-13
Objetivo Principal
El objetivo de la unidad ISS-13 es exponer y configurar las cinco entidades del dominio de negocio (Client, ProductType, Product, Sale y ProductSale) en la interfaz de administración nativa de Django (/admin/). La configuración debe permitir la exploración eficiente mediante filtros, cuadros de búsqueda, ordenamiento y listas editables para los catálogos y clientes, garantizando de manera estricta la inmutabilidad de las ventas (las transacciones comerciales pueden inspeccionarse y auditarse, pero no pueden crearse, modificarse ni eliminarse mediante la interfaz del Admin).
Requisitos Previos y Entorno
- ISS-12 Superado: La lógica de negocio, consulta y anulación de ventas vía API ya debe estar implementada y verificada.
- Registro de Aplicación en Configuración: La aplicación
django.contrib.admindebe estar declarada dentro deINSTALLED_APPSen el paquete de configuración principal. En el proyecto StoreLab, este paquete reside enconfig/settings/__init__.py. - URLConf Raíz: La ruta para el panel administrativo (
path("admin/", admin.site.urls)) debe estar expuesta enconfig/urls.py, tal como fue establecido desde la unidad ISS-00.
B. Conceptos Esenciales y Vocabulario Técnico
ModelAdmin: Clase fundamental del módulodjango.contrib.adminque encapsula toda la representación visual, comportamiento, permisos y lógica de edición de un modelo dentro del panel administrativo.@admin.register: Decorador sintáctico que vincula directamente una claseModelAdmincon un modelo del ORM de Django, reemplazando la invocación explícita deadmin.site.register(Model, ModelAdmin).list_display: Tupla o lista de nombres de campos que define cuáles columnas se visualizarán en la tabla de listado principal (change list) del modelo.list_filter: Tupla o lista de campos que genera una barra lateral con filtros interactivos para segmentar los registros del listado (por ejemplo, por estado o por fechas).search_fields: Tupla o lista de campos que habilita un cuadro de búsqueda de texto global. Admite la notación de doble guión bajo (__) para navegar hacia campos de modelos relacionados a través de llaves foráneas.list_editable: Tupla o lista de campos que permite la modificación rápida e in situ directamente desde la vista de lista, sin necesidad de ingresar al formulario de detalle.ordering: Tupla que especifica el criterio de ordenamiento por defecto en SQL (expresado en la cláusulaORDER BY) al presentar los registros en el panel.readonly_fields: Tupla o lista de campos que se muestran en el formulario de detalle únicamente en modo lectura, deshabilitando cualquier modificación interactiva.fieldsets: Estructura de datos declarativa (lista de tuplas) utilizada para agrupar y diagramar orgánicamente los campos dentro del formulario de detalle, permitiendo usar títulos, secciones y clases CSS comocollapse.autocomplete_fields: Tupla que sustituye los desplegables de llaves foráneas (<select>) por un control de búsqueda dinámico con AJAX. Exige que el modelo relacionado tenga definidosearch_fieldsen su propioModelAdmin.TabularInline: Componente que permite renderizar un modelo hijo (relacionado por una FK) en forma de tabla incrustada dentro del formulario de detalle del modelo padre.has_add_permission: Método gancho (hook) deModelAdminoInlineModelAdminque determina si el usuario autenticado tiene permiso para crear nuevos registros. RetornarFalsedeshabilita los botones de creación.has_delete_permission: Método gancho que determina si se permite la eliminación de registros. RetornarFalseremueve el botón de borrado y las acciones masivas de eliminación.date_hierarchy: Cadena que especifica un campo de fecha o fecha-hora para generar un componente navegable jerárquico (Año → Mes → Día) sobre el listado.select_related: Método delQuerySetque optimiza las consultas SQL mediante una cláusulaJOIN, trayendo los datos de relaciones 1→1 o N→1 en una sola sentencia e impidiendo el problema de rendimiento N+1.
C. Estructura de Archivos y Paquetes
La implementación de ISS-13 requiere la reescritura completa y estructurada de los archivos de administración dentro de cada aplicación de negocio:
apps/
├── client/
│ └── admin.py # Configuración de ClientAdmin
├── product/
│ └── admin.py # Configuración de ProductTypeAdmin y ProductAdmin
└── sale/
└── admin.py # Configuración de ProductSaleInline, SaleAdmin y ProductSaleAdmin
D. Explicación por Bloques Semánticos: ClientAdmin (apps/client/admin.py)
Código Fuente
from django.contrib import admin
from apps.client.models import Client
@admin.register(Client)
class ClientAdmin(admin.ModelAdmin):
list_display = ("name", "email", "phone", "address", "status", "created_at")
list_filter = ("status",)
search_fields = ("name", "email", "phone", "address")
list_editable = ("status",)
ordering = ("name", "email")
readonly_fields = ("created_at", "updated_at")
fieldsets = (
("Información del cliente", {"fields": ("name", "email", "phone", "address")}),
("Estado", {"fields": ("status",)}),
(
"Auditoría",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
Explicación por Bloques
- Registro con
@admin.register(Client): Asocia la claseClientAdmincon la entidadClientsin necesidad de llamadas imperativas al registro global. - Columnas Visibles (
list_display): Presenta la información básica del comprador, incluyendo su estado (status) y fecha de creación (created_at). - Filtros y Búsqueda (
list_filterysearch_fields): Habilita la barra lateral para filtrar por estado (active/inactive) y la búsqueda por concordancia de texto en nombre, correo, teléfono y dirección. - Activación/Inactivación Rápida (
list_editable = ("status",)): - Permite alternar el estado del cliente directamente desde el listado.
- Regla Técnica Restrictiva: El primer campo definido en
list_display(name) no puede incluirse enlist_editable. Django requiere obligatoriamente que la primera columna sea un enlace hipertexto no editable que dirija al formulario de edición detallado del registro. - Agrupación Semántica (
fieldsets): - "Información del cliente": Agrupa los datos personales y de contacto.
- "Estado": Permite controlar el campo
status. - "Auditoría": Agrupa
created_atyupdated_at. Utiliza el atributo"classes": ("collapse",)para que aparezca colapsado por defecto, reduciendo la carga visual. - Ausencia de Contraseñas: El modelo
Clientno tiene ni debe tener un campo de contraseña. En la arquitectura de StoreLab,Clientrepresenta únicamente la entidad comercial compradora. La autenticación y las credenciales de acceso están centralizadas exclusivamente en la entidadsecurity.User.
E. Explicación por Bloques Semánticos: Catálogo de Productos (apps/product/admin.py)
Código Fuente
from django.contrib import admin
from apps.product.models import Product, ProductType
@admin.register(ProductType)
class ProductTypeAdmin(admin.ModelAdmin):
list_display = ("name", "status", "created_at")
list_filter = ("status",)
search_fields = ("name", "description")
list_editable = ("status",)
ordering = ("name",)
readonly_fields = ("created_at", "updated_at")
fieldsets = (
(None, {"fields": ("name", "description", "status")}),
(
"Auditoría",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("name", "product_type", "price", "stock", "status")
list_filter = ("status", "product_type")
search_fields = ("name", "description")
list_editable = ("price", "stock", "status")
ordering = ("name",)
autocomplete_fields = ("product_type",)
readonly_fields = ("created_at", "updated_at")
fieldsets = (
("Información", {"fields": ("name", "description", "product_type")}),
("Precio y stock", {"fields": ("price", "stock")}),
("Estado", {"fields": ("status",)}),
(
"Auditoría",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
def get_queryset(self, request):
return super().get_queryset(request).select_related("product_type")
Explicación por Bloques
- Requisito Técnico de Autocompletado (
search_fieldsenProductTypeAdmin): - En
ProductTypeAdmin, se declara explícitamentesearch_fields = ("name", "description"). - Invariante: Esto no es opcional. Para que un modelo hijo (
Product) pueda usarautocomplete_fields = ("product_type",), el modelo padre referenciado (ProductType) debe poseer obligatoriamentesearch_fieldsen su respectivoModelAdmin. De lo contrario, la ejecución depython manage.py checkarroja un error crítico de verificación del sistema (SystemCheckError). - Buscador Dinámico (
autocomplete_fieldsenProductAdmin): - Reemplaza el elemento desplegable tradicional por un componente interactivo de búsqueda en tiempo real vía AJAX, previniendo el colapso del navegador cuando existen miles de tipos de productos.
- Gestión Directa del Catálogo (
list_editable): - En
ProductAdmin, se definelist_editable = ("price", "stock", "status"). Permite actualizar precios, existencias e inhabilitar productos directamente desde el listado comercial. - Optimización de Consultas SQL (
get_querysetyselect_related): - Sobrescribir
get_querysetincluyendo.select_related("product_type")fuerza a Django a ejecutar unINNER JOINentre la tablaproductsyproduct_types. - Prevención de Consultas N+1: Sin este ajuste, la renderización de un listado de 50 productos realizaría 1 consulta para cargar los productos y 50 consultas individuales adicionales para obtener el nombre del tipo de producto asociado. Con
select_related, todo el listado se obtiene en exactamente 1 consulta SQL.
F. Explicación por Bloques Semánticos: Ventas e Inmutabilidad (apps/sale/admin.py)
Código Fuente
from django.contrib import admin
from apps.sale.models import ProductSale, Sale
class ProductSaleInline(admin.TabularInline):
model = ProductSale
extra = 0
can_delete = False
show_change_link = True
fields = ("product", "quantity", "unit_price", "line_total", "status")
readonly_fields = ("product", "quantity", "unit_price", "line_total", "status")
def has_add_permission(self, request, obj=None):
return False
@admin.register(Sale)
class SaleAdmin(admin.ModelAdmin):
list_display = (
"id",
"client",
"sale_date",
"subtotal",
"tax",
"discounts",
"total",
"status",
)
list_filter = ("status", "sale_date")
search_fields = ("client__name", "client__email")
ordering = ("-sale_date",)
date_hierarchy = "sale_date"
readonly_fields = (
"client",
"sale_date",
"subtotal",
"tax",
"discounts",
"total",
"status",
"created_at",
"updated_at",
)
fieldsets = (
("Información de la venta", {"fields": ("client", "status")}),
(
"Totales",
{
"fields": ("subtotal", "tax", "discounts", "total"),
"classes": ("collapse",),
},
),
("Fecha", {"fields": ("sale_date",), "classes": ("collapse",)}),
(
"Auditoría",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
inlines = (ProductSaleInline,)
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_queryset(self, request):
return super().get_queryset(request).select_related("client")
@admin.register(ProductSale)
class ProductSaleAdmin(admin.ModelAdmin):
list_display = ("sale", "product", "quantity", "unit_price", "line_total", "status")
list_filter = ("status", "sale__sale_date", "product__status")
search_fields = ("sale__client__name", "product__name")
ordering = ("-sale__sale_date", "id")
readonly_fields = (
"sale",
"product",
"quantity",
"unit_price",
"line_total",
"status",
"created_at",
"updated_at",
)
fieldsets = (
("Línea", {"fields": ("sale", "product", "quantity", "unit_price", "line_total")}),
("Estado", {"fields": ("status",)}),
(
"Auditoría",
{"fields": ("created_at", "updated_at"), "classes": ("collapse",)},
),
)
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_queryset(self, request):
return super().get_queryset(request).select_related(
"sale", "sale__client", "product"
)
Explicación por Bloques y Protección Transaccional
1. Visualización Incrustada (ProductSaleInline)
- Hereda de
admin.TabularInlinepara presentar las líneas de venta como una tabla dentro de la vista de la venta cabecera. extra = 0elimina filas vacías por defecto.can_delete = Falseremueve la casilla de verificación para eliminar líneas.show_change_link = Truehabilita un enlace para inspeccionar la ficha individual del detalle.has_add_permission(self, request, obj=None)retornaFalsepara bloquear cualquier intento de adjuntar ítems manualmente.
2. Navegación e Inmutabilidad (SaleAdmin y ProductSaleAdmin)
- Navegación Temporal (
date_hierarchy = "sale_date"): Genera accesos directos por año, mes y día en la parte superior del listado. - Búsqueda por Relaciones:
search_fields = ("client__name", "client__email")realiza la búsqueda cruzando la llave foránea mediante el ORM. - Lectura Total (
readonly_fields): Todos los campos financieros (subtotal,tax,discounts,total), de cliente, fechas y estados se marcan como de solo lectura. - Bloqueo de Modificación y Destrucción:
has_add_permissionretornaFalse.has_delete_permissionretornaFalse.
3. Justificación de la Inmutabilidad
Permitir la creación, edición o borrado de ventas desde el panel administrativo rompería tres invariantes críticas del dominio:
1. Fotografía del Precio Histórico: Al registrar una venta vía API (SaleQuerySet.register), el sistema congela el precio del producto en ese instante (unit_price). La edición en el Admin podría corromper la contabilidad.
2. Invariante Transaccional de Inventario: El alta de una venta requiere descontar stock de forma atómica (select_for_update). Crear una venta desde el Admin no ejecutaría esta lógica y generaría inconsistencias en el inventario.
3. Reabastecimiento en Anulación: Borrar una venta desde el Admin mediante una sentencia DELETE eliminaría el registro de la base de datos sin ejecutar la devolución de inventario. La anulación legítima se realiza exclusivamente invocando el método de negocio sale.void(), el cual devuelve las existencias y marca el registro como inactive.
G. Creación del Superusuario y Modelo de Seguridad
Invocación CLI
Para habilitar el acceso al panel administrativo, se debe invocar la siguiente tarea administrativa en la terminal con el entorno virtual activo:
La CLI solicitará interactivamente el nombre de usuario, correo electrónico y contraseña (la cual debe cumplir con los validadores configurados en AUTH_PASSWORD_VALIDATORS).
Lógica Interna de Autenticación
En ISS-03 se definió la clase StoreUserManager, donde el método create_superuser fuerza los valores por defecto:
def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("status", RecordStatus.ACTIVE)
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
return super().create_superuser(username, email, password, **extra_fields)
Al guardarse la instancia de User, su método save() ejecuta:
Esto garantiza que el superusuario nazca con status = "active" e is_active = True. Esta sincronización es indispensable porque el subsistema de administración de Django (django.contrib.admin) y su middleware de sesión (AuthenticationMiddleware) verifican estrictamente is_staff e is_active para permitir el inicio de sesión basado en cookies y sesiones en /admin/.
H. Análisis Comparativo de Arquitectura y Errores Frecuentes
Arquitectura Admin vs. API REST
| Criterio | Django Admin (/admin/) |
API REST (/api/) |
|---|---|---|
| Mecanismo de Autenticación | Basado en Sesiones HTTP (django.contrib.sessions) y Cookies. |
Sin estado (Stateless), JWT mediante StoreLabJWTAuthentication (o OPEN en Fase I). |
| Protección contra Ataques | Tokens CSRF pasados por Cookies/Formularios (CsrfViewMiddleware). |
Cabeceras HTTP Authorization: Bearer <token>. |
| Formato de Intercambio | Plantillas HTML/Formularios renderizados en el servidor. | Cargas de pago en formato JSON con sintaxis camelCase. |
| Público Objetivo | Operadores internos, auditores y administradores del sistema. | Aplicaciones de cliente (Web SPA, Mobile), servicios externos e integraciones. |
| Comportamiento con Ventas | Inspección en modo lectura (Inmutable). | Procesamiento transaccional de alta (register) y anulación (void). |
Errores Frecuentes de Configuración
- Omitir
search_fieldsen el Modelo Referenciado: - Causa: Intentar utilizar
autocomplete_fields = ("product_type",)enProductAdmincuandoProductTypeAdminno ha declaradosearch_fields. - Consecuencia: Django detiene la ejecución arrojando un error
SystemCheckErroral ejecutarpython manage.py check. - Asignar
list_editableen el Primer Campo delist_display: - Causa: Incluir el primer elemento de
list_display(por ejemplo,nameenClientAdmin) dentro de la tuplalist_editable. - Consecuencia: Incompatibilidad de diseño en Django, ya que la primera columna debe ser el enlace primario al formulario de modificación. El chequeo del sistema falla impidiendo el arranque.
- Intentar Crear o Eliminar Ventas desde el Admin:
- Causa: No sobrescribir
has_add_permissionnihas_delete_permissionretornandoFalseenSaleAdminyProductSaleInline. - Consecuencia: Los usuarios podrían eludir el gestor transaccional
SaleQuerySet.registery la rutina de devolución de inventariosale.void(), provocando descuadres irrecoverables en el stock y la pérdida de la trazabilidad de los precios históricos.
I. Criterios de Aceptación (AC-13-01 al AC-13-05)
- AC-13-01: Las cinco entidades del negocio (
Client,ProductType,Product,SaleyProductSale) deben estar debidamente registradas en el Admin, exponiendo en sus listados las columnas (list_display), filtros (list_filter) y controles de búsqueda (search_fields) requeridos. - AC-13-02: El formulario de detalle de
Clientdebe organizar sus campos mediante grupos visuales (fieldsets) y no debe incluir bajo ningún concepto campos de contraseña. - AC-13-03: La vista de
Productdebe listar explícitamente el precio (price) y el stock (stock), omitiendo campos inexistentes en el dominio como marca, cantidad o stock mínimo. - AC-13-04: Las ventas (
Sale) y sus detalles (ProductSale) no deben permitir la creación o eliminación desde la interfaz del Admin, e inline no debe ofrecer filas editables o de creación. - AC-13-05: El comando de diagnóstico
python manage.py checkdebe ejecutarse limpiamente sin reportar advertencias ni errores en las configuraciones de autocompletado o listas editables.
J. Verificación, Evidencias y Tabla GATE
Comando de Verificación
La validación de la configuración de Django Admin se efectúa ejecutando:
Evidencias Documentadas
- EVI-13-01: Mensaje en terminal
System check identified no issues (0 silenced).confirmando que las dependencias de autocompletado, las llaves foráneas, losfieldsetsy la sintaxis de losModelAdminson conformes a las reglas del framework.
Tabla GATE
| AC | Verificación | Evidencia | Resultado |
|---|---|---|---|
| AC-13-01 | Inspección de apps/client/admin.py, apps/product/admin.py y apps/sale/admin.py |
Registros @admin.register confirmados para los 5 modelos |
PASS |
| AC-13-02 | Revisión de fieldsets en ClientAdmin |
Grupos "Información", "Estado" y "Auditoría" sin campo de clave | PASS |
| AC-13-03 | Revisión de columnas en ProductAdmin |
Columnas price y stock presentes en list_display |
PASS |
| AC-13-04 | Evaluación de permisos en SaleAdmin y ProductSaleInline |
has_add_permission y has_delete_permission retornan False |
PASS |
| AC-13-05 | Ejecución de python manage.py check |
EVI-13-01: Cero errores identificados | PASS |
K. Cuestionario de Preguntas de Defensa Oral Técnica
Pregunta 1: ¿Por qué es técnicamente obligatorio declarar search_fields en ProductTypeAdmin para que funcione autocomplete_fields en ProductAdmin?
Respuesta Pedagógica:
El componente autocomplete_fields utiliza una consulta asíncrona (AJAX) para buscar registros del modelo relacionado en tiempo real a medida que el usuario escribe en el formulario. Para poder procesar el texto ingresado por el usuario, el servidor necesita saber sobre cuáles columnas de la base de datos debe aplicar la cláusula SQL WHERE ... LIKE %texto%. Esa información la provee la propiedad search_fields del ModelAdmin del modelo referenciado (ProductTypeAdmin). Si no está definida, Django no puede construir la consulta SQL de búsqueda y lanza un SystemCheckError preventivo durante la verificación del proyecto.
Pregunta 2: ¿Qué ocurre si un desarrollador incluye el campo name en list_editable dentro de ClientAdmin si name es el primer campo de list_display?
Respuesta Pedagógica:
Django Admin requiere por arquitectura que al menos una columna de la vista de lista contenga un hipervínculo para ingresar al formulario de edición detallado (change form). Por defecto, este enlace se le asigna a la primera columna definida en list_display. Si se coloca esa misma primera columna dentro de list_editable, se genera un conflicto inresoluble: la celda no puede ser simultáneamente un campo de texto/edición in situ y un enlace de navegación HTML. El chequeo del sistema detectará esta colisión y bloqueará el inicio de la aplicación hasta que se corrija la inconsistencia o se defina explícitamente list_display_links.
Pregunta 3: ¿Cuál es el riesgo de rendimiento conocido como el "Problema de Consultas N+1" en las vistas de lista del Admin y cómo se resuelve en ProductAdmin?
Respuesta Pedagógica:
El problema N+1 ocurre cuando se renderiza una lista de $N$ elementos que contienen una llave foránea hacia otra tabla. El ORM primero ejecuta 1 consulta para obtener los $N$ registros principales (por ejemplo, 50 productos). Luego, al renderizar cada fila en la plantilla HTML, el sistema ejecuta 1 consulta adicional por cada registro para traer el objeto relacionado (el tipo de producto), acumulando $1 + N$ consultas SQL a la base de datos (51 consultas en total).
Se resuelve sobrescribiendo el método get_queryset en ProductAdmin e invocando .select_related("product_type"). Esto instruye al ORM a realizar un INNER JOIN en la base de datos desde la primera consulta, reduciendo la carga total a exactamente 1 consulta SQL, independientemente del número de filas en la lista.
Pregunta 4: ¿Por qué se deshabilita la creación y eliminación de ventas en SaleAdmin mediante has_add_permission y has_delete_permission retornando False?
Respuesta Pedagógica:
Se deshabilita para preservar de forma estricta la integridad transaccional de las operaciones de negocio y la consistencia del inventario. La creación de una venta requiere la ejecución del método de gestor SaleQuerySet.register, el cual opera dentro de un bloque transaction.atomic(), bloquea filas con select_for_update(), valida y descuenta el stock de los productos y congela el precio histórico (unit_price).
Por otro lado, la eliminación de una venta desde el Admin mediante un DELETE de SQL removería el registro pero dejaría el stock descontado en los productos. La única forma válida de cancelar una venta es a través de la anulación lógica expuesta por la API, la cual ejecuta sale.void(), reabastece las existencias de los productos afectados y marca el estado de la transacción como inactive.
Pregunta 5: ¿En qué se diferencia el modelo de autenticación y seguridad de Django Admin respecto a la API REST de StoreLab?
Respuesta Pedagógica:
Django Admin utiliza un modelo de autenticación basado en sesiones y cookies HTTP (django.contrib.sessions), respaldado por el middleware AuthenticationMiddleware. Requiere el uso de tokens CSRF en formulación HTML para mitigar ataques de falsificación de peticiones en sitios cruzados. Además, exige que el usuario tenga los atributos is_staff=True e is_active=True.
Por el contrario, la API REST de StoreLab es sin estado (stateless) y utiliza tokens JWT (StoreLabJWTAuthentication) transmitidos en la cabecera HTTP Authorization: Bearer <token>. La autorización en la API no depende de los permisos nativos de grupos del Admin, sino de la clase de permiso personalizada HasResourceAccess, la cual evalúa la matriz dinámica de roles y recursos (User → RoleUser → Role → ResourceRole → Resource).
Navegación de la ruta: ← ISS-12 · 🛠 Construir · ↑ Ruta Django · → ISS-13 · 🛠 Construir