在Django中序列化外键对象

45 投票
5 回答
38861 浏览
提问于 2025-04-16 04:22

我一直在用Django开发一些RESTful服务,目的是让这些服务可以被Flash和Android应用使用。

开发服务的接口相对简单,但我在处理那些有外键和多对多关系的对象时遇到了一些问题。

我有一个这样的模型:

class Artifact( models.Model ):
    name                = models.CharField( max_length = 255 )
    year_of_origin      = models.IntegerField( max_length = 4, blank = True, null = True )
    object_type         = models.ForeignKey( ObjectType, blank = True, null = True )
    individual          = models.ForeignKey( Individual, blank = True, null = True )
    notes               = models.TextField( blank = True, null = True )

然后我会对这个模型进行查询,使用select_related(),这样可以确保外键关系被正确处理:

artifact = Artifact.objects.select_related().get(pk=pk)

一旦我得到了这个对象,我就会对它进行序列化,然后把结果返回给我的视图:

serializers.serialize( "json", [ artifact ] )

这是我得到的结果,注意外键(object_type和individual)只是它们相关对象的ID。

[
      {
            pk: 1
            model: "artifacts.artifact"
            fields: {
                year_of_origin: 2010
                name: "Dummy Title"
                notes: ""
                object_type: 1
                individual: 1
            }
      }
]

这很好,但我希望使用select_related()时,它能自动把外键字段填充为相关对象,而不仅仅是对象的ID。

我最近才开始使用Django,但之前在CakePHP上开发过一段时间。

我特别喜欢Cake的ORM,因为它默认会跟踪关系,并创建嵌套对象,还可以在查询时选择解除这些关系。

这样做让服务的抽象变得非常简单,不需要每次都手动处理。

我发现Django默认并不会这样做,但有没有办法自动序列化一个对象及其所有相关对象呢?如果有任何建议或推荐的资料,我会非常感激。

5 个回答

6

我知道这个话题已经有很多年了,不过我还是想分享我的解决方案,给那些还在寻找答案的人(我在搜索的时候也来到了这里)。

请注意,我想要的是一个简单的函数,它可以给我模型或查询集中的嵌套对象(外键对象)或字典(这些字典也可以包含嵌套的对象或字典),然后我可以把这些转换成JSON格式。

在我的models.py文件中,我有一个自定义函数(这个函数不在模型类里面):

Models.py

def django_sub_dict(obj):
    allowed_fields = obj.allowed_fields() # pick the list containing the requested fields
    sub_dict = {}
    for field in obj._meta.fields: # go through all the fields of the model (obj)
        if field.name in allowed_fields: # be sure to only pick fields requested
            if field.is_relation: # will result in true if it's a foreign key
                sub_dict[field.name] = django_sub_dict(
                    getattr(obj, field.name)) # call this function, with a new object, the model which is being referred to by the foreign key.
            else: # not a foreign key? Just include the value (e.g., float, integer, string)
                sub_dict[field.name] = getattr(obj, field.name)
    return sub_dict # returns the dict generated

这个函数会遍历一个models.Model对象中的所有字段,如果提供了这个models.Model。我在模型中调用这个函数的方式如下(为了完整性,这里包括了一个完整的模型):

同样的Models.py

class sheet_categories(models.Model):
    id = models.AutoField(primary_key=True, unique=True)
    create_date = models.DateField(auto_now_add=True)
    last_change = models.DateField(auto_now=True)
    name = models.CharField(max_length=128)
    sheet_type = models.ForeignKey(
        sheet_types, models.SET_NULL, blank=False, null=True)
    balance_sheet_sort = models.IntegerField(unique=True)

    def allowed_fields(self):
        return [
                'name',
                'sheet_type',
                'balance_sheet_sort',
                ]

    def natural_key(self):
        return django_sub_dict(self) # call the custom function (which is included in this models.py)

注意: 嵌套的JSON对象只会包含模型中allowed_fields里列出的字段,因此不会包含敏感信息。

为了最终生成JSON,我在views.py中有以下视图代码。

views.py

class BalanceSheetData(ListView): # I believe this doesn't have to **be** a ListView.
    model = models.sheet_categories

    def get_queryset(self):
        return super().get_queryset().filter() # the filter is for future purposes. For now, not relevant

    def get(self, request, *args, **kwargs):
        context = {
            'queryset': serializers.serialize("json",
                                          self.get_queryset(),
                                          use_natural_foreign_keys=True, # this or the one below makes django include the natural_key() within a model. Not sure.
                                          use_natural_primary_keys=True, # this or the one above makes django include the natural_key() within a model. Not sure.
                                          ),
        }
        return JsonResponse(context)

这最终给了我所需的所有嵌套细节,以JSON格式返回。虽然我没有分享这个JSON响应,因为它几乎是不可读的。

欢迎随时评论。

14

更新:实际上,Manoj 的解决方案有点过时了,Wad of Stuff 的序列化工具也有一段时间没有更新了,当我尝试使用时,发现它似乎不再支持 Django 1.6 了。

不过,可以看看 Django 的官方文档。它提供了一些方法来使用内置的自然键。看起来 Django 的内置序列化工具在支持将 ImageField 作为自然键的一部分时有点问题,但这个问题你自己是可以轻松解决的。

23

我有过类似的需求,虽然不是为了RESTful的目的。我通过使用一个“完整”的序列化模块来实现我的需求,在我的案例中是Django Full Serializers。这个模块是wadofstuff的一部分,并且是根据新的BSD许可证发布的。

wadofstuff让这个过程变得很简单。例如,在你的情况下,你需要做以下几步:

第一,安装wadofstuff。

第二,在你的settings.py文件中添加以下设置:

SERIALIZATION_MODULES = {
    'json': 'wadofstuff.django.serializers.json'
}

第三,对用于序列化的代码做一个小改动:

artifact = Artifact.objects.select_related().get(pk=pk)
serializers.serialize( "json", [ artifact ], indent = 4, 
    relations = ('object_type', 'individual',))

关键的变化是relations这个关键词参数。唯一需要注意的是,要使用构成关系的字段名称,而不是相关模型的名称。

注意事项

根据文档

wadofstuff的序列化器在序列化模型时与Django的序列化器是100%兼容的。在反序列化数据流时,Deserializer类目前只适用于标准Django序列化器返回的序列化数据。

(强调部分)

希望这对你有帮助。

撰写回答