Database
 sql >> Base de Dados >  >> RDS >> Database

Diversão com os novos recursos do Postgres do Django


Esta postagem de blog aborda como usar os novos ModelFields específicos do PostgreSQL introduzidos no Django 1.8 - os campos ArrayField, HStoreField e Range.

Este post é dedicado aos incríveis apoiadores desta campanha Kickstarter montada por Marc Tamlyn, o verdadeiro playa que fez isso acontecer.

Clube Playaz?


Como sou um grande geek e não tenho chance de entrar em um verdadeiro Playaz Club (e porque no dia 4 Tay era a bomba), decidi construir meu próprio Playaz Club virtual online. O que é isso exatamente? Uma rede social privada, apenas para convidados, direcionada a um pequeno grupo de indivíduos com ideias semelhantes.

Para este post, vamos nos concentrar no modelo de usuário e explorar como os novos recursos do PostgreSQL do Django suportam a modelagem. Os novos recursos aos quais estamos nos referindo são apenas PostgreSQL, então não se preocupe em tentar isso a menos que você tenha seu banco de dados ENGINE igual a django.db.backends.postgresql_psycopg2 . Você precisará da versão>=2.5 de psycopg2 . Aight playa, vamos fazer isso.

Holla se você comigo! :)


Modelando o representante de um Playa


Cada playa tem um representante, e eles querem que o mundo inteiro saiba sobre seu representante. Então, vamos criar um perfil de usuário (também conhecido como “representante”) que permite que cada um de nossos playaz expresse sua individualidade.

Aqui está o modelo básico para um representante 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()

Nada específico para 1.8 acima. Apenas um modelo padrão para estender o usuário básico do Django, pois um playa ainda precisa de um nome de usuário e endereço de e-mail, certo? Além disso, adicionamos dois novos campos para armazenar o capô playaz e o código de área.


Bankroll e RangeField


Para um playa, repaginar seu capuz nem sempre é suficiente. A Playaz geralmente gosta de exibir seu bankroll, mas ao mesmo tempo não quer deixar as pessoas saberem exatamente o tamanho desse bankroll. Podemos modelar isso com um dos novos campos de intervalo do Postgres. Claro que usaremos o BigIntegerRangeField para modelar melhor os dígitos massivos, certo?
bankroll = pgfields.BigIntegerRangeField(default=(10, 100))

Os campos Range são baseados nos objetos Range psycopg2 e podem ser usados ​​para Numeric e DateRanges. Com o campo bankroll migrado para o banco de dados, podemos interagir com nossos campos range passando a ele um objeto range, então criar nossa primeira playa ficaria assim:
>>>
>>> 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()

Observe esta linha:calvins_rep.bankroll = (100000000, 150000000) . Aqui estamos definindo um campo de intervalo usando uma tupla simples. Também é possível definir o valor usando um NumericRange objeto assim:
from psycopg2.extras import NumericRange
br = NumericRange(lower=100000000, upper=150000000)
calvin.rep.bankroll = br
calvin.rep.save()

Isso é essencialmente o mesmo que usar a tupla. No entanto, é importante saber sobre o NumericRange objeto, pois é usado para filtrar o modelo. Por exemplo, se quiséssemos encontrar todos os playas cujo bankroll fosse superior a 50 milhões (o que significa que todo o intervalo de bankroll é superior a 50 milhões):
Rep.objects.filter(bankroll__fully_gt=NumericRange(50000000, 50000000))

E isso retornará a lista dessas playas. Alternativamente, se quiséssemos encontrar todas as playas cujo bankroll esteja “algo em torno da faixa de 10 a 15 milhões”, poderíamos usar:
Rep.objects.filter(bankroll__overlap=NumericRange(10000000, 15000000))

Isso retornaria todos os playas que possuem uma faixa de bankroll que inclui pelo menos uma parte da faixa de 10 a 15 milhões. Uma consulta mais absoluta seria todos os playas que têm um bankroll totalmente entre um intervalo, ou seja, todos que estão ganhando pelo menos 10 milhões, mas não mais que 15 milhões. Essa consulta ficaria assim:
Rep.objects.filter(bankroll__contained_by=NumericRange(10000000, 15000000))

Mais informações sobre consultas baseadas em intervalo podem ser encontradas aqui.


Skillz como ArrayField


Não é tudo sobre o bankroll, playaz tem skillz, todos os tipos de skillz. Vamos modelar aqueles com um ArrayField.
skillz = pgfields.ArrayField(
    models.CharField(max_length=100, blank=True),
    blank = True,
    null = True,
)

Para declarar o ArrayField temos que dar um primeiro argumento, que é o campo base. Ao contrário das listas Python, ArrayFields deve declarar cada elemento da lista como do mesmo tipo. Basefield declara que tipo é este e pode ser qualquer um dos tipos de campo de modelo padrão. No caso acima, acabamos de usar um CharField como nosso tipo base, o que significa skillz será um array de strings.

