将Django Model对象转换为dict,并保留所有字段

如何将django Model对象转换为带有所有字段的dict ?理想情况下,所有字段都包含外键和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:转换一个完整的模型's对象到一个单一的字典

如何将Django Model对象转换为字典,同时还保留外键?< / >

320386 次浏览

更新

@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中删除错误标记的外键。

为此,我们可以使用循环创建一个新字典,其中包含除下划线以外的所有项。或者,为了节省时间,我们可以将它们添加到原始的dict中,因为字典只是在引子下的集合。

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

这样我们就剩下了下面的dict:

>>>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字段名中使用下划线。有利的是,这将适用于用户创建的字段不包含下划线的任何字段集。

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

对于下面的示例,dict将基于model_to_dict形成,otherDict将由filter的values方法形成。我本可以对模型本身这样做,但我不能让我的机器接受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]}
>>>

我希望这能让你对你的问题有一个大致的答案。

(不是故意的)

好吧,它并不依赖于类型。我可能误解了最初的问题,所以如果是这样的话请原谅我。如果你创建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. 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框架的ModelSerializer允许你从一个模型自动构建一个序列化器。

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}

我找到了一个很好的解决方法:

假设你有一个模型对象o:

就叫:

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

也许这个能帮到你。也许这不会隐藏多对多的关系,但当你想以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

这里有很多有趣的解。我的解决方案是在我的模型中添加一个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. 如果你的查询是Model.Objects.get():

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

    model_dict = Model.Objects.get().__dict__

  2. filter()/all() < p >:

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

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

@Zags的解决方案太棒了!

不过,为了使它对JSON友好,我要为datefields添加一个条件。

奖金轮

如果你想要一个更好的python命令行显示的django模型,让你的models子类如下:

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

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,
}

你见过的最好的解决方案。

将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: 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.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

@zags的答案是全面的,应该足够但是#5方法(这是IMO中最好的一个)会抛出一个错误,所以我改进了helper函数。

由于OP要求将many_to_many字段转换为主键列表而不是对象列表,因此我增强了该函数,使返回值现在是JSON可序列化的——通过将datetime对象转换为str对象和many_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

更简单的方法是使用基本Python中的pprint

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

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

在Python 3.7上测试

当我试图使用django-rest框架将django站点转换为API时,我遇到了这个问题。通常django会从数据库中返回三种类型的对象。它们包括一个查询集、一个模型实例和一个分页器对象。对我来说,这些是需要转换的。

Queryset

queryset就像django中的模型对象列表。这是把它转换成字典的代码。

model_data=Model.object.all()# This returns a queryset object
model_to_dict=[model for model in model_data.values()]
return Response(model_to_dict,status=status.HTTP_200_OK)

模型实例

模型实例是模型的单个对象。

model_instance=Model.objects.get(pk=1)# This will return only a single model object
model_to_dict=model_to_dict(model_instance)
return Response(model_to_dict,status=status.HTTP_200_OK)

Paginator对象

分页器对象是一个包含特定页面的模型对象的对象。

model_queryset=Model.objects.all()
paginator = Paginator(model_queryset, 10)
try:
selected_results = paginator.page(page)
except Exception:
selected_results=result
paginator_to_dict=list(selected_results.object_list.values())
return Response(selected_results,status=status.HTTP_200_OK)

至少我是这么解决的。

将模型转换为字典并保留所有ForiegnKey模型关系。我使用了以下方法:

无详细名称

from django.forms.models import model_to_dict


instance = MyModel.objects.get(pk=1) # EXAMPLE


instance_dict = {key: getattr(instance, key) for key in model_to_dict(instance).keys()}

输出

{'foreign_key': [<OtherModel: OtherModel object>],
'id': 1,
'many_to_many': [<OtherModel: OtherModel object>],
'normal_value': 1}

如果你想在模板中显示外键关系的__str__()值,这可能很有用。

将关键字参数fields=exclude=包含到model_to_dict(instance, [...])中,可以过滤特定的字段。

详细名称

from django.forms.models import model_to_dict


instance = MyModel.objects.get(pk=1) # EXAMPLE


