แปลงวัตถุโมเดล Django เป็น dict โดยมีทุกฟิลด์เหมือนเดิม


257

หนึ่งในวิธีการที่ไม่แปลงวัตถุ Django รูปแบบการ Dict ที่มีทั้งหมดของเขตของตนหรือไม่ editable=Falseทั้งหมดนึกคิดรวมถึงปุ่มต่างประเทศและเขตข้อมูลที่มี

ให้ฉันทำอย่างละเอียด สมมติว่าฉันมีรุ่น Django ดังต่อไปนี้:

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

ในเทอร์มินัลฉันได้ทำสิ่งต่อไปนี้แล้ว:

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

ฉันต้องการแปลงเป็นพจนานุกรมต่อไปนี้:

{'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}

คำถามที่มีคำตอบที่ไม่น่าพอใจ:

Django: การแปลงทั้งชุดของวัตถุของ Model เป็นพจนานุกรมเดียว

ฉันจะเปลี่ยนวัตถุของ Django Model เป็นพจนานุกรมและยังมีคีย์ต่างประเทศได้อย่างไร


1
คุณสามารถประกาศวิธีการที่เรียกว่าto_dictและจัดการในแบบที่คุณต้องการ
karthikr

@karthikr ใช่ฉันทำได้ คำถามคือวิธีการสร้างวิธีการดังกล่าว การสร้างพจนานุกรมจากฟิลด์ทั้งหมดของแบบจำลองด้วยตนเองไม่ใช่คำตอบที่เหมาะสม
Zags

ฉันใช้ประโยชน์จากไลบรารี ReST ที่มีอยู่เช่น Django Rest Framework, Tastypie หรือ Piston เนื่องจากพวกเขาทั้งหมดมีกลไกในการแปลงอินสแตนซ์ของโมเดล django เป็นพื้นฐานสำหรับการทำให้เป็นอนุกรม หากคุณสงสัยมากขึ้นคุณสามารถดูรหัสของพวกเขาได้ แต่ส่วนใหญ่จะเป็น_metaคำจำกัดความของโมเดลเพื่อค้นหาฟิลด์ที่เกี่ยวข้องกับโมเดลและดึงค่ามาจากอินสแตนซ์
Kevin Stone

คำตอบ:


524

มีหลายวิธีในการแปลงอินสแตนซ์เป็นพจนานุกรมโดยมีองศาการจัดการตัวพิมพ์มุมและการใกล้เคียงกับผลลัพธ์ที่ต้องการ


1 instance.__dict__

instance.__dict__

ซึ่งผลตอบแทน

{'_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}

นี่เป็นวิธีที่ง่ายที่สุด แต่ขาดไป many_to_many , foreign_keyจะเรียกไม่ถูกและมันมีสองสิ่งพิเศษที่ไม่พึงประสงค์ในนั้น


2 model_to_dict

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

ซึ่งผลตอบแทน

{'foreign_key': 2,
 'id': 1,
 'many_to_many': [<OtherModel: OtherModel object>],
 'normal_value': 1}

นี่เป็นสิ่งเดียวเท่านั้นที่มี many_to_manyฟิลด์แต่ไม่มีฟิลด์ที่แก้ไขไม่ได้


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

ซึ่งผลตอบแทน

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

นี่แย่กว่ามาตรฐานอย่างเคร่งครัด model_to_dictเรียกใช้


4 query_set.values()

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

ซึ่งผลตอบแทน

{'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}

นี่เป็นเอาต์พุตเดียวกันกับinstance.__dict__แต่ไม่มีฟิลด์เพิ่มเติม foreign_key_idยังคงผิดและmany_to_manyยังขาดหายไป


5. ฟังก์ชั่นที่กำหนดเอง

รหัสสำหรับ django's model_to_dictมีคำตอบส่วนใหญ่ มันลบเขตข้อมูลที่ไม่สามารถแก้ไขได้อย่างชัดเจนดังนั้นการลบการตรวจสอบและการรับรหัสต่างประเทศสำหรับเขตข้อมูลจำนวนมากไปยังเขตพื้นที่ต่าง ๆ ส่งผลให้รหัสต่อไปนี้ซึ่งทำงานตามที่ต้องการ:

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

ในขณะที่นี่เป็นตัวเลือกที่ซับซ้อนที่สุดการโทรto_dict(instance)ให้ผลลัพธ์ตามที่ต้องการ:

{'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. ใช้ Serializers

ModelSerialzer ของDjango Rest Frameworkอนุญาตให้คุณสร้าง serializer โดยอัตโนมัติจากรุ่น

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

SomeModelSerializer(instance).data

ผลตอบแทน

{'auto_now_add': '2018-12-20T21:34:29.494827Z',
 'foreign_key': 2,
 'id': 1,
 'many_to_many': [2],
 'normal_value': 1,
 'readonly_value': 2}

นี่เกือบจะดีเท่ากับฟังก์ชั่นที่กำหนดเอง แต่ auto_now_add เป็นสตริงแทนวัตถุ datetime


รอบโบนัส: การพิมพ์แบบที่ดีขึ้น

หากคุณต้องการรุ่น django ที่มีการแสดงบรรทัดคำสั่งของ python ที่ดีกว่าให้รุ่นของคุณเป็นคลาสย่อยดังต่อไปนี้:

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

ตัวอย่างเช่นถ้าเรากำหนดแบบจำลองของเราเช่น:

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

การโทรSomeModel.objects.first()ตอนนี้ให้เอาต์พุตดังนี้:

{'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}

2
ขอบคุณสำหรับคำตอบนี้! คุณอาจเปลี่ยนisinstanceการทดสอบในการแก้ปัญหา # 5 (โบนัส) if f.many_to_manyเพื่อ
dhobbs

1
@dhobbs ฉันถ่ายแบบรหัสที่ออกจาก Django ของรหัสที่ใช้model_to_dict isinstanceผมไม่แน่ใจว่าทำไมพวกเขาทำทางเลือกนี้ แต่อาจจะมีเหตุผลที่ดีสำหรับมัน (เช่นmany_to_manyคุณสมบัติถูกนำมาใช้ในรุ่นที่ใหม่กว่า)
Zags

มันจะกลับ@propertyเขตข้อมูลหรือไม่
angrysumit

1
ฉันสงสัยว่าวิธีการเหล่านี้จะรักษาฟิลด์ที่มีคำอธิบายประกอบ / รวมอย่างไร
เมห์เม็

สิ่งที่ฉันทำคือตรวจสอบหา get_FOO_display และคืนค่านั้นแทนที่จะเป็นค่าใด ๆ ที่อาจมีอยู่จริง
Bobort

9

ฉันพบวิธีแก้ไขที่เป็นระเบียบเพื่อให้ได้ผลลัพธ์:

สมมติว่าคุณมีโมเดลวัตถุo:

เพียงโทร:

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

10
นี่เป็นเพียงตัวเลือก # 4 ในคำตอบของฉัน #
344 Zags

7

วิธีแก้ปัญหา @Zags นั้นงดงามมาก!

ฉันจะเพิ่มแม้ว่าเงื่อนไขสำหรับ datefields เพื่อให้ JSON เป็นมิตร

รอบโบนัส

หากคุณต้องการรุ่น django ที่มีการแสดงบรรทัดคำสั่งของ python ที่ดีขึ้นให้รุ่นของคุณเป็นคลาสย่อยของเด็ก ๆ ดังต่อไปนี้:

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

ตัวอย่างเช่นถ้าเรากำหนดแบบจำลองของเราเช่น:

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

การโทรSomeModel.objects.first()ตอนนี้ให้เอาต์พุตดังนี้:

{'created': 1426552454.926738,
'value': 1, 'value2': 2, 'reference1': 1, u'id': 1, 'reference2': [1]}

หากคุณต้องการแปลงเป็นและจาก JSON คุณควรดูที่ Django Rest Framework หรือใช้สิ่งนี้: stackoverflow.com/a/22238613/2800876
Zags

แน่นอน! แต่การเปลี่ยนแปลงเล็กน้อยในโค้ดของคุณเพิ่มความสะดวกสบายอย่างมาก!
Diego Freitas Coelho

4

วิธีที่ง่ายที่สุด

  1. หากแบบสอบถามของคุณคือModel.Objects.get ():

    get () จะส่งคืนอินสแตนซ์เดี่ยวเพื่อให้คุณสามารถใช้งานได้โดยตรง __dict__จากอินสแตนซ์ของคุณ

    model_dict = Model.Objects.get().__dict__

  2. สำหรับตัวกรอง () / ทั้งหมด ():

    all () / filter ()จะส่งคืนรายการอินสแตนซ์เพื่อให้คุณสามารถใช้values()เพื่อรับรายการของวัตถุ

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


4

เพียงแค่vars(obj)มันจะระบุค่าทั้งหมดของวัตถุ

>>> obj_attrs = vars(obj)
>>> obj_attrs
 {'_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,
 }

คุณสามารถเพิ่มสิ่งนี้ได้เช่นกัน

>>> 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=<UTC>),
    'file_data_id': 797719,
   }

3

ปรับปรุง

คำตอบที่รวบรวมใหม่ที่โพสต์โดย @zags นั้นสมบูรณ์และสวยงามกว่าของฉัน โปรดอ้างอิงคำตอบนั้นแทน

เป็นต้นฉบับ

หากคุณยินดีที่จะกำหนดวิธีการ to_dict ของคุณเองเช่น @karthiker แนะนำนั่นก็แค่เดือดร้อนปัญหานี้ลงไปที่ปัญหาชุด

>>># 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}

เราจำเป็นต้องลบคีย์ต่างประเทศที่ติดฉลากผิดออกจากDictอื่น ๆ

ในการทำเช่นนี้เราสามารถใช้การวนซ้ำที่สร้างพจนานุกรมใหม่ที่มีทุกรายการยกเว้นที่มีขีดล่างในพจนานุกรม หรือเพื่อเป็นการประหยัดเวลาเราสามารถเพิ่มสิ่งเหล่านั้นลงในdictดั้งเดิมได้เนื่องจากพจนานุกรมตั้งอยู่ใต้กระโปรงหน้ารถ

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

ดังนั้นเราจะเหลือมีดังต่อไปDict :

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

และคุณแค่คืนสิ่งนั้น

ในข้อเสียคุณไม่สามารถใช้เครื่องหมายขีดล่างในชื่อฟิลด์ที่สามารถแก้ไขได้ = false ในทางกลับกันสิ่งนี้จะใช้ได้กับชุดของเขตข้อมูลใด ๆ ที่เขตข้อมูลที่ผู้ใช้ทำไม่มีขีดล่าง

นี่ไม่ใช่วิธีที่ดีที่สุดในการทำเช่นนี้ แต่สามารถทำงานเป็นวิธีแก้ปัญหาชั่วคราวจนกว่าจะพบวิธีการที่ตรงกว่า

สำหรับตัวอย่างด้านล่าง dict จะเกิดขึ้นตาม model_to_dict และ otherDict จะเกิดขึ้นโดยวิธีค่าของตัวกรอง ฉันจะทำสิ่งนี้กับโมเดลด้วยตัวเอง แต่ฉันไม่สามารถรับเครื่องของฉันเพื่อรับโมเดลอื่น ๆ ได้

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

ที่ควรจะนำคุณเข้าสู่สนามเบสบอลคร่าวๆของคำตอบสำหรับคำถามของคุณฉันหวังว่า


1
ไม่แน่ใจว่าคุณกำลังพยายามใช้อะไรอยู่ที่reนี่ ถ้าเป็นการกรองคีย์ด้วยการขีดเส้นใต้คีย์เหล่านี้นี่ไม่ใช่รหัสที่ถูกต้องหรือพฤติกรรมที่ถูกต้อง คอลัมน์re.match("_", "reference1_id")ส่งคืนNoneและถูกต้องตามกฎหมายในฐานข้อมูลอาจมีขีดล่างในชื่อ
Zags

re.match ("_", "reference1_id") ส่งคืนไม่มีควรเป็น: re.match (". * _. *", "reference1_id")
Gadget

ฉันทำการเปลี่ยนแปลงบางอย่างเพื่อลบตัวอย่างที่ไม่ดีและรวมดีกว่า ฉันยังเปลี่ยนบางสิ่งเพื่อแสดงว่านี่จะเป็นวิธีแก้ปัญหาชั่วคราวสำหรับชุดย่อยของทุกรุ่น ฉันไม่รู้ว่าคุณจะทำอย่างไรกับแบบจำลองที่มีขีดเส้นใต้ในeditable=falseสาขาของพวกเขา ฉันแค่พยายามจัดหาสิ่งที่คุณอาจจะสามารถทำงานด้วยได้จนกว่าจะมีโซลูชัน Canon เพิ่มเติม
Gadget

อาจใช้"_" in stringมากกว่าreในกรณีนั้น
Zags

ใช่ว่าจะเป็นวิธีที่ง่ายกว่ามากในการทำ มันไม่ได้เกิดขึ้นกับฉันที่จะใช้ด้วยวิธีนี้ แต่ตอนนี้มันสมเหตุสมผลดีอย่างสมบูรณ์ ฉันได้เปลี่ยนคำตอบที่จะใช้แทนin re
Gadget

2

โซลูชั่นที่น่าสนใจมากมายที่นี่ ทางออกของฉันคือการเพิ่มวิธี as_dict ให้กับแบบจำลองของฉันด้วยความเข้าใจ dict

def as_dict(self):
    return dict((f.name, getattr(self, f.name)) for f in self._meta.fields)

เป็นโบนัสโซลูชันนี้จับคู่กับรายการความเข้าใจเหนือแบบสอบถามทำให้เป็นโซลูชันที่ดีถ้าคุณต้องการส่งออกแบบจำลองของคุณไปยังไลบรารีอื่น ตัวอย่างเช่นการทิ้งโมเดลของคุณลงใน dataframe แพนด้า:

pandas_awesomeness = pd.DataFrame([m.as_dict() for m in SomeModel.objects.all()])

1
วิธีนี้ใช้งานได้ดีสำหรับเขตข้อมูลค่าเช่นสตริงและ int แต่จะมีปัญหากับคีย์ต่างประเทศและอื่น ๆ อีกมากมายกับหลายสาขา
Zags

จุดดีมาก! โดยเฉพาะอย่างยิ่งสำหรับหลาย ๆ คน ใครอยากจะใส่เงื่อนไขในการจัดการกรณีเหล่านั้นอย่างเหมาะสมหรือ จำกัด การแก้ปัญหานี้กับรุ่นที่เรียบง่าย ขอบคุณ
t1m0

1

(ไม่ได้หมายถึงการแสดงความคิดเห็น)

ตกลงมันไม่ได้ขึ้นอยู่กับประเภทในแบบนั้น ฉันอาจเข้าใจผิดคำถามต้นฉบับที่นี่ดังนั้นโปรดยกโทษให้ฉันหากเป็นเช่นนั้น หากคุณสร้าง serliazers.py คุณจะต้องสร้างคลาสที่มีเมตาคลาส

Class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = modelName
        fields =('csv','of','fields')

จากนั้นเมื่อคุณได้รับข้อมูลในคลาสมุมมองคุณสามารถ:

model_data - Model.objects.filter(...)
serializer = MyModelSerializer(model_data, many=True)
return Response({'data': serilaizer.data}, status=status.HTTP_200_OK)

นั่นคือสิ่งที่ฉันมีในสถานที่ต่างๆและมันก็ส่งคืน JSON ที่ดีผ่านทาง JSONRenderer

อย่างที่ฉันบอกว่านี่เป็นความอนุเคราะห์จาก DjangoRestFramework ดังนั้นมันจึงคุ้มค่าที่จะดู


1

วิธีที่ง่ายกว่าคือการใช้pprintซึ่งอยู่ในฐานหลาม

import pprint
item = MyDjangoModel.objects.get(name = 'foo')
pprint.pprint(item.__dict__, indent = 4)

สิ่งนี้จะให้ผลลัพธ์ที่มีลักษณะคล้ายกับjson.dumps(..., indent = 4)แต่จัดการกับชนิดข้อมูลแปลก ๆ ที่อาจฝังอยู่ในอินสแตนซ์โมเดลของคุณเช่นModelStateและUUIDอื่น ๆ

ทดสอบกับ Python 3.7


0

บางทีนี่อาจช่วยคุณได้ ขอให้สิ่งนี้ไม่ได้ปกปิดความสัมพันธ์กับคนจำนวนมาก แต่มันค่อนข้างมีประโยชน์เมื่อคุณต้องการส่งแบบจำลองของคุณในรูปแบบ 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

0

ทางออกที่ดีที่สุดที่คุณเคยเห็น

แปลงอินสแตนซ์ django.db.models.Model และฟิลด์ ForeignKey, ManyToManyField และ @Property ที่เกี่ยวข้องทั้งหมดเป็น 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: /programming/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

คำตอบจาก @zags นั้นครอบคลุมและน่าจะพอเพียงแต่เป็น# 5 (ซึ่งเป็น IMO ที่ดีที่สุด) ส่งข้อผิดพลาดดังนั้นฉันจึงปรับปรุงฟังก์ชันตัวช่วย

เนื่องจาก OP ขอให้แปลงmany_to_manyฟิลด์เป็นรายการของคีย์หลักมากกว่ารายการของวัตถุฉันจึงปรับปรุงฟังก์ชั่นเพื่อให้ค่าที่ส่งคืนเป็น JSON ต่อเนื่องกันได้โดยการแปลงdatetimeวัตถุเป็นstrและmany_to_manyวัตถุเป็นรายการของ ID

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

คุณได้รับข้อผิดพลาดอะไร ฉันยินดีที่จะอัพเดทคำตอบ
Zags

ปัจจุบันการทำงานของลูปผ่านconcrete_fieldsและm2m_fieldsมีลักษณะเหมือนกันดังนั้นสมมติว่าm2m_fieldsลูปมีการใช้งานที่ไม่ถูกต้องที่นี่
Daniel Himmelstein

@Zags ข้อผิดพลาดAttributeError: 'list' object has no attribute 'values_list' ที่ฉันไม่สามารถหาเหตุผลที่อยู่เบื้องหลัง ฉันใช้ Django 2.1.1
Armin Hemati Nik

@ daniel-himmelstein ขอบคุณที่ชี้ให้เห็นฉันแก้ไขรหัส เหตุผลสำหรับลูปที่เหมือนกันนั้นเกิดจากการดำเนินการที่แตกต่างกันในรหัสท้องถิ่นของฉันและฉันลืมที่จะปรับให้เหมาะสมสำหรับคำตอบ SO
Armin Hemati Nik

@ArminHemati Django การเปลี่ยนแปลงการดำเนินการและเป็นผลของfield.value_from_object model_to_dictฉันได้อัปเดตหัวข้อที่ 5 ของคำตอบเพื่อแสดงสิ่งนี้แล้ว
Zags
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.