Reputation: 110
I'm trying to write a test for my form which uses a custom ModelChoiceField:
from django.forms import ModelChoiceField
class CycleModelChoiceField(ModelChoiceField):
def label_from_instance(self, cycle):
return str(cycle.begin_date)
Whatever the user selects in this field needs to be passed into 2 other fields (DateField and radio ChoiceField) by overriding the clean()
method. That's complicated logic that I need to test.
So here's what I've tried in my test so far:
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'])
But the form.is_valid()
returns false and form.errors
says:
{'cycle': [u'Select a valid choice. That choice is not one of the available choices.']}
There must be something wrong with my form construction. 'cycle': cyc
clearly isn't working as intended. I've also tried 'cycle': '0'
and 'cycle': '1'
.
What is the correct way to construct a form like this?
EDIT:
I should explain what the available choices are. There's only one CycleDate in the database and only one choice. After running the lines of my test in the shell, I typed form.fields['cycle'].choices.choice(cyc)
which returns (1, '2015-05-01')
. The strange thing is that form.fields['cycle'].queryset
returns []
. Maybe the issue is related to that?
EDIT2: Here's my form with that complicated (read: messy and terrible and shameful) clean method:
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
EDIT3 Fixed a typo in my test, and also here's my test's setUp method:
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()
EDIT4:
In addition to all this mess, I'm now getting KeyError: 'cycle'
whenever I call form.is_valid()
. Probably due to the clean() method trying to access cleaned_data['cycle'] when the cycle field has an invalid selection.
Upvotes: 3
Views: 8838
Reputation: 3742
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'])
especially line
cyc = list(CycleDate.objects.all())[0]
why not:
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's init:
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")
Upvotes: 1
Reputation: 110
As pointed out by DanielRoseman, the answer is to use the id of the instance.
So the correct way to construct a form with a ModelChoiceField is as follows:
my_instance = MyModelName.objects.get(whatever instance you need)
form_data = {'my_regular_choice_field': '0', 'my_model_choice_field': my_instance.id}
form = MyFormName(data=form_data)
Upvotes: 0