Преобразовать объект модели Django, чтобы диктовать все поля без изменений

156

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

  • 1
    Вы можете объявить метод to_dict и обработать его так, как вы хотите.
  • 0
    @kartikr да, я мог. Вопрос в том, как создать такой метод. Создание словаря вручную из всех полей модели не является подходящим ответом.
Показать ещё 1 комментарий
Теги:
django-models
dictionary

9 ответов

287
Лучший ответ

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


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 с 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}
  • 0
    Спасибо за этот ответ! Вы можете изменить тест isinstance в решении №5 (и бонус) на if f.many_to_many .
  • 0
    @dhobbs Я смоделировал этот код из кода Django model_to_dict , который использует isinstance . Я не уверен, почему они сделали этот выбор, но для этого может быть веская причина (например, свойство many_to_many , представленное в более поздней версии)
Показать ещё 2 комментария
7

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

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

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

type(o).objects.filter(pk=o.pk).values().first()
  • 3
    Это просто вариант № 4 в моем ответе
4
Решение

@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]}
  • 0
    Если вы хотите конвертировать в и из JSON, вы должны заглянуть в Django Rest Framework или использовать что-то вроде этого: stackoverflow.com/a/22238613/2800876
  • 0
    Конечно! Но это небольшое изменение в вашем коде добавляет много удобства!
3

Простейший способ,

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

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

    model_dict = Model.Objects.get().__dict__

  • для filter()/all():

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

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

  • 0
    Спасибо за публикацию, но, похоже, это уже информация, содержащаяся в принятом ответе.
3

Если вы хотите определить свой собственный метод 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}

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

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

- EDIT -

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

В приведенном ниже примере dict будет сформирован на основе model_to_dict, а другойDict будет сформирован методом значений фильтров. Я бы сделал это с самими моделями, но я не могу заставить свою машину принимать 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 и допустимые столбцы в базе данных могут иметь подчеркивания в своих именах.
  • 0
    re.match ("_", "reference1_id") действительно возвращает None, это должно было быть: re.match (". * _. *", "reference1_id")
Показать ещё 3 комментария
1

просто vars(obj), он будет 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,
 }
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)

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

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

0

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

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

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

pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])
  • 1
    Это прекрасно работает для полей значений, таких как строки и целые, но будет иметь некоторые проблемы с внешними ключами и даже больше со многими многими полями
  • 0
    Очень хороший момент! Особенно для многих для многих. Можно было бы добавить некоторые условные выражения для надлежащей обработки этих случаев или ограничить это решение простыми моделями. Благодарю.
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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню