Il semble que vous posiez des questions sur la différence entre le modèle de données et le modèle de domaine - ce dernier est l'endroit où vous pouvez trouver la logique métier et les entités telles que perçues par votre utilisateur final, le premier est l'endroit où vous stockez réellement vos données.
De plus, j'ai interprété la troisième partie de votre question comme: comment remarquer l'échec de garder ces modèles séparés.
Ce sont deux concepts très différents et il est toujours difficile de les séparer. Cependant, certains modèles et outils communs peuvent être utilisés à cette fin.
À propos du modèle de domaine
La première chose que vous devez reconnaître est que votre modèle de domaine ne concerne pas vraiment les données; il s'agit d' actions et de questions telles que "activer cet utilisateur", "désactiver cet utilisateur", "quels utilisateurs sont actuellement activés?" et "quel est le nom de cet utilisateur?". En termes classiques: il s'agit de requêtes et de commandes .
Penser en commandes
Commençons par regarder les commandes de votre exemple: "activer cet utilisateur" et "désactiver cet utilisateur". La bonne chose à propos des commandes est qu'elles peuvent facilement être exprimées par de petits scénarios donnés quand ils sont alors:
étant donné un utilisateur inactif
lorsque l'administrateur active cet utilisateur
alors l'utilisateur devient actif
et un e-mail de confirmation est envoyé à l'utilisateur
et une entrée est ajoutée dans le journal du système
(etc. , etc.)
De tels scénarios sont utiles pour voir comment différentes parties de votre infrastructure peuvent être affectées par une seule commande - dans ce cas, votre base de données (une sorte d'indicateur `` actif ''), votre serveur de messagerie, votre journal système, etc.
De tels scénarios vous aident également à configurer un environnement de développement piloté par les tests.
Et enfin, la réflexion dans les commandes vous aide vraiment à créer une application orientée tâche. Vos utilisateurs l'apprécieront :-)
Exprimer des commandes
Django propose deux façons simples d'exprimer des commandes; ce sont deux options valables et il n'est pas rare de mélanger les deux approches.
La couche service
Le module de service a déjà été décrit par @Hedde . Ici, vous définissez un module séparé et chaque commande est représentée comme une fonction.
services.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
Utilisation de formulaires
L'autre façon est d'utiliser un formulaire Django pour chaque commande. Je préfère cette approche, car elle combine plusieurs aspects étroitement liés:
- exécution de la commande (que fait-elle?)
- validation des paramètres de commande (est-ce possible?)
- présentation de la commande (comment faire?)
forms.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
Penser dans les requêtes
Votre exemple ne contenait aucune requête, j'ai donc pris la liberté de créer quelques requêtes utiles. Je préfère utiliser le terme «question», mais les requêtes sont la terminologie classique. Les requêtes intéressantes sont: "Quel est le nom de cet utilisateur?", "Cet utilisateur peut-il se connecter?", "Me montrer une liste d'utilisateurs désactivés" et "Quelle est la répartition géographique des utilisateurs désactivés?"
Avant de vous lancer dans la réponse à ces requêtes, vous devez toujours vous poser deux questions: s'agit-il d'une requête de présentation uniquement pour mes modèles, et / ou d'une requête de logique métier liée à l'exécution de mes commandes, et / ou d'une requête de rapport .
Les requêtes de présentation sont simplement faites pour améliorer l'interface utilisateur. Les réponses aux requêtes de logique métier affectent directement l'exécution de vos commandes. Les requêtes de rapport sont uniquement à des fins analytiques et ont des contraintes de temps plus lâches. Ces catégories ne s'excluent pas mutuellement.
L'autre question est: "ai-je un contrôle total sur les réponses?" Par exemple, lorsque nous demandons le nom de l'utilisateur (dans ce contexte), nous n'avons aucun contrôle sur le résultat, car nous nous appuyons sur une API externe.
Faire des requêtes
La requête la plus élémentaire dans Django est l'utilisation de l'objet Manager:
User.objects.filter(active=True)
Bien sûr, cela ne fonctionne que si les données sont réellement représentées dans votre modèle de données. Ce n'est pas toujours le cas. Dans ces cas, vous pouvez considérer les options ci-dessous.
Balises et filtres personnalisés
La première alternative est utile pour les requêtes qui sont simplement de présentation: balises personnalisées et filtres de modèle.
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éthodes de requête
Si votre requête n'est pas simplement de présentation, vous pouvez ajouter des requêtes à votre services.py (si vous l'utilisez), ou introduire un module 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
Modèles de proxy
Les modèles de proxy sont très utiles dans le contexte de la logique métier et du reporting. Vous définissez essentiellement un sous-ensemble amélioré de votre modèle. Vous pouvez remplacer le QuerySet de base d'un gestionnaire en remplaçant la Manager.get_queryset()
méthode.
models.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
Modèles de requête
Pour les requêtes qui sont intrinsèquement complexes, mais qui sont exécutées assez souvent, il existe la possibilité de modèles de requête. Un modèle de requête est une forme de dénormalisation dans laquelle les données pertinentes pour une seule requête sont stockées dans un modèle distinct. L'astuce est bien sûr de synchroniser le modèle dénormalisé avec le modèle principal. Les modèles de requête ne peuvent être utilisés que si les modifications sont entièrement sous votre contrôle.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
La première option consiste à mettre à jour ces modèles dans vos commandes. Ceci est très utile si ces modèles ne sont modifiés que par une ou deux commandes.
forms.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()
Une meilleure option serait d'utiliser des signaux personnalisés. Ces signaux sont bien entendu émis par vos commandes. Les signaux ont l'avantage de pouvoir synchroniser plusieurs modèles de requête avec votre modèle d'origine. De plus, le traitement du signal peut être déchargé sur des tâches d'arrière-plan, en utilisant Celery ou des cadres similaires.
signaux.py
user_activated = Signal(providing_args = ['user'])
user_deactivated = Signal(providing_args = ['user'])
forms.py
class ActivateUserForm(forms.Form):
# see above
def execute(self):
# see above
user_activated.send_robust(sender=self, user=user)
models.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()
Le garder propre
Lorsque vous utilisez cette approche, il devient ridiculement facile de déterminer si votre code reste propre. Suivez simplement ces directives:
- Mon modèle contient-il des méthodes qui font plus que gérer l'état de la base de données? Vous devez extraire une commande.
- Mon modèle contient-il des propriétés qui ne correspondent pas aux champs de la base de données? Vous devez extraire une requête.
- Mon modèle fait-il référence à une infrastructure qui n'est pas ma base de données (comme le courrier)? Vous devez extraire une commande.
Il en va de même pour les vues (car les vues souffrent souvent du même problème).
- Ma vue gère-t-elle activement les modèles de base de données? Vous devez extraire une commande.
Quelques références
Documentation Django: modèles proxy
Documentation Django: signaux
Architecture: conception pilotée par domaine