Django自定义YamlField实现过程解析
本文详解如何在Django中实现自定义YamlField,解决后台Textarea输入YAML格式并自动转换为字典或列表的问题。通过继承models.Field并重写关键方法,实现数据库存储与Python对象的双向转换,同时提供自定义Widget以优化Admin界面展示。
Django自定义YamlField实现过程解析
需求
在使用Django Admin时,希望后台的Textarea多行文本框能够按照YAML格式进行编写。数据库层面保存为Text文本类型,而在接口中读取该字段时,能够自动将其转换为字典或列表格式,以便前端或调用方直接使用。
曾尝试通过pip install django-yamlfield安装第三方库,并在修改以支持新版Django后使用。
发现接口中返回的字段依然保持为字符串形式,未能自动解析为预期的数据结构,不符合业务需求。
此前曾尝试编写过一版解决方案。
import yaml
from django.db import models
class YamlField(models.TextField):
def to_python(self, value): # 将数据库内容转为python对象时调用
if not value:
value = {}
if isinstance(value, (list, dict)):
return value
return yaml.safe_load(value)
def get_prep_value(self, value): # create时插入数据, 转为字符串存储
return value if value is None else yaml.dump(value, default_flow_style=False)
def from_db_value(self, value, expression, connection): # 从数据库读取字段是调用
return self.to_python(value)
主要问题在于输入框中输入的YAML列表格式。
- a - b - c
保存后,该数据会被转换为字典的字符串形式。
['a','b','c']
无法原样保存原始YAML结构。经过反复研究,参考django-jsonfield的实现思路,重新编写了一版解决方案。
核心原理是改为继承models.Field类(若仅继承models.TextField,则formfield和value_to_string方法可能不生效)。
数据库层面依然将存储的YAML文本转换为dict/list对象;在Django Admin中,通过自定义Widget将其显示为YAML字符串格式,以方便编辑。
为了确保保存时能验证表单中YAML字符串格式的正确性,还需要自定义一个Form。完整代码如下。
import django
from django.db import models
from django import forms
from django.core.exceptions import ValidationError
import yaml
class YamlWidget(forms.Textarea):
def render(self, name, value, attrs=None, renderer=None):
if value is None:
value = ""
if not isinstance(value, str):
value = yaml.safe_dump(value, default_flow_style=False)
if django.VERSION < (2, 0):
return super().render(name, value, attrs)
return super().render(name, value, attrs, renderer)
class YamlFormField(forms.CharField):
empty_values = [None, '']
def __init__(self, *args, **kwargs):
if 'widget' not in kwargs:
kwargs['widget'] = YamlWidget
super().__init__(*args, **kwargs)
def to_python(self, value):
if isinstance(value, str) and value:
try:
return yaml.safe_load(value)
except Exception as exc:
raise forms.ValidationError('Yaml decode error: %s' % (exc.args[0],))
else:
return value
def validate(self, value):
if value in self.empty_values and self.required:
raise forms.ValidationError(self.error_messages['required'], code='required')
class YamlField(models.Field):
description = "Yaml object"
def get_internal_type(self):
return 'TextField'
def formfield(self, **kwargs):
defaults = {
'form_class': YamlFormField,
'widget': YamlWidget
}
defaults.update(**kwargs)
return super().formfield(**defaults)
def to_python(self, value: str): # 将数据库内容转为python对象时调用
if value is None:
if not self.null and self.blank:
return ""
return None
if isinstance(value, (list, dict)):
return value
value = yaml.safe_load(value)
return value
def validate(self, value, model_instance): # 验证从接受到字典格式
if not self.null and value is None:
raise ValidationError(self.error_messages['null'])
try:
self.get_prep_value(value)
except ValueError:
raise ValidationError(self.error_messages['invalid'] % value)
def get_prep_value(self, value: (list, dict)): # 保存时插入数据, 转为字符串存储
if value is None:
return None
value = yaml.safe_dump(value, default_flow_style=False)
return value
def from_db_value(self, value: str, expression, connection, *args, **kwargs): # 从数据库读取字段是调用
return self.to_python(value)
def value_to_string(self, obj): # Rest Framework调用时
return self.value_from_object(obj)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









