Flask: remembering variables + DOM manipulation throughout application - javascript

I'm developing a Flask application that contains a ton of inputs. Some the inputs depend on other inputs so there is some DOM manipulation to expose these dependent inputs. I would like for the application to remember the DOM state when the user goes back so the user doesn't have to reenter everything. What's the best way to do this?
Below demonstrates what I think should work. I have an input box that takes a number. There is also a button that adds the same input field to the DOM. When the user clicks submit, these inputs are appended to input_list and the list is displayed on the UI. Because input_list is a global variable, the application will remember these inputs and their values (even if I go back in the browser or reload the page) - this is what I'm looking for.
Again, I'm not sure if this is the best way to do it. I know flask.g can be used to store application globals but I'm not sure this is the right use case for that (I've only seen that used to store database connections). I've also heard cookies may be useful for remembering changes in the DOM. Any thoughts/examples would be helpful and if anyone thinks my way is okay, I'd appreciate feedback on my code below.
app.py:
#app.route('/input_page', methods=['GET', 'POST'])
def input_page():
global input_list
input_list = []
if request.method == 'POST':
if request.form.get('empty_list'):
input_list = []
elif request.form.get('submit_list'):
input_list = [int(i) for i in request.form.getlist('x')]
return render_template('input_page.html', input_list=input_list)
input_page.html:
<p>{{ input_list }}</p>
<form method="POST" action="/input_page">
<div id="input_container">
{% if input_list %}
{% for i in input_list %}
<input type="number" name="x" value="{{ input_list[loop.index0] }}">
{% endfor %}
{% else %}
<input type="number" name="x">
{% endif %}
</div>
<button type="button" id="add_input">Add Input</button>
<input type="submit" value="Submit List" name="submit_list">
<input type="submit" value="Empty List" name="empty_list">
</form>
<script>
$(document).ready(function(){
$('#add_input').click(function(){
var $new_input = $('<input>', {type: 'number', name: 'x'});
$('#input_container').append($new_input);
});
})
</script>

You seem to be on the right track. However, I would shy away from a global variable because that essentially exposes the variable to other concurrent clients. I would however, recommend using the Flask session object:
from flask import Flask, session, ... # other flask modules you already have imported
Then in your Python code, you'd use:
def input_page():
if request.method == 'POST':
if request.form.get('empty_list'):
session['input_list'] = []
elif request.form.get('submit_list'):
session['input_list'] = [int(i) for i in request.form.getlist('x')]
input_list = session.get('input_list', False) # create an input_list var that either grabs the 'input_list' object from the session object or defaults to False if no key found
return render_template('input_page.html', input_list=input_list)

Related

How to deal with dependable dropdowns in Django

I am trying to create a dependable dropdown on Django but since my JavaScript/ajax knowledge is not great, I have hit rock bottom. Note: I have read previous questions on this matter but none of them fully solved my problem.
Problem Description:
Due to my database size, I am retrieving partial data from the server whenever a view is requested. This makes my job of using forms harder since I am using the username of the user to filter my server. Here is a simplified version of my code.
urls.py
urlpatterns = [
url(r'^SpecificVessel', views.SpecificVessel, name="goSpecificVessel"),
]
views.py
#login_required
def SpecificVessel(request):
#Get the username to filter the tables from SQL Server:
username = None
if request.user.is_authenticated:
username = request.user.username
#Get the shipnames.
cursor.execute("select distinct SHIPNAME from Table where [GROUP]=" + "'" + username + "'")
row = cursor.fetchall()
df_listofships = pd.DataFrame(data=row, columns=['SHIPNAME'])
shipnames = list(df_listofships['SHIPNAME'].tolist()) # LIST FOR SHIP SELECTION
#Get All the data from database.
cursor.execute("select * from Table2 where [GROUP]=" + "'" + username + "'")
row = cursor.fetchall()
df = pd.DataFrame(data=row)
colnames = list(dftrans.columns.values.tolist()) #LIST FOR YEAR DROPDOWN SELECTION
#getting the dropdown selections:
Dropdown_shipname = request.POST.get('Dropdown_shipname')
Dropdown = request.POST.getlist('Dropdown')
return render(request, 'SpecificVessel.html',
{'colnames': colnames, 'Dropdown': Dropdown, 'shipnames': shipnames, 'Dropdown_shipname': Dropdown_shipname,})
SpecificVessel.html
<form method="post">
{% csrf_token %}
<div class="form-group col-md-4">
<label for="Dropdown_shipname"><b>Select Vessel</b></label>
<select name="Dropdown_shipname" id="Dropdown_shipname" data-style="btn-default" class="selectpicker form-control" >
{% for i in shipnames %}
<option value="{{ i }}" {% if Dropdown_shipname == i %} selected {% endif %}>{{ i }} </option>
{% endfor %}
</select>
</div>
<div class="form-group col-md-4">
<label for="Dropdown"><b> Select Month </b></label>
<select name="Dropdown" id="Dropdown" data-style="btn-default" class="selectpicker form-control" multiple>
{% for i in colnames %}
<option value="{{ i }}" {% if Dropdown == i %} selected {% endif %} >{{ i }} </option>
{% endfor %}
</select>
</div>
<div class="form-group col-md-1 margin_top_25">
<input type="submit" value="Submit" />
</div>
</form>
What is the problem?
The solution I have in the code above provides me with independent dropdowns. That is, whenever there is a mismatch, it throws me an error. I have been trying to approach this in different way, however, after long research online, I found out that javascript or ajax may be the way to go about this. My question is this: Is there any way in which I could get what the user has selected in Dropdown_shipname before he submits the results? If yes, how would you solve this problem?
I hope I was clear enough. Please let me know if I should explain the problem any better.
There's a lot I feel I need to address before answering your main question.
The if request.user.is_authenticated bit is unnecessary; you already decorate the view with #login_required, so there's no way the user isn't authenticated.
Where does cursor come from? It doesn't look like you're using Django's database stuff (the ORM, or even raw cursors), but something else? Why is that?
Having a global cursor may lead to trouble down the line in production, when it's being shared between requests in a multithreaded situation. (Using Django's database functionality the database connections are correctly reset between requests, and each thread gets its own connection.)
Your SQL queries are vulnerable to SQL injection attacks, since you're just concatenating strings together. You need to use placeholders (parametrized queries) instead. How that's done depends on the database and database driver you're using.
You definitely don't need Pandas and a Pandas dataframe to extract the data from your database result! (My pet peeve: useless use of Pandas.)
The first retrieval would be shipnames = [row[0] for row in cursor].
The second retrieval would be colnames = [d[0] for d in cursor.description] (or similar; depends on your database). (However, you really don't want to fetch a number of rows just to get the column names; one row, e.g. LIMIT 1 in standard SQL, would do.)
You should be using Django forms to manage, well, forms. That way you don't need to manage rendering the <select>s and <option>s and selecteds manually.
This view would likely become a FormView subclass.
You say "This makes my job of using forms harder since I am using the username of the user to filter my server.", but that's a non-issue. You can well pass in your Django request, or just the User, or an username, to a custom form class, and have it modify or even add fields dynamically to the form based on it.
That said, the most minimal solution here is a tiny bit of JavaScript, to refresh the page with an added query string argument for the first selection. That is, when the user changes the shipname field, you'd refresh the page with e.g. ?shipname=selection-here, and deal with figuring out the correct choices for the other field in your view code.
The most minimal way I can think of is
<script>
document.getElementById("Dropdown_shipname").addEventListener("change", (event) => {
location.href = `?shipname=${event.target.value}`;
}, false);
</script>
Beyond that, you could use an AJAX request to selectively refresh only part of the page, and beyond that, maybe refactor the form into, say, a React.js or Vue.js component that deals with the form.
But either way, no, you're not going to be able to dynamically change the other field without JavaScript.

How to set a variable (using set tag) in django template?

I want something like this inside django template. I want to use these variables in javascript file or another template file.
{% set titles = {
'table': form._meta.model._meta.verbose_name_plural.title(),
'form': form._meta.model._meta.verbose_name.title()
}
%}
{% set dtable = "dt_" + page %}
How can I create "set tag" in django template? Your answer would be helpful for me.
Although I think this is generally to be avoided (mixing template & logic is generally advised against in django), it appears this is possible through the use of the with tag.
{% with name="World" greeting="Hello" %}
<html>
<div>{{ greeting }} {{name}}!</div>
</html>
{% endwith %}
In javascript, it would probably look like this
$(document).ready(function(){
{% with greeting="hello" %}
var text = '{{ greeting }}'
{% endwith %}
});
There are some constraints, I don't believe you can set tuples or dictionaries this way. That said, if you're trying to set more complex types using {% with ... %} that logic should probably be handled in the view.
Referenced from this SO question.
CASE 1:
If you want to access your dictionary in the HTML template. You can pass titles dictionary to the template and access any key of the dictionary by the dot (.) operator.
<div class="something1">{{ titles.table }}</div>
<div class="something2">{{ titles.form }}</div>
CASE 2:
If you want to send a dictionary to the HTML template and access in your javascript file, I would suggest you to store them in a data attribute of an input (hidden) element or any element and then access via javascript.
<input type="hidden" id="my-data-table" data-table="{{ titles }}">
You can access dictionary in your javascript file like this,
var myTitles = document.getElementById("my-data-table");
var titles = myTitles.getAttribute("data-table");

Tranforming buttons - works in development, not deployment

I have a sign out form. Users click a button to secure a reservation. That button changes from “free” to “reserved”, the reserved button includes the user’s name. I’m using Django, but added JS so that the button will change without a refresh.
This works fine on my local server, but when I deploy to PythonAnywhere it doesn’t. Instead, the button transforms, but displays “none” and my console check is “undefined”.
Any idea what went wrong?
Abridged code below:
Main.JS
$( 'button' ).click(function() {
console.log( 'we clicked!' ); // Sanity Check II
var pk = this.id
var button = $(this)
var user = $( 'button' ).attr("user")
console.log( pk, user) // Sanity Check III
$.ajax({
url: "/reserve/",
type: "POST", //Send the info to reserve view
data: { pk : pk},
success: function(data) {
var number = JSON.parse(data);
console.log(number, number.result)
if (number.result == 1 ) {
if (number.result == 2) {
console.log(user)
$(button).toggleClass( "free reserved")
$(button).children("div.tchr").html(user); //Send user info to button, reverts on refresh
$.toast({
heading: "Reservation Complete!",
icon: 'success',
stack: 4,
hideAfter: 2000
});
}
And a button
home.html
{% for p in periods %}
<td class="slots">
Period {{p}} <br>{% for x in this_week %} {% with d=x.day|date:"w" %} {% if d == '1' and x.period == p %} {% if x.is_reserved == True %} # this_week is the queryset of reservations, day is the day converted into a weekday
<button id="{{x.pk}}" type="submit" class="reserved btn">
<span class="cart">{{x.cart}}</span>
<div class="tchr" style="text-align:left">{{x.teacher}}</div></button> {% else %}
<button id="{{x.pk}}" type="submit" user="{{request.user }}" class="free btn">{% csrf_token %}
<span class="cart">{{x.cart}}</span>
<div class="tchr">{{x.teacher}}</div></button> {% endif %} {% endif %} {% endwith %} {% endfor %}
</td>
{% endfor %}
There are a few more options (Cleared, Reserved, Blocked)
Again, this works fine in development! What am I missing?
You need to serve your static files.
Javascript files count as static files. In Development environment, when DEBUG is set to true inside your settings.py files, Django takes car of collecting your static files and serving them, however when you turn DEBUG to false in your deployment server it stops.
Take a look at staticfiles app documentation on Django project to know how to use it. This is a simple static files server that you can use.
To make sure that this is your problem, navigate to your settings.py file and turn DEBUG to true then restart your server. If everything works as expected, follow the instructions on the link above.
Make sure not to rely on DEBUG mode since it could cause performance degradation and massive memory leaks

How to save multiple forms in on page in Django

I am trying to make a betting app in Django. After a user signs in, there's a page with several forms, each responsible for saving the result of a match. Each form takes two integer inputs (number of goals for each team). I want to have a save button at the end of these forms such that when clicked, the input data are recorded in the database. I have two models, Game and Bet. Game is responsible for storing the actual result of games, while Bet is responsible for recording user predictions.
class Game(models.Model):
team1_name = models.CharField(max_length=100)
team2_name = models.CharField(max_length=100)
team1_score = models.IntegerField()
team2_score = models.IntegerField()
class Bet(models.Model):
user = models.ForeignKey(User)
game = models.ForeignKey(Game)
team1_score = models.IntegerField()
team2_score = models.IntegerField()
And here's the main page
{% for game in games %}
<form action="../place_bet/" method="post">
{% csrf_token %}
<table>
<tr>
<th class="table-col"><label for="team1_score">{{ game.team1_name}}</label></th>
<th class="table-col">{{ form.team1_score }}</th>
</tr>
<tr>
<td class="table-col"><label for="team2_score">{{ game.team2_name}}</label></td>
<td class="table-col">{{ form.team2_score }}</td>
</tr>
</table>
<input type="submit" value="Submit" id="submit-button"/>
</form>
{% endfor %}
My question is how I can capture the input fields for different forms in the place_bet view that is triggered when submit button is clicked.
For a throughly answer about how to process multiple repeated fields with one form on Django, please read this answer history. This will be an answer on good practices to process many forms from one page in Django.
So there's a recipe for this, what we will need is the following:
A View, it can be class-based or function-based, in this example, I'll use class-based because it's tidy.
An URL where this View is served, the only special thing about it is an optional parameter added to the end of it.
A template with the correct setup for calling the right view function.
Optionally, you can use a Form to validate the data, but it's not a requirement.
So, the first things first, let's create the View. This will separate concerns to better readability.
from django.shortcuts import render, get_object_or_404
from django.views import View
from django.http import HttpResponseBadRequest
class PlaceBet(View):
template_name = 'place_bets.html'
context = some_base_context_dict
def get(self, request):
# the user is requesting the game list
self.context['games'] = Game.objects.all()
return render(request, self.template_name, self.context)
def post(self, request, game_id=None):
# the user is submitting one of game's predictions
if not game_id:
return HttpResponseBadRequest('hey, send a game id, you fool!')
game = get_object_or_404(Game, pk=game_id)
# here you can use a Form to validate the data, but hey,
# do your homework
bet = Bet.objects.create(user=request.user, game=game,
team1_score=request.POST.get('team1_score'),
team2_score=request.POST.get('team2_score'))
# bet is saved at this point so now we can now take the user
# elsewhere or i dunno, the same page...
self.context['games'] = Game.objects.all()
self.context['new_bet'] = bet
response = render(request, self.template_name, self.context)
# it's good practice to say you created something
response.status_code = 201
return response
Now, the Urls need some work too, you're passing a parameter, so...
urlpatterns = [
url(r'^place_bet$', PlaceBet.as_view()),
url(r'^place_bet/(?P<game_id>[^/.]+)', PlaceBet.as_view(), name='place-bet') #name parameter, very important for the future...
]
Your template is almost right, but it needs some work:
{% for game in games %}
<!-- the action now points to the URL by name, and passes the game_id -->
<form action="{% url 'place-bet' game.pk %}" method="POST">
{% csrf_token %}
<table>
<tr>
<th class="table-col">
<label for="team1_score">{{ game.team1_name}}</label>
</th>
<th class="table-col">
<!-- you need something to submit -->
<input type="number" name="team1_score" value="{{ game.team1_score }}">
</th>
</tr>
<tr>
<td class="table-col">
<label for="team2_score">{{ game.team2_name}}</label>
</td>
<td class="table-col">
<input type="number" name="team2_score" value="{{ game.team2_score }}">
</td>
</tr>
</table>
<input type="submit" value="Submit" id="submit-button"/>
</form>
{% endfor %}
And there it is, now when you press the Submit button, the browser will send the POST data to the post method of the view, using an action URL that encodes the game's ID, so there's no room for mistakes.
This code can be improved a lot, but it will get you going.

Javascript validation for dynamically generated forms in Django template

I am struggling with something that is probably very basic: I need to generate a form with marks for my University database application. Each student in each module has a class got "Performance" that stores all the marks for the module. There are different assessments and the Performance class calculates the average for them.
I need to be able to enter, for example, all the marks for the first assessment, and I did that with a dynamically generated Django Form as a table in the template:
{% for performance in performances %}
<tr>
<td>
{{ performance.student }}
</td>
<td>
{% if to_mark == 1 %}
<input type="text" class="input-mini" name="{{ student.student_id }}" value="{{ performance.assessment_1 }}">
{% else %}
{{ performance.assessment_1 }}
{% endif %}
</td>
And the same for the other assessments (to_mark gets passed on by views.py to indicate which assessments needs to be marked here)
I have failed to use Inlineformfields and therefore decided to generate a form dynamically and validate it with Javascript, also because the validation is simple (has to be a number between 1 and 100), and also because I want to use Javascript to calculate the average on the fly.
My problem is that I have no clue about Javascript. All the tutorials (like this one http://www.w3schools.com/js/js_form_validation.asp) use the name of the form field in the Javascript function, but in my case that name is dynamically generated, so that I can access it easily in the views.py:
if student.student_id in request.POST:
tmp = request.POST[student.student_id]
try:
mark = int(tmp)
if mark in range(0, 100):
performance = Performance.objects.get(student=student, module=module)
if to_change == 1:
performance.assessment_1 = mark
...and so on for the other assessments
except ValueError:
pass (in case someone accidentally enters a letter and not a number)
Is there a way I can use Javascript to address my form fields? Or should I use a different approach than taking the student_id as the name to read it out? How could I do that?
Thanks,
Tobi
There are at least 3 ways to get to the form fields using JavaScript:
By ID, by CSS class or by DOM traversal. If you're not familiar with JavaScript, I would suggest finding a django-friendly client-side form validator like: https://code.google.com/p/django-ajax-forms/

Categories

Resources