wake-up-neo.com

Konvertieren Sie Django Modellobjekt, um es mit allen intakten Feldern zu diktieren

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

Wie kann ich Django Modellobjekte in ein Wörterbuch umwandeln und trotzdem ihre Fremdschlüssel haben?

198
Zags

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.


1. 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.


2. 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.


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ückkehrt

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

Dies ist strikt schlechter als der Standardaufruf model_to_dict.


4. 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.


5. Benutzerdefinierte Funktion

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}

6. Verwenden Sie Serializer

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.


Bonusrunde: Besserer Modelldruck

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}
382
Zags

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()
10
Alfred Huang

@Zags Lösung war wunderschön!

Ich würde jedoch eine Bedingung für Datumsfelder hinzufügen, um es JSON-freundlich zu machen.

Bonusrunde

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,

  1. 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__

  2. 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,
 }
2
A.Raouf

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.

2
Gadget

(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.

1
nathj07

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

0
Shuge Lee

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
0
Pjl

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()])
0
t1m0

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
0
Armin Hemati