Django自定义YamlField实现过程解析

闻溪
闻溪
发布时间:2026-10-14 | 4人浏览过
原创

本文详解如何在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)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表