You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django自定义Volume多输入字段报错,求排查解决

问题描述

尝试创建一个包含长度、宽度、高度和单位4部分的自定义Volume字段,计划通过继承models.JSONField实现,现有代码如下:

inventory/models.py

from django.db import models
from tpt.fields import VolumeField

class Measurement(models.Model):
    volumne = VolumeField()

tpt/fields.py

from django.db import models
from tpt import forms
    
class VolumeField(models.JSONField):
    description = 'Package volume field in 3 dimensions'
    
    def __init__(self, length=None, width=None, height=None, units=None, *args, **kwargs):
        
        self.widget_args = {
            "length": length,
            "width": width,
            "height": height,
            "unit_choices": units,
        }

        super(VolumeField, self).__init__(*args, **kwargs)
        
    def formfield(self, **kwargs):
        defaults = {"form_class": forms.VolumeWidgetField}
        defaults.update(kwargs)
        defaults.update(self.widget_args)
        return super(VolumeField, self).formfield(**defaults)

tpt/forms.py

import json
from django import forms

class VolumeWidget(forms.widgets.MultiWidget):
    def __init__(self, attrs=None):
        widgets = [forms.NumberInput(),
                   forms.NumberInput(),
                   forms.NumberInput(),
                   forms.TextInput()]
        super(VolumeWidget, self).__init__(widgets, attrs)

    def decompress(self, value):
        if value:
            return json.loads(value)
        else:
            return [0, 0, 0, '']

class VolumeWidgetField(forms.fields.MultiValueField):
    widget = VolumeWidget

    def __init__(self, *args, **kwargs):
        list_fields = [forms.fields.CharField(max_length=8),
                       forms.fields.CharField(max_length=8),
                       forms.fields.CharField(max_length=8),
                       forms.fields.CharField(max_length=4)]
        super(VolumeWidgetField, self).__init__(list_fields, *args, **kwargs)

    def compress(self, values):                                                
        return json.dumps(values)

服务器可正常启动,但在Admin后台添加Measurement模型条目时出现如下错误:

[13/May/2024 11:57:59] "GET /admin/inventory/measurement/ HTTP/1.1" 200 12250
Internal Server Error: /admin/inventory/measurement/add/
Traceback (most recent call last):
  ...
TypeError: Field.__init__() got an unexpected keyword argument 'encoder'

尝试继承models.CharField测试时,先提示CharFields must define a 'max_length' attribute.,添加max_length后又出现类似的TypeError: Field.__init__() got an unexpected keyword argument 'max_length'错误,请问哪里出错了?


解决方案

错误根源

问题出在formfield方法中,你将模型字段的固有参数(比如JSONField的encoder、CharField的max_length)和自定义的widget_args混合后传递给了表单字段,但表单字段并不认识这些模型层面的参数,因此抛出“意外关键字参数”的错误。

当调用父类的formfield方法时,Django会自动携带模型字段的相关参数,而你后续把widget_args合并到defaults中,导致这些无关参数被传递给表单字段的构造函数,触发报错。

修复代码

修改tpt/fields.py中的formfield方法,分离模型参数和表单参数:

def formfield(self, **kwargs):
    # 仅提取表单字段需要的自定义参数
    form_specific_args = {k: v for k, v in self.widget_args.items() if v is not None}
    # 设置表单类并合并用户传入的表单参数
    form_specific_args["form_class"] = forms.VolumeWidgetField
    form_specific_args.update(kwargs)
    # 调用父类方法,仅传递表单相关参数
    return super(VolumeField, self).formfield(**form_specific_args)

额外优化点

  1. 修正字段拼写:Measurement模型中的volumne改为volume,避免拼写错误。
  2. 数值字段类型修正:长度、宽度、高度改用FloatField更合理,替换tpt/forms.py中VolumeWidgetField的list_fields:
list_fields = [
    forms.FloatField(),
    forms.FloatField(),
    forms.FloatField(),
    forms.CharField(max_length=4)
]
  1. 单位选择规范化:如果单位是固定选项,把TextInput换成Select组件,限制输入:
# 修改VolumeWidget的widgets定义
widgets = [
    forms.NumberInput(),
    forms.NumberInput(),
    forms.NumberInput(),
    forms.Select(choices=[('cm', '厘米'), ('m', '米'), ('in', '英寸')])
]

同时对应调整表单字段:

# 在list_fields中替换第四个字段
forms.ChoiceField(choices=[('cm', '厘米'), ('m', '米'), ('in', '英寸')])

内容的提问来源于stack exchange,提问作者Stuart Clarke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:37:05