Questo post del blog spiega come utilizzare i nuovi ModelField specifici di PostgreSQL introdotti in Django 1.8:ArrayField, HStoreField e Range Fields.
Questo post è dedicato ai fantastici sostenitori di questa campagna Kickstarter messa insieme da Marc Tamlyn, il vero playa che l'ha realizzata.
Playaz Club?
Dato che sono un grande fanatico e non ho alcuna possibilità di entrare in un vero Playaz Club (e perché nel giorno 4 Tay era la bomba), ho deciso di costruire il mio Playaz Club online virtuale. Cos'è esattamente? Un social network privato, solo su invito, rivolto a un piccolo gruppo di persone che la pensano allo stesso modo.
Per questo post, ci concentreremo sul modello utente ed esploreremo come le nuove funzionalità PostgreSQL di Django supportano la modellazione. Le nuove funzionalità a cui ci riferiamo sono solo PostgreSQL, quindi non preoccuparti di provarlo a meno che tu non abbia il tuo database ENGINE
uguale a django.db.backends.postgresql_psycopg2
. Avrai bisogno della versione>=2.5 di psycopg2
. Aight playa, facciamolo.
Ciao se sei con me! :)
Modellazione di un rappresentante di Playa
Ogni playa ha una reputazione e vogliono che il mondo intero sappia della sua reputazione. Creiamo quindi un profilo utente (noto anche come "rappresentante") che consenta a ciascuno dei nostri playaz di esprimere la propria individualità.
Ecco il modello base per un rappresentante playaz:
from django.db import models
from django.contrib.auth.models import User
class Rep(models.Model):
playa = models.OneToOneField(User)
hood = models.CharField(max_length=100)
area_code = models.IntegerField()
Niente di specifico per 1.8 sopra. Solo un modello standard per estendere l'utente Django di base, perché un playa ha ancora bisogno di un nome utente e un indirizzo e-mail, giusto? Inoltre abbiamo aggiunto due nuovi campi per memorizzare il cappuccio playaz e il prefisso.
Il bankroll e il RangeField
Per una playa, rifare il cofano non è sempre abbastanza. A Playaz piace spesso ostentare il proprio bankroll, ma allo stesso tempo non vuole far sapere alle persone esattamente quanto è grande quel bankroll. Possiamo modellarlo con uno dei nuovi campi della gamma Postgres. Ovviamente useremo il BigIntegerRangeField
per modellare meglio cifre enormi, giusto?
bankroll = pgfields.BigIntegerRangeField(default=(10, 100))
I campi Intervallo si basano sugli oggetti Intervallo psycopg2 e possono essere utilizzati per Numeric e DateRanges. Con il campo bankroll migrato nel database, possiamo interagire con i nostri campi range passandogli un oggetto range, quindi la creazione della nostra prima playa sarebbe simile a questa:
>>>>>> from playa.models import Rep
>>> from django.contrib.auth.models import User
>>> calvin = User.objects.create_user(username="snoop", password="dogg")
>>> calvins_rep = Rep(hood="Long Beach", area_code=213)
>>> calvins_rep.bankroll = (100000000, 150000000)
>>> calvins_rep.playa = calvin
>>> calvins_rep.save()
Nota questa riga:calvins_rep.bankroll = (100000000, 150000000)
. Qui stiamo impostando un campo di intervallo usando una semplice tupla. È anche possibile impostare il valore utilizzando un NumericRange
oggetto in questo modo:
from psycopg2.extras import NumericRange
br = NumericRange(lower=100000000, upper=150000000)
calvin.rep.bankroll = br
calvin.rep.save()
Questo è essenzialmente lo stesso che usare la tupla. Tuttavia, è importante conoscere il NumericRange
oggetto in quanto viene utilizzato per filtrare il modello. Ad esempio, se volessimo trovare tutte le playa il cui bankroll era maggiore di 50 milioni (il che significa che l'intero range di bankroll è maggiore di 50 milioni):
Rep.objects.filter(bankroll__fully_gt=NumericRange(50000000, 50000000))
E questo restituirà l'elenco di quei playa. In alternativa, se volessimo trovare tutte le playa il cui bankroll è "da qualche parte intorno all'intervallo da 10 a 15 milioni", potremmo usare:
Rep.objects.filter(bankroll__overlap=NumericRange(10000000, 15000000))
Ciò restituirebbe tutte le playa che hanno un intervallo di bankroll che include almeno una parte dell'intervallo da 10 a 15 milioni. Una query più assoluta sarebbero tutte le playa che hanno un bankroll completamente compreso tra un intervallo, ovvero tutti quelli che stanno guadagnando almeno 10 milioni ma non più di 15 milioni. Quella query sarebbe simile a:
Rep.objects.filter(bankroll__contained_by=NumericRange(10000000, 15000000))
Ulteriori informazioni sulle query basate sull'intervallo sono disponibili qui.
Skillz come ArrayField
Non si tratta solo del bankroll, playaz ha skillz, tutti i tipi di skillz. Modelliamo quelli con un ArrayField.
skillz = pgfields.ArrayField(
models.CharField(max_length=100, blank=True),
blank = True,
null = True,
)
Per dichiarare l'ArrayField
dobbiamo dargli un primo argomento, che è il campo base. A differenza degli elenchi Python, ArrayFields deve dichiarare ogni elemento dell'elenco come lo stesso tipo. Basefield dichiara di che tipo si tratta e può essere qualsiasi tipo di campo modello standard. Nel caso precedente, abbiamo appena utilizzato un CharField
come nostro tipo di base, che significa skillz
sarà un array di stringhe.
Memorizzazione dei valori in ArrayField
è esattamente come ti aspetti:
>>> from django.contrib.auth.models import User
>>> calvin = User.objects.get(username='snoop')
>>> calvin.rep.skillz = ['ballin', 'rappin', 'talk show host', 'merchandizn']
>>> calvin.rep.save()
Trovare playas da skillz
Se abbiamo bisogno di una playa particolare con un'abilità particolare, come la troveremo? Usa i __contains
filtro:
Rep.objects.filter(skillz__contains=['rappin'])
Per i playa che hanno una delle abilità ['rappin', 'djing', 'producing'] ma nessun'altra abilità, puoi fare una domanda in questo modo:
Rep.objects.filter(skillz__contained_by=['rappin', 'djing', 'producing'])
O se vuoi trovare qualcuno che abbia una certa lista di abilità:
Rep.objects.filter(skillz__overlap=['rappin', 'djing', 'producing'])
Potresti anche trovare solo quelle persone che hanno indicato un'abilità come prima abilità (perché tutti elencano prima la loro abilità migliore):
Rep.objects.filter(skillz__0='ballin')
Gioco come HStore
Il gioco può essere pensato come un elenco di abilità varie e casuali che un playa potrebbe avere. Dal momento che il gioco copre tutti i tipi di cose, modelliamolo come un campo HStore, il che in pratica significa che possiamo inserire qualsiasi vecchio dizionario Python lì:
game = pgfields.HStoreField()
Prenditi un secondo per pensare a quello che abbiamo appena fatto qui. HStore è piuttosto enorme. Fondamentalmente consente l'archiviazione di dati di tipo "NoSQL", proprio all'interno di postgreSQL. Inoltre, poiché è all'interno di PostgreSQL, possiamo collegare (tramite chiavi esterne) tabelle che contengono dati NoSQL con tabelle che memorizzano dati di tipo SQL regolari. Puoi persino archiviare entrambi nella stessa tabella su colonne diverse come stiamo facendo qui. Forse i playa non hanno più bisogno di usare quel MongoDB jankie, all-talk...
Tornando ai dettagli di implementazione, se provi a migrare il nuovo campo HStore nel database e finisci con questo errore-
django.db.utils.ProgrammingError: type "hstore" does not exist
-quindi il tuo database PostgreSQL è precedente alla 8.1 (tempo di aggiornamento, playa) o non ha l'estensione HStore installata. Tieni presente che in PostgreSQL l'estensione HStore è installata per database e non a livello di sistema. Per installarlo dal prompt di psql, esegui il seguente SQL:
CREATE EXTENSION hstore
Oppure, se lo desideri, puoi farlo tramite una migrazione SQL con il seguente file di migrazione (supponendo che tu sia connesso al database come superutente):
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunSQL("CREATE EXTENSION IF NOT EXISTS hstore")
]
Infine, dovrai anche assicurarti di aver aggiunto 'django.contrib.postgres'
a 'settings.INSTALLED_APPS'
per utilizzare i campi di HStore.
Con questa configurazione possiamo aggiungere dati al nostro HStoreField
game
lanciandogli un dizionario in questo modo:
>>> calvin = User.objects.get(username="snoop")
>>> calvin.rep.game = {'best_album': 'Doggy Style', 'youtube-channel': \
'https://www.youtube.com/user/westfesttv', 'twitter_follows' : '11000000'}
>>> calvin.rep.save()
Tieni presente che il dict deve utilizzare solo stringhe per tutte le chiavi e i valori.
E ora per alcuni esempi più interessanti...
Adeguamento
Scriviamo una funzione "mostra gioco" per cercare il gioco playaz e restituire un elenco di playaz che corrispondono. In termini geek, stiamo cercando nel campo HStore tutte le chiavi passate nella funzione. Sembra qualcosa del genere:
def show_game(key):
return Rep.Objects.filter(game__has_key=key).values('game','playa__username')
Sopra abbiamo usato il has_key
filtro per il campo HStore per restituire un set di query, quindi filtrarlo ulteriormente con la funzione valori (principalmente per mostrare che puoi concatenare django.contrib.postgres
roba con roba del set di query normale).
Il valore restituito sarebbe un elenco di dizionari:
[
{'playa__username': 'snoop',
'game': {'twitter_follows': '11000000',
'youtube-channel': 'https://www.youtube.com/user/westfesttv',
'best_album': 'Doggy Style'
}
}
]
Come si suol dire, il gioco riconosce il gioco e ora possiamo anche cercare il gioco.
High Rollers
Se crediamo a ciò che i playaz ci dicono sui loro bankroll, possiamo usarlo per classificarli in categorie (dato che è un intervallo). Aggiungiamo una classifica Playa basata sul bankroll con i seguenti livelli:
-
giovane dollaro:bankroll inferiore a centomila
-
balla – bankroll tra 100.000 e 500.000 con l'abilità 'ballin'
-
playa – bankroll tra 500.000 e 1.000.000 con due skillz e un po' di gioco
-
high roller:bankroll superiore a 1.000.000
-
OG – ha l'abilità "gangsta" e la chiave del gioco "vecchia scuola"
La query per balla è sotto. Questa sarebbe l'interpretazione restrittiva, che restituirebbe solo quelli il cui intero range di bankroll rientra nei limiti specificati:
Rep.objects.filter(bankroll__contained_by=[100000, 500000], skillz__contains=['ballin'])
Prova tu stesso il resto per un po' di pratica. Se hai bisogno di aiuto, leggi i documenti.