Flask template does not render after redirection - javascript

I am building a very basic CARTO application using Vue JS and Flask. The full code can be found in this github repository.
The application works like this:
The user select an option in a modal form and clicks on visualize button. This sends a Vue method called loadMap which sends a POST request with a query string (query computed property) value as a payload.
Then, it redirects the route to another template (map.html) and takes a variable (features) that is generated from a SQL API call.
This variable is a GeoJSON which will populate the data source of the map.
The problem is that after doing the redirection, the map template does not render. This is my Python code:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
from flask import Flask, render_template, request, url_for, redirect
app = Flask(__name__, static_folder='static', template_folder='templates')
#app.route('/')
def home():
"""Displays the homepage."""
return render_template('index.html')
#app.route('/query', methods=['POST'])
def query():
data = request.get_json()
query = data['query']
return redirect(url_for('map', query=query))
#app.route('/map', methods=['GET'])
def map():
query = request.args['query']
url = 'https://ramiroaznar.carto.com/api/v2/sql?q={}&format=geojson'.format(query)
session = requests.Session()
r = session.get(url)
features = r.json()
return render_template('map.html', features=features)
if __name__ == '__main__':
app.run(debug=True)
It looks like the POST and GET call are done correctly, but then the redirection is not launched, so the map is not loaded:
How can I fix this problem? Is there a more elegant way to achieve this (loading a new website with data from an API call)?

The problem was located in my js code. So I fix it moving the map logic to the vue instance. I did also simplify the python code, in order to avoid using two templates. Now the app works, but I should refactor the js code to improve the performance (it takes too long to load) and scalalibility (the map logic should be moved again outside the vue instance). You can find the working example in this glitch repository.
The following snippet is my Flask python code...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
from flask import Flask, render_template, request, url_for, redirect
app = Flask(__name__, static_folder='static', template_folder='templates')
#app.route('/')
def home():
"""Displays the homepage."""
return render_template('index.html')
#app.route('/data', methods=['GET'])
def data():
query = request.args.get('query')
url = 'https://ramiroaznar.carto.com/api/v2/sql?q={}&format=geojson'.format(query)
session = requests.Session()
r = session.get(url)
features = r.json()
return features
if __name__ == '__main__':
app.run(debug=True)
... and this is my js code:
Vue.config.ignoredElements = [/as-\w+/];
const vm = new Vue({
el: "#app",
data: {
geometryType: 'cities',
map: false,
features: {}
},
computed: {
query: function() {
let table = ''
if (this.geometryType == 'cities'){
table = 'ne_10m_populated_places_simple';
} else {
table = 'world_borders';
}
return `select * from ${table}`
}
},
methods: {
loadMap: function() {
this.map = true;
this.$http.get('/data', {params: {query: this.query}} ).then(response => {
this.features = JSON.parse(response.bodyText);
console.log(this.features);
}, response => {
console.log('an error ocurred')
})
}
},
watch: {
features: function() {
const map = new mapboxgl.Map({
container: 'map',
style: carto.basemaps.voyager,
center: [10.151367,51.172455],
zoom: 2,
scrollZoom: false
});
const nav = new mapboxgl.NavigationControl({ showCompass: false });
map.addControl(nav, 'top-left');
carto.setDefaultAuth({
username: 'cartovl',
apiKey: 'default_public'
});
console.log(this.features);
const source = new carto.source.GeoJSON(this.features);
const viz = new carto.Viz();
const layer = new carto.Layer('layer', source, viz);
layer.addTo(map, 'watername_ocean');
}
}
})

Instead of a redirect try to render the template.
#app.route('/query', methods=['POST'])
def query():
data = request.get_json()
query = data['query']
return render_template('map.html', query=query)
Hope this helps :P

Related

Django How to add data to database from javascript

