diff --git a/README.md b/README.md
index 9318bd60..6ae24d2b 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,12 @@ To run with a specific template theme created:
python -m caimira.apps.calculator --theme=caimira/apps/templates/{theme}
```
+To run the entire app in a different `APPLICATION_ROOT` path:
+
+```
+python -m caimira.apps.calculator --app_root=/myroot
+```
+
To run the calculator on a different URL path:
```
diff --git a/app-config/caimira-webservice/app.sh b/app-config/caimira-webservice/app.sh
index 0d0452c8..40380a7e 100755
--- a/app-config/caimira-webservice/app.sh
+++ b/app-config/caimira-webservice/app.sh
@@ -6,13 +6,16 @@ if [[ "$APP_NAME" == "caimira-webservice" ]]; then
args+=("--no-debug")
fi
- if [ ! -z "$CAIMIRA_THEME" ]; then
- args+=("--theme=${CAIMIRA_THEME}")
+ if [ ! -z "$APPLICATION_ROOT" ]; then
+ args+=("--app_root=${APPLICATION_ROOT}")
fi
if [ ! -z "$CAIMIRA_CALCULATOR_PREFIX" ]; then
args+=("--prefix=${CAIMIRA_CALCULATOR_PREFIX}")
fi
+ if [ ! -z "$CAIMIRA_THEME" ]; then
+ args+=("--theme=${CAIMIRA_THEME}")
+ fi
export "ARVE_API_KEY"="$ARVE_API_KEY"
export "ARVE_CLIENT_ID"="$ARVE_CLIENT_ID"
diff --git a/app-config/docker-compose.yml b/app-config/docker-compose.yml
index cb9d3958..b2046c21 100644
--- a/app-config/docker-compose.yml
+++ b/app-config/docker-compose.yml
@@ -11,6 +11,7 @@ services:
environment:
- COOKIE_SECRET
- APP_NAME=caimira-webservice
+ - APPLICATION_ROOT=/
- CAIMIRA_CALCULATOR_PREFIX=/calculator-cern
- CAIMIRA_THEME=caimira/apps/templates/cern
user: ${CURRENT_UID}
@@ -20,6 +21,7 @@ services:
environment:
- COOKIE_SECRET
- APP_NAME=caimira-webservice
+ - APPLICATION_ROOT=/
- CAIMIRA_CALCULATOR_PREFIX=/calculator-open
user: ${CURRENT_UID}
diff --git a/app-config/openshift/deploymentconfig.yaml b/app-config/openshift/deploymentconfig.yaml
index 5e23cede..456a2d4b 100644
--- a/app-config/openshift/deploymentconfig.yaml
+++ b/app-config/openshift/deploymentconfig.yaml
@@ -205,6 +205,8 @@
value: '3'
- name: APP_NAME
value: caimira-webservice
+ - name: APPLICATION_ROOT
+ value: /
- name: CAIMIRA_CALCULATOR_PREFIX
value: /calculator-cern
- name: CAIMIRA_THEME
@@ -297,6 +299,8 @@
env:
- name: APP_NAME
value: caimira-webservice
+ - name: APPLICATION_ROOT
+ value: /
- name: CAIMIRA_CALCULATOR_PREFIX
value: /calculator-open
image: '${PROJECT_NAME}/caimira-webservice'
diff --git a/caimira/apps/calculator/__init__.py b/caimira/apps/calculator/__init__.py
index 2f42bd0d..c36e56ec 100644
--- a/caimira/apps/calculator/__init__.py
+++ b/caimira/apps/calculator/__init__.py
@@ -73,7 +73,8 @@ class BaseRequestHandler(RequestHandler):
print(traceback.format_exc())
self.finish(template.render(
user=self.current_user,
- calculator_prefix=self.settings["calculator_prefix"],
+ get_url = template.globals['get_url'],
+ get_calculator_url = template.globals["get_calculator_url"],
active_page='Error',
contents=contents
))
@@ -87,7 +88,8 @@ class Missing404Handler(BaseRequestHandler):
"page.html.j2")
self.finish(template.render(
user=self.current_user,
- calculator_prefix=self.settings["calculator_prefix"],
+ get_url = template.globals['get_url'],
+ get_calculator_url = template.globals["get_calculator_url"],
active_page='Error',
contents='Unfortunately the page you were looking for does not exist.
'
))
@@ -193,8 +195,9 @@ class LandingPage(BaseRequestHandler):
"index.html.j2")
report = template.render(
user=self.current_user,
- calculator_prefix=self.settings["calculator_prefix"],
- text_blocks=template_environment.globals['common_text'],
+ get_url = template_environment.globals['get_url'],
+ get_calculator_url = template_environment.globals['get_calculator_url'],
+ text_blocks=template_environment.globals["common_text"],
)
self.finish(report)
@@ -205,9 +208,10 @@ class AboutPage(BaseRequestHandler):
template = template_environment.get_template("about.html.j2")
report = template.render(
user=self.current_user,
- calculator_prefix=self.settings["calculator_prefix"],
+ get_url = template.globals['get_url'],
+ get_calculator_url = template.globals["get_calculator_url"],
active_page="about",
- text_blocks=template_environment.globals['common_text']
+ text_blocks=template_environment.globals["common_text"]
)
self.finish(report)
@@ -220,9 +224,10 @@ class CalculatorForm(BaseRequestHandler):
report = template.render(
user=self.current_user,
xsrf_form_html=self.xsrf_form_html(),
- calculator_prefix=self.settings["calculator_prefix"],
+ get_url = template.globals['get_url'],
+ get_calculator_url = template.globals["get_calculator_url"],
calculator_version=__version__,
- text_blocks=template_environment.globals['common_text'],
+ text_blocks=template_environment.globals["common_text"],
)
self.finish(report)
@@ -236,8 +241,9 @@ class CompressedCalculatorFormInputs(BaseRequestHandler):
except Exception as err: # noqa
self.set_status(400)
return self.finish("Invalid calculator data: it seems incomplete. Was there an error copying & pasting the URL?")
- self.redirect(f'{self.settings["calculator_prefix"]}?{args}')
-
+ template_environment = self.settings["template_environment"]
+ self.redirect(f'{template_environment.globals["get_calculator_url"]()}?{args}')
+
class ReadmeHandler(BaseRequestHandler):
def get(self):
@@ -246,8 +252,9 @@ class ReadmeHandler(BaseRequestHandler):
readme = template.render(
active_page="calculator/user-guide",
user=self.current_user,
- calculator_prefix=self.settings["calculator_prefix"],
- text_blocks=template_environment.globals['common_text'],
+ get_url = template.globals['get_url'],
+ get_calculator_url = template.globals["get_calculator_url"],
+ text_blocks=template_environment.globals["common_text"],
)
self.finish(readme)
@@ -337,28 +344,39 @@ class CasesData(BaseRequestHandler):
# If any of the 'New_cases' is 0, it means the data is not updated.
if (cases.loc[eight_days_ago:current_date]['New_cases'] == 0).any(): return self.finish('')
return self.finish(str(round(cases.loc[eight_days_ago:current_date]['New_cases'].mean())))
+
+def get_url(app_root: str, relative_path: str = '/'):
+ return app_root.rstrip('/') + relative_path.rstrip('/')
+
+def get_calculator_url(app_root: str, calculator_prefix: str, relative_path: str = '/'):
+ return app_root.rstrip('/') + calculator_prefix.rstrip('/') + relative_path.rstrip('/')
def make_app(
debug: bool = False,
+ APPLICATION_ROOT: str = '/',
calculator_prefix: str = '/calculator',
theme_dir: typing.Optional[Path] = None,
) -> Application:
static_dir = Path(__file__).absolute().parent.parent / 'static'
calculator_static_dir = Path(__file__).absolute().parent / 'static'
+
+ get_root_url = functools.partial(get_url, APPLICATION_ROOT)
+ get_root_calculator_url = functools.partial(get_calculator_url, APPLICATION_ROOT, calculator_prefix)
+
urls: typing.Any = [
- (r'/?', LandingPage),
- (r'/_c/(.*)', CompressedCalculatorFormInputs),
- (r'/about', AboutPage),
- (r'/static/(.*)', StaticFileHandler, {'path': static_dir}),
- (calculator_prefix + r'/?', CalculatorForm),
- (calculator_prefix + r'/report', ConcentrationModel),
- (calculator_prefix + r'/report-json', ConcentrationModelJsonResponse),
- (calculator_prefix + r'/baseline-model/result', StaticModel),
- (calculator_prefix + r'/user-guide', ReadmeHandler),
- (calculator_prefix + r'/api/arve/v1/(.*)/(.*)', ArveData),
- (calculator_prefix + r'/cases/(.*)', CasesData),
- (calculator_prefix + r'/static/(.*)', StaticFileHandler, {'path': calculator_static_dir}),
+ (get_root_url(r'/?'), LandingPage),
+ (get_root_url(r'/_c/(.*)'), CompressedCalculatorFormInputs),
+ (get_root_url(r'/about'), AboutPage),
+ (get_root_url(r'/static/(.*)'), StaticFileHandler, {'path': static_dir}),
+ (get_root_calculator_url(r'/?'), CalculatorForm),
+ (get_root_calculator_url(r'/report'), ConcentrationModel),
+ (get_root_calculator_url(r'/report-json'), ConcentrationModelJsonResponse),
+ (get_root_calculator_url(r'/baseline-model/result'), StaticModel),
+ (get_root_calculator_url(r'/user-guide'), ReadmeHandler),
+ (get_root_calculator_url(r'/api/arve/v1/(.*)/(.*)'), ArveData),
+ (get_root_calculator_url(r'/cases/(.*)'), CasesData),
+ (get_root_calculator_url(r'/static/(.*)'), StaticFileHandler, {'path': calculator_static_dir}),
]
caimira_templates = Path(__file__).parent.parent / "templates"
@@ -372,9 +390,11 @@ def make_app(
undefined=jinja2.StrictUndefined, # fail when rendering any undefined template context variable
)
- template_environment.globals['common_text'] = markdown_tools.extract_rendered_markdown_blocks(
+ template_environment.globals["common_text"] = markdown_tools.extract_rendered_markdown_blocks(
template_environment.get_template('common_text.md.j2')
)
+ template_environment.globals['get_url']=get_root_url
+ template_environment.globals['get_calculator_url']=get_root_calculator_url
if debug:
tornado.log.enable_pretty_logging()
@@ -382,10 +402,11 @@ def make_app(
return Application(
urls,
debug=debug,
- calculator_prefix=calculator_prefix,
+ # calculator_prefix=calculator_prefix,
+ # APPLICATION_ROOT=APPLICATION_ROOT,
template_environment=template_environment,
default_handler_class=Missing404Handler,
- report_generator=ReportGenerator(loader, calculator_prefix),
+ report_generator=ReportGenerator(loader, get_root_url, get_root_calculator_url),
xsrf_cookies=True,
# COOKIE_SECRET being undefined will result in no login information being
# presented to the user.
diff --git a/caimira/apps/calculator/__main__.py b/caimira/apps/calculator/__main__.py
index 7ec8b69a..d9c46bd5 100644
--- a/caimira/apps/calculator/__main__.py
+++ b/caimira/apps/calculator/__main__.py
@@ -16,6 +16,11 @@ def configure_parser(parser) -> argparse.ArgumentParser:
help="A directory containing extensions for templates and static data",
default=None,
)
+ parser.add_argument(
+ "--app_root",
+ help="Change the APPLICATION_ROOT of the app",
+ default="/"
+ )
parser.add_argument(
"--prefix",
help="Change the URL path prefix to the calculator app",
@@ -36,7 +41,7 @@ def main():
if theme_dir is not None:
theme_dir = Path(theme_dir).absolute()
assert theme_dir.exists()
- app = make_app(debug=args.no_debug, calculator_prefix=args.prefix, theme_dir=theme_dir)
+ app = make_app(debug=args.no_debug, APPLICATION_ROOT=args.app_root, calculator_prefix=args.prefix, theme_dir=theme_dir)
app.listen(args.port)
IOLoop.instance().start()
diff --git a/caimira/apps/calculator/report_generator.py b/caimira/apps/calculator/report_generator.py
index 81a64543..1dcfa00d 100644
--- a/caimira/apps/calculator/report_generator.py
+++ b/caimira/apps/calculator/report_generator.py
@@ -155,7 +155,7 @@ def calculate_report_data(form: FormData, model: models.ExposureModel) -> typing
}
-def generate_permalink(base_url, calculator_prefix, form: FormData):
+def generate_permalink(base_url, get_root_url, get_root_calculator_url, form: FormData):
form_dict = FormData.to_dict(form, strip_defaults=True)
# Generate the calculator URL arguments that would be needed to re-create this
@@ -165,8 +165,8 @@ def generate_permalink(base_url, calculator_prefix, form: FormData):
# Then zlib compress + base64 encode the string. To be inverted by the
# /_c/ endpoint.
compressed_args = base64.b64encode(zlib.compress(args.encode())).decode()
- qr_url = f"{base_url}/_c/{compressed_args}"
- url = f"{base_url}{calculator_prefix}?{args}"
+ qr_url = f"{base_url}{get_root_url()}/_c/{compressed_args}"
+ url = f"{base_url}{get_root_calculator_url()}?{args}"
return {
'link': url,
@@ -342,7 +342,8 @@ def comparison_report(
@dataclasses.dataclass
class ReportGenerator:
jinja_loader: jinja2.BaseLoader
- calculator_prefix: str
+ get_root_url: typing.Any
+ get_root_calculator_url: typing.Any
def build_report(
self,
@@ -377,8 +378,9 @@ class ReportGenerator:
context['alternative_scenarios'] = comparison_report(
form, report_data, alternative_scenarios, scenario_sample_times, executor_factory=executor_factory,
)
- context['permalink'] = generate_permalink(base_url, self.calculator_prefix, form)
- context['calculator_prefix'] = self.calculator_prefix
+ context['permalink'] = generate_permalink(base_url, self.get_root_url, self.get_root_calculator_url, form)
+ context['get_url'] = self.get_root_url
+ context['get_calculator_url'] = self.get_root_calculator_url
return context
@@ -387,7 +389,7 @@ class ReportGenerator:
loader=self.jinja_loader,
undefined=jinja2.StrictUndefined,
)
- env.globals['common_text'] = markdown_tools.extract_rendered_markdown_blocks(
+ env.globals["common_text"] = markdown_tools.extract_rendered_markdown_blocks(
env.get_template('common_text.md.j2')
)
env.filters['non_zero_percentage'] = non_zero_percentage
@@ -401,4 +403,4 @@ class ReportGenerator:
def render(self, context: dict) -> str:
template = self._template_environment().get_template("calculator.report.html.j2")
- return template.render(**context, text_blocks=template.globals['common_text'])
+ return template.render(**context, text_blocks=template.globals["common_text"])
diff --git a/caimira/apps/templates/about.html.j2 b/caimira/apps/templates/about.html.j2
index 2ecce51d..36ea62e8 100644
--- a/caimira/apps/templates/about.html.j2
+++ b/caimira/apps/templates/about.html.j2
@@ -14,7 +14,7 @@ For information on the Airborne Transmission of SARS-CoV-2, feel free to check o
CAiMIRA stands for CERN Airborne Model for Indoor Risk Assessment, previously known as CARA - COVID Airborne Risk Assessment, developed in the spring of 2020 to better understand and quantify the risk of long-range airborne spread of SARS-CoV-2 virus in workplaces.
Since then, the model has evolved and now is capable of simulating the short-range component. CAiMIRA comes with different applications that allow more or less flexibility in the input parameters: