466 Stimmen

Konvertiere ein Django-Modellobjekt in ein Dictionary mit allen Feldern intakt

Wie konvertiert man ein Django-Model-Objekt in ein Dict mit allen seinen Feldern? Alle sollten idealerweise auch Fremdschlüssel und Felder mit editable=False enthalten.

Lassen Sie mich das genauer erklären. Angenommen, ich habe ein Django-Modell wie folgt:

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 Dictionary konvertieren:

{'auto_now_add': datetime.datetime(2015, 3, 16, 21, 34, 14, 926738, tzinfo=),
 'foreign_key': 1,
 'id': 1,
 'many_to_many': [1],
 'normal_value': 1,
 'readonly_value': 2}

Fragen mit unbefriedigenden Antworten:

Django: Konvertierung eines gesamten Sets von Objekten eines Modells in ein einziges Dictionary

Wie kann ich Django-Model-Objekte in ein Dictionary umwandeln und immer noch ihre Fremdschlüssel behalten?

970voto

Zags Punkte 30855

Es gibt viele Möglichkeiten, eine Instanz in ein Wörterbuch zu konvertieren, mit unterschiedlichen Grad an Randfallbehandlung und Nähe zum gewünschten Ergebnis.


1. instance.__dict__

instance.__dict__

was zurückgibt

{'_foreign_key_cache': ,
 '_state': ,
 'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=),
 '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 hat zwei unerwünschte zusätzliche Elemente darin.


2. model_to_dict

from django.forms.models import model_to_dict
model_to_dict(instance)

was zurückgibt

{'foreign_key': 2,
 'id': 1,
 'many_to_many': [],
 'normal_value': 1}

Dies ist das einzige mit many_to_many, aber es fehlen die nicht-editierbaren Felder.


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])

was zurückgibt

{'foreign_key': 2, 'id': 1, 'normal_value': 1}

Dies ist streng genommen schlechter als der Standardaufruf von model_to_dict.


4. query_set.values()

SomeModel.objects.filter(id=instance.id).values()[0]

was zurückgibt

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=),
 'foreign_key_id': 2,
 'id': 1,
 'normal_value': 1,
 'readonly_value': 2}

Dies ist die gleiche Ausgabe wie instance.__dict__, aber ohne die zusätzlichen Felder. foreign_key_id ist immer noch falsch und many_to_many fehlt immer noch.


5. Benutzerdefinierte Funktion

Der Code für model_to_dict von Django hatte die meisten Antworten. Er hat explizit nicht-editierbare Felder entfernt, also entfernen dieses Checks und das Abfragen der IDs von Fremdschlüsseln für viele-zu-viele-Felder führt zum folgenden Code, der wie gewünscht funktioniert:

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

Obwohl 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=),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

6. Verwenden von Serialisierern

Django Rest Framework's ModelSerializer ermöglicht es Ihnen, automatisch einen Serializer aus einem Modell zu erstellen.

from rest_framework import serializers
class SomeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = SomeModel
        fields = "__all__"

SomeModelSerializer(instance).data

liefert

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


Bonusrunde: bessere Modellanzeige

Wenn Sie ein Django-Modell wünschen, das eine bessere Python-Befehlszeilenanzeige hat, lassen Sie Ihre Modelle von dem folgenden Kind-Klasse:

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

Zum Beispiel, wenn wir unsere Modelle wie folgt 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 jetzt eine Ausgabe wie diese:

