sql >> Database >  >> RDS >> Database

Plezier met de nieuwe Postgres-functies van Django

Deze blogpost behandelt het gebruik van de nieuwe PostgreSQL-specifieke ModelFields die zijn geïntroduceerd in Django 1.8 - de ArrayField-, HStoreField- en Range-velden.

Dit bericht is opgedragen aan de geweldige donateurs van deze Kickstarter-campagne, samengesteld door Marc Tamlyn, de echte playa die het mogelijk heeft gemaakt.


Playaz Club?

Omdat ik een enorme nerd ben en geen kans heb om ooit in een echte Playaz Club te komen (en omdat vroeger 4 Tay de bom was), besloot ik mijn eigen virtuele online Playaz Club te bouwen. Wat is dat precies? Een privé, alleen op uitnodiging toegankelijk sociaal netwerk gericht op een kleine groep gelijkgestemde individuen.

Voor dit bericht gaan we ons concentreren op het gebruikersmodel en onderzoeken hoe de nieuwe PostgreSQL-functies van Django de modellering ondersteunen. De nieuwe functies waarnaar we verwijzen zijn alleen voor PostgreSQL, dus probeer dit niet tenzij u uw database ENGINE heeft gelijk aan django.db.backends.postgresql_psycopg2 . Je hebt versie>=2.5 van psycopg2 . nodig . Aight playa, laten we dit doen.

Holla als je met me meegaat! :)



Een vertegenwoordiger van Playa modelleren

Elke playa heeft een vertegenwoordiger, en ze willen dat de hele wereld hun vertegenwoordiger kent. Laten we dus een gebruikersprofiel maken (ook wel een 'rep' genoemd) waarmee al onze playaz hun individualiteit kan uiten.

Dit is het basismodel voor een playaz-vertegenwoordiger:

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()

Niets specifieks voor 1.8 hierboven. Gewoon een standaardmodel om de basis Django-gebruiker uit te breiden, want een playa heeft nog steeds een gebruikersnaam en e-mailadres nodig, toch? Bovendien hebben we twee nieuwe velden toegevoegd om de playaz-kap en het netnummer op te slaan.



Bankroll en het RangeField

Voor een playa is je kap niet altijd genoeg. Playaz pronkt vaak met hun bankroll, maar wil mensen tegelijkertijd niet laten weten hoe groot die bankroll is. Dat kunnen we modelleren met een van de nieuwe Postgres Range Fields. Natuurlijk gebruiken we het BigIntegerRangeField om massieve cijfers beter te modelleren, amiright?

bankroll = pgfields.BigIntegerRangeField(default=(10, 100))

Bereikvelden zijn gebaseerd op de psycopg2 Range-objecten en kunnen worden gebruikt voor numerieke en datumbereiken. Nu het bankroll-veld naar de database is gemigreerd, kunnen we communiceren met onze bereikvelden door er een bereikobject aan door te geven, dus het maken van onze eerste playa zou er ongeveer zo uitzien:

>>>
>>> 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()

Let op deze regel:calvins_rep.bankroll = (100000000, 150000000) . Hier stellen we een bereikveld in met behulp van een eenvoudige tuple. Het is ook mogelijk om de waarde in te stellen met een NumericRange object als volgt:

from psycopg2.extras import NumericRange
br = NumericRange(lower=100000000, upper=150000000)
calvin.rep.bankroll = br
calvin.rep.save()

Dit is in wezen hetzelfde als het gebruik van de tuple. Het is echter belangrijk om te weten over de NumericRange object zoals dat wordt gebruikt om het model te filteren. Als we bijvoorbeeld alle playa's willen vinden waarvan de bankroll groter is dan 50 miljoen (wat betekent dat het hele bankrollbereik groter is dan 50 miljoen):

Rep.objects.filter(bankroll__fully_gt=NumericRange(50000000, 50000000))

En dat zal de lijst met die playa's teruggeven. Als alternatief, als we alle playa's willen vinden waarvan de bankroll "ergens rond de 10 tot 15 miljoen ligt", kunnen we gebruiken:

Rep.objects.filter(bankroll__overlap=NumericRange(10000000, 15000000))

Dit zou alle playa's teruggeven die een bankrollbereik hebben dat ten minste een deel van het bereik van 10 tot 15 miljoen omvat. Een meer absolute vraag zou zijn alle playa's met een bankroll die volledig tussen een range ligt, d.w.z. iedereen die minstens 10 miljoen maar niet meer dan 15 miljoen verdient. Die zoekopdracht zou er als volgt uitzien:

Rep.objects.filter(bankroll__contained_by=NumericRange(10000000, 15000000))

Meer informatie over op bereik gebaseerde zoekopdrachten vindt u hier.



Skillz als ArrayField

Het gaat niet alleen om de bankroll, playaz heeft skillz, allerlei soorten skillz. Laten we die met een ArrayField modelleren.

skillz = pgfields.ArrayField(
    models.CharField(max_length=100, blank=True),
    blank = True,
    null = True,
)

Het ArrayField declareren we moeten het een eerste argument geven, namelijk het basisveld. In tegenstelling tot Python-lijsten, moet ArrayFields elk element van de lijst als hetzelfde type declareren. Basefield verklaart welk type dit is, en het kan elk van de standaard veldtypes zijn. In het bovenstaande geval hebben we zojuist een CharField . gebruikt als ons basistype, wat skillz . betekent zal een array van strings zijn.

Waarden opslaan in het ArrayField is precies zoals je verwacht:

>>>
>>> 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()

Playa's zoeken door skillz

