Python >> Programma Python >  >> Python

Modelli Django avanzati:migliora il tuo sviluppo Python

Introduzione

I modelli sono un concetto fondamentale del framework Django. Secondo le filosofie di progettazione di Django per i modelli, dovremmo essere il più espliciti possibile con la denominazione e la funzionalità dei nostri campi e assicurarci di includere tutte le funzionalità rilevanti relative al nostro modello nel modello stesso, piuttosto che nelle viste o da qualche parte altro. Se hai già lavorato con Ruby on Rails, queste filosofie di progettazione non sembreranno nuove poiché sia ​​Rails che Django implementano il modello Active Record per i loro sistemi di mappatura relazionale a oggetti (ORM) per gestire i dati archiviati.

In questo post esamineremo alcuni modi per sfruttare queste filosofie, le funzionalità di base di Django e persino alcune librerie per migliorare i nostri modelli.

getter/setter/deleter proprietà

Come caratteristica di Python dalla versione 2.2, l'utilizzo di una proprietà sembra un attributo ma in realtà è un metodo. Sebbene l'utilizzo di una proprietà su un modello non sia così avanzato, possiamo utilizzare alcune funzionalità sottoutilizzate della proprietà Python per rendere i nostri modelli più potenti.

Se stai utilizzando l'autenticazione integrata di Django o hai personalizzato la tua autenticazione utilizzando AbstractBaseUser , probabilmente hai familiarità con last_login campo definito in User modello, che è un timestamp salvato dell'ultimo accesso dell'utente alla tua applicazione. Se vogliamo usare last_login , ma hanno anche un campo chiamato last_seen salvato in una cache più frequentemente, potremmo farlo abbastanza facilmente.

Per prima cosa creeremo una proprietà Python che trova un valore nella cache e, se non può, restituisce il valore dal database.

accounts/models.py

from django.contrib.auth.base_user import AbstractBaseUser
from django.core.cache import cache


class User(AbstractBaseUser):
...

@property
def last_seen(self):
"""
Returns the 'last_seen' value from the cache for a User.
"""
last_seen = cache.get('last_seen_{0}'.format(self.pk))

# Check cache result, otherwise return the database value
if last_seen:
return last_seen

return self.last_login

Nota:ho snellito un po' il modello poiché c'è un tutorial separato su questo blog sulla personalizzazione specifica del modello utente Django integrato.

La proprietà sopra controlla la nostra cache per last_seen dell'utente valore e, se non trova nulla, restituirà il last_login memorizzato dall'utente valore dal modello. Fare riferimento a <instance>.last_seen ora fornisce un attributo molto più personalizzabile sul nostro modello dietro un'interfaccia molto semplice.

Possiamo espandere questo per includere il comportamento personalizzato quando un valore viene assegnato alla nostra proprietà (some_user.last_seen = some_date_time ) o quando un valore viene eliminato dalla proprietà (del some_user.last_seen ).

...

@last_seen.setter
def last_seen(self, value):
"""
Sets the 'last_seen_[uuid]' value in the cache for a User.
"""
now = value

# Save in the cache
cache.set('last_seen_{0}'.format(self.pk), now)

@last_seen.deleter
def last_seen(self):
"""
Removes the 'last_seen' value from the cache.
"""
# Delete the cache key
cache.delete('last_seen_{0}'.format(self.pk))

...

Ora, ogni volta che viene assegnato un valore al nostro last_seen proprietà, lo salviamo nella cache e quando un valore viene rimosso con del , lo rimuoviamo dalla cache. Usando setter e deleter è descritto nella documentazione di Python ma è raramente visto in natura quando si guardano i modelli di Django.

Potresti avere un caso d'uso come questo, in cui desideri archiviare qualcosa che non deve necessariamente essere mantenuto in un database tradizionale o, per motivi di prestazioni, non dovrebbe esserlo. L'utilizzo di una proprietà personalizzata come l'esempio sopra è un'ottima soluzione.

In un caso d'uso simile, python-social-auth library, uno strumento per la gestione dell'autenticazione dell'utente utilizzando piattaforme di terze parti come GitHub e Twitter, creerà e gestirà l'aggiornamento delle informazioni nel database in base alle informazioni dalla piattaforma con cui l'utente ha effettuato l'accesso. In alcuni casi, le informazioni restituite non corrispondono ai campi del nostro database. Ad esempio, python-social-auth la libreria passerà un fullname argomento della parola chiave durante la creazione dell'utente. Se, magari nel nostro database, abbiamo usato full_name come nome dell'attributo, potremmo essere in difficoltà.

Un modo semplice per aggirare questo problema è usare il getter/setter schema dall'alto:

@property
def fullname(self) -> str:
return self.full_name

@fullname.setter
def fullname(self, value: str):
self.full_name = value

Ora, quando python-social-auth salva il fullname di un utente al nostro modello (new_user.fullname = 'Some User' ), lo intercetteremo e lo salveremo nel campo del nostro database, full_name , invece.

through relazioni modello

Le relazioni molti-a-molti di Django sono un ottimo modo per gestire in modo semplice relazioni oggettuali complesse, ma non ci danno la possibilità di aggiungere attributi personalizzati ai intermediate models loro creano. Per impostazione predefinita, questo include semplicemente un identificatore e due riferimenti di chiave esterna per unire gli oggetti insieme.

Utilizzando Django ManyToManyField through parametro, possiamo creare noi stessi questo modello intermedio e aggiungere eventuali campi aggiuntivi che riteniamo necessari.

Se la nostra applicazione, ad esempio, non solo avesse bisogno che gli utenti avessero abbonamenti all'interno dei gruppi, ma volesse tenere traccia dell'inizio dell'iscrizione, potremmo utilizzare un modello intermedio personalizzato per farlo.

