`); }

模型序列化

创建于 访问量:

什么是序列化

对于Django而言,模型是一个重要的部分,我们从中获取或更改数据,都是用Django的模型对象。而前端并不能直接读取这个对象,于是需要把对象映射为JSON数据进行传递。

把模型映射到JSON数据,这一过程就称之为序列化。当然,我们只需要把模型对象转为Dict,剩下的交给json标准库就可以了。

如何序列化

在进行序列化之前,我们首先需要明白,Model的字段大致可分为三种,一种是直接存储的字段(Field),一种是关系字段(Relation Field),一种是Django帮助我们反向映射过来的关系(Relation)。

第一种很容易获取,直接从Model对象的属性中读取即可,我们只需要注意,把时间字段,文件字段等无法被json库直接序列化的字段转为字符串即可。

第二种与第三种,看起来不同,一个是我们自己定义,一个是Django自动生成。但实际上,使用过Django的ORM的人都知道,它们操作起来完全是一样的。那么,序列化的时候我们可以把这两种归为一类——关系字段。
对于关系字段而言,并不能直接序列化,因为它们也都是对象。那么需要一种递归的方法去获取它们的数据。
但不能无限递归下去

class ModelSerializationMixin:
    def to_dict(self):
        pass

当我们定义Model模型的时候,直接同时继承这个类,即可让Model拥有对应方法。

获取字段

Django的Model提供了一个很棒的属性_meta,里面有get_fields()get_field()两个方法。可从前者获取模型全部字段,可以使用字段名(字符串)从后者那里拿到对应的的字段。

class ModelSerializationMixin:
    def to_dict(self):

        def get_all_fields():
            for field in self._meta.get_fields():
                print(field)

此时可以看到该对象的全部的字段。这看起来是很棒的第一步,但先等等,如果我只想获取指定的几个字段,或者不想获取指定的某几个字段。结合get_field(),稍微修改一下。

def to_dict(self, fields=None, exclude=None):
    exclude = exclude if exclude else []

    def get_all_fields():
        if fields is None:
            for field in self._meta.get_fields():
                if field.name in exclude:
                    continue
                yield field
        else:
            for field_name in fields:
                if field_name in exclude:
                    continue
                yield self._meta.get_field(field_name)

这看起来就很完美了,当不指定fields时,可以获取全部的字段,当给出了字段名的一个列表或集合之后,只返回指定的字段。

处理普通字段

对于普通的字段,例如CharField等,我们直接获取即可。不过对于DateTimeField等字段,我们需要进行适当转换才行。

def to_dict(self, fields=None, exclude=None, raw_data=False):
    exclude = exclude if exclude else []

    def get_all_fields():
        if fields is None:
            for field in self._meta.get_fields():
                if field.name in exclude:
                    continue
                yield field
        else:
            for field_name in fields:
                if field_name in exclude:
                    continue
                yield self._meta.get_field(field_name)

    for field in get_all_fields():
        result[field.name] = getattr(model, field.name)
        if not raw_data:
            # 将不能直接用json解析的对象转为字符串
            if isinstance(result[field.name], datetime.datetime):
                result[field.name] = result[field.name].strftime("%Y-%m-%d %H:%M:%S")
            elif isinstance(result[field.name], datetime.date):
                result[field.name] = result[field.name].strftime("%Y-%m-%d")
            elif isinstance(result[field.name], (File,)):
                result[field.name] = settings.MEDIA_URL + str(result[field.name])

    return result

可以看到,我们使用了field对象的name属性,它可以获取你在定义Model时定义的字段名。在这里我们使用它去获取Model对象的属性。

处理关系字段

在Django里,关系字段有四种many_to_many, many_to_one, one_to_many, one_to_one。我们可以从django.db.models.fields.__init__.Field这个类里看到,每个Field都有这四个属性。这里我们不得不使用四个条件判断语句,但为了整体代码清晰,每种不同的关系字段都由一个单独的函数来处理。

def m2m(field):
    if isinstance(field, models.ManyToManyRel):
        if field.related_name is None:
            query_set = getattr(self, field.name + '_set').all()
        else:
            query_set = getattr(self, field.related_name).all()
    else:
        query_set = getattr(self, field.name).all()
    if relation_data:
        result[field.name] = [self._to_dict(each) for each in query_set]
    else:
        result[field.name] = [each.id for each in query_set]
def o2m(field):
    if field.related_name is None:
        query_set = getattr(self, field.name + '_set').all()
    else:
        query_set = getattr(self, field.related_name).all()
    if relation_data:
        result[field.name] = [self._to_dict(each) for each in query_set]
    else:
        result[field.name] = [each.id for each in query_set]
def o2m(field):
    if field.related_name is None:
        query_set = getattr(self, field.name + '_set').all()
    else:
        query_set = getattr(self, field.related_name).all()
    if relation_data:
        result[field.name] = [self._to_dict(each) for each in query_set]
    else:
        result[field.name] = [each.id for each in query_set]
def o2o(field):
    try:
        if relation_data:
            result[field.name] = self._to_dict(getattr(self, field.name))
        else:
            result[field.name] = getattr(self, field.name + '_id')
    except ObjectDoesNotExist:
        result[field.name] = None

把一切组合起来就变成这样

import datetime

from django.core.exceptions import ObjectDoesNotExist
from django.conf import settings
from django.db import models
from django.core.files import File


class ModelSerializationMixin:

    def to_dict(self, fields=None, exclude=None, relation=False, relation_data=None, raw_data=False):
        """
        将Model对象序列化

        * int, str, float, bool 等对象正常解析
        * DateTimeField 解析为 get_datetime_format 的格式,
        * DateField 解析为 get_date_format 格式
        * FileField, ImageField 等解析为路径字符串

        :param self: 需要序列化的对象
        :param fields: str[] 需要序列化的字段名, 不给值时默认序列化所有字段
        :param exclude: str[] 不需要序列化的字段名
        :param relation: Boolean 为真时序列化关系字段
        :param relation_data: Boolean 为假时仅序列化关系字段的 id, 默认值等于 relation
        :param raw_data: Boolean 为真时将不再将 datetime 等特殊对象转为字符串,而提供原始对象
        :return: dict
        """
        if self is None:
            return None

        result = dict()  # 返回结果
        exclude = exclude if exclude else []
        relation_data = relation_data if relation_data is not None else relation

        def get_all_fields():
            if fields is None:
                for field in self._meta.get_fields():
                    if field.name in exclude:
                        continue
                    yield field
            else:
                for field_name in fields:
                    if field_name in exclude:
                        continue
                    yield self._meta.get_field(field_name)

        def if_relation(func):
            # 仅在relation为真时才执行
            def inline(field):
                if not relation:
                    return None
                return func(field)

            return inline

        @if_relation
        def m2m(field):
            if isinstance(field, models.ManyToManyRel):
                if field.related_name is None:
                    query_set = getattr(self, field.name + '_set').all()
                else:
                    query_set = getattr(self, field.related_name).all()
            else:
                query_set = getattr(self, field.name).all()
            if relation_data:
                result[field.name] = [self._to_dict(each) for each in query_set]
            else:
                result[field.name] = [each.id for each in query_set]

        @if_relation
        def o2m(field):
            if field.related_name is None:
                query_set = getattr(self, field.name + '_set').all()
            else:
                query_set = getattr(self, field.related_name).all()
            if relation_data:
                result[field.name] = [self._to_dict(each) for each in query_set]
            else:
                result[field.name] = [each.id for each in query_set]

        @if_relation
        def o2o(field):
            try:
                if relation_data:
                    result[field.name] = self._to_dict(getattr(self, field.name))
                else:
                    result[field.name] = getattr(self, field.name + '_id')
            except ObjectDoesNotExist:
                result[field.name] = None

        @if_relation
        def m2o(field):
            if relation_data:
                result[field.name] = self._to_dict(getattr(self, field.name))
            else:
                result[field.name] = getattr(self, field.name + '_id')

        def normal(field):
            result[field.name] = getattr(self, field.name)
            if raw_data:
                return
            # 将不能直接用json解析的对象转为字符串
            if isinstance(result[field.name], datetime.datetime):
                result[field.name] = result[field.name].strftime("%Y-%m-%d %H:%M:%S")
            elif isinstance(result[field.name], datetime.date):
                result[field.name] = result[field.name].strftime("%Y-%m-%d")
            elif isinstance(result[field.name], (File,)):
                result[field.name] = settings.MEDIA_URL + str(result[field.name])

        for field in get_all_fields():
            if field.many_to_many:
                m2m(field)
            elif field.one_to_many:
                o2m(field)
            elif field.one_to_one:
                o2o(field)
            elif field.many_to_one:
                m2o(field)
            else:
                normal(field)

        return result

当我们需要使用的时候,只需要像下面这样定义Model即可

class Article(models.Model, ModelSerializationMixin):
    title = models.CharField(max_length=20)