Armazenando valores no ArrayField é exatamente como você espera:
>>>
>>> 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()

Encontrando playas por skillz


Se precisarmos de uma determinada playa com uma habilidade específica, como vamos encontrá-la? Use os __contains filtro:
Rep.objects.filter(skillz__contains=['rappin'])

Para playas que têm alguma das habilidades ['rappin', 'djing', 'produção'], mas nenhuma outra habilidade, você pode fazer uma consulta assim:
Rep.objects.filter(skillz__contained_by=['rappin', 'djing', 'producing'])

Ou se você quiser encontrar alguém que tenha uma determinada lista de habilidades:
Rep.objects.filter(skillz__overlap=['rappin', 'djing', 'producing'])

Você pode até encontrar apenas aquelas pessoas que listaram uma habilidade como sua primeira habilidade (porque todos listam sua melhor habilidade primeiro):
Rep.objects.filter(skillz__0='ballin')



Jogo como HStore


O jogo pode ser pensado como uma lista de diversas habilidades aleatórias que um playa pode ter. Como Game abrange todos os tipos de coisas, vamos modelá-lo como um campo HStore, o que basicamente significa que podemos colocar qualquer dicionário Python antigo lá:
game = pgfields.HStoreField()

Pare um segundo para pensar sobre o que acabamos de fazer aqui. HStore é muito grande. Ele basicamente permite o armazenamento de dados do tipo “NoSQL”, bem dentro do postgreSQL. Além disso, como está dentro do PostgreSQL, podemos vincular (através de chaves estrangeiras), tabelas que contêm dados NoSQL com tabelas que armazenam dados regulares do tipo SQL. Você pode até armazenar ambos na mesma tabela em colunas diferentes, como estamos fazendo aqui. Talvez os playas não precisem mais usar aquele mongoDB falante e falante…

Voltando aos detalhes de implementação, se você tentar migrar o novo campo HStore para o banco de dados e acabar com este erro-
django.db.utils.ProgrammingError: type "hstore" does not exist

-então seu banco de dados PostgreSQL é pré 8.1 (hora de atualizar, playa) ou não tem a extensão HStore instalada. Tenha em mente que no PostgreSQL a extensão HStore é instalada por banco de dados e não em todo o sistema. Para instalá-lo a partir do prompt do psql, execute o seguinte SQL:
CREATE EXTENSION hstore

Ou se quiser, você pode fazer isso através de uma migração SQL com o seguinte arquivo de migração (supondo que você esteja conectado ao banco de dados como superusuário):
from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = []

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

Finalmente, você também precisará ter certeza de que adicionou 'django.contrib.postgres' para 'settings.INSTALLED_APPS' para fazer uso de campos HStore.

Com essa configuração, podemos adicionar dados ao nosso HStoreField game jogando um dicionário para ele assim:
>>>
>>> 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()

Tenha em mente que o dict deve usar apenas strings para todas as chaves e valores.

E agora para alguns exemplos mais interessantes…


Propz


Vamos escrever uma função “show game” para pesquisar o jogo playaz e retornar uma lista de playaz que correspondem. Em termos nerds, estamos pesquisando no campo HStore por quaisquer chaves passadas para a função. Parece algo assim:
def show_game(key):
    return Rep.Objects.filter(game__has_key=key).values('game','playa__username')

Acima usamos o has_key filtre para o campo HStore retornar um conjunto de consultas e, em seguida, filtre-o com a função de valores (principalmente para mostrar que você pode encadear django.contrib.postgres coisas com coisas de conjunto de consultas regulares).

O valor de retorno seria uma lista de dicionários:
[
  {'playa__username': 'snoop',
  'game': {'twitter_follows': '11000000',
           'youtube-channel': 'https://www.youtube.com/user/westfesttv',
           'best_album': 'Doggy Style'
        }
  }
]

Como se costuma dizer, o jogo reconhece o jogo e agora também podemos procurar o jogo.


High Rollers


Se acreditarmos no que os playaz estão nos dizendo sobre seus bankrolls, podemos usar isso para classificá-los em categorias (já que é um intervalo). Vamos adicionar um ranking Playa baseado no bankroll com os seguintes níveis:

  • fanfarrão jovem – banca inferior a cem mil

  • balla – banca entre 100.000 e 500.000 com a habilidade ‘ballin’

  • playa – bankroll entre 500.000 e 1.000.000 com duas habilidades e algum jogo

  • high roller - bankroll superior a 1.000.000

  • O.G. – tem a habilidade ‘gangsta’ e a chave do jogo “old-school”

A consulta para balla está abaixo. Esta seria a interpretação estrita, que retornaria apenas aqueles cujo range de bankroll inteiro está dentro dos limites especificados:
Rep.objects.filter(bankroll__contained_by=[100000, 500000], skillz__contains=['ballin'])

Experimente o resto você mesmo para alguma prática. Se precisar de ajuda, leia os documentos.