Parece que está preguntando sobre la diferencia entre el modelo de datos y el modelo de dominio : en este último es donde puede encontrar la lógica comercial y las entidades tal como las percibe su usuario final, el primero es donde realmente almacena sus datos.
Además, he interpretado la tercera parte de su pregunta como: cómo notar la falla para mantener estos modelos separados.
Estos son dos conceptos muy diferentes y siempre es difícil mantenerlos separados. Sin embargo, existen algunos patrones y herramientas comunes que pueden usarse para este propósito.
Sobre el modelo de dominio
Lo primero que debe reconocer es que su modelo de dominio no se trata realmente de datos; se trata de acciones y preguntas como "activar este usuario", "desactivar este usuario", "¿qué usuarios están activados actualmente?" y "¿cuál es el nombre de este usuario?". En términos clásicos: se trata de consultas y comandos .
Pensar en comandos
Comencemos mirando los comandos en su ejemplo: "activar este usuario" y "desactivar este usuario". Lo bueno de los comandos es que pueden expresarse fácilmente mediante pequeños escenarios de cuándo-entonces:
dado un usuario inactivo
cuando el administrador activa a este usuario
, el usuario se activa
y se envía un correo electrónico de confirmación al usuario
y se agrega una entrada al registro del sistema
(etc., etc.)
Tales escenarios son útiles para ver cómo diferentes partes de su infraestructura pueden verse afectadas por un solo comando, en este caso, su base de datos (algún tipo de bandera 'activa'), su servidor de correo, el registro de su sistema, etc.
Tal escenario también lo ayuda realmente a configurar un entorno de desarrollo dirigido por pruebas.
Y finalmente, pensar en comandos realmente te ayuda a crear una aplicación orientada a tareas. Sus usuarios apreciarán esto :-)
Expresando comandos
Django proporciona dos formas fáciles de expresar comandos; Ambos son opciones válidas y no es inusual mezclar los dos enfoques.
La capa de servicio
El módulo de servicio ya ha sido descrito por @Hedde . Aquí define un módulo separado y cada comando se representa como una función.
servicios.py
def activate_user(user_id):
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
Usando formularios
La otra forma es usar un formulario Django para cada comando. Prefiero este enfoque, porque combina múltiples aspectos estrechamente relacionados:
- ejecución del comando (¿qué hace?)
- validación de los parámetros del comando (¿puede hacer esto?)
- presentación del comando (¿cómo puedo hacer esto?)
formas.py
class ActivateUserForm(forms.Form):
user_id = IntegerField(widget = UsernameSelectWidget, verbose_name="Select a user to activate")
# the username select widget is not a standard Django widget, I just made it up
def clean_user_id(self):
user_id = self.cleaned_data['user_id']
if User.objects.get(pk=user_id).active:
raise ValidationError("This user cannot be activated")
# you can also check authorizations etc.
return user_id
def execute(self):
"""
This is not a standard method in the forms API; it is intended to replace the
'extract-data-from-form-in-view-and-do-stuff' pattern by a more testable pattern.
"""
user_id = self.cleaned_data['user_id']
user = User.objects.get(pk=user_id)
# set active flag
user.active = True
user.save()
# mail user
send_mail(...)
# etc etc
Pensando en consultas
Su ejemplo no contenía ninguna consulta, así que me tomé la libertad de hacer algunas consultas útiles. Prefiero usar el término "pregunta", pero las consultas es la terminología clásica. Las consultas interesantes son: "¿Cuál es el nombre de este usuario?", "¿Puede este usuario iniciar sesión?", "Mostrarme una lista de usuarios desactivados" y "¿Cuál es la distribución geográfica de los usuarios desactivados?"
Antes de embarcarse en responder estas preguntas, siempre debe hacerse dos preguntas: ¿es esta una consulta de presentación solo para mis plantillas, y / o una consulta de lógica de negocios vinculada a la ejecución de mis comandos, y / o una consulta de informes ?
Las consultas de presentación se realizan simplemente para mejorar la interfaz de usuario. Las respuestas a las consultas de lógica de negocios afectan directamente la ejecución de sus comandos. Las consultas de informes son meramente para fines analíticos y tienen limitaciones de tiempo más flexibles. Estas categorías no son mutuamente excluyentes.
La otra pregunta es: "¿Tengo control completo sobre las respuestas?" Por ejemplo, al consultar el nombre del usuario (en este contexto) no tenemos ningún control sobre el resultado, porque confiamos en una API externa.
Haciendo consultas
La consulta más básica en Django es el uso del objeto Manager:
User.objects.filter(active=True)
Por supuesto, esto solo funciona si los datos están realmente representados en su modelo de datos. Este no es siempre el caso. En esos casos, puede considerar las siguientes opciones.
Etiquetas y filtros personalizados
La primera alternativa es útil para consultas que son simplemente de presentación: etiquetas personalizadas y filtros de plantilla.
template.html
<h1>Welcome, {{ user|friendly_name }}</h1>
template_tags.py
@register.filter
def friendly_name(user):
return remote_api.get_cached_name(user.id)
Métodos de consulta
Si su consulta no es simplemente de presentación, puede agregar consultas a sus services.py (si está usando eso), o introducir un módulo queries.py :
queries.py
def inactive_users():
return User.objects.filter(active=False)
def users_called_publysher():
for user in User.objects.all():
if remote_api.get_cached_name(user.id) == "publysher":
yield user
Modelos proxy
Los modelos proxy son muy útiles en el contexto de la lógica empresarial y los informes. Básicamente, define un subconjunto mejorado de su modelo. Puede anular el QuerySet base de un Administrador anulando el Manager.get_queryset()
método.
modelos.py
class InactiveUserManager(models.Manager):
def get_queryset(self):
query_set = super(InactiveUserManager, self).get_queryset()
return query_set.filter(active=False)
class InactiveUser(User):
"""
>>> for user in InactiveUser.objects.all():
… assert user.active is False
"""
objects = InactiveUserManager()
class Meta:
proxy = True
Modelos de consulta
Para consultas que son inherentemente complejas, pero que se ejecutan con bastante frecuencia, existe la posibilidad de modelos de consulta. Un modelo de consulta es una forma de desnormalización donde los datos relevantes para una sola consulta se almacenan en un modelo separado. El truco, por supuesto, es mantener el modelo desnormalizado sincronizado con el modelo primario. Los modelos de consulta solo se pueden usar si los cambios están completamente bajo su control.
modelos.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
La primera opción es actualizar estos modelos en sus comandos. Esto es muy útil si estos modelos solo se cambian con uno o dos comandos.
formas.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Una mejor opción sería utilizar señales personalizadas. Estas señales son, por supuesto, emitidas por sus comandos. Las señales tienen la ventaja de que puede mantener múltiples modelos de consulta sincronizados con su modelo original. Además, el procesamiento de la señal se puede descargar a tareas en segundo plano, utilizando Celery o marcos similares.
señales.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
formas.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
modelos.py
class InactiveUserDistribution(models.Model):
# see above
@receiver(user_activated)
def on_user_activated(sender, **kwargs):
user = kwargs['user']
query_model = InactiveUserDistribution.objects.get_or_create(country=user.country)
query_model.inactive_user_count -= 1
query_model.save()
Manteniéndolo limpio
Al usar este enfoque, resulta ridículamente fácil determinar si su código se mantiene limpio. Simplemente siga estas pautas:
- ¿Mi modelo contiene métodos que hacen más que administrar el estado de la base de datos? Deberías extraer un comando.
- ¿Mi modelo contiene propiedades que no se asignan a los campos de la base de datos? Deberías extraer una consulta.
- ¿Mi modelo hace referencia a la infraestructura que no es mi base de datos (como el correo)? Deberías extraer un comando.
Lo mismo ocurre con las vistas (porque las vistas a menudo sufren el mismo problema).
- ¿Mi vista gestiona activamente los modelos de bases de datos? Deberías extraer un comando.
Algunas referencias
Documentación de Django: modelos proxy
Documentación de Django: señales
Arquitectura: diseño dirigido por dominios