Django and Jinja2 randomize form fields display - python

I have the following situation:
forms.py
REASONS = [
{'code': 1, 'reason': 'I want to unsubscribe'},
{'code': 2, 'reason': 'I hate this site'}]
Myform(forms.Form):
magic_field = forms.CharField(required=True)
def __init__(self):
# Depending on the REASONS list add the fields to the form
for key in REASONS:
self.fields['reason_{}'.format(key['code'])] = forms.BooleanField(
label=_(key['reason']),
widget=widgets.CheckboxInput())
What I want is to have the order the reasons rendered in randomized order.
template.html
<form method="POST" action="{% url unsubscribe %}">
{% if some_event %}
{{ form.magic_field }}
{% endif %}
{{ form.reason_1 }} # <-- randomize this order
{{ form.reason_2 }} # <-- randomize this order
</form>

why dont you shuffle the REASONS first and then use a {% for %} loop in the template?
Something like:
REASONS = [
{'code': 1, 'reason': 'I want to unsubscribe'},
{'code': 2, 'reason': 'I hate this site'}]
Myform(forms.Form):
def __init__(self):
random.shuffle(REASONS) # use some magic method to shuffle here
for key in REASONS:
...
<form method="POST" action="{% url unsubscribe %}">
{% for field in form %} #cf https://docs.djangoproject.com/en/dev/topics/forms/#looping-over-the-form-s-fields
{{ field }}
{% endfor %}
</form>
Hope this helps
Edit
You could create a filter or a function (i'd use a function) something like
{% if some_event %}
{{ form.magic_field }}
{% endif %}
{% for field in form %}
{% if is_reason_field(field) %}
{{ field }}
{% endif %}
{% endfor %}
on your helpers.py something like: (I don't know how exactly do this)
#register.function
def is_reason_field(field):
# i'm not sure if field.name exists, you should inspect the field attributes
return field.name.startswith("reason_"):
hmm now that I see it, you could do this directly in the template since you are using jinja2

Related

Django Validation Error raised but not displayed for shopping cart app

I am running Django 2.2 and have written a simple shopping cart. I wish to validate two fields at the same time in such a way that both cannot be empty at the same time. In my forms.py,
from django import forms
class CartAddProductForm(forms.Form):
cc_handle = forms.CharField(required=False, label='CC Handle', empty_value='')
lc_handle = forms.CharField(required=False, label='LC Handle', empty_value='')
def clean(self):
cleaned_data = super().clean()
if cleaned_data.get('cc_handle') == '' and cleaned_data.get('lc_handle') == '':
print("issue detected")
raise forms.ValidationError('Either cc or lc handle is required.')
return cleaned_data
This is following the official Django docs on cleaning and validating fields that depend on each other. The print() statement above lets me know that the issue has been detected, i.e. both fields are empty. Running the Django server, I see that the issue was indeed detected but no validation error message was displayed on top of the originating page. The originating page is the product page that contains the product and a link to add the product to the shopping cart. Normally the validation error message is displayed at the top of the page.
According to the docs, the validation is done when is_valid() is called. So I put a diagnostic print of my views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from shop.models import Product
from .cart import Cart
from .forms import CartAddProductForm
#require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
cart.add(product=product,
cc_handle=cd['cc_handle'],
lc_handle=cd['lc_handle'])
else:
print('invalid form')
return redirect('cart:cart_detail')
And indeed the words 'invalid form' popped up. The code then takes me to the shopping cart. Instead, what I want is to be at the product page and show the validation error informing the reader that both fields cannot be empty. Is there a simple way of doing it?
For required=True fields in the forms, if I leave it blank, there will be a message popping up saying that I need to fill it in. So I want to do something similar except the validation requires that both fields cannot be empty.
This is different from this Stackoverflow answer because that is a registration form. You can redirect it to the same form whereas for this case, the CartAddProductForm is embedded in all the products page on the site. If possible, I want the validation to occur at the same stage as the field with required=True option.
The product/detail.html template looks like the following.
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{{ product.name }}
{% endblock %}
{% block content %}
<div class="product-detail">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
<h1>{{ product.name }}</h1>
<h2>{{ product.category }}</h2>
<p class="price">${{ product.price }}</p>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<input type="submit" value="Add to cart">
</form>
{{ product.description|linebreaks }}
</div>
{% endblock %}
Adding this line in form template has cleared your issue.
{{ cart_product_form.non_field_errors }}
product/detail.html:
{% extends "shop/base.html" %}
{% load static %}
{% block title %}
{{ product.name }}
{% endblock %}
{% block content %}
<div class="product-detail">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static "img/no_image.png" %}{% endif %}">
<h1>{{ product.name }}</h1>
<h2>{{ product.category }}</h2>
<p class="price">${{ product.price }}</p>
<form action="{% url "cart:cart_add" product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
{{ cart_product_form.non_field_errors }} // This line will raise validation errors
<input type="submit" value="Add to cart">
</form>
{{ product.description|linebreaks }}
</div>
{% endblock %}
Doc:(Copied from official documentation)
Note that any errors raised by your Form.clean() override will not be
associated with any field in particular. They go into a special
“field” (called all), which you can access via the
non_field_errors() method if you need to. If you want to attach errors
to a specific field in the form, you need to call add_error().
Your view is inconditionnally redirecting to cart_details so no surprise you don't see the validation errors - you'd have to render the invalid form for this. You should only redirect when the post succeeded.

WTForms render_field() method reference for-loop variables

I'm using Flask to create a web app. I'm trying to create a form using Flask-WTForms by iterating through a list passed in the render_template() method. However, I can't reference the variable in the for-loop inside the template.
View
class FormExample(Form):
category1 = StringField("Category 1")
category2 = StringField("Category 2")
categories = ['category1', 'category2']
def form():
form = FormExample(request.form)
return_template("form.html", categories=categories, form=form)
_formhelpers.html (suggested to use under the docs)
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
{% for error in field.errors %}
{{ error }}
{% endfor %}
{% endif %}
</dd>
{% endmacro %}
Template (form.html)
<form method="POST">
{% for category in categories %}
{{render_field(form.category)}}
{% endfor %}
</form>
When trying to reference form.category in form.html I'm given the following error through the Flask debugger:
jinja2.exceptions.UndefinedError: '__main__.EvaluateCaseForm object' has no attribute 'category'
I've already looked at the official documentation here and couldn't find the answer. I've also tried referencing {{render_field({{ form.category }})}}, {{render_field(form.{{category}})}}, and {{render_field({% form.category %})}}
Is there a way to reference the for-loop variable category inside the render_field() method?
WTForms uses the __getitem__ protocol to allow fields can be accessed like dictionary values, for example form[fieldname].
So in your code, replace form.category with form[category]:
<form method="POST">
{% for category in categories %}
{{ render_field(form[category]) }}
{% endfor %}
</form>
Is there a way to reference the for-loop variable category inside the render_field() method?
Yup:
{% for category in categories %}
{{render_field(category)}}
{% endfor %}

Django - dictionary key and value not displaying in template

I'm trying to build a simple blog with Django and am trying to display blog posts and the number of comments on associated with a post. Unfortunately, I'm running into trouble getting my dictionary to print out values--or, at least where I want to.
In my views.h file:
class IndexView(generic.TemplateView):
template_name = 'blogs/index.html'
num_comments = { }
def get_blogs(self):
"""
Returns the last 5 published blog posts.
"""
blogs = BlogPost.objects.filter(
pub_date__lte = timezone.now()
).order_by('-pub_date')[:5]
for blog in blogs:
# Setting our num_comments dictionary by getting
# the number of comments from a particular blog post
self.num_comments[blog.id] = len(Comment.objects.filter(blog_post = blog.id))
return blogs
In my index.html file:
{{ view.num_comments }}
{% if view.get_blogs %}
{% for blog in view.get_blogs %}
<div>
<h1>{{ blog.post_title }}</h1>
<p>{{ blog.post_text }}</p>
<ul>
{{ blog.id }}
{{ view.num_comments }}
{% for key, value in view.num_comments %}
<li>
{{ key }} <-- Does not display
{{ value }} <-- Does not display
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
{% else %}
<p>No blogs are available.</p>
{% endif %}
Where I explicitly call {{ view.num_comments }}, the correct dictionary is being displayed. Any idea why my dictionary isn't getting the key and value pair correctly? Thanks.
You should use .items() method to access the key and value pairs:
{% for key, value in view.num_comments.items %}
Also see the third example in Django documentation.

How to compare django object and string in templates

i want to know how to compare an object with a string in templates. Do in need to do it in views.py or in my html templates.
my views.py
permission = permission_group_sas.objects.all()
context_dict = {
'permission':permission,
# 'groups':groups,
}
return render_to_response(template, RequestContext(request, context_dict))
my html
{% for user in user.groups.all %}
{% for value in permission %}
<h4>1. {{ user.name }}
<p>2. {{ value.group }}</p></h4>
{% if value.group == user.name %}
OK!!
{% endif%}
{% endfor %}
{% endfor %}
value.group is an object and user.name is a string. So the word OK!! does not appear in my screen What is the correct way to do it. Thank you.

Django Multiple File upload failing

I have a single HTML page with 2 Django Forms in it, all in the same <form>..</form> tag. Everything works great, except when I try to upload multiple files.
Each form has their own image, and for some reason I can only save the image from the first form. Other data from the second form still gets saved, but without any image. I don't see any errors or exception raised, so I don't know what's going wrong :s.
Here's my views.py
def display_form(request):
if request.method == 'POST':
form_team = TeamForm(request.POST, request.FILES, prefix="team")
form_player = PlayerForm(request.POST, request.FILES, prefix="play")
#form_ketua = KetuaForm(request.POST, request.FILES, prefix="ketua")
if all([form.is_valid() for form in [form_team, form_player]]):
# save Team data first, overwrite if exists
try:
team = Team.objects.get(kota=form_Team.cleaned_data['name'])
team.profil = form_Team.cleaned_data['profil']
team.save()
except Team.DoesNotExist:
team = Team(**form_Team.cleaned_data)
team.save()
play = form_Player.save(commit=False)
play.name = team
play.save()
else:
form_team = TeamForm(prefix="team")
form_player = PlayerForm(prefix="play")
#form_ketua = KetuaForm(prefix="ketua")
print "a"
# list with tuple (form, legend) to pass as context
forms = [(form_Team, 'Team Data'),
(form_Player, 'Player Profile'),
]
return render_to_response(
'form/team.html',
{
'formlist': forms,
},
)
What am I doing wrong?
EDIT: Here's my template
{% extends "base.html" %}
{% block title %}Form - {{ title }}{% endblock %}
{% block content %}
<form action="." method="POST" enctype="multipart/form-data">{% csrf_token %}
{% for formitem in formlist %}
{% if formitem.1 %}
<fieldset>
<legend>{{ formitem.1 }}</legend>
{% endif %}
{{ formitem.0.non_field_errors }}
{% for field in formitem.0.visible_fields %}
<div class="formfield">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
{% if formitem.1 %}
</fieldset>
{% endif %}
{% endfor %}
<div id="formbuttons">
<input type="submit" value="Submit" class="button">
<input type="reset" value="Reset" class="button">
</div>
</form>
{% endblock %}
looks like you are missing a play.save() (you save the form with commit=False)

Categories

Resources