Saltar a contenido

📚 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.

⛶ Ver presentación completa ⬇ Archivo editable (.pptx)

8 diapositivas · se visualiza 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

  1. ISS-12 Superado: La lógica de negocio, consulta y anulación de ventas vía API ya debe estar implementada y verificada.
  2. Registro de Aplicación en Configuración: La aplicación django.contrib.admin debe estar declarada dentro de INSTALLED_APPS en el paquete de configuración principal. En el proyecto StoreLab, este paquete reside en config/settings/__init__.py.
  3. URLConf Raíz: La ruta para el panel administrativo (path("admin/", admin.site.urls)) debe estar expuesta en config/urls.py, tal como fue establecido desde la unidad ISS-00.

B. Conceptos Esenciales y Vocabulario Técnico

  • ModelAdmin: Clase fundamental del módulo django.contrib.admin que 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 clase ModelAdmin con un modelo del ORM de Django, reemplazando la invocación explícita de admin.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áusula ORDER 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 como collapse.
  • 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 definido search_fields en su propio ModelAdmin.
  • 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) de ModelAdmin o InlineModelAdmin que determina si el usuario autenticado tiene permiso para crear nuevos registros. Retornar False deshabilita los botones de creación.
  • has_delete_permission: Método gancho que determina si se permite la eliminación de registros. Retornar False remueve 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 del QuerySet que optimiza las consultas SQL mediante una cláusula JOIN, 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

  1. Registro con @admin.register(Client): Asocia la clase ClientAdmin con la entidad Client sin necesidad de llamadas imperativas al registro global.
  2. Columnas Visibles (list_display): Presenta la información básica del comprador, incluyendo su estado (status) y fecha de creación (created_at).
  3. Filtros y Búsqueda (list_filter y search_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.
  4. Activación/Inactivación Rápida (list_editable = ("status",)):
  5. Permite alternar el estado del cliente directamente desde el listado.
  6. Regla Técnica Restrictiva: El primer campo definido en list_display (name) no puede incluirse en list_editable. Django requiere obligatoriamente que la primera columna sea un enlace hipertexto no editable que dirija al formulario de edición detallado del registro.
  7. Agrupación Semántica (fieldsets):
  8. "Información del cliente": Agrupa los datos personales y de contacto.
  9. "Estado": Permite controlar el campo status.
  10. "Auditoría": Agrupa created_at y updated_at. Utiliza el atributo "classes": ("collapse",) para que aparezca colapsado por defecto, reduciendo la carga visual.
  11. Ausencia de Contraseñas: El modelo Client no tiene ni debe tener un campo de contraseña. En la arquitectura de StoreLab, Client representa únicamente la entidad comercial compradora. La autenticación y las credenciales de acceso están centralizadas exclusivamente en la entidad security.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

  1. Requisito Técnico de Autocompletado (search_fields en ProductTypeAdmin):
  2. En ProductTypeAdmin, se declara explícitamente search_fields = ("name", "description").
  3. Invariante: Esto no es opcional. Para que un modelo hijo (Product) pueda usar autocomplete_fields = ("product_type",), el modelo padre referenciado (ProductType) debe poseer obligatoriamente search_fields en su respectivo ModelAdmin. De lo contrario, la ejecución de python manage.py check arroja un error crítico de verificación del sistema (SystemCheckError).
  4. Buscador Dinámico (autocomplete_fields en ProductAdmin):
  5. 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.
  6. Gestión Directa del Catálogo (list_editable):
  7. En ProductAdmin, se define list_editable = ("price", "stock", "status"). Permite actualizar precios, existencias e inhabilitar productos directamente desde el listado comercial.
  8. Optimización de Consultas SQL (get_queryset y select_related):
  9. Sobrescribir get_queryset incluyendo .select_related("product_type") fuerza a Django a ejecutar un INNER JOIN entre la tabla products y product_types.
  10. 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.TabularInline para presentar las líneas de venta como una tabla dentro de la vista de la venta cabecera.
  • extra = 0 elimina filas vacías por defecto.
  • can_delete = False remueve la casilla de verificación para eliminar líneas.
  • show_change_link = True habilita un enlace para inspeccionar la ficha individual del detalle.
  • has_add_permission(self, request, obj=None) retorna False para 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_permission retorna False.
  • has_delete_permission retorna False.

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:

python manage.py createsuperuser

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:

self.is_active = self.status == RecordStatus.ACTIVE

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

  1. Omitir search_fields en el Modelo Referenciado:
  2. Causa: Intentar utilizar autocomplete_fields = ("product_type",) en ProductAdmin cuando ProductTypeAdmin no ha declarado search_fields.
  3. Consecuencia: Django detiene la ejecución arrojando un error SystemCheckError al ejecutar python manage.py check.
  4. Asignar list_editable en el Primer Campo de list_display:
  5. Causa: Incluir el primer elemento de list_display (por ejemplo, name en ClientAdmin) dentro de la tupla list_editable.
  6. 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.
  7. Intentar Crear o Eliminar Ventas desde el Admin:
  8. Causa: No sobrescribir has_add_permission ni has_delete_permission retornando False en SaleAdmin y ProductSaleInline.
  9. Consecuencia: Los usuarios podrían eludir el gestor transaccional SaleQuerySet.register y la rutina de devolución de inventario sale.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, Sale y ProductSale) 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 Client debe 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 Product debe 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 check debe 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:

python manage.py check

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, los fieldsets y la sintaxis de los ModelAdmin son 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