I am have created a page in django, on this page I have create a button that calls a JavaScript function which in turn gets data from a API. This part of my code works as expected as it writes the response data to the console. However I cannot seem to get that data to be inserted into the model I have created in django.
I am not sure how python/javascript/models are meant to all link together.
models.py
from django.db import models
class Set(models.Model):
scry_id = models.CharField(max_length=255)
code = models.CharField(max_length=255)
name = models.CharField(max_length=255)
set_type = models.CharField(max_length=255)
release_date = models.DateField()
card_count = models.IntegerField()
block_code = models.CharField(max_length=255, null=True)
block_name = models.CharField(max_length=255, null=True)
parent_set_code = models.CharField(max_length=255, null=True)
digital_only = models.BooleanField(default=False)
foil_only = models.BooleanField(default=False)
nonfoil_only = models.BooleanField(default=False)
icon = models.CharField(max_length=255)
status = models.BooleanField(default=False)
def __str__(self):
return self.name
sets.html
{% extends "main/index.html "%}
{% block content %}
<div class="background card">
<div class="card-body">
<button class="btn" id="setRefresh" style="border: 1px solid" onclick="setRefresh()"><i class="fas fa-sync"></i></button>
</div>
</div>
{% endblock%}
custom.js
function setRefresh() {
const Url="https://api.scryfall.com/sets";
fetch(Url)
.then(res => res.json())
.then(data => obj = data.data)
.then(() => obj.sort(function(a,b){return a.released_at.localeCompare(b.released_at);}))
.then(() => {
for (var i = 0; i < obj.length; i++) {
//console.log(obj[i].name);
}
})
}
view.py
def sets(request):
return render(request,
"main/sets.html",
{"Sets": Set.objects.all})
There are two missing parts. First you need to have a url to listen for changes and then you need to have a view function where you want to set data. And you need to make some changes for the JS part of your code.Example below can clear this up and it is functional as well:
views.py
#ajax_required
def views_name(request):
try:
if request.method == 'POST':
post_id = request.POST.get('post')
YourModel.objects.create(id=post_id)
except Exception: # pragma: no cover
return HttpResponseBadRequest()
urls.py
urlpatterns = [
url(r'^whatever/$', views.views_name, name='whatever'),
]
custom.js:
$(function () {
$(".class-name").click(function () {
var csrf = $(this).attr('csrf');
var post = $(this).attr('page-id');
$.ajax({
url: '/whatever/',
data: {
'post': post,
'csrfmiddlewaretoken': csrf
},
type: 'post',
cache: false,
success: function (returned_values) {
// do whatever you want after success!
},
});
});
})
There are two ways to do it
Method 1 : After retrieving the data, send them to your django app instead of logging them into the console . have a django view that handles the corresponding request coming from your js code containing the data then adding them to the db. In other words you should fetch again , but this time to your django app using a post request .
.Your view should look like this :
from .models import Set
from django.http import HttpResponse
import json
def save(request):
data=json.loads(request.body)
for entry in data:
s = Set()
s.scry_id=entry["scry_id"]
#in the next lines you map entry fields to Set fields
s.save()
return HttpResponse("ok")
Method 2 : Your button call your django app on click , then your django app retrieve the data from https://api.scryfall.com/sets itself then save them to the db. Your code should look like this
from .models import Set
from django.http import HttpResponse
import json
import requests
def save(request):
response = requests.request("GET", "https://api.scryfall.com/sets")
data=response.json()
for entry in data:
s = Set()
s.scry_id=entry["scry_id"]
#in the next lines you map entry fields to Set fields
s.save()
return HttpResponse("ok")
Of course in either case don't forget to map your urlconf to the save view

Javascript accessing local JSON files in flask app

I'm running a flask app locally and my goal is to pass a dictionary from python to Javascript. I currently am able to store the dictionary as a json file in the local folder, however my ajax request cannot seem to access the local json. Why would this not work, and is there a flask module that allows me to pass python dictionaries to javascript locally?
# app.py
dictionary_a = {
"a": {
"b": {
"function_handler": c,
"defaults": {'d':e, 'f':g},
},
"h": {
"function_handler": i,
"defaults": {'d':l},
},
},
}
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
#app.route('/')
def index():
jsonn = json.dumps(dictionary_a, default=dumper, indent=2)
with open('data.json', 'w') as json_file:
json.dump(jsonn, json_file)
return render_template('home.html')
This is my python code,
# code.js
$.getJSON("../../data.json", function(json) {
console.log(json); // this will show the info it in firebug console
});
This is code in javascript
Folder structure is
>project
-app.py
>static
>js
-code.js
-data.json
error message: GET http://localhost:5000/data.json 404 (NOT FOUND)
Solved:
# code.js
var script = document.currentScript;
var fullUrl = script.src;
These lines allow getting the path of the code.js
var jsonUrl = fullUrl.replace("code.js", "data.json") // hard coded jsonUrl
$.getJSON(jsonUrl, function(from_to_conversions) {
console.log(from_to_conversions)
});
I have the json file get dumped into the static/js folder for this to work
Are you trying to render your python dictionary in an HTML file?
you should consider using Jinja.
If you add a dictionary to your response, it will be received as a JSON by your html page, as of flask 1.1.
# app.py
dictionary_a = {
"a": {
"b": {
"function_handler": c,
"defaults": {'d':e, 'f':g},
},
"h": {
"function_handler": i,
"defaults": {'d':l},
},
},
}
#app.route('/')
def index():
jsonn = dictionary_a
return render_template('home.html', jsonn=jsonn)
then you can handle it in your html page using Jinja.
You can loop through its elements, add if statements, etc...
your home.html should look something like this:
<html>
...
<body>
...
{{jsonn}}
...
</body>
...
</html>
you can also pass it directly to your javascript code and handle it like a json if need be
<script>
const jsonn = `{{jsonn | safe}}`
</script>

