Как преобразовать объект модели 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 в словарь и сохранить их внешние ключи?
Есть много способов преобразовать экземпляр в словарь с различной степенью обработки угловых случаев и приближением к желаемому результату.
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
назван неправильно, и в нем есть две нежелательные дополнительные вещи.
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
, но в нем отсутствуют недоступные для редактирования поля.
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
.
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
по-прежнему отсутствует.
Код для 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}
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}
isinstance
в решении №5 (и бонус) на if f.many_to_many
.
model_to_dict
, который использует isinstance
. Я не уверен, почему они сделали этот выбор, но для этого может быть веская причина (например, свойство many_to_many
, представленное в более поздней версии)
Я нашел аккуратное решение, чтобы получить результат:
Предположим, что у вас есть объект модели o
:
Просто позвоните:
type(o).objects.filter(pk=o.pk).values().first()
@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]}
Простейший способ,
Если ваш запрос Model.Objects.get():
get() вернет один экземпляр, чтобы вы могли напрямую использовать __dict__
из вашего экземпляра
model_dict = Model.Objects.get().__dict__
для filter()/all():
all()/filter() вернет список экземпляров, чтобы вы могли использовать values()
для получения списка объектов.
model_values = Model.Objects.all(). values ()
Если вы хотите определить свой собственный метод 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]}
>>>
Это должно поставить вас в приблизительный приблизительный ответ на ваш вопрос, надеюсь.
re
. Если это отфильтровать ключи с подчеркиванием в них, это не правильный код и не правильное поведение. re.match("_", "reference1_id")
возвращает None
и допустимые столбцы в базе данных могут иметь подчеркивания в своих именах.
просто 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,
}
(не означает, чтобы сделать комментарий)
Хорошо, на самом деле это не зависит от типов. Возможно, я неправильно понял первоначальный вопрос, поэтому простите меня, если это так. Если вы создадите 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, поэтому стоит посмотреть на это.
Здесь много интересных решений. Моим решением было добавить метод 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()])
Возможно, это поможет вам. Пусть это не скрывает многих во многих отношениях, но очень удобно, когда вы хотите отправить свою модель в формате 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
to_dict
и обработать его так, как вы хотите.