Ejemplo n.º 1
0
def create_app(dev_server=False):
    app = Flask(__name__)
    app.config.from_envvar('SETTINGS_FILE')
    app.jinja_options['extensions'].append('jinja2.ext.do')

    if install_logging:
        create_logging_manager(app)
        # Flask has now kindly installed its own log handler which we will summarily remove.
        app.logger.propagate = 1
        app.logger.handlers = []
        if not app.debug:
            logging.root.setLevel(logging.INFO)
        else:
            logging.root.setLevel(logging.DEBUG)

    from apps.metrics import request_duration, request_total

    # Must be run before crsf.init_app
    @app.before_request
    def before_request():
        request._start_time = time.time()

    @app.after_request
    def after_request(response):
        try:
            request_duration.labels(
                request.endpoint,
                request.method).observe(time.time() - request._start_time)
        except AttributeError:
            logging.exception(
                "Request without _start_time - check app.before_request ordering"
            )
        request_total.labels(request.endpoint, request.method,
                             response.status_code).inc()
        return response

    for extension in (cdn, csrf, cache, db, mail, assets, toolbar):
        extension.init_app(app)

    cors_origins = ['https://map.emfcamp.org', 'https://wiki.emfcamp.org']
    if app.config.get('DEBUG'):
        cors_origins = ['http://localhost:8080', 'https://maputnik.github.io']
    CORS(app,
         resources={r"/api/*": {
             "origins": cors_origins
         }},
         supports_credentials=True)

    migrate.init_app(app, db)

    login_manager.setup_app(app, add_context_processor=True)
    app.login_manager.login_view = 'users.login'

    from models.user import User, load_anonymous_user
    from models import site_state, feature_flag

    @login_manager.user_loader
    def load_user(userid):
        user = User.query.filter_by(id=userid).first()
        if user:
            set_user_id(user.email)
        return user

    login_manager.anonymous_user = load_anonymous_user

    if app.config.get('TICKETS_SITE'):
        global gocardless_client
        gocardless_client = gocardless_pro.Client(
            access_token=app.config['GOCARDLESS_ACCESS_TOKEN'],
            environment=app.config['GOCARDLESS_ENVIRONMENT'])
        stripe.api_key = app.config['STRIPE_SECRET_KEY']

        @app.before_request
        def load_per_request_state():
            site_state.get_states()
            feature_flag.get_db_flags()

    if app.config.get('NO_INDEX'):
        # Prevent staging site from being displayed on Google
        @app.after_request
        def send_noindex_header(response):
            response.headers['X-Robots-Tag'] = 'noindex, nofollow'
            return response

    @app.before_request
    def simple_cache_warning():
        if not dev_server and app.config.get('CACHE_TYPE', 'null') == 'simple':
            logging.warn(
                'Per-process cache being used outside dev server - refreshing will not work'
            )

    @app.after_request
    def send_security_headers(response):
        use_hsts = app.config.get('HSTS', False)
        if use_hsts:
            max_age = app.config.get('HSTS_MAX_AGE', 3600 * 24 * 7 * 4)
            response.headers[
                'Strict-Transport-Security'] = 'max-age=%s' % max_age

        response.headers['X-Frame-Options'] = 'deny'
        response.headers['X-Content-Type-Options'] = 'nosniff'

        return response

    @app.errorhandler(404)
    def handle_404(e):
        return render_template('errors/404.html'), 404

    @app.errorhandler(500)
    def handle_500(e):
        return render_template('errors/500.html'), 500

    @app.shell_context_processor
    def shell_imports():
        ctx = {}

        # Import models and constants
        import models
        for attr in dir(models):
            if attr[0].isupper():
                ctx[attr] = getattr(models, attr)

        # And just for convenience
        ctx['db'] = db

        return ctx

    from apps.common import load_utility_functions
    load_utility_functions(app)

    from apps.base import base
    from apps.metrics import metrics
    from apps.users import users
    from apps.tickets import tickets
    from apps.payments import payments
    from apps.cfp import cfp
    from apps.cfp_review import cfp_review
    from apps.schedule import schedule
    from apps.arrivals import arrivals
    from apps.api import api_bp
    app.register_blueprint(base)
    app.register_blueprint(users)
    app.register_blueprint(metrics)
    app.register_blueprint(tickets)
    app.register_blueprint(payments)
    app.register_blueprint(cfp)
    app.register_blueprint(cfp_review, url_prefix='/cfp-review')
    app.register_blueprint(schedule)
    app.register_blueprint(arrivals, url_prefix='/arrivals')
    app.register_blueprint(api_bp, url_prefix='/api')

    if app.config.get('VOLUNTEERS'):
        from apps.volunteer import volunteer
        app.register_blueprint(volunteer, url_prefix='/volunteer')

        from flask_admin import Admin
        from apps.volunteer.flask_admin_base import VolunteerAdminIndexView

        global volunteer_admin
        volunteer_admin = Admin(
            url='/volunteer/admin',
            name='EMF Volunteers',
            template_mode='bootstrap3',
            index_view=VolunteerAdminIndexView(url='/volunteer/admin'),
            base_template='volunteer/admin/flask-admin-base.html')
        volunteer_admin.endpoint_prefix = 'volunteer_admin'
        volunteer_admin.init_app(app)

        import apps.volunteer.admin  # noqa: F401

    from apps.admin import admin
    app.register_blueprint(admin, url_prefix='/admin')

    return app
