Reputation: 461
The beginning is simple:
class Question(models.Model):
question_string = models.CharField(max_length=255)
answers = models.CharField(max_length=255)
answers are json of list of strings e.g ['Yes', 'No']. Number of answers is dynamic. The challenge for me now is to write a form for this model.
Current state is:
class NewQuestionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(NewQuestionForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['answers'] = AnswerField(num_widgets=len(json.loads(self.instance.answers)))
class Meta:
model = Question
fields = ['question']
widgets = {
'question': forms.TextInput(attrs={'class': "form-control"})
}
class AnswerField(forms.MultiValueField):
def __init__(self, num_widgets, *args, **kwargs):
list_fields = []
list_widgets = []
for garb in range(0, num_widgets):
field = forms.CharField()
list_fields.append(field)
list_widgets.append(field.widget)
self.widget = AnswerWidget(widgets=list_widgets)
super(AnswerField, self).__init__(fields=list_fields, *args, **kwargs)
def compress(self, data_list):
return json.dumps(data_list)
class AnswerWidget(forms.MultiWidget):
def decompress(self, value):
return json.loads(value)
The problem is: i get 'the JSON object must be str, not 'NoneType'' in template with '{{ field }}'
What is wrong?
Upvotes: 1
Views: 1364
Reputation: 461
I found the problem. I forgot to add 'answers' to class Meta 'fields'.
So my example of dynamic Multiwidget created from Model is:
class NewQuestionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
# need this to create right number of fields from POST
edit_mode = False
if len(args) > 0:
edit_mode = True
answer_fields = 0
for counter in range(0, 20):
answer_key = "answers_" + str(counter)
if args[0].get(answer_key, None) is not None:
answer_fields = counter + 1
else:
break
super(NewQuestionForm, self).__init__(*args, **kwargs)
if edit_mode:
self.fields['answers'] = AnswerField(num_widgets=answer_fields, required=False)
# get number of fields from DB
elif 'instance' in kwargs:
self.fields['answers'] = AnswerField(num_widgets=len(json.loads(self.instance.answers)), required=False)
else:
self.fields['answers'] = AnswerField(num_widgets=1, required=False)
class Meta:
model = Question
fields = ['question', 'answers']
widgets = {
'question': forms.TextInput(attrs={'class': "form-control"})
}
def clean_answers(self):
temp_data = []
for tdata in json.loads(self.cleaned_data['answers']):
if tdata != '':
temp_data.append(tdata)
if not temp_data:
raise forms.ValidationError('Please provide at least 1 answer.')
return json.dumps(temp_data)
'clean_answers' has 2 porposes: 1. Remove empty answers. 2. I failed to set required attribute on first widget. So i check here at least 1 answer exists
class AnswerWidget(forms.MultiWidget):
def decompress(self, value):
if value:
return json.loads(value)
else:
return ['']
class AnswerField(forms.MultiValueField):
def __init__(self, num_widgets, *args, **kwargs):
list_fields = []
list_widgets = []
for loop_counter in range(0, num_widgets):
list_fields.append(forms.CharField())
list_widgets.append(forms.TextInput(attrs={'class': "form-control"}))
self.widget = AnswerWidget(widgets=list_widgets)
super(AnswerField, self).__init__(fields=list_fields, *args, **kwargs)
def compress(self, data_list):
return json.dumps(data_list)
Upvotes: 1