Тази публикация в блога обхваща как да използвате новите специфични за PostgreSQL ModelFields, въведени в Django 1.8 – полетата ArrayField, HstoreField и Range.
Тази публикация е посветена на страхотните поддръжници на тази кампания на Kickstarter, съставена от Марк Тамлин, истинската игра, която я направи.
Playaz Club?
Тъй като съм голям маниак и нямам шанс някога да вляза в истински Playaz Club (и тъй като в деня 4 Tay беше бомбата), реших да създам свой собствен виртуален онлайн Playaz Club. Какво точно е това? Частна социална мрежа само с покани, насочена към малка група от съмишленици.
За тази публикация ще се съсредоточим върху потребителския модел и ще проучим как новите PostgreSQL функции на Django поддържат моделирането. Новите функции, за които говорим, са само за PostgreSQL, така че не се притеснявайте да опитвате това, освен ако нямате вашата база данни ENGINE
равно на django.db.backends.postgresql_psycopg2
. Ще ви трябва версия>=2.5 на psycopg2
. Aight playa, нека направим това.
Здравейте, ако сте с мен! :)
Моделиране на представител на Playa
Всяка playa има представител и искат целият свят да знае за тяхното представяне. Така че нека създадем потребителски профил (известен още като „представител“), който позволява на всеки от нашите playaz да изрази своята индивидуалност.
Ето основния модел за представител на 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()
Нищо конкретно за 1.8 по-горе. Просто стандартен модел за разширяване на основния потребител на Django, тъй като playa все още се нуждае от потребителско име и имейл адрес, нали? Освен това добавихме две нови полета за съхраняване на playaz hood и кода на зоната.
Bankroll и RangeField
За една игра, поправянето на качулката не винаги е достатъчно. Playaz често обичат да парадират с банката си, но в същото време не искат да уведомяват хората колко точно е този банкрол. Можем да моделираме това с едно от новите полета за обхват на Postgres. Разбира се, ще използваме BigIntegerRangeField
за по-добро моделиране на масивни цифри, нали?
bankroll = pgfields.BigIntegerRangeField(default=(10, 100))
Полетата за диапазон са базирани на обектите на диапазона psycopg2 и могат да се използват за числови и диапазони на дати. С мигрираното в базата данни полето за банкрол, ние можем да взаимодействаме с нашите полета за диапазон, като му предадем обект за диапазон, така че създаването на първата ни playa би изглеждало така:
>>>>>> 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()
Обърнете внимание на този ред:calvins_rep.bankroll = (100000000, 150000000)
. Тук задаваме поле за диапазон, като използваме обикновен кортеж. Възможно е също да зададете стойността с помощта на NumericRange
обект така:
from psycopg2.extras import NumericRange
br = NumericRange(lower=100000000, upper=150000000)
calvin.rep.bankroll = br
calvin.rep.save()
Това по същество е същото като използването на кортежа. Важно е обаче да знаете за NumericRange
обект, тъй като се използва за филтриране на модела. Например, ако искаме да намерим всички playas, чийто банкрол е по-голям от 50 милиона (което означава, че целият диапазон на банкрол е по-голям от 50 милиона):
Rep.objects.filter(bankroll__fully_gt=NumericRange(50000000, 50000000))
И това ще върне списъка с тези playas. Като алтернатива, ако искаме да намерим всички playas, чийто банкрол е „някъде около диапазона от 10 до 15 милиона“, бихме могли да използваме:
Rep.objects.filter(bankroll__overlap=NumericRange(10000000, 15000000))
Това ще върне всички игри, които имат диапазон на банкрол, който включва поне част от диапазона от 10 до 15 милиона. По-абсолютна заявка би била всички игри, които имат банкрол изцяло между диапазона, т.е. всеки, който прави поне 10 милиона, но не повече от 15 милиона. Тази заявка би изглеждала така:
Rep.objects.filter(bankroll__contained_by=NumericRange(10000000, 15000000))
Повече информация за заявките, базирани на диапазон, можете да намерите тук.
Skillz като ArrayField
Не всичко е свързано с банката, playaz има skillz, всички видове умения. Нека ги моделираме с ArrayField.
skillz = pgfields.ArrayField(
models.CharField(max_length=100, blank=True),
blank = True,
null = True,
)
За да декларирате ArrayField
трябва да му дадем първи аргумент, който е базовото поле. За разлика от списъците на Python, ArrayFields трябва да декларират всеки елемент от списъка като един и същ тип. Basefield декларира кой тип е това и може да бъде всеки от стандартните типове полета на модела. В случая по-горе току-що използвахме CharField
като наш базов тип, което означава skillz
ще бъде масив от низове.
Съхраняване на стойности в ArrayField
е точно както очаквате:
>>> 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()
Намиране на playas от skillz
Ако имаме нужда от конкретна игра с определено умение, как ще ги намерим? Използвайте __contains
филтър:
Rep.objects.filter(skillz__contains=['rappin'])
За playas, които имат някое от уменията [‘rappin’, ‘djing’, ‘producing’], но нямат други умения, можете да направите заявка по следния начин:
Rep.objects.filter(skillz__contained_by=['rappin', 'djing', 'producing'])
Или ако искате да намерите някой, който притежава някое от определен списък с умения:
Rep.objects.filter(skillz__overlap=['rappin', 'djing', 'producing'])
Можете дори да намерите само онези хора, които са посочили умение като първо умение (защото всеки първо изброява най-доброто си умение):
Rep.objects.filter(skillz__0='ballin')
Игра като Hstore
Играта може да се разглежда като списък с различни, произволни умения, които една playa може да притежава. Тъй като играта обхваща всякакви неща, нека я моделираме като поле на Hstore, което основно означава, че можем да поставим всеки стар речник на Python там:
game = pgfields.HStoreField()
Отделете секунда, за да помислите какво направихме току-що тук. Hstore е доста огромен. По същество позволява съхранение на данни от тип „NoSQL“, точно в postgreSQL. Освен това, тъй като е вътре в PostgreSQL, можем да свързваме (чрез външни ключове), таблици, които съдържат NoSQL данни с таблици, които съхраняват обикновени данни от SQL тип. Можете дори да съхранявате и двете в една и съща таблица в различни колони, както правим тук. Може би playas няма нужда да използва този jankie, all-talk MongoDB вече...
Връщайки се към подробностите за внедряването, ако се опитате да мигрирате новото поле на Hstore в базата данни и се окажете с тази грешка-
django.db.utils.ProgrammingError: type "hstore" does not exist
-тогава вашата база данни PostgreSQL е преди 8.1 (време за надграждане, playa) или няма инсталирано разширение Hstore. Имайте предвид, че в PostgreSQL разширението Hstore е инсталирано за база данни, а не за цялата система. За да го инсталирате от вашата psql подкана, изпълнете следния SQL:
CREATE EXTENSION hstore
Или, ако искате, можете да го направите чрез SQL миграция със следния файл за миграция (ако приемем, че сте били свързани към базата данни като суперпотребител):
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.RunSQL("CREATE EXTENSION IF NOT EXISTS hstore")
]
И накрая, ще трябва също да се уверите, че сте добавили 'django.contrib.postgres'
към 'settings.INSTALLED_APPS'
за да използвате полетата на Hstore.
С тази настройка можем да добавим данни към нашия HStoreField
game
като му хвърлите речник така:
>>> 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()
Имайте предвид, че dict трябва да използва само низове за всички ключове и стойности.
А сега за още няколко интересни примера...
Propz
Нека напишем функция „покажи игра“, за да търсим играта playaz и да върнем списък с playaz, които съвпадат. С маниакални термини, ние търсим в полето Hstore всички ключове, предадени във функцията. Изглежда нещо подобно:
def show_game(key):
return Rep.Objects.filter(game__has_key=key).values('game','playa__username')
По-горе използвахме has_key
филтрирайте за полето Hstore, за да върнете набор от заявки, след което го филтрирахте допълнително с функцията за стойности (главно за да покажете, че можете да верижите django.contrib.postgres
неща с обикновен набор от заявки.
Връщаната стойност ще бъде списък с речници:
[
{'playa__username': 'snoop',
'game': {'twitter_follows': '11000000',
'youtube-channel': 'https://www.youtube.com/user/westfesttv',
'best_album': 'Doggy Style'
}
}
]
Както се казва, Game разпознава Game и сега можем да търсим и играта.
High Rollers
Ако вярваме на това, което playaz ни казват за техните банкроли, тогава можем да използваме това, за да ги класираме в категории (тъй като това е диапазон). Нека добавим класиране на Playa въз основа на банката със следните нива:
-
Young Buck – банкрол по-малко от сто бона
-
balla – банкрол между 100 000 и 500 000 с умението „ballin“
-
playa – банкрол между 500 000 и 1 000 000 с две умения и малко игра
-
high roller – банкрол над 1 000 000
-
О.Г. – притежава умението „гангста“ и ключ за игра „стара школа“
Заявката за balla е по-долу. Това би било стриктното тълкуване, което би върнало само онези, чийто целият обхват на банката е в определените граници:
Rep.objects.filter(bankroll__contained_by=[100000, 500000], skillz__contains=['ballin'])
Опитайте сами останалото за малко практика. Ако имате нужда от помощ, прочетете документите.