Ejemplo n.º 2
0
def create_app(dev_server=False, config_override=None):
    app = Flask(__name__)
    app.config.from_envvar("SETTINGS_FILE")
    if config_override:
        app.config.from_mapping(config_override)
    app.jinja_options["extensions"].append("jinja2.ext.do")

    if install_logging:
        create_logging_manager(app)
        # Flask has now kindly installed its own log handler which we will summarily remove.
        app.logger.propagate = 1
        app.logger.handlers = []
        if not app.debug:
            logging.root.setLevel(logging.INFO)
        else:
            logging.root.setLevel(logging.DEBUG)

    from apps.metrics import request_duration, request_total

    # Must be run before crsf.init_app
    @app.before_request
    def before_request():
        request._start_time = time.time()

    @app.after_request
    def after_request(response):
        try:
            request_duration.labels(
                request.endpoint,
                request.method).observe(time.time() - request._start_time)
        except AttributeError:
            logging.exception(
                "Request without _start_time - check app.before_request ordering"
            )
        request_total.labels(request.endpoint, request.method,
                             response.status_code).inc()
        return response

    for extension in (csrf, cache, db, mail, static_digest, toolbar):
        extension.init_app(app)

    def log_email(message, app):
        app.logger.info("Emailing %s: %r", message.recipients, message.subject)

    email_dispatched.connect(log_email)

    cors_origins = ["https://map.emfcamp.org", "https://wiki.emfcamp.org"]
    if app.config.get("DEBUG"):
        cors_origins = ["http://localhost:8080", "https://maputnik.github.io"]
    CORS(app,
         resources={r"/api/*": {
             "origins": cors_origins
         }},
         supports_credentials=True)

    migrate.init_app(app, db)

    login_manager.init_app(app, add_context_processor=True)
    app.login_manager.login_view = "users.login"

    from models.user import User, load_anonymous_user
    from models import site_state, feature_flag

    @login_manager.user_loader
    def load_user(userid):
        user = User.query.filter_by(id=userid).first()
        if user:
            set_user_id(user.email)
        return user

    login_manager.anonymous_user = load_anonymous_user

    global gocardless_client
    gocardless_client = gocardless_pro.Client(
        access_token=app.config["GOCARDLESS_ACCESS_TOKEN"],
        environment=app.config["GOCARDLESS_ENVIRONMENT"],
    )
    stripe.api_key = app.config["STRIPE_SECRET_KEY"]
    pytransferwise.environment = app.config["TRANSFERWISE_ENVIRONMENT"]
    pytransferwise.api_key = app.config["TRANSFERWISE_API_TOKEN"]

    @app.before_request
    def load_per_request_state():
        site_state.get_states()
        feature_flag.get_db_flags()

    if app.config.get("NO_INDEX"):
        # Prevent staging site from being displayed on Google
        @app.after_request
        def send_noindex_header(response):
            response.headers["X-Robots-Tag"] = "noindex, nofollow"
            return response

    @app.before_request
    def simple_cache_warning():
        if not dev_server and app.config.get("CACHE_TYPE", "null") == "simple":
            logging.warning(
                "Per-process cache being used outside dev server - refreshing will not work"
            )

    @app.context_processor
    def add_csp_nonce():
        g.csp_nonce = secrets.token_urlsafe(16)
        return {"csp_nonce": g.csp_nonce}

    @app.after_request
    def send_security_headers(response):
        use_hsts = app.config.get("HSTS", False)
        if use_hsts:
            max_age = app.config.get("HSTS_MAX_AGE", 3600 * 24 * 30 * 6)
            response.headers[
                "Strict-Transport-Security"] = "max-age=%s" % max_age

        response.headers["X-Frame-Options"] = "deny"
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"

        csp = {
            "script-src": ["'self'", "https://js.stripe.com"],
            "style-src":
            ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
            # Note: the below is more strict as it only allows inline styles in style=
            # attributes, however it's unsupported by Safari at this time...
            #  "style-src-attr": ["'unsafe-inline'"],
            "font-src": ["'self'", "https://fonts.gstatic.com"],
            "frame-src": [
                "https://js.stripe.com/",
                "https://media.ccc.de",
                "https://www.youtube.com",
                "https://archive.org",
            ],
        }

        # Edit record hash to support the modal dialogues in flask-admin
        csp["script-src"].append(
            "'sha256-Jxve8bBSodQplIZw4Y1walBJ0hFTx8sZ5xr+Pjr/78Y='")

        # View record hash to support the modal dialogues in flask-admin
        csp["script-src"].append(
            "'sha256-XOlW2U5UiDeV2S/HgKqbp++Fo1I5uiUT2thFRUeFW/g='")

        if app.config.get("DEBUG_TB_ENABLED"):
            # This hash is for the flask debug toolbar. It may break once they upgrade it.
            csp["script-src"].append(
                "'sha256-zWl5GfUhAzM8qz2mveQVnvu/VPnCS6QL7Niu6uLmoWU='")

        if "csp_nonce" in g:
            csp["script-src"].append(f"'nonce-{g.csp_nonce}'")

        value = "; ".join(k + " " + " ".join(v) for k, v in csp.items())

        if app.config.get("DEBUG"):
            response.headers["Content-Security-Policy"] = value
        else:
            response.headers["Content-Security-Policy-Report-Only"] = (
                value +
                "; report-uri https://emfcamp.report-uri.com/r/d/csp/reportOnly"
            )
            response.headers[
                "Report-To"] = '{"group":"default","max_age":31536000,"endpoints":[{"url":"https://emfcamp.report-uri.com/a/d/g"}],"include_subdomains":false}'

            # Disable Network Error Logging.
            # This doesn't seem to be very useful and it's using up our report-uri quota.
            response.headers["NEL"] = '{"max_age":0}'
        return response

    if not app.debug:

        @app.errorhandler(Exception)
        def handle_exception(e):
            """ Generic exception handler to catch and log unhandled exceptions in production. """
            if isinstance(e, HTTPException):
                # HTTPException is used to implement flask's HTTP errors so pass it through.
                return e

            app.logger.exception("Unhandled exception in request: %s", request)
            return render_template("errors/500.html"), 500

    @app.errorhandler(404)
    def handle_404(e):
        return render_template("errors/404.html"), 404

    @app.errorhandler(500)
    def handle_500(e):
        return render_template("errors/500.html"), 500

    @app.shell_context_processor
    def shell_imports():
        ctx = {}

        # Import models and constants
        import models

        for attr in dir(models):
            if attr[0].isupper():
                ctx[attr] = getattr(models, attr)

        # And just for convenience
        ctx["db"] = db

        return ctx

    from apps.common import load_utility_functions

    load_utility_functions(app)

    from apps.base import base
    from apps.metrics import metrics
    from apps.users import users
    from apps.tickets import tickets
    from apps.payments import payments
    from apps.cfp import cfp
    from apps.cfp_review import cfp_review
    from apps.schedule import schedule
    from apps.arrivals import arrivals
    from apps.api import api_bp
    from apps.villages import villages

    app.register_blueprint(base)
    app.register_blueprint(users)
    app.register_blueprint(metrics)
    app.register_blueprint(tickets)
    app.register_blueprint(payments)
    app.register_blueprint(cfp)
    app.register_blueprint(cfp_review, url_prefix="/cfp-review")
    app.register_blueprint(schedule)
    app.register_blueprint(arrivals, url_prefix="/arrivals")
    app.register_blueprint(api_bp, url_prefix="/api")
    app.register_blueprint(villages, url_prefix="/villages")

    if app.config.get("VOLUNTEERS"):
        from apps.volunteer import volunteer

        app.register_blueprint(volunteer, url_prefix="/volunteer")

        from flask_admin import Admin
        from apps.volunteer.flask_admin_base import VolunteerAdminIndexView

        global volunteer_admin
        volunteer_admin = Admin(
            url="/volunteer/admin",
            name="EMF Volunteers",
            template_mode="bootstrap3",
            index_view=VolunteerAdminIndexView(url="/volunteer/admin"),
            base_template="volunteer/admin/flask-admin-base.html",
        )
        volunteer_admin.endpoint_prefix = "volunteer_admin"
        volunteer_admin.init_app(app)

        import apps.volunteer.admin  # noqa: F401

    from apps.admin import admin

    app.register_blueprint(admin, url_prefix="/admin")

    from apps.notification import notify

    app.register_blueprint(notify, url_prefix="/notify")

    return app