In Django, how to render both JSON data with api and info from context dictionary

In Django, I want to render a page that includes a Chart js chart that relies on data from my database. I believe I need to implement an API for this. The same page with the chart will contain other info from the database that I think is rendered with a context dictionary and {{ variable }}. I know how to do one or the the other, but not both on the same page. Here is what I have so far. In views.py:
from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from django.views import generic
from django.views.generic import View
from .models import Article
from rest_framework.views import APIView
from rest_framework.response import Response
class ChartData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, format=None):
articles = Article.objects.all()
correlationlist = []
nocorrelationlist = []
for corr in articles:
if corr.correlation_type == "Correlation":
correlationlist.append(1)
nocorrelationlist.append(0)
elif corr.correlation_type == "No Correlation":
correlationlist.append(0)
nocorrelationlist.append(1)
else:
pass
correlation_items = correlationlist
nocorrelation_items = nocorrelationlist
data = {
"correlation_items": correlation_items,
"nocorrelation_items": nocorrelation_items,
}
return Response(data)
The Javascript I have on the page where the chart appears is:
$(document).ready(function(){
var endpoint = 'api/chart/data/'
var defaultData1 = []
var defaultData2 = [];
$.ajax({
method: "GET",
url: endpoint,
success: function(data){
defaultData1 = data.correlation_items
defaultData2 = data.nocorrelation_items
setChart()
},
error: function(error_data){
console.log("error")
console.log(error_data)
}
})
function setChart(){
CHART js code goes here
}
})
But on the page where the chart appears, I also want to include other information from the data base, such as:
The title of the article is: {{ title }}
To do this and to render the page, I think I need to create a function in views as follows:
def results(request):
myresults = Article.objects.all()
context = {'myresults': myresults}
return render(request, 'my_page_with_results_and_chart.html', context)
In short, how do you render a page that pulls data from an API for a chart, but also gets database information from the render(request, 'page.html', context) method? This is driving me crazy.
not really sure what you're asking, seems very ambiguous. From a glance it looks like, myresults = Article.objects.all() and articles = Article.objects.all() are the same and you just want to get the title from the article?
Just add articles into your data dictionary
data = {
"myresults": articles,
"correlation_items": correlation_items,
"nocorrelation_items": nocorrelation_items,
}
then just reference it in your html. You cant just do {{ title }} though like your example as 'title' is not a key to your dictionary. if 'articles' is a dictionary with title as a key, you'll need to do {{ myresults.title }} in the html. I hope i answered your question :/
I'm not sure if this is what you're asking as it seems too basic but just from your examples it looks like you are..? Anyway if you do need to make an api the Django RESTful library is really good and great documentation http://www.django-rest-framework.org/

Return variables from Flask to template without rendering the template [duplicate]

