En este post, comenzaremos un nuevo proyecto de Django que consistirá en una plataforma de e-learning con nuestro propio sistema de gestión de contenido (CMS). Las plataformas de aprendizaje en línea son un gran ejemplo de aplicaciones donde necesitas proporcionar herramientas para generar contenido teniendo en cuenta la flexibilidad de los contenidos que se ofrecen.
En este capítulo, aprenderás cómo:
- Crear fixtures para tus modelos y aplicarlos
- Usar la herencia de modelos para crear modelos de datos para contenido polimórfico
- Crear campos de modelo personalizados
- Ordenar contenidos y módulos del curso
- Construir vistas de autenticación para el CMS
Configuración del proyecto de e-learning
Tu proyecto práctico final será una plataforma de e-learning. Primero, crea un entorno virtual para tu nuevo proyecto dentro del directorio env/ con el siguiente comando:
python -m venv env/educa
```
Si estás usando Linux o macOS, ejecuta el siguiente comando para activar tu entorno virtual:
source env/educa/bin/activate
```
Si estás usando Windows, utiliza el siguiente comando en su lugar:
.\env\educa\Scripts\activate
```
Instala Django en tu entorno virtual con el siguiente comando:
pip install Django
```
Vas a gestionar la carga de imágenes en tu proyecto, así que también necesitas instalar Pillow con el siguiente comando:
pip install Pillow
```
Crea un nuevo proyecto utilizando el siguiente comando:
django-admin startproject educa
```
Ingresa al nuevo directorio educa y crea una nueva aplicación utilizando los siguientes comandos:
cd educa
django-admin startapp courses
```
Edita el archivo settings.py del proyecto educa y agrega courses a las aplicaciones instaladas (INSTALLED_APPS), de la siguiente manera. La nueva línea está resaltada en azul:
INSTALLED_APPS = [
...
'courses',
]
```
Sirviendo archivos multimedia
Antes de crear los modelos para los cursos y su contenido, prepararemos el proyecto para servir archivos multimedia. Los profesores de los cursos podrán subir archivos multimedia al contenido del curso utilizando el CMS que construiremos. Por lo tanto, configuraremos el proyecto para servir archivos multimedia.
Edita el archivo settings.py
del proyecto y agrega las siguientes líneas:
MEDIA_URL = 'media/'
MEDIA_ROOT = BASE_DIR / 'media'
Esto permitirá a Django gestionar las subidas de archivos y servir archivos multimedia. MEDIA_URL
es la URL base utilizada para servir los archivos multimedia subidos por los usuarios. MEDIA_ROOT
es la ruta local donde se encuentran. Las rutas y URLs de los archivos se construyen dinámicamente anteponiendo la ruta del proyecto o la URL de medios a ellos para una mayor portabilidad.
Ahora, edita el archivo urls.py
principal del proyecto educa
y modifica el código de la siguiente manera. Las líneas nuevas están resaltadas en negrita:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Hemos agregado la función de ayuda static()
para servir archivos multimedia con el servidor de desarrollo de Django durante el desarrollo (es decir, cuando la configuración DEBUG
está establecida en True
).
Ten en cuenta que la función auxiliar static()
es adecuada para el desarrollo, pero no para la producción. Django es muy ineficiente para servir archivos estáticos. Nunca sirvas tus archivos estáticos con Django en un entorno de producción. Aprenderemos cómo servir archivos estáticos en un entorno de producción más adelante.
Aquí hay algunos puntos importantes adicionales:
- Los archivos estáticos son cosas como archivos CSS, JavaScript e imágenes que no cambian con frecuencia y no requieren procesamiento dinámico por parte de Django.
- En producción, es mejor servir archivos estáticos usando un servidor web dedicado como Nginx o Apache, ya que están optimizados para servir archivos estáticos de manera eficiente.
- Usar Django para servir archivos estáticos en producción puede ralentizar tu aplicación y aumentar la carga del servidor.
Construyendo los modelos de cursos
Tu plataforma de e-learning ofrecerá cursos sobre diversos temas. Cada curso se dividirá en un número configurable de módulos, y cada módulo contendrá un número configurable de contenidos. Los contenidos serán de varios tipos: texto, archivos, imágenes o vídeos. El siguiente ejemplo muestra cómo será la estructura de datos de tu catálogo de cursos:
Asignatura 1 (subject) Curso 1 Módulo 1 Contenido 1 (imagen) Contenido 2 (texto) Módulo 2 Contenido 3 (texto) Contenido 4 (archivo) Contenido 5 (video) ...
Construyamos los modelos de cursos. Edita el archivo models.py
de la aplicación courses
y agrega el siguiente código:
from django.db import models
from django.contrib.auth.models import User
class Subject(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class Course(models.Model):
owner = models.ForeignKey(User,
related_name='courses_created',
on_delete=models.CASCADE)
subject = models.ForeignKey(Subject,
related_name='courses',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200, unique=True)
overview = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created']
def __str__(self):
return self.title
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
def __str__(self):
return self.title
Estos son los modelos iniciales de Subject (asignatura)
, Course (Curso)
y Module (Módulo)
. Los campos del modelo Course
son los siguientes:
owner
: El profesor que creó este curso.subject
: La materia o asignatura a la que pertenece este curso. Es un campoForeignKey
que apunta al modeloSubject
.title
: El título del curso.slug
: El slug del curso. Se utilizará en las URLs más adelante.overview
: Una columnaTextField
para almacenar una descripción general del curso.created
: La fecha y hora en que se creó el curso. Django lo establecerá automáticamente al crear nuevos objetos debido aauto_now_add=True
.
Cada curso se divide en varios módulos. Por lo tanto, el modelo Module
contiene un campo ForeignKey
que apunta al modelo Course
.
Abre el shell y ejecuta el siguiente comando:
python manage.py makemigrations
Verás la siguiente salida:
Migrations for 'courses':
courses/migrations/0001_initial.py:
- Crear modelo Curso
- Crear modelo Módulo
- Crear modelo Tema
- Agregar campo tema al curso
Luego, ejecuta el siguiente comando para aplicar todas las migraciones a la base de datos:
python manage.py migrate
Registrando los modelos en el panel de administración
Agreguemos los modelos de cursos al panel de administración. Edita el archivo admin.py
dentro del directorio de la aplicación courses
y agrega el siguiente código:
from django.contrib import admin
from .models import Subject, Course, Module
@admin.register(Subject)
class SubjectAdmin(admin.ModelAdmin):
list_display = ['title', 'slug']
prepopulated_fields = {'slug': ('title',)}
class ModuleInline(admin.StackedInline):
model = Module
@admin.register(Course)
class CourseAdmin(admin.ModelAdmin):
list_display = ['title', 'subject', 'created']
list_filter = ['created', 'subject']
search_fields = ['title', 'overview']
prepopulated_fields = {'slug': ('title',)}
inlines = [ModuleInline]
Explicación:
- Este código registra los modelos
Subject
,Course
yModule
en el panel de administración de Django. - La función
@admin.register()
decora cada clase y se usa para registrar el modelo correspondiente. list_display
define las columnas que se muestran en la lista de objetos de cada modelo.prepopulated_fields
establece que el camposlug
se genere automáticamente a partir del título.- La clase
ModuleInline
define una relación "inline" entre los modelosCourse
yModule
, lo que permite editar módulos directamente desde la página del curso. list_filter
ysearch_fields
permiten filtrar y buscar objetos en la lista.
Ahora los modelos de la aplicación courses
están registrados en el panel de administración.
Usando fixtures para proporcionar datos iniciales a los modelos
A veces, es posible que desees rellenar previamente tu base de datos con datos ya registrados. Esto es útil para incluir automáticamente datos iniciales en la configuración del proyecto, en lugar de tener que agregarlos manualmente. Django viene con una forma simple de cargar y volcar datos desde la base de datos a archivos llamados fixtures. Django admite fixtures en formatos JSON, XML o YAML.
Va a crear un fixture para incluir varios objetos Subject (asignaturas) iniciales para el proyecto.
Pasos:
- Crea un superusuario: Para comenzar, ejecute el siguiente comando en su terminal para crear un superusuario que pueda acceder al panel de administración:
python manage.py createsuperuser
- Inicia el servidor de desarrollo: Luego, ejecuta el siguiente comando para iniciar el servidor de desarrollo de Django:
python manage.py runserver
- Accede al panel de administración: Abra la siguiente URL en su navegador para acceder al panel de administración de Django:
http://127.0.0.1:8000/admin/courses/subject/
Crea Asignaturas: Utiliza el panel de administración para crear varios objetos Subject iniciales para el proyecto.
Observa la lista de cambios: Verás la lista de cambios de las asignaturas que has creado.
Ejecuta el siguiente comando:
python manage.py dumpdata courses --indent=2
Explicación:
- Ejecuta este comando en la terminal para extraer datos de la base de datos a un archivo JSON.
dumpdata
es el comando utilizado para exportar datos.courses
indica que solo se extraerán datos de la aplicacióncourses
.--indent=2
agrega sangría para mejorar la legibilidad del archivo JSON.
Salida esperada:
Verás una salida similar a la siguiente:
[
{
"model": "courses.subject",
"pk": 1,
"fields": {
"title": "Física",
"slug": "fisica"
}
},
{
"model": "courses.subject",
"pk": 2,
"fields": {
"title": "Matemáticas",
"slug": "matematicas"
}
},
// ... (otras asignaturas que creaste)
]
Explicación de la salida:
- La salida es un array JSON que contiene un objeto por cada sujeto que has creado.
- Cada objeto tiene tres propiedades:
model
: nombre del modelo (ej. "courses.subject").pk
: clave primaria del objeto en la base de datos.fields
: diccionario con los campos del modelo y sus valores.
Siguiente paso:
Vamos a guardar esta salida en un archivo JSON dentro de una nueva carpeta llamadafixtures que crearemos dentro
de la aplicación courses
. Te proporcionaré los comandos para hacerlo a continuación.
>>> mkdir courses/fixtures
>>> python manage.py dumpdata courses --indent=2 --output=courses/fixtures/subjects.json
Ahora vuelve a ejecutar el servidor de desarrollo y usa el panel de administración para borrar todas las asignaturas (subjects) que hayas creado, tal como se muestra en la siguiente imagen:
Depués de borrarlas todas, vuelve a cargarlas en la base de datos de nuevo usando el siguiente comando:
python manage.py loaddata subjects.json
Como verás todas la asignaturas guardadas en el archivo subjects.json dentro del directorio fixtures han sido cargadas de nuevo en la base de datos de la aplicación.
Por defecto, Django busca estos archivos dentro del directorio fixtures que pueda haber en cada aplicación, pero puedes especificar otro directorio. Para ello debes usar el argumento FIXTURE_DIRS dentro del settings.py del proyecto para decirle a Django donde debe buscar estos archivos.
Estos archivos "fixtures" no solo son útiles para cargar datos iniciales sino también para proporcionar o proporcionar datos para hacer futuros text del modelo.
Puedes leer sobre cómo usar fixtures para testing en https://docs.djangoproject.com/en/5.0/ topics/testing/tools/#fixture-loading.
Si quieres cargar fixtures en las migraciones de modelos, consulta la documentación de Django sobre migraciones de datos. Puedes encontrar la documentación para migrar datos en https://docs.djangoproject.com/en/5.0/topics/migrations/#data-migrations.
Hasta aquí, hemos creado los modelos para gestionar las asignaturas, cursos y módulos de los cursos. A continuación, crearemos modelos para gestionar diferentes tipos de contenidos de módulos.
Creación de modelos para contenido polimórfico
Tenemos en mente agregar diferentes tipos de contenido a los módulos del curso, como texto, imágenes, archivos y videos.
El polimorfismo es la provisión de una sola interfaz para entidades de diferentes tipos. Necesitas un modelo de datos versátil que te permita almacenar contenido diverso que sea accesible a través de una sola interfaz. En post anteriores vimos la conveniencia de usar relaciones genéricas para crear claves externas que puedan apuntar a los objetos de cualquier modelo. Vamos a crear un modelo Content que represente el contenido de los módulos y definir una relación genérica para asociar cualquier objeto con el objeto del contenido.
Edita el archivo models.py de la aplicación de course (cursos) y agrega las siguientes importaciones:
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
Luego, agrega el siguiente código al final del archivo:
class Content(models.Model):
module = models.ForeignKey(Module,
related_name='contents',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
Explicación:
- Importaciones:
ContentType
: Este modelo de Django almacena información sobre todos los modelos registrados en la aplicación.GenericForeignKey
: Este campo permite crear relaciones con objetos de cualquier modelo registrado.
- Modelo "Content":
module
: CampoForeignKey
que relaciona cada contenido con un módulo específico.content_type
: CampoForeignKey
que referencia el modelo al que pertenece el contenido específico (texto, imagen, video, etc.).object_id
: Campo que almacena la clave primaria del objeto relacionado (por ejemplo, la ID de un texto específico).item
: CampoGenericForeignKey
que combinacontent_type
yobject_id
para acceder al objeto relacionado de forma genérica.
El modelo "Content"
Este modelo representa el contenido de un módulo. Dado que un módulo puede contener distintos tipos de contenido (texto, imágenes, videos, etc.), se define un campo ForeignKey
que apunta al modelo Module
. Además, se establece una relación genérica para asociar objetos de diferentes modelos que representan estos tipos de contenido.
Relación genérica:
Para establecer una relación genérica se necesitan tres campos:
content_type
: CampoForeignKey
al modeloContentType
. Este modelo almacena información sobre todos los modelos registrados en la aplicación.object_id
: CampoPositiveIntegerField
que almacena la clave primaria del objeto relacionado.item
: CampoGenericForeignKey
al objeto relacionado, combinando los dos campos anteriores.
Almacenamiento en la base de datos:
Solo los campos content_type
y object_id
tienen una columna correspondiente en la tabla de la base de datos de este modelo. El campo item
permite recuperar o establecer directamente el objeto relacionado, y su funcionalidad se basa en los otros dos campos.
Modelos específicos para cada tipo de contenido:
Se utilizará un modelo diferente para cada tipo de contenido. Los modelos Content
tendrán algunos campos comunes, pero diferirán en los datos que pueden almacenar. De esta manera, se crea una interfaz única para manejar diferentes tipos de contenido.
Explicación adicional:
- Un modelo genérico como "Content" es útil cuando la misma funcionalidad se aplica a diferentes tipos de datos. En este caso, todos los tipos de contenido comparten características como estar dentro de un módulo, pero tienen características específicas (texto, imagen, etc.) que se gestionan en modelos separados.
- El uso de un campo
GenericForeignKey
brinda flexibilidad y evita la necesidad de definir relaciones específicas con cada modelo de contenido.
Utilizando la herencia de modelos
Django soporta la herencia de modelos. Funciona de manera similar a la herencia de clases estándar en Python.
Django ofrece las siguientes tres opciones para usar la herencia de modelos:
• Modelos abstractos: Útiles cuando quieres incluir alguna información común en varios modelos.
• Herencia de modelos con múltiples tablas: Aplicable cuando cada modelo en la jerarquía es considerado un modelo completo por sí mismo.
• Modelos proxy: Útiles cuando necesitas cambiar el comportamiento de un modelo, por ejemplo, incluyendo métodos adicionales, cambiando el gestor predeterminado o utilizando diferentes opciones meta.
Vamos a examinar más de cerca cada una de ellas.
Modelos abstractos
Un modelo abstracto es una clase base en la cual defines los campos que quieres incluir en todos los modelos hijos.
Django no crea ninguna tabla de base de datos para los modelos abstractos. Una tabla de base de datos es creada para cada modelo hijo, incluyendo los campos heredados de la clase abstracta y los definidos en el modelo hijo.
Para marcar un modelo como abstracto, necesitas incluir abstract=True en su clase Meta. Django reconocerá que es un modelo abstracto y no creará una tabla de base de datos para él. Para crear modelos hijos, simplemente necesitas subclasear el modelo abstracto.
El siguiente ejemplo muestra un modelo Content abstracto y un modelo hijo Text:
from django.db import models class BaseContent(models.Model): title = models.CharField(max_length=100) created = models.DateTimeField(auto_now_add=True) class Meta: abstract = True class Text(BaseContent): body = models.TextField()
En este caso, Django crearía una tabla únicamente para el modelo Text, incluyendo los campos title, created y body.
Herencia de modelos con múltiples tablas
En la herencia de modelos con múltiples tablas, cada modelo corresponde a una tabla de base de datos. Django crea un campo OneToOneField para la relación entre el modelo hijo y su modelo padre. Para usar la herencia de modelos con múltiples tablas, debes subclasificar un modelo existente. Django creará una tabla de base de datos tanto para el modelo original como para el submodelo. El siguiente ejemplo muestra la herencia de modelos con múltiples tablas:
from django.db import models class BaseContent(models.Model): title = models.CharField(max_length=100) created = models.DateTimeField(auto_now_add=True) class Meta: abstract = True class Text(BaseContent): body = models.TextField()
Django incluirá automáticamente un campo OneToOneField generado en el modelo Text y creará una tabla de base de datos para cada modelo.
Modelos proxy
Un modelo proxy cambia el comportamiento de un modelo. Ambos modelos operan en la tabla de base de datos del modelo original. Para crear un modelo proxy, agrega proxy=True a la clase Meta del modelo. El siguiente ejemplo ilustra cómo crear un modelo proxy:
from django.db import models
from django.utils import timezone
class BaseContent(models.Model):
title = models.CharField(max_length=100)
created = models.DateTimeField(auto_now_add=True)
class OrderedContent(BaseContent):
class Meta:
proxy = True
ordering = ['created']
def created_delta(self):
return timezone.now() - self.created
- Este código define dos modelos:
BaseContent
: El modelo base que ya hemos visto anteriormente, con los campostitle
ycreated
.OrderedContent
: Un modelo proxy que hereda deBaseContent
. Los modelos proxy no crean nuevas tablas en la base de datos, sino que actúan como una vista diferente de la tabla del modelo base.
Características del modelo "OrderedContent":
proxy = True
: Indica que es un modelo proxy.ordering = ['created']
: Establece el orden predeterminado para las consultas (QuerySets) del modelo, ordenando por la fecha de creación (created
).created_delta()
: Define un método adicional que calcula el tiempo transcurrido desde la creación del contenido.
Beneficios de usar un modelo proxy:
- Permite definir órdenes predeterminados para consultas específicas sin modificar el modelo base.
- Agrega métodos personalizados al modelo sin afectar el modelo base.
- Mantiene la sincronización con la base de datos del modelo base.
Importante:
- Los objetos de
OrderedContent
yBaseContent
representan datos en la misma tabla de la base de datos. - Puedes acceder a los objetos a través del ORM utilizando cualquiera de los dos modelos.
Creando los modelos de Contenido
El modelo de Contenido (Content) de tu aplicación de cursos contiene una relación genérica para asociar diferentes tipos de contenido con él. Crearemos un modelo diferente para cada tipo de contenido. Todos los modelos de Contenido tendrán algunos campos en común y campos adicionales para almacenar datos personalizados. Vas a crear un modelo abstracto que proporcione los campos comunes para todos los modelos de Contenido.
Edita el archivo models.py de la aplicación de cursos y agrega el siguiente código:
class ItemBase(models.Model):
owner = models.ForeignKey(User,
related_name='%(class)s_related',
on_delete=models.CASCADE)
title = models.CharField(max_length=250)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
def __str__(self):
return self.title
class Text(ItemBase):
content = models.TextField()
class File(ItemBase):
file = models.FileField(upload_to='files')
class Image(ItemBase):
file = models.FileField(upload_to='images')
class Video(ItemBase):
url = models.URLField()
Explicación:
- Este código define varios modelos:
ItemBase
: Un modelo abstracto que define campos comunes para todos los tipos de contenido (propietario, título, fecha de creación y actualización). Al ser abstracto, este modelo no se utiliza directamente para crear tablas en la base de datos.Text
,File
,Image
,Video
: Modelos heredados deItemBase
, cada uno con un campo específico para su tipo de contenido:Text
: Campocontent
para texto.File
: Campofile
para archivos genéricos.Image
: Campofile
para imágenes.Video
: Campourl
para enlaces a videos externos.
Campos en "ItemBase":
owner
: CampoForeignKey
que relaciona el contenido con el usuario que lo creó.title
: CampoCharField
para el título del contenido.created
: CampoDateTimeField
para la fecha de creación del contenido.updated
: CampoDateTimeField
para la fecha de actualización del contenido.
Uso de "related_name" con modelos heredados:
- El campo
owner
está definido en un modelo abstracto, por lo que necesita unrelated_name
diferente para cada modelo heredado. - El placeholder
%(class)s
enrelated_name
permite generar automáticamente el nombre en base al nombre de la clase heredada. Ejemplo:Text
tendrátext_related
comorelated_name
.
Resumen:
- Este código crea un modelo base para contenido y modelos específicos para diferentes tipos de contenido (texto, archivos, imágenes, videos).
- Los modelos heredados comparten campos comunes y agregan campos específicos para su tipo de contenido.
- El uso de un modelo base y
related_name
con placeholder facilita la organización y flexibilidad del código.
El campo owner te permite almacenar qué usuario creó el contenido. Dado que este campo está definido en una clase abstracta, necesitas un related_name diferente para cada submodelo. Django te permite especificar un marcador de posición para el nombre de la clase del modelo en el atributo related_name como %(class)s. Al hacerlo, el related_name para cada modelo hijo se generará automáticamente. Dado que estás usando '%(class)s_related' como el related_name, la relación inversa para los modelos hijos será text_related, file_related, image_related y video_related, respectivamente.
Has definido cuatro modelos de Contenido diferentes que heredan del modelo abstracto ItemBase. Son los siguientes:
• Text: Para almacenar contenido de texto
• File: Para almacenar archivos, como PDFs
• Image: Para almacenar archivos de imagen
• Video: Para almacenar videos; utilizas un campo URLField para proporcionar una URL de video con el fin de incrustarlo
Cada modelo hijo contiene los campos definidos en la clase ItemBase además de sus propios campos. Se creará una tabla de base de datos para los modelos Text, File, Image y Video, respectivamente. No habrá tabla de base de datos asociada con el modelo ItemBase ya que es un modelo abstracto.
Edita el modelo de Contenido (Content) que creaste anteriormente y modifica su campo content_type, de la siguiente manera:
Edita el modelo de Contenido que creaste anteriormente y modifica su campo content_type de la siguiente manera:
Agregas un argumento limit_choices_to para limitar los objetos ContentType que pueden ser utilizados para la relación genérica. Utilizas la búsqueda de campo model__in para filtrar la consulta a los objetos ContentType con un atributo model que sea 'text', 'video', 'image' o 'file'.
Ahora, creemos una migración para incluir los nuevos modelos que has agregado. Ejecuta el siguiente comando desde la línea de comandos:
python manage.py makemigrations
Verás la siguiente salida:
courses/migrations/0002_video_text_image_file_content.py
- Crear modelo Video
- Crear modelo Text
- Crear modelo Image
- Crear modelo File
- Crear modelo Content
Luego, ejecuta el siguiente comando para aplicar la nueva migración:
python manage.py migrate
La salida que ves debería terminar con la siguiente línea:
Aplicando courses.0002_video_text_image_file_content... OK
Has creado modelos que son adecuados para agregar un contenido variado a los módulos del curso. Sin embargo, todavía falta algo en tus modelos: los módulos del curso y los contenidos deberían seguir un orden determinado. Necesitas un campo que te permita ordenarlos fácilmente.
Creando campos de modelo personalizados
Django te brinda una colección completa de campos de modelo que puedes usar para construir tus modelos. Sin embargo, también puedes crear tus propios campos de modelo para almacenar datos personalizados o modificar el comportamiento de los campos existentes.
Necesidad de un campo de orden:
- Necesitas un campo que permita definir un orden para los objetos de tu modelo.
- Una forma sencilla de especificar un orden utilizando campos de Django existentes es agregando un
PositiveIntegerField
a tus modelos. - Mediante números enteros, puedes especificar fácilmente el orden de los objetos.
Campo de orden personalizado:
Puedes crear un campo de orden personalizado que herede de
PositiveIntegerField
y proporcione un comportamiento adicional.Este campo tendrá dos funcionalidades clave:
Asignación automática de orden:
- Al guardar un nuevo objeto sin un orden específico, tu campo debe asignar automáticamente el número que sigue al último objeto ordenado existente.
- Ejemplo: Si hay dos objetos con orden 1 y 2 respectivamente, al guardar un tercer objeto, debe asignarse automáticamente el orden 3 si no se ha proporcionado un orden específico.
Ordenar objetos con respecto a otros campos:
- Los módulos de un curso se ordenarán con respecto al curso al que pertenecen, y el contenido de los módulos se ordenará con respecto al módulo al que pertenecen.
Instrucciones:
- Crea un nuevo archivo
fields.py
dentro del directorio de la aplicacióncourses
. - Agrega el siguiente código al archivo
fields.py
:
Este es el OrderField personalizado. Hereda del campo PositiveIntegerField proporcionado por Django.
Tu campo OrderField toma un parámetro opcional for_fields, que te permite indicar los campos utilizados para ordenar los datos.
Tu campo sobrescribe el método pre_save() del campo PositiveIntegerField, que se ejecuta antes de guardar el campo en la base de datos. En este método, realizas las siguientes acciones:
1.) Verificas si ya existe un valor para este campo en la instancia del modelo. Utilizas self.attname, que es el nombre de atributo dado al campo en el modelo. Si el valor del atributo es diferente de None, calculas el orden que debes darle de la siguiente manera:
1. Construyes un QuerySet para recuperar todos los objetos del modelo del campo. Recuperas la clase del modelo al que pertenece el campo accediendo a self.model.
2. Si hay algún nombre de campo en el atributo for_fields del campo, filtras el QuerySet por el valor actual de los campos del modelo en for_fields. De esta manera, calculas el orden con respecto a los campos dados.
3. Recuperas el objeto con el orden más alto con last_item = qs.latest(self.attname) de la base de datos. Si no se encuentra ningún objeto, asumes que este objeto es el primero y le asignas el orden 0.
4. Si se encuentra un objeto, sumas 1 al orden más alto encontrado.
5. Asignas el orden calculado al valor del campo en la instancia del modelo usando setattr() y lo devuelves.
2.) Si la instancia del modelo tiene un valor para el campo actual, lo utilizas en lugar de calcularlo.
Cuando crees campos de modelo personalizados, hazlos genéricos. Evita codificar datos que dependan de un modelo o campo específico. Tu campo debería funcionar en cualquier modelo.
Puedes encontrar más información en https://docs.djangoproject.com/en/5.0/howto/custom-model-fields/.
Agregando orden a los objetos módulo y contenido
from .fields import OrderField
class Module(models.Model):
# ...
order = OrderField(blank=True, for_fields=['course'])
Nombras el nuevo campo como order y especificas que el orden se calcula con respecto al curso al establecer for_fields=['course']. Esto significa que el orden para un nuevo módulo se asignará agregando 1 al último módulo del mismo objeto Course.
Ahora, puedes editar el método str() del modelo Module para incluir su orden, de la siguiente manera:
class Module(models.Model):
# ...
def __str__(self):
return f'{self.order}. {self.title}'
Los contenidos del módulo también necesitan seguir un orden particular. Agrega un campo OrderField al modelo Content, de la siguiente manera:
class Content(models.Model):
# ...
order = OrderField(blank=True, for_fields=['module'])
Esta vez, especificas que el orden se calcula con respecto al campo module.
Finalmente, vamos a añadir un orden predeterminado para ambos modelos. Agrega la siguiente clase Meta a los modelos Module y Content:
class Module(models.Model):
# ...
class Meta:
ordering = ['order']
class Content(models.Model):
# ...
class Meta:
ordering = ['order']
Cuando creas campos de modelo personalizados, hazlos genéricos. Evita codificar datos que dependan de un modelo o campo específico. Tu campo debería funcionar en cualquier modelo.
Los modelos "Module" y "Content" ahora deberían tener el siguiente aspecto:
class Module(models.Model):
course = models.ForeignKey(Course,
related_name='modules',
on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
order = OrderField(blank=True, for_fields=['course'])
class Meta:
ordering = ['order']
def __str__(self):
return f'{self.order}. {self.title}'
class Content(models.Model):
module = models.ForeignKey(Module,
related_name='contents',
on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType,
on_delete=models.CASCADE,
limit_choices_to={'model__in': (
'text',
'video',
'image',
'file'
)})
object_id = models.PositiveIntegerField()
item = GenericForeignKey('content_type', 'object_id')
order = OrderField(blank=True, for_fields=['module'])
class Meta:
ordering = ['order']
Creación de la migración:
Ahora que hemos actualizado los modelos, necesitamos crear una nueva migración para reflejar los cambios en el campo "order". Para ello, abre la terminal y ejecuta el siguiente comando:
python manage.py makemigrations courses
El problema es que tendremos la siguiente salida:
specifying a default. This is because the database needs something to populate
existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null
value for this column)
2) Quit and manually define a default value in models.py.
Select an option:""
Django nos está diciendo que tenemos que facilitarle un valor por defecto para el nuevo campo que hemos creado "order" y que se aplicará en los valores que ya existen en la base de datos. Si el campo incluye la sentencia null=True, se aceptarán valores nulos y Django creará las migraciones automáticamente en vez de preocuparnos por los valores por defecto. En nuestro caso puedes especificar un valor por defecto o cancelar la migración y añadir el atributo default al campo order en el archivo models.py antes de realizar la migración.
En nuestro caso pulsaremos 1 más Enter para facilitar al programa un valor por defecto en los valores existentes. Verás la siguiente salida:
The datetime and django.utils.timezone modules are available, so it is possible
to provide e.g. timezone.now as a value.
Type 'exit' to exit this prompt
>>>"""
Ahora pulsa un 0 más Enter. Con esto le estaremos facilitando el valor por defecto necesario. Django te volverá a preguntar lo mismo esta vez en relación con el modelo Module. Al igual que antes, escoge la primera opción y vuelve a introducir el cero como valor por defecto.
Finalmente verás una salida similar a esta, con las migraciones realizadas:
courses/migrations/0003_alter_content_options_alter_module_options_and_more.py
- Change Meta options on content
- Change Meta options on module
- Add field order to content
- Add field order to module"""
Ahora aplica las migraciones con la siguiente instrucción:
python manage.py migrate
La salida de este comando te indicará si la migraciones han sido correctamente aplicadas:
Applying courses.0003_alter_content_options_alter_module_options_and_more... OK
Bien, ahora vamos a probar este nuevo campo. Abre el shell con el siguiente comando:
python manage.py shell
¿Qué tal si creamos un nuevo curso?
>>> from django.contrib.auth.models import User
>>> from courses.models import Subject, Course, Module
>>> user = User.objects.last()
>>> subject = Subject.objects.last()
>>> c1 = Course.objects.create(subject=subject, owner=user, title='Curso 1', slug='curso1')
Has creado un curso en la base de datos. Ahora, añadirás módulos al curso y verás cómo su orden se calcula automáticamente. Creas un módulo inicial y verificas su orden:
>>> m1 = Module.objects.create(course=c1, title='Módulo 1')
>>> m1.order
0
OrderField establece su valor en 0, ya que este es el primer objeto Módulo creado para el curso dado. Puedes crear un segundo módulo para el mismo curso:
>>> m2 = Module.objects.create(course=c1, title='Módulo 2')
>>> m2.order
1
OrderField calcula el siguiente valor de orden, añadiendo 1 al orden más alto de los objetos existentes. Creemos un tercer módulo, forzando un orden específico:
>>> m3 = Module.objects.create(course=c1, title='Módulo 3', order=5)
>>> m3.order
5
Si proporcionas un orden personalizado al crear o guardar un objeto, OrderField usará ese valor en lugar de calcular el orden. Añadamos un cuarto módulo:
>>> m4 = Module.objects.create(course=c1, title='Módulo 4')
>>> m4.order
6
El orden para este módulo se ha establecido automáticamente. Tu campo OrderField no garantiza que todos los valores de orden sean consecutivos. Sin embargo, respeta los valores de orden existentes y siempre asigna el siguiente orden basado en el orden existente más alto.
Vamos a crear un segundo curso y añadirle un módulo:
>>> c2 = Course.objects.create(subject=subject, title='Curso 2',
slug='curso2', owner=user)
>>> m5 = Module.objects.create(course=c2, title='Módulo 1')
>>> m5.order
0
Para calcular el orden del nuevo módulo, el campo solo toma en consideración los módulos existentes que pertenecen al mismo curso. Dado que este es el primer módulo del segundo curso, el orden resultante es 0. Esto se debe a que especificaste for_fields=['course']
en el campo de orden del modelo Module.
¡Felicidades! Has creado con éxito tu primer campo de modelo personalizado. A continuación, vas a crear un sistema de autenticación para el CMS.
Añadiendo vistas de autenticación:
Ahora que has creado un modelo de datos polimórfico, vas a construir un CMS para gestionar los cursos y sus contenidos. El primer paso es añadir un sistema de autenticación para el CMS.
Añadiendo un sistema de autenticación:
Vas a utilizar el framework de autenticación de Django para que los usuarios se autentiquen en la plataforma de e-learning. Tanto los instructores o proferosres como los estudiantes serán instancias del modelo de usuario de Django, por lo que podrán iniciar sesión en el sitio utilizando las vistas de autenticación de django.contrib.auth
.
Edita el archivo urls.py principal del proyecto educa e incluye las vistas de inicio de sesión y cierre de sesión del framework de autenticación de Django:
from django.contrib import admin
from django.urls import path
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.auth import views as auth_views
urlpatterns = [
path('accounts/login/', auth_views.LoginView.as_view(), name='login'),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Si la configuración DEBUG
está activada, se añaden las URLs para servir archivos estáticos.
Creando las plantillas de autentificación.
Crea la siguiente estructura de archivos dentro de la aplicación courses.
base.html
registration/
login.html
logged_out.html
Antes de construir las plantillas de autentificación, necesitamos preparar la plantila base.html. Edítala y añade el siguiente contenido:
{% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>{% block title %}Educa{% endblock %}</title> <link href="{% static "css/base.css" %}" rel="stylesheet"> </head> <body> <div id="header"> <a href="/" class="logo">Educa</a> <ul class="menu"> {% if request.user.is_authenticated %} <li> <!-- Actualmente es necesario enviar la orden de logout mediante el método post, sino no se ejecutará. Para disimular el botón y que parezca un enlace hay que añadir código CCS. #logout-form #log_out button --> <form id="logout-form" action="{% url 'logout' %}" method="post"> {% csrf_token %} <button type="submit">Sign out</button> </form> </li> {% else %} <li><a href="{% url "login" %}">Sign in</a></li> {% endif %} </ul> </div> <div id="content"> {% block content %} {% endblock %} </div> <script> document.addEventListener('DOMContentLoaded', (event) => { // DOM loaded {% block domready %} {% endblock %} }) </script> </body> </html>
Código CSS para disimular el botón como que fuera un enlace:
Elearning/educa/courses/static/css/base.css
#...
#logout-form {
display: inline;
margin: 0;
padding: 0;
border: none;
background: none;
}
#logout-form button {
padding: 0;
border: none;
background: none;
color: white; /* Color del enlace */
text-decoration: none; /* Subrayado para indicar que es un enlace */
cursor: pointer; /* Cambiar el cursor al pasar por encima */
}
Este es el template base que será extendido por el resto de los templates. En este template, defines los siguientes bloques:
title
: El bloque para que otros templates añadan un título personalizado para cada página.content
: El bloque principal para el contenido. Todos los templates que extiendan el template base deben añadir contenido a este bloque.domready
: Ubicado dentro del event listener de JavaScript para el evento DOMContentLoaded. Permite ejecutar código cuando el Document Object Model (DOM) ha terminado de cargar.
Los estilos CSS usados en este template están ubicados en el directorio static/
de la aplicación de cursos en el código que viene con este post. Copia el directorio static/
dentro del mismo directorio de tu proyecto para usarlos. Puedes encontrar el contenido del directorio en este enlace.
Edita la plantilla registration/login.html y añade el siguiente código:
Elearning/educa/courses/templates/registration/login.html
{% extends "base.html" %}
{% block title %}Log-in{% endblock %}
{% block content %}
<h1>Log-in</h1>
<div class="module">
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% else %}
<p>Please, use the following form to log-in:</p>
{% endif %}
<div class="login-form">
<form action="{% url 'login' %}" method="post">
{{ form.as_p }}
{% csrf_token %}
<input type="hidden" name="next" value="{{ next }}" />
<p><input type="submit" value="Log-in"></p>
</form>
</div>
</div>
{% endblock %}
Esta es la plantilla de login standar para la vista de login.
Ahora edita la plantilla registration/logged_out.html y añade el siguiente código:
Elearning/educa/courses/templates/registration/logged_out.html
{% extends "base.html" %}
{% block title %}Logged out{% endblock %}
{% block content %}
<h1>Logged out</h1>
<div class="module">
<p>
You have been successfully logged out.
You can <a href="{% url 'login' %}">log-in again</a>.
</p>
</div>
{% endblock %}
Esta es la plantilla que mostrará al usuario después de que se haya desconectado.
Vamos a ver si funciona. Ejecuta el servidor de desarrollo de Django con:
python manage.py runserver
Si abres esta dirección en tu navegador, http://127.0.0.1:8000/accounts/login/, deberías ver la siguiente página.
Abre esta dirección http://127.0.0.1:8000/accounts/logout/ en el navegador. Debería verse la página de desconexión tal como se muestra a continuación:
Con esto habremos creado un sistema de autenticación de usuarios para nuestro CMS.
El código fuente de este capítulo se puede encontrar en el siguiente enlace de GITHUB