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?

1voto

djvg Punkte 7864

Zu viele großartige Antworten bereits, aber keine davon erwähnt die eingebauten Serialisierer von Django.

Das ModelSerializer von Django Rest Framework wird mehrmals erwähnt, aber das ist eine zusätzliche Abhängigkeit.

Der eingebaute python.Serializer sieht wie ein geeigneter Kandidat aus. Aus der docstring:

Ein Python "Serializer". Macht nicht wirklich viel Serialisierung per se - konvertiert nur zu und von grundlegenden Python-Datentypen (Listen, Dictionaries, Strings, etc.). Nützlich als Grundlage für andere Serialisierer.

So würde es funktionieren:

from django.core.serializers.python import Serializer

instanz = ...  # erstelle Instanz wie im OP

serialisierte_instanz = Serializer().serialize([instanz])[0]

Die Ausgabe unterscheidet sich leicht von dem angeforderten Dictionary, aber die gewünschten Felder sind alle vorhanden. Zum Beispiel:

{
    'fields': {
        'auto_now_add': datetime.datetime(2023, 3, 21, 12, 12, 27, 270255, tzinfo=datetime.timezone.utc),
        'foreign_key': 1,
        'many_to_many': [1],
        'normal_value': 1,
        'readonly_value': 2,
    },
    'model': 'verschiedenes.einModell',
    'pk': 1,
}

Dies kann umstrukturiert werden, um mit dem Dictionary aus dem OP übereinzustimmen, wenn nötig. Zum Beispiel:

instanz_dict = {
    'id': serialisierte_instanz['pk'],
    **serialisierte_instanz['fields'],
}

Dieser Ansatz funktioniert auch gut, wenn Sie eine Abfrage haben, die bereits in eine Liste von Objekten ausgewertet wurde.

1voto

pedrobern Punkte 1204

Ich mag es, Modelinstanzen für Snapshot-Tests in ein Dictionary umzuwandeln. So mache ich es:

Hinweis: Es gibt die Option camelize, weil es besser ist, alle Snapshots konsistent zu halten, entweder von Modelinstanzen oder von API-Aufrufen, wenn die API-Antwort die Objekte karmeliert zurückgibt.

from rest_framework import serializers
from djangorestframework_camel_case.util import camelize as _camelize

def model_to_dict(instance, camelize=False):
    """
    Konvertiert eine Modelinstanz in ein Dictionary.
    """
    class Serializer(serializers.ModelSerializer):
        class Meta:
            model = type(instance)
            fields = "__all__"
    data = Serializer(instance).data
    if camelize:
        data = _camelize(data)
    # Konvertiere von OrderedDict in ein Dictionary
    return dict(data)

0voto

Amin Hemati Nik Punkte 467

Die Antwort von @zags ist umfassend und sollte ausreichen aber die #5 Methode (die meiner Meinung nach die beste ist) wirft einen Fehler, also habe ich die Hilfsfunktion verbessert.

Da der OP darum gebeten hat, many_to_many Felder in eine Liste von Primärschlüsseln umzuwandeln, anstatt in eine Liste von Objekten, habe ich die Funktion erweitert, so dass der Rückgabewert jetzt JSON-serialisierbar ist - indem datetime Objekte in str und many_to_many Objekte in eine Liste von Ids umgewandelt werden.

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

0voto

Lewis Punkte 2458

Um ein Modell in ein Dictionary umzuwandeln und alle ForiegnKey-Modellbeziehungen beizubehalten, habe ich Folgendes verwendet:

Ohne Vollständigen Namen

from django.forms.models import model_to_dict

instance = MyModel.objects.get(pk=1) # BEISPIEL

instance_dict = {key: getattr(instance, key) for key in model_to_dict(instance).keys()}

Ausgabe

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

Dies kann nützlich sein, wenn Sie den __str__()-Wert in Ihrem Template für Fremdschlüsselbeziehungen anzeigen möchten.

Durch die Verwendung der Schlagwörter fields= und exclude= in model_to_dict(instance, [...]) können Sie spezifische Felder filtern.

Mit Vollständigen Namen

from django.forms.models import model_to_dict

instance = MyModel.objects.get(pk=1) # BEISPIEL

instance_dict = {instance._meta.get_field(key).verbose_name if hasattr(instance._meta.get_field(key), 'verbose_name') else key: getattr(instance, key) for key in model_to_dict(instance).keys()}

Beispiel-Ausgabe (Wenn es vollständige Bezeichnungen für das gegebene Beispiel gäbe)

{'Other Model:': [],
 'id': 1,
 'My Other Model:': [],
 'Normal Value:': 1}

0voto

Tony Punkte 737

Die beste Lösung, die du jemals gesehen hast.

Wandeln Sie eine django.db.models.Model-Instanz und alle zugehörigen ForeignKey-, ManyToManyField- und @Property-Funktionsfelder in ein Dictionary um.

"""
Wandeln Sie eine django.db.models.Model-Instanz und alle zugehörigen ForeignKey-, ManyToManyField- und @property-Funktionsfelder in ein Dictionary um.
Verwendung:
    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 = {}

        # Unterstützung von Feldfiltern und -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)

        # Unterstützung von Syntax "Feld__KinderFeld__..."
        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)

        # Unterstützung von @property-Decorator-Funktionen
        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"):
                # Stellen Sie sicher, dass es eine Instanz von django.db.models.fields.Field ist
                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

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