How can I submit multiple forms using Django and JavaScript? - javascript

I'm working on an app that displays a form on the index page, into which you can enter information to eventually calculate a gross total and output other details based on the form inputs.
My form inherits from form.Forms.
A button, "Add row", creates as many copies of this row as needed.
Each form contains a form with the ID "calc-form". My problem is that only one form is included in the POST method. I would like to process all forms with one click.
How can I include all additional forms in the POST method when I click "Calculate"?
index.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% load static %}
<script type="text/javascript" src="{% static 'app.js' %}"></script>
<table id="table-id">
<tr id='table-row'>
<form id="calc-form" action="{% url 'result' %}" method="post">
{% csrf_token %}
{% for field in form %}
<td>{{ field|as_crispy_field }}</td>
{% endfor %}
</form>
</tr>
</table>
<br>
<input type="submit" value="Calculate" form="calc-form">
<button type="button" id="add-row-btn" onclick="addRow()">Add row</button>
<button type="button" id="delete-row-btn" onclick="deleteRow()">Delete last row</button>
{% endblock content %}
views.py
from django.shortcuts import render
from .forms import TariffCalcForm
def index(request):
form = TariffCalcForm
context = {'form': form}
return render(request, 'clickapp/index.html', context)
def result(request):
context = {}
if request.method == 'POST':
for r in request:
form = TariffCalcForm(request.POST)
if form.is_valid():
context['form'] = form
return render(request, 'result.html', context)
else:
return render(request, 'index.html', context)
app.js
function addRow() {
const formTable = document.getElementById('table-id');
const formRow = document.getElementById('table-row');
const formRowCopy = formRow.cloneNode(true);
formTable.appendChild(formRowCopy);
}

The form wizard may be appropriate. It allows you to progress through a sequence of forms, storing the results of submitting each in the client's session. This is of course in the database, but it can be made transient, and can be an anonymous session. The final submit can process the data from all the forms and delete it from the session (which may be automatic, not sure) before rendering a results page for the user.
There is limited capacity for skipping some forms based on the data in the previous ones.
The other approach is to go client-based. You use one huge form, but with Javascript of some sort to steer the user through it section by section. "Submit" on earlier parts does not involve the server, but just hides the sub-form and reveals the next (or switches between tabs, which the user may also do for himself). Methods can range from simple Jquery up to enormous Javascript frameworks.

Related

ModelChoiceField javascript onChange arguments

I have a Django project that has a Students model with multiple fields, and I have implemented a ModelChoiceField form which drops down and allows for selecting a particular record in the Students table.
forms.py:
class StudentChoiceField(forms.Form):
students = forms.ModelChoiceField(
queryset=Student.objects.values_list().order_by("last_name"),
empty_label="(select student)",
widget=forms.Select(attrs={"onChange":'refresh()'})
)
def __init__(self, *args, **kwargs):
super(StudentChoiceField, self).__init__(*args, **kwargs)
# without the next line label_from_instance does NOT work
self.fields['students'].queryset = Student.objects.all().order_by("last_name")
self.fields['students'].label_from_instance = lambda obj: "%s %s" % (obj.last_name, obj.first_name)
The label_from_instance method is overridden, so that the drop-down form displays just two model fields (there are eleven total in the model).
When a student is selected, I want to update some textfields in the page to display the remaining fields of the model. Currently, have implemented a javascript function refresh() which is invoked for the onChange event of the StudentChoiceField form.
index.html (all_students_choice is the StudentChoiceField form):
{% extends "base.html" %}
{% block content %}
<body>
<script>
function refresh(){
var id = document.getElementById("id_students").value;
console.log(id);
}
</script>
<div class="container">
<form method=POST action="">
{% csrf_token %}
{{ all_students_choice }}
</form>
</div>
</body>
{% endblock %}
I have confirmed through the browser console that the javascript function is getting called, and printing the value of the ModelChoiceField form. As expected, after selecting an instance from the dropdown menu the value of the form element is the primary key of the table.
I need advice on the best approach to populate the textfields which I will be adding to display the remaining Student model fields (aside from first and last name). Should these be passed as parameters to the javascript function? Is there a best way to approach this problem.
Answering this question with the approach that that was eventually used, in case it would be of assistance to someone else. Decided to render the same template, but with additional element in the context to reference the selected student. In the initial index home page, the selected_student is None:
def index(request):
....
context = {
'students_choice_ln': students_choice_ln,
'students_choice_fn': students_choice_fn,
'selected_student': None
}
return render(request, 'awards/index.html', context)
For the select function, the selected_student is passed in through the context:
def select(request):
if request.method == "GET":
...
student_id = ...
selected_student = Student.objects.get(pk=student_id)
...
context = {
...
'students_choice_ln': students_choice_ln,
'students_choice_fn': students_choice_fn,
'selected_student': selected_student,
...
}
return render(request, 'awards/index.html', context)
The template can then check whether the selected_student variable is available or not, and then accordingly display the additional fields in a separate div.
If there are any experienced web developers / django developers who see problems with this structure, perhaps they can point them out.

