Cuando empezamos a escribir el código de nuestro software siempre pensamos que hay mejores formas de realizar alguna funcionalidad, y casi siempre es así, por ello es que hoy te vengo a recomendar algunas buenas prácticas cuando se trabaja con Django.

Entornos virtuales

Siempre utiliza Entornos Virtuales para cada proyecto, no utilices un mismo entorno para mas de 1 proyecto, esto debido a que cada proyecto no siempre utiliza las mismas versiones de las librerías y, instalarlas de forma global en tu sistema, además de llenar de librerías y temporales lo cuál puede relentizar el funcionamiento de tu ordenador, cada vez que necesites cambiar de versión deberás desinstalar la existente e instalar la necesaria.

Puedes crear entornos virtuales de muchas maneras, hay varias librerías que permiten esto, desde virtualenv, conda, venv, pipenv entre otros.

Crea un archivo requirements.txt

Cuando creamos un proyecto, ya sea para colocarlo en un repositorio público o para un equipo de trabajo, debemos indicarle las librerías y las respectivas versiones de estas que se están utilizando, por ello, podemos crear un archivo donde indiquemos todo esto, por convención, este archivo suele denominarse requirements.

Puedes crearlo utilizando el comando: pip freeze > requirements.txt esto generará un archivo con los nombres de las librerías y sus versiones.

attrs==19.3.0
Click==7.0
cubes==1.1
expressions==0.2.3
Flask==1.1.1
grako==3.99.9
importlib-metadata==0.23
itsdangerous==1.1.0
Jinja2==2.10.3
jsonschema==3.1.1
MarkupSafe==1.1.1
more-itertools==7.2.0
psycopg2==2.8.4
pyrsistent==0.15.4
python-dateutil==2.8.0
six==1.12.0
SQLAlchemy==1.3.10
Werkzeug==0.16.0
zipp==0.6.0

Y luego pudes instalar cada una de ella con el comando: pip install -r requirements.txt

Separa tu archivo settings.py

Cuando creamos nuestro proyecto de Django, el archivo donde se encuentra toda la configuración, este archivo es settings.py, sin embargo no es recomendable dejarlo tal cual, debemos separarloen al menos 2 archivos, uno llamado base.py donde colocaremos todos los apartados a excepción de la configuración de Base de Datos, y luego al menos un archivo llamado local.py o development.py con la configuración de Base de Datos local, luego es recomendable crear 2 archivos más, uno llamado staging.py para el modo pruebas y uno llamado production.py para el modo Producción.

En el siguiente vídeo, en el apartado Configuración del Proyecto Minuto 3:43 puedes ver un ejemplo de esto.

Utiliza variables de entorno

Si bien, separar nuestro archivo de configuración o settings es una muy buena idea, la información sensible la pueden tener todos los desarolladores, por ello, algunos equipos optan por utilizar las denominadas Variables de Entorno, de esta forma, los miembros del equipo sólo tendrán las credenciales de desarollo y nada de producción, esto otorga seguridad y una separación aún mas completa de las funcionalidades dependiendo el entorno de desarrollo.

Aquí hay un vídeo super recomendado de ejemplo del canal de Sergio Infante.

Imports en orden

Por lo general cuando escribimos nuestros imports en nuestro proyecto se puede cometer una ligera mala práctica, importar todos los paquetes que necesitemos en desorden. Se recomienda importar en el siguiente orden:

  • Future(si es que se desea importar)
  • Librerías nativas o estandars de Python
  • Librerías de terceros
  • Paquetes propios de Django o el framework a utilizar
  • Paquetes o aplicaciones locales(de nuestro proyecto)
  • Try/Except en un import

Además, entre cada tipo de import o jerarquía, se recomienda dejar un espacio en blanco. Aquí un ejemplo.

# future
from __future__ import unicode_literals

# standard library
import json
from itertools import chain

# third-party
import bcrypt

# Django
from django.http import Http404
from django.http.response import (
    Http404, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse,
    cookie,
)

# local Django
from .models import LogEntry

# try/except
try:
    import yaml
except ImportError:
    yaml = None

CONSTANT = 'foo'


class Example:
    # ...

Modelos

Nombre de modelos

Se recomienda que los modelos sean nombrados en forma Singular y si contiene mas de una palabra, la primera letra de cada palabra debe ir en Mayúscula.( UpperCamelCase )

class Owner(models.Model):
    pass

class Item(models.Model):
    pass

Nombre de Campos de Modelo

Todos los nombres de los campos de un modelo deben ir en minúscula, si se desean utilizar mas de una palabra, deben unirse mediante subguiones(_).

# ESTO ES CORRECTO
class Person(models.Model):
    first_name = models.CharField(max_length=20)
    last_name = models.CharField(max_length=40)

