Exprimiendo el ORM de Django

Exprimiendo el ORM
de Django
Héctor Pablos López
PyconES 2017
#whoami
Héctor Pablos López
Frontend developer @ StyleSage
www.stylesage.co
hector@stylesage.co
Contenido
• Introducción
• Expresiones (Query expressions)
• Joining
• Cuando todo lo demás falla
Introducción
Introducción
Why I hate the Django ORM: YouTube Video
https://www.youtube.com/watch?v=GxL9MnWlCwo
Rant alert
Introducción
Why I hate the Django ORM: YouTube Video
https://www.youtube.com/watch?v=GxL9MnWlCwo
Alex Gaynor
Desarrollador del ORM de Django
Introducción
• Es agnóstico en cuanto al motor de BD
• Escribimos consultas en alto nivel… Y en Python!
• Completamente integrado en Django (Migraciones,
modelos…)
¿Por qué usarlo?
Introducción
• El ORM como a mi me hubiera gustado que me lo
explicaran
• Ejemplos en Python Y SQL
Objetivo de esta charla
Introducción
Car.ojects.values(‘model_name’).filter(build_year__gt=1995)
SELECT car.model_name
FROM car
WHERE car.build_year > 1995;
Expresión básica
Expresiones
Expresiones
¿Qué son?
Un valor (simple o calculado) que se puede usar en una orden de DML
.filter(expression)
.order_by(expression)
.annotate(expression)
.aggregate(expression)
Expresiones
Objetos F()
Representan el valor de un campo
Car.ojects.filter(units_produced__gt=F(‘units_sold’))
SELECT *
FROM car
WHERE car.units_produced > car.units_sold
Expresiones
Objetos Q()
Encapsulan colecciones de argumentos con nombre
Car.ojects.filter(Q(kilometers__gt=1000) | Q(build_year__lt=1990))
SELECT *
FROM car
WHERE car.kilometers > 1000
OR car.build_year < 1990
Expresiones
annotate()
Anotar (dar nombre) a una expresión
Car.objects.annotate(plate=‘plate_number’)
SELECT plate_number AS plate
FROM car
Expresiones
Func() y Aggregate()
Funciones de nuestra base de datos… Las que queramos!
class DaysBetween(Func):
function = ‘DAYS_BETWEEN’
class Now(Func):
function = ‘NOW'
class Median(Aggregate):
function = ‘MEDIAN'
Expresiones
Combinar expresiones
Expresiones
Combinar expresiones
Funciones y más funciones
Company.objects 
.annotate(number_of_days=DaysBetween(Now(), F(‘date_started’))) 
.filter(number_of_days__lt=100)
SELECT DAYS_BETWEEN(NOW(), date_Started) AS number_of_days
FROM company
WHERE DAYS_BETWEEN(NOW(), date_Started) < 100;
Expresiones
Combinar expresiones
Funciones y más funciones… Agregadas!
Company.objects.values(‘region_id’) 
.annotate(all_the_days=Sum(DaysBetween(Now(), F(‘date_started’))))
SELECT SUM(DAYS_BETWEEN(NOW(), date_Started)) AS all_the_days
FROM company
GROUP BY region_id;
Expresiones
Combinar expresiones
Expresiones condicionales
Product.objects.annotate(
discounted_product_count=Count(
Case(
When(is_discounted=True, sold_out=False, then=F('id')),
default=None,
output_field=IntegerField()
),
distinct=True
)
)
Expresiones
Combinar expresiones
Product.objects.annotate(
discounted_product_count=Count(
[ … ]
),
avg_discount=Sum(
Case(
When(is_discounted=True, sold_out=True, then=F('discount_percent')),
default=0,
output_field=FloatField()
)
) / Case(
When(Q(discounted_product_count__gt=0), then=F('discounted_product_count')),
default=1,
output_field=IntegerField()
) / 100
)
Expresiones
Combinar expresiones
Joining
Joining
Con Foreign Key
class Parameter(models.Model):
name = models.CharField(max_length=30, unique=True)
parameter_category = models.ForeignKey(ParameterCategory)
affected_entities = models.ManyToManyField(
Entity,
blank=False,
help_text=‘Entities affected by this’,
)
Parameter.objects 
.values(‘name', 'parameter_category__name')
SELECT "parameter"."name",
"parametercategory"."name"
FROM "parameter"
INNER JOIN "parametercategory" ON
("parameter"."parameter_category_id" = "parametercategory"."id");
Joining
Con Foreign Key
class Parameter(models.Model):
name = models.CharField(max_length=30, unique=True)
parameter_category = models.ForeignKey(ParameterCategory)
affected_entities = models.ManyToManyField(
Entity,
blank=False,
help_text=‘Entities affected by this’,
)
Parameter.objects.values('name', ‘affected_entities__name')
SELECT "parameter"."name",
“entity"."name"
FROM "parameter"
LEFT OUTER JOIN “affected_entities"
ON ("parameter"."id" = "affected_entities"."parameter_id")
LEFT OUTER JOIN “entity"
ON ("affected_entites"."entity_id" = "entity"."id")
Joining
Foreign keys: select_related()
>> parameters = list(
Parameter.objects.values(‘name', ‘parameter_category__name’)
)
>> # Lista de diccionarios
>> parameters[0][‘parameter_category__name’]
u’Name of the category’
>> parameters = list(
Parameter.objects.select_related(‘parameter_category’)
)
>> # Lista de modelos. Accedemos a parameter_category sin consulta
>> # adicional
>> parameters[0].parameter_category.name
u’Name of the category’
Joining
ManyToMany fields: prefetch_related()
>> parameters = list(
Parameter.objects.values(‘name', ‘affected_entities__name’)
)
>> # Lista de diccionarios
>> parameters[0][‘affected_entities__name’]
u’Name of the entity’
>> parameters = list(
Parameter.objects.prefetch_related(‘affected_entities’)
)
>> # Lista de modelos. Accedemos a la lista de entidades sin consulta
>> # adicional
>> parameters[0].affected_entities.all()[0].name
u’Name of the category’
Joining
Sin Foreign Key
¯_(ツ)_/¯
Método extra()
Joining
Sin Foreign Key… Pero con modelos no gestionados por Django
class TableWithoutForeignKey(models.Model):
not_real = models.ForeignKey(OtherTable, db_column=‘other_column')
foo = models.CharField(max_length=50)
bar = models.IntegerField()
class Meta:
db_table = ‘table_without_foreign_key’
managed = False
Si todo lo demás falla
Si todo lo demás falla
RawSQL()
queryset.annotate(
val=RawSQL("select col from sometable where othercol = %s", (someparam,))
)
Añadir subqueries dentro del select
Si todo lo demás falla
extra()
Joins sin Foreign Key
QuerysetTable.extra(
where=[
'table_to_join_with.id = queryset_table.table_to_join_with_id'
],
tables = ['table_to_join_with'],
)
Si todo lo demás falla
Cursores
connection = connections[‘connnection_in_settings']
cursor = connection.cursor()
sql = """
SELECT
some_data,
some_other_data,
a_thing,
footer
FROM
THE_TABLE
WHERE
id=%s
AND code=%s
ORDER BY
column_to_order_by;
"""
cursor.execute(sql, [table_id_to_get, table_code_to_get])
desc = cursor.description
the_data_list = [
dict(zip([col[0].lower() for col in desc], row)) for row in cursor.fetchall()
]
#whoami
Héctor Pablos López
Frontend developer @ StyleSage
www.stylesage.co
hector@stylesage.co
1 of 33

More Related Content

What's hot(20)

Introduction to Django Rest FrameworkIntroduction to Django Rest Framework
Introduction to Django Rest Framework
bangaloredjangousergroup1.4K views
Introduction to pythonIntroduction to python
Introduction to python
Syed Zaid Irshad2.1K views
Clean coding-practicesClean coding-practices
Clean coding-practices
John Ferguson Smart Limited32.8K views
Python decoratorsPython decorators
Python decorators
Alex Su2.2K views
RPGLE MINICOMPUTADORASRPGLE MINICOMPUTADORAS
RPGLE MINICOMPUTADORAS
Gerardo Plasencia1.3K views
Practical Groovy DSLPractical Groovy DSL
Practical Groovy DSL
Guillaume Laforge10.3K views
Python GeneratorsPython Generators
Python Generators
Akshar Raaj803 views
Python collectionsPython collections
Python collections
Manusha Dilan750 views
PhpPhp
Php
samirlakhanistb750 views
Tech talks#6: Code RefactoringTech talks#6: Code Refactoring
Tech talks#6: Code Refactoring
Nguyễn Việt Khoa2.5K views
Go Lang TutorialGo Lang Tutorial
Go Lang Tutorial
Wei-Ning Huang18.4K views

Similar to Exprimiendo el ORM de Django

San Luis Oct 2008San Luis Oct 2008
San Luis Oct 2008Raul Espinola
494 views17 slides
San Luis Oct 2008San Luis Oct 2008
San Luis Oct 2008guest02b440
178 views17 slides

Similar to Exprimiendo el ORM de Django(20)

San Luis Oct 2008San Luis Oct 2008
San Luis Oct 2008
Raul Espinola494 views
San Luis Oct 2008San Luis Oct 2008
San Luis Oct 2008
guest02b440178 views
Expediente Xpath  #SEOnderground 2021Expediente Xpath  #SEOnderground 2021
Expediente Xpath #SEOnderground 2021
MJ Cachón Yáñez1.9K views
Introducción a DjangoIntroducción a Django
Introducción a Django
Julio Gálvez503 views
Taller de Django betabeersTaller de Django betabeers
Taller de Django betabeers
betabeers1.1K views
Introducción a DjangoIntroducción a Django
Introducción a Django
Joaquim Rocha1.4K views
DjangoDjango
Django
Adolfo Fitoria9.3K views
Cuida tu código: Clean CodeCuida tu código: Clean Code
Cuida tu código: Clean Code
Jesus Maria Escudero Justel185 views
Tdd y clean code SG campusTdd y clean code SG campus
Tdd y clean code SG campus
Software Guru895 views
Testing efectivo con pytestTesting efectivo con pytest
Testing efectivo con pytest
Hector Canto656 views
DjangoDjango
Django
ykro925 views
5 Vb.Net5 Vb.Net
5 Vb.Net
erinel800 views
Refactoring with php stormRefactoring with php storm
Refactoring with php storm
Daniel González Cerviño55 views
Google HackingGoogle Hacking
Google Hacking
Dani Martinez31.1K views
Patrones de diseño.pptxPatrones de diseño.pptx
Patrones de diseño.pptx
gigoallspam110 views
Frameworks de templates y xmlFrameworks de templates y xml
Frameworks de templates y xml
roger gustavo saravia aramayo181 views
Introducción a DjangoIntroducción a Django
Introducción a Django
Paradigma Digital2.4K views
Semana 6   Estructura y ComponentesSemana 6   Estructura y Componentes
Semana 6 Estructura y Componentes
Richard Eliseo Mendoza Gafaro723 views

Exprimiendo el ORM de Django

  • 1. Exprimiendo el ORM de Django Héctor Pablos López PyconES 2017
  • 2. #whoami Héctor Pablos López Frontend developer @ StyleSage www.stylesage.co hector@stylesage.co
  • 3. Contenido • Introducción • Expresiones (Query expressions) • Joining • Cuando todo lo demás falla
  • 5. Introducción Why I hate the Django ORM: YouTube Video https://www.youtube.com/watch?v=GxL9MnWlCwo Rant alert
  • 6. Introducción Why I hate the Django ORM: YouTube Video https://www.youtube.com/watch?v=GxL9MnWlCwo Alex Gaynor Desarrollador del ORM de Django
  • 7. Introducción • Es agnóstico en cuanto al motor de BD • Escribimos consultas en alto nivel… Y en Python! • Completamente integrado en Django (Migraciones, modelos…) ¿Por qué usarlo?
  • 8. Introducción • El ORM como a mi me hubiera gustado que me lo explicaran • Ejemplos en Python Y SQL Objetivo de esta charla
  • 11. Expresiones ¿Qué son? Un valor (simple o calculado) que se puede usar en una orden de DML .filter(expression) .order_by(expression) .annotate(expression) .aggregate(expression)
  • 12. Expresiones Objetos F() Representan el valor de un campo Car.ojects.filter(units_produced__gt=F(‘units_sold’)) SELECT * FROM car WHERE car.units_produced > car.units_sold
  • 13. Expresiones Objetos Q() Encapsulan colecciones de argumentos con nombre Car.ojects.filter(Q(kilometers__gt=1000) | Q(build_year__lt=1990)) SELECT * FROM car WHERE car.kilometers > 1000 OR car.build_year < 1990
  • 14. Expresiones annotate() Anotar (dar nombre) a una expresión Car.objects.annotate(plate=‘plate_number’) SELECT plate_number AS plate FROM car
  • 15. Expresiones Func() y Aggregate() Funciones de nuestra base de datos… Las que queramos! class DaysBetween(Func): function = ‘DAYS_BETWEEN’ class Now(Func): function = ‘NOW' class Median(Aggregate): function = ‘MEDIAN'
  • 17. Expresiones Combinar expresiones Funciones y más funciones Company.objects .annotate(number_of_days=DaysBetween(Now(), F(‘date_started’))) .filter(number_of_days__lt=100) SELECT DAYS_BETWEEN(NOW(), date_Started) AS number_of_days FROM company WHERE DAYS_BETWEEN(NOW(), date_Started) < 100;
  • 18. Expresiones Combinar expresiones Funciones y más funciones… Agregadas! Company.objects.values(‘region_id’) .annotate(all_the_days=Sum(DaysBetween(Now(), F(‘date_started’)))) SELECT SUM(DAYS_BETWEEN(NOW(), date_Started)) AS all_the_days FROM company GROUP BY region_id;
  • 20. Expresiones Combinar expresiones Product.objects.annotate( discounted_product_count=Count( [ … ] ), avg_discount=Sum( Case( When(is_discounted=True, sold_out=True, then=F('discount_percent')), default=0, output_field=FloatField() ) ) / Case( When(Q(discounted_product_count__gt=0), then=F('discounted_product_count')), default=1, output_field=IntegerField() ) / 100 )
  • 23. Joining Con Foreign Key class Parameter(models.Model): name = models.CharField(max_length=30, unique=True) parameter_category = models.ForeignKey(ParameterCategory) affected_entities = models.ManyToManyField( Entity, blank=False, help_text=‘Entities affected by this’, ) Parameter.objects .values(‘name', 'parameter_category__name') SELECT "parameter"."name", "parametercategory"."name" FROM "parameter" INNER JOIN "parametercategory" ON ("parameter"."parameter_category_id" = "parametercategory"."id");
  • 24. Joining Con Foreign Key class Parameter(models.Model): name = models.CharField(max_length=30, unique=True) parameter_category = models.ForeignKey(ParameterCategory) affected_entities = models.ManyToManyField( Entity, blank=False, help_text=‘Entities affected by this’, ) Parameter.objects.values('name', ‘affected_entities__name') SELECT "parameter"."name", “entity"."name" FROM "parameter" LEFT OUTER JOIN “affected_entities" ON ("parameter"."id" = "affected_entities"."parameter_id") LEFT OUTER JOIN “entity" ON ("affected_entites"."entity_id" = "entity"."id")
  • 25. Joining Foreign keys: select_related() >> parameters = list( Parameter.objects.values(‘name', ‘parameter_category__name’) ) >> # Lista de diccionarios >> parameters[0][‘parameter_category__name’] u’Name of the category’ >> parameters = list( Parameter.objects.select_related(‘parameter_category’) ) >> # Lista de modelos. Accedemos a parameter_category sin consulta >> # adicional >> parameters[0].parameter_category.name u’Name of the category’
  • 26. Joining ManyToMany fields: prefetch_related() >> parameters = list( Parameter.objects.values(‘name', ‘affected_entities__name’) ) >> # Lista de diccionarios >> parameters[0][‘affected_entities__name’] u’Name of the entity’ >> parameters = list( Parameter.objects.prefetch_related(‘affected_entities’) ) >> # Lista de modelos. Accedemos a la lista de entidades sin consulta >> # adicional >> parameters[0].affected_entities.all()[0].name u’Name of the category’
  • 28. Joining Sin Foreign Key… Pero con modelos no gestionados por Django class TableWithoutForeignKey(models.Model): not_real = models.ForeignKey(OtherTable, db_column=‘other_column') foo = models.CharField(max_length=50) bar = models.IntegerField() class Meta: db_table = ‘table_without_foreign_key’ managed = False
  • 29. Si todo lo demás falla
  • 30. Si todo lo demás falla RawSQL() queryset.annotate( val=RawSQL("select col from sometable where othercol = %s", (someparam,)) ) Añadir subqueries dentro del select
  • 31. Si todo lo demás falla extra() Joins sin Foreign Key QuerysetTable.extra( where=[ 'table_to_join_with.id = queryset_table.table_to_join_with_id' ], tables = ['table_to_join_with'], )
  • 32. Si todo lo demás falla Cursores connection = connections[‘connnection_in_settings'] cursor = connection.cursor() sql = """ SELECT some_data, some_other_data, a_thing, footer FROM THE_TABLE WHERE id=%s AND code=%s ORDER BY column_to_order_by; """ cursor.execute(sql, [table_id_to_get, table_code_to_get]) desc = cursor.description the_data_list = [ dict(zip([col[0].lower() for col in desc], row)) for row in cursor.fetchall() ]
  • 33. #whoami Héctor Pablos López Frontend developer @ StyleSage www.stylesage.co hector@stylesage.co

Editor's Notes

  1. Por qué odio el ORM de Django. En él ponen ejemplo de una agrupación muy sencilla que no se podía hacer en el momento de su charla, ahora sí se puede, pero a mi, personalmente, me molesta especialmente no poder hacer un join de dos tablas sin haber definido una foreign key para ellas. Pero os preguntaréis por qué comento este vídeo, si podría ser sólo un usuario cabreado, pues bien…
  2. Porque el autor de la charla es este chico Alex Gaynor, quien tiene una gran carrera, siendo uno de sus trabajos… Desarrollar el ORM de Django
  3. 1 - Es agnóstico en cuanto al motor de BD, es decir, podemos cambiar nuestro motor de base de datos y tendremos un impacto mínimo, o incluso ningún impacto en nuestro código. Es un poco mentira, porque luego intercambiar bases de datos sin ningún problema no es tan fácil. 2 - Escribimos consultas en alto nivel: Por ejemplo, podemos beneficiarnos del control de flujo de un lenguaje de programación de alto nivel para construir consultas de una manera mucho más sencilla que concatenando cadenas manualmente a una sentencia sql. Y ejecución lazy, y demás… 3 - Podemos gestionar la estructura de nuestra base de datos desde nuestro django, con la ventaja de tener la historia de la base de datos en control de versiones, la facilidad para hacer despliegues con cambios en la base de datos, etc…
  4. Es el tutorial de orm 2.0, donde termina el tutorial empieza esta charla.
  5. Una base de datos no sólo es capaz de devolvernos los datos que tiene almacenados, por lo que el ORM no es sólo para recibir diccionarios o modelos, también lo podemos utilizar para pedir que sea la base de datos la que procese los datos y nos devuelva resultados. Podemos tener bases de datos de Big Data
  6. Las expresiones pueden ser tan sencillas como una cadena que representa un campo del modelo, o pueden volverse tremendamente complejas. Lo que hay dentro de estas funciones son expresiones, pero un aggregate también es una expresión en si misma.
  7. F() representa el valor de un campo. Con F, por ejemplo, podemos comprar unos campos con otros, pero también realizar operaciones aritméticas.
  8. Encapsulan blablabla no significa nada, es lo que dice la documentación. Pero podemos decir que son trozos del Where, que suena mucho mejor. Sobre todo, usados para poder hacer condiciones con ORs. Se pueden usar igual que los objetos F(), para representar el valor de un campo. https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.Q https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q
  9. Nos sirve para dar nombre a una expresión, que puede ser todo lo compleja que queramos. Pero esto no es sólo útil para hacer más manejable el query set resultante. También sirve para tener un nombre con el que referirnos a esa expresión en cualquier expresión posterior. https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.annotate
  10. También podemos crearnos nuestra propia librería de funciones de la base de datos. La gran mayoría de las funciones que nuestra base de datos tenga las podemos convertir a expresiones Func. También las agregadas. Aquí están ejemplos muy sencillos, pero se puede custodiar completamente el SQL generado por cada función, en caso de que su traducción no sea estándar. Eso sí, una vez cambiado el SQL nos podemos olvidar un poco de que nuestro código sea agnóstico respecto a la BD. https://docs.djangoproject.com/en/1.11/ref/models/expressions/#func-expressions
  11. Vemos que podemos usar funciones. Incluso funciones como parámetros de otras funciones. El ORM de Django se encarga traducir correctamente este SQL. Con annotate le damos un nombre al resultado de la función y lo podremos usar como cualquier otro parámetro. Filtrando, por ejemplo, las empresas que tengan menos de 100 días de existencia.
  12. Pero también podemos usar funciones dentro de funciones agregadas, aquí sumaríamos los días que lleva de existencia cada empresa.
  13. Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F.
  14. Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F. En vez de traerme 200 millones de productos y calcular, hago los cálculos en la base de datos.
  15. Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F.
  16. Hacer joins en tablas referenciadas por claves foráneas o tablas intermedias es extremadamente fácil. simplemente hay que indicar los campos que nos interesan del resto de tablas y Django genera la consulta SQL correcta, con un inner join. https://docs.djangoproject.com/en/1.10/ref/models/querysets/#django.db.models.query.QuerySet.select_related
  17. Lo mismo con las relaciones many to many. En este caso vemos que, adecuadamente, se hace un outer join. Esto es muy potente y muy fácil de usar, mucho mejor que construir las sentencias SQL como cadenas, debemos aprovecharnos de ello. https://docs.djangoproject.com/en/1.10/ref/models/querysets/#django.db.models.query.QuerySet.select_related
  18. select_related() hace que, en una sola consulta, obtengamos todos los datos de la tabla relacionada con la categoría del parámetro y lo convierte a model, pudiendo acceder sin hacer queries adicionales a dichos datos https://docs.djangoproject.com/en/1.11/ref/models/querysets/#select-related
  19. prefetch_related() es lo mismo pero para relaciones many to many. Es muy útil, porque en lugar de tener una lista con información redundante de cada parámetro para cada entidad afectada (el producto cartesiano), tenemos un modelo para cada parámetro, que a su vez contiene una “lista” (ManyRelatedManager, actúa como un queryset) con cada uno de los modelos de entidades relacionados. https://docs.djangoproject.com/en/1.11/ref/models/querysets/#prefetch-related
  20. Se puede hacer, pero es una solución que requiere escribir SQL. Veremos la mejor opción en la siguiente sección.
  21. Muchas veces usamos bases de datos en nuestra app de Django que no se gestionan desde la app de Django. Es decir, Django no se encarga del DDL, no crea las tablas ni las modifica, incluso puede que no escriba, sólo lee datos. En este caso… Podemos definir la Foreign Key que queramos! Incluso aunque no sea real.
  22. Ejemplo de la documentación, perdemos agnosticismo con la base de datos, debemos usarlo con mucho cuidado si usamos alias de tablas y debemos escapar los parámetros si vienen del exterior.
  23. Con extra podemos, por ejemplo, hacer joins entre tablas que no tienen definida una foreign key en el modelo. Las condiciones del where, de hecho, pueden ser varias. El método extra() lleva siendo desaconsejado por los desarrolladores de Django desde hace mucho tiempo, se dice que se va a deprecar, pero nunca se hace, ya que es necesario para este tipo de casos.
  24. La opción última es ejecutar SQL plano, sin más mediante un cursor. En las líneas resaltada en amarillo cogemos la descripción del cursor (nombres de las columnas) para crear una lista de diccionarios, de la misma forma que si ejecutáramos un QuerySet en lugar de un cursor.