python - 如何构建具有 ModelChoiceField 的 Django 表单?

标签 python django forms

我正在尝试为使用自定义 ModelChoiceField 的表单编写测试:

from django.forms import ModelChoiceField
class CycleModelChoiceField(ModelChoiceField):
    def label_from_instance(self, cycle):
        return str(cycle.begin_date)

无论用户在此字段中选择什么,都需要通过覆盖 clean() 方法传递到其他 2 个字段(DateField 和 radio ChoiceField)。这是我需要测试的复杂逻辑。

到目前为止,这是我在测试中尝试过的内容:

self.client.login(username='user', password='1234')
response = self.client.get(reverse('my_form'), follow=True)
cyc = list(CycleDate.objects.all())[0]
form_data = {'type_of_input': '0', 'cycle': cyc,'usage_type': 'E',}
form = EnergyUsageForm(data=form_data, user=response.context['user'])

但是 form.is_valid() 返回 false 并且 form.errors 说:

{'cycle': [u'Select a valid choice. That choice is not one of the available choices.']}

我的表单构造一定有问题。 'cycle': cyc 显然没有按预期工作。我也试过 'cycle': '0''cycle': '1'

构造这样一个表单的正确方法是什么?

编辑: 我应该解释可用的选择是什么。数据库中只有一个 CycleDate,而且只有一个选择。在 shell 中运行我的测试行后,我输入 form.fields['cycle'].choices.choice(cyc) 返回 (1, '2015-05-01' )。奇怪的是 form.fields['cycle'].queryset 返回 []。也许问题与此有关?

编辑 2: 这是我使用那种复杂(阅读:凌乱、可怕和可耻)干净方法的表格:

class EnergyUsageForm(forms.Form):
    # Override init so that we can pass the user as a parameter.
    # Then put the cycle form inside init so that it can access the current user variable
    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        super(EnergyUsageForm, self).__init__(*args, **kwargs)

        # Get the last 12 cycle dates for the current user
        td = datetime.date.today
        cycle_dates = CycleDate.objects.filter(cycle_ref=Account.objects.get(holder__user=user).cycle,
                                               begin_date__lte=td).order_by('begin_date')
        self.fields['cycle'] = CycleModelChoiceField(queryset = cycle_dates,
                                                      required = False,
                                                      widget = forms.Select(attrs={"onChange":'changeCalendar()'}),
                                                      label = "Choose a billing cycle")


    type_of_input = forms.ChoiceField(required=False,
                                    widget=forms.Select(attrs={"onChange": "switchInput()"}),
                                    choices=INPUT,
                                    initial='0',
                                    label="Choose a way to display usage", )

    end_date = forms.DateField(widget=forms.TextInput(attrs=
                                {
                                    'class':'datepicker'
                                }), 
                                label="Choose start date",
                                help_text='Choose a beginning date for displaying usage',
                                required=True,
                                initial=datetime.date.today,)

    period = forms.ChoiceField(required=True,
                                widget=forms.RadioSelect, 
                                choices=DISPLAY_PERIOD, 
                                initial='01',
                                label="Choose period to display",)

    usage_type = forms.ChoiceField(required=True,
                                widget=forms.RadioSelect,
                                choices=USAGE_TYPE,
                                initial='E',
                                label="Choose type of usage to display",)

    def clean_end_date(self):
        data = self.cleaned_data['end_date']

        if datetime.date.today() < data:
            raise forms.ValidationError("Don't choose a future date")
        # Always return the cleaned data, whether you have changed it or
        # not.
        return data

    def clean(self):
        cleaned_data = super(EnergyUsageForm, self).clean()
        selection = cleaned_data['type_of_input']
        # Check if the user wants to use cycle_dates instead
        if selection == '0':
            # Set the end_date and period
            cleaned_data['end_date'] = cleaned_data['cycle'].begin_date #MUST BE CHANGED TO END_DATE LATER
            cleaned_data['period'] = cleaned_data['cycle'].duration
        return cleaned_data

编辑3 修正了我测试中的一个拼写错误,还有我的测试的 setUp 方法:

client = Client()
    def setUp(self):
        user = User.objects.create_user(username = 'user', password = '1234')
        user.save()
        profile = UserProfile.objects.create(user = user)
        profile.save()
        account = Account(number=1, first_name='test',
                          last_name='User',
                          active=True,
                          holder=profile,
                          holder_verification_key=1)

        account.save()

        the_cycle = Cycle.objects.create(name = 'test cycle')
        the_cycle.save()
        cd = CycleDate.objects.create(begin_date = datetime.date(2015, 5, 1),
                                 end_date = datetime.date.today(),
                                 cycle_ref = the_cycle)
        cd.save()

编辑 4: 除了所有这些困惑之外,每当我调用 form.is_valid() 时,我现在都会收到 KeyError: 'cycle'。可能是由于 clean() 方法在循环字段具有无效选择时尝试访问 cleaned_data['cycle']。

最佳答案

self.client.login(username='user', password='1234')
response = self.client.get(reverse('my_form'), follow=True)
cyc = list(CycleDate.objects.all())[0]
form_data = {'type_of_input': '0', 'cycle': cyc,'usage_type': 'E',}
form = EnergyUsageForm(data=form_data, user=response.context['user'])

尤其是线

cyc = list(CycleDate.objects.all())[0]
  • 首先你得到所有 CycleDates 的迭代器(好吧,或多或少好吧......)
  • 然后你把所有的东西都读到内存里(为什么!)
  • 最后..您选择第一个列表元素(当然,如果列表为空,您将得到异常...)

为什么不呢:

cyc = CycleDate.objects.first()
if cyc:
    # cycle is a ModelChoice - in html it stores primary key!
    form_data = {'type_of_input': '0', 'cycle': cyc.pk, 'usage_type': 'E'}
    form = EnergyUsageForm(data=form_data, user=response.context['user'])

EnergyUsageForm 的初始化:

def __init__(self, *args, **kwargs):
    user = kwargs.pop('user', None)
    super(EnergyUsageForm, self).__init__(*args, **kwargs)

    # today is a function!
    td = datetime.date.today()
    account = Account.objects.get(holder__user=user)
    cycle_dates = CycleDate.objects.filter(cycle_ref=account.cycle,
                                           begin_date__lte=td)
                                   .order_by('begin_date')

    self.fields['cycle'] = CycleModelChoiceField(queryset=cycle_dates,
                                                 required=False,
                                                 widget=forms.Select(attrs={"onChange":'changeCalendar()'}),
                                                 label = "Choose a billing cycle")

关于python - 如何构建具有 ModelChoiceField 的 Django 表单?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37548503/

相关文章:

python - 如何将数组的前 N ​​个元素设置为零?

django - null=True,blank=True 但在发布期间每次都会引发错误 may not be null

python - 将 URL 分配给 python 脚本以在 Google Cloud 上运行 cron 作业?

javascript - 将动态形式序列化为对象数组

python - 如何从列表中删除每 12 个零

categorical-data - 指定 patsy/statsmodels 生成的类别的名称形式 'C'

python - Maxscript Python 添加修饰符

Django form.cleaned_data为None

javascript - 如何使用 onchange 事件启用表单提交?

php - Ajax FormData 文件上传不起作用 [JQuery] [PHP]