Problem with logic in Django template - python

Supose this portion of a Django template. regs is a list of Reg objects. Reg.editable is a BooleanField.
I want to render a radio button per element in the list. If r.editable is False, the radio button must be disabled:
{% for r in regs %}
<input type="radio" value="{{ forloop.counter }}"
{% if forloop.first %}checked="checked"{% endif %}
{% if not r.editable %}disabled="disabled"{% endif %}/>
{% endfor %}
As you can see, I'm using forloop.first to check the first radio button, but this has a problem! What about if the first element has editable == False? The first radio button will be rendered disabled and chequed. If then a user submit "the form" I'll receive a value not expected.
Am I clear with the problem here? How can I rewrite this template to render as checked the FIRST ENABLED radio button?
Thanks

Djangos templating language doesn't give you a lot in the way of logic in the template (I've heard positive things about Jinja2 if you want to change that). There's also the "Smart" {% if %} tag which adds some more functionality and incidentally is being proposed for inclusion in Django 1.2.
As for solving this problem I would most likely move the logic over to the view. (Disclaimer: don't have the time to test this code snippet but it should provide the general idea)
def my_view(request, *args, **kwargs):
# QuerySet is fetched however it's done...
regs = Regs.objects.all()
# Wrap regs in display information
checked = False
radio_buttons = []
for r in regs:
if r.editable:
if not checked:
radio_buttons.append( { 'checked':True, 'enabled':True, 'object':r } )
# Set to true once
checked = True
else:
radio_buttons.append( { 'checked':False, 'enabled':True, 'object':r } )
else:
radio_buttons.append( { 'checked':False, 'enabled':False, 'object':r } )
# Then pass in radio_buttons for the value of regs down here
render_to_whatever(..., { 'regs':radio_buttons } )
In this case we've wrapped the QuerySet which will give our template some more details about rendering. The template becomes "dumb" now.
{% for r in regs %}
<input type="radio" value="{{ forloop.counter }}"
{% if r.checked %}checked="checked"{% endif %}
{% if not r.enabled %}disabled="disabled"{% endif %}/>
{% comment %} To access the original object use: {{ r.object }} {% endcomment %}
{% endfor %}

The real answer to this question is as follows:
Such logic has no place in the template. You can preprocess the context before passing it to the template, thus eleminating the need to do this using the (intentionally) crippled template engine logic.
In my opinion, what you are doing is wrong. I mean, django has perfectly fine forms api, why render the inputs directly then? Some might argue, that django's forms api is inflexible, but for this specific need it will undoubtely be sufficient.
And to reiterate - this kind of logic does not belong in the presentation layer. So don't put it there, it will bite you. In fact it already did.

Just adjust your ifs
{% for r in regs %}
{% if forloop.first %}
<input type="radio" value="{{ forloop.counter }}" checked="checked"/>
{% else %}
{% if not r.editable %}
<input type="radio" value="{{ forloop.counter }}" disabled="disabled"/>
{% endif %}
{% endif %}
{% endfor %}
PS: Your question did not clearly explain what you wanted. I made some reasonable assumption. Update the question if what you want is something else.

Similar to T. Stone's answer of doing this logic in the view, you could just add a new template variable that indicated the first checked radio:
def my_view(request, *args, **kwargs):
regs = Regs.objects.all()
checked_index = None
for i, reg in enumerate(regs):
if reg.enabled:
checked_index = i
break
# pass checked_index into the template...
Template:
{% for r in regs %}
{% ifequal forloop.counter0 checked_index %}
<input type="radio" value="{{ forloop.counter }}" checked="checked"/>
{% else %}
<input type="radio" value="{{ forloop.counter }}" {% if not r.editable %}disabled="disabled"{% endif %}/>
{% endif %}
{% endfor %}

Similar to becomingGuru but solving your problems:
{% for r in regs %}
{% if not r.editable %}
<input type="radio" value="{{ forloop.counter }}" disabled="disabled"/>
{% else %}
{% if forloop.first %}
<input type="radio" value="{{ forloop.counter }}" checked="checked"/>
{% endif %}
{% endif %}
{% endfor %}
It first checks if r is editable, and then checks if it is the first.
Regards.

Related

Iterating over a ManytoMany field and comparing values with another iteration not working as expected

I have two models. The Course model has a many-to-many field related to the Student model.
Problem is, when I iterate through the course.student and iterate trough the students to compare if values are same, there are too many iterations.
Basically a list of students is normally displayed in my template, and when I submit the form, the Course.student field is updated by one. So when iteration starts again, there will be 2 students to compare, which will display more students per page each time. I need to find a way to display the checked students just one time, otherwise display the form.
{% for crs_stud in course.student.all %}
{% for student in students %}
{% if crs_stud == student %}
<p>{{ student.name.upper }} is already enrolled.</p>
{% else %}
<form action="." method="POST">
{% csrf_token %}
<label for="id_{{ student.id }}">{{ student.name }}</label>
<input type="checkbox" id="id_{{ student_id }}"
value="{{ student.id }}"
name="student_ids">
<input type="submit">
</form>
{% endif %}
{% endfor %}
{% endfor %}
You should only need a single loop through the students.
{% with crs_stud=course.student.all %}
{% for student in students %}
{% if student in crs_stud %}
<p>{{ student.name.upper }} is already enrolled.</p>
{% else %}
// display enrollment form
{% endif %}
{% endfor %}
{% endwith %}
The {% with %} tag isn't required here, but will prevent duplicate queries.
You might want to try to move this logic out of the template and into your view/helper functions/model methods. For example, you could pass two lists to the template, enrolled_students and unenrolled_students, then loop through both lists in the template.

How to get all the options submitted by form

How to get all the options submitted by the form in Django, this is the form which I used.
{% extends 'quiz/base.html' %}
{% block content%}
<h1>You are at quiz page</h1>
<form action="{% url 'quiz:process_data' %}" method="post">
{% csrf_token %}
{% for question in question_set %}
<h3>{{question.id}}.{{question.question_text }}</h3>
{% for option in question.options_set.all %}
<input type="radio" name="choice{{question.id}}" value="{{ option.options}}" > {{option.options}}<br>
{% endfor %}
{% endfor %}
<input type="Submit" name="Submit">
</form>
{% endblock%}
I tried selected_choice=request.POST ,but getting this as output csrfmiddlewaretokenchoice1Submitchoice3. How can I solve this? Thank you
In django request.POST is dictionary-like object, see details here.
So to obtain argument choice in view you can use following syntax:
selected_choice=request.POST.get('choice')
which return choice value or None in case this is empty.
Since request.POST is dict-like object you can use items() method to get all values and filter them:
for k, v in request.POST.items():
if k.startswith('choice'):
print(k, v)
this will print only those params with choice text in name.
selected_choice=request.POST.get('choice')
The above should work just fine, but if you're crazy, you can try this:
{% extends 'quiz/base.html' %}
{% block content%}
<h1>You are at quiz page</h1>
<form action="{% url 'quiz:process_data' %}" method="post" id= "crazyform">
{% csrf_token %}
{% for question in question_set %}
<h3>{{question.id}}.{{question.question_text }}</h3>
{% for option in question.options_set.all %}
<input type="radio" name="choice" value="{{ option.options}}" > {{option.options}}<br>
{% endfor %}
{% endfor %}
<input type="hidden" name="crazychoice" class="crazy" value="nothing">
<input type="Submit" name="Submit">
</form>
{% endblock%}
Then some JQuery:
$('#crazyform input').on('change', function() {
$(".crazy").val($('input[name=choice]:checked', '#crazyform').val())})
Everytime you click a radio button, the value of the hidden input field changes to the value of the selected radio button.
Then in your view you can:
selected_choice = request.POST.get("crazychoice", "")

Django Template to make a list to be sent as post

I am trying to make an MCQ quiz and I have used the following code in a template:
{% for MCQ in mcq %}
<input type="radio" name="MCQ" value="{{ MCQ.id }}">{{ MCQ.MCQ_Text }}
{% endfor %}
The problem is that I want to use MCQ_list = request.POST[MCQ] to access it as a list. How do I make a list in the template? Alternatively, is there a better way to approach this?
Edit:
The mcq is nested inside
{% for Questions, mcq in Quest_dic.items %}
<br>
{{ Questions.Question_Text }}
{% for MCQ in mcq %}
<br>
<input type="radio" name="ListOrAlternateHere" value="{{ MCQ.id }}">{{ MCQ.MCQ_Text }}
{% endfor %}
<hr>
{% endfor %}
change your input to type=checkbox. This will let you read the checked items as a list in python:
mcq_list = request.POST.getlist('MCQ')

Learning to use Jinja2 templates, missing URL values

I'm learning how to use Jinja2 templates (see code below). When I add an item to my form, I expect that the url will change to something like this.
http://localhost:8080/?food=steak&food=eggs&food=cheese
However, what ends up happening is that the first food will have a value, but everything else will be blank. It looks something like this:
http://localhost:8080/?food=asd&food=&food=
What am I doing wrong?
<form>
<h2>Add a Food</h2>
<input type="text" name="food">
{% if items %}
{% for items in items %}
<input type="hidden" name="food" value="{{item}}">
{% endfor %}
{% endif %}
<button>Add</button>
{% if items %}
<br>
<br>
<h2>Shopping List</h2>
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% endif %}
</form>
Here is the function to render the HTML:
class MainPage(Handler):
def get(self):
items = self.request.get_all("food")
self.render("shopping_list.html", items=items)
For item in items?
{% for item in items %}
<input type="hidden" name="food" value="{{item}}">
{% endfor %}
I'm not certain, but I think the issue is with your <button> tag. Try something like <button type='submit'>. I think your current button just doesn't do anything.
w3 documentation here

