Python 将 Django 模型对象转换为 dict,所有字段都完好无损
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/21925671/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Convert Django Model object to dict with all of the fields intact
提问by Zags
How does one convert a Django Model object to a dict with allof its fields? All ideally includes foreign keys and fields with editable=False.
如何将 Django 模型对象转换为包含所有字段的字典?所有理想情况下都包括外键和带有editable=False.
Let me elaborate. Let's say I have a Django model like the following:
让我详细说明一下。假设我有一个如下所示的 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")
In the terminal, I have done the following:
在终端中,我完成了以下操作:
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()
I want to convert this to the following dictionary:
我想将其转换为以下字典:
{'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}
Questions with unsatisfactory answers:
回答不满意的问题:
Django: Converting an entire set of a Model's objects into a single dictionary
How can I turn Django Model objects into a dictionary and still have their foreign keys?
采纳答案by Zags
There are many ways to convert an instance to a dictionary, with varying degrees of corner case handling and closeness to the desired result.
有很多方法可以将实例转换为字典,具有不同程度的极端情况处理和与所需结果的接近程度。
1. instance.__dict__
1. instance.__dict__
instance.__dict__
which returns
返回
{'_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}
This is by far the simplest, but is missing many_to_many, foreign_keyis misnamed, and it has two unwanted extra things in it.
这是迄今为止最简单的,但缺少many_to_many,foreign_key被错误命名,并且其中有两个不需要的额外内容。
2. model_to_dict
2. model_to_dict
from django.forms.models import model_to_dict
model_to_dict(instance)
which returns
返回
{'foreign_key': 2,
 'id': 1,
 'many_to_many': [<OtherModel: OtherModel object>],
 'normal_value': 1}
This is the only one with many_to_many, but is missing the uneditable fields.
这是唯一一个带有many_to_many,但缺少不可编辑的字段。
3. model_to_dict(..., fields=...)
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])
which returns
返回
{'foreign_key': 2, 'id': 1, 'normal_value': 1}
This is strictly worse than the standard model_to_dictinvocation.
这比标准model_to_dict调用更糟糕。
4. query_set.values()
4. query_set.values()
SomeModel.objects.filter(id=instance.id).values()[0]
which returns
返回
{'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}
This is the same output as instance.__dict__but without the extra fields.
foreign_key_idis still wrong and many_to_manyis still missing.
这是相同的输出,instance.__dict__但没有额外的字段。
foreign_key_id仍然是错误的并且many_to_many仍然丢失。
5. Custom Function
5. 自定义功能
The code for django's model_to_dicthad most of the answer.  It explicitly removed non-editable fields, so removing that check and getting the ids of foreign keys for many to many fields results in the following code which behaves as desired:
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
While this is the most complicated option, calling to_dict(instance)gives us exactly the desired result:
虽然这是最复杂的选项,但调用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. Use Serializers
6. 使用序列化程序
Django Rest Framework's ModelSerialzer allows you to build a serializer automatically from a model.
Django Rest Framework的 ModelSerialzer 允许您从模型自动构建序列化器。
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = "__all__"
SomeModelSerializer(instance).data
returns
返回
{'auto_now_add': '2018-12-20T21:34:29.494827Z',
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}
This is almost as good as the custom function, but auto_now_add is a string instead of a datetime object.
这几乎与自定义函数一样好,但 auto_now_add 是一个字符串而不是日期时间对象。
Bonus Round: better model printing
奖励回合:更好的模型打印
If you want a django model that has a better python command-line display, have your models child-class the following:
如果您想要一个具有更好 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
So, for example, if we define our models as such:
因此,例如,如果我们这样定义我们的模型:
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")
Calling SomeModel.objects.first()now gives output like this:
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}
回答by Gadget
Update
更新
The newer aggregated answer posted by @zags is more complete and elegant than my own. Please refer to that answer instead.
@zags 发布的较新的汇总答案比我自己的更完整和优雅。请改为参考该答案。
Original
原来的
If you are willing to define your own to_dict method like @karthiker suggested, then that just boils this problem down to a sets problem.
如果您愿意像@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}
We need to remove the mislabeled foreign keys from otherDict.
我们需要从otherDict 中删除错误标记的外键。
To do this, we can use a loop that makes a new dictionary that has every item except those with underscores in them. Or, to save time, we can just add those to the original dictsince dictionaries are just sets under the hood.
为此,我们可以使用一个循环来创建一个新字典,该字典包含除带下划线的项目之外的所有项目。或者,为了节省时间,我们可以将它们添加到原始字典中,因为字典只是在幕后设置。
>>>for item in otherDict.items():
...    if "_" not in item[0]:
...            dict.update({item[0]:item[1]})
...
>>>
Thus we are left with the following dict:
因此,我们留下了以下dict:
>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
 u'id': 1L,
 'reference1': 1L,
 'reference2': [1L],
 'value': 1,
 'value2': 2L}