Ejemplo n.º 3
0
def create_app(dev_server=False):
    app = Flask(__name__)
    app.config.from_envvar("SETTINGS_FILE")
    app.jinja_options["extensions"].append("jinja2.ext.do")

    if install_logging:
        create_logging_manager(app)
        # Flask has now kindly installed its own log handler which we will summarily remove.
        app.logger.propagate = 1
        app.logger.handlers = []
        if not app.debug:
            logging.root.setLevel(logging.INFO)
        else:
            logging.root.setLevel(logging.DEBUG)

    from apps.metrics import request_duration, request_total

    # Must be run before crsf.init_app
    @app.before_request
    def before_request():
        request._start_time = time.time()

    @app.after_request
    def after_request(response):
        try:
            request_duration.labels(
                request.endpoint,
                request.method).observe(time.time() - request._start_time)
        except AttributeError:
            logging.exception(
                "Request without _start_time - check app.before_request ordering"
            )
        request_total.labels(request.endpoint, request.method,
                             response.status_code).inc()
        return response

    for extension in (csrf, cache, db, mail, static_digest, toolbar):
        extension.init_app(app)

    def log_email(message, app):
        app.logger.info("Emailing %s: %r", message.recipients, message.subject)

    email_dispatched.connect(log_email)

    cors_origins = ["https://map.emfcamp.org", "https://wiki.emfcamp.org"]
    if app.config.get("DEBUG"):
        cors_origins = ["http://localhost:8080", "https://maputnik.github.io"]
    CORS(app,
         resources={r"/api/*": {
             "origins": cors_origins
         }},
         supports_credentials=True)

    migrate.init_app(app, db)

    login_manager.init_app(app, add_context_processor=True)
    app.login_manager.login_view = "users.login"

    from models.user import User, load_anonymous_user
    from models import site_state, feature_flag

    @login_manager.user_loader
    def load_user(userid):
        user = User.query.filter_by(id=userid).first()
        if user:
            set_user_id(user.email)
        return user

    login_manager.anonymous_user = load_anonymous_user

    global gocardless_client
    gocardless_client = gocardless_pro.Client(
        access_token=app.config["GOCARDLESS_ACCESS_TOKEN"],
        environment=app.config["GOCARDLESS_ENVIRONMENT"],
    )
    stripe.api_key = app.config["STRIPE_SECRET_KEY"]

    @app.before_request
    def load_per_request_state():
        site_state.get_states()
        feature_flag.get_db_flags()

    if app.config.get("NO_INDEX"):
        # Prevent staging site from being displayed on Google
        @app.after_request
        def send_noindex_header(response):
            response.headers["X-Robots-Tag"] = "noindex, nofollow"
            return response

    @app.before_request
    def simple_cache_warning():
        if not dev_server and app.config.get("CACHE_TYPE", "null") == "simple":
            logging.warn(
                "Per-process cache being used outside dev server - refreshing will not work"
            )

    @app.after_request
    def send_security_headers(response):
        use_hsts = app.config.get("HSTS", False)
        if use_hsts:
            max_age = app.config.get("HSTS_MAX_AGE", 3600 * 24 * 30 * 6)
            response.headers[
                "Strict-Transport-Security"] = "max-age=%s" % max_age

        response.headers["X-Frame-Options"] = "deny"
        response.headers["X-Content-Type-Options"] = "nosniff"

        return response

    if not app.debug:

        @app.errorhandler(Exception)
        def handle_exception(e):
            """ Generic exception handler to catch and log unhandled exceptions in production. """
            if isinstance(e, HTTPException):
                # HTTPException is used to implement flask's HTTP errors so pass it through.
                return e

            app.logger.exception("Unhandled exception in request: %s", request)
            return render_template("errors/500.html"), 500

    @app.errorhandler(404)
    def handle_404(e):
        return render_template("errors/404.html"), 404

    @app.errorhandler(500)
    def handle_500(e):
        return render_template("errors/500.html"), 500

    @app.shell_context_processor
    def shell_imports():
        ctx = {}

        # Import models and constants
        import models

        for attr in dir(models):
            if attr[0].isupper():
                ctx[attr] = getattr(models, attr)

        # And just for convenience
        ctx["db"] = db

        return ctx

    from apps.common import load_utility_functions

    load_utility_functions(app)

    from apps.base import base
    from apps.metrics import metrics
    from apps.users import users
    from apps.tickets import tickets
    from apps.payments import payments
    from apps.cfp import cfp
    from apps.cfp_review import cfp_review
    from apps.schedule import schedule
    from apps.arrivals import arrivals
    from apps.api import api_bp

    app.register_blueprint(base)
    app.register_blueprint(users)
    app.register_blueprint(metrics)
    app.register_blueprint(tickets)
    app.register_blueprint(payments)
    app.register_blueprint(cfp)
    app.register_blueprint(cfp_review, url_prefix="/cfp-review")
    app.register_blueprint(schedule)
    app.register_blueprint(arrivals, url_prefix="/arrivals")
    app.register_blueprint(api_bp, url_prefix="/api")

    if app.config.get("VOLUNTEERS"):
        from apps.volunteer import volunteer

        app.register_blueprint(volunteer, url_prefix="/volunteer")

        from flask_admin import Admin
        from apps.volunteer.flask_admin_base import VolunteerAdminIndexView

        global volunteer_admin
        volunteer_admin = Admin(
            url="/volunteer/admin",
            name="EMF Volunteers",
            template_mode="bootstrap3",
            index_view=VolunteerAdminIndexView(url="/volunteer/admin"),
            base_template="volunteer/admin/flask-admin-base.html",
        )
        volunteer_admin.endpoint_prefix = "volunteer_admin"
        volunteer_admin.init_app(app)

        import apps.volunteer.admin  # noqa: F401

    from apps.admin import admin

    app.register_blueprint(admin, url_prefix="/admin")

    from apps.notification import notify

    app.register_blueprint(notify, url_prefix="/notify")

    return app
