lunes, 14 de septiembre de 2020

Código para mostrar las diferencias entre tus modelos Django y la base de datos

¿Cómo saber si existen diferencias entre tus modelos Django y la base de datos? 

Es posible que, por lo menos una vez en la vida, hayas tenido problemas aplicando las migraciones de tu proyecto Django.
Las migraciones son la forma en que Django propaga los cambios que realiza en sus modelos (agregar un campo, eliminar un modelo, etc.) en el esquema de su base de datos. Están diseñados para ser en su mayoría automáticos, pero necesitará saber cuándo realizar migraciones, cuándo ejecutarlas y los problemas comunes que puede encontrar. https://docs.djangoproject.com/en/3.1/topics/migrations/

¿Cuáles son los problemas más comunes? 

 El sistema de migraciones de Django es un sistema bastante potente. Te permite realizar varias operaciones importantes en tu base de datos, sin tan siquiera tener que escribir SQL. El sistema puede crear, a partir de los modelos que hayas creado en tus aplicaciones, toda la estructura de la base de datos, las modificaciones que realices a tus modelos se verán reflejadas en tu base de datos con tan solo escribir python manage.py makemigrations y python manage.py migrate. También te permite realizar la operación de manera contraria, creando tus modelos a partir de una base de datos ya existente. Pero, como bien lo dice el equipo de Django en su guía:
"no es perfecto y, para cambios complejos, es posible que no detecte lo que esperaba".
De modo que puedas verte en la necesidad de realizar ajustes en tus modelos o en la base de datos de forma manual. El siguiente código te puede ayudar a encontrar las diferencias existentes entre tus modelos y la base de datos:

from django.db import connections
from django.db.models import ForeignKey, ManyToOneRel, ManyToManyRel, ManyToManyField
from django.db.utils import OperationalError
from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings



def get_cursor(db_alias="default"):
"""
Obtiene el cursor de la conección a la base de datos.
"""
return connections[db_alias].cursor()


def check_database():
"""
Compruba que la información de los modelos está acorde con la información
en la base de datos. Los campos que estén acordes con la información de la
base de datos, serán marcados con una [X]; una casilla vacia en caso contrario [ ].
"""
from django.contrib.contenttypes.models import ContentType
qs = ContentType.objects.all()
cursor = get_cursor()

models_evals = []
dbname = str(settings.DATABASES["default"]["NAME"])
text = f"=======================\nTest: {dbname}\n=======================\n"
print(text)
for ct in qs:
model = ct.model_class()
if (model is None):
continue
models_evals, text = check_model(model, cursor, models_evals, text)
filename = dbname.replace("$", "_").replace(".", "_").replace(" ", "_")
filename = "test/" + filename.split("/")[-1] + ".txt"
f = open(filename, "w")
f.write(text)
f.close()
print(f"Guardado en: '{filename}'")


def check_model(model, cursor, models_evals=[], text=""):
"""
Compara la información de los campos del modelo indicado, con la base de datos.
"""
if (str(model) in models_evals):
return models_evals, text

print("\n----------------------------------")
print(model, model._meta.verbose_name)
text += f"\n{model} | {model._meta.verbose_name}\n"
models_evals.append(str(model))

try:
fields = model._meta.get_fields()
except (AttributeError) as e:
return models_evals, text

manytomanyfieldsmodels = []

for field in fields:
name = field.name

if isinstance(field, ForeignKey):
name = name + "_id"

elif isinstance(field, ManyToManyField):
manytomanyfieldsmodels.append(field.related_model)
print(f"Relación: {field}")
text += f"Relación: {field}\n"
continue

elif (isinstance(field, (ManyToOneRel, ManyToManyRel))):
print(f"Dependiente: {field}")
text += f"Dependiente: {field}\n"
continue

pri = f" - Field: {field.__class__.__name__}( '{name}' )"
try:
cursor.execute(f"SELECT {name} FROM {model._meta.db_table};")
except (OperationalError) as e:
pri = "[ ] "+ pri + f" | Error: {e}"
else:
pri = "[X] " + pri

print(pri)
text += f"{pri}\n"

for many_model in manytomanyfieldsmodels:
models_evals, text = check_model(many_model, cursor, models_evals, text)

return models_evals, text