Trouble accessing json properties in Jinja2 template - json

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.

Related

Getting error in printing the dictionary values to html

I want to print to get the details of different cities weather and I had written the code as below. While retrieving the data, I am not able to fetch the data to HTML.
views.py:
from django.shortcuts import render
# Create your views here.
import urllib.request
import json
import requests
def display(request):
city=['vijayawada','guntur','tenali','rajahmundry','amaravathi']
for i in city:
source = urllib.request.urlopen(
'https://api.openweathermap.org/data/2.5/weather?q=' + i + '&units=metric&appid=5980455dc827c861d5ac4125c3673b43').read()
list_of_data = json.loads(source)
data = {
"country_code": str(list_of_data['sys']['country']),
"coordinate": str(list_of_data['coord']['lon']) + ', '
+ str(list_of_data['coord']['lat']),
"temp": str(list_of_data['main']['temp']) + ' °C',
"pressure": str(list_of_data['main']['pressure']),
"humidity": str(list_of_data['main']['humidity']),
'main': str(list_of_data['weather'][0]['main']),
'description': str(list_of_data['weather'][0]['description']),
'icon': list_of_data['weather'][0]['icon'],
}
print(data)
return render(request, "display.html",data)
display.html:
<html>
<head>
<title>API Display</title>
</head>
<body>
<h1>Weather details</h1>
<form>
{% for i in data %}
{{ country_code }}
{{ temp }}
{{ i }}
{% endfor %}
</form>
</body>
</html>
I am trying to print the values of the dictionary to html but not getting printed. The values are printing correctly in the console which means the for loop in views.py is working correctly.
Please tell me where I am wrong
You might want to make a function that handles the request for each city:
def get_city_weather(city_name):
source = urllib.request.urlopen(
'https://api.openweathermap.org/data/2.5/weather?q=' + city_name + '&units=metric&appid=5980455dc827c861d5ac4125c3673b43').read()
list_of_data = json.loads(source)
return {
"country_code": str(list_of_data['sys']['country']),
"coordinate": str(list_of_data['coord']['lon']) + ', '
+ str(list_of_data['coord']['lat']),
"temp": str(list_of_data['main']['temp']) + ' °C',
"pressure": str(list_of_data['main']['pressure']),
"humidity": str(list_of_data['main']['humidity']),
'main': str(list_of_data['weather'][0]['main']),
'description': str(list_of_data['weather'][0]['description']),
'icon': list_of_data['weather'][0]['icon'],
}
Then use dict comprehension to compile the data dict to send to the template:
def display(request):
cities=['vijayawada','guntur','tenali','rajahmundry','amaravathi']
data = {city_name: get_city_weather(city_name) for city_name in cities}
return render(request, "display.html", data)
At this point data is a dictionary who's key is the city name and who's value is the dictionary returned by get_city_weather, so it looks like (abbreviated):
{'vijayawada':
{'country_code': 'ABC'
# ...
}
# ...
}
Then in the template you could loop through this like:
{% for city_name, city_weather in data.items %}
The temperature in {{city_name}} is {{city_weather.temp}}
and the air pressure is {{city_weather.pressure}}.
{% endfor %}
Or if you don't want to specify the keys like temp and pressure, you could do:
{% for city_name, city_weather in data.items %}
<p>Weather data for {{city_name}}:</p>
<ul>
{% for k,v in city_weather.items %}
<li>{{k}}: {{v}}</li>
{% endfor %}
</ul>
<hr />
{% endfor %}
That is because you are overwriting your data dictionary. It's being passed only as one object, it should render something if you try {{data}} or {{data.country_code}}, {{data.temp}}...
One way to solve this, is to have a nested dictionary with the data of every city:
views.py
def loop_dict(request):
cities=['vijayawada','guntur','tenali','rajahmundry','amaravathi']
data = {}
for city in cities:
source = urllib.request.urlopen(
'https://api.openweathermap.org/data/2.5/weather?q=' + city + '&units=metric&appid=5980455dc827c861d5ac4125c3673b43').read()
list_of_data = json.loads(source)
data[city] = {
"country_code": str(list_of_data['sys']['country']),
"coordinates": {
'latitude': str(list_of_data['coord']['lat']),
'longitude': str(list_of_data['coord']['lon'])
},
"temp": str(list_of_data['main']['temp']) + ' °C',
"pressure": str(list_of_data['main']['pressure']),
"humidity": str(list_of_data['main']['humidity']),
'main': str(list_of_data['weather'][0]['main']),
'description': str(list_of_data['weather'][0]['description']),
'icon': list_of_data['weather'][0]['icon'],
}
context = {'data': data}
return render(request, 'loop_dict.html', context)
loop_dict.html:
{% extends 'base.html' %}
{% block content %}
{% for key, obj in data.items %}
<h2>City: {{key}}</h2><br>
<p>Country Code: {{obj.country_code}}</p><br>
<p>Latitude: {{obj.coordinates.latitude}}</p><br>
<p>Longitude: {{obj.coordinates.longitude}}</p><br>
<p>Temp: {{obj.temp}}</p><br>
<hr>
{% endfor %}
{% endblock %}
AFAIK, django template system recognizes dict variable just like Python itself does.
So, if we try to convert your template code back to Python code, we would get something like this:
for i in data:
print(country_code)
print(temp)
print(i)
Now, ask yourself: will it work?
Obviously, it's not.
So, how exactly in Python you would loop and access dict values?
You can do it like this:
for i in data:
print(i)
Or with k,v loop variables (better way):
for k, v in enumerate(data):
print(f"key: {k}, value: {v}")
But, in your template code, you are trying to access certain values with the key (country_code etc).
In this case, you probably don't even need to use the for-loop.
And your display.html file can be rewritten like this:
<html>
<head>
<title>API Display</title>
</head>
<body>
<h1>Weather details</h1>
<form>
{{ data.country_code }}
{{ data.temp }}
</form>
</body>
</html>
Also, as I can see your view retrieves this data for each given city.
Thought you only pass the last one to your template.
In order to fix this, you must rewrite your view code.
I guess you could add a new variable called collected_data and then at the end of every data retrieve loop just append data to this new variable.
After that, you could just pass the collected_data to the template.
And then loop through it within the template.
But I exactly don't know how your template should look like, so I leave it to you.
I've tried to explain what's the issue is.

