Flask, json, render_template, form action, data tables - json

I want this to happen: A user selects from a form dropdown, the form passes the variables to the Flask server app_route function which calls an sql which is filtered dynamically by those parameters entered, and returns a datatable to the browser.
In Flask, I set up an app_route that provides url_for a json file. The object returned is a json dict which is the result of an sql query filtered by the parameters from a form submitted by the user. The action function of the form posts to this function and returns the json dict url.
Datatable needs the a url for the data. It does not seem to allow me to use a jinja template variable. My conflict is that I need to both redirect/render template of the html page that has the datatable and return a url containing the son dict.
I want to render_template('the_page_with_datatable.html', my_local_json_dict_variable).
I can either render_template('the_page_with_datatable.html') or return(my_local_json_dict_variable) from the function called under the app_route called by the form submit and assign to the url_for location, but not both.
How is this done?
So I can already return a data frame_to_html using normal jinja variable but I particularly want to have the datatables functionality. I don't want to render any other kind of table. I can also render the datatable with static sql where I have use sql response to an api. Issue is the submit form action is returning one url, when I need two - the json url and the render_template url.
HTML/JS
<form class="form-inline" id="my_form" action="get_data" method="POST">
<div class="form-group">
<select name="year" class="selectpicker form-control">
{% for yr in years %}
<option value="{{ yr }}">{{ yr }}</option>
{% endfor %}
</select>
<select name="month" class="selectpicker form-control">
{% for month in months %}
<option value="{{ month }}">{{ month }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-default">Go</button>
</form>
<table id="values_table" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>Name</th>
<th>Number</th>
<th>Date</th>
<th>values_€</th>
</tr>
</thead>
</table>
<script>
function setupData() {
$(document).ready(function () {
$('#values_table').DataTable( {
dom: 'Bfrtip',
"ajax": {
"url": "/get_data",
"dataType": "json",
"dataSrc": "data",
"contentType":"application/json"
},
"columns": [
{"data": "PersonName"},
{"data": "PersonNumber"},
{"data": "Date"},
{"data": "values_€"},
]
});
});
});
}
$( window ).on( "load", setupData );
</script>
Flask routes
#renders page with select form and datatable
#app.route("/values_select" , methods=['GET','POST'])
def values_select():
years, months = api().values_select()
return render_template('values_select.html', years=years, months=months)
#get json data for datatable to parse from url
#app.route("/get_data" , methods=['GET','POST'])
def get_data():
year = request.form.get('year')
month = request.form.get('month')
data = assets_api().values(month, year)
return jsonify(data=data)

It will be best to have a second route that returns json, and utilise the ajax data source of datatables. A basic outline:
Your json route (dropdown as a request arg):
#app.route('/get_data')
def get_json():
filter_val = request.args.get('filter')
result = # Your query here (using filter_val, or all rows if none?)
return jsonify(result)
Your current route will likely remain as-is, but:
def my_page():
# ...
return render_template('page.html')
And the js:
$('#table').DataTable( {
ajax: {
url: "{{ url_for('get_json') }}?filter=" + $("#dropdown").val(),
dataSrc: ''
},
columns: [ ... ]
} );
This is explained in a lot more (better) detail on the datatables ajax guide. But I hope this is a start.
Edit:
One this is setup, you can handle the re-fetching of data slightly differently. You no longer need the form. You can handle either the click of a button, or or change of the dropdown (below). Calling the table.reload() function will refetch the data. Something like:
$("#the_dropdown").change(function() {
table.reload();
});

Related

Trouble accessing json properties in Jinja2 template