And you just return that.
你只需返回那个。
On the downside, you can't use underscores in your editable=false field names. On the upside, this will work for any set of fields where the user-made fields do not contain underscores.
不利的一面是,您不能在 editable=false 字段名称中使用下划线。从好的方面来说,这适用于用户创建的字段不包含下划线的任何字段集。
This is not the best way of doing this, but it could work as a temporary solution until a more direct method is found.
这不是最好的方法,但它可以作为一个临时解决方案,直到找到更直接的方法。
For the example below, dict would be formed based on model_to_dict and otherDict would be formed by filter's values method. I would have done this with the models themselves, but I can't get my machine to accept otherModel.
对于下面的示例,dict 将基于 model_to_dict 形成,otherDict 将通过过滤器的 values 方法形成。我本来可以用模型本身来做这件事,但我不能让我的机器接受其他模型。
>>> 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]}
>>>
That should put you in a rough ballpark of the answer to your question, I hope.
我希望,这应该让您大致了解问题的答案。
回答by nathj07
(did not mean to make the comment)
(没有发表评论的意思)
Ok, it doesn't really depend on types in that way. I may have mis-understood the original question here so forgive me if that is the case. If you create serliazers.py then in there you create classes that have meta classes.
好的,它并不真正依赖于那种方式。我可能误解了这里的原始问题,如果是这样的话,请原谅我。如果您创建 serliazers.py,那么您将在其中创建具有元类的类。
Class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = modelName
        fields =('csv','of','fields')
Then when you get the data in the view class you can:
然后当您在视图类中获取数据时,您可以:
model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)
That is pretty much what I have in a vareity of places and it returns nice JSON via the JSONRenderer.
这几乎是我在很多地方所拥有的,它通过 JSONRenderer 返回了很好的 JSON。
As I said this is courtesy of the DjangoRestFramework so it's worth looking into that.
正如我所说,这是由 DjangoRestFramework 提供的,因此值得研究一下。
回答by Alfred Huang
I found a neat solution to get to result:
我找到了一个巧妙的解决方案来获得结果:
Suppose you have an model object o:
假设你有一个模型对象o:
Just call:
只需致电:
type(o).objects.filter(pk=o.pk).values().first()
回答by Pjl
Maybe this help you. May this not covert many to many relantionship, but es pretty handy when you want to send your model in json format.
也许这对你有帮助。可能这不会掩盖多对多的关系,但是当您想以 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
回答by t1m0
Lots of interesting solutions here. My solution was to add an as_dict method to my model with a dict comprehension.
这里有很多有趣的解决方案。我的解决方案是使用 dict 理解向我的模型添加一个 as_dict 方法。
def as_dict(self):
    return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)
As a bonus, this solution paired with an list comprehension over a query makes for a nice solution if you want export your models to another library. For example, dumping your models into a pandas dataframe:
作为奖励,如果您想将模型导出到另一个库,此解决方案与对查询的列表理解相结合是一个不错的解决方案。例如,将您的模型转储到 Pandas 数据帧中:
pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])
回答by Mohideen bin Mohammed
Simplest way,
最简单的方法,
- If your query is Model.Objects.get(): - get() will return single instance so you can direct use - __dict__from your instance- model_dict = - Model.Objects.get().__dict__
- for filter()/all(): - all()/filter()will return list of instances so you can use - values()to get list of objects.- model_values = Model.Objects.all().values() 
- 如果您的查询是Model.Objects.get(): - get() 将返回单个实例,因此您可以直接 - __dict__从您的实例中使用- 模型_dict = - Model.Objects.get().__dict__
- 对于过滤器()/全部(): - all()/filter()将返回实例列表,因此您可以使用它 - values()来获取对象列表。- model_values = Model.Objects.all().values() 
回答by Diego Freitas Coelho
@Zags solution was gorgeous!
@Zags 解决方案太棒了!
I would add, though, a condition for datefields in order to make it JSON friendly.
不过,我会为日期字段添加一个条件,以使其对 JSON 友好。
Bonus Round
奖金回合
If you want a django model that has a better python command-line display, have your models child class the following:
如果您想要一个具有更好 python 命令行显示的 django 模型,请让您的模型子类如下:
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
So, for example, if we define our models as such:
因此,例如,如果我们这样定义我们的模型:
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")
Calling SomeModel.objects.first()now gives output like this:
SomeModel.objects.first()现在调用会给出如下输出:
{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}
回答by A.Raouf
just vars(obj), it will state the whole values of the object
只是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,
 }
You can add this also
你也可以添加这个
>>> 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,
   }
回答by Tony
Best solution you have ever see.
您所见过的最佳解决方案。
Convert django.db.models.Model instance and all related ForeignKey, ManyToManyField and @Property function fields into dict.
将 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
https://gist.github.com/shuge/f543dc2094a3183f69488df2bfb51a52