Why is using jinga template returning data as none on a webpage using flask?

I am trying to print basically a table to display data from a function I have called on flask on a webpage. I looked over Jinga templates and that is what I attempted to use, however to no avail.
My code is attached below. result from my cv_acp file is what I am trying to display in a table form.
Currently, my TSN_PIC returns result as follows:
The input video frame is classified to be PlayingCello - 99.33
PlayingGuitar - 0.28 PlayingPiano - 0.16 BoxingSpeedBag - 0.10
StillRings - 0.06
But I want to be able to display this on a web page using flask in a table format
My code is as follows:
cv_acp
def TSN_PIC(img):
img = image.imread(img)
fig, ax = plt.subplots(figsize=(18, 18))
ax.imshow(img.asnumpy())
transform_fn = transforms.Compose([
video.VideoCenterCrop(size=224),
video.VideoToTensor(),
video.VideoNormalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
img_list = transform_fn([img.asnumpy()])
net = get_model('vgg16_ucf101', nclass=101, pretrained=True)
pred = net(nd.array(img_list[0]).expand_dims(axis=0))
classes = net.classes
topK = 5
ind = nd.topk(pred, k=topK)[0].astype('int')
print('The input video frame is classified to be')
for i in range(topK):
result = ('\t%s - %.2f'%(classes[ind[i].asscalar()], nd.softmax(pred)[0][ind[i]].asscalar()*100))
print((result))
return plt.show()
app.py
#app.route("/cv/action_prediction/TSN_PIC", methods=['POST'])
def cv_acp_TSN_PIC_upload_image():
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No image selected for uploading')
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
print(app.config)
path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
print(path)
file.save(path)
#print(file.config)
result = cv_acp.TSN_PIC(path)
# print (results)
#print('upload_image filename: ' + filename)
flash('Image successfully uploaded and displayed below')
return render_template('cv_acp_TSN_PIC.html', filename=filename, result=result)
else:
flash('Allowed image types are -> png, jpg, jpeg, gif')
return redirect(request.url)
#app.route('/cv/action_prediction/TSN_PIC/display/<filename>')
def cv_acp_TSN_PIC_display_image(filename):
#print('display_image filename: ' + filename)
#return MASK_RCNN('static', filename='uploads/' + filename)
return redirect(url_for('static', filename='uploads/' + filename), code=301)
if __name__ == "__main__":
#app.run()
app.run()
cv_acp_TSN_PIC.html
<div id="content" class="p-4 p-md-5 pt-5">
<h2 class="mb-4">TSN PIC</h2>
<!Doctype html>
<title>Python Flask File Upload Example</title>
<h2>Select a file to upload</h2>
<p>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</p>
{% if filename %}
<div>
<img src="{{ url_for('cv_acp_TSN_PIC_display_image', filename=filename) }}">
{% block content %}
<div class="container">
<p>{{results}}</p>
</div>
{% endblock %}
</div>
{% endif %}
<form method="post" action="/cv/action_prediction/TSN_PIC" enctype="multipart/form-data">
<dl>
<p>
<input type="file" name="file" autocomplete="off" required>
</p>
</dl>
<p>
<input type="submit" value="Submit">
</p>
</form>
</div>
Create a list of dictionaries of the data you need and return that. Then you can loop over the data and build your table.
results = []
for i in range(topK):
result = ('\t%s - %.2f'%(classes[ind[i].asscalar()], nd.softmax(pred)[0][ind[i]].asscalar()*100))
datadict = {
'header': yourheadername,
'data': yourdatahere
}
results.append(datadict)
return results
<table border=1>
{% for result in results%}
<tr>
<th>
{{result.header}}
</th>
</tr>
<tr>
<td>
{{result.data}}
</td>
</tr>
{% endfor %}
</table>
If you're working in a terminal or in a Jupyter notebook, plt.show() does what you want. For a web page, not so much.
You have a good start otherwise, based it seems on getting an uploaded image to display. So your challenge will be to either save the matplotlib image to disk before you generate the page, or to defer generating the image until it's requested by way of the <img src=..., then somehow return the image bits from cv_acp_TSN_PIC_display_image instead of a path to the saved file.
To do the former, plt.savefig('uploads/image.png') might be what you need, with the caveat that a fixed filename will break things badly as soon as you have multiple users hitting the app.
To do the latter, see this question and its answer.

fetch is returning html rather than JSON response

I am working on the cs50 web development project Network. Basically building a twitter copycat.
It is still in process but so far I have two fetch requests for a JSON response. Once works perfectly but the other, which is a very similar request, returns html instead of JSON, causing an error. Can't figure out why this one doesn't work. Code snippets below:
Here is the one that is returning html for my profile.html file for some reason. The commented out parts are the actual fetch JSON request but I temporarily changed it to show me in the console what is was returning.
profile.js:
function load_posts_profile() {
console.log("load_posts_profile running");
document.querySelector('#prof-posts').style.display = 'block';
fetch(`/profile_posts`)
//.then(response => response.json())
.then(response => response.text())
.then(text => console.log(text))
//.then(posts => {
// Print posts
//console.log(posts);
//posts.forEach(post => show_posts_profile(post));
//});
}
profile.html:
{% extends "network/layout.html" %}
{% load static %}
{% block body %}
{% if user.is_authenticated %}
<!--insert profle name below in h3-->
<h2 id="profile-name"></h2>
<br>
<h4 id="followers"></h4>
<br>
<h4 id="following"></h4>
<!--add js to load user's posts only-->
<div id="prof-posts">
</div>
{% else %}
<strong> Login To See Profile</strong>
{% endif %}
{% endblock %}
{% block script %}
<script src="{% static 'network/profile.js' %}"></script>
{% endblock %}
urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="index"),
path("login", views.login_view, name="login"),
path("logout", views.logout_view, name="logout"),
path("register", views.register, name="register"),
path("all_posts", views.all_posts, name="all_posts"),
path("<str:poster>", views.profile, name="profile"),
path("profile_posts", views.profile_posts, name="profile_posts")
]
views.py snippet:
def all_posts(request):
posts = Post.objects.all()
posts = posts.order_by("-timestamp").all()
return JsonResponse([post.serialize() for post in posts], safe=False)
def profile_posts(request, poster):
posts = Post.objects.get(poster=poster)
posts = posts.order_by("-timestamp").all()
return JsonResponse([post.serialize() for post in posts], safe=False)
def profile(request, poster):
return render(request, "network/profile.html", {
"poster": poster,
})
And here is the js file with the similar fetch request that works perfectly. Why would this one work(ie return JSON) but the other returns html?:
function load_posts() {
console.log("load_posts running");
document.querySelector('#all-posts').style.display = 'block';
fetch(`/all_posts`)
.then(response => response.json())
.then(posts => {
// Print posts
console.log(posts);
posts.forEach(post => show_posts(post));
});
}
When in debug mode django returns an html response when an error occures, usually this is the problem, I think you're passing a parameter poster to the view function but you're not excepting one in the url path
In the future there are a few things to remember, when you get a different response then the one you expected, you should check your logs in the terminal or check the response code in the console client side, if it starts with 5 ot is a server error, of ot Starts with 4 it wasn't found or similar and you should check the logs
Here is how you should modify your code:
Add str:poster to the url path and modify the fetch request to include this in your url

