Convierta el objeto Modelo Django a dictado con todos los campos intactos

¿Cómo se convierte uno un objeto Modelo django en un dict con todos sus campos? Lo ideal es que todos incluyan claves externas y campos con editable = False.

Déjame elaborar. Digamos que tengo un modelo de django como el siguiente:

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") 

En la terminal, he hecho lo siguiente:

 other_model = OtherModel() other_model.save() instance = SomeModel() instance.normal_value = 1 instance.readonly_value = 2 instance.foreign_key = other_model instance.save() instance.many_to_many.add(other_model) instance.save() 

Quiero convertir esto al siguiente diccionario:

 {'auto_now_add': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=), 'foreign_key': 1, 'id': 1, 'many_to_many': [1], 'normal_value': 1, 'readonly_value': 2} 

Preguntas con respuestas insatisfactorias:

Django: convertir un conjunto completo de los objetos de un modelo en un solo diccionario

¿Cómo puedo convertir los objetos del modelo Django en un diccionario y aún tener sus claves externas?

Hay muchas maneras de convertir una instancia en un diccionario, con diversos grados de manejo de casos de esquina y cercanía al resultado deseado.


1. instance.__dict__

 instance.__dict__ 

que devuelve

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

Este es, con mucho, el más simple, pero falta many_to_many , foreign_key se foreign_key erróneamente y tiene dos elementos adicionales no deseados.


2. model_to_dict

 from django.forms.models import model_to_dict model_to_dict(instance) 

que devuelve

 {'foreign_key': 2, 'id': 1, 'many_to_many': [], 'normal_value': 1} 

Este es el único con many_to_many , pero falta los campos no editables.


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 devuelve

 {'foreign_key': 2, 'id': 1, 'normal_value': 1} 

Esto es estrictamente peor que la invocación estándar de model_to_dict .


4. query_set.values()

 SomeModel.objects.filter(id=instance.id).values()[0] 

que devuelve

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

Esta es la misma salida que la instance.__dict__ pero sin los campos adicionales. foreign_key_id sigue siendo incorrecto y many_to_many todavía falta.


5. Función personalizada

El código para model_to_dict de django tenía la mayor parte de la respuesta. Se eliminaron explícitamente los campos no editables, por lo que al eliminar esa verificación se obtiene el siguiente código que se comporta como se desea:

 from django.db.models.fields.related import ManyToManyField def to_dict(instance): opts = instance._meta data = {} for f in opts.concrete_fields + opts.many_to_many: if isinstance(f, ManyToManyField): if instance.pk is None: data[f.name] = [] else: data[f.name] = list(f.value_from_object(instance).values_list('pk', flat=True)) else: data[f.name] = f.value_from_object(instance) return data 

Si bien esta es la opción más complicada, llamar a to_dict(instance) nos da exactamente el resultado deseado:

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

6. Use serializadores

El ModelSerialzer de Django Rest Framework le permite crear un serializador automáticamente a partir de un modelo.

 from rest_framework import serializers class SomeModelSerializer(serializers.ModelSerializer): class Meta: model = SomeModel fields = "__all__" SomeModelSerializer(instance).data 

devoluciones

 {'auto_now_add': '2018-12-20T21:34:29.494827Z', 'foreign_key': 2, 'id': 1, 'many_to_many': [2], 'normal_value': 1, 'readonly_value': 2} 

Esto es casi tan bueno como la función personalizada, pero auto_now_add es una cadena en lugar de un objeto de fecha y hora.


Ronda de bonos: mejor modelo de impresión

Si desea un modelo django que tenga una mejor visualización de la línea de comandos de python, haga que sus modelos sean de la siguiente clase:

 from django.db import models from django.db.models.fields.related import ManyToManyField class PrintableModel(models.Model): def __repr__(self): return str(self.to_dict()) def to_dict(self): opts = self._meta data = {} 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)) else: data[f.name] = f.value_from_object(self) return data class Meta: abstract = True 

Así, por ejemplo, si definimos nuestros modelos como tales:

 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") 

Llamar a SomeModel.objects.first() ahora da un resultado como este:

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

Encontré una solución ordenada para llegar al resultado:

Supongamos que tienes un objeto modelo o :

Solo llama:

 type(o).objects.filter(pk=o.pk).values().first() 

¡La solución de @Zags era hermosa!

Sin embargo, agregaría una condición para los campos de fecha para que sea compatible con JSON.

Ronda de bonos

Si desea un modelo de django que tenga una mejor visualización de la línea de comandos de Python, haga que sus modelos sean de la siguiente clase:

 from django.db import models from django.db.models.fields.related import ManyToManyField class PrintableModel(models.Model): def __repr__(self): return str(self.to_dict()) def to_dict(self): opts = self._meta data = {} 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).timestamp() else: data[f.name] = None else: data[f.name] = f.value_from_object(self) return data class Meta: abstract = True 

