Wie konvertiert man ein Django Model-Objekt in ein Diktat mit allen Feldern? Alle enthalten idealerweise Fremdschlüssel und Felder mit editable = False.
Lassen Sie mich näher darauf eingehen. Angenommen, ich habe ein Django Modell wie das folgende:
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")
Im Terminal habe ich folgendes gemacht:
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()
Ich möchte dies in das folgende Wörterbuch konvertieren:
{'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}
Fragen mit unbefriedigenden Antworten:
Django: Konvertieren eines ganzen Satzes von Objekten eines Modells in ein einzelnes Wörterbuch
Es gibt viele Möglichkeiten, eine Instanz in ein Wörterbuch zu konvertieren, mit unterschiedlichem Grad an Behandlung von Groß- und Kleinschreibung und Nähe zum gewünschten Ergebnis.
instance.__dict__
instance.__dict__
was zurückkehrt
{'_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}
Dies ist bei weitem das einfachste, aber es fehlt many_to_many
, foreign_key
Ist falsch benannt und es enthält zwei unerwünschte zusätzliche Dinge.
model_to_dict
from Django.forms.models import model_to_dict
model_to_dict(instance)
was zurückkehrt
{'foreign_key': 2,
'id': 1,
'many_to_many': [<OtherModel: OtherModel object>],
'normal_value': 1}
Dies ist die einzige mit many_to_many
, Aber es fehlen die nicht bearbeitbaren Felder.
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])
was zurückkehrt
{'foreign_key': 2, 'id': 1, 'normal_value': 1}
Dies ist strikt schlechter als der Standardaufruf model_to_dict
.
query_set.values()
SomeModel.objects.filter(id=instance.id).values()[0]
was zurückkehrt
{'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}
Dies ist die gleiche Ausgabe wie instance.__dict__
, Jedoch ohne die zusätzlichen Felder. foreign_key_id
Ist immer noch falsch und many_to_many
Fehlt immer noch.
Der Code für Djangos model_to_dict
Hatte die meiste Antwort. Nicht bearbeitbare Felder wurden explizit entfernt. Wenn Sie also diese Prüfung entfernen und die IDs von Fremdschlüsseln für viele bis viele Felder abrufen, wird der folgende Code angezeigt, der sich wie gewünscht verhält:
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
Während dies die komplizierteste Option ist, liefert der Aufruf von to_dict(instance)
genau das gewünschte Ergebnis:
{'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 Mit ModelSerialzer können Sie einen Serializer automatisch aus einem Modell erstellen.
from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = "__all__"
SomeModelSerializer(instance).data
kehrt zurück
{'auto_now_add': '2018-12-20T21:34:29.494827Z',
'foreign_key': 2,
'id': 1,
'many_to_many': [2],
'normal_value': 1,
'readonly_value': 2}
Dies ist fast so gut wie die benutzerdefinierte Funktion, aber auto_now_add ist eine Zeichenfolge anstelle eines datetime-Objekts.
Wenn Sie ein Django Modell mit einer besseren python Befehlszeilenanzeige möchten, lassen Sie Ihre Modelle Folgendes untergeordnet klassifizieren:
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
Wenn wir zum Beispiel unsere Modelle als solche definieren:
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")
Der Aufruf von SomeModel.objects.first()
gibt nun eine Ausgabe wie folgt aus:
{'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}
Ich habe eine saubere Lösung gefunden, um zum Ergebnis zu kommen:
Angenommen, Sie haben ein Modellobjekt o
:
Ruf einfach an:
type(o).objects.filter(pk=o.pk).values().first()
@Zags Lösung war wunderschön!
Ich würde jedoch eine Bedingung für Datumsfelder hinzufügen, um es JSON-freundlich zu machen.
Wenn Sie ein Django Modell mit einer besseren python Befehlszeilenanzeige möchten, lassen Sie Ihre Modelle die folgenden untergeordneten Klassen:
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
Wenn wir zum Beispiel unsere Modelle als solche definieren:
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")
Der Aufruf von SomeModel.objects.first()
gibt nun eine Ausgabe wie folgt aus:
{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}
Einfachster Weg,
Wenn Ihre Abfrage Model.Objects.get (): lautet
get () gibt eine einzelne Instanz zurück, sodass Sie direkt __dict__
von Ihrer Instanz aus verwenden können
model_dict = Model.Objects.get().__dict__
für filter ()/all ():
all ()/filter () gibt eine Liste von Instanzen zurück, sodass Sie mit values()
eine Liste von Objekten abrufen können.
model_values = Model.Objects.all (). values ()
nur vars(obj)
, es gibt die gesamten Werte des Objekts an
{'_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,
}
Wenn Sie bereit sind, Ihre eigene to_dict-Methode zu definieren, wie von @karthiker vorgeschlagen, führt dies nur zu einem Mengenproblem.
>>># 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}
Wir müssen die falsch beschrifteten Fremdschlüssel aus otherDict entfernen.
Dazu können Sie eine Schleife verwenden, die ein neues Wörterbuch erstellt, in dem alle Elemente außer denen mit Unterstrichen enthalten sind. Oder um Zeit zu sparen, können wir diese einfach zum Original hinzufügen dikt, da Wörterbücher nur unter der Haube abgelegt werden.
>>>for item in otherDict.items():
... if "_" not in item[0]:
... dict.update({item[0]:item[1]})
...
>>>
Somit bleibt uns folgendes dikt:
>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=<UTC>),
u'id': 1L,
'reference1': 1L,
'reference2': [1L],
'value': 1,
'value2': 2L}
Und das gibst du einfach zurück.
Andererseits können Sie in Ihren editable = false-Feldnamen keine Unterstriche verwenden. Positiv ist zu vermerken, dass dies für alle Felder funktioniert, bei denen die benutzerdefinierten Felder keine Unterstriche enthalten.
--BEARBEITEN--
Dies ist nicht der beste Weg, dies zu tun, aber es könnte als vorübergehende Lösung funktionieren, bis eine direktere Methode gefunden wird.
Für das folgende Beispiel würde dict basierend auf model_to_dict und otherDict mit der Methode values des Filters gebildet. Ich hätte das mit den Modellen selbst gemacht, aber ich kann meine Maschine nicht dazu bringen, otherModel zu akzeptieren.
>>> 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]}
>>>
Ich hoffe, das sollte Sie in eine schwierige Lage versetzen, die Antwort auf Ihre Frage zu finden.
(wollte den Kommentar nicht machen)
Ok, es kommt nicht wirklich auf die Typen an. Möglicherweise habe ich die ursprüngliche Frage hier falsch verstanden. Verzeihen Sie mir, wenn dies der Fall ist. Wenn Sie serliazers.py erstellen, erstellen Sie dort Klassen mit Metaklassen.
Class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = modelName
fields =('csv','of','fields')
Wenn Sie dann die Daten in der Ansichtsklasse erhalten, können Sie:
model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)
Das ist so ziemlich das, was ich in einer Vielzahl von Orten habe und es gibt Nice JSON über den JSONRenderer zurück.
Wie ich bereits sagte, wurde dies mit freundlicher Genehmigung des DjangoRestFrameworks durchgeführt.
Beste Lösung, die Sie jemals gesehen haben.
Konvertieren Sie die Instanz Django.db.models.Model und alle zugehörigen Funktionsfelder ForeignKey, ManyToManyField und @Property in 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
Vielleicht hilft dir das. Möge dies nicht viele in viele Beziehungen konvertieren, aber es ist ziemlich praktisch, wenn Sie Ihr Modell im JSON-Format senden möchten.
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
Viele interessante Lösungen hier. Meine Lösung bestand darin, meinem Modell eine as_dict-Methode mit einem Diktierverständnis hinzuzufügen.
def as_dict(self):
return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)
Als Bonus ist diese Lösung in Verbindung mit einem Listenverständnis über eine Abfrage eine gute Lösung, wenn Sie Ihre Modelle in eine andere Bibliothek exportieren möchten. Beispiel: Speichern Sie Ihre Modelle in einem pandas dataframe:
pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])
Die Antwort von @zags ist umfassend und sollte ausreichen aber die # 5 Methode (welche die beste IMO ist) wirft einen Fehler aus, so dass ich die Hilfsfunktion verbessert habe.
Da das OP die Konvertierung von many_to_many
Felder in eine Liste von Primärschlüsseln anstatt in eine Liste von Objekten. Ich habe die Funktion so erweitert, dass der Rückgabewert jetzt JSON-serialisierbar ist - durch Konvertieren von datetime
Objekten in str
und many_to_many
widerspricht einer Liste von IDs.
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