I've searched around but the answers I've found aren't working for some reason.
I'm building a flask web app. I load a csv into a dataframe and display it in a table on a page (index). You can click a button to select a row and the row's details will populate below the table.
Everything works and I can print the json object returned when a row is selected, but can't print any of the properties.
I've tried row.first, row['first'], row.first[0], and other various arrangements.
What am I doing wrong / what is the proper way to interact with json objects in Jinja2?
Json from single row that is correctly returned to the template
{
"seq": {
"0": 1
},
"first": {
"0": "Justin"
},
"last": {
"0": "Powell"
},
"state": {
"0": "ND"
},
"email": {
"0": "idomapsok#bu.ne"
}
}
views.py
from flask import render_template, jsonify
from wsaflask import bp
import pandas as pd
data = pd.read_csv('wsaflask/input.csv')
#bp.route('/update-details/<int:id>', methods = ['POST'])
def update_details(id):
row_details = data.loc[data['seq'] == id]
return jsonify('', render_template('row-details.html', row = row_details.to_json()))
#bp.route('/')
def index():
return render_template('index.html', data = data)
row-details.html
<div id="detail-display">
{% if row %}
<!-- How to print row.first (name) here -->
<p>{{ row }}</p>
{% endif %}
</div>
index.html
<body>
<section class="container">
<div style="max-height: 35vh; overflow-y: scroll;">
<table class="table">
<tr>
{% for col in data.columns %}
<th>{{ col }}</th>
{% endfor %}
<th>
action
</th>
</tr>
{% for _, row in data.iterrows() %}
<tr>
{% for col in data.columns %}
<td>{{ row[col] }}</td>
{% endfor %}
<td>
<button onclick="updateDetails(row_id = {{row['seq']}})" class="btn btn-link btn-sm">Select</button>
</td>
</tr>
{% endfor %}
</table>
</div>
<!-- Div below is replaced with row-details.html via js -->
<div id="detail-display">
</div>
</section>
</body>
Javascript:
function updateDetails(row_id){
var id = row_id
$.ajax({
url:"/update-details/" + id,
type:"POST",
dataType:"json",
success: function(data){
$('#detail-display').replaceWith(data)
}
});
}
Okay I figured it out, #jspcal got me looking in the right direction and I found this post.
In short, Flask's to_json() function returns a json string rather than an actual object, though they look similar.
The view to update the data now looks like this:
import json
#bp.route('/update-details/<int:id>', methods = ['POST'])
def update_details(id):
row_df = data.loc[data['seq'] == id]
row_json = row_df.to_json(orient = 'records')
results = json.loads(row_json)
return jsonify('', render_template('row-details.html', row = results))
First I added the 'orient' parameter. This changes the structure of the resulting json string. See the docs here. It was a bit of a guessing game for me, but here's how the json looks now:
[
{
'seq': 1,
'first': 'Justin',
'last': 'Powell',
'state': 'ND',
'email': 'idomapsok#bu.ne'
}
]
Then after importing json (just import json), load the json string using json.loads(your_json_string), and pass the results to the template instead of just the string.
Now in my template, I can get the first name using row[0].first.
I'm not 100% sure what's happening so I'm sure this isn't the ideal way to handle this, but it gets the job done.

django: html form submit without jumping into new page or refreshing/reloading

