En muchos casos, cuando estamos programando funcionalidades y obtenemos datos a través del ORM de Django, necesitamos obtener dichos datos como un Diccionario de Python, por defecto los datos son de tipo ModelObject, sin embargo, hay diversas maneras de poder convertir estos datos a un Diccionario, aquí te mostraremos algunas.

instance.__dict__

La forma mas simple y directa es utilizar el atributo .__dict__ del objeto que deseamos convertir, sin embargo esto nos trae un inconveniente: aquellos campos que sean many_to_many serán omitidos y los campos foreign_key retornarán con nombres cambiados y tendremos valores que no deseamos.

Para todos los ejemplos utilizaremos el siguiente modelo:

from django.db import models

class OtherModel(models.Model): pass

class SomeModel(models.Model):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")

Realizando una consulta para obtener una instancia de este objeto que ya contenga datos en la Base de Datos y utilizando el instance.__dict__ obtendriamos lo siguiente:

instance = SomeModel.objects.get(id=1)
instance = instance.__dict__
"""
{
     '_foreign_key_cache': <OtherModel: OtherModel object>,
     '_state': <django.db.models.base.ModelState at 0x7ff0993f6908>,
     'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
     'foreign_key_id': 2,
     'id': 1,
     'normal_value': 1,
     'readonly_value': 2
}
"""

model_to_dict

Otra opción es utilizar una función que incorpora Django, model_to_dict, sin embargo, esta función por defecto omite los campos que no son editables.

from django.forms.models import model_to_dict

model_to_dict(instance)
"""
{
     'foreign_key': 2,
     'id': 1,
     'many_to_many': [<OtherModel: OtherModel object>],
     'normal_value': 1
}
"""

Pero model_to_dict() tiene un parámetro llamado fields donde podemos indicarle que campos deseamos:

model_to_dict(instance, fields=[field.name for field in instance._meta.fields])
"""
{
    'foreign_key': 2, 
    'id': 1, 
    'normal_value': 1
}
"""

Este resultado no es muy eficiente y obtenemos un resultado peor que el por defecto.

query.values()

Otra opción es utilizar la función .values() en nuestra query, sin embargo obtenemos un resultado parecido a instance.__dict__ pero no tenemos los campos extras que no deseabamos y los campos foreign_key tienen una clave distinta, sumándole que los campos many_to_many no aparecen.

instance = SomeModel.objects.filter(id=1).values().first()

{
     'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
     'foreign_key_id': 2,
     'id': 1,
     'normal_value': 1,
     'readonly_value': 2
}

serialize

Django, además de la función model_to_dict() contiene otra función llamada serialize(), esta función recibe un conjunto de datos y retorna una lista de diccionarios( o JSON pero para fines del post los trataremos igual), cada diccionario corresponde a cada elemento del conjunto de datos, sin embargo, obtenemos mas información de la necesaria pero nos retorna la información de los campos como deseamos.

Esta función recibe un conjunto de datos, por lo que no podemos enviarle sólo un objeto o instancia, pero podemos hacerle un hack, enviarle una lista de elemento con sólo un elemento.

serialize() tiene su propia documentación dentro de la Documentación Oficial de Django donde se especifican mas características de ella, aquí te dejo el enlace:

Serializer - Django Documentation

from django.core.serializers import serialize

serialize('json', [instance, ])
"""
[
    {
        "pk": 1,
        "model": "otherapp.othemodel",
        "fields": {
             "auto_now_add": "2013-01-16T08:16:59.844Z",
             "foreign_key": 2,
             "normal_value": 1,
             "readonly_value": 2,
             "many_to_many": [2, 3]
        }
    }
]

"""

Serializers

Una manera mas complicada(internamente) y requiere instalar un framework pero muy efectiva y facil de realizar, utilizar los Serializers de Django Rest Framework.

Basicamente, los Serializers pueden generarse basados en un Modelo en específico, al definirlos de esta forma, toman de forma automática los campos definidos en el Modelo o que hayan sido especificados en su clase Meta, en el atributo fields para generar un JSON completamente modificable.

Si quieres averiguar más sobre esto, te dejo el link añ curso de nuestro canal de Youtube:

Curso Django Rest Framework

O directamente un vídeo sobre los Serializers:

from rest_framework import serializers

class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = "__all__"

SomeModelSerializer(instance).data
"""
{
     'auto_now_add': '2018-12-20T21:34:29.494827Z',
     'foreign_key': 2,
     'id': 1,
     'many_to_many': [2],
     'normal_value': 1,
     'readonly_value': 2
}
"""

Función personalizada

Si ninguna de las opciones antes mencionadas te convence y quieres realizar tu propia función que realice este procedimiento, puedes tomar como base la siguiente, esta función ha sido obtenida modificando el código fuente de la función model_to_dict() que tiene definido Django.

from itertools import chain

def to_dict(instance):
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields):
        data[f.name] = f.value_from_object(instance)
    for f in opts.many_to_many:
        data[f.name] = [i.id for i in f.value_from_object(instance)]
    return data

to_dict(instance)
"""
{
     'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
     'foreign_key': 2,
     'id': 1,
     'many_to_many': [2],
     'normal_value': 1,
     'readonly_value': 2
}
"""

Si deseas, puedes generalizar esta función para que todos tus modelos la contengan.

from itertools import chain

from django.db import models

class DictModel(models.Model):

    def to_dict(self):
        data = {}
        opts = self._meta        
        for f in opts.concrete_fields + opts.many_to_many:
            if isinstance(f, ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = list(f.value_from_object(self).values_list('pk', flat=True))
            elif isinstance(f, DateTimeField):
                if f.value_from_object(self) is not None:
                    data[f.name] = f.value_from_object(self).isoformat()
            else:
                data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)
        return data

    class Meta:
        abstract = True

class SomeModel(DictModel):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")


instance.to_dict()
"""
{
     'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
     'foreign_key': 2,
     'id': 1,
     'many_to_many': [2],
     'normal_value': 1,
     'readonly_value': 2
}
"""

Si deseas que cuando imprimas en consola tus objetos para probar los resultados, puedes sobreescribir el método __repr__() de DictModel.

from itertools import chain

from django.db import models

class DictModel(models.Model):

    def __repr__(self):
        return str(self.to_dict())

    def to_dict(self):
        data = {}
        opts = self._meta        
        for f in opts.concrete_fields + opts.many_to_many:
            if isinstance(f, ManyToManyField):
                if self.pk is None:
                    data[f.name] = []
                else:
                    data[f.name] = list(f.value_from_object(self).values_list('pk', flat=True))
            elif isinstance(f, DateTimeField):
                if f.value_from_object(self) is not None:
                    data[f.name] = f.value_from_object(self).isoformat()
            else:
                data[f.name] = None
            else:
                data[f.name] = f.value_from_object(self)
        return data

    class Meta:
        abstract = True

class SomeModel(DictModel):
    normal_value = models.IntegerField()
    readonly_value = models.IntegerField(editable=False)
    auto_now_add = models.DateTimeField(auto_now_add=True)
    foreign_key = models.ForeignKey(OtherModel, related_name="ref1")
    many_to_many = models.ManyToManyField(OtherModel, related_name="ref2")


instance.to_dict()
"""
{
     'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=<UTC>),
     'foreign_key': 2,
     'id': 1,
     'many_to_many': [2],
     'normal_value': 1,
     'readonly_value': 2
}
"""