How do you iterating context variables with forms in Django? - django

I have a list of questions and a form that has a corresponding number of answer fields.
Example:
questions = ['What is your favorite color?', 'What is your first car?']
How do I display in template, alternating question and answer pair?
Desired results:
What is your favorite color?
Answer: [answer_field1]
What is your first car?
Answer: [answer_field2]
Currently, my template has this code but it is not displaying correctly.
{% load i18n %}
{% block head %}
{{ wizard.form.media }}
{% endblock %}
{% block content %}
Please answer these questions:
<p>
<p>
<form action="" method="post">{% csrf_token %}
{% for question in questions %}
{{ question }}
{{ form.as_p }}
{% endfor %}
I don't have control over looping over 'form' because Django does that automatically. I'm not sure how to zip 'form' and 'questions' together to for loop them together.

you might need to write a custom templatetag to achieve such task. but before you would do that, you might need to consider these questions
Does the index of a question in the questionsList correspond with the index of its answer field in the form?
Does the length of the questionsList equals the number of fields present in the form?
Try this
templatetags/mytag.py
from django import template
register = template.Library()
def do_question_n_ans(parser, token):
"""
Usage:
{% do_question_n_ans_for question in questionsList and ans_field in form.fields %}
"""
bits = token.contents.split()
if len(bits) != 8:
raise template.TemplateSyntaxError("'%s' takes exactly seven arguments" % bits[0])
if len(bits[3]) != len(bits[7]):
raise template.TemplateSyntaxError("Question list and anwser fields must b of the same length")
if bits[2] != 'in':
raise template.TemplateSyntaxError("Second argument to '%s' must be 'in'" % bits[0])
if bits[4] != 'and':
raise template.TemplateSyntaxError("Third argument to '%s' must be 'and'" % bits[0])
if bits[7] != 'in':
raise template.TemplateSyntaxError("Sixth argument to '%s' must be 'in'" % bits[0])
return QuestionAnswerFormatNode(bits[1], bits[3], bits[5], bits[7]
class QuestionAnswerFormatNode(template.Node):
def __init__(self, question, questionsList, ans_field, fields):
self.questionsList = questionsList
self.fields = fields
self.question = question
self.ans_field = ans_field
def render(self, context):
try:
questionsList = template.resolve_variable(self.questionsList, context)
except template.VariableDoesNotExist:
raise template.VariableDoesNotExist
question_index = questionsList.index(question)
ans_field = fields[question_index]
context[self.ans_field] = ans_field
return ''
register.tag('do_question_n_ans_for', do_question_n_ans)
you could then use the tag within your html code
{% load mytag %}
{% for question in questionList %}
{% do_question_n_ans_for question in questionsList and ans_field in form.fields %}
<p>Question: {{ question }}</p>
<p>Answer: {{ ans_field }}</p>
{% endfor %}
Note:
I presumed you already have the questions and the answer fields arranged in this format in their respective list
['question1', 'question2', 'question3'] and ['answerFieldToQuestion1', 'answerFieldToQuestion2', 'answerFieldToQuestion3'].
you should be able to check out the order of your form fields by printing yourForm.fields.
Please let me know if there are any errors as i didn't test the above code, but i guarantee you it would work just fine

Related

Django: how to display my own dynamically-made fields

I have a SurveyForm where I create dynamically my fields. Here's the most basic code I could do and still have my problem:
class SurveyForm(forms.Form):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields = []
self.fields_alone = []
new_field = forms.CharField(label=f'1 - 2: This is the question',
widget=widgets.Input(attrs={}))
self.fields.append(('id_1', new_field))
self.fields_alone.append(new_field)
self.fields = OrderedDict(self.fields)
In my template, when I do a "classical" loop I can display the fields and it works, but the second loop, which is supposed to access to the same fields, doesn't work:
<form action="" method="post">
{% for field in form %}
{{ field.label }} :{{ field }} <br>
{% endfor %}
{% for field in form.fields_alone %}
{{ field.label }} :{{ field }} <br>
{% endfor %}
</form>
The second loop display the field as a string like <django.forms.fields.CharField object at 0x0000012C00EF60D0>
What am I missing to display is like the "classical" loop?
I found the problem. It's in the source code of Django.
Django can only convert fields to HTML if they are BoundField. The problem is that you can access to the BoundField "version" of the fields of your form through iteration on the form itself.
In the django/forms/forms.py:
def __getitem__(self, name):
# blabla code
if name not in self._bound_fields_cache:
self._bound_fields_cache[name] = field.get_bound_field(self, name)
So you can get HTML code only through an iteration on the form or direct access to a field via form['myfield'].
So in my form I did:
class SurveyForm(forms.Form):
def field_by_id(self, field_id):
return self[field_id]
Then I've made a template tag in my application which is:
#register.filter(name='field_by_id')
def field_by_id(arg1, arg2):
"""
Returns the result of field_by_id() (= method has to exist in arg1!)
Usage: {{ form|field_by_id:XXX }} (XXX = field id string)
:param arg1: object of class Form
:param arg2: field id string
:returns corresponding field
"""
return arg1.field_by_id(arg2)
and then in my template, I use it like this:
{% for question_group, questions in form.question_groups.items %}
{% for question, answers in questions.items %}
{% for answer in answers %}
{% with form|field_by_id:answer as field %}
{{ field.label }} :{{ field }} <br>
{% endwith %}
{% endfor %}
{% endfor %}
{% endfor %}
And it works. Tricky solution, but I have many sub-groups (I could have used FormSet's for a single sub-group).
The fields list is meant to store the form fields. Whereas fields_alone isn't.
That is also the reason why you are able to loop over the form itself and not over form.fields.
Since I cant figure out why you need to have this other list I suggest that you add all the fields to the actual fields list.
Comment if there are any further questions to this problem
Here:
self.fields = []
You're overwriting the form's own fields ordereddict. You just want to add to it:
self.fields["whatevernameyouwant"] = new_field
(works on django 1.11, not tested on 2.x / 3.x)
EDIT:
the problem is when I try to use the fields through another property
You mean the form.fields_alone thing ? Well, you can always make it a property instead:
class YourForm(...):
def __init__(...)
# ....
self._fields_alone = []
self.fields["whatevernameyouwant"] = new_field
self.fields_alone.append("whatevernameyouwant")
#property
def fields_alone(self):
for name in self._fields_alone:
yield self[name]
But it won't remove this field from the "normal" form iterator (for field in form) so you'll have the field twice, so you'd also need to redefine __iter__ too:
def __iter__(self):
for name in self.fields:
if name not in self._fields_alone:
yield self[name]

Queryset object in Django form not iterable

I'm trying to get the form to create the fields based on what exam page the user is on. In the error page, all local variables have the correct value for form and view, but I keep getting ExamQuestion object not iterable and an error at line 0 of the template. It also highlights the render() at line 44 in the view as the source of the problem. If I change line 28 from exam__name=exam_name to exam__name="exam_name", basically turning the variable into a str, the page runs but no data is passed.
In the error console choice_list shows querysets as individual list items as it should for forms.py
How do I make the object ExamQuestion iterable? I've been stumped for a week now. I've written a hundred ways at this point.
I know it's listing questions instead of answers for the questions, I'm just trying to get it to load ANY queryset and freaking run at this point.
view
def exampage(request, exam_name):
exams = Exam.objects.all()
questionlist = ExamQuestion.objects.filter(exam__name=exam_name)
choicelist = ExamChoice.objects.filter(question__exam__name=exam_name)
form = ExamTest(request.POST, exam_name=exam_name)
if request.method == "POST":
if form.is_valid():
#form.save()
#choice = form.cleaned_data.get('choice')
return redirect('exampage.html')
return render(request, 'exams/exampage.html', {'exams': exams,'questionlist': questionlist, 'exam_name': exam_name, 'choicelist': choicelist, 'form': form, 'choice': choice})
else:
form = ExamTest(exam_name=exam_name)
return render(request, 'exams/exampage.html', {'exams': exams,'questionlist': questionlist, 'exam_name': exam_name, 'choicelist': choicelist, 'form': form})
form
class ExamTest(forms.Form):
def __init__(self, *args, **kwargs):
exam_name = kwargs.pop('exam_name')
super(ExamTest, self).__init__(*args, **kwargs)
#choice_list = [x for x in ExamQuestion.objects.filter(exam__name="dcjs01")]
#choice_list = []
x = ExamQuestion.objects.filter(exam__name=exam_name)
#for q in x:
# choice_list.append(q)
self.fields["choices"] = forms.ChoiceField(choices=x, label="testlabel")
template
{% extends 'portal/base.html' %}
{% block content %}
<h1>{{ exam_name }} Page</h1>
{{ exam_id }}
<hr>
{% for exam in exams %}
<li>{{ exam }}</li>
{% endfor %}
<h1>! {{ questionlist }} !</h1>
<form method="post" action="#">
{% csrf_token %}
formtest{{ form }}
<button type="submit"> finish test </button>
</form>
{% endblock %}
The first part of the question is - you getting the ExamQuestion not iterable error:
here I think is the problem, that you, in the Form init function pass the Queryset (objects.filter(xxx)), but not the .all() which will select it.
the second thought is - would'n it be better to pass the questions as a parameter to the Form, as you previously selected all the question for this particular exam?
figured it out. choices=x needs to be a tuple
self.fields['name'] = forms.ChoiceField(choices=tuple([(name, name) for name in x]))

Using redirect sends me to /tag/?search=input instead of /tag/input (Django URL argument from form)

I have a page where there is a path /tag/name_of_tag and you can see all posts tagged with that tag.
Inside the page, you can also select another tag in a form and go to that tag.
The problem is that instead of going to /tag/searched_tag, it goes to /tag/?search=searched_tag
How can I change it doesn't leave the ?search= part?
urls.py:
url(r'tag/(?P<input_tag>\w+)$', views.tag_view, name='tag'),
views.py:
def tag_view(request, input_tag):
form = TagSearchForm()
if request.method == 'GET':
form = TagSearchForm(request.GET)
if form.is_valid():
input = form.cleaned_data['search']
print(input)
return redirect('fortykwords:tag_view', input)
else:
form = SearchForm()
latest_post_list = Post.objects.filter(tags=input_tag, status__exact="published")
paginator = Paginator(latest_post_list, 3)
page = request.GET.get('page')
posts = paginator.get_page(page)
context = {'latest_post_list': latest_post_list, 'page_tag': input_tag, 'form': form}
return render(request, 'fortykwords/tag.html', context)
forms.py:
class TagSearchForm(forms.Form):
search = tagulous.forms.SingleTagField(
tag_options=tagulous.models.TagOptions(
autocomplete_view='fortykwords:post_tags_autocomplete'
),
label='Tags',
required=True,
help_text=_('Filter by lead tags. You can organize leads by any tag you want.'),
)
tag.html:
{% extends "base_generic.html" %}
{% block content %}
<form action="." method="get">
{{ form }}
<input type="submit" value="Submit" />
</form>
<h3>Posts with the tag {{ page_tag }}</h3>
{% if latest_post_list %}
<ul>
{% for post in latest_post_list %}
<li> {{ post.author }} {{ post.pub_date }}
<br>
{{ post.title }}</li>
{% for tag in post.tags.all %}
{{ tag.name }}
{% endfor %}
{% endfor %}
</ul>
{% else %}
<p>No posts are available.</p>
{% endif %}
{% endblock %}
You need to provide the argument input to redirect method as input_tag=input.
Example:
return redirect('fortykwords:tag_view', input_tag=input)
It's showing as /tag/?search=searched_tag because your form is submitting by GET but never getting to the redirect. It seems is_valid() is returning False.
I've tested a very similar version of your code and don't think it's a bug in tagulous, but would still be interested to know what had gone wrong (I wrote tagulous). Spotted a couple of places you can streamline your code a bit, so try::
def tag_view(request, input_tag):
# Can't see any POSTs in your example, so you can pass the form GET here
# Might also be nice to pass the original tag in so it shows in the form
form = TagSearchForm(request.GET, initial={'search': input_tag})
# The form already has the GET, so you can go straight into the is_valid
if form.is_valid():
input = form.cleaned_data['search']
print('Valid: ', input)
return redirect('fortykwords:tag_view', input)
else:
print('Invalid: ', form.errors, form.non_field_errors)
# You can remove the else for if not GET, which would never be reached
# on to pagination as before
(although fwiw I'd recommend ipdb instead of print)

Displaying None Field Errors in Django Template

I want to display my non_field_erors in my template. So far, I can display all kind of errors of my forms with:
-> base.html
{% if form.errors %}
{% for field in form %}
{% if field.errors %}
<div class="ui-state-error ui-corner-all notification" >
<p>
<span class="ui-icon ui-icon-alert"></span>
{{ field.label_tag }}:{{ field.errors|striptags }}
<a class="hide" onClick="hideBar(this)">hide</a>
</p>
</div>
{% endif %}
{% endfor%}
{% endif %}
AND
{{ form.non_field_errors }}
I've added a new form which has only an IntegerField:
class MerchantForm(forms.ModelForm):
price = forms.IntegerField(widget=forms.TextInput(attrs={'class':'small'}))
def clean_price(self):
price = self.cleaned_data.get('price')
if price == 120:
raise forms.ValidationError('error blah.')
return price
When I post price as 120, I don't get any validation errors in my page.
And my view is:
def bid(request,product_slug):
.
.
form = MerchantForm()
context = RequestContext(request,{
'form':form,
....
})
if request.method == 'POST':
form = MerchantForm(request.POST)
if form.is_valid():
return HttpResponse('ok')
# else:
# return HttpResponse(form.errors.get('__all__'))
return render_to_response('bid.html',context_instance=context)
I can retrieve the error with commented lines but I don't want to do that in views.py. Any ideas ?
Oh dear.
First of all, why are you asking about non_field_errors when the code snippet you post clearly has the error as being raised in clean_price, and therefore is associated with ths price field?
Secondly, your view code is upside down. You create an empty form instance, and add it to the context. Then you create another form instance, bound to the POST data, but don't put it into the context. So the template never sees the bound form, so naturally you never see any validation errors in the template.

How to get 'switch-case' statement functionality in Django templates?

I found a link to have a 'switch' tag in Django templates, but I was wondering if this can be somehow achieved without it. Using only the stuff which comes with Django? Basically is there other way then using multiple 'if' or 'ifequal' statements?
Thanks in advance for any tips/suggestions.
As of Django 1.4, there is {% elif %}:
{% if a %}
thing
{% elif b %}
other thing
{% elif c %}
another thing
{% endif %}
To the previous responders: Without understanding the use case, you've made assumptions and criticized the questioner. #Ber says "all over the place" which is certainly not implied by the questioner. Not fair.
I have a case where I would like to do a {% switch %} statement in exactly one place in my Django template. Not only is it not convenient to move the equivalent of the switch statement into Python code, but that would actually make both the view and the template harder to read and take simple conditional logic that belongs in one place and split it into two places.
In many cases where I could imagine a {% switch %} (or an {% if %}) being useful, not using one requires putting HTML in a view. That's a far worse sin and is why {% if %} exists in the first place. {% switch %} is no different.
Fortunately, Django is extensible and multiple people have implemented switch. Check out:
Switch template tag
from django import template
from django.template import Library, Node, VariableDoesNotExist
register = Library()
#register.tag(name="switch")
def do_switch(parser, token):
"""
The ``{% switch %}`` tag compares a variable against one or more values in
``{% case %}`` tags, and outputs the contents of the matching block. An
optional ``{% else %}`` tag sets off the default output if no matches
could be found::
{% switch result_count %}
{% case 0 %}
There are no search results.
{% case 1 %}
There is one search result.
{% else %}
Jackpot! Your search found {{ result_count }} results.
{% endswitch %}
Each ``{% case %}`` tag can take multiple values to compare the variable
against::
{% switch username %}
{% case "Jim" "Bob" "Joe" %}
Me old mate {{ username }}! How ya doin?
{% else %}
Hello {{ username }}
{% endswitch %}
"""
bits = token.contents.split()
tag_name = bits[0]
if len(bits) != 2:
raise template.TemplateSyntaxError("'%s' tag requires one argument" % tag_name)
variable = parser.compile_filter(bits[1])
class BlockTagList(object):
# This is a bit of a hack, as it embeds knowledge of the behaviour
# of Parser.parse() relating to the "parse_until" argument.
def __init__(self, *names):
self.names = set(names)
def __contains__(self, token_contents):
name = token_contents.split()[0]
return name in self.names
# Skip over everything before the first {% case %} tag
parser.parse(BlockTagList('case', 'endswitch'))
cases = []
token = parser.next_token()
got_case = False
got_else = False
while token.contents != 'endswitch':
nodelist = parser.parse(BlockTagList('case', 'else', 'endswitch'))
if got_else:
raise template.TemplateSyntaxError("'else' must be last tag in '%s'." % tag_name)
contents = token.contents.split()
token_name, token_args = contents[0], contents[1:]
if token_name == 'case':
tests = map(parser.compile_filter, token_args)
case = (tests, nodelist)
got_case = True
else:
# The {% else %} tag
case = (None, nodelist)
got_else = True
cases.append(case)
token = parser.next_token()
if not got_case:
raise template.TemplateSyntaxError("'%s' must have at least one 'case'." % tag_name)
return SwitchNode(variable, cases)
class SwitchNode(Node):
def __init__(self, variable, cases):
self.variable = variable
self.cases = cases
def __repr__(self):
return "<Switch node>"
def __iter__(self):
for tests, nodelist in self.cases:
for node in nodelist:
yield node
def get_nodes_by_type(self, nodetype):
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
for tests, nodelist in self.cases:
nodes.extend(nodelist.get_nodes_by_type(nodetype))
return nodes
def render(self, context):
try:
value_missing = False
value = self.variable.resolve(context, True)
except VariableDoesNotExist:
no_value = True
value_missing = None
for tests, nodelist in self.cases:
if tests is None:
return nodelist.render(context)
elif not value_missing:
for test in tests:
test_value = test.resolve(context, True)
if value == test_value:
return nodelist.render(context)
else:
return ""
Unfortunately, this is not possible with the default Django template engine. You'll have to write something ugly like this to emulate a switch.
{% if a %}
{{ a }}
{% else %}
{% if b %}
{{ b }}
{% else %}
{% if c %}
{{ c }}
{% else %}
{{ default }}
{% endif %}
{% endif %}
{% endif %}
or if only one if condition can be true and you don't need a default.
{% if a %}
{{ a }}
{% endif %}
{% if b %}
{{ b }}
{% endif %}
{% if c %}
{{ c }}
{% endif %}
Usually, when the template engine is not powerful enough to accomplish what you want this is a sign that the code should be moved into Django view instead of in the template. For example:
# Django view
if a:
val = a
elif b:
val = b
elif c:
val = c
else:
val = default
# Template
{{ val }}
In a very general view, the need for a switch statement is a sign that there is a need to create new classes and objects that capture the different "cases".
Then, instead of "swtich"ing all over the place, you only need to call an object method or reference an object attribute and your done.