I have a function that analyzes a CSV file with Pandas and produces a dict with summary information. I want to return the results as a response from a Flask view. How do I return a JSON response?
#app.route("/summary")
def summary():
d = make_summary()
# send it back as json
A view can directly return a Python dict or list and Flask will call jsonify automatically.
#app.route("/summary")
def summary():
d = make_summary()
return d
For older Flask versions, or to return a different JSON-serializable object, import and use jsonify.
from flask import jsonify
#app.route("/summary")
def summary():
d = make_summary()
return jsonify(d)
jsonify serializes the data you pass it to JSON. If you want to serialize the data yourself, do what jsonify does by building a response with status=200 and mimetype='application/json'.
from flask import json
#app.route('/summary')
def summary():
data = make_summary()
response = app.response_class(
response=json.dumps(data),
status=200,
mimetype='application/json'
)
return response
Pass keyword arguments to flask.jsonify and they will be output as a JSON object.
#app.route('/_get_current_user')
def get_current_user():
return jsonify(
username=g.user.username,
email=g.user.email,
id=g.user.id
)
{
"username": "admin",
"email": "admin#localhost",
"id": 42
}
If you already have a dict, you can pass it directly as jsonify(d).
If you don't want to use jsonify for some reason, you can do what it does manually. Call flask.json.dumps to create JSON data, then return a response with the application/json content type.
from flask import json
#app.route('/summary')
def summary():
data = make_summary()
response = app.response_class(
response=json.dumps(data),
mimetype='application/json'
)
return response
flask.json is distinct from the built-in json module. It will use the faster simplejson module if available, and enables various integrations with your Flask app.
To return a JSON response and set a status code you can use make_response:
from flask import jsonify, make_response
#app.route('/summary')
def summary():
d = make_summary()
return make_response(jsonify(d), 200)
Inspiration taken from this comment in the Flask issue tracker.
As of version 1.1.0 Flask, if a view returns a dict it will be turned into a JSON response.
#app.route("/users", methods=['GET'])
def get_user():
return {
"user": "John Doe",
}
If you want to analyze a file uploaded by the user, the Flask quickstart shows how to get files from users and access them. Get the file from request.files and pass it to the summary function.
from flask import request, jsonify
from werkzeug import secure_filename
#app.route('/summary', methods=['GET', 'POST'])
def summary():
if request.method == 'POST':
csv = request.files['data']
return jsonify(
summary=make_summary(csv),
csv_name=secure_filename(csv.filename)
)
return render_template('submit_data.html')
Replace the 'data' key for request.files with the name of the file input in your HTML form.
Flask 1.1.x supports returning a JSON dict without calling jsonify. If you want to return something besides a dict, you still need to call jsonify.
#app.route("/")
def index():
return {
"api_stuff": "values",
}
is equivalent to
#app.route("/")
def index():
return jsonify({
"api_stuff": "values",
})
See the pull request that added this: https://github.com/pallets/flask/pull/3111
I use a decorator to return the result of jsonfiy. I think it is more readable when a view has multiple returns. This does not support returning a tuple like content, status, but I handle returning error statuses with app.errorhandler instead.
import functools
from flask import jsonify
def return_json(f):
#functools.wraps(f)
def inner(**kwargs):
return jsonify(f(**kwargs))
return inner
#app.route('/test/<arg>')
#return_json
def test(arg):
if arg == 'list':
return [1, 2, 3]
elif arg == 'dict':
return {'a': 1, 'b': 2}
elif arg == 'bool':
return True
return 'none of them'
Prior to Flask 0.11, jsonfiy would not allow returning an array directly. Instead, pass the list as a keyword argument.
#app.route('/get_records')
def get_records():
results = [
{
"rec_create_date": "12 Jun 2016",
"rec_dietary_info": "nothing",
"rec_dob": "01 Apr 1988",
"rec_first_name": "New",
"rec_last_name": "Guy",
},
{
"rec_create_date": "1 Apr 2016",
"rec_dietary_info": "Nut allergy",
"rec_dob": "01 Feb 1988",
"rec_first_name": "Old",
"rec_last_name": "Guy",
},
]
return jsonify(results=list)
In Flask 1.1, if you return a dictionary and it will automatically be converted into JSON. So if make_summary() returns a dictionary, you can
from flask import Flask
app = Flask(__name__)
#app.route('/summary')
def summary():
d = make_summary()
return d
The SO that asks about including the status code was closed as a duplicate to this one. So to also answer that question, you can include the status code by returning a tuple of the form (dict, int). The dict is converted to JSON and the int will be the HTTP Status Code. Without any input, the Status is the default 200. So in the above example the code would be 200. In the example below it is changed to 201.
from flask import Flask
app = Flask(__name__)
#app.route('/summary')
def summary():
d = make_summary()
return d, 201 # 200 is the default
You can check the status code using
curl --request GET "http://127.0.0.1:5000/summary" -w "\ncode: %{http_code}\n\n"
The answer is the same when using Flask's class-based views.
from flask import Flask, request, jsonify
from flask.views import MethodView
app = Flask(__name__)
class Summary(MethodView):
def get(self):
d = make_summary()
return jsonify(d)
app.add_url_rule('/summary/', view_func=Summary.as_view('summary'))
if its a dict, flask can return it directly (Version 1.0.2)
def summary():
d = make_summary()
return d, 200
To serialize an object, use jsonify from flask module to jsonify the object, a dictionary gets serialized by default. Also, if you're dealing with files you can always use make_response.
I like this way:
#app.route("/summary")
def summary():
responseBody = { "message": "bla bla bla", "summary": make_summary() }
return make_response(jsonify(responseBody), 200)

