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

Convert Django Model object to dict with all of the fields intact

如何将django模型对象转换为包含所有字段的dict?理想情况下,所有字段都包含外键和editable=false的字段。

让我详细说明一下。假设我有一个像下面这样的django模型:

1
2
3
4
5
6
7
8
9
10
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")

在终端中,我完成了以下操作:

1
2
3
4
5
6
7
8
9
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()

我要将此转换为以下词典:

1
2
3
4
5
6
{'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。instance.__dict__

1
instance.__dict__

哪些回报

1
2
3
4
5
6
7
{'_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

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

哪些回报

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

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

三。model_to_dict(..., fields=...)

1
2
from django.forms.models import model_to_dict
model_to_dict(instance, fields=[field.name for field in instance._meta.fields])

哪些回报

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

这比标准的model_to_dict调用更糟糕。

4。query_set.values()

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

哪些回报

1
2
3
4
5
{'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的代码得到了大部分答案。它显式地删除了不可编辑的字段,因此删除该检查将导致以下代码的行为符合要求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

虽然这是最复杂的选择,但调用to_dict(instance)会给我们带来理想的结果:

1
2
3
4
5
6
{'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。使用串行化器

DjangoRest框架的ModelSerialzer允许您从模型自动构建序列化程序。

1
2
3
4
5
6
7
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields ="__all__"

SomeModelSerializer(instance).data

收益率

1
2
3
4
5
6
{'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是一个字符串,而不是日期时间对象。

奖励回合:更好的模型打印

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

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

1
2
3
4
5
6
7
8
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()现在产生如下输出:

1
2
3
4
5
6
{'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

只要打电话:

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


@扎格斯的解决方案太棒了!

不过,我会为datefields添加一个条件,以使它更适合JSON。

奖金轮

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

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

1
2
3
4
5
6
7
8
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()现在产生如下输出:

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


最简单的方法,

  • 如果查询是model.objects.get():

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

    型号:dict=Model.Objects.get().__dict__

  • 对于filter()/all():

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

    model_values=model.objects.all().values())


  • 如果您愿意像@karthiker建议的那样定义自己的to-dict方法,那么这将把问题归结为集合问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    >>># 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中,因为字典只是设置在风帽下。

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

    因此,留给我们的是以下口述:

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

    你只要把它还给我。

    不利的是,在可编辑=假字段名中不能使用下划线。另一方面,这将适用于任何一组字段,其中用户创建的字段不包含下划线。

    --编辑——

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

    对于下面的示例,dict将基于model_to_dict形成,otherdict将通过filter的values方法形成。我本来可以用模型自己来做的,但我不能让我的机器接受其他模型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >>> 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]}
    >>>

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


    仅限于vars(obj),它将说明对象的整个值。

    1
    2
    3
    4
    5
    6
    7
    8
    {'_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,
     }


    最好的解决方案。

    将django.db.models.model实例和所有相关的foreignkey、manytomanyfield和@property函数字段转换为dict。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    """
    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/f543dc2094a183f69488df2bfb51a52


    (不是有意发表评论)

    好吧,这并不取决于类型。我可能误解了最初的问题,如果是这样的话,请原谅我。如果创建serliazers.py,那么在其中创建具有元类的类。

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

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

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

    这几乎就是我在不同地方所拥有的,它通过JSONrenderer返回了很好的JSON。

    正如我所说,这是由Djangorestframework提供的,所以值得研究一下。


    这里有很多有趣的解决方案。我的解决方案是在我的模型中添加一个"as-dict"方法,并进行听写理解。

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

    另外,如果您希望将模型导出到另一个库中,此解决方案与对查询的列表理解相结合可以提供一个很好的解决方案。例如,将您的模型转储到熊猫数据帧中:

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


    也许这对你有帮助。也许这并不能隐藏多对多的关系,但是当您希望以JSON格式发送模型时,这非常方便。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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