Existem várias maneiras de converter uma instância em um dicionário, com graus variados de manipulação de casos de canto e proximidade com o resultado desejado.
1 instance.__dict__
instance.__dict__
que retorna
{'_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}
Essa é de longe a mais simples, mas está faltando many_to_many
, foreign_key
está com o nome errado e tem duas coisas extras indesejadas.
2) model_to_dict
from django.forms.models import model_to_dict
model_to_dict(instance)
que retorna
{'foreign_key': 2,
'id': 1,
'many_to_many': [<OtherModel: OtherModel object>],
'normal_value': 1}
Este é o único com many_to_many
, mas faltam os campos não editáveis.
3) model_to_dict(..., fields=...)
from django.forms.models import model_to_dict
model_to_dict(instance, fields=[field.name for field in instance._meta.fields])
que retorna
{'foreign_key': 2, 'id': 1, 'normal_value': 1}
Isso é estritamente pior que a model_to_dict
invocação padrão .
4) query_set.values()
SomeModel.objects.filter(id=instance.id).values()[0]
que retorna
{'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}
É a mesma saída, instance.__dict__
mas sem os campos extras.
foreign_key_id
ainda está errado e many_to_many
ainda está faltando.
5. Função Personalizada
O código para django's model_to_dict
teve a maior parte da resposta. Ele removeu explicitamente os campos não editáveis, portanto, remover essa verificação e obter os IDs de chaves estrangeiras de muitos a muitos campos resulta no seguinte código, que se comporta conforme o desejado:
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
Embora essa seja a opção mais complicada, a chamada to_dict(instance)
nos dá exatamente o resultado desejado:
{'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}
6. Use serializadores
O ModelSerialzer do Django Rest Framework permite criar um serializador automaticamente a partir de um modelo.
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = "__all__"
SomeModelSerializer(instance).data
retorna
{'auto_now_add': '2018-12-20T21:34:29.494827Z',
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
Isso é quase tão bom quanto a função personalizada, mas auto_now_add é uma sequência em vez de um objeto datetime.
Rodada de bônus: melhor impressão do modelo
Se você deseja um modelo de django que tenha uma melhor exibição da linha de comando python, faça com que seus modelos sejam da classe filho da seguinte maneira:
from django.db import models
from itertools import chain
class PrintableModel(models.Model):
def __repr__(self):
return str(self.to_dict())
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
class Meta:
abstract = True
Então, por exemplo, se definirmos nossos modelos como tal:
class OtherModel(PrintableModel): pass
class SomeModel(PrintableModel):
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")
A chamada SomeModel.objects.first()
agora produz resultados como este:
{'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}
to_dict
e manipulá-lo da maneira que desejar.