{'auto_now_add': datetime.datetime(2018, 12, 20, 21, 34, 29, 494827, tzinfo=),
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

14voto

Alfred Huang Punkte 16379

Ich habe eine elegante Lösung gefunden, um zum Ergebnis zu gelangen:

Angenommen, Sie haben ein Modellobjekt o:

Rufen Sie einfach auf:

type(o).objects.filter(pk=o.pk).values().first()

12voto

Einfachster Weg,

  1. Wenn Ihre Abfrage Model.Objects.get() lautet:

    get() gibt eine einzelne Instanz zurück, sodass Sie direkt __dict__ von Ihrer Instanz verwenden können.

    model_dict = Model.Objects.get().\_\_dict\_\_

  2. für filter()/all():

    all()/filter() gibt eine Liste von Instanzen zurück, sodass Sie values() verwenden können, um eine Liste von Objekten zu erhalten.

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

10voto

A.Raouf Punkte 1901

Nur vars(obj), es zeigt alle Werte des Objekts an

>>> obj_attrs = vars(obj)
>>> obj_attrs
 {'_file_data_cache': ,
  '_state': ,
  'aggregator_id': 24,
  'amount': 5.0,
  'biller_id': 23,
  'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=),
  'file_data_id': 797719,
 }

Du kannst dies auch hinzufügen

>>> keys = obj_attrs.keys()
>>> temp = [obj_attrs.pop(key) if key.startswith('_') else None for key in keys]
>>> del temp
>>> obj_attrs
   {
    'aggregator_id': 24,
    'amount': 5.0,
    'biller_id': 23,
    'datetime': datetime.datetime(2018, 1, 31, 18, 43, 58, 933277, tzinfo=),
    'file_data_id': 797719,
   }

9voto

Gadget Punkte 434

Aktualisierung

Die neuere zusammengefasste Antwort von @zags ist vollständiger und eleganter als meine eigene. Bitte beachten Sie stattdessen diese Antwort.

Original

Wenn Sie bereit sind, Ihre eigene to_dict-Methode zu definieren, wie von @karthiker vorgeschlagen, reduziert sich dieses Problem einfach auf ein Set-Problem.

>>># Gibt ein Set aller Schlüssel zurück, die nicht mit editable = False markiert sind
>>>dict = model_to_dict(Instanz)
>>>dict

{u'id': 1L, 'reference1': 1L, 'reference2': [1L], 'value': 1}

>>># Gibt ein Set von mit editable = False markierten Schlüsseln, falsch benannten Fremdschlüsseln und normalen Schlüsseln zurück
>>>otherDict = SomeModel.objects.filter(id=Instanz.id).values()[0]
>>>otherDict

{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=),
 u'id': 1L,
 'reference1_id': 1L,
 'value': 1L,
 'value2': 2L}

Wir müssen die falsch benannten Fremdschlüssel aus otherDict entfernen.

Dafür können wir eine Schleife verwenden, die ein neues Dictionary erstellt, das jedes Element außer denen mit Unterstrichen enthält. Oder, um Zeit zu sparen, können wir diese einfach dem ursprünglichen dict hinzufügen, da Dictionarys im Kern nur Sets sind.

>>>for item in otherDict.items():
...    if "_" not in item[0]:
...            dict.update({item[0]:item[1]})
...
>>>

Somit bleiben wir mit dem folgenden dict:

>>>dict
{'created': datetime.datetime(2014, 2, 21, 4, 38, 51, tzinfo=),
 u'id': 1L,
 'reference1': 1L,
 'reference2': [1L],
 'value': 1,
 'value2': 2L}

Und das geben Sie einfach zurück.

Der Nachteil ist, dass Sie Unterstriche in Ihren editierbaren Feldnamen nicht verwenden können. Der Vorteil ist, dass dies für jede Feldgruppe funktioniert, bei der die vom Benutzer erstellten Felder keine Unterstriche enthalten.

Dies ist nicht der beste Weg, dies zu tun, könnte aber als vorübergehende Lösung funktionieren, bis eine direktere Methode gefunden wird.

Für das unten stehende Beispiel würde das Dictionary basierend auf model_to_dict gebildet und otherDict durch die values-Methode des Filters gebildet werden. Ich hätte dies gerne mit den Modellen selbst gemacht, aber ich bekomme meine Maschine nicht dazu, 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]}
>>>

Das sollte Sie ungefähr auf die Antwort auf Ihre Frage bringen, hoffe ich.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X