qwerty
qwerty

Reputation: 546

Deploy Flask app on a given directory: static files not found

I am trying to deploy a Flask web to a Debian server. The files' structure is the following:

run.py
config.py
requirements.txt
/app
    constants.py
    errors.py
    forms.py
    __init__.py
    models.py
    routes.py
    /static
        /css
            /vendor
                bulma.css
        /js
            /vendor
                fontawesome.js

    /templates
        index.html
        login.html
        404.html

In order to deploy it on the server, I create a virtualenv and install the requirements.txt. Then, I start the web running the Python file run.py. When then I try to load the index page or the login page, it is unable to find any of the routing paths (although it works fine locally). Also, it is unable to locate the CSS and JS files. Instead of that, it fires the error handler on errors.py, raising an HTML error 404. The content of the files where I think the problem might be located is listed bellow:

Content of run.py

from app import app, db, socketio
from flask_assets import Environment, Bundle
from flask import Flask

if __name__ == '__main__':
    assets = Environment(app)
    assets.register('mainCss', \
        'css/vendor/bulma.css', filters='cssmin')
    assets.register('mainJs', \
        'js/own.js', 'js/vendor/fontawesome.js', filters='jsmin')

    socketio.run(app, debug=False, port=80, host= '0.0.0.0')

Content of config.py

import os
basedir = os.path.abspath(os.path.dirname(__file__))


class Config(object):
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'app.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    MAIL_SERVER = os.environ.get('MAIL_SERVER')
    MAIL_PORT = int(os.environ.get('MAIL_PORT') or 25)
    MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS') is not None
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
    ADMINS = ['[email protected]']

Content of /app/__init__.py

import logging
from logging.handlers import SMTPHandler, RotatingFileHandler
import os
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from config import Config
from flask_socketio import SocketIO
from threading import Lock

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login = LoginManager(app)

thread = None
thread_lock = Lock()
socketio = SocketIO(app)

from app import routes, models, errors

Content of /app/routes.py

from flask import render_template, flash, redirect, url_for, request
from app import app, db
from app.forms import LoginForm

@app.route('/login', methods=['GET', 'POST'])
def login():

    form = LoginForm()
    if form.validate_on_submit():
        #Validate login
        return redirect(url_for('index'))
    return render_template('login.html', title='Login', form=form)

@app.route('/index', methods=['GET', 'POST'])
def index():
    return render_template('index.html', title='Index')

Therefore, my question is: why does this work locally but it fails to find the paths of the static files when deployed to a Debian server and how can I solve this issue?

EDIT: I've just realised that the problem is that on the server, my page is hosted on an extra directory, such as www.myweb.com/directory, while when running local my webpage is on localhost:5000/, therefore the page is unable to locate the CSS and JS files on the server due to the extra directory. Now I am trying to figure out how to couple with this extra level on the url. I believe this could be done with something like this, but for the time being I am not able to find the correct implementation for this issue.

Upvotes: 3

Views: 1321

Answers (1)

Gabriel Cappelli
Gabriel Cappelli

Reputation: 4190

You can use DispatcherMiddleware

The first parameter of the DispatcherMiddleware constructor is the fallback application, in case no paths are found, if we pass the original wsgi_app your application will still be available in root path /.

The second parameter is a dictionary of prefixes: applications. {'/directory': app} will make requests of /directory be forwarded to your app

Here's how you can glue everything together

from app import app, db, socketio
from flask_assets import Environment, Bundle
from flask import Flask
from werkzeug.middleware.dispatcher import DispatcherMiddleware

if __name__ == '__main__':
    assets = Environment(app)
    assets.register('mainCss', \
        'css/vendor/bulma.css', filters='cssmin')
    assets.register('mainJs', \
        'js/own.js', 'js/vendor/fontawesome.js', filters='jsmin')

    app.wsgi_app = DispatcherMiddleware( 
           app.wsgi_app, # If path is not found use the original wsgi_app
           {'/directory': app} # If prefix is /directory pass it to app
    )
    socketio.run(app, debug=False, port=80, host= '0.0.0.0')

Upvotes: 2

Related Questions