Python/Flask/HTML - Render HTML in new window instead of home page

I have a Python code where I'm using Flask to create a webpage. In the home page, I'm filling out a form, submitting using a button and it displays a table based on the inputs.
The problem i'm having is that once I click the button to submit the form, it renders the table on that same webpage. I would like to create a new window using JavaScript window.open() or whatever other method you might suggest to render that table inside the new window and leave the home page as it is. I tried looking around and I can't seem to get anything to work. I've read through this question and this question. But those suggestions don't seem to match what i'm looking for.
This is my code:
Python code
from flask import Flask, render_template, request,
app = Flask(__name__)
def get_table(user_input):
...
return dict //returns list of dictionaries, for example...
//dict = [{'name':'Joe','age':'25'},
// {'name':'Mike','age':'20'},
// {'name':'Chris','age':'29'}]
#app.route("/")
def home():
return render_template('home.html')
#app.route("/table", methods = ['POST'])
def table():
user_input = request.form['input']
dict_table = get_table(user_input) //return list of dictionaries
return render_template('table.html', dict_table=dict_table)
if __name__ == '__main__':
app.run(debug=True)
home.html
<!DOCTYPE html>
<html>
<head>
<title>Homepage</title>
</head>
<body>
<form action="/table" method="post">
<select name="input">
<option value="1">Input</option>
</select>
<button type="submit">Click Me!</button>
</form>
</body>
</html>
table.html
<!DOCTYPE html>
<html>
<head>
<title>Table</title>
</head>
<body>
<table id="table">
{% if dict_table %}
<tr>
{% for key in dict_table[0] %}
<th>{{ key }}</th>
{% endfor %}
</tr>
{% endif %}
{% for dict in dict_table %}
<tr>
{% for value in dict.values() %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</body>
</html>
Could someone explain in a clear way how I can click the form submit button on my homepage, stay on the homepage home.html, and make the table from table.html open up in a new window (maybe using window.open() from JavaScript or something else)?
I would appreciate it if someone could walk me through the steps on how to do this with my code provided and show me specifically where to call functions and things like that. I'm new to Flask/HTML/JS and I'm just trying to learn for personal use and I'm getting frustrated reading links and documents that show just how to display a URL like google.com in a new tab, which is not what I want. Thanks!
Step1: Check out this link that explains how to use Jquery and Ajax with FLASK
The key concept here is AJAX (asynchronous JavaScript and XML). In short, it is an architecture that makes it possible to send requests to the server in the background (called Asynchronous requests) and then modifies the content of the page currently displayed by the web browser according to the result received from the server, avoiding as well as the server does not transmit the complete page again.
Step2: A solution to your problem
First we write the routes:
from flask import Flask, render_template, request,
app = Flask(__name__)
user_input = None
def get_table(user_input):
...
return dict # returns list of dictionaries, for example...
# dict = [{'name':'Joe','age':'25'},
# {'name':'Mike','age':'20'},
# {'name':'Chris','age':'29'}]
#app.route('/')
def home():
return render_template('home.html')
#app.route('/_ajax_user_input')
def ajax_user_input():
global user_input
user_input = request.args.get('user_input', 0, type=int)
return "ok"
#app.route("/table")
def table():
x = user_input
dict_table = get_table(x) # return list of dictionaries
return render_template('table.html', dict_table=dict_table)
After we attack the templates:
home.html:
<select id="input" name="input">
<option value="1">Input</option>
</select>
<button type="button" class="test"> Click Me! </button>
<script>
$(document).ready(function(){
$('.test').bind('click', function() {
$.getJSON($SCRIPT_ROOT + '/_ajax_user_input',{
user_input: $('#input').val(),
},function() {
window.open('http://127.0.0.1:5000/table', '_blank');
});
return false;
});
});
</script>
table.html:
<table id="table">
{% if dict_table %}
<tr>
{% for key in dict_table[0] %}
<th>{{ key }}</th>
{% endfor %}
</tr>
{% endif %}
{% for dict in dict_table %}
<tr>
{% for value in dict.values() %}
<td>{{ value }}</td>
{% endfor %}
</tr>
{% endfor %}
</table>
Basically here is what happens:
when I click on my button, I call the Javascript script:
$('.test').bind('click', function() {
This sends an ajax request to FLASK, which consists in executing the ajax_user_input() function:
$.getJSON($SCRIPT_ROOT + '/_ajax_user_input',
To this function I send a data (the value selected by the user in the select tag) and this data is stored in the variable user_input:
user_input: $('#input').val(),
On the side of Flask I get the data and I store it in a global variable that I named user_input too:
global user_input
user_input = request.args.get('user_input', 0, type=int)
Then in my script I call a javascript method that allows me to open a url in a new tab (more details here):
window.open('http://127.0.0.1:5000/table', '_blank');
The 'table' route, stores in the variable x the data previously stored in my global variable (user_input), then it calls the function get_table() (by passing him the variable x in parameter) which returns a list of the dictionaries, and finally it returns the page table.html with the list of the dictionaries in parameter:
x = user_input
dict_table = get_table(x)
return render_template('table.html', dict_table=dict_table)
I hope this will help you, even though I am convinced that there are many other ways to do it, perhaps more effective.

How to pass a javascript variable to WTForm? Python/Flask

I am creating a data entry/timing web app using Python and Flask. When the user goes to the log page, they enter in the "session ID" for their task, and the time of form submission and user ID are automatically input to the database as well.
Now I am trying to use Javascript to record when the page is loaded for a task so I have a start time and an end time. I first attempted to POST the javascript variable via AJAX to the back end, but I couldn't seem to get it to work. Now I am trying to have the javascript variable be sent alongside the session ID as part of a "HiddenField". I am not yet at the point of configuring the page load time script, I am just trying to get the Javascript to talk to WTForms to talk to my database commit. Here is my code currently:
views.py
#app.route('/logpage', methods=['GET', 'POST'])
#login_required
def logpage():
form = LogForm()
if form.validate_on_submit():
timer = request.get_data("timer")
entry = LogData(sessionid=form.sessionid.data, user_id=current_user.get_id(), starttime=form.testvar.data, endtime=datetime.utcnow())
db.session.add(entry)
db.session.commit()
return redirect(url_for('home'))
return render_template('logpage.html', form=form)
models.py
class LogForm(FlaskForm):
sessionid = StringField('sessionid', validators=[InputRequired(), Length(min=16, max=16)])
testvar = HiddenField('testvar')
logpage.html
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Active Log Page{% endblock %}
{% block head %}
<!-- Custom styles for this template -->
<link href="static/css/starter-template.css" rel="stylesheet">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="{{ url_for('static', filename='jquery.js') }}">\x3C/script>')</script>
<script src="static/js/timer.js?version=25"></script>
{% endblock %}
{% block body %}
<div class="container">
<div class="form-group">
<form class="form-logpage", method="POST" action="/logpage", id="jsinput">
{{ form.hidden_tag() }}
{{ wtf.form_field(form.sessionid) }}
<div id="jsinput">
{{ wtf.form_field(form.testvar) }}
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Finish Job</button>
</form>
</div>
</div>
{% endblock %}
timer.js
var myData = "dataVariable";
document.getElementById("jsinput").value = "myData";
As you can see, I tried to use document.getElementById to put the variable in logpage.html, but when I hit submit, the starttime field is empty. I've looked around the documentation for a way to put a variable in the WTForm, but I can't find anything relevant.
Before, during some other issues it's showed as null and /x and ''b but I've done a good bit of tinkering since then and the database entry is just ''.
If there is a more elegant way to record page load datetime and record simultaneously to the database with the rest of the data, I would love to hear suggestions. Previous SO post here: Python/Flask: How to tell how long a user spends on a page? (Data entry/time log app)
Here's the solution I came up with for this problem:
Add a hidden field to the form with an id.
Grab the ID in your script and assign a value to it.
In your form.py:
from flask_wtf import FlaskForm
from wtforms import SubmitField
from wtforms import HiddenField
class MyForm(FlaskForm):
my_data = HiddenField(
'my_data',
render_kw={'id':'my_data'}
)
save = SubmitField(
'Save',
render_kw={'id':'save'}
)
create and pass your form in your view and then render the save and my_data fields in your html template using {{ form.save }} and {{ form.my_data }}
Add the following to your javascript:
var saveButton = document.getElementById('save');
saveButton.addEventListener('click', function() {
document.getElementById('my_data').value = 'MY DATA IS HERE!!';
});
you can now access your data in your python view using:
from flask import request
...some code...
request.form['my_data']
It's a late answer, but hopefully useful to someone out there...

Django-tinyMCE Submit button not working

I've implemented TinyMCE with the django-tinymce package. However, my submit button which worked fine without TinyMCE now has become rather useless since I can't submit the form, once everything is filled out.
I can use Ctrl + S inside of TinyMCE (I discovered that by accident) and everything will get submitted correctly. Also, I can use the save-button of the TinyMCE "save" plugin to submit.. Do I have to configure the submit button to make it work with TinyMCE?
Template:
{% extends 'medisearch/header.html' %}
{% load crispy_forms_tags %}
{% block header %}
{{ form.media }}
{% endblock %}
{% block content %}
▷⋅⋅⋅⋅⋅⋅⋅<form action="{{ url }}" method="post">
▷⋅⋅⋅⋅⋅⋅⋅ <div class="form-group">
▷⋅⋅⋅⋅⋅⋅⋅ {% csrf_token %}
▷⋅⋅⋅⋅⋅⋅⋅ {{ form|crispy }}
▷⋅⋅⋅⋅⋅⋅⋅ </div>
▷⋅⋅⋅⋅⋅⋅⋅ <input type="submit" class="btn btn-primary" value="Speichern" />
▷⋅⋅⋅⋅⋅⋅⋅</form>
{% endblock %}
views.py
class EntryDetail(DetailView):
model = Mediwiki
slug_field = 'non_proprietary_name'
template_name = 'mediwiki/entry.html'
class MediwikiForm(FormView):
template_name = 'mediwiki/create.html'
form_class = MediwikiForm⋅
success_url = "/" #TODO user get's redirected to page he's created⋅
def form_valid(self, form):
form.save()
return super(MediwikiForm, self).form_valid(form)
class EntryDisplay(View):
def get(self, request, *args, **kwargs):
try:
view = EntryDetail.as_view()
return view(request, *args, **kwargs)
except Http404: # If there's no entry in db:
if check_user_editor(request.user) == True:
view = MediwikiForm.as_view()
return view(request, *args, **kwargs)
else:
pass
def post(self, request, *args, **kwargs):
view = MediwikiForm.as_view()
return view(request, *args, **kwargs)⋅
forms.py
class MediwikiForm(ModelForm):
wiki_page = forms.CharField(widget=TinyMCE(attrs={'cols': 80, 'rows': 30}))
class Meta:
model = Mediwiki⋅
fields = '__all__'
TinyMCE is in urls.py and under INSTALLED_APPS..
I know it's probably too late for you, but it seems that i had the same issue, just now and my solution might help someone in the future.
You are using crispy, which includes the javascript files for the form on it's own.
Therefore the django_tinymce/init_tinymce.js will be referenced twice.
This will break the submittion of your content, since the form is initialized twice.
In order to fix this you may just remove the call of {{ form.media }}.
I had a similar issue and learned that it has to do with the way that TinyMCE deals with text areas. The following init script worked for me:
<script>
tinymce.init({
selector:'.editor',
setup: function (editor) {
editor.on('submit', function (e) {
editor.save();
});
}
});
</script>
#artifex_knowledge answers makes sense and it works.
To build up on it, besides calling editor.save() on submit (or on change), keep in mind that if users don't fill the text area, they won't be able to submit the form, but the this field is required error message won't be displayed.
This is because the text area field (in this case wiki_page) is required by default, so in the html it will be rendered with required. But TinyMCE hides the text area (and replaces it with an iframe :( ), so if you try to submit the form with an empty required, it won't, but the error message will keep hidden.
(A possible solution is to use JS to remove the required attribute and check it in django later).
Just delete required field from textarea element, which is used as editor.
Deleting the 'required' field in the textarea element solved my problem (like Krysits mentioned)
I also had the same issue as yours, and I just removed for instance: "wiki_page" charfield from the subclass of Modelform, and put Tinymce widget in the Meta class.
class MediwikiForm(ModelForm):
class Meta:
model = Mediwiki⋅
fields = '__all__'
widgets = {
'wiki_page': TinyMCE(attrs={'cols': 80, 'rows': 30})
}

How to fire a Javascript alert and then redirect the page after user clicks 'ok' in Django?

I have contact form on my Django website. When someone clicks submit, I would like to use a Javascript alert to inform them that their message has been sent and then redirect them to back to the homepage. I've setup the HttpResponseRedirect part in my view, but I'm struggling to get the JS piece working alongside. Here is my views.py:
from datetime import date
from django.http import HttpResponse, HttpResponseRedirect
from django.template import RequestContext
from django.shortcuts import render_to_response
from .form import ContactUsForm
def contact(request):
form = ContactUsForm(request.POST or None)
if form.is_valid():
this_form = form.save(commit=False)
this_form.save()
return HttpResponseRedirect('/')
return render_to_response('contact/form.html', locals(), context_instance=RequestContext(request))
In your template, you could use something as simple as this (uses some jQuery):
$(function()
{
$('form').on('submit', function(){
alert('Your message has been sent!');
});
});
Note a few things:
This is a little misleading, as it alerts the user that their message has been sent, even
if it doesn't actually get sent (it never checks with the server to make sure). So, if there was some error in the form, this would definitely mislead the user.
This alert will bind to any form on the page, so it may be better to target the specific form with its id.
So that's the quick-and-dirty solution. I'd recommend, though, using Django's messages framework instead. Basically, you'd not use any Javascript to submit the form, but instead add something like this to your view:
from django.contrib import messages
messages.success(request, 'Message sent.')
And then in your template (best to add this to your base template so it shows up wherever the user goes, since messages expire with every page-load:
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}

Categories