accounts/models.py

import uuid

from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models
from django.utils.timezone import now


class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)


class Group(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
members = models.ManyToManyField(User, through='Membership')

class Membership(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
joined = models.DateTimeField(editable=False, default=now)

Nell'esempio sopra, stiamo ancora utilizzando un ManyToManyField per gestire la relazione tra un utente e un gruppo, ma passando il Membership modello utilizzando il through argomento della parola chiave, ora possiamo aggiungere il nostro joined attributo personalizzato al modello per monitorare quando è stata avviata l'appartenenza al gruppo. Questo through model è un modello Django standard, richiede solo una chiave primaria (qui utilizziamo gli UUID) e due chiavi esterne per unire gli oggetti.

Utilizzando lo stesso modello di tre modelli, potremmo creare un semplice database di abbonamento per il nostro sito:

import uuid

from django.contrib.auth.base_user import AbstractBaseUser
from django.db import models
from django.utils.timezone import now


class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
...

class Plan(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=50, unique=True, default='free')
subscribers = models.ManyToManyField(User, through='Subscription', related_name='subscriptions', related_query_name='subscriptions')

class Subscription(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.CASCADE)
plan = models.ForeignKey(Plan, on_delete=models.CASCADE)
created = models.DateTimeField(editable=False, default=now)
updated = models.DateTimeField(auto_now=True)
cancelled = models.DateTimeField(blank=True, null=True)

Qui siamo in grado di monitorare quando un utente si è iscritto per la prima volta, quando ha aggiornato il proprio abbonamento e se abbiamo aggiunto i percorsi del codice per esso, quando un utente ha annullato l'abbonamento alla nostra applicazione.

Usando through modelli con ManyToManyField è un ottimo modo per aggiungere più dati ai nostri modelli intermedi e fornire un'esperienza più completa ai nostri utenti senza molto lavoro aggiuntivo.

Modelli proxy

Normalmente in Django, quando sottoclassi un modello (questo non include modelli astratti ) in una nuova classe, il framework creerà nuove tabelle di database per quella classe e le collegherà (tramite OneToOneField ) alle tabelle del database padre. Django chiama questa "ereditarietà multi-tabella" ed è un ottimo modo per riutilizzare i campi e le strutture del modello esistenti e aggiungervi i propri dati. “Non ripeterti”, come affermano le filosofie del design di Django.

Esempio di eredità multitabella:

from django.db import models

class Vehicle(models.Model):
model = models.CharField(max_length=50)
manufacturer = models.CharField(max_length=80)
year = models.IntegerField(max_length=4)

class Airplane(Vehicle):
is_cargo = models.BooleanField(default=False)
is_passenger = models.BooleanField(default=True)

Questo esempio creerebbe entrambi vehicles_vehicle e vehicles_airplane tabelle di database, collegate con chiavi esterne. Questo ci consente di sfruttare i dati esistenti che risiedono all'interno di vehicles_vehicle , aggiungendo i nostri attributi specifici del veicolo a ciascuna sottoclasse, vehicle_airplane , in questo caso.

In alcuni casi d'uso, potrebbe non essere necessario archiviare dati aggiuntivi. Invece, potremmo modificare alcuni dei comportamenti del modello genitore, magari aggiungendo un metodo, una proprietà o un gestore del modello. Qui è dove proxy models splendore. Proxy models permetterci di cambiare il comportamento Python di un modello senza modifica del database.

vehicles/models.py

from django.db import models

class Car(models.Model):
vin = models.CharField(max_length=17)
model = models.CharField(max_length=50)
manufacturer = models.CharField(max_length=80)
year = models.IntegerField(max_length=4)
...

class HondaManager(models.Manager):
def get_queryset(self):
return super(HondaManager, self).get_queryset().filter(model='Honda')

class Honda(Car):
objects = HondaManager()

class Meta:
proxy = True

@property
def is_domestic(self):
return False

def get_honda_service_logs(self):
...

Proxy models sono dichiarati proprio come i normali modelli. Nel nostro esempio, diciamo a Django che Honda è un proxy model impostando il proxy attributo della Honda Meta classe su True . Ho aggiunto una proprietà e un esempio di stub di metodo, ma puoi vedere che abbiamo aggiunto un gestore di modelli personalizzato al nostro proxy model Honda .

Ciò garantisce che ogni volta che richiediamo oggetti dal database utilizzando il nostro modello Honda, otteniamo solo Car istanze dove model= 'Honda' . I modelli proxy ci consentono di aggiungere rapidamente personalizzazioni ai modelli esistenti utilizzando gli stessi dati. Se dovessimo eliminare, creare o aggiornare qualsiasi Car utilizzando il nostro modello o manager Honda, verrebbe salvato nel vehicles_car database proprio come se stessimo usando il genitore (Car ) classe.

Concludi

Se ti senti già a tuo agio con le classi Python, ti sentirai come a casa con i modelli di Django:ereditarietà, ereditarietà multipla, sostituzioni di metodi e introspezione. Questi modelli fanno tutti parte del modo in cui è stato progettato il mappatore relazionale di oggetti Django.

L'ereditarietà multi-tabella e la definizione manuale di tabelle intermedie per i join SQL non sono necessariamente concetti di base, ma sono implementati semplicemente con un po' di know-how di Django e Python. Essere in grado di sfruttare le funzionalità del linguaggio e del framework insieme è uno dei motivi per cui Django è un framework web popolare.

Per ulteriori letture, consulta l'argomento della documentazione di Django per i modelli. La seconda metà della pagina copre parte di ciò che abbiamo trattato qui e altro ancora:imparo sempre qualcosa di nuovo quando leggo una pagina dei loro argomenti di alto livello.