# ESTO ES INCORRECTO
class Person(models.Model):
    FirstName = models.CharField(max_length=20)
    Last_Name = models.CharField(max_length=40)

Orden de un modelo

Cuando escribimos nuestros modelos, debemos mantener un orden al escribir los campos, los campos de managers personalizados, la clase Meta, el método __str__, etc, procura mantener el siguiente orden:

  • Todos los campos del modelos
  • Los campos que hagan referencia a Managers personalizaros
  • class Meta
  • def __str__()
  • def save()
  • def get_absolute_url()
  • Otros métodos propios que desees añadir
class Person(models.Model):
    first_name = models.CharField(max_length=20)
    last_name = models.CharField(max_length=40)

    custom_manager = CustomManager()

    class Meta:
        pass

    def __str__(self):
        pass

    def save():
        pass

    def get_absolute_url():
        pass


    def get_full_name():
        pass

Choices en modelo

Si vas a definir Opciones(Choices) para un campo de modelo, define cada opción como una Lista de Tuplas, con todos sus nombres en Mayúscula.

class MyModel(models.Model):
    DIRECTION_UP = 'U'
    DIRECTION_DOWN = 'D'
    DIRECTION_CHOICES = [
        (DIRECTION_UP, 'Up'),
        (DIRECTION_DOWN, 'Down'),
    ]

BooleanField

No declares un campo de tipo BooleanField con null=True o blank=True; si deseas que el campo booleano sea opcional, utiliza el tipo de campo NullBoleanField.

No utilizar .all() antes de un .filter()

Cuando vayas a realizar un filtro en tu consulta con el ORM de Django, no es necesario que agregues un .all() antes de un .filter() ya que .filter() va a buscar en todos los existentes si es que no se ha filtrado antes.

No utilices muchos Boolean si no es necesario

A veces utilizamos campos Boolean para manejar Estados del modelo, es decir, las diferentes etapas que puede tener cada instancia de este, por ejemplo.

class Article(models.Model):
    is_published = models.BooleanField(default=False)
    is_verified = models.BooleanField(default=False)
    …

Sin embargo, podemos manejar los estados con un Choices, quedandode la siguiente manera.

Cabe aclarar que esto dependería del caso de uso o requerimiento, pero de ser posible, aplicalo.

class Article(models.Model):
    NEW = 'N'
    VERIFIED = 'F'
    PUBLISHED = 'P'
    STATUSES = [
        (NEW, 'new'),
        (VERIFIED, 'verified'),
        (PUBLISHED, 'published')
    ]

    status = models.IntegerField(choices=STATUSES)
    …

Obtener el último y el primer elemento

Si deseas obtener el último elemento o el primero, por lo general solemos realizar lo siguiente:

  • Para obtener el primero: Model.objects.all().order_by('created')[0]
  • Para obtener el último: Model.objects.all().order_by('-created')[0]

Sin embargo, es mejor utilizar .latest('created') o .earliest('created') para obtener el último o primero respectivamente.

Otra opción es utilizar Model.objects.all().order_by('created').first() para obtener el primero o Model.objects.all().order_by('created').last()

Campo nombrado con _id

No agregues el sufijo o prefijo _id a un campo relacional ya que Django se lo agrega de forma automática.

Modelos Abstractos

Siempre que puedas, utiliza modelos Abstractos para evitar repetir la escritura de campos globales, recuerda que los Modelos no son mas que clases y se utiliza la Programación Orientada a Objetos para su definición, así que podemos utilizar la Herencia o Abstracción.

class BaseModel(models.Model):
    state = models.BooleanField(default=True)
    created = models.DateField(auto_now_add=True, auto_now=False)
    modified = models.DateField(auto_now=True, auto_now_add=False)
    deleted = models.DateField(auto_now=True, auto_now_add=False)

    class Meta:
        abstract = True


class Comment(BaseModel):
    pass

Utiliza Managers

Cuando un proyecto empieza a escalar, es normal que empecemos a esconder diversas consultas para ser llamadas de forma mas corta y directo, esto debido a que puede que una consulta contenga un filtro que es utilizado en múltiples funciones, por ello existen los Managers, los cuales nos permiten definir nuestras propias consultas personalizadas para poder invocarlas de forma reducida cuando necesitemos, hay que recordar que Django por defecto trae enlazado un Manager en todos nuestros modelos a través del atrributo OBJECTS.

Por ejemplo.

from django.db import models
from django.db.models.functions import Coalesce

class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(
            num_responses=Coalesce(models.Count("response"), 0)
        )

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    objects = models.Manager()
    custom_manager = PollManager()


class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    # ...

Si quieres averiguar mas información sobre los Managers, aquí te dejo el enlace a la Documentacion Oficial de Django.