User Must Take Action for Page Redirection to Work in Flask

I have a /map route in my Flask app. In this map, when an object is selected, a button is shown to redirect to the next page (let's call it /demo). Currently this demo page is accessible by everyone if you type in the url as www.mysite.com/demo.
How can I make it so that the user must select an object on the /map page first in order to be allowed access on the /demo page?
When you select an object in the map, a GET request is fired:
Flask:
#app.route('/getID', methods=['GET'])
def getID():
ID = float(request.args.get("ID",""))
objectCollection = db["objects"]
for object in objectCollection.find({"properties.ID":ID},{"properties.ID":1,"properties.Address":1,"_id":0}).limit(1):
return jsonify(selectedObject=object)
JS:
var objectIDClick = event.feature.getProperty('ID');
$.ajax({
url: '/getID?' + 'ID=' + objectIDClick,
type: 'GET',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
async: false,
success: function (data) { ... }
});
If a user tries to access the /demo page without selecting an object first in the /map page, simply redirect them back to the /map page.
I know I have to use something like:
if request.method == 'GET':
return redirect(url_for('demo'))
else:
return render_template('map.html')
But I can't seem to put it all together. My routes are defined as follows:
#app.route("/map")
def map():
return render_template("map.html")
#app.route("/demo")
def demo():
return render_template("demo.html")
You could have the /demo page check the request's Referer: HTTP header. If the referrer isn't /map, then don't show the page.
Here is one implementation. For convenience, I have added the test in a Flask view decorator.
This program uses Flask and html, no Javascript. You'll need to adapt it to Javascript if required.
from urlparse import urlparse
from functools import wraps
from flask import (
Flask,
redirect,
request,
render_template_string,
url_for)
app = Flask(__name__)
def confirm_referrer(path, redirect_func):
def decorator(f):
#wraps(f)
def wrapper(*args, **kw):
ref = urlparse(request.referrer or '')
if (request.host, path) == (ref.netloc, ref.path):
return f(*args, **kw)
return redirect(url_for(redirect_func))
return wrapper
return decorator
#app.route('/map')
def map():
return render_template_string('''
<html><body>
<p>Demonstrate</p>
</body></html>''')
#app.route('/demo')
#confirm_referrer('/map', 'map')
def demo():
return '''<p>This is a demonstration</p>'''
if __name__ == '__main__':
app.run(debug=True)
EDIT: Responding to new requirements.
from urlparse import urlparse
from functools import wraps
from flask import (
Flask,
redirect,
request,
render_template_string,
url_for)
app = Flask(__name__)
def confirm_referrer(paths, redirect_func):
def decorator(f):
#wraps(f)
def wrapper(*args, **kw):
ref = urlparse(request.referrer or '')
if any((request.host, path) == (ref.netloc, ref.path)
for path in paths):
return f(*args, **kw)
return redirect(url_for(redirect_func))
return wrapper
return decorator
#app.route('/map')
def map():
return render_template_string('''
<html><body>
<p>Demonstrate</p>
</body></html>''')
#app.route('/map_ru')
def map_ru():
return render_template_string(u'''
<html><body>
<p><a href="{{ url_for("demo") }}">
\u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c
</a></p>
</body></html>''')
#app.route('/demo')
#confirm_referrer(['/map', '/map_ru'], 'map')
def demo():
return '''<p>This is a demonstration</p>'''
if __name__ == '__main__':
app.run(debug=True)

Categories