Merge branch 'feature/application_root' into 'master'

Added env variable for application root

See merge request caimira/caimira!434
This commit is contained in:
Luis Aleixo 2023-03-21 11:20:24 +01:00
commit 68dba71cc7
16 changed files with 127 additions and 88 deletions

View file

@ -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:
```

View file

@ -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"

View file

@ -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}

View file

@ -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'

View file

@ -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.<br><br><br><br>'
))
@ -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.

View file

@ -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()

View file

@ -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"])

View file

@ -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:
<ul>
<li><a href='{{ calculator_prefix }}'>CAiMIRA calculator app</a></li>
<li><a href='{{ get_calculator_url() }}'>CAiMIRA calculator app</a></li>
<li><a href='/expert-app'>CAiMIRA expert app</a></li>
</ul>

View file

@ -5,13 +5,13 @@
{% block extra_headers %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css" integrity="sha512-aOG0c6nPNzGk+5zjwyJaoRUgCdOrfSDhmMID2u4+OIslr0GjpLKo7Xm0Ao3xmpM4T8AmIouRkqwj1nrdVsLKEQ==" crossorigin="anonymous">
<link rel="stylesheet" href="{{ calculator_prefix }}/static/css/form.css">
<link rel="stylesheet" href="{{ get_calculator_url('/static/css') }}/form.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css"/>
{% endblock extra_headers %}
{% block body_scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js" integrity="sha512-uto9mlQzrs59VwILcLiRYeLKPPbS/bT71da/OEBYEwcdNUk8jYIy+D176RYoop1Da+f9mvkYrmj5MCLZWEtQuA==" crossorigin="anonymous"></script>
<script src="{{ calculator_prefix }}/static/js/form.js"></script>
<script src="{{ get_calculator_url('/static/js') }}/form.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
{% endblock body_scripts %}
@ -21,7 +21,7 @@
{% if DEBUG %}
<form id="covid_calculator" name="covid_calculator" onsubmit="return debug_submit(this)" class="form-inline">
{% else %}
<form id="covid_calculator" name="covid_calculator" action="{{ calculator_prefix }}/report" onsubmit="return validate_form(this)" method="POST">
<form id="covid_calculator" name="covid_calculator" action="{{ get_calculator_url() }}/report" onsubmit="return validate_form(this)" method="POST">
{% endif %}
{{ xsrf_form_html }}
@ -31,7 +31,7 @@
<div class="container container--padding">
<div class="d-flex header-height">
<h1 class="align-self-center">Calculator</h1>
<img src="/static/images/caimira_logo.200x200.png" class="logo_form align-self-center ml-3">
<img src="{{ get_url('/static/images') }}/caimira_logo.200x200.png" class="logo_form align-self-center ml-3">
</div>
</div>
</header>
@ -222,7 +222,7 @@
If these conditions are not met, the air exchange might not be homogenous producing an artificially lower risk further away from the window.
<br>
<br>
<img src="/static/images/nat_vent_dimensions.png" id="nat_vent_image">
<img src="{{ get_url('/static/images') }}/nat_vent_dimensions.png" id="nat_vent_image">
</div>
</div>
</div>
@ -327,14 +327,14 @@
<input type="radio" id="mask_type_1" name="mask_type" value="Type I" checked="checked" onclick="require_fields(this)">
<label for="mask_type_1">
Surgical/Type I
<img class="mask_icons" src="/static/images/masks/t1.png">
<img class="mask_icons" src="{{ get_url('/static/images/masks') }}/t1.png">
</label>
</div>
<div>
<input type="radio" id="mask_type_ffp2" name="mask_type" value="FFP2" onclick="require_fields(this)">
<label for="mask_type_ffp2">
Respirator/FFP2
<img class="mask_icons" src="/static/images/masks/ffp2.png">
<img class="mask_icons" src="{{ get_url('/static/images/masks') }}/ffp2.png">
</label>
</div>
</div>
@ -342,7 +342,7 @@
<input type="radio" id="mask_type_cloth" name="mask_type" value="Cloth" onclick="require_fields(this)">
<label for="mask_type_cloth">
Cloth
<img class="mask_icons" src="/static/images/masks/cloth.png">
<img class="mask_icons" src="{{ get_url('/static/images/masks') }}/cloth.png">
</label>
</div>
</div>
@ -717,13 +717,13 @@
<li>If coffee breaks are included, they are spread out evenly throughout the day,
in addition to any lunch break (if applicable).</li>
</ul>
Refer to <a href="{{ calculator_prefix }}/user-guide"> Calculator App user guide </a>
Refer to <a href="{{ get_calculator_url() }}/user-guide"> Calculator App user guide </a>
for more detailed explanations on how to use this tool. <br>
</div>
<div class="center container--padding pr-3 pl-3">
<button class="btn btn-primary bigButton">
<a href="{{ calculator_prefix }}/user-guide" class="{{ "nav-link active" if "user-guide" in active_page else "nav-link" }}" style="color:white">User guide</a>
<a href="{{ get_calculator_url() }}/user-guide" class="{{ "nav-link active" if "user-guide" in active_page else "nav-link" }}" style="color:white">User guide</a>
</button>
<button class="btn btn-primary bigButton ml-2" type="button" data-toggle="collapse" data-target="#collapseDisclaimer" aria-expanded="false" aria-controls="collapseDisclaimer">
Disclaimer

View file

@ -6,12 +6,12 @@
<title>Report | CAiMIRA (CERN Airborne Model for Indoor Risk Assessment)</title>
<link rel="stylesheet" type="text/css" href="{{ calculator_prefix }}/static/css/report.css">
<link rel="stylesheet" type="text/css" href="{{ get_calculator_url('/static/css') }}/report.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" type="image/x-icon" href="static/icons/favicon.ico">
<link rel="stylesheet" href="{{ get_url('/static/css') }}/style.css">
<link rel="icon" type="image/x-icon" href="{{ get_url('/static/icons') }}/favicon.ico">
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="{{ calculator_prefix }}/static/js/report.js" type="application/javascript"></script>
<script src="{{ get_calculator_url('/static/js') }}/report.js" type="application/javascript"></script>
</head>
@ -26,7 +26,7 @@
{% block report_header %}
<div id="report-header-div" class="d-flex flex-row" style="margin: 1%">
<a href="/" class="align-self-center"><img id="report_logo" src="/static/images/caimira_logo.200x200.png" class="d-inline-block align-middle mr-3"></a>
<a href="{{ get_url() }}" class="align-self-center"><img id="report_logo" src="{{ get_url('/static/images') }}/caimira_logo.200x200.png" class="d-inline-block align-middle mr-3"></a>
<div style="margin-right: -105px" class='align-self-center mr-auto'>
<h2 class="header_text mb-0"><a href="{{ permalink.shortened }}" style="color: #2f4858">REPORT - {{ form.simulation_name }}</a></h2>
<p class="mb-0" id="report_version"> Created {{ creation_date }} using CAiMIRA calculator version v{{ form.calculator_version }}</p>
@ -85,7 +85,7 @@
{% endif %}
</h6>
<br>
<img src="/static/images/long_range_anim.png" class="align-middle mb-3 pi-image">
<img src="{{ get_url('/static/images') }}/long_range_anim.png" class="align-middle mb-3 pi-image">
<div class="d-flex" style="min-height: 160px">
{% block long_range_warning_animation %}
<div class="intro-banner-vdo-play-btn animation-color m-auto d-flex align-items-center justify-content-center">
@ -106,7 +106,7 @@
With <b>short-range interactions</b>
</h6>
<br>
<img src="/static/images/short_range_anim.png" class="align-middle mb-3 pi-image">
<img src="{{ get_url('/static/images') }}/short_range_anim.png" class="align-middle mb-3 pi-image">
<div class="d-flex" style="min-height: 160px">
{% block warning_animation %}
<div class="intro-banner-vdo-play-btn animation-color m-auto d-flex align-items-center justify-content-center">
@ -609,7 +609,7 @@
<br><br><br>
<div id="disclaimer" style="border: #dee2e6 1px solid; margin: 1%; padding: 20px" class="rounded">
{% block disclaimer %}
<p class="image"> <img align="middle" src="{{ calculator_prefix }}/static/images/disclaimer.jpg" width="40" height="40"><b>Disclaimer:</b><br><br></p>
<p class="image"> <img align="middle" src="{{ get_calculator_url('/static/images') }}/disclaimer.jpg" width="40" height="40"><b>Disclaimer:</b><br><br></p>
{{ text_blocks['Disclaimer'] }}
{% endblock disclaimer %}
</div>

View file

@ -4,14 +4,14 @@
{% block main %}
<header class= "bg-light">
<div class="container container--padding">
<img src="/static/images/caimira_full_text.png" class="logo d-block m-auto" id="desktop_logo">
<img src="/static/images/caimira_full_logo.png" class="logo d-none m-auto" id="mobile_logo">
<img src="{{ get_url('/static/images') }}/caimira_full_text.png" class="logo d-block m-auto" id="desktop_logo">
<img src="{{ get_url('/static/images') }}/caimira_full_logo.png" class="logo d-none m-auto" id="mobile_logo">
</div>
</header>
<div class="container container--padding">
<div class="d-flex mb-3 justify-content-center" id="calculator_app_button">
<div><a href="{{ calculator_prefix }}" role="button" class="btn btn-outline-primary"><div class="d-flex d-row"><i class="icon-calculator"></i><span class="pl-1">Calculator</div></a></div>
<div><a href="{{ get_calculator_url() }}" role="button" class="btn btn-outline-primary"><div class="d-flex d-row"><i class="icon-calculator"></i><span class="pl-1">Calculator</div></a></div>
</div>
<div class="split">
<div class="col-lg-8 col-md-7 pl-0">
@ -20,7 +20,7 @@
<p>
CAiMIRA is a risk assessment tool developed to model the concentration of viruses in enclosed spaces, in order to inform space-management decisions.
It does this by simulating the airborne spread SARS-CoV-2 virus in a finite volume, assuming homogenous mixing for the long-range component and a two-stage jet model for short-range, and estimates the risk of COVID-19 airborne transmission therein.
Please see the <a href="/about">About</a> page for more details on the methodology, assumptions and limitations of CAiMIRA.
Please see the <a href="{{ get_url('/about') }}">About</a> page for more details on the methodology, assumptions and limitations of CAiMIRA.
</p>
<p>
The full CAiMIRA source code can be accessed freely under an Apache 2.0 open source license from our <a href="https://gitlab.cern.ch/caimira/caimira">code repository</a>.
@ -30,7 +30,7 @@
</div>
<div id="apps_section" class="d-none">
<div class="d-flex flex-row" >
<div class="pr-3"><a href="{{ calculator_prefix }}" role="button" class="btn btn-lg btn-outline-primary"><div class="d-flex d-row"><i class="icon-calculator"></i><span class="pl-2">Calculator</div></a></div>
<div class="pr-3"><a href="{{ get_calculator_url() }}" role="button" class="btn btn-lg btn-outline-primary"><div class="d-flex d-row"><i class="icon-calculator"></i><span class="pl-2">Calculator</div></a></div>
<br>
<div class="expert_app_button"><a href="/expert-app" role="button" class="btn btn-lg btn-outline-secondary"><div class="d-flex d-row"><i class="icon-expert"></i><span class="pl-2">Expert (beta)</div></a></div>
</div>
@ -38,7 +38,7 @@
</div>
</div>
<div class="align-self-center">
<img src="static/images/CAiMIRA_1_Vs3_Colour.jpg" class="caimira_home_image">
<img src="{{ get_url('/static/images') }}/CAiMIRA_1_Vs3_Colour.jpg" class="caimira_home_image">
</div>
</div>
@ -46,11 +46,11 @@
<div class="split">
<div class="w-25 mobile-sub-section"><hr width="95%">
<div class="d-flex m-2 align-items-center"><i class="bi bi-window" style="font-size: 25px"></i><div><p class="paragraph-title ml-2">Applications</p></div></div>
<div class="m-2">CAiMIRA is composed of two applications, the <a href="{{ calculator_prefix }}">Calculator</a> and the <a href="/expert-app">Expert App</a>.</div>
<div class="m-2">CAiMIRA is composed of two applications, the <a href="{{ get_calculator_url() }}">Calculator</a> and the <a href="/expert-app">Expert App</a>.</div>
</div>
<div class="w-25 mobile-sub-section"><hr width="95%">
<div class="d-flex m-2 align-items-center"><i class="bi bi-info-square-fill" style="font-size: 25px"></i><div><p class="paragraph-title ml-2">About</p></div></div>
<div class="m-2"><a href="/about">About</a> page for details on methodology, assumptions and limitations of CAiMIRA.</div>
<div class="m-2"><a href="{{ get_url('/about') }}">About</a> page for details on methodology, assumptions and limitations of CAiMIRA.</div>
</div>
<div class="w-25 mobile-sub-section"><hr width="95%">
<div class="d-flex m-2 align-items-center"><i class="bi bi-journal-text" style="font-size: 25px"></i><div><p class="paragraph-title ml-2">Documentation</p></div></div>

View file

@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, viewport-fit=cover">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta id="url_prefix" data-calculator_prefix="{{ calculator_prefix }}">
<meta id="url_prefix" data-calculator_prefix="{{ get_calculator_url() }}">
<title>
{% block title %}
@ -14,10 +14,10 @@
</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="{{ get_url('/static/css') }}/style.css">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,500,600,700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.9.1/font/bootstrap-icons.css">
<link rel="icon" type="image/x-icon" href="static/icons/favicon.ico">
<link rel="icon" type="image/x-icon" href="{{ get_url('/static/icons') }}/favicon.ico">
{% block extra_headers %}
{% endblock extra_headers %}
@ -28,7 +28,7 @@
<nav class="navbar navbar-dark navbar-expand-lg">
<div class="container">
<a href="/" class="navbar-brand"><img src="/static/images/caimira_logo_white_text.png" alt="Logo" title="Logo"></a>
<a href="{{ get_url() }}/" class="navbar-brand"><img src="{{ get_url('/static/images') }}/caimira_logo_white_text.png" alt="Logo" title="Logo"></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
@ -36,26 +36,26 @@
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ml-auto">
<li class="nav-link"><a href="/" class="{{ "header-navbar nav-link active" if "home/" == active_page else "header-navbar nav-link" }}">Home</a></li>
<li class="nav-link"><a href="{{ get_url() }}/" class="{{ "header-navbar nav-link active" if "home/" == active_page else "header-navbar nav-link" }}">Home</a></li>
<div id="apps_dropdown">
<li class="nav-item dropdown p-2">
<a class="nav-link dropdown-toggle {{ "header-navbar nav-link active" if "calculator/" in active_page else "header-navbar nav-link" }}" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Apps
</a>
<ul class="dropdown-menu dropwown-navbar-colors" style="min-width: 12rem;" aria-labelledby="navbarDropdown">
<li><a href="{{ calculator_prefix }}" class="{{ "header-navbar nav-link active" if "calculator/" == active_page else "header-navbar nav-link" }}">Calculator</a></li>
<li><div class="d-flex"><span class="d-flex align-self-center submenu-division"></span><a href="{{ calculator_prefix }}/user-guide" class="{{ "header-navbar nav-link active" if "user-guide" in active_page else "header-navbar nav-link" }}">User Guide</a></div></li>
<li><a href="{{ get_calculator_url() }}" class="{{ "header-navbar nav-link active" if "calculator/" == active_page else "header-navbar nav-link" }}">Calculator</a></li>
<li><div class="d-flex"><span class="d-flex align-self-center submenu-division"></span><a href="{{ get_calculator_url() }}/user-guide" class="{{ "header-navbar nav-link active" if "user-guide" in active_page else "header-navbar nav-link" }}">User Guide</a></div></li>
<li><a href="/expert-app" class="{{ "header-navbar nav-link active" if "/expert-app" == active_page else "header-navbar nav-link" }}">Expert app (beta)</a></li>
</ul>
</li>
</div>
<div id="mobile_calculator_option">
<li class="nav-link"><a href="{{ calculator_prefix }}" class="{{ "header-navbar nav-link active" if "calculator/" == active_page else "header-navbar nav-link" }}">Calculator</a></li>
<li class="nav-link"><a href="{{ calculator_prefix }}/user-guide" class="{{ "header-navbar nav-link active" if "user-guide" in active_page else "header-navbar nav-link" }}">User Guide</a></li>
<li class="nav-link"><a href="{{ get_calculator_url() }}" class="{{ "header-navbar nav-link active" if "calculator/" == active_page else "header-navbar nav-link" }}">Calculator</a></li>
<li class="nav-link"><a href="{{ get_calculator_url() }}/user-guide" class="{{ "header-navbar nav-link active" if "user-guide" in active_page else "header-navbar nav-link" }}">User Guide</a></li>
</div>
{% block covid_information%}
{% endblock covid_information%}
<li class="nav-link"><a href="/about" class="{{ "header-navbar nav-link active" if "about" == active_page else "header-navbar nav-link" }}">About</a></li>
<li class="nav-link"><a href="{{ get_url('/about') }}" class="{{ "header-navbar nav-link active" if "about" == active_page else "header-navbar nav-link" }}">About</a></li>
{% if user.is_authenticated() %}
<li class="nav-item dropdown p-2">
<a class="nav-link active dropdown-toggle d-inline-block" href="https://cern.ch/users-portal" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@ -85,7 +85,7 @@
<div class="row text-light text-center py-4 justify-content-center">
<div class="col-sm-10 col-md-8 col-lg-6">
<img src="/static/images/caimira_logo_white_text.png" alt="Logo">
<img src="{{ get_url('/static/images') }}/caimira_logo_white_text.png" alt="Logo">
<p><span style="font-size:10px;"><em>CERN strives to deploy its know-how and technologies to help solve
the challenges arising in the local and global fight against COVID-19. As a particle physics
research organisation, CERN is not in a position to advise on medical research, health or health
@ -103,17 +103,12 @@
</div>
</footer>
<script src="/static/js/js_packaged_for_theme.js"></script>
<script src="{{ get_url('/static/js') }}/js_packaged_for_theme.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
<script src="/static/js/jquery.colorbox-min.js"></script>
<script src="/static/js/ScrollMagic.min.js"></script>
<script src="{{ get_url('/static/js') }}/jquery.colorbox-min.js"></script>
<script src="{{ get_url('/static/js') }}/ScrollMagic.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js" integrity="sha512-8qmis31OQi6hIRgvkht0s6mCOittjMa9GMqtK9hes5iEQBQE/Ca6yGE5FsW36vyipGoWQswBj/QBm2JR086Rkw==" crossorigin="anonymous"></script>
<script src="/static/js/usage-tracking.js"></script>
<!-- Popper JS -->
<script src="/static/js/popper.min.js"></script>
<!-- Font Awesome -->
<script src="/static/js/all.min.js"></script>
<script src="{{ get_url('/static/js') }}/usage-tracking.js"></script>
{% block body_scripts %}
{% endblock body_scripts %}

View file

@ -181,7 +181,7 @@
<div class="alert alert-warning" role="alert">Events with a <strong>P(i) between 2% and 10%</strong> shall be subject to ALARA principles (see footnote) to minimise the risk before proceeding.</div>
<div class="alert alert-danger mb-0" role="alert">Events with a <strong>P(i) exceeding 10% or a number of expected new cases that exceeds 1</strong> may not take place until additional measures are in place and a risk reduction has been performed.</div>
</div>
<div class="col-xl-3 align-self-center text-center"><img id="scale_warning" class="rounded" src="{{ calculator_prefix }}/static/images/warning_scale/{{ cern_level }}.png"></div>
<div class="col-xl-3 align-self-center text-center"><img id="scale_warning" class="rounded" src="{{ get_calculator_url('/static/images/warning_scale') }}/{{ cern_level }}.png"></div>
</div>
</div>
<br>

View file

@ -5,7 +5,7 @@
<div>
<p>
CAiMIRA has been developed by CERN with the intention of allowing members of personnel with roles related to supervision, health & safety or space management to simulate the concerned workplaces on CERN sites.
A hosted <a href="{{ calculator_prefix }}">CERN version of the CAiMIRA Calculator</a> is available on this site to members of the CERN personnel.
A hosted <a href="{{ get_calculator_url() }}">CERN version of the CAiMIRA Calculator</a> is available on this site to members of the CERN personnel.
</p>
</div>
{% endblock caimira_at_cern %}

View file

@ -103,7 +103,7 @@ class TestOpenApp(tornado.testing.AsyncHTTPTestCase):
async def test_permalink_urls(http_server_client, baseline_form):
base_url = 'proto://hostname/prefix'
permalink_data = generate_permalink(base_url, "/calculator", baseline_form)
permalink_data = generate_permalink(base_url, lambda: "", lambda: "/calculator", baseline_form)
expected = f'{base_url}/calculator?exposed_coffee_break_option={baseline_form.exposed_coffee_break_option}&'
assert permalink_data['link'].startswith(expected)

View file

@ -43,6 +43,7 @@ REQUIREMENTS: dict = {
'test': [
'pytest',
'pytest-mypy != v0.10.1',
'mypy != 1.1.1',
'pytest-tornasync',
'numpy-stubs @ git+https://github.com/numpy/numpy-stubs.git',
'types-dataclasses',