Django自定义模型字段与选择失败验证

时间:2014-07-25 16:35:54

标签: python django

我正在尝试创建一个自定义模型字段,该字段将namedtuples作为选项(将名称存储在CharField中的db中,并从to_python返回namedtuple对象 - followup to this question)。在我的模型测试中,我得到了表单验证错误Select a valid choice. ReminderFrequency(db_value="monthly", display_value="every month", get_next_reminder_time=1) is not one of the available choices.也就是说,验证所看到的选择值是实际的namedtuple对象(namedtuple的__repr__返回那个很好的长描述性字符串),而不是db_value属性。我在这里做错了什么?

完整示例:

from django.core.exceptions import ValidationError
from django.db import models
from django.forms.fields import TypedChoiceField
from django.utils import six

class NTChoiceFormField(TypedChoiceField):
    def to_python(self, value):
        if value in self.empty_values:
            return ''
        #do i need to return the namedtuple here too?  how? i don't have 
        #access to the nt_choices_dict...
        return value

class NamedTupleChoiceField(models.CharField):
    """
    A field to handle using namedtuple as choices.

    Accepts all CharField options, except it substitutes
    `nt_choices` for the `choices` keyword argument.

    `nt_choices` should be a list or tuple of namedtuples of the same type.
    """
    __metaclass__ = models.SubfieldBase

    description = unicode("A choice field that stores namedtuple names in the db"
                    " and returns the namedtuple itself in python code.")

    def __init__(self, *args, **kwargs):
        nt_choices = kwargs.pop("nt_choices")
        assert not kwargs.has_key("choices"), "Specify nt_choices as list or tuple of named tuples instead of standard Django choices tuple."
        assert hasattr(nt_choices, "__iter__")

        # make sure all nt_choices are of same type
        self.choice_base_class = type(nt_choices[0])
        for choice in nt_choices[1:]:
            assert isinstance(choice, self.choice_base_class)
        # store choices in a dict so we can get the namedtuples out by key from db
        self.nt_choice_dict = {}
        for choice in nt_choices:
            self.nt_choice_dict.update({choice.db_value: choice})
        # build the choices spec as tuple of tuples that django wants
        choices_tuple = [(choice.db_value, choice.display_value) for choice in nt_choices]
        super(NamedTupleChoiceField, self).__init__(choices=choices_tuple, *args, **kwargs)

    def get_prep_value(self, value):
        if isinstance(value, six.string_types):
            return value
        elif value is None:
            return u''
        return value.db_value

    def get_default(self):
        # Error checking to avoid specifying default as instance of namedtuple
        assert not isinstance(self.default, self.choice_base_class), "Specify default=<SomeChoice>.db_value instead of SomeChoice instance."
        default = super(NamedTupleChoiceField, self).get_default()
        return default

    def formfield(self, **kwargs):
        form_field = super(NamedTupleChoiceField, self).formfield(
                                          choices_form_class=NTChoiceFormField,
                                          **kwargs)
        return form_field

    def to_python(self, value):
        if value in (None, u"") or isinstance(value, self.choice_base_class):
            return value
        return self.nt_choice_dict[value]

这是我的失败测试:

ReminderFrequency = namedtuple('ReminderFrequency',
                               ['db_value',
                                'display_value',
                                "get_next_reminder_time"])

Weekly = ReminderFrequency(db_value="weekly",
                           display_value="every week",
                           get_next_reminder_time=1)
Monthly = ReminderFrequency(db_value="monthly",
                           display_value="every month",
                           get_next_reminder_time=1)

class NamedTupleChoiceModel(models.Model):
    nt_choice = NamedTupleChoiceField(nt_choices=[Weekly, Monthly],
                                    max_length=50,
                                    default=Monthly.db_value)


class NamedTupleChoiceFieldTests(TestCase):
    def test_with_modelform(self):
        data = {'nt_choice': Monthly.db_value}
        form = modelform_factory(model=NamedTupleChoiceModel)(data)
        success = form.is_valid()  # need to assign if we want to print errors
        self.assert_(success, msg=form.errors)
        form.save()
        self.assertEqual(NamedTupleChoiceModel.objects.count(), 1)

0 个答案:

没有答案