将Django模型对象转换为所有字段均完整的dict


257

如何将Django模型对象转换为具有所有字段的字典?理想情况下,所有内容都包含带有的外键和字段editable=False

让我详细说明。假设我有一个类似以下的Django模型:

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

在终端中,我已执行以下操作:

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

我想将其转换为以下字典:

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

答案不令人满意的问题:

Django:将整个模型对象集转换为单个字典

如何将Django模型对象转换为字典并仍然具有其外键?


1
您可以声明一个名为的方法to_dict,并以所需的方式对其进行处理。
karthikr 2014年

@karthikr是的,我可以。问题是如何创建这样的方法。从模型的所有字段手动构建字典都不是合适的答案。
Zags 2014年

我会利用Django Rest Framework,Tastypie或Piston之类的现有ReST库,因为它们都提供了将Django模型实例转换为基元进行序列化的机制。如果您更好奇如何操作,可以查看其代码,但这主要是遍历模型的_meta定义,以查找与模型关联的字段并在实例上检索其值。
凯文·斯通

Answers:


523

有多种方法可将实例转换为字典,并具有不同程度的特殊情况处理和接近所需结果的程度。


1。 instance.__dict__

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}

到目前为止,这是最简单的方法,但是缺少many_to_manyforeign_key被错误命名,并且其中有两个多余的多余内容。


2。 model_to_dict

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}

这是唯一的many_to_many,但缺少不可编辑的字段。


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

哪个返回

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

这绝对比标准model_to_dict调用差。


4。 query_set.values()

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

哪个返回

{'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}

这与输出相同,instance.__dict__但没有额外的字段。 foreign_key_id仍然是错误的,many_to_many仍然不见了。


5.自定义功能

django的代码model_to_dict具有大部分答案。它显式删除了不可编辑的字段,因此删除该检查并获取多对多字段的外键ID会导致以下代码按预期运行:

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}

6.使用序列化器

Django Rest Framework的ModelSerialzer允许您从模型自动构建序列化器。

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}

这几乎与自定义函数一样好,但是auto_now_add是字符串而不是datetime对象。


奖金回合:更好的模型印刷

如果您想要一个具有更好的python命令行显示的Django模型,请让您的模型将以下子类:

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

因此,例如,如果我们这样定义模型:

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

SomeModel.objects.first()现在调用将产生如下输出:

{'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}

2
感谢您的回答!您可以将isinstance解决方案5中的测试(和奖金)更改为if f.many_to_many
dhobbs

1
@dhobbs我model_to_dict使用Django的代码对代码进行了建模isinstance。我不确定他们为什么会做出此选择,但可能有充分的理由(例如,该many_to_many属性在更高版本中引入)
Zags

还会返回@property字段吗?
Angrysumit '18 -10-8

1
我想知道这些方法如何处理带注释/聚合的字段?
mehmet

我要做的就是检查get_FOO_display并返回该值,而不是实际存在的任何值。
Bobort

9

我找到了一个整洁的解决方案以得到结果:

假设您有一个模型对象o

只需致电:

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

10
这只是我的答案中的第4个选项
-Zags,

7

@Zags解决方案很棒!

不过,我将为datefields添加一个条件,以使其对JSON友好。

奖金回合

如果您希望Django模型具有更好的python命令行显示,请让您的模型子类具有以下功能:

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

因此,例如,如果我们这样定义模型:

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

SomeModel.objects.first()现在调用将产生如下输出:

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

如果要与JSON进行相互转换,则应查看Django Rest Framework或使用类似的方法:stackoverflow.com/a/22238613/2800876
Zags

当然!但是,对您的代码进行的这一微小更改增加了很多便利!
迭戈·弗雷塔斯·科埃略

4

最简单的方法

  1. 如果您的查询是Model.Objects.get():

    get()将返回单个实例,因此您可以直接__dict__从实例中使用

    model_dict = Model.Objects.get().__dict__

  2. 对于filter()/ all():

    all()/ filter()将返回实例列表,因此您可以values()用来获取对象列表。

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


4

只是vars(obj),它将说明对象的整个值

>>> obj_attrs = vars(obj)
>>> obj_attrs
 {'_file_data_cache': <FileData: Data>,
  '_state': <django.db.models.base.ModelState at 0x7f5c6733bad0>,
  'aggregator_id': 24,
  'amount': 5.0,
  'biller_id': 23,
  'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
  'file_data_id': 797719,
 }

您也可以添加

>>> keys = obj_attrs.keys()
>>> temp = [obj_attrs.pop(key) if key.startswith('_') else None for key in keys]
>>> del temp
>>> obj_attrs
   {
    'aggregator_id': 24,
    'amount': 5.0,
    'biller_id': 23,
    'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=<UTC>),
    'file_data_id': 797719,
   }

3

更新资料

@zags发布的较新的汇总答案比我自己的答案更完整,更优雅。请改为参考该答案。

原版的

如果您愿意像@karthiker建议的那样定义自己的to_dict方法,那么就可以将此问题归结为集合问题。

>>># 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=<UTC>),
 u'id': 1L,
 'reference1_id': 1L,
 'value': 1L,
 'value2': 2L}

我们需要从otherDict中删除贴标签的外键。

为此,我们可以使用一个循环来创建一个新字典,该字典除了包含下划线的项外,还包含所有项。或者,为了节省时间,我们可以将它们添加到原始字典中,因为字典只是在幕后设置的。

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

因此,我们只能用下面的字典

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

然后您将其退回。

不利的一面是,您不能在editable = false字段名称中使用下划线。从好的方面来说,这将适用于用户创建的字段不包含下划线的任何字段集。

