我刚刚编写了 Field 和 Widget 子类,它们解决了这个特殊问题,并且可以与 JS 自动完成一起使用,例如 - 并且是可重用的。尽管如此,它比您的解决方案需要更多的工作,我不确定您是否要使用我的解决方案。无论哪种方式 - 我希望我会得到一些支持 - 我花了很多时间和精力来写这篇文章......
我建议不要像以前那样定义你的 ModelForm 并弄乱clean_ :
class SongForm(forms.ModelForm):
artist = CustomModelChoiceField( queryset = Artist.objects.all(), query_field = "name" )
class Meta:
model = Song
现在,CustomModelChoiceField(我想不出更好的类名称)是 ModelChoiceField 子类,这很好,因为我们可以使用 queryset 参数来缩小可接受的选择范围。如果widget 参数不存在,如上所示,则使用该字段的默认值(稍后会详细介绍)。 query_field 是可选的,默认为 "pk"。所以,这里是域代码:
class CustomModelChoiceField( forms.ModelChoiceField ):
def __init__( self, queryset, query_field = "pk", **kwargs ):
if "widget" not in kwargs:
kwargs["widget"] = ModelTextInput( model_class = queryset.model, query_field = query_field )
super( CustomModelChoiceField, self ).__init__( queryset, **kwargs )
def to_python( self, value ):
try:
int(value)
except:
from django.core.exceptions import ValidationError
raise ValidationError(self.error_messages['invalid_choice'])
return super( CustomModelChoiceField, self ).to_python( value )
__init__ 的主体意味着在创建CustomModelChoiceField 期间设置widget = None 为我们提供了简单的ModelChoiceField(这在调试时非常有用......)。现在,实际工作在ModelTextInput 小部件中完成:
class ModelTextInput( forms.TextInput ):
def __init__( self, model_class, query_field, attrs = None ):
self.model_class = model_class
self.query_field = query_field
super( ModelTextInput, self ).__init__( attrs )
def render(self, name, value, attrs = None ):
try:
obj = self.model_class.objects.get( pk = value )
value = getattr( obj, self.query_field )
except:
pass
return super(ModelTextInput, self).render( name, value, attrs )
def value_from_datadict( self, data, files, name ):
try:
return self.model_class.objects.get( **{ self.query_field : data[name] } ).id
except:
return data[name]
它本质上是TextInput,它知道两个额外的东西——它代表哪个模型的哪个属性。 (model_class 应该替换为 queryset 以缩小实际工作的可能选择,我稍后会修复它)。查看value_from_datadict 的实现很容易发现为什么字段中的to_python 必须被覆盖-它期望int 值,但不检查它是否为真-并且只是将值传递给关联模型,该模型失败了丑陋的例外。
我对此进行了一段时间的测试,它可以工作 - 您可以指定不同的模型字段,表单字段将通过这些字段尝试找到您的 artist,表单错误处理由基类自动完成,您无需编写自定义clean_ 方法每次你想使用类似的功能。
我现在太累了,但我明天会尝试编辑这篇文章(和代码)。