Flask, serving a React application: cannot refresh "pages" - python

So I am serving a index.html file from Flask.
The index.html file comes from a built project using: https://github.com/facebookincubator/create-react-app
I have a couple routes setup in the React app, for example:
"/users"
and "/contracts"
When I refresh one of these routes, I get a 404 from flask, but while "clicking" on links found on the site, they work perfectly fine.

When you are clicking the links in the interface, React is re-rendering the page without any server-side intervention, and then updating the route in the URL bar. When loading however, you are making that route request to the server direct, and Flask does not have that route registered.
Simplest way is to register these routes in the decorator for the function serving your homepage view
#app.route('/')
#app.route('/users')
#app.route('/contracts')
def home_page():
If there are many many routes, you may want to use the catch-all URL pattern.

Specifying every route is too prone to error. Here is a more general solution.
Add an error handler to catch 404:
#app.errorhandler(404)
def handle_404(e):
if request.method == 'GET':
return redirect(f'/?request_path={quote_plus(request.path)}')
return e
The error handler will redirect to the home page, where your React works, passing the actual requested request_path as a parameter.
In your view handler do this:
#app.route('/')
def public_page_index(request_path=None):
return render_template('index.html',
request_path=request.args.get('request_path', ''))
The index.html template will create a hidden input:
<input type="hidden" name="request_path" value="{{request_path}}">
Then the actual React path will be available for your React code to respond to. This is what I've done in my Home page component, using jquery and useHistory().
useEffect(() => {
// find if server refresh needs history
const $request_path = $('input[name=request_path]');
const val = $request_path.val();
if (val) {
$request_path.val("");
history.push(val);
return;
}
}, []);

Related

Flask background processing after render template

I'm trying to submit a form, where the user can potentially upload several files, from which a background process is triggered that will take several minutes to complete. The issue I have currently is that when the user clicks the Submit button, nothing appears to happen for the user for several seconds while the files upload. Is there a way I can redirect to a (static*) holding page while the files upload and the processing happens, and then once the background process completes, this renders a new template?
Using Python 3.6.1 and Flask 0.12.2.
* I say static for now, but at some point in the future, I wish to use AJAX to update this page to give more information to the user
N.B. I have seen several similar questions, and the general answer is to use a module like Celery to perform async operations. Unfortunately, I do not have access to Celery as I cannot download new packages due to corporate policy.
main.py:
from flask import Flask, request, render_template, url_for, redirect
app = Flask(__name__, static_url_path = "/static", static_folder = "static")
#app.route("/", methods=['GET'])
def home():
return render_template("index.html")
#app.route("/in_progress", methods=['GET', 'POST'])
def in_progress():
if request.method == 'GET':
# Do the background processing here
return render_template('result.html') # This is the final part of the process
elif request.method == 'POST':
# Upload the files and do some pre-processing with the form
# This takes a couple of seconds so appears to not be doing anything for the user
# Want to render the GET 'in_progress.html' template at the top of this to give a better user experience
return render_template('in_progress.html') # Reloads same page using GET
index.html:
...some irrelevant HTML
<form action="{{ url_for('in_progress') }}" method="POST" id="form" name="form" enctype="multipart/form-data">
...other irrelevant HTML

Implementing File Uploads From Angular to Flask

I am trying to implement file uploading for my Web tool. The front end is developed using angular and the back end is using flask. Following tutorials on the flask website I have set up the following flask app:
from flask import Flask, request
from werkzeug import secure_filename
import os
UPLOAD_FOLDER = '/home/openstack/Documents/Confmrg/bcknd/uploads'
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
#app.route('/uploader' , methods = ['GET' , 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['file']
filename = secure_filename(f.filename)
f.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'File Uploaded'
if __name__ == '__main__':
app.run(debug = True)
I run this and the web server is hosted on http://localhost:5000
So on my angular component html I place the following:
<form action="http://localhost:5000/uploader" method="POST" enctype = "multipart/form-data">
<input type="file" name="file" />
<input type="submit" />
</form>
However when I run the angular web page, and test it by uploading a file and clicking submit, nothing happens. There is no error, and nothing is output in the console. I must be missing something, could someone point me in the right direction?
Form onSubmit handler
To answer your immediate question, what's happening is input type submit in Angular calls the onSubmit method of the form (instead of submitting the form like in plain HTML). And because you don't have a handler for onSubmit in your class, nothing is happening.
For a quick test, follow this link to create a simple onSubmit handler method to test that your submit button works.
Here's a Stackblitz example which logs to console when you click the submit button: https://stackblitz.com/edit/angular-uy481f
File upload
To make file upload work, you would need to make a few things. This touches the component class, creating a new service and injecting it, and updating your form to bind it to the class.
Create a proper Angular form. Here's an example.
Create a method that will handle the onSubmit() of the form.
Create a service that will handle Http calls to upload files.
Inject the service into your class, and call the file upload method of that class.
As you can see, there's a lot involved in making this work unlike having a simple post form in the template. As such, it will be too much for a single answer.
But hopefully, the initial paragraph answered your question and the rest of the answer pointed you in the right direction.

