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

时间:2014-04-04 02:13:30

标签: django

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

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)仍然没有运气。这已经耗费了我很多时间,并希望寻求一些聪明的线索。

1 个答案:

答案 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)