Django form with Dynamic fields. Invalid but no errors - python

I have been working with a Django form that can generate a number of fields dynamically according to the parameters passed in, as I have learnt from SO, like this:
class Review_Form(forms.Form):
def __init__(self, *args, **kwargs):
sentences = kwargs.pop('sentences')
super(Review_Form, self).__init__(*args, **kwargs)
counter = 1
for q in sentences:
self.fields['review' + str(counter)] = forms.CharField(label='Review' + str(counter), required=False)
counter += 1
As for the corresponding html, I pass the each field separately into contest['block'], where block is a list of dictionaries, with key value pair indicating the ith field of the form and the corresponding items I need.
So the html is as following:
<form action="{% url 'done_review' title%}" method="post">
<div class="container">
<div class="row">
{% for b in block %}
<div class="8u">
<p> {{b.sent}} </p>
</div>
<div class="4u">
<input class="front_form-control" value="{{b.text}}" type={{b.field}}
</div>
</div>
{% csrf_token %}
{% endfor %}
</div>
<div class="container">
<div class="row">
<div class="12u">
<button class="btn btn-lg btn-info" type="submit" value="Submit">Done Review
</button>
</div>
</div>
</div>
{% csrf_token %}
</form>
Then, unfortunately, the form is not valid after I submit it. I tried to test it by printing the errors in views.py, like the following:
if form.is_valid():
# do something
else:
print form.errors, 'here1'
print form.non_field_errors(), 'here2'
field_errors = [(field.label, field.errors) for field in form]
print field_errors, 'here3'
It prints out like this:
here1
here2
[('Review1', []), ('Review2', []), ...many more... ('Review38', [])] here3
I really don't understand why the form is not valid and I have got stuck here for days and googled everywhere. Hope someone could help me here.
Many thanks!!!

Finally, the problem is solved.
This happens because the form is unbound, because of one of my careless mistakes:
form = Review_Form(sentences=sents)
instead of what it should be:
form = Review_Form(request.POST, sentences=sents)
I answered this because I think this is probably useful for other new developers like me.
Read more on unbound and bound forms, if you like, from here: Django form API

Related

Create a preview screen in Django