Is there a way to test if flask template contains a link?

I've been testing whether routes exist using
def test_index(self):
r = self.app.get("/")
self.assertEqual(200, r.status_code, "Status code was not 'OK'.")
My template has a hyperlink to another page. Is there a way to test if this exists?
Well, if you are testing templates, every template you render is the result of a request to some route. If you render url's in a template using url_for(), then it will raise a BuildError if the url is pointing to a non existing route, and the server will return the status code 500. Therefore, you don't need to parse your templates manually for testing purposes if you just check the route instead.
Example:
from flask import Flask, render_template_string
app = Flask(__name__)
#app.route('/index')
def index():
return render_template_string("""
{{ url_for('index') }}
{{ url_for('blabla') }}
""")
def test_index(self):
r = self.app.get("/index")
self.assertEqual(200, r.status_code, "Status code was not 'OK'.")
This will result in a
routing.BuildError: Could not build url for endpoint 'blabla'. Did you mean 'static' instead? error, which makes your tests fail.
I hope this explanation is clear enough!

How to overcome the forced authentication of oauth_aware?

I was going through some examples on how to implement oauth2 with a Google App Engine application and after deploying, I noticed that my #decorator.oauth_aware redirects the user to log-in page by default...
I found a similar question here but there is no actual solution on how to go about with it :
class RootHandler(handlers.BaseHandler):
#decorator.oauth_aware
def get(self):
variables = {'has_credentials' : decorator.has_credentials()}
if decorator.has_credentials():
#do something
variables.update( {
'url': decorator.authorize_url()
})
return self.render('index.html', variables)
What I want is, if the user is not logged-in/doesn't have credentials, display the index page with a sign in button (that has the authorisation url) and only afterwards redirect to root again and display further info.

Pass url params from flask to angular

I have a flask route search that serves json search results and an index one that serves a page to search from, simplified as
#app.route('/search')
def search():
res = querydb(request.args)
return jsonify(res)
#app.route('/index')
def index():
return render_template('index.html')
In index.html, I have search forms linked to angular variables, and a button that queries /search using angular's $http and url params from the search forms.
I would like the ability to additionally fetch initial results based on the url (for example, let the url /index?color=red load the /index page and fetch results from /search?color=red on load).
To do this, I'm redefinig the jinja template tags as <%blah%> (to not interfere with angular's), and rendering the page with render_template('index.html', color='red'), with a snippet in the html like
<div ng-controller="MainCtrl" ng-init="fetch('<%color%>')"> </div>
There mustbe a better way to send the params from flask to angular (trying $routeParams or $location.search() doesn't seem to work with flask, returning empty objects). Or should I be composing the views differently somehow?
You should handle your page routes from angular and your API endpoints from flask. So your flask file might look like:
#app.route('/api/search')
def search():
res = querydb(request.args)
return jsonify(res)
#app.route('/')
def index():
return render_template('index.html')
Angular App
var app = angular.module('myApp', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/index', {
templateUrl: 'mainView.html'
controller: 'MainCtrl'
});
});
controller
var MainCtrl = function($location, $http, $scope) {
//Now you can access the search params
var searchParams = $location.search();
$http.get('/api/search?color=' + searchParams.color).success(function(data) {
$scope.results = data;
});
}
Now if you go to http://somedomain/#/index?color=red it should fetch the initial results. Note that Angular handels the part of the url after the /#. Also you'll need to include the angular-route.js file to get routing working in angular.
When you're using angular routing your index.html file will just have all the boiler plate stuff that you want to include in each view and the layout for your page will go into mainView.html. index.html will need to have <div ng-view></div> somewhere in the body to tell angular where to inject mainView.html

Categories

Resources