Flask, json, render_template, form action, data tables

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();
});

Right way to use the data in Jekyll

The goal is to use the variables defined in the front-matter section in a particular page.
Here my structure of the file system:
_Components
c1.html
c2.html
Here I have defined the attributes in the front-matters.
_Includes > Components
c1.html
Here I want to use a loop to refer to a variable defined in the _Components > c1.html page.
How can I achieve this goal ?
In my _Includes > Components > c1.html I have the following code:
<body class="full">
{% assign fullcomponents = site.components %}
{% for component in fullcomponents | where:"title","c1html" %}
{% component.title %}
{% endfor %}
<div class="container-fluid" id="componentscontainer">
<div class="col-md-12">
<div class="panel panel-primary" id ="panelcomponentlarge">
<div class="panel-heading" >
Chart C3 Line
</div>
etc...
Surely I'm missing some trivial things.
SECOND ATTEMPT
I figured out that I can provide a data layer for that so, I tried to split this information into a new data file.
Here the content of components.json
{
"Components": [
"ChartC3Line":{
"component":"ChartC3Line",
"description":"This is an attempt"
},
"ChartC3Lines":{
"component":"ChartC3Lines",
"description":"This is an attempt"
}
]
}
And I'm trying to get this information with the following code:
{% assign comp = site.data.components.Components[ChartC3Line] %}
HTML:
{% highlight html linenos%}
<p> Description: {{ comp.description }} </p>
but anything is coming up.
THIRD ATTEMPT
I found a solution but I don't like it at all here my new json file
{
"ChartC3":[
{
"component":"ChartC3Line",
"description":"This is an attempt"
}],
"ChartC4":[
{
"component":"ChartC3Line",
"description":"This is an attemptSSSSSSS"
}]
}
I don't want to have an object of several arrays of one element!
Here the code to retrieve the right information:
{% assign comp = site.data.components.ChartC4[0] %}
HTML:
{% highlight html linenos%}
<p> Description: {{ comp.description }} </p>
SOLVED
Following the structure of a json file, I changed my structure in an easier way:
{
"ChartC3":
{
"component":"ChartC3Line",
"description":"This is an attempt"
},
"ChartC4":
{
"component":"ChartC3Line",
"description":"This is an attemptSSSSSSS"
}
}
Now I can easily have access to the right object.
{% assign comp = site.data.components.ChartC3 %}
HTML:
{% highlight html linenos%}
<p> Description: {{ comp.description }} </p>