Qué tal gente de codigofacilito mi nombre es Eduardo y en esta ocasión me gustaría listar un par de consejos que pueden ser de mucha utilizar cuando nos encontremos trabajando con el Framework Django. El listado está enfocado, principalmente, en los modelos y consultas a la base de datos. Bien, una vez dicho esto comencemos.
N+1 Query
Este es un problema muy recurrente cuando trabajamos con algún ORM, no es un problema exclusivo de Django. Veamos un ejemplo para que nos quede más en claro.
for book in Book.objects.all():
print(book.author)
En este caso observamos una relación entre el modelo Book y el modelo Author, un libro le pertenece a un autor. El problema aquí es que por cada iteración se hará una consulta a la base de datos para obtener el autor del libro. De allí el nombre n + 1 query, el primer query es la consulta de todos los libros y las siguientes serán las peticiones para los autores. Si tenemos pocos registros en la base de datos quizás esto no suene a un gran problema de performance, pero, ¿Y qué pasa si tenemos, cien, mil, cien mil o muchos más?
Una solución al problema es utilizar el método select_related.
for book in Book.objects.all().select_related('author'):
print(book.author)
Ahora al obtener todos los libros también se obtendrá la relación con el autor.
Sobreescritura de métodos
En ocasiones tendremos la necesidad de establecer, arbitrariamente, ciertos valores a nuestros objetos antes o después que estos se persistan, es decir cuando se almacenen en la base de datos. Regularmente optaremos por sobreescribir el método init o el método save de nuestros modelos.
class Product(models.Model):
...
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Product, self).save(*args, **kwargs)
Este es un ejemplo muy sencillo en donde simplemente establecemos un slug antes de la persistencia. El problema recae cuando necesitamos realizar una gran cantidad de tareas o tareas complejas antes de cierta acción. Quizás notificar a otros modelos, enviar correos, actualizar campos, disparar jobs etc.. En estos casos lo mejor que podemos hacer es utilizar los signals que nos provee Django.
from django.db.models.signals import pre_save
class Product(models.Model):
...
def set_slug(sender, instance, *args, **kwargs):
slug = slugify(instance.title)
pre_save.connect(set_slug, sender=Product)
Con el pre_save obtendremos el mismo resultado que sobreescribiendo el método save, pero con la ventaja que ahora el modelo queda mucho más legible y las tareas a realizar las delegamos a otras funciones.
Si así lo deseamos podemos tener la n cantidad de signals por modelo.
Obten solo lo que necesitas.
Cuando trabajemos con modelos los cuales posean una gran cantidad de atributos, veinte, treinta, o más y solo necesitemos utilizar un par de ellos, la mejor opción será pedir a la base de datos todos los valores que realmente utilizaremos, para ello podemos apoyarnos de los métodos only y defer
#Obtenemos solo los atributos id y username
user = User.objects.get(pk=1).only('id', 'username')
#Obtenemos todos los atributo exceptuando id y username
user = User.objects.get(pk=1).defer('id', 'username')
Todo en cache
El ORM de Django es lazy por default. Esto significa que ninguna consulta se realiza hasta que el resultado es requerido, esto regularmente en la vista.
A veces pensamos que tener la menor cantidad de variables en nuestras vistas es mejor, sin embargo, en ocasiones esto no es del todo cierto.
{% for product in products.objects.all() %}
<p>{{ product.title }}</p>
{% endfor %}
{% for produc in products.objects.all() %}
<p>El titulo es : {{ product.title }}</p>
{% endfor %}
En este caso se realizan dos consultas. Lo que podemos hacer para mejorar esto es generar una variable en nuestra vista, de tal forma que el resultado quede en cache.
def funcion(request):
products = products.objects.all()
return render(request, 'index.html' {
'products': products
})
Recordemos, en Python la legibilidad cuenta.
Búsquedas por llaves
Siempre que sea posible intentemos realizar la búsqueda utilizando alguna llave, ya sea primaria a foranea.
author = Author.objects.pk(pk=1)
#Evitar
books = Book.objects.filter(autor=author)
#Mejor
books = Book.objects.filter(autor_id=author.id)
books = Book.objects.filter(autor_id=1) #Filtro por llave foránea
Count y Exists
Siempre que debamos validar la cantidad o existencia de registros en nuestra base de datos debemos apoyarnos de los métodos count y exists dejando a un lado la función len.
#Contamos
Products.objects.all().count()
#Existe el producto
Products.objects.filter(title__icontains='manzana').exists()
Si debemos trabajar con elementos que no sabemos si existen podemos apoyarnos de condicionales.
products = Products.objects.filter(title__icontains='manzana').
if products:
print(products.firs().description)
En este casa conociendo que el método filter nos retorna una lista vacía si la condición no se cumple, podemos condicionar el resultado para obtener el primer registro de nuestra lista,
Recuerda que una lista de Python es considerada False.
Update sobre Save
Finalmente, siempre que sea posible actualiza tus registros utilizando el método update sobre Save.
product = Product.objects.get(pk=1)
product.title = 'Manzana Fuji'
product.save()
Mucho mejor
Product.objects.filter(pk=1).update(title='Manzana Fuji')