Ejemplo n.º 4
0
from flask_admin import Admin
from apps.volunteer.flask_admin_base import VolunteerAdminIndexView

volunteer_admin = Admin(
    url="/volunteer/admin",
    name="EMF Volunteers",
    template_mode="bootstrap3",
    index_view=VolunteerAdminIndexView(url="/volunteer/admin"),
    base_template="volunteer/admin/flask-admin-base.html",
)
volunteer_admin.endpoint_prefix = "volunteer_admin"

from . import role  # noqa: F401
from . import shift  # noqa: F401
from . import venue  # noqa: F401
from . import volunteer  # noqa: F401
Ejemplo n.º 5
0
def create_app(dev_server=False):
    app = Flask(__name__)
    app.config.from_envvar('SETTINGS_FILE')
    app.jinja_options['extensions'].append('jinja2.ext.do')

    if install_logging:
        create_logging_manager(app)
        # Flask has now kindly installed its own log handler which we will summarily remove.
        app.logger.propagate = 1
        app.logger.handlers = []
        if not app.debug:
            logging.root.setLevel(logging.INFO)
        else:
            logging.root.setLevel(logging.DEBUG)

    from apps.metrics import request_duration, request_total

    # Must be run before crsf.init_app
    @app.before_request
    def before_request():
        request._start_time = time.time()

    @app.after_request
    def after_request(response):
        try:
            request_duration.labels(request.endpoint, request.method).observe(
                time.time() - request._start_time)
        except AttributeError:
            logging.exception("Request without _start_time - check app.before_request ordering")
        request_total.labels(request.endpoint, request.method, response.status_code).inc()
        return response

    for extension in (cdn, csrf, cache, db, mail, assets, toolbar):
        extension.init_app(app)

    def log_email(message, app):
        app.logger.info("Emailing %s: %r", message.recipients, message.subject)

    email_dispatched.connect(log_email)

    cors_origins = ['https://map.emfcamp.org', 'https://wiki.emfcamp.org']
    if app.config.get('DEBUG'):
        cors_origins = ['http://localhost:8080', 'https://maputnik.github.io']
    CORS(app, resources={r"/api/*": {"origins": cors_origins}},
        supports_credentials=True)

    migrate.init_app(app, db)

    login_manager.setup_app(app, add_context_processor=True)
    app.login_manager.login_view = 'users.login'

    from models.user import User, load_anonymous_user
    from models import site_state, feature_flag

    @login_manager.user_loader
    def load_user(userid):
        user = User.query.filter_by(id=userid).first()
        if user:
            set_user_id(user.email)
        return user

    login_manager.anonymous_user = load_anonymous_user

    if app.config.get('TICKETS_SITE'):
        global gocardless_client
        gocardless_client = gocardless_pro.Client(access_token=app.config['GOCARDLESS_ACCESS_TOKEN'],
                                                  environment=app.config['GOCARDLESS_ENVIRONMENT'])
        stripe.api_key = app.config['STRIPE_SECRET_KEY']

        @app.before_request
        def load_per_request_state():
            site_state.get_states()
            feature_flag.get_db_flags()

    if app.config.get('NO_INDEX'):
        # Prevent staging site from being displayed on Google
        @app.after_request
        def send_noindex_header(response):
            response.headers['X-Robots-Tag'] = 'noindex, nofollow'
            return response

    @app.before_request
    def simple_cache_warning():
        if not dev_server and app.config.get('CACHE_TYPE', 'null') == 'simple':
            logging.warn('Per-process cache being used outside dev server - refreshing will not work')

    @app.after_request
    def send_security_headers(response):
        use_hsts = app.config.get('HSTS', False)
        if use_hsts:
            max_age = app.config.get('HSTS_MAX_AGE', 3600 * 24 * 7 * 4)
            response.headers['Strict-Transport-Security'] = 'max-age=%s' % max_age

        response.headers['X-Frame-Options'] = 'deny'
        response.headers['X-Content-Type-Options'] = 'nosniff'

        return response

    @app.errorhandler(404)
    def handle_404(e):
        return render_template('errors/404.html'), 404

    @app.errorhandler(500)
    def handle_500(e):
        return render_template('errors/500.html'), 500

    @app.shell_context_processor
    def shell_imports():
        ctx = {}

        # Import models and constants
        import models
        for attr in dir(models):
            if attr[0].isupper():
                ctx[attr] = getattr(models, attr)

        # And just for convenience
        ctx['db'] = db

        return ctx

    from apps.common import load_utility_functions
    load_utility_functions(app)

    from apps.base import base
    from apps.metrics import metrics
    from apps.users import users
    from apps.tickets import tickets
    from apps.payments import payments
    from apps.cfp import cfp
    from apps.cfp_review import cfp_review
    from apps.schedule import schedule
    from apps.arrivals import arrivals
    from apps.api import api_bp
    app.register_blueprint(base)
    app.register_blueprint(users)
    app.register_blueprint(metrics)
    app.register_blueprint(tickets)
    app.register_blueprint(payments)
    app.register_blueprint(cfp)
    app.register_blueprint(cfp_review, url_prefix='/cfp-review')
    app.register_blueprint(schedule)
    app.register_blueprint(arrivals, url_prefix='/arrivals')
    app.register_blueprint(api_bp, url_prefix='/api')

    if app.config.get('VOLUNTEERS'):
        from apps.volunteer import volunteer
        app.register_blueprint(volunteer, url_prefix='/volunteer')

        from flask_admin import Admin
        from apps.volunteer.flask_admin_base import VolunteerAdminIndexView

        global volunteer_admin
        volunteer_admin = Admin(url='/volunteer/admin', name='EMF Volunteers',
                                template_mode='bootstrap3',
                                index_view=VolunteerAdminIndexView(url='/volunteer/admin'),
                                base_template='volunteer/admin/flask-admin-base.html')
        volunteer_admin.endpoint_prefix = 'volunteer_admin'
        volunteer_admin.init_app(app)

        import apps.volunteer.admin  # noqa: F401

    from apps.admin import admin
    app.register_blueprint(admin, url_prefix='/admin')

    from apps.notification import notify
    app.register_blueprint(notify, url_prefix='/notify')

    return app