Det här blogginlägget beskriver hur man använder de nya PostgreSQL-specifika ModelFields som introducerades i Django 1.8 - ArrayField, HStoreField och Range Fields.
Det här inlägget är tillägnat de fantastiska supportrarna av denna Kickstarter-kampanj sammansatt av Marc Tamlyn, den sanna playan som fick det att hända.
Playaz Club?
Eftersom jag är en stor nörd och inte har någon chans att någonsin komma in i en riktig Playaz-klubb (och eftersom Tay var bomben på den tiden) bestämde jag mig för att bygga min egen virtuella Playaz-klubb online. Vad är det exakt? Ett privat, inbjudningsbart socialt nätverk riktat till en liten grupp likasinnade.
För det här inlägget kommer vi att fokusera på användarmodellen och utforska hur Djangos nya PostgreSQL-funktioner stödjer modelleringen. De nya funktionerna vi hänvisar till är endast PostgreSQL, så försök inte prova detta om du inte har din databas ENGINE
lika med django.db.backends.postgresql_psycopg2
. Du behöver version>=2.5 av psycopg2
. Aight playa, låt oss göra det här.
Hallå om du är med mig! :)
Modellera en Playas representant
Varje playa fick en rep, och de vill att hela världen ska veta om deras rep. Så låt oss skapa en användarprofil (aka en "rep") som gör det möjligt för var och en av våra playaz att uttrycka sin individualitet.
Här är grundmodellen för en playaz-representant:
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()
Inget specifikt för 1.8 ovan. Bara en standardmodell för att utöka basanvändaren för Django, för en playa behöver fortfarande ett användarnamn och e-postadress, eller hur? Dessutom har vi lagt till två nya fält för att lagra playaz-kåpan och riktnummer.
Bankrulle och RangeField
För en playa räcker det inte alltid att repa din huva. Playaz gillar ofta att stoltsera med sin bankrulle, men vill samtidigt inte låta folk veta exakt hur stor bankrullen är. Vi kan modellera det med en av de nya Postgres Range Fields. Naturligtvis kommer vi att använda BigIntegerRangeField
för att bättre modellera massiva siffror, eller hur?
bankroll = pgfields.BigIntegerRangeField(default=(10, 100))
Områdesfält är baserade på psycopg2 Range-objekten och kan användas för numeriska och datumintervall. Med bankrullefältet migrerat till databasen kan vi interagera med våra intervallfält genom att skicka det ett intervallobjekt, så att skapa vår första playa skulle se ut ungefär så här:
>>>>>> 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()
Lägg märke till den här raden:calvins_rep.bankroll = (100000000, 150000000)
. Här ställer vi in ett avståndsfält genom att använda en enkel tupel. Det är också möjligt att ställa in värdet med ett NumericRange
objekt som så:
from psycopg2.extras import NumericRange
br = NumericRange(lower=100000000, upper=150000000)
calvin.rep.bankroll = br
calvin.rep.save()
Detta är i huvudsak samma sak som att använda tupeln. Det är dock viktigt att känna till NumericRange
objekt som det används för att filtrera modellen. Om vi till exempel ville hitta alla playas vars bankrulle var större än 50 miljoner (vilket innebär att hela bankrulleintervallet är större än 50 miljoner):
Rep.objects.filter(bankroll__fully_gt=NumericRange(50000000, 50000000))
Och det kommer att returnera listan över dessa playas. Alternativt, om vi ville hitta alla playas vars bankrulle är "någonstans runt intervallet 10 till 15 miljoner", kan vi använda:
Rep.objects.filter(bankroll__overlap=NumericRange(10000000, 15000000))
Detta skulle returnera alla playas som har ett bankrulleintervall som inkluderar åtminstone en del av intervallet 10 till 15 miljoner. En mer absolut fråga skulle vara alla playas som har en bankrulle helt mellan ett intervall, det vill säga alla som tjänar minst 10 miljoner men inte mer än 15 miljoner. Den frågan skulle se ut så här:
Rep.objects.filter(bankroll__contained_by=NumericRange(10000000, 15000000))
Mer information om intervallbaserade frågor finns här.
Skillz som ArrayField
Allt handlar inte om bankrullen, playaz fick skillz, alla typer av skillz. Låt oss modellera dem med ett ArrayField.
skillz = pgfields.ArrayField(
models.CharField(max_length=100, blank=True),
blank = True,
null = True,
)
För att deklarera ArrayField
vi måste ge det ett första argument, vilket är basfältet. Till skillnad från Python-listor måste ArrayFields deklarera varje element i listan som samma typ. Basefield deklarerar vilken typ detta är, och det kan vara vilken som helst av standardmodellfältstyperna. I fallet ovan har vi precis använt ett CharField
som vår bastyp, vilket betyder skillz
kommer att vara en rad strängar.
Lagra värden i ArrayField
är precis som du förväntar dig:
>>> 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()
Hitta playas efter skillz
Om vi behöver en viss playa med en viss färdighet, hur ska vi hitta dem? Använd __contains
filter:
Rep.objects.filter(skillz__contains=['rappin'])
För playas som har någon av färdigheterna ['rappin', 'djing', 'producera'] men inga andra färdigheter, kan du göra en fråga så här:
Rep.objects.filter(skillz__contained_by=['rappin', 'djing', 'producing'])
Eller om du vill hitta någon som har någon av en viss lista med färdigheter:
Rep.objects.filter(skillz__overlap=['rappin', 'djing', 'producing'])
Du kan till och med bara hitta de personer som listade en färdighet som sin första färdighet (eftersom alla listar sin bästa färdighet först):
Rep.objects.filter(skillz__0='ballin')
Spel som HStore
Spelet kan ses som en lista över diverse, slumpmässiga färdigheter som en playa kan ha. Eftersom Game spänner över alla möjliga saker, låt oss modellera det som ett HStore-fält, vilket i princip betyder att vi kan stoppa in vilken gammal Python-ordbok som helst där:
game = pgfields.HStoreField()
Ta en sekund att tänka på vad vi just gjorde här. HStore är ganska stort. Det tillåter i princip datalagring av "NoSQL"-typ, precis inuti postgreSQL. Plus, eftersom det är inuti PostgreSQL kan vi länka (via främmande nycklar), tabeller som innehåller NoSQL-data med tabeller som lagrar vanliga SQL-data. Du kan till och med lagra båda i samma tabell på olika kolumner som vi gör här. Playas kanske inte behöver använda den där jankie, all-talk MongoDB längre...
För att komma tillbaka till implementeringsdetaljerna, om du försöker migrera det nya HStore-fältet till databasen och får det här felet-
django.db.utils.ProgrammingError: type "hstore" does not exist
-Då är din PostgreSQL-databas pre 8.1 (dags att uppgradera, playa) eller har inte HStore-tillägget installerat. Tänk på att i PostgreSQL installeras HStore-tillägget per databas och inte hela systemet. För att installera det från din psql-prompt, kör följande SQL:
CREATE EXTENSION hstore
Eller om du vill kan du göra det genom en SQL-migrering med följande migreringsfil (förutsatt att du var ansluten till databasen som superanvändare):
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunSQL("CREATE EXTENSION IF NOT EXISTS hstore")
]
Slutligen måste du också se till att du har lagt till 'django.contrib.postgres'
till 'settings.INSTALLED_APPS'
att använda HStore-fält.
Med den inställningen kan vi lägga till data till vårt HStoreField
game
genom att kasta en ordbok på det så här:
>>> 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()
Tänk på att dict endast får använda strängar för alla nycklar och värden.
Och nu till några fler intressanta exempel...
Propz
Låt oss skriva en "visa spel"-funktion för att söka i playaz-spelet och returnera en lista med playaz som matchar. I nördiga termer söker vi genom HStore-fältet efter alla nycklar som skickas in i funktionen. Det ser ut ungefär så här:
def show_game(key):
return Rep.Objects.filter(game__has_key=key).values('game','playa__username')
Ovan har vi använt has_key
filtrera för HStore-fältet för att returnera en frågeuppsättning och filtrerade den sedan ytterligare med värdefunktionen (främst för att visa att du kan kedja django.contrib.postgres
saker med vanliga frågeuppsättningsgrejer).
Returvärdet skulle vara en lista med ordböcker:
[
{'playa__username': 'snoop',
'game': {'twitter_follows': '11000000',
'youtube-channel': 'https://www.youtube.com/user/westfesttv',
'best_album': 'Doggy Style'
}
}
]
Som de säger, Game känner igen Game, och nu kan vi söka efter spel också.
High Rollers
Om vi tror på vad playaz berättar om sina bankrullar, så kan vi använda det för att rangordna dem i kategorier (eftersom det är ett intervall). Låt oss lägga till en Playa-rankning baserat på bankrullen med följande nivåer:
-
young buck – bankrulle mindre än hundra tusenlappar
-
balla – bankrulle mellan 100 000 och 500 000 med färdigheten 'ballin'
-
playa – bankrulle mellan 500 000 och 1 000 000 med två skillz och lite spel
-
high roller – bankrulle större än 1 000 000
-
O.G. – har färdigheten 'gangsta' och spelnyckeln "old-school"
Frågan för balla finns nedan. Detta skulle vara den strikta tolkningen, som endast skulle returnera de vars hela bankrulleintervall ligger inom de angivna gränserna:
Rep.objects.filter(bankroll__contained_by=[100000, 500000], skillz__contains=['ballin'])
Testa resten själv för lite träning. Om du behöver hjälp, läs dokumenten.