我有以下用于自定义模型字段的类:
class PaymentGateway(object):
def fullname(self):
return self.__module__ + "." + self.__class__.__name__
def authorize(self):
raise NotImplemented()
def pay(self):
raise NotImplemented()
def __unicode__(self):
return self.fullname()
class DPS(PaymentGateway):
def authorize(self):
pass
def pay(self):
pass
这就是我编写自定义模型字段的方式:
from django.db import models
from django.utils.six import with_metaclass
from django.utils.module_loading import import_by_path
class PaymentGatewayField(with_metaclass(models.SubfieldBase, models.CharField)):
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 255
super(PaymentGatewayField, self).__init__(*args, **kwargs)
def to_python(self, value):
if value and isinstance(value, basestring):
kls = import_by_path(value)
return kls()
return value
def get_prep_value(self, value):
if value and not isinstance(value, basestring):
return value.fullname()
return value
def value_from_object(self, obj):
return self.get_prep_value(getattr(obj, self.attname))
def formfield(self, **kwargs):
defaults = {'form_class': PaymentGatewayFormField}
defaults.update(kwargs)
return super(PaymentGatewayField, self).formfield(**defaults)
class PaymentGatewayFormField(BaseTemporalField):
def to_python(self, value):
if value in self.empty_values:
return None
if isinstance(value, PaymentGateway):
return value
if value and isinstance(value, basestring):
kls = import_by_path(value)
return kls()
return super(PaymentGatewayFormField, self).to_python(value)
这就是它在模型中的使用方式:
class BillingToken(models.Model):
user = models.ForeignKey('User', related_name='billingtokens')
name = models.CharField(max_length=255)
card_number = models.CharField(max_length=255)
expire_on = models.DateField()
token = models.CharField(max_length=255)
payment_gateway = PaymentGatewayField(choices=[('project.contrib.paymentgateways.dps.DPS', 'DPS')])
我已将模型添加到管理员:
class BillingTokenInline(admin.StackedInline):
model = BillingToken
extra = 0
class UserAdmin(admin.ModelAdmin):
inlines = [BillingTokenInline]
admin.site.register(User, UserAdmin)
因此,如果我去编辑现有的用户记录,其账单记录已经选择了'DPS',并点击保存,我会收到无效的选择错误:
Select a valid choice. project.contrib.paymentgateways.dps.DPS is not one of the available choices.
我试图跟踪django代码并发现错误消息在django.forms.fields.ChoiceField
中定义:
class ChoiceField(Field):
widget = Select
default_error_messages = {
'invalid_choice': _('Select a valid choice. %(value)s is not one of the available choices.'),
}
def __init__(self, choices=(), required=True, widget=None, label=None,
initial=None, help_text='', *args, **kwargs):
super(ChoiceField, self).__init__(required=required, widget=widget, label=label,
initial=initial, help_text=help_text, *args, **kwargs)
self.choices = choices
def __deepcopy__(self, memo):
result = super(ChoiceField, self).__deepcopy__(memo)
result._choices = copy.deepcopy(self._choices, memo)
return result
def _get_choices(self):
return self._choices
def _set_choices(self, value):
# Setting choices also sets the choices on the widget.
# choices can be any iterable, but we call list() on it because
# it will be consumed more than once.
self._choices = self.widget.choices = list(value)
choices = property(_get_choices, _set_choices)
def to_python(self, value):
"Returns a Unicode object."
if value in self.empty_values:
return ''
return smart_text(value)
def validate(self, value):
"""
Validates that the input is in self.choices.
"""
super(ChoiceField, self).validate(value)
if value and not self.valid_value(value):
raise ValidationError(
self.error_messages['invalid_choice'],
code='nvalid_choice',
params={'value': value},
)
def valid_value(self, value):
"Check to see if the provided value is a valid choice"
text_value = force_text(value)
for k, v in self.choices:
if isinstance(v, (list, tuple)):
# This is an optgroup, so look inside the group for options
for k2, v2 in v:
if value == k2 or text_value == force_text(k2):
return True
else:
if value == k or text_value == force_text(k):
return True
return False
但是在这个函数的raise ValidationError
行之前放了一些调试语句后,这里不会引发异常,但是这里肯定引用了错误消息。这暗示我,其他地方正在扩展ChoiceField可能会引发这个异常,我已经尝试过明显的(ChoiceField,TypedChoiceField,MultipleChoiceField,TypedMultipleChoiceField)仍然没有运气。这已经耗费了我很多时间,并希望寻求一些聪明的线索。
答案 0 :(得分:1)
最后,弄清楚它在哪里抛出错误:
它在django/db/models/fields/__init__.py
第236行
通常是因为第234行:
elif value == option_key:
其中value是PaymentGateway
对象,option_key是字符串
要解决此问题,我必须覆盖clean方法:
def clean(self, value, model_instance):
value = unicode(value)
self.validate(value, model_instance)
self.run_validators(value)
return self.to_python(value)