Programing

Django Model 객체를 변환하여 모든 필드를 그대로 유지하십시오.

lottogame 2020. 5. 7. 07:54
반응형

Django Model 객체를 변환하여 모든 필드를 그대로 유지하십시오.


django Model 객체를 모든 필드 가있는 dict로 어떻게 변환 합니까? 이상적으로는 editable = False 인 외래 키와 필드를 포함합니다.

자세히 설명하겠습니다. 다음과 같은 장고 모델이 있다고 가정 해 봅시다.

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 Model 객체를 사전으로 바꾸고 외래 키를 어떻게 가질 수 있습니까?


다양한 수준의 코너 케이스 처리 및 원하는 결과와의 근접성을 통해 인스턴스를 사전으로 변환하는 방법에는 여러 가지가 있습니다.


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_many되어 foreign_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이지만 편집 할 수없는 필드가 없습니다.


삼. 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 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는 datetime 객체 대신 문자열입니다.


보너스 라운드 : 더 나은 모델 인쇄

더 나은 파이썬 커맨드 라인 디스플레이를 가진 장고 모델을 원한다면, 모델이 다음과 같은 자식 클래스를 갖도록하십시오 :

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()

@ Zags 솔루션은 화려했습니다!

그러나 JSON을 친화적으로 만들기 위해 날짜 필드에 대한 조건을 추가하려고합니다.

보너스 라운드

더 나은 파이썬 명령 줄 표시를 가진 장고 모델을 원한다면 모델에 다음과 같은 자식 클래스를 작성하십시오.

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

가장 간단한 방법,

  1. 쿼리가 Model.Objects.get () 인 경우 :

    get ()은 단일 인스턴스를 반환하므로 __dict__인스턴스에서 직접 사용할 수 있습니다

    model_dict = Model.Objects.get().__dict__

  2. 에 대한 필터 () / 모든 () :

    all () / filter () 는 인스턴스 목록을 반환하므로 values()객체 목록을 얻는 데 사용할 수 있습니다 .

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


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

@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는 필터의 값 방법으로 구성됩니다. 모델 자체 에서이 작업을 수행했지만 기계가 다른 모델을 수락하도록 할 수는 없습니다.

>>> 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의 예의이므로 그렇게 볼 가치가 있습니다.


아마도 이것은 당신을 도울 것입니다. 이것은 많은 대대적 인 포기를 가리지 않지만 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

흥미로운 솔루션이 많이 있습니다. 내 솔루션은 dict comprehension으로 모델에 as_dict 메소드를 추가하는 것이 었습니다.

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

또한이 솔루션을 쿼리에 대한 목록 이해와 함께 사용하면 모델을 다른 라이브러리로 내보내려는 경우 훌륭한 솔루션이됩니다. 예를 들어, 팬더 데이터 프레임에 모델을 덤프하는 경우 :

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

지금까지 본 최고의 솔루션.

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)이 오류를 발생시켜 도우미 기능을 향상 시켰습니다.

영업 이익은 변환 요청으로 many_to_many기본 키가 아닌 객체의 목록의 목록에 필드를 반환 값이 이제 JSON의 직렬화, 그래서 나는 기능을 강화 - 변환하여 datetime, 물체 strmany_to_manyID의 목록에 개체를.

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

참고 URL : https://stackoverflow.com/questions/21925671/convert-django-model-object-to-dict-with-all-of-the-fields-intact

반응형