Als we een bepaalde playa met een bepaalde vaardigheid nodig hebben, hoe gaan we die dan vinden? Gebruik de __contains filter:

Rep.objects.filter(skillz__contains=['rappin'])

Voor playa's die een van de vaardigheden ['rappin', 'djing', 'producing'] maar geen andere vaardigheden hebben, kun je een zoekopdracht als volgt doen:

Rep.objects.filter(skillz__contained_by=['rappin', 'djing', 'producing'])

Of als je iemand wilt vinden die een bepaalde lijst met vaardigheden heeft:

Rep.objects.filter(skillz__overlap=['rappin', 'djing', 'producing'])

Je zou zelfs alleen die mensen kunnen vinden die een vaardigheid als hun eerste vaardigheid hebben vermeld (omdat iedereen hun beste vaardigheid eerst vermeldt):

Rep.objects.filter(skillz__0='ballin')



Game als HStore

Spel kan worden gezien als een lijst met diverse, willekeurige vaardigheden die een playa zou kunnen hebben. Aangezien Game van alles omvat, laten we het modelleren als een HStore-veld, wat in feite betekent dat we elk oud Python-woordenboek daarin kunnen plaatsen:

game = pgfields.HStoreField()

Denk even na over wat we hier net hebben gedaan. HStore is behoorlijk groot. Het maakt in principe "NoSQL" -type gegevensopslag mogelijk, precies binnen postgreSQL. En omdat het zich in PostgreSQL bevindt, kunnen we (via externe sleutels) tabellen die NoSQL-gegevens bevatten, koppelen aan tabellen die gewone SQL-gegevens opslaan. Je kunt ze zelfs allebei in dezelfde tabel op verschillende kolommen opslaan, zoals we hier doen. Misschien hoeven playas die jankie, all-talk MongoDB niet meer te gebruiken...

Terugkomend op implementatiedetails, als u probeert het nieuwe HStore-veld naar de database te migreren en deze fout krijgt-

django.db.utils.ProgrammingError: type "hstore" does not exist

-dan is uw PostgreSQL-database pre 8.1 (time to upgrade, playa) of is de HStore-extensie niet geïnstalleerd. Houd er rekening mee dat in PostgreSQL de HStore-extensie per database wordt geïnstalleerd en niet voor het hele systeem. Om het te installeren vanaf uw psql-prompt, voert u de volgende SQL uit:

CREATE EXTENSION hstore

Of als u wilt, kunt u dit doen via een SQL-migratie met het volgende migratiebestand (ervan uitgaande dat u als supergebruiker met de database bent verbonden):

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = []

    operations = [
        migrations.RunSQL("CREATE EXTENSION IF NOT EXISTS hstore")
    ]

Ten slotte moet je er ook voor zorgen dat je 'django.contrib.postgres' hebt toegevoegd naar 'settings.INSTALLED_APPS' om gebruik te maken van HStore-velden.

Met die setup kunnen we gegevens toevoegen aan ons HStoreField game door er een woordenboek tegenaan te gooien als volgt:

>>>
>>> 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()

Houd er rekening mee dat het dictaat alleen tekenreeksen mag gebruiken voor alle sleutels en waarden.

En nu wat meer interessante voorbeelden...



Propz

Laten we een "show game" -functie schrijven om het playaz-spel te doorzoeken en een lijst met playaz die overeenkomen terug te sturen. In geeky-termen zoeken we door het HStore-veld naar sleutels die aan de functie zijn doorgegeven. Het ziet er ongeveer zo uit:

def show_game(key):
    return Rep.Objects.filter(game__has_key=key).values('game','playa__username')

Hierboven hebben we de has_key . gebruikt filter voor het HStore-veld om een ​​queryset te retourneren, en vervolgens verder gefilterd met de waardenfunctie (voornamelijk om te laten zien dat je django.contrib.postgres kunt koppelen dingen met gewone zoekopdrachten).

De retourwaarde zou een lijst met woordenboeken zijn:

[
  {'playa__username': 'snoop',
  'game': {'twitter_follows': '11000000',
           'youtube-channel': 'https://www.youtube.com/user/westfesttv',
           'best_album': 'Doggy Style'
        }
  }
]

Zoals ze zeggen, Game herkent Game, en nu kunnen we ook naar games zoeken.



Highrollers

Als we geloven wat de playaz ons vertellen over hun bankrolls, dan kunnen we dat gebruiken om ze in categorieën te rangschikken (omdat het een range is). Laten we een Playa-ranglijst toevoegen op basis van de bankroll met de volgende niveaus:

  • jong geld – bankroll minder dan honderdduizend

  • balla – bankroll tussen 100.000 en 500.000 met de vaardigheid 'ballin'

  • playa – bankroll tussen 500.000 en 1.000.000 met twee skillz en wat spel

  • high roller – bankroll groter dan 1.000.000

  • OG – heeft de vaardigheid ‘gangsta’ en game key ‘old-school’

De vraag voor balla staat hieronder. Dit zou de strikte interpretatie zijn, die alleen degenen zou retourneren wiens hele bankrollbereik binnen de gespecificeerde limieten ligt:

Rep.objects.filter(bankroll__contained_by=[100000, 500000], skillz__contains=['ballin'])

Probeer de rest zelf uit om te oefenen. Als je hulp nodig hebt, lees dan de documenten.



  1. Verkrijg de korte dagnaam in PostgreSQL

  2. Hoe verwijder ik specifieke rijen in SQLite Database?

  3. Hoe het verschil tussen twee datums in T-SQL te berekenen

  4. Entiteitsframework erg traag om voor de eerste keer te laden na elke compilatie