it-roy-ru.com

Преобразовать Django Объект модели для указания всех полей без изменений

Как можно преобразовать объект 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 объекты модели в словарь и по-прежнему иметь их внешние ключи?

184
Zags

Есть много способов преобразовать экземпляр в словарь с различной степенью обработки угловых случаев и приближением к желаемому результату.


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, но в нем отсутствуют недоступные для редактирования поля.


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. Пользовательская функция

Код для Django model_to_dict имел большую часть ответа. Он явно удалил не редактируемые поля, поэтому удаление этой проверки приводит к следующему коду, который ведет себя как нужно:

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) дает нам именно тот результат, который вам нужен:

{'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.


Бонус раунд: лучшая модель печати

Если вам нужна модель Django с улучшенным отображением командной строки python, сделайте так, чтобы дочерние классы вашей модели были следующими:

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

Так, например, если мы определим наши модели как таковые:

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}
353
Zags

Я нашел аккуратное решение, чтобы получить результат:

Предположим, у вас есть модельный объект o:

Просто позвони:

type(o).objects.filter(pk=o.pk).values().first()
9
Alfred Huang

Решение @Zags было великолепным!

Я бы добавил, однако, условие для полей даты, чтобы сделать его дружественным к JSON.

Бонус Раунд

Если вам нужна модель Django с лучшим отображением командной строки python, сделайте так, чтобы дочерний класс ваших моделей был следующим:

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]}
5
Diego Freitas Coelho

Самый простой способ,

  1. Если ваш запрос Model.Objects.get ():

    get () вернет один экземпляр, так что вы можете напрямую использовать __dict__ из вашего экземпляра

    model_dict = Model.Objects.get().__dict__

  2. для фильтр ()/все ():

    all ()/filter () вернет список экземпляров, поэтому вы можете использовать values() для получения списка объектов.

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

3
Mohideen bin Mohammed

просто vars(obj), он будет указывать все значения объекта

{'_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,
 }
2
A.Raouf

Если вы хотите определить свой собственный метод to_dict, как предложил @karthiker, то это сводит эту проблему к задаче множеств.

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

И вы просто верните это.

С другой стороны, вы не можете использовать подчеркивания в своих редактируемых = ложных именах полей. С другой стороны, это будет работать для любого набора полей, где пользовательские поля не содержат подчеркивания.

--Правка--

Это не лучший способ сделать это, но он может работать как временное решение, пока не будет найден более прямой метод.

Для приведенного ниже примера 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]}
>>>

Это должно поставить вас в грубую точку ответа на ваш вопрос, я надеюсь.

2
Gadget

(не хотел комментировать)

Хорошо, это не зависит от типов таким образом. Возможно, я неправильно понял первоначальный вопрос, так что простите, если это так. Если вы создадите 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)

Это в значительной степени то, что я имею в разных местах, и он возвращает Nice JSON через JSONRenderer.

Как я уже сказал, это любезно предоставлено DjangoRestFramework, так что стоит посмотреть на это.

1
nathj07

Лучшее решение, которое вы когда-либо видели.

Преобразуйте экземпляр 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

0
Shuge Lee

Может быть, это поможет вам. Пусть это не скрывает отношения многих ко многим, но очень удобно, когда вы хотите отправить свою модель в формате 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
Pjl

Здесь много интересных решений. Мое решение состояло в том, чтобы добавить метод as_dict к моей модели с пониманием dict.

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

В качестве бонуса, это решение в сочетании с пониманием списка по запросу делает решение Nice, если вы хотите экспортировать свои модели в другую библиотеку. Например, выгрузка ваших моделей в кадр данных pandas:

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