I have a Django form that receives a text (that I copy from Google Classroom: a bunch of student comments). I use these comments to make student's attendance. What I want to achieve is:
Accessing /insertion/ url via GET user receive the page form as a response, to choose the class (class01, class02, etc) and to past the text
When the user clicks on submit in this form (post method), it is redirect to the same /insertion/ url, but now the form is bound to the data submited, and the page shows a preview page (based on a boolean variable I'm passing through context), showing what students are present and what are absent based on the text informed. At that page, a new submit button will be shown below a text like "if everything's ok, hit the ok button".
After click this ok button, a pdf will be generated and the user will be redirected to /files/ url, to see the generated pdf and previous generated pdf.
views.py
def insertion(request):
context = {}
if request.method == 'GET':
form = AttendanceDataForm()
context.update({"form": form})
if request.method == 'POST':
form = AttendanceDataForm(request.POST)
context.update({"form": form})
if form.is_valid():
lesson = form.cleaned_data['lesson']
raw_text = form.cleaned_data['raw_text']
# Get course students
course_students = md.Student.objects.filter(course_id=lesson.course_id)
# Get present students based on raw text informed
present_students = [s for s in course_students if s.full_name in raw_text]
# Get absent students based on raw text informed
absent_students = [s for s in course_students if s.full_name not in raw_text]
context.update({
"present_students": present_students,
"absent_students": absent_students,
"render_preview": True
})
context.update({"active_freq": True})
return render(request, 'core/insertion.html', context)
def files(request):
context = {}
if request.method == 'POST':
# How can I access all expensive calculation I did in the previous view?
context.update({"active_gen": True})
return render(request, "core/files.html", context)
insertion.html
<div class="row">
<div class="col-12 col-md-6">
<h3>Informar FrequĂȘncia</h3>
{% crispy form %}
</div>
<div class="col-12 col-md-6">
{% if render_preview %}
<div class="container">
<div class="row p-4 bg-white rounded mt-4">
<div class="col-12 col-sm-6">
<h5>Alunos presentes</h5>
<ul class="previewer-list">
{% for student in present_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
<div class="col-12 col-sm-6">
<h5>Alunos ausentes</h5>
<ul class="previewer-list">
{% for student in absent_students %}
<li>{{ student.id }} - {{ student.full_name }}</li>
{% endfor %}
</ul>
</div>
</div>
<p class="mt-3">If everything's ok, hit the OK button</p>
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<button type="submit" class="btn btn-primary">OK</button>
</form>
</div>
{% endif %}
</div>
</div>
I could get to implement 1 and 2, but 3 is a mistery right now. What I couldn't get is how I can access the expensive calculations I did in insertion view in the files view. How can I do that?
Here's a solution using session framework.
We'll save the calculations in the session and access those values in another view later.
For starters, we'll just save the ids (pk) of the students instead of the student instances because they are not JSON serializable [See note below].
def insertion(request):
# do expensive calucations ...
present_ids = [s.pk for s in present_students]
absent_ids = [s.pk for s in absent_students]
request.session['attendance_data'] = {
'present_ids': present_ids,
'absent_ids': absent_ids
}
def files(request):
attendance_data = request.session.get('attendance_data')
if not attendance_data:
# show error or something else ...
pass
present_students = md.Student.objects.filter(
pk__in=attendance_data['present_ids']
)
absent_students = md.Student.objects.filter(
pk__in=attendance_data['absent_ids']
)
# generate the pdf ...
Note: If you wish, you can also save the student instances in the session but you'll have to change the SESSION_SERIALIZER setting to use the PickleSerializer. See notes about session serialization.
You could submit the primary keys as form data in hidden fields. Just choose an appropriate delimiter based on your primary key (for example, don't delimit with a hyphen if you use a GUID primary key).
<form method="post" action="{% url "core:files" %}">
{% csrf_token %}
<input type="hidden"
name="present"
value="{% for s in present_students %}{{ s.pk }},{% endfor %}"
>
<input type="hidden"
name="absent"
value="{% for s in absent_students %}{{ s.pk }},{% endfor %}"
>
<button type="submit" class="btn btn-primary">OK</button>
</form>
Then in the view you can pick up the PKs in the view from the form data then request.
def files(request):
context = {}
if request.method == 'POST':
present_pks = request.POST.pop('present').split(',')[:-1]
absent_pks = request.POST.pop('absent').split(',')[:-1]
# do type conversions if needed
...
# Because we already have the pks separated, we can combine them
# for the query in order to do just 1 query
course_students = md.Student.objects.filter(pk__in=present_pks + absent_pks).all()
absent_students = []
present_students = []
for student in course_students:
if student.pk in absent_pks:
absent_students.append(student)
else:
present_students.append(student)

How to dynamically add input fields in form for FieldList [Flask]?

I'm designing a website with a form that allows the user to input stuff and add more input fields, it sort of works like those inputfields that you enter your skills with, soi you can add more skills if you have more. But I'm struggling to display more than one input fields with FieldList as it only shows the number of min_entries I set it, even when I copy and paste the html code it only accepts the first one.
forms.py (please focus on the contents for now)
class ExerciseForm(FlaskForm):
exercise = StringField(validators=[DataRequired()])
testcase = StringField(validators=[DataRequired()])
class AddTopicForm(FlaskForm):
topicname = StringField('Topic Name', validators=[DataRequired(), Length(min=2, max=100)])
contents = FieldList(StringField(validators=[DataRequired()]), min_entries=1, max_entries=20)
exercises = FieldList(FormField(ExerciseForm), min_entries=0, max_entries=10)
submit = SubmitField('Add Topic')
html (I have no idea how to add one, and how to make it work)
<div class="addtopicform">
<form method="POST" action="">
{{ form.hidden_tag() }}
<fieldset>
<div class="form-group">
{{ form.topicname }}
</div>
<div class="form-group">
{{ form.contents }}
</div>
<button type="button" onclick="addsection()">Add Section</button>
<script>
</script>
<div class="form-group">
{% for field in form.exercises %}
{{ field }}
{% endfor %}
</div>
<div class="form-group form-btn">
{{ form.submit(class="btn") }}
</div>
</fieldset>
</form>
</div>
website.py (The best result is one data in contents, but obviously I want more)
#app.route("/addtopic/", methods=['GET', 'POST'])
def addtopic():
if 'AccountID' in session:
form = AddTopicForm()
if session["Type"] == "teacher":
if request.method == 'POST' and form.validate_on_submit():
print("success")
topicname = request.form['topicname']
contents = form.contents.data
exercises = form.exercises.data
print(topicname)
print(contents)
print(exercises)
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
return render_template('addtopic.html', user=session['Name'], acctype=session["Type"], form=form)
else:
flash("Only teachers can access to this page", "failure")
return render_template('course.html', user=session['Name'], acctype=session["Type"])
else:
flash('An account is needed to access courses', 'failure')
return redirect(url_for('login'))
PLease help me, I couldnt find any useful resources online as all of them assumed I already have a list or is using the input element instead of flask element, which doesnt work at all with flask forms

Django and Pymongo - Take multiple inputs and update a single field in all the objects

Update multiple documents in django-mongodb with user inputs
I have a form which is meant to update all the price attribute of the objects in the product_details collection of my mongoDB.It is like Bulk price updating feature.I have tried few but finding it difficult.
Please suggest the method to do so in django.
How can I update the price of multiple products using the same form and view?
price.html
<form class="col s12" action="{% url "bulk" %}" method="POST">{% csrf_token %}
<button class="btn waves-effect waves-light" type="submit" name="action">Update<i class="material-icons right">cloud</i>
</button>
{% for r in result %}
<div class="col s6 m7">
<div class="card horizontal">
<div class="card-image" >
<img class ="materialboxed" width="650" src="{{r.ppro}}" style="width:100px;
height:150px;
max-width:100%;
max-height:100%;" >
</div>
<div class="card-stacked">
<div class="card-content">
<p style="font-size:15px;">{{r.ptitle}}<br>{{r.price}}</p>
</div>
<div class="card-action">
<div class="input-field col s4">
<input id="input_text" type="text" name=price value="{{r.price}}" data-length="10">
<label for="input_text">Price</label>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</form>
</div>
views.py
def bulk_price(request):
product_list= user_db.product_details.find({"shop_id":request.session["_id"]})
user_data = user_db.store_details.find_one({"_id":request.session["_id"]})
if product_list is not None:
return render(request,'price.html',{'result':product_list,'status':user_data['status']})
return render(request,'price.html')
mongoDB structure of product_details object
First of all, your input field name should be unique, you can use product id as name -
<input id="input_text" type="text" name="{{r.object_id}}" value="{{r.price}}" data-length="10">
Now, in the Django view, you can iterate through all the post data received from the form and update the database. Here is the code that should work -
def bulk_price(request):
#If request method is POST, update the database
if request.method == "POST":
for key in request.POST: #Iterate through POST variables
value = request.POST[key]
try:
objectId = ObjectId(key)
except Exeption as e:
#The key is not a valid object id, it might be csrfmiddlewaretoken or some other post data
continue
user_db.product_details.update_one(
{'_id': objectId},
{'$set': {'price': value}},
upsert=False
)
#Render the update price page
product_list= user_db.product_details.find({"shop_id":request.session["_id"]})
user_data = user_db.store_details.find_one({"_id":request.session["_id"]})
if product_list is not None:
return render(request,'price.html',{'result':product_list,'status':user_data['status']})
return render(request,'price.html')
Don't forget to import ObjectId():
from bson.objectid import ObjectId
Note: To use MongoDB ObjectID in Django template, you will need a custom template filter. Refer to this answer - https://stackoverflow.com/a/24936772/8039601

Longitude Latitude on Flask WTF Forms

I am trying to get the Flask WTF form to read longitude and latitude, but I can't find the proper form field for it. I found some examples where FloatField is used successfully, but the example are really old and I presume there have been changes in the WTF forms.
Here is the Form class I am using:
class GetFieldForm(Form):
field_name = StringField(Designation')
field_latitude = FloatField(u'Latitude', default=-30, validators=[InputRequired()], description='48.182601')
field_longitude = FloatField(u'Longitude', default=150,
validators=[InputRequired()], description='11.304939')
submit = SubmitField(u'Signup')
Passing the values from the form to the Google Maps:
#frontend.route('/demo')
def demo():
field_form = GetFieldForm()
if field_form.validate_on_submit():
flash('Hello, {}. You have successfully signed up'
.format(escape(field_form.name.data)))
field_latitude = field_form.field_latitude.data
field_longitude = field_form.field_longitude.data
mymap = Map(
identifier="view-side",
lat=field_latitude,
lng=field_longitude,
zoom=18,
style="height:720px;width:720px;margin:0;", # hardcoded!
markers=[(field_latitude, field_longitude)],
maptype='SATELLITE'
)
return render_template('demo.html', mymap=mymap, field_form=field_form)
With the following Jinja2 template:
<div class="container-fluid">
<div class="row-fluid">
<div id="map" class="col-md-7">
{{mymap.html}}
</div>
<div class="col-md-5">
<div class="panel panel-default">
<!-- Panel Title -->
<div class="panel-heading">
<h2 class="panel-title text-center">Locate Your Field <span class="glyphicon glyphicon-map-marker"></span></h2>
<div class="panel-body">
{% if request.args.get('legacy') -%}
{{wtf.quick_form(field_form, novalidate=True)}}
{%- else -%}
{{field_form|render_form()}}
{%- endif %}
</div>
</div>
</div>
</div>
The problem is, that the form doesn't allow me to pass longitude and latitude as floats:
Versions I am using:
Flask 0.12
Flask-WTF 0.14.2
Flask-GoogleMaps https://github.com/rochacbruno/Flask-GoogleMaps
EDIT: Got a hint that I the error may have something to do with FLASK-Bootstrap>
The problem is Flask-Bootstrap, that you seem to be using.
https://github.com/mbr/flask-bootstrap/blob/master/flask_bootstrap/forms.py#L97-L99
It overrides the float type to use "number" as input type, which is
not quite right there, since "number" in html5 is limited to integers
if the step argument is not provided. So you have to set the step
attribute on your input field, though I don't know how to do that with
Flask-Bootstrap. Preferably something like step="0.000001" should
suffice.
But how would I pass the step parameter is beyond me...
Found a solution.
Instead of generating the form like this:
{{field_form|render_form()}}
do this:
{{wtf.quick_form(field_form, novalidate=True)}}

Display form input with Django

So basically I want to make a simple form I can enter text and the after I hit submit, see the text.
Here is my forms.py:
class Search(forms.Form):
search = forms.CharField()
Here is my views.py:
def search(request):
context = RequestContext(request)
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
ticker = search.save()
ticker.save()
success = True
else:
print search.errors
else:
search = Search()
return render_to_response('ui/search.html', {"search":search}, context)
Here is the html form that you use to type in (I'm using bootstrap for styling purposes):
<form class="navbar-form navbar-right" role="search" action="/search/" method="post" name="tick">
{% csrf_token %}
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter stock symbol">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
And finally, I want the text entered in the form to be displayed on "search.html" which looks like this currently:
{% extends 'ui/base.html' %}
{% block title %} search {% endblock %}
{% block body_block %}
<br>
<p>test</p>
{{ form.search.data }} <!--I'm pretty sure this is not correct -->
{% endblock %}
Anyone know how I can do this? Thanks.
Your form name is search.
To render the value with modern django, you need to call the value method of the field, therefore your template should look like the following:
{{ search.search.value }}
Your template is wrong, as you suspect.
It is looking for a context variable named "form", but you have given it a context dictionary with a key named "search".
Also, "data" is the argument that you use to build up your Search object (correctly), but when you want to extract the user's input from it, you should use the field names instead, and you need to call value() on them in order to get the bound value. So, to get the contents of the text field called search, you should use search.search.value.
Try changing the line
{{ form.search.data }}
to
{{ search.search.value }}

Categories