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