sql >> Base de Datos >  >> RDS >> Database

Diversión con las nuevas características de Postgres de Django

Esta publicación de blog cubre cómo usar los nuevos ModelFields específicos de PostgreSQL presentados en Django 1.8:ArrayField, HStoreField y Range Fields.

Esta publicación está dedicada a los increíbles patrocinadores de esta campaña de Kickstarter organizada por Marc Tamlyn, el verdadero jugador que lo hizo posible.


Club Playaz?

Como soy un gran geek y no tengo ninguna posibilidad de entrar en un Playaz Club real (y porque en el día 4 Tay era la bomba), decidí crear mi propio Playaz Club virtual en línea. ¿Qué es eso exactamente? Una red social privada, solo por invitación, dirigida a un pequeño grupo de personas con ideas afines.

Para esta publicación, nos centraremos en el modelo de usuario y exploraremos cómo las nuevas funciones de PostgreSQL de Django admiten el modelado. Las nuevas funciones a las que nos referimos son solo de PostgreSQL, así que no se moleste en probar esto a menos que tenga su base de datos ENGINE igual a django.db.backends.postgresql_psycopg2 . Necesitará la versión>=2.5 de psycopg2 . Aight playa, hagamos esto.

¡Hola si estás conmigo! :)



Modelado de un representante de Playa

Cada playa tiene un representante, y quieren que todo el mundo sepa sobre su representante. Así que vamos a crear un perfil de usuario (también conocido como "representante") que permita a cada uno de nuestros playaz expresar su individualidad.

Este es el modelo básico para un representante de 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 de 1.8 anterior. Solo un modelo estándar para extender el usuario base de Django, porque una playa aún necesita un nombre de usuario y una dirección de correo electrónico, ¿verdad? Además, agregamos dos nuevos campos para almacenar el código de área y el código de área de Playaz.



Bankroll y RangeField

Para una playa, restaurar tu barrio no siempre es suficiente. A Playaz a menudo le gusta hacer alarde de su bankroll, pero al mismo tiempo no quiere que la gente sepa exactamente qué tan grande es ese bankroll. Podemos modelar eso con uno de los nuevos campos de rango de Postgres. Por supuesto, usaremos el BigIntegerRangeField para modelar mejor los dígitos masivos, ¿verdad?

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

Los campos de rango se basan en los objetos Range de psycopg2 y se pueden usar para rangos numéricos y de fechas. Con el campo de fondos migrado a la base de datos, podemos interactuar con nuestros campos de rango pasándoles un objeto de rango, por lo que crear nuestra primera playa se vería así:

>>>
>>> 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 línea:calvins_rep.bankroll = (100000000, 150000000) . Aquí estamos configurando un campo de rango usando una tupla simple. También es posible establecer el valor usando un NumericRange objeto así:

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

Esto es esencialmente lo mismo que usar la tupla. Sin embargo, es importante conocer el NumericRange objeto que se utiliza para filtrar el modelo. Por ejemplo, si quisiéramos encontrar todas las playas cuyo bankroll fuera superior a 50 millones (lo que significa que todo el rango de bankroll es superior a 50 millones):

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

Y eso devolverá la lista de esas playas. Alternativamente, si quisiéramos encontrar todas las playas cuyo bankroll esté "alrededor del rango de 10 a 15 millones", podríamos usar:

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

Esto devolvería todas las playas que tienen un rango de fondos que incluye al menos una parte del rango de 10 a 15 millones. Una consulta más absoluta sería todas las playas que tienen un bankroll completamente dentro de un rango, es decir, todos los que ganan al menos 10 millones pero no más de 15 millones. Esa consulta se vería así:

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

Puede encontrar más información sobre las consultas basadas en rangos aquí.



Skillz como ArrayField

No se trata solo de los fondos, playaz tiene habilidades, todo tipo de habilidades. Modelemos aquellos con un ArrayField.

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

Para declarar el ArrayField tenemos que darle un primer argumento, que es el campo base. A diferencia de las listas de Python, ArrayFields debe declarar cada elemento de la lista como del mismo tipo. Basefield declara qué tipo es este y puede ser cualquiera de los tipos de campo de modelo estándar. En el caso anterior, acabamos de usar un CharField como nuestro tipo base, lo que significa skillz será una matriz de cadenas.