instance_dict = {instance._meta.get_field(key).verbose_name if hasattr(instance._meta.get_field(key), 'verbose_name') else key: getattr(instance, key) for key in model_to_dict(instance).keys()}

示例输出(如果给定示例有详细的名称)

{'Other Model:': [<OtherModel: OtherModel object>],
'id': 1,
'My Other Model:': [<OtherModel: OtherModel object>],
'Normal Value:': 1}

我喜欢将模型实例转换为dict进行快照测试,以下是我如何做到的:

注意:这里有camelize选项,因为如果api响应返回cammelized的对象,最好保持所有快照一致,无论是来自模型实例还是api调用。

from rest_framework import serializers
from djangorestframework_camel_case.util import camelize as _camelize


def model_to_dict(instance, camelize=False):
"""
Convert a model instance to dict.
"""
class Serializer(serializers.ModelSerializer):
class Meta:
model = type(instance)
fields = "__all__"
data = Serializer(instance).data
if camelize:
data = _camelize(data)
# convert from ordered dict to dict
return dict(data)
我创建了一个小片段,利用django的model_to_dict,但遍历对象的关系。 对于循环依赖项,它终止递归并放入引用依赖项对象的字符串。你可以将其扩展到包含不可编辑的字段

我在测试期间使用它来创建模型快照。

from itertools import chain


from django.db.models.fields.files import FileField, ImageField
from django.forms.models import model_to_dict




def get_instance_dict(instance, already_passed=frozenset()):
"""Creates a nested dict version of a django model instance
Follows relationships recursively, circular relationships are terminated by putting
a model identificator `{model_name}:{instance.id}`.
Ignores image and file fields."""
instance_dict = model_to_dict(
instance,
fields=[
f
for f in instance._meta.concrete_fields
if not isinstance(f, (ImageField, FileField))
],
)


already_passed = already_passed.union(
frozenset((f"{instance.__class__.__name__}:{instance.id}",))
)
# Go through possible relationships
for field in chain(instance._meta.related_objects, instance._meta.concrete_fields):
if (
(field.one_to_one or field.many_to_one)
and hasattr(instance, field.name)
and (relation := getattr(instance, field.name))
):
if (
model_id := f"{relation.__class__.__name__}:{relation.id}"
) in already_passed:
instance_dict[field.name] = model_id
else:
instance_dict[field.name] = get_instance_dict(relation, already_passed)


if field.one_to_many or field.many_to_many:
relations = []
for relation in getattr(instance, field.get_accessor_name()).all():
if (
model_id := f"{relation.__class__.__name__}:{relation.id}"
) in already_passed:
relations.append(model_id)
else:
relations.append(get_instance_dict(relation, already_passed))
instance_dict[field.get_accessor_name()] = relations


return instance_dict

我已经使用下一个函数转换模型到字典

def model_to_dict(obj):
return {x: obj.__dict__[x] for x in obj.__dict__ if x in {y.column for y in obj._meta.fields}}

例子

{'id': 8985,
'title': 'Dmitro',
'email_address': 'it9+8985@localhost',
'workspace_id': 'it9',
'archived': False,
'deleted': False,
'inbox': False,
'read': True,
'created_at': datetime.datetime(2022, 5, 5, 16, 55, 29, 791844, tzinfo=    <UTC>),
'creator': 'An So',
'last_message_id': 500566,
'stat_data': {'count_messages': 1, 'count_attachments': 0},
'stat_dirty': False,
'assign_to_id': None,
'assigned_at': None,
'assignment_note': None,
'initial_last_update_ts': 1651769728,
'renamed_manually': False,
'unread_timestamp': datetime.datetime(2022, 5, 5, 16, 55, 29, 842507, tzinfo=<UTC>)}


{'id': 6670,
'email_id': 473962,
'message_id': 500620,
'filename': 'Screenshot.png',
'size': 6076854,
'mimetype': 'image/png',
'aws_key': 'dev/RLpdcza46KFpITDWO_kv_fg2732waccB43z5RmT9/Screenshot.png',
'aws_key1': '',
'aws_key_thumb': 'dev/iaCdvcZmUKq-gJim7HT33ID46Ng4WOdxx-TdVuIU/f4b0db49-7f2d-4def-bdc1-8e394f98727f.png',
's3stored_file_id': 4147}