Comment convertir un objet Django Model en un dict avec tous de ses champs? Tous incluent idéalement des clés étrangères et des champs avec editable = False.
Laissez-moi élaborer. Disons que j'ai un modèle Django comme celui-ci:
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")
Dans le terminal, j'ai fait ce qui suit:
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()
Je veux convertir ceci dans le dictionnaire suivant:
{'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}
Questions avec des réponses insatisfaisantes:
Django: Conversion d'un ensemble complet d'objets du modèle en un seul dictionnaire
Il existe de nombreuses façons de convertir une instance en dictionnaire, avec différents degrés de traitement des majuscules et de proximité avec le résultat souhaité.
instance.__dict__
instance.__dict__
qui retourne
{'_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}
Ceci est de loin le plus simple, mais il manque many_to_many
, foreign_key
est mal nommé, et il contient deux éléments supplémentaires indésirables.
model_to_dict
from Django.forms.models import model_to_dict
model_to_dict(instance)
qui retourne
{'foreign_key': 2,
'id': 1,
'many_to_many': [<OtherModel: OtherModel object>],
'normal_value': 1}
C'est le seul avec many_to_many
, mais il manque les champs non modifiables.
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])
qui retourne
{'foreign_key': 2, 'id': 1, 'normal_value': 1}
Ceci est strictement pire que l'invocation standard model_to_dict
.
query_set.values()
SomeModel.objects.filter(id=instance.id).values()[0]
qui retourne
{'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}
C'est la même sortie que instance.__dict__
mais sans les champs supplémentaires. foreign_key_id
est toujours faux et many_to_many
est toujours manquant.
Le code pour model_to_dict
de Django avait la majeure partie de la réponse. Il a explicitement supprimé les champs non modifiables. Par conséquent, si vous supprimez cette vérification et obtenez les ID de clés étrangères pour plusieurs champs, le code suivant se comporte comme souhaité:
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
Bien que ceci soit l'option la plus compliquée, appeler to_dict(instance)
nous donne exactement le résultat souhaité:
{'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 vous permet de créer un sérialiseur automatiquement à partir d'un modèle.
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = "__all__"
SomeModelSerializer(instance).data
résultats
{'auto_now_add': '2018-12-20T21:34:29.494827Z',
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
C'est presque aussi bon que la fonction personnalisée, mais auto_now_add est une chaîne au lieu d'un objet datetime.
Si vous voulez un modèle Django ayant un meilleur affichage de ligne de commande python, faites en sorte que vos modèles soient de la classe enfant suivante:
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
Ainsi, par exemple, si nous définissons nos modèles en tant que tels:
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")
L'appel de SomeModel.objects.first()
donne maintenant une sortie comme celle-ci:
{'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}
J'ai trouvé une solution soignée pour arriver à un résultat:
Supposons que vous ayez un objet modèle o
:
Il suffit d'appeler:
type(o).objects.filter(pk=o.pk).values().first()
La solution @Zags était magnifique!
J'ajouterais cependant une condition pour datefields afin de rendre JSON convivial.
Si vous voulez un modèle Django ayant un meilleur affichage de ligne de commande python, faites en sorte que la classe enfant de votre modèle soit comme suit:
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
Ainsi, par exemple, si nous définissons nos modèles en tant que tels:
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")
L'appel de SomeModel.objects.first()
donne maintenant une sortie comme ceci:
{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}
Manière la plus simple,
Si votre requête est Model.Objects.get ():
get () retournera une instance unique afin que vous puissiez directement utiliser __dict__
de votre instance
model_dict = Model.Objects.get().__dict__
pour filter ()/all ():
all ()/filter () retournera la liste des instances afin que vous puissiez utiliser values()
pour obtenir la liste des objets.
model_values = Model.Objects.all (). values ()
juste vars(obj)
, il indiquera les valeurs entières de l'objet
{'_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,
}
Si vous êtes prêt à définir votre propre méthode to_dict, comme l'a suggéré @karthiker, le problème se résume à un ensemble.
>>># 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}
Nous devons supprimer les clés étrangères mal étiquetées de otherDict.
Pour ce faire, nous pouvons utiliser une boucle qui crée un nouveau dictionnaire contenant tous les éléments sauf ceux avec des traits de soulignement. Ou, pour gagner du temps, nous pouvons simplement ajouter ceux-ci à l'original dict puisque les dictionnaires sont simplement placés sous le capot.
>>>for item in otherDict.items():
... if "_" not in item[0]:
... dict.update({item[0]:item[1]})
...
>>>
Il nous reste donc ce qui suit dict:
>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
u'id': 1L,
'reference1': 1L,
'reference2': [1L],
'value': 1,
'value2': 2L}
Et vous venez de retourner ça.
En revanche, vous ne pouvez pas utiliser de traits de soulignement dans les noms de champs modifiables = faux. En revanche, cela fonctionnera pour tout ensemble de champs où les champs créés par l'utilisateur ne contiennent pas de trait de soulignement.
--MODIFIER--
Ce n'est pas la meilleure façon de le faire, mais cela pourrait fonctionner comme une solution temporaire jusqu'à ce qu'une méthode plus directe soit trouvée.
Pour l'exemple ci-dessous, dict serait formé en fonction de model_to_dict et otherDict serait formé par la méthode values de filter. J'aurais fait cela avec les modèles eux-mêmes, mais je ne parviens pas à faire accepter ma machine par 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]}
>>>
Cela devrait vous donner une idée approximative de la réponse à votre question, j'espère.
(ne voulait pas faire le commentaire)
Ok, ça ne dépend pas vraiment des types de cette façon. J'ai peut-être mal compris la question initiale, alors pardonnez-moi si tel est le cas. Si vous créez serliazers.py, vous créez des classes qui ont des méta-classes.
Class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = modelName
fields =('csv','of','fields')
Ensuite, lorsque vous obtenez les données dans la classe de vue, vous pouvez:
model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)
C’est à peu près ce que j’ai dans une variété d’endroits et cela renvoie Nice JSON via JSONRenderer.
Comme je l'ai dit, ceci est une gracieuseté de DjangoRestFramework, il est donc intéressant de se pencher sur la question.
La meilleure solution que vous ayez jamais vue.
Convertir l’instance Django.db.models.Model et tous les champs de fonction ForeignKey, ManyToManyField et @Property associés en 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
Peut-être que cela vous aide. Puisse cette relation ne pas dissimuler plusieurs relations à plusieurs, mais elle est très pratique lorsque vous souhaitez envoyer votre modèle au format 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
Beaucoup de solutions intéressantes ici. Ma solution consistait à ajouter une méthode as_dict à mon modèle avec une compréhension dict.
def as_dict(self):
return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)
En prime, cette solution, associée à une compréhension de liste sur une requête, constitue une solution intéressante si vous souhaitez exporter vos modèles dans une autre bibliothèque. Par exemple, vider vos modèles dans un pandas dataframe:
pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])
La réponse de @zags est complète et devrait suffire mais la méthode # 5 (qui est la meilleure IMO) renvoie une erreur, j'ai donc amélioré la fonction d'assistance.
Comme l'OP a demandé de convertir les champs many_to_many
en liste de clés primaires plutôt qu'en liste d'objets, j'ai amélioré la fonction afin que la valeur renvoyée soit désormais JSON sérialisable - en convertissant les objets datetime
en str
et many_to_many
objets dans une liste d'identifiants.
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