管理表单中的django自定义模型字段给出了无效的选择错误

管理表单中的django自定义模型字段给出了无效的选择错误,django,Django,我有以下用于自定义模型字段的类: class PaymentGateway(object): def fullname(self): return self.__module__ + "." + self.__class__.__name__ def authorize(self): raise NotImplemented() def pay(self): raise NotImplemented() de

我有以下用于自定义模型字段的类:

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 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)
因此,如果我去编辑现有的用户记录,它的billingtoken记录已经选择了“DPS”,然后点击save,我会得到一个无效的选择错误:

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

但是在这个函数的
raisevalidationerror
行前面放置了一些调试语句之后,这里没有引发异常,但是错误消息肯定是从这里引用的。这提示我,其他地方正在扩展ChoiceField,可能会引发此异常,我已经尝试了一些明显的异常(ChoiceField、TypedChoiceField、MultipechIceField、TypedMultipleChoiceField),但仍然没有成功。这已经占用了我很多时间,我想寻找一些聪明的线索。

最后,找出错误的原因:

它位于
django/db/models/fields/\uuuuu init\uuuuu.py
第236行

通常由于第234行:

elif value == option_key:
其中,value是一个
PaymentGateway
对象,option\u键是一个字符串

要解决此问题,我必须重写clean方法:

def clean(self, value, model_instance):
    value = unicode(value) 
    self.validate(value, model_instance)
    self.run_validators(value)      
    return self.to_python(value)

我也有同样的方法,但在我看来,重写clean(或validate)方法是非常蹩脚的。但这似乎是我唯一的选择。