Django Modelオブジェクトをすべてのフィールドをそのままにしてdictに変換します


258

どのようにして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:モデルのオブジェクトのセット全体を単一の辞書に変換する

Djangoモデルオブジェクトを辞書に変換しても、外部キーを保持するにはどうすればよいですか?


1
呼び出されるメソッドを宣言し、to_dictそれを希望どおりに処理できます。
karthikr 2014

@karthikrはい、できます。問題は、そのようなメソッドを作成する方法です。モデルのすべてのフィールドから手動で辞書を作成することは適切な答えではありません。
Zags 2014

Django Rest Framework、Tastypie、Pistonなどの既存のReSTライブラリは、djangoモデルのインスタンスをプリミティブに変換してシリアル化するメカニズムを備えているため、活用します。方法に興味がある場合は、コードを確認できますが、ほとんどの場合、モデルの_meta定義を調べて、モデルに関連付けられたフィールドを見つけ、インスタンスの値を取得します。
Kevin Stone

回答:


526

インスタンスをディクショナリに変換するには多くの方法があり、コーナーケースの処理の度合いや目的の結果への近さはさまざまです。


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つあります。


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.カスタム関数

ジャンゴのためのコード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は日時オブジェクトではなく文字列です。


ボーナスラウンド:より良いモデル印刷

より優れた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 16

1
@dhobbs私はそのコードをDjangoのmodel_to_dictコードからモデル化しましたisinstance。なぜ彼らがこの選択をしたのかはわかりませんが、それについては正当な理由があるかもしれません(たとえば、このmany_to_manyプロパティは
新しい

@propertyフィールドも返しますか?
angrysumit

1
これらのメソッドは注釈付き/集約フィールドをどのように処理するのでしょうか?
メフメット

私がしていることの1つは、get_FOO_displayをチェックして、実際に存在する値ではなく、その値を返すことです。
Bobort

9

私は結果を得るためのきちんとした解決策を見つけました:

モデルオブジェクトがあるとしますo

ただ電話する:

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

10
これは私の回答のオプション#4にすぎません
Zags

7

@Zagsソリューションは豪華でした!

ただし、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

したがって、たとえば、モデルを次のように定義すると、

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の残りのフレームワークや、このような使用のものになりますstackoverflow.com/a/22238613/2800876
ZAGS

承知しました!しかし、コードへのこの小さな変更により、非常に便利になります。
Diego Freitas Coelho

4

最も簡単な方法は、

  1. クエリがModel.Objects.get()の場合:

    get()は単一のインスタンスを返すため__dict__、インスタンスから直接使用できます

    model_dict = Model.Objects.get().__dict__

  2. フィルター()/すべての():

    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が残ります。

>>>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はフィルターの値メソッドによって形成されます。私はモデル自体でこれを行ったでしょうが、私のマシンに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
ガジェット2014年

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
これは、文字列やintなどの値フィールドでは問題なく機能しますが、外部キーや
Zags

とても良い点!特に多対多。これらのケースを適切に処理するためにいくつかの条件を設定するか、このソリューションを単純なモデルに制限する必要があります。ありがとう。
t1m0 2016年

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)られますが、モデルインスタンスに埋め込まれている可能性のある奇妙なデータタイプ(ModelStateおよびなど)が正しく処理されますUUID

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フィールドをオブジェクトのリストではなく主キーのリストに変換するよう要求したので、関数を拡張datetimeしてstrmany_to_manyオブジェクトをIDのリストに変換し、オブジェクトをIDのリストに変換することで、戻り値がJSONシリアル化できるようにしました。

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ループはここで間違って実装されています。
Daniel Himmelstein

@ザグスエラーは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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.