Almacenar valores en el ArrayField es exactamente como esperabas:

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

Encontrar playas por skillz

Si necesitamos una playa en particular con una habilidad en particular, ¿cómo la encontraremos? Usa el __contains filtro:

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

Para jugadores que tienen cualquiera de las habilidades ['rappin', 'djing', 'producing'] pero ninguna otra habilidad, puede hacer una consulta como esta:

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

O si desea encontrar a alguien que tenga alguna de una determinada lista de habilidades:

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

Incluso podría encontrar solo a aquellas personas que enumeraron una habilidad como su primera habilidad (porque todos enumeran su mejor habilidad primero):

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



Juego como HStore

El juego se puede considerar como una lista de habilidades misceláneas y aleatorias que podría tener un jugador. Dado que Game abarca todo tipo de cosas, modelémoslo como un campo HStore, lo que básicamente significa que podemos incluir cualquier diccionario antiguo de Python:

game = pgfields.HStoreField()

Tómese un segundo para pensar en lo que acabamos de hacer aquí. HStore es bastante grande. Básicamente permite el almacenamiento de datos de tipo "NoSQL", justo dentro de PostgreSQL. Además, dado que está dentro de PostgreSQL, podemos vincular (a través de claves externas) tablas que contienen datos NoSQL con tablas que almacenan datos de tipo SQL regulares. Incluso puede almacenar ambos en la misma tabla en diferentes columnas como lo estamos haciendo aquí. Tal vez las playas ya no necesiten usar ese fanático de MongoDB…

Volviendo a los detalles de implementación, si intenta migrar el nuevo campo HStore a la base de datos y termina con este error:

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

-entonces su base de datos PostgreSQL es anterior a 8.1 (hora de actualizar, playa) o no tiene instalada la extensión HStore. Tenga en cuenta que en PostgreSQL, la extensión HStore se instala por base de datos y no en todo el sistema. Para instalarlo desde su indicador psql, ejecute el siguiente SQL:

CREATE EXTENSION hstore

O si lo desea, puede hacerlo a través de una migración de SQL con el siguiente archivo de migración (suponiendo que estaba conectado a la base de datos como superusuario):

from django.db import models, migrations

class Migration(migrations.Migration):

    dependencies = []

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

Finalmente, también deberá asegurarse de haber agregado 'django.contrib.postgres' a 'settings.INSTALLED_APPS' para hacer uso de los campos HStore.

Con esa configuración podemos agregar datos a nuestro HStoreField game arrojándole un diccionario así:

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

Tenga en cuenta que el dict solo debe usar cadenas para todas las claves y valores.

Y ahora algunos ejemplos más interesantes...



Propz

Escribamos una función de "mostrar juego" para buscar el juego de playaz y devolver una lista de playaz que coincidan. En términos geek, estamos buscando a través del campo HStore cualquier clave pasada a la función. Se parece a esto:

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

Arriba hemos usado el has_key filtre el campo HStore para devolver un conjunto de consultas, luego lo filtró aún más con la función de valores (principalmente para mostrar que puede encadenar django.contrib.postgres cosas con cosas regulares del conjunto de consultas).

El valor devuelto sería una lista de diccionarios:

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

Como dicen, Game reconoce Game, y ahora también podemos buscar game.



Grandes apostadores

Si creemos lo que los playaz nos dicen sobre sus fondos, podemos usarlo para clasificarlos en categorías (ya que es un rango). Agreguemos una clasificación de Playa basada en el bankroll con los siguientes niveles:

  • macho joven:fondos de menos de cien de los grandes

  • balla – bankroll entre 100.000 y 500.000 con la habilidad 'ballin'

  • playa – bankroll entre 500.000 y 1.000.000 con dos skillz y algo de juego

  • gran apostador:fondos superiores a 1 000 000

  • O.G. - tiene la habilidad 'gangsta' y la clave del juego "old-school"

La consulta para balla está abajo. Esta sería la interpretación estricta, que devolvería solo aquellos cuyo rango completo de fondos esté dentro de los límites especificados:

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

Pruebe el resto usted mismo para practicar. Si necesita ayuda, lea los documentos.