Sembra che ti stia chiedendo la differenza tra il modello di dati e il modello di dominio : quest'ultimo è dove puoi trovare la logica aziendale e le entità percepite dal tuo utente finale, il primo è dove memorizzi effettivamente i tuoi dati.
Inoltre, ho interpretato la terza parte della tua domanda come: come notare il fallimento nel mantenere separati questi modelli.
Questi sono due concetti molto diversi ed è sempre difficile tenerli separati. Tuttavia, ci sono alcuni modelli e strumenti comuni che possono essere utilizzati per questo scopo.
Informazioni sul modello di dominio
La prima cosa che devi riconoscere è che il tuo modello di dominio non riguarda davvero i dati; si tratta di azioni e domande come "attiva questo utente", "disattiva questo utente", "quali utenti sono attualmente attivati?" e "qual è il nome di questo utente?". In termini classici: si tratta di query e comandi .
Pensare nei comandi
Cominciamo guardando i comandi nel tuo esempio: "attiva questo utente" e "disattiva questo utente". La cosa bella dei comandi è che possono essere facilmente espressi da piccoli scenari dati quando-allora:
dato un utente inattivo
quando l'amministratore attiva questo utente
quindi l'utente diventa attiva
e una e-mail di conferma viene inviata all'utente
e viene aggiunta una voce al registro di sistema
(ecc ecc)
Tali scenari sono utili per vedere come diverse parti della tua infrastruttura possono essere influenzate da un singolo comando - in questo caso il tuo database (una sorta di flag 'attivo'), il tuo server di posta, il tuo registro di sistema, ecc.
Tale scenario ti aiuta davvero anche nella creazione di un ambiente di sviluppo guidato dai test.
Infine, pensare nei comandi ti aiuta davvero a creare un'applicazione orientata alle attività. I tuoi utenti lo apprezzeranno :-)
Esprimere i comandi
Django offre due semplici modi per esprimere i comandi; sono entrambe opzioni valide e non è insolito mescolare i due approcci.
Il livello di servizio
Il modulo di servizio è già stato descritto da @Hedde . Qui si definisce un modulo separato e ogni comando è rappresentato come una funzione.
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
Utilizzando i moduli
L'altro modo è usare un Django Form per ogni comando. Preferisco questo approccio, perché combina molteplici aspetti strettamente correlati:
- esecuzione del comando (cosa fa?)
- validazione dei parametri di comando (può farlo?)
- presentazione del comando (come posso fare questo?)
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
Pensando nelle query
Il tuo esempio non conteneva alcuna domanda, quindi mi sono preso la libertà di fare alcune utili domande. Preferisco usare il termine "domanda", ma query è la terminologia classica. Interessanti query sono: "Qual è il nome di questo utente?", "L'utente può accedere?", "Mostrami un elenco di utenti disattivati" e "Qual è la distribuzione geografica degli utenti disattivati?"
Prima di iniziare a rispondere a queste query, dovresti sempre porti due domande: è una query di presentazione solo per i miei modelli e / o una query di logica aziendale legata all'esecuzione dei miei comandi e / o una query di report .
Le query di presentazione sono fatte semplicemente per migliorare l'interfaccia utente. Le risposte alle query della logica di business influiscono direttamente sull'esecuzione dei comandi. Le query di reporting sono puramente a scopo analitico e presentano vincoli temporali più ampi. Queste categorie non si escludono a vicenda.
L'altra domanda è: "ho il controllo completo sulle risposte?" Ad esempio, quando si esegue una query sul nome dell'utente (in questo contesto) non abbiamo alcun controllo sul risultato, poiché ci affidiamo a un'API esterna.
Fare query
La query più semplice in Django è l'uso dell'oggetto Manager:
User.objects.filter(active=True)
Naturalmente, questo funziona solo se i dati sono effettivamente rappresentati nel modello di dati. Questo non è sempre il caso. In questi casi, puoi considerare le opzioni di seguito.
Tag e filtri personalizzati
La prima alternativa è utile per le query che sono puramente di presentazione: tag personalizzati e filtri modello.
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)
Metodi di query
Se la tua query non è semplicemente di presentazione, puoi aggiungere query al tuo services.py (se lo stai usando), o introdurre un modulo 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
Modelli proxy
I modelli proxy sono molto utili nel contesto della logica aziendale e del reporting. Fondamentalmente definisci un sottoinsieme migliorato del tuo modello. È possibile ignorare il QuerySet di base di un Manager ignorando il Manager.get_queryset()
metodo.
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
Modelli di query
Per query intrinsecamente complesse, ma eseguite abbastanza spesso, esiste la possibilità di modelli di query. Un modello di query è una forma di denormalizzazione in cui i dati rilevanti per una singola query sono memorizzati in un modello separato. Il trucco ovviamente è mantenere il modello denormalizzato in sincronia con il modello primario. I modelli di query possono essere utilizzati solo se le modifiche sono interamente sotto il tuo controllo.
models.py
class InactiveUserDistribution(models.Model):
country = CharField(max_length=200)
inactive_user_count = IntegerField(default=0)
La prima opzione è aggiornare questi modelli nei tuoi comandi. Questo è molto utile se questi modelli sono cambiati solo da uno o due comandi.
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()
Un'opzione migliore sarebbe quella di utilizzare segnali personalizzati. Questi segnali sono ovviamente emessi dai tuoi comandi. I segnali hanno il vantaggio di poter sincronizzare più modelli di query con il modello originale. Inoltre, l'elaborazione del segnale può essere scaricata su attività in background, utilizzando sedano o framework simili.
signals.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()
Mantenerlo pulito
Quando si utilizza questo approccio, diventa ridicolmente facile determinare se il codice rimane pulito. Segui queste linee guida:
- Il mio modello contiene metodi che fanno qualcosa in più rispetto alla gestione dello stato del database? Dovresti estrarre un comando.
- Il mio modello contiene proprietà che non si associano ai campi del database? È necessario estrarre una query.
- Il mio modello fa riferimento a un'infrastruttura che non è il mio database (come la posta)? Dovresti estrarre un comando.
Lo stesso vale per le viste (poiché le viste spesso soffrono dello stesso problema).
- La mia vista gestisce attivamente i modelli di database? Dovresti estrarre un comando.
Alcuni riferimenti
Documentazione Django: modelli proxy
Documentazione Django: segnali
Architettura: Domain Driven Design