Así, por ejemplo, si definimos nuestros modelos como tales:

 class OtherModel(PrintableModel): pass class SomeModel(PrintableModel): value = models.IntegerField() value2 = models.IntegerField(editable=False) created = models.DateTimeField(auto_now_add=True) reference1 = models.ForeignKey(OtherModel, related_name="ref1") reference2 = models.ManyToManyField(OtherModel, related_name="ref2") 

Llamar a SomeModel.objects.first() ahora da un resultado como este:

 {'created': 1426552454.926738, 'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]} 

Si está dispuesto a definir su propio método to_dict, como sugirió @karthiker, entonces solo se reduce este problema a un problema de conjuntos.

 >>># Returns a set of all keys excluding editable = False keys >>>dict = model_to_dict(instance) >>>dict {u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1} >>># Returns a set of editable = False keys, misnamed foreign keys, and normal keys >>>otherDict = SomeModel.objects.filter(id=instance.id).values()[0] >>>otherDict {'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=), u'id': 1L, 'reference1_id': 1L, 'value': 1L, 'value2': 2L} 

Necesitamos eliminar las claves externas mal etiquetadas de otherDict .

Para hacer esto, podemos usar un bucle que hace un nuevo diccionario que tiene todos los elementos excepto aquellos con guiones bajos en ellos. O, para ahorrar tiempo, solo podemos agregarlos al dictamen original, ya que los diccionarios se configuran bajo el capó.

 >>>for item in otherDict.items(): ... if "_" not in item[0]: ... dict.update({item[0]:item[1]}) ... >>> 

Así nos quedamos con el siguiente dictado :

 >>>dict {'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=), u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1, 'value2': 2L} 

Y usted acaba de devolver eso.

En el lado negativo, no puede usar guiones bajos en sus nombres de campos editables = falsos. Por el lado positivo, esto funcionará para cualquier conjunto de campos donde los campos hechos por el usuario no contengan guiones bajos.

–EDITAR–

Esta no es la mejor manera de hacerlo, pero podría funcionar como una solución temporal hasta que se encuentre un método más directo.

Para el ejemplo a continuación, dict se formaría basándose en model_to_dict y otherDict se formaría mediante el método de valores de filtro. Habría hecho esto con los modelos en sí, pero no puedo hacer que mi máquina acepte otherModel.

 >>> import datetime >>> dict = {u'id': 1, 'reference1': 1, 'reference2': [1], 'value': 1} >>> otherDict = {'created': datetime.datetime(2014, 2, 21, 4, 38, 51), u'id': 1, 'reference1_id': 1, 'value': 1, 'value2': 2} >>> for item in otherDict.items(): ... if "_" not in item[0]: ... dict.update({item[0]:item[1]}) ... >>> dict {'reference1': 1, 'created': datetime.datetime(2014, 2, 21, 4, 38, 51), 'value2': 2, 'value': 1, 'id': 1, 'reference2': [1]} >>> 

Eso debería ponerlo en un áspero estadio de la respuesta a su pregunta, espero.

Forma más sencilla,

  1. Si su consulta es Model.Objects.get ():

    get () devolverá una instancia única para que pueda usar directamente __dict__ desde su instancia

    model_dict = Model.Objects.get().__dict__

  2. para filter () / all ():

    all () / filter () devolverá una lista de instancias para que pueda usar values() para obtener una lista de objetos.

    model_values ​​= Model.Objects.all (). values ​​()

solo vars(obj) , indicará todos los valores del objeto

 {'_file_data_cache': , '_state': , 'aggregator_id': 24, 'amount': 5.0, 'biller_id': 23, 'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=), 'file_data_id': 797719, } 

(no quiso hacer el comentario)

Ok, realmente no depende de los tipos de esa manera. Puede que haya entendido mal la pregunta original aquí, así que perdóneme si ese es el caso. Si creas serliazers.py entonces creas clases que tienen metaclases.

 Class MyModelSerializer(serializers.ModelSerializer): class Meta: model = modelName fields =('csv','of','fields') 

Luego, cuando obtiene los datos en la clase de vista, puede:

 model_data - Model.objects.filter(...) serializer = MyModelSerializer(model_data, many=True) return Response({'data': serilaizer.data}, status=status.HTTP_200_OK) 

Eso es más o menos lo que tengo en una gran variedad de lugares y devuelve JSON agradable a través del JSONRenderer.

Como dije, esto es cortesía de DjangoRestFramework, así que vale la pena analizarlo.

La mejor solución que jamás hayas visto.

Convierta la instancia de django.db.models.Model y todos los campos relacionados con ForeignKey, ManyToManyField y @Property en dict.

 """ Convert django.db.models.Model instance and all related ForeignKey, ManyToManyField and @property function fields into dict. Usage: class MyDjangoModel(... PrintableModel): to_dict_fields = (...) to_dict_exclude = (...) ... a_dict = [inst.to_dict(fields=..., exclude=...) for inst in MyDjangoModel.objects.all()] """ import typing import django.db.models import django.forms.models def get_decorators_dir(cls, exclude: typing.Optional[set]=None) -> set: """ Ref: https://stackoverflow.com/questions/4930414/how-can-i-introspect-properties-and-model-fields-in-django :param exclude: set or None :param cls: :return: a set of decorators """ default_exclude = {"pk", "objects"} if not exclude: exclude = default_exclude else: exclude = exclude.union(default_exclude) return set([name for name in dir(cls) if name not in exclude and isinstance(getattr(cls, name), property)]) class PrintableModel(django.db.models.Model): class Meta: abstract = True def __repr__(self): return str(self.to_dict()) def to_dict(self, fields: typing.Optional[typing.Set]=None, exclude: typing.Optional[typing.Set]=None): opts = self._meta data = {} # support fields filters and excludes if not fields: fields = set() else: fields = set(fields) default_fields = getattr(self, "to_dict_fields", set()) fields = fields.union(default_fields) if not exclude: exclude = set() else: exclude = set(exclude) default_exclude = getattr(self, "to_dict_exclude", set()) exclude = exclude.union(default_exclude) # support syntax "field__childField__..." self_fields = set() child_fields = dict() if fields: for i in fields: splits = i.split("__") if len(splits) == 1: self_fields.add(splits[0]) else: field_name = splits[0] if field_name not in child_fields: child_fields[field_name] = set() child_fields[field_name].add("__".join(splits[1:])) self_exclude = set() child_exclude = dict() if exclude: for i in exclude: splits = i.split("__") if len(splits) == 1: self_exclude.add(splits[0]) else: field_name = splits[0] if field_name not in child_exclude: child_exclude[field_name] = set() child_exclude[field_name].add("__".join(splits[1:])) for f in opts.concrete_fields + opts.many_to_many: if not getattr(f, 'editable', False): continue if self_fields and f.name not in self_fields: continue if self_exclude and f.name in self_exclude: continue if isinstance(f, django.db.models.ManyToManyField): if self.pk is None: data[f.name] = [] else: result = [] m2m_inst = f.value_from_object(self) for obj in m2m_inst: if isinstance(PrintableModel, obj) and hasattr(obj, "to_dict"): d = obj.to_dict(fields=child_fields.get(f.name), exclude=child_exclude.get(f.name)) else: d = django.forms.models.model_to_dict(obj, fields=child_fields.get(f.name), exclude=child_exclude.get(f.name)) result.append(d) data[f.name] = result elif isinstance(f, django.db.models.ForeignKey): if self.pk is None: data[f.name] = [] else: foreign_inst = getattr(self, f.name) if isinstance(foreign_inst, PrintableModel) and hasattr(foreign_inst, "to_dict"): data[f.name] = foreign_inst.to_dict(fields=child_fields.get(f.name), exclude=child_exclude.get(f.name)) else: data[f.name] = django.forms.models.model_to_dict(foreign_inst, fields=child_fields.get(f.name), exclude=child_exclude.get(f.name)) elif isinstance(f, (django.db.models.DateTimeField, django.db.models.DateField)): v = f.value_from_object(self) if v is not None: data[f.name] = v.isoformat() else: data[f.name] = None else: data[f.name] = f.value_from_object(self) # support @property decorator functions decorator_names = get_decorators_dir(self.__class__) for name in decorator_names: if self_fields and name not in self_fields: continue if self_exclude and name in self_exclude: continue value = getattr(self, name) if isinstance(value, (set, )): data[name] = list(value) else: data[name] = value return data 

https://gist.github.com/shuge/f543dc2094a3183f69488df2bfb51a52

Tal vez esto te ayude. Puede que esto no convierta a muchas relaciones, pero es bastante útil cuando quiere enviar su modelo en formato json.

 def serial_model(modelobj): opts = modelobj._meta.fields modeldict = model_to_dict(modelobj) for m in opts: if m.is_relation: foreignkey = getattr(modelobj, m.name) if foreignkey: try: modeldict[m.name] = serial_model(foreignkey) except: pass return modeldict 

Muchas soluciones interesantes aquí. Mi solución fue agregar un método as_dict a mi modelo con una comprensión de dictado.

 def as_dict(self): return dict((f.name, getattr(self, f.name)) for f in self._meta.fields) 

Como beneficio adicional, esta solución combinada con una lista de comprensión sobre una consulta lo convierte en una buena solución si desea exportar sus modelos a otra biblioteca. Por ejemplo, volcando tus modelos en un dataframe de pandas:

 pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])