这不是执行此操作的最佳方法,但是在找到更直接的方法之前,它可以作为临时解决方案。

对于以下示例,将基于model_to_dict形成dict,并通过filter的values方法形成otherDict。我本来可以用模型自己完成的,但是我无法让我的机器接受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]}
>>>

我希望,这应该使您对问题的答案有个大概的了解。


1
不确定您要re在这里使用什么。如果要过滤掉带有下划线的键,则既不是正确的代码也不是正确的行为。 re.match("_", "reference1_id")返回值None和数据库中的合法列的名称中可能带有下划线。
扎格斯2014年

re.match(“ _”,“ reference1_id”)确实返回None,应该是:re.match(“。* _。*”,“ reference1_id”)
小工具

我进行了一些更改以删除错误的示例,并包括一个更好的示例。我还更改了一些内容以表示这将是所有模型子集的临时解决方案。我不知道您要为在其editable=false领域中带有下划线的模型做什么。我只是想提供一些您可以使用的功能,直到可以提供更多的经典解决方案为止。

也许使用"_" in string而不是re那样。
萨格斯2014年

是的,这将是一种更简单的方法。以这种方式使用它并不是我想到的,但是现在完全有意义。我已将答案改为in而不是re

2

这里有很多有趣的解决方案。我的解决方案是使用dict理解将as_dict方法添加到模型中。

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

另外,如果您要将模型导出到另一个库,则此解决方案与对查询的列表理解一起可以提供一个不错的解决方案。例如,将模型转储到pandas数据框中:

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

1
这对于诸如字符串和整数之类的值字段有效,但对于外键会存在一些问题,甚至在许多字段中也会存在更多问题
Zags

很好点!特别是对于许多人。人们可能希望添加一些条件来适当地处理这些情况,或者将这种解决方案限制为简单模型。谢谢。
t1m0

1

(并非要发表评论)

好的,它并不是真的那样依赖类型。我可能对这里的原始问题有误解,因此请原谅。如果创建serliazers.py,则在其中创建具有元类的类。

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

然后,当您在视图类中获取数据时,您可以:

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

这在很多地方都差不多,它通过JSONRenderer返回了不错的JSON。

正如我所说的,这是DjangoRestFramework的礼貌,因此值得研究。


1

更简单的方法是只使用pprint,这在基本Python中

import pprint
item = MyDjangoModel.objects.get(name = 'foo')
pprint.pprint(item.__dict__, indent = 4)

这给出的输出类似于,json.dumps(..., indent = 4)但可以正确处理可能嵌入在模型实例中的怪异数据类型,例如ModelStateUUID

在Python 3.7上测试


0

也许这对您有帮助。也许这并不能掩盖很多对很多的关系,但是当您要以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

0

您见过的最佳解决方案。

将django.db.models.Model实例以及所有相关的ForeignKey,ManyToManyField和@Property函数字段转换为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.core.exceptions
import django.db.models
import django.forms.models


def get_decorators_dir(cls, exclude: typing.Optional[set]=None) -> set:
    """
    Ref: /programming/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.Iterable]=None, exclude: typing.Optional[typing.Iterable]=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:
                    self_fields.add(splits[0])

                    field_name = splits[0]
                    child_fields.setdefault(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 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:
                    data[f.name] = None
                    try:
                        foreign_inst = getattr(self, f.name)
                    except django.core.exceptions.ObjectDoesNotExist:
                        pass
                    else:
                        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)
                            )
                        elif foreign_inst is not None:
                            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, PrintableModel) and hasattr(value, "to_dict"):
                data[name] = value.to_dict(
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name)
                )
            elif hasattr(value, "_meta"):
                # make sure it is a instance of django.db.models.fields.Field
                data[name] = django.forms.models.model_to_dict(
                    value,
                    fields=child_fields.get(name),
                    exclude=child_exclude.get(name),
                )
            elif isinstance(value, (set, )):
                data[name] = list(value)
            else:
                data[name] = value

        return data

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


0

@zags的回答很全面,应该足够了,但是#5方法(这是IMO最好的方法)抛出错误,因此我改进了辅助函数。

由于OP请求转换many_to_many领域成主键,而不是对象的列表清单,我增强了功能,所以返回值现在为JSON序列化-通过将datetime物体进入strmany_to_many对象ID的列表。

import datetime

def ModelToDict(instance):
    '''
    Returns a dictionary object containing complete field-value pairs of the given instance

    Convertion rules:

        datetime.date --> str
        many_to_many --> list of id's

    '''

    concrete_fields = instance._meta.concrete_fields
    m2m_fields = instance._meta.many_to_many
    data = {}

    for field in concrete_fields:
        key = field.name
        value = field.value_from_object(instance)
        if type(value) == datetime.datetime:
            value = str(field.value_from_object(instance))
        data[key] = value

    for field in m2m_fields:
        key = field.name
        value = field.value_from_object(instance)
        data[key] = [rel.id for rel in value]

    return data

你得到什么错误?我很高兴更新答案
Zags

当前,循环的功能concrete_fieldsm2m_fields外观相同,因此,假设m2m_fields此处的循环实现不正确。
丹尼尔·希默尔斯泰因

@Zags错误是AttributeError: 'list' object has no attribute 'values_list' 我找不到其背后的原因。我正在使用Django 2.1.1
Armin Hemati Nik

@ daniel-himmelstein感谢您指出,我已修复了代码。循环相同的原因是由于我在本地代码中执行了不同的操作,因此我忘记针对SO答案进行优化。
Armin Hemati Nik

@ArminHemati Django更改了的实现,field.value_from_object因此model_to_dict。我已经更新了答案的第5节以反映这一点。
Zags
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.