将Django Model对象转换为dict,所有字段都保持不变

Zag*_*ags 215 python django dictionary django-models

如何将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")
Run Code Online (Sandbox Code Playgroud)

在终端中,我做了以下事情:

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()
Run Code Online (Sandbox Code Playgroud)

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

{'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}
Run Code Online (Sandbox Code Playgroud)

回答不满意的问题:

Django:将一整套Model的对象转换为单个字典

如何将Django Model对象转换为字典并仍然拥有其外键?

Zag*_*ags 426

有许多方法可以将实例转换为字典,具有不同程度的角落案例处理和与所需结果的接近程度.


1. instance.__dict__

instance.__dict__
Run Code Online (Sandbox Code Playgroud)

返回

{'_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}
Run Code Online (Sandbox Code Playgroud)

到目前为止这是最简单的,但是缺失many_to_many,foreign_key被错误命名,并且它有两个不需要的额外的东西.


2. model_to_dict

from django.forms.models import model_to_dict
model_to_dict(instance)
Run Code Online (Sandbox Code Playgroud)

返回

{'foreign_key': 2,
 'id': 1,
 'many_to_many': [<OtherModel: OtherModel object>],
 'normal_value': 1}
Run Code Online (Sandbox Code Playgroud)

这是唯一一个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])
Run Code Online (Sandbox Code Playgroud)

返回

{'foreign_key': 2, 'id': 1, 'normal_value': 1}
Run Code Online (Sandbox Code Playgroud)

这严格比标准model_to_dict调用更糟糕.


4. query_set.values()

SomeModel.objects.filter(id=instance.id).values()[0]
Run Code Online (Sandbox Code Playgroud)

返回

{'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}
Run Code Online (Sandbox Code Playgroud)

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


5.自定义功能

django的代码model_to_dict有大部分答案.它明确删除了不可编辑的字段,因此删除该检查会产生以下代码,其行为符合要求:

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
Run Code Online (Sandbox Code Playgroud)

虽然这是最复杂的选项,但是调用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}
Run Code Online (Sandbox Code Playgroud)

6.使用序列化程序

Django Rest Framework的ModelSerialzer允许您从模型中自动构建序列化程序.

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

SomeModelSerializer(instance).data
Run Code Online (Sandbox Code Playgroud)

回报

{'auto_now_add': '2018-12-20T21:34:29.494827Z',
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}
Run Code Online (Sandbox Code Playgroud)

这几乎和自定义函数一样好,但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
Run Code Online (Sandbox Code Playgroud)

因此,例如,如果我们将模型定义为:

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")
Run Code Online (Sandbox Code Playgroud)

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}
Run Code Online (Sandbox Code Playgroud)

  • 感谢您的回答!您可以将解决方案#5中的“ isinstance”测试(和奖励)更改为“ if f.many_to_many”。 (2认同)
  • 我所做的就是检查 get_FOO_display 并返回该值,而不是实际存在的任何值。 (2认同)

Alf*_*ang 10

我找到了一个简洁的解决方案来获得结果:

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

只需致电:

type(o).objects.filter(pk=o.pk).values().first()
Run Code Online (Sandbox Code Playgroud)

  • 这只是我答案中的选项#4 (7认同)

A.R*_*ouf 8

只是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,
 }
Run Code Online (Sandbox Code Playgroud)

你也可以添加这个

>>> 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,
 }
Run Code Online (Sandbox Code Playgroud)


Gad*_*get 7

更新

@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}
Run Code Online (Sandbox Code Playgroud)

我们需要从otherDict 中删除错误标记的外键。

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

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

因此,我们留下了以下dict

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

你只需返回那个。

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

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

对于下面的示例,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]}
>>>
Run Code Online (Sandbox Code Playgroud)

我希望,这应该让您大致了解问题的答案。


小智 6

@Zags解决方案很华丽!

不过,我想补充一下datefields的条件,以使JSON友好.

奖金回合

如果你想要一个具有更好的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
Run Code Online (Sandbox Code Playgroud)

因此,例如,如果我们将模型定义为:

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")
Run Code Online (Sandbox Code Playgroud)

SomeModel.objects.first()现在调用给出如下输出:

{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}
Run Code Online (Sandbox Code Playgroud)


Moh*_*med 5

最简单的方法,

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

    get()将返回单个实例,以便您可以直接__dict__从您的实例使用。

    model_dict = Model.Objects.get().__dict__

  2. 对于filter()/all()

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

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

  • `model_dict = Model.Objects.get().__dict__` 给出一个带有名为 `_state` 的键的项目以及其余项目 (2认同)

use*_*531 5

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

import pprint
item = MyDjangoModel.objects.get(name = 'foo')
pprint.pprint(item.__dict__, indent = 4)
Run Code Online (Sandbox Code Playgroud)

这提供的输出看起来类似于json.dumps(..., indent = 4),但它正确处理可能嵌入模型实例中的奇怪数据类型,例如ModelStateUUID等。

在 Python 3.7 上测试


小智 5

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

查询集

查询集就像 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)
Run Code Online (Sandbox Code Playgroud)

模型实例

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

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)
Run Code Online (Sandbox Code Playgroud)

分页器对象

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

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)
Run Code Online (Sandbox Code Playgroud)

至少我就是这样解决的。