How can I use break and continue in Django templates?

I want to put break and continue in my code, but it doesn't work in Django template. How can I use continue and break using Django template for loop. Here is an example:
{% for i in i_range %}
{% for frequency in patient_meds.frequency %}
{% ifequal frequency i %}
<td class="nopad"><input type="checkbox" name="frequency-1" value="{{ i }}" checked/> {{ i }} AM</td>
{{ forloop.parentloop|continue }} ////// It doesn't work
{ continue } ////// It also doesn't work
{% endifequal %}
{% endfor%}
<td class="nopad"><input type="checkbox" name="frequency-1" value="{{ i }}"/> {{ i }} AM</td>
{% endfor %}
Django doesn't support it naturally.
You can implement forloop|continue and forloop|break with custom filters.
http://djangosnippets.org/snippets/2093/
For-loops in Django templates are different from plain Python for-loops, so continue and break will not work in them. See for yourself in the Django docs, there are no break or continue template tags. Given the overall position of Keep-It-Simple-Stupid in Django template syntax, you will probably have to find another way to accomplish what you need.
For most of cases there is no need for custom templatetags, it's easy:
continue:
{% for each in iterable %}
{% if conditions_for_continue %}
<!-- continue -->
{% else %}
... code ..
{% endif %}
{% endfor %}
break use the same idea, but with the wider scope:
{% set stop_loop="" %}
{% for each in iterable %}
{% if stop_loop %}{% else %}
... code ..
under some condition {% set stop_loop="true" %}
... code ..
{% endif %}
{% endfor %}
if you accept iterating more than needed.
If you want a continue/break after certain conditions, I use the following Simple Tag as follows with "Vanilla" Django 3.2.5:
#register.simple_tag
def define(val=None):
return val
Then you can use it as any variable in the template
{% define True as continue %}
{% for u in queryset %}
{% if continue %}
{% if u.status.description == 'Passed' %}
<td>Passed</td>
{% define False as continue %}
{% endif %}
{% endif %}
{% endfor %}
Extremely useful for any type of variable you want to re-use on template without using with statements.

Categories

Resources