Django model_to_dict的一个问题

django 中,使用 from django.forms import model_to_dict 方法可以将一个定义好的model转成dict 的类型。十分方便。但是今天在做的时候遇到一个问题。在这里记录一下。

我的 python 版本为 2.7.8 ; Django版本为 1.8

错误

  • 我的model对象为
...
tenant_id = models.CharField(max_length=32, help_text=u"租户id")
    service_id = models.CharField(max_length=32, help_text=u"服务id")
    service_key = models.CharField(max_length=32, help_text=u"服务key")
    app_version = models.CharField(max_length=20, null=False, help_text=u"当前最新版本")
    app_alias = models.CharField(max_length=100, help_text=u"服务发布名称")
    logo = models.FileField(upload_to=logo_path, null=True, blank=True, help_text=u"logo")
    info = models.CharField(max_length=100, null=True, blank=True, help_text=u"简介")
    desc = models.CharField(max_length=400, null=True, blank=True, help_text=u"描述")
...

当我使用django自带的model_to_dict时也没有问题,结果如下。

{'is_ok': True, 'min_node': 1L, 'image': u'goodrain.me/runner', 'show_category': u'305,,', 'extend_method': u'stateless', 'logo': <FieldFile: None>, 'service_key': u'1ca2903c681d7b57e3b89609d4023a6e', ....}

但是当我将结果进行json.dumps()操作是出现如下的错误

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/lib/python2.7/json/__init__.py", line 243, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <FieldFile: None> is not JSON serializable

错误原因

model里面的logo字段为FileField类型,这是一个复合类型。当该字段没有值的时候,使用model_to_dict出来的结果是 'logo': <FieldFile: None> ,这个原因导致 json.dumps()报错。

解决方法

一番周折,找到这篇文章。自定义model_to_dict()方法,在model里面加上一个方法。如下

 def to_dict(self):
        opts = self._meta
        data = {}
        for f in opts.concrete_fields:
            value = f.value_from_object(self)
            if isinstance(value, datetime):
                value = value.strftime('%Y-%m-%d %H:%M:%S')
            elif isinstance(f, FileField):
                value = value.url if value else None
            data[f.name] = value
        return data

使用时直接 model_instance.to_dict()

  • 核心代码

    """
    Why is `__fields` in here?
        it holds the list of fields except for the one ends with a suffix '__[field_name]'.
        When converting a model object to a dictionary using this method,
        You can use a suffix to point to the field of ManyToManyField in the model instance.
        The suffix ends with '__[field_name]' like 'publications__name'
    """
    __fields = list(map(lambda a: a.split('__')[0], fields or []))

    for f in chain(opts.concrete_fields, opts.virtual_fields, opts.many_to_many):
        is_edtiable = getattr(f, 'editable', False)

        if fields and f.name not in __fields:
            continue

        if exclude and f.name in exclude:
            continue

        if isinstance(f, ManyToManyField):
            if instance.pk is None:
                data[f.name] = []
            else:
                qs = f.value_from_object(instance)
                if qs._result_cache is not None:
                    data[f.name] = [item.pk for item in qs]
                else:
                    try:
                        m2m_field  = list(filter(lambda a: f.name in a and a.find('__') != -1, fields))[0]
                        key = m2m_field[len(f.name) + 2:]
                        data[f.name] = list(qs.values_list(key, flat=True))
                    except IndexError:
                        data[f.name] = list(qs.values_list('pk', flat=True))

        elif isinstance(f, DateTimeField):
            date = f.value_from_object(instance)
            data[f.name] = date_to_strf(date) if date_to_strf else date_to_timestamp(date)

        elif isinstance(f, ImageField):
            image = f.value_from_object(instance)
            data[f.name] = image.url if image else None

        elif isinstance(f, FileField):
            file = f.value_from_object(instance)
            data[f.name] = file.url if file  else None

        elif is_edtiable:
            data[f.name] = f.value_from_object(instance)

    """
    Just call an instance's function or property from a string with the function name in `__fields` arguments.
    """
    funcs = set(__fields) - set(list(data.keys()))
    for func in funcs:
        obj = getattr(instance, func)
        if inspect.ismethod(obj):
            data[func] = obj()
        else:
            data[func] = obj
    return data
    
posted @ 2018-03-28 15:58  rilweic  阅读(1937)  评论(0编辑  收藏  举报