I am new to django and html. below is my first test web page of a simple online calculator.
I found a problem that when clicking the "submit" button, it tends to jump to a new web page or a new web tab. this is not what I want. Once the user input the data and click "submit" button, I want the "result" field on the page directly show the result (i.e. partially update only this field) without refresh/jump to the new page. Also I want the user input data kept in the same page after clicking "submit".
I saw there might be several different ways to do this work, iframe/AJAX. Since I am new, what is the really simplest way to achieve this goal? BTW, I dont write javascripts.
html:
<form method="POST">
{% csrf_token %}
<div>
<label>num_1:</label>
<input type="text" name="num_1" value="1" placeholder="Enter value" />
</div>
<div>
<label>num_2:</label>
<input type="text" name="num_2" value="2" placeholder="Enter value" />
</div>
<br />
<div>{{ result }}</div>
<button type="submit">Submit</button>
</form>
view.py
def post_list(request):
result = 0
if request.method == "POST":
num1 = request.POST.get('num_1')
num2 = request.POST.get('num_2')
result = int(num1) + int(num2)
print(request.POST)
print(result)
context = {
'result': result
}
return render(request, 'blog/post_list.html', context)
This is a simple example of using Ajax, which I hope will be useful to you.
first you need change post_list view:
view
from django.http import JsonResponse
def post_list(request):
if request.method == "POST":
num1 = request.POST.get('num_1')
num2 = request.POST.get('num_2')
result = int(num1) + int(num2)
return JsonResponse({"result":result})
else:
return render(request, 'blog/post_list.html', context={"result":0})
I use JsonResponse because I just want to get the result data in ajax and display it in the html , for GET request render the html file and for POST request use JsonResponse to return a json like context.
And your html file should to be a look like this:
html
<script src="https://code.jquery.com/jquery-3.6.0.js" integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk=" crossorigin="anonymous"></script>
<form method="POST" id="post-form">
{% csrf_token %}
<div>
<label>num_1:</label>
<input type="text" name="num_1" value="1" placeholder="Enter value" />
</div>
<div>
<label>num_2:</label>
<input type="text" name="num_2" value="2" placeholder="Enter value" />
</div>
<br />
<div id="result" >{{ result }}</div>
<button type="submit" >Submit</button>
</form>
<script>
$(document).ready(
$('#post-form').submit(function(e){
e.preventDefault();
var serializedData = $(this).serialize();
$.ajax({
type:"POST",
url: "/your_url/",
data: serializedData,
success: function(data){
$("#result").text(data["result"]);
}
});
})
);
</script>
First I added jQuery cdn and then your html file, except that I added attribute id=post-form to the form and added id=result, then <script> tag was added and jquery function inside the tag was execute when your form Submited(detect event by the id #post-form).
And get the data(num_1, num_2) by serialize method then use Ajax to send POST reqeust to the view function(post_list), in Ajax you just need to pass serializedData and url(also you can use the Django url tag or set it in action form or...), After that we need to send data to the html(means the result data we received from the View).
in success function Ajax you can add html tag to your html file or
replace the some values,...
In Ajax, you must specify your URL to send the data.
for example if you have this url.py
urls.py
from .views import post_list
urlpatterns = [
path("posts_list/", post_list, name="post_list"),
]
In ajax you can add an address like this:
$.ajax({
type:"POST",
url: "/posts_list/",
....
Or
$.ajax({
type:"POST",
url: "{% url 'post_list' %}",
....
And if you have app_name in urls.py you can added url: "{% url 'app_name:post_list' %}",

How to display data in a drop down corresponding to the data from another dropdown in html page?

I am trying to create a web page using Python DJANGO, in which I have two dropdowns out of which one is for category and another for subcategory, for which data is getting fetched from database. In DB, there are two columns for category and another for subcategory respectively.
For example, there are 2 Categories, i.e boys and girls. and for boys there are 3 names under SubCategory column, similarly for girls. So my want is that, in drop down 1, it should show 'boys' and 'girls'. When user chooses any of them, the corresponding names should appear in the second drop down(i.e only boys names should appear in dropdown 2 when "boys" is selected in dropdown1).
But the way I have written, its showing all the data irrespective of the selection in dropdown1. How can I make the subcategory data appear categorically?
View.py:
def createTicketView(request):
if request.method == 'POST':
taskName=request.POST.get('title')
taskDescription=request.POST.get('description')
Category=request.POST.get('category')
SubCategory=request.POST.get('type')
user_id=request.user.id
task=UserTaskDetails.objects.create(user_id=user_id,
taskName=taskName,taskDescription=taskDescription,
Category=Category,SubCategory=SubCategory)
task.save()
return redirect('home')
category =ServiceCategoryDetails.objects.values('category').distinct()
subcategory=ServiceCategoryDetails.objects.values('SubCategory').distinct()
return render(request,'custws/ticket.html',{'title':'create ticket',
'category':category,
'subcategory':subcategory,
'})
Html code:
<div class="form-group col-md-6">
<label for="category" class="font-weight-bold">Category</label>
<select name="category" id="category" class="form-control">
{% for type in category %}
<option value={{type.category}}>{{type.category}}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-md-6">
<label for="subcat" class="font-weight-bold">Sub Category</label>
<select name="subcat" id="subcat" class="form-control">
{% for type in subcategory %}
<option value={{type.SubCategory}}>{{type.SubCategory}}</option>
{% endfor %}
</select>
</div>
You could use ajax. When user changes 1st input, ajax will update options of your 2nd input.
You could use django-select2 with dependent_fields option.
HTML
<form>
...
<select id='subcat'></select>
</form>
$('#sub-btn').click(function(e){
e.preventDefault();
var data = {cat_id: <get_your_selected_id>}
$.ajax(
{
type:"POST",
beforeSend: function (xhr) {
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader("X-CSRFToken", csrf_token);
},
url: "<your-url>",
data:data,
success: function(data){
// on success, dynamically append the select drop down list
$('#subcat').html(data)
}
});
})
VIEW
def myview(request):
...
if request.method == 'POST':
cat_id = request.POST.get('cat_id')
# create options, note: 'subcategories' should be replaced with your category related name
options = [f"<option value='{i.pk}'>{i.value}</option>"+'\n'
for i in Category.objects.get(id=cat_id).subcategories.all()]
return HttpResponse(options)
Then, the success callback is called and the options are dynamically appended. You can also return JsonResponse of the data and construct the option element in frontend.
Besides, take a look of django-autocomplete-light, which is very convenient for doing autocomplete stuff and gracefully handles with all kinds of relations between fields

How to render a field request without refreshing the page?

I have a form with a few fields, the first being where a person enters their ID #, which is tied to a separate model for verification. I made this function get_employee_name, which returns the name based on the ID from the other model, but I'm not sure how to display it in the page, right on the top, without refreshing after the person tabs/clicks out? I'm not too familiar with html, but I was reading an ajax GET request would do the trick, but I'm not sure how to approach this.
This is basically so the person knows that the ID # they entered matches their name before proceeding to fill the rest out.
views.py
class EnterExitArea(CreateView):
model = EmployeeWorkAreaLog
template_name = "enter_exit_area.html"
form_class = WarehouseForm
def form_valid(self, form):
emp_num = form.cleaned_data['adp_number']
area = form.cleaned_data['work_area']
station = form.cleaned_data['station_number']
if 'enter_area' in self.request.POST:
form.save()
return HttpResponseRedirect(self.request.path_info)
elif 'leave_area' in self.request.POST:
form.save()
EmployeeWorkAreaLog.objects.filter(adp_number=emp_num, work_area=area, station_number=station).update(time_out=datetime.now())
return HttpResponseRedirect(self.request.path_info)
def get_employee_name(request):
adp_number = request.POST.get('adp_number')
employee = Salesman.objects.get(adp_number=adp_number)
employee_name = employee.slsmn_name
return employee_name
models.py
class EmployeeWorkAreaLog(TimeStampedModel, SoftDeleteModel, models.Model):
employee_name = models.CharField(max_length=25)
adp_number = models.ForeignKey(Salesman, on_delete=models.SET_NULL, help_text="Employee #", null=True, blank=False) #(max_length=50, help_text="Employee #", blank=False)
...
def __str__(self):
return self.adp_number
forms.py
class WarehouseForm(AppsModelForm):
class Meta:
model = EmployeeWorkAreaLog
widgets = {
'adp_number': ForeignKeyRawIdWidget(EmployeeWorkAreaLog._meta.get_field('adp_number').remote_field, site),
}
fields = ('adp_number', 'work_area', 'station_number')
enter_exit_area.html
{% extends "base.html" %}
{% block main %}
<form id="warehouseForm" action="" method="POST" data-stations-url="{% url 'operations:ajax_load_stations' %}" novalidate >
{% csrf_token %}
<div>
<div>
{{ form.adp_number.help_text }}
{{ form.adp_number }}
</div>
<div>
{{ form.work_area.help_text }}
{{ form.work_area }}
</div>
<div>
{{ form.station_number.help_text }}
{{ form.station_number }}
</div>
</div>
<div>
<div>
<button type="submit" name="enter_area" value="Enter">Enter Area</button>
<button type="submit" name="leave_area" value="Leave">Leave Area</button>
</div>
</div>
</form>
{% endblock main %}
We'll use ajax, with jQuery so be sure you have jQuery before you read.
first, you've to create an endpoint to GET, go to urls.py & add an endpoint say
path('/myserver/getID/', views.get_employee_name, name="whatever")
now, this calls get_employee_name right? Let's now call it in JS without refreshing.
here's the basic syntax ->
$.ajax({THIS IS A SIMPLE DICT})
ajax takes parameters
type which is the request type
url which is the request URL which we just made above (not the full url, you're specifying the endpoint from where you're located on the website so you just use /myserver/getID/)
it also takes data which is a dictionary with your posted data (yes a dictionary inside the bigger ajax dictionary
it CAN take success which is a function to call after getting the response with status 200 (success) and that success function can have the parameter response which is your response
it CAN take error which is a function that gets called after an error & takes error as argument
enough talking...
$.ajax({
url: 'myserver/getID',
type: 'GET',
data: // don't specify this, we're not posting any data,
success: function (response) {console.log(response.data)}, //this will be what returned from python
error: function (error){console.log(error)}
})
this is a simple ajax request
NOTE, if you return a redirect from python & accept it from ajax, it won't work, ajax can't redirect, be sure to remember that because most of the time people ask why redirect('mylink') doesn't work after I return it from ajax.
Another NOTE is the when dealing with post requests with ajax, you must include the csrf token which can be included by
csrfmiddlewaretoken: '{%csrf_token%}'
You can use Fetch API too if you want, or even normal XMLhttprequest.
Sounds like you have a few questions and should split them up but just to answer the main question in your title, "How to render a field request without refreshing the page?", this is how you do that part with some DOM manipulation. This is basic HTML and JavaScript you would need to fit into your project.
Once you get the name back from your lookup, you just need to insert the value into the DOM and that will render it, not requiring a refresh. Here's a simple example:
var clickMe = function() {
var element = document.getElementById('heading');
// Do your AJAX and lookup something...
element.textContent = "Name Lookup From Server Request";
}
<div>
<h1 id="heading"></h1>
<button onclick="clickMe()">Click Me</button>
</div>

Django : HTML form action directing to view (or url?) with 2 arguments

Started learning django about a week ago and ran into a wall. Would really appreciate any enlightenment...
models.py
class data(models.Model):
course = models.CharField(max_length = 250)
def __str__(self):
return self.course
html
Converted the objects in models.course to schlist
<link rel="stylesheet" type="text/css" href="{% static '/chosen/chosen.css' %}" />
<form action={% views.process %} method="GET">
<div>
<h4 style="font-family:verdana;">First Course: </h4>
<select data-placeholder="Course" style="width:350px;" class="chosen-select" tabindex="7">
<option value=""></option>
{% for item in schlist %}
<option> {{ item }} </option>
{% endfor %}
</select>
</div>
</br>
<div>
<h4 style="font-family:verdana;">Second Course:</h4>
<select data-placeholder="Course" style="width:350px;" class="chosen-select" tabindex="7">
<option value=""></option>
{% for item in schlist %}
<option> {{ item }} </option>
{% endfor %}
</select>
</div>
</br>
<input type="submit" value="Compare!" />
</form>
urls.py (having my doubts if this works..)
urlpatterns = [
url(r'^(\d+)/(\d+)$',views.process, name = 'process'),
]
view.py
def process(request,q1 ,q2):
obj1= get_object_or_404(Schdata, course = q1)
obj2= get_object_or_404(Schdata, course = q2)
........
Was wondering if it is possible for the form action to direct the action to
(1) view.py or (2) url.py (and eventually to a view.py) with 2 arguments selected?
If so how should the form action be? {{view ?}} or {{url ?}}. Am I missing out the definition of my arguments in my HTML?
Directing to views.py:
User input is CharField, could use get_object_or_404 to get the model pk. However when defining my urls.py I would get a Noreverse error as my url arguments is the primary key.
Directing to urls.py:
Url arguments is primary key. From the way I see it, I need to magically convert my User input Charfield to a pk before passing it to urls.py
Is there a (or) function for get() in django? E.g get_object_or_404(pk = q1 or course = q1)?
Would really appreciate any advice. Been staring at this for hours.
You are trying to use the reverse resolution of urls in Django.
In your html file correct form action url to the following and method should be POST:
<form action={% url 'process' %} method="POST">
In case you are trying to pass parameters along then use this:
<form action={% url 'process' request.user.id 4 %} method="POST">
Reference:
https://docs.djangoproject.com/en/1.10/topics/http/urls/
Yes i'm late but it can help others for better understanding how Django processes the request.
Django 3.0 pattern
How Django processes the request
Basic :
First Django check the matching URL.
If URL is matched then calling the defined view to process the request. (Success)
If URL not matched/found the Django invokes error Page Not Found
In detail reading :
Official Django Documentations How Django processes a request
These are your URL patterns :
urlpatterns = [ path('profile/edit/<int:pk>/',views.editprofile, name='editprofile'),]
Third argument in urlpatterns is for if you want to change the url pattern from current to this :
urlpatterns = [ url('profile/edit/user/id/<int:pk>',views.editprofile, name = 'editprofile'),]
You don't need to redefine url pattern in all Templates where you using url name.
For Example :
This is my template profile.html where i used the url name instead of hard coded url.
<a class="item" href="{% url 'editprofile' user.id %}" >Edit profile </a>
Solution of your problem :
.html
Only use url name instead of hard coded url in your templates and pass arguments.
<form action={% process no_of_arguments %} method="POST">
views.py
Here you can process your request
def process(request,no_of_arguments):
Become good django developer
You can also use Django ModelForms for your model.
Using model forms or simple form you can do multiple things
Modular approach
Write server side validation in related form instead of doing in views.py
Readable code - Clean code