Repository: FlexMeasures/flexmeasures (language: python)

Issue:
Upgrade or replace Flask-Classful
Flask-Classful is handy for grouping API endpoints, but it seems not to be maintained well.

There is one maintainer, and he is not finishing a review for [a PR](https://github.com/teracyhq/flask-classful/pull/145) which by now [has become security-critical](https://security.snyk.io/vuln/SNYK-PYTHON-WERKZEUG-3319936). It stands in the way of upgrading Werkzeug, as well as Flask.

We have two options:

- go on with the branch of the PR, as [one Github user says he has been doing](https://github.com/teracyhq/flask-classful/pull/145#issuecomment-1331627685)
- Investigate how difficult it is to stop using Flask-Classful (and only [use MethodView](https://flask.palletsprojects.com/en/2.2.x/views/#method-dispatching-and-apis), for instance)


Discussion / hints:
I'd vote for option 1 at the moment, so our upgrades do not unnecessarily stall on this. Let's consider forking first, for stability, then updating our requirements. I saw [this example](https://stackoverflow.com/a/35998253) for a `requirements.txt`, but I don't know if this will work, too, for setting a git source in a `requirements.in`.
Note: If we can use Werkzeug >= 2.2 again, we can remove the Flask <2.1.2 version pin, but also others, e.g. the one for Flask-SQLAlchemy < 3.
## An update and a pondering why we are stuck here

### Update 

The repo has been moved by the maintainer into the pallets-ecosystem: https://github.com/pallets-eco/flask-classful

That's wonderful, but still someone from that ecosystem needs to stand up and do a new release.

As it is, we're still blocked. Comments on the PR are not answered.
The likelihood of that PR someday being actually released has increased a bit, so option 1 from above is looking a bit better.

### Pondering

But can we list here the reasons why we want to use Flask-Classful, @Flix6x ? I forget.

- We can organize API routes in their own modules, while versions are in blueprints. That sounds like a nice organization to me.
- After versions <=2 were sunset, maybe our APIs are pretty REST-like now, making MethodView better to use. 
- Another magic feature there is that one doesn't need to use `@route()` anymore, but we actually do that every time
- For some of the Flask-Classful features (and even more), we can also think about moving to a more powerful API framework (which also supports marshmallow natively) like https://apiflask.com or https://flask-smorest.readthedocs.io.

The latter is actually the way forward to resolving #17 which is a high-value ticket as well, for the time when frontend developers interact with FlexMeasures.

Fix patch:
diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict
index 5b95070a8..62a9a81b4 100644
--- a/.vscode/spellright.dict
+++ b/.vscode/spellright.dict
@@ -262,3 +262,4 @@ ctx_loader
 ctx_arg_name
 ctx_arg_pos
 dataset
+UNPROCESSABLE_ENTITY
diff --git a/documentation/changelog.rst b/documentation/changelog.rst
index 852ae7d47..29a25553b 100644
--- a/documentation/changelog.rst
+++ b/documentation/changelog.rst
@@ -20,10 +20,12 @@ Infrastructure / Support
 
 * Allow additional datetime conversions to quantitative time units, specifically, from timezone-naive and/or dayfirst datetimes, which can be useful when importing data [see `PR #831 <https://github.com/FlexMeasures/flexmeasures/pull/831>`_]
 * Add a new tutorial to explain the use of the `AggregatorReporter` to compute the headroom and the `ProfitOrLossReporter` to compute the cost of running a process [see `PR #825 <https://github.com/FlexMeasures/flexmeasures/pull/825>`_ and `PR #856 <https://github.com/FlexMeasures/flexmeasures/pull/856>`_]
+* Updated admin dashboard for inspecting asynchronous tasks (scheduling, forecasting, reporting, etc.), and improved performance and security of the server by upgrading Flask and Flask extensions [see `PR #838 <https://github.com/FlexMeasures/flexmeasures/pull/838>`_]
 * Script to update dependencies across supported Python versions [see `PR #843 <https://github.com/FlexMeasures/flexmeasures/pull/843>`_]
 * Test all supported Python versions in our CI pipeline (GitHub Actions) [see `PR #847 <https://github.com/FlexMeasures/flexmeasures/pull/847>`_]
 * Have our CI pipeline (GitHub Actions) build the Docker image and make a schedule [see `PR #800 <https://github.com/FlexMeasures/flexmeasures/pull/800>`_]
 
+
 v0.15.1 | August 28, 2023
 ============================
 
diff --git a/flexmeasures/api/common/implementations.py b/flexmeasures/api/common/implementations.py
index 80c59f348..c706f35d7 100644
--- a/flexmeasures/api/common/implementations.py
+++ b/flexmeasures/api/common/implementations.py
@@ -28,7 +28,7 @@ def make_response(status: str, reason: str, last_run: datetime) -> dict:
         return dict(
             status=status,
             reason=reason,
-            lastrun=last_run,
+            lastrun=last_run.isoformat(),
             frequency=current_app.config.get(
                 "MONITOR_FREQUENCY_%s" % task_name.upper(), 10
             ),
diff --git a/flexmeasures/api/common/schemas/sensor_data.py b/flexmeasures/api/common/schemas/sensor_data.py
index 153c884ab..b026d417a 100644
--- a/flexmeasures/api/common/schemas/sensor_data.py
+++ b/flexmeasures/api/common/schemas/sensor_data.py
@@ -28,7 +28,6 @@
     units_are_convertible,
     is_energy_price_unit,
 )
-from flexmeasures.auth.policy import check_access
 
 
 class SingleValueField(fields.Float):
@@ -119,10 +118,6 @@ class GetSensorDataSchema(SensorDataDescriptionSchema):
         ),
     )
 
-    @validates_schema
-    def check_user_may_read(self, data, **kwargs):
-        check_access(data["sensor"], "read")
-
     @validates_schema
     def check_schema_unit_against_type(self, data, **kwargs):
         requested_unit = data["unit"]
@@ -141,8 +136,8 @@ def check_schema_unit_against_type(self, data, **kwargs):
                 f"The unit requested for this message type should be convertible from an energy price unit, got incompatible unit: {requested_unit}"
             )
 
-    @post_load
-    def dump_bdf(self, sensor_data_description: dict, **kwargs) -> dict:
+    @staticmethod
+    def load_data_and_make_response(sensor_data_description: dict) -> dict:
         """Turn the de-serialized and validated data description into a response.
 
         Specifically, this function:
@@ -249,10 +244,6 @@ class PostSensorDataSchema(SensorDataDescriptionSchema):
         many=False,
     )
 
-    @validates_schema
-    def check_user_may_create(self, data, **kwargs):
-        check_access(data["sensor"], "create-children")
-
     @validates_schema
     def check_schema_unit_against_type(self, data, **kwargs):
         posted_unit = data["unit"]
diff --git a/flexmeasures/api/v3_0/accounts.py b/flexmeasures/api/v3_0/accounts.py
index 21da1ff4b..325e6c66a 100644
--- a/flexmeasures/api/v3_0/accounts.py
+++ b/flexmeasures/api/v3_0/accounts.py
@@ -26,9 +26,9 @@
 class AccountAPI(FlaskView):
     route_base = "/accounts"
     trailing_slash = False
+    decorators = [auth_required()]
 
     @route("", methods=["GET"])
-    @auth_required("token", "session")
     @as_json
     def index(self):
         """API endpoint to list all accounts accessible to the current user.
diff --git a/flexmeasures/api/v3_0/assets.py b/flexmeasures/api/v3_0/assets.py
index a7cce8cbc..17cd1c158 100644
--- a/flexmeasures/api/v3_0/assets.py
+++ b/flexmeasures/api/v3_0/assets.py
@@ -2,7 +2,7 @@
 
 from flask import current_app
 from flask_classful import FlaskView, route
-from flask_security import auth_token_required
+from flask_security import auth_required
 from flask_json import as_json
 from marshmallow import fields
 from webargs.flaskparser import use_kwargs, use_args
@@ -32,6 +32,7 @@ class AssetAPI(FlaskView):
 
     route_base = "/assets"
     trailing_slash = False
+    decorators = [auth_required()]
 
     @route("", methods=["GET"])
     @use_kwargs(
@@ -81,7 +82,6 @@ def index(self, account: Account):
         return assets_schema.dump(account.generic_assets), 200
 
     @route("/public", methods=["GET"])
-    @auth_token_required
     @as_json
     def public(self):
         """Return all public assets.
diff --git a/flexmeasures/api/v3_0/sensors.py b/flexmeasures/api/v3_0/sensors.py
index 3105fed86..34f912d9a 100644
--- a/flexmeasures/api/v3_0/sensors.py
+++ b/flexmeasures/api/v3_0/sensors.py
@@ -113,6 +113,12 @@ def index(self, account: Account):
         post_sensor_schema,
         location="json",
     )
+    @permission_required_for_context(
+        "create-children",
+        ctx_arg_pos=1,
+        ctx_loader=lambda bdf: bdf.sensor,
+        pass_ctx_to_loader=True,
+    )
     def post_data(self, bdf: BeliefsDataFrame):
         """
         Post sensor data to FlexMeasures.
@@ -157,7 +163,8 @@ def post_data(self, bdf: BeliefsDataFrame):
         get_sensor_schema,
         location="query",
     )
-    def get_data(self, response: dict):
+    @permission_required_for_context("read", ctx_arg_pos=1, ctx_arg_name="sensor")
+    def get_data(self, sensor_data_description: dict):
         """Get sensor data from FlexMeasures.
 
         .. :quickref: Data; Download sensor data
@@ -192,6 +199,9 @@ def get_data(self, response: dict):
         :status 403: INVALID_SENDER
         :status 422: UNPROCESSABLE_ENTITY
         """
+        response = GetSensorDataSchema.load_data_and_make_response(
+            sensor_data_description
+        )
         d, s = request_processed()
         return dict(**response, **d), s
 
@@ -214,6 +224,7 @@ def get_data(self, response: dict):
         },
         location="json",
     )
+    @permission_required_for_context("create-children", ctx_arg_name="sensor")
     def trigger_schedule(  # noqa: C901
         self,
         sensor: Sensor,
@@ -386,6 +397,7 @@ def trigger_schedule(  # noqa: C901
     @optional_duration_accepted(
         timedelta(hours=6)
     )  # todo: make this a Marshmallow field
+    @permission_required_for_context("read", ctx_arg_name="sensor")
     def get_schedule(self, sensor: Sensor, job_id: str, duration: timedelta, **kwargs):
         """Get a schedule from FlexMeasures.
 
diff --git a/flexmeasures/api/v3_0/users.py b/flexmeasures/api/v3_0/users.py
index 459439954..7c753f705 100644
--- a/flexmeasures/api/v3_0/users.py
+++ b/flexmeasures/api/v3_0/users.py
@@ -2,7 +2,7 @@
 from marshmallow import fields
 from sqlalchemy.exc import IntegrityError
 from webargs.flaskparser import use_kwargs
-from flask_security import current_user
+from flask_security import current_user, auth_required
 from flask_security.recoverable import send_reset_password_instructions
 from flask_json import as_json
 from werkzeug.exceptions import Forbidden
@@ -33,6 +33,7 @@
 class UserAPI(FlaskView):
     route_base = "/users"
     trailing_slash = False
+    decorators = [auth_required()]
 
     @route("", methods=["GET"])
     @use_kwargs(
diff --git a/flexmeasures/app.py b/flexmeasures/app.py
index f6ba210d7..bb7239a7f 100644
--- a/flexmeasures/app.py
+++ b/flexmeasures/app.py
@@ -93,6 +93,9 @@ def create(  # noqa C901
     app.queues = dict(
         forecasting=Queue(connection=redis_conn, name="forecasting"),
         scheduling=Queue(connection=redis_conn, name="scheduling"),
+        # reporting=Queue(connection=redis_conn, name="reporting"),
+        # labelling=Queue(connection=redis_conn, name="labelling"),
+        # alerting=Queue(connection=redis_conn, name="alerting"),
     )
 
     # Some basic security measures
diff --git a/flexmeasures/auth/decorators.py b/flexmeasures/auth/decorators.py
index 0624a06f6..79f42a369 100644
--- a/flexmeasures/auth/decorators.py
+++ b/flexmeasures/auth/decorators.py
@@ -7,6 +7,7 @@
 
 from typing import Callable
 from functools import wraps
+import inspect
 from flask import current_app
 from flask_json import as_json
 from flask_security import (
@@ -184,7 +185,9 @@ def decorated_view(*args, **kwargs):
             # if a loader is given, use that, otherwise fall back to context_from_args
             if ctx_loader is not None:
                 if pass_ctx_to_loader:
-                    if issubclass(ctx_loader, AuthModelMixin):
+                    if inspect.isclass(ctx_loader) and issubclass(
+                        ctx_loader, AuthModelMixin
+                    ):
                         context = ctx_loader.query.get(context_from_args)
                     else:
                         context = ctx_loader(context_from_args)
diff --git a/flexmeasures/ui/crud/users.py b/flexmeasures/ui/crud/users.py
index 0305a6fcd..af2359273 100644
--- a/flexmeasures/ui/crud/users.py
+++ b/flexmeasures/ui/crud/users.py
@@ -23,8 +23,6 @@
 """
 User Crud views for admins.
 
-Note: This uses the internal API 2.0 ― if these endpoints get updated in a later version,
-      we should change the version here.
 """
 
 
diff --git a/flexmeasures/ui/static/css/external/rq-dashboard-bootstrap.min.css b/flexmeasures/ui/static/css/external/rq-dashboard-bootstrap.min.css
index 85737d808..bdc760a14 100644
--- a/flexmeasures/ui/static/css/external/rq-dashboard-bootstrap.min.css
+++ b/flexmeasures/ui/static/css/external/rq-dashboard-bootstrap.min.css
@@ -1,612 +1,5 @@
-/* Our menu and rq-dashboard need to load a different version of bootstrap css on the same page. Here we add a custom class (rq-style) to each line so that rq-dashboard styling doesn't influence the rest of our app. */
+/* Our menu and rq-dashboard need to load a different version of bootstrap css on the same page. Here we add a custom class (.rq-style) so that rq-dashboard styling doesn't influence the rest of our app. */
 
-.rq-style article,.rq-style aside,.rq-style details,.rq-style figcaption,.rq-style figure,.rq-style footer,.rq-style header,.rq-style hgroup,.rq-style nav,.rq-style section{display:block;}
-.rq-style audio,.rq-style canvas,.rq-style video{display:inline-block;*display:inline;*zoom:1;}
-.rq-style audio:not([controls]){display:none;}
-.rq-style html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
-.rq-style a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-.rq-style a:hover,.rq-style a:active{outline:0;}
-.rq-style sub,.rq-style sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
-.rq-style sup{top:-0.5em;}
-.rq-style sub{bottom:-0.25em;}
-.rq-style img{max-width:100%;height:auto;border:0;-ms-interpolation-mode:bicubic;}
-.rq-style button,.rq-style input,.rq-style select,.rq-style textarea{margin:0;font-size:100%;vertical-align:middle;}
-.rq-style button,.rq-style input{*overflow:visible;line-height:normal;}
-.rq-style button::-moz-focus-inner,.rq-style input::-moz-focus-inner{padding:0;border:0;}
-.rq-style button,.rq-style input[type="button"],.rq-style input[type="reset"],.rq-style input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
-.rq-style input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
-.rq-style input[type="search"]::-webkit-search-decoration,.rq-style input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
-.rq-style textarea{overflow:auto;vertical-align:top;}
-.rq-style body{margin:0;font-family:"Helvetica Neue",Helvetica,arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
-.rq-style a{color:#0088cc;text-decoration:none;}
-.rq-style a:hover{color:#005580;text-decoration:underline;}
-.rq-style .row{margin-left:-20px;*zoom:1;}.row:before,.rq-style .row:after{display:table;content:"";}
-.rq-style .row:after{clear:both;}
-.rq-style [class*="span"]{float:left;margin-left:20px;}
-.rq-style .span1{width:60px;}
-.rq-style .span2{width:140px;}
-.rq-style .span3{width:220px;}
-.rq-style .span4{width:300px;}
-.rq-style .span5{width:380px;}
-.rq-style .span6{width:460px;}
-.rq-style .span7{width:540px;}
-.rq-style .span8{width:620px;}
-.rq-style .span9{width:700px;}
-.rq-style .span10{width:780px;}
-.rq-style .span11{width:860px;}
-.rq-style .span12,.rq-style .container{width:940px;}
-.rq-style .offset1{margin-left:100px;}
-.rq-style .offset2{margin-left:180px;}
-.rq-style .offset3{margin-left:260px;}
-.rq-style .offset4{margin-left:340px;}
-.rq-style .offset5{margin-left:420px;}
-.rq-style .offset6{margin-left:500px;}
-.rq-style .offset7{margin-left:580px;}
-.rq-style .offset8{margin-left:660px;}
-.rq-style .offset9{margin-left:740px;}
-.rq-style .offset10{margin-left:820px;}
-.rq-style .offset11{margin-left:900px;}
-.rq-style .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.rq-style .row-fluid:after{display:table;content:"";}
-.rq-style .row-fluid:after{clear:both;}
-.rq-style .row-fluid>[class*="span"]{float:left;margin-left:2.127659574%;}
-.rq-style .row-fluid>[class*="span"]:first-child{margin-left:0;}
-.rq-style .row-fluid .span1{width:6.382978723%;}
-.rq-style .row-fluid .span2{width:14.89361702%;}
-.rq-style .row-fluid .span3{width:23.404255317%;}
-.rq-style .row-fluid .span4{width:31.914893614%;}
-.rq-style .row-fluid .span5{width:40.425531911%;}
-.rq-style .row-fluid .span6{width:48.93617020799999%;}
-.rq-style .row-fluid .span7{width:57.446808505%;}
-.rq-style .row-fluid .span8{width:65.95744680199999%;}
-.rq-style .row-fluid .span9{width:74.468085099%;}
-.rq-style .row-fluid .span10{width:82.97872339599999%;}
-.rq-style .row-fluid .span11{width:91.489361693%;}
-.rq-style .row-fluid .span12{width:99.99999998999999%;}
-.rq-style .container{width:940px;margin-left:auto;margin-right:auto;*zoom:1;}.container:before,.rq-style .container:after{display:table;content:"";}
-.rq-style .container:after{clear:both;}
-.rq-style .container-fluid{padding-left:20px;padding-right:20px;*zoom:1;}.container-fluid:before,.rq-style .container-fluid:after{display:table;content:"";}
-.rq-style .container-fluid:after{clear:both;}
-.rq-style p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,arial,sans-serif;font-size:13px;line-height:18px;}.rq-style p small{font-size:11px;color:#999999;}
-.rq-style .lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
-.rq-style h1,.rq-style h2,.rq-style h3,.rq-style h4,.rq-style h5,.rq-style h6{margin:0;font-weight:bold;color:#333333;text-rendering:optimizelegibility;}.rq-style h1 small,.rq-style h2 small,.rq-style h3 small,.rq-style h4 small,.rq-style h5 small,.rq-style h6 small{font-weight:normal;color:#999999;}
-.rq-style h1{font-size:30px;line-height:36px;}.rq-style h1 small{font-size:18px;}
-.rq-style h2{font-size:24px;line-height:36px;}.rq-style h2 small{font-size:18px;}
-.rq-style h3{line-height:27px;font-size:18px;}.rq-style h3 small{font-size:14px;}
-.rq-style h4,.rq-style h5,.rq-style h6{line-height:18px;}
-.rq-style h4{font-size:14px;}.rq-style h4 small{font-size:12px;}
-.rq-style h5{font-size:12px;}
-.rq-style h6{font-size:11px;color:#999999;text-transform:uppercase;}
-.rq-style .page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
-.rq-style .page-header h1{line-height:1;}
-.rq-style ul,.rq-style ol{padding:0;margin:0 0 9px 25px;}
-.rq-style ul ul,.rq-style ul ol,.rq-style ol ol,.rq-style ol ul{margin-bottom:0;}
-.rq-style ul{list-style:disc;}
-.rq-style ol{list-style:decimal;}
-.rq-style li{line-height:18px;}
-.rq-style ul.unstyled{margin-left:0;list-style:none;}
-.rq-style dl{margin-bottom:18px;}
-.rq-style dt,.rq-style dd{line-height:18px;}
-.rq-style dt{font-weight:bold;}
-.rq-style dd{margin-left:9px;}
-.rq-style hr{margin:18px 0;border:0;border-top:1px solid #e5e5e5;border-bottom:1px solid #ffffff;}
-.rq-style strong{font-weight:bold;}
-.rq-style em{font-style:italic;}
-.rq-style .muted{color:#999999;}
-.rq-style abbr{font-size:90%;text-transform:uppercase;border-bottom:1px dotted #ddd;cursor:help;}
-.rq-style blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}.rq-style blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
-.rq-style blockquote small{display:block;line-height:18px;color:#999999;}.rq-style blockquote small:before{content:'\2014 \00A0';}
-.rq-style blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}.rq-style blockquote.pull-right p,.rq-style blockquote.pull-right small{text-align:right;}
-.rq-style q:before,.rq-style q:after,.rq-style blockquote:before,.rq-style blockquote:after{content:"";}
-.rq-style address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
-.rq-style small{font-size:100%;}
-.rq-style cite{font-style:normal;}
-.rq-style code,.rq-style pre{padding:0 3px 2px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.rq-style code{padding:3px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;}
-.rq-style pre{display:block;padding:8.5px;margin:0 0 9px;font-size:12px;line-height:18px;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;white-space:pre;white-space:pre-wrap;word-break:break-all;}.rq-style pre.prettyprint{margin-bottom:18px;}
-.rq-style pre code{padding:0;background-color:transparent;}
-.rq-style form{margin:0 0 18px;}
-.rq-style fieldset{padding:0;margin:0;border:0;}
-.rq-style legend{display:block;width:100%;padding:0;margin-bottom:27px;font-size:19.5px;line-height:36px;color:#333333;border:0;border-bottom:1px solid #eee;}
-.rq-style label,.rq-style input,.rq-style button,.rq-style select,.rq-style textarea{font-family:"Helvetica Neue",Helvetica,arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;}
-.rq-style label{display:block;margin-bottom:5px;color:#333333;}
-.rq-style input,.rq-style textarea,.rq-style select,.rq-style .uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;margin-bottom:9px;font-size:13px;line-height:18px;color:#555555;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.rq-style .uneditable-textarea{width:auto;height:auto;}
-.rq-style label input,.rq-style label textarea,.rq-style label select{display:block;}
-.rq-style input[type="image"],.rq-style input[type="checkbox"],.rq-style input[type="radio"]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:0;cursor:pointer;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.rq-style input[type="file"]{padding:initial;line-height:initial;border:initial;background-color:#ffffff;background-color:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
-.rq-style input[type="button"],.rq-style input[type="reset"],.rq-style input[type="submit"]{width:auto;height:auto;}
-.rq-style select,.rq-style input[type="file"]{height:28px;*margin-top:4px;line-height:28px;}
-.rq-style select{width:220px;background-color:#ffffff;}
-.rq-style select[multiple],.rq-style select[size]{height:auto;}
-.rq-style input[type="image"]{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
-.rq-style textarea{height:auto;}
-.rq-style input[type="hidden"]{display:none;}
-.rq-style .radio,.rq-style .checkbox{padding-left:18px;}
-.rq-style .radio input[type="radio"],.rq-style .checkbox input[type="checkbox"]{float:left;margin-left:-18px;}
-.rq-style .controls>.radio:first-child,.rq-style .controls>.checkbox:first-child{padding-top:5px;}
-.rq-style .radio.inline,.rq-style .checkbox.inline{display:inline-block;margin-bottom:0;vertical-align:middle;}
-.rq-style .radio.inline+.radio.inline,.rq-style .checkbox.inline+.checkbox.inline{margin-left:10px;}
-.rq-style .controls>.radio.inline:first-child,.rq-style .controls>.checkbox.inline:first-child{padding-top:0;}
-.rq-style input,.rq-style textarea{-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;}
-.rq-style input:focus,.rq-style textarea:focus{border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(82, 168, 236, 0.6);outline:0;outline:thin dotted \9;}
-.rq-style input[type="file"]:focus,.rq-style input[type="checkbox"]:focus,.rq-style select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-.rq-style .input-mini{width:60px;}
-.rq-style .input-small{width:90px;}
-.rq-style .input-medium{width:150px;}
-.rq-style .input-large{width:210px;}
-.rq-style .input-xlarge{width:270px;}
-.rq-style .input-xxlarge{width:530px;}
-.rq-style input[class*="span"],.rq-style select[class*="span"],.rq-style textarea[class*="span"],.rq-style .uneditable-input{float:none;margin-left:0;}
-.rq-style input.span1,.rq-style textarea.span1,.rq-style .uneditable-input.span1{width:50px;}
-.rq-style input.span2,.rq-style textarea.span2,.rq-style .uneditable-input.span2{width:130px;}
-.rq-style input.span3,.rq-style textarea.span3,.rq-style .uneditable-input.span3{width:210px;}
-.rq-style input.span4,.rq-style textarea.span4,.rq-style .uneditable-input.span4{width:290px;}
-.rq-style input.span5,.rq-style textarea.span5,.rq-style .uneditable-input.span5{width:370px;}
-.rq-style input.span6,.rq-style textarea.span6,.rq-style .uneditable-input.span6{width:450px;}
-.rq-style input.span7,.rq-style textarea.span7,.rq-style .uneditable-input.span7{width:530px;}
-.rq-style input.span8,.rq-style textarea.span8,.rq-style .uneditable-input.span8{width:610px;}
-.rq-style input.span9,.rq-style textarea.span9,.rq-style .uneditable-input.span9{width:690px;}
-.rq-style input.span10,.rq-style textarea.span10,.rq-style .uneditable-input.span10{width:770px;}
-.rq-style input.span11,.rq-style textarea.span11,.rq-style .uneditable-input.span11{width:850px;}
-.rq-style input.span12,.rq-style textarea.span12,.rq-style .uneditable-input.span12{width:930px;}
-.rq-style input[disabled],.rq-style select[disabled],.rq-style textarea[disabled],.rq-style input[readonly],.rq-style select[readonly],.rq-style textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
-.rq-style .control-group.warning>label,.rq-style .control-group.warning .help-block,.rq-style .control-group.warning .help-inline{color:#c09853;}
-.rq-style .control-group.warning input,.rq-style .control-group.warning select,.rq-style .control-group.warning textarea{color:#c09853;border-color:#c09853;}.control-group.warning input:focus,.rq-style .control-group.warning select:focus,.rq-style .control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:0 0 6px #dbc59e;-moz-box-shadow:0 0 6px #dbc59e;box-shadow:0 0 6px #dbc59e;}
-.rq-style .control-group.warning .input-prepend .add-on,.rq-style .control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
-.rq-style .control-group.error>label,.rq-style .control-group.error .help-block,.rq-style .control-group.error .help-inline{color:#b94a48;}
-.rq-style .control-group.error input,.rq-style .control-group.error select,.rq-style .control-group.error textarea{color:#b94a48;border-color:#b94a48;}.control-group.error input:focus,.rq-style .control-group.error select:focus,.rq-style .control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:0 0 6px #d59392;-moz-box-shadow:0 0 6px #d59392;box-shadow:0 0 6px #d59392;}
-.rq-style .control-group.error .input-prepend .add-on,.rq-style .control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
-.rq-style .control-group.success>label,.rq-style .control-group.success .help-block,.rq-style .control-group.success .help-inline{color:#468847;}
-.rq-style .control-group.success input,.rq-style .control-group.success select,.rq-style .control-group.success textarea{color:#468847;border-color:#468847;}.control-group.success input:focus,.rq-style .control-group.success select:focus,.rq-style .control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:0 0 6px #7aba7b;-moz-box-shadow:0 0 6px #7aba7b;box-shadow:0 0 6px #7aba7b;}
-.rq-style .control-group.success .input-prepend .add-on,.rq-style .control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
-.rq-style input:focus:required:invalid,.rq-style textarea:focus:required:invalid,.rq-style select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b;}.rq-style input:focus:required:invalid:focus,.rq-style textarea:focus:required:invalid:focus,.rq-style select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
-.rq-style .form-actions{padding:17px 20px 18px;margin-top:18px;margin-bottom:18px;background-color:#f5f5f5;border-top:1px solid #ddd;}
-.rq-style .uneditable-input{display:block;background-color:#ffffff;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
-.rq-style :-moz-placeholder{color:#999999;}
-.rq-style ::-webkit-input-placeholder{color:#999999;}
-.rq-style .help-block{margin-top:5px;margin-bottom:0;color:#999999;}
-.rq-style .help-inline{display:inline-block;*display:inline;*zoom:1;margin-bottom:9px;vertical-align:middle;padding-left:5px;}
-.rq-style .input-prepend,.rq-style .input-append{margin-bottom:5px;*zoom:1;}.input-prepend:before,.rq-style .input-append:before,.rq-style .input-prepend:after,.rq-style .input-append:after{display:table;content:"";}
-.rq-style .input-prepend:after,.rq-style .input-append:after{clear:both;}
-.rq-style .input-prepend input,.rq-style .input-append input,.rq-style .input-prepend .uneditable-input,.rq-style .input-append .uneditable-input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}.input-prepend input:focus,.rq-style .input-append input:focus,.rq-style .input-prepend .uneditable-input:focus,.rq-style .input-append .uneditable-input:focus{position:relative;z-index:2;}
-.rq-style .input-prepend .uneditable-input,.rq-style .input-append .uneditable-input{border-left-color:#ccc;}
-.rq-style .input-prepend .add-on,.rq-style .input-append .add-on{float:left;display:block;width:auto;min-width:16px;height:18px;margin-right:-1px;padding:4px 5px;font-weight:normal;line-height:18px;color:#999999;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#f5f5f5;border:1px solid #ccc;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
-.rq-style .input-prepend .active,.rq-style .input-append .active{background-color:#a9dba9;border-color:#46a546;}
-.rq-style .input-prepend .add-on{*margin-top:1px;}
-.rq-style .input-append input,.rq-style .input-append .uneditable-input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
-.rq-style .input-append .uneditable-input{border-right-color:#ccc;}
-.rq-style .input-append .add-on{margin-right:0;margin-left:-1px;-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
-.rq-style .input-append input:first-child{*margin-left:-160px;}.input-append input:first-child+.add-on{*margin-left:-21px;}
-.rq-style .search-query{padding-left:14px;padding-right:14px;margin-bottom:0;-webkit-border-radius:14px;-moz-border-radius:14px;border-radius:14px;}
-.rq-style .form-search input,.rq-style .form-inline input,.rq-style .form-horizontal input,.rq-style .form-search textarea,.rq-style .form-inline textarea,.rq-style .form-horizontal textarea,.rq-style .form-search select,.rq-style .form-inline select,.rq-style .form-horizontal select,.rq-style .form-search .help-inline,.rq-style .form-inline .help-inline,.rq-style .form-horizontal .help-inline,.rq-style .form-search .uneditable-input,.rq-style .form-inline .uneditable-input,.rq-style .form-horizontal .uneditable-input{display:inline-block;margin-bottom:0;}
-.rq-style .form-search label,.rq-style .form-inline label,.rq-style .form-search .input-append,.rq-style .form-inline .input-append,.rq-style .form-search .input-prepend,.rq-style .form-inline .input-prepend{display:inline-block;}
-.rq-style .form-search .input-append .add-on,.rq-style .form-inline .input-prepend .add-on,.rq-style .form-search .input-append .add-on,.rq-style .form-inline .input-prepend .add-on{vertical-align:middle;}
-.rq-style .control-group{margin-bottom:9px;}
-.rq-style .form-horizontal legend+.control-group{margin-top:18px;-webkit-margin-top-collapse:separate;}
-.rq-style .form-horizontal .control-group{margin-bottom:18px;*zoom:1;}.form-horizontal .control-group:before,.rq-style .form-horizontal .control-group:after{display:table;content:"";}
-.rq-style .form-horizontal .control-group:after{clear:both;}
-.rq-style .form-horizontal .control-group>label{float:left;width:140px;padding-top:5px;text-align:right;}
-.rq-style .form-horizontal .controls{margin-left:160px;}
-.rq-style .form-horizontal .form-actions{padding-left:160px;}
-.rq-style table{max-width:100%;border-collapse:collapse;border-spacing:0;}
-.rq-style .table{width:100%;margin-bottom:18px;}.table th,.rq-style .table td{padding:8px;line-height:18px;text-align:left;border-top:1px solid #ddd;}
-.rq-style .table th{font-weight:bold;vertical-align:bottom;}
-.rq-style .table td{vertical-align:top;}
-.rq-style .table thead:first-child tr th,.rq-style .table thead:first-child tr td{border-top:0;}
-.rq-style .table tbody+tbody{border-top:2px solid #ddd;}
-.rq-style .table-condensed th,.rq-style .table-condensed td{padding:4px 5px;}
-.rq-style .table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapsed;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th+th,.rq-style .table-bordered td+td,.rq-style .table-bordered th+td,.rq-style .table-bordered td+th{border-left:1px solid #ddd;}
-.rq-style .table-bordered thead:first-child tr:first-child th,.rq-style .table-bordered tbody:first-child tr:first-child th,.rq-style .table-bordered tbody:first-child tr:first-child td{border-top:0;}
-.rq-style .table-bordered thead:first-child tr:first-child th:first-child,.rq-style .table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
-.rq-style .table-bordered thead:first-child tr:first-child th:last-child,.rq-style .table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
-.rq-style .table-bordered thead:last-child tr:last-child th:first-child,.rq-style .table-bordered tbody:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
-.rq-style .table-bordered thead:last-child tr:last-child th:last-child,.rq-style .table-bordered tbody:last-child tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
-.rq-style .table-striped tbody tr:nth-child(odd) td,.rq-style .table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
-.rq-style table .span1{float:none;width:44px;margin-left:0;}
-.rq-style table .span2{float:none;width:124px;margin-left:0;}
-.rq-style table .span3{float:none;width:204px;margin-left:0;}
-.rq-style table .span4{float:none;width:284px;margin-left:0;}
-.rq-style table .span5{float:none;width:364px;margin-left:0;}
-.rq-style table .span6{float:none;width:444px;margin-left:0;}
-.rq-style table .span7{float:none;width:524px;margin-left:0;}
-.rq-style table .span8{float:none;width:604px;margin-left:0;}
-.rq-style table .span9{float:none;width:684px;margin-left:0;}
-.rq-style table .span10{float:none;width:764px;margin-left:0;}
-.rq-style table .span11{float:none;width:844px;margin-left:0;}
-.rq-style table .span12{float:none;width:924px;margin-left:0;}
-.rq-style [class^="icon-"]{display:inline-block;width:14px;height:14px;vertical-align:text-top;background-image:url(../fonts/glyphicons-halflings.png);background-position:14px 14px;background-repeat:no-repeat;*margin-right:.3em;}[class^="icon-"]:last-child{*margin-left:0;}
-.rq-style .icon-white{background-image:url(../fonts/glyphicons-halflings-white.png);}
-.rq-style .icon-glass{background-position:0 0;}
-.rq-style .icon-music{background-position:-24px 0;}
-.rq-style .icon-search{background-position:-48px 0;}
-.rq-style .icon-envelope{background-position:-72px 0;}
-.rq-style .icon-heart{background-position:-96px 0;}
-.rq-style .icon-star{background-position:-120px 0;}
-.rq-style .icon-star-empty{background-position:-144px 0;}
-.rq-style .icon-user{background-position:-168px 0;}
-.rq-style .icon-film{background-position:-192px 0;}
-.rq-style .icon-th-large{background-position:-216px 0;}
-.rq-style .icon-th{background-position:-240px 0;}
-.rq-style .icon-th-list{background-position:-264px 0;}
-.rq-style .icon-ok{background-position:-288px 0;}
-.rq-style .icon-remove{background-position:-312px 0;}
-.rq-style .icon-zoom-in{background-position:-336px 0;}
-.rq-style .icon-zoom-out{background-position:-360px 0;}
-.rq-style .icon-off{background-position:-384px 0;}
-.rq-style .icon-signal{background-position:-408px 0;}
-.rq-style .icon-cog{background-position:-432px 0;}
-.rq-style .icon-trash{background-position:-456px 0;}
-.rq-style .icon-home{background-position:0 -24px;}
-.rq-style .icon-file{background-position:-24px -24px;}
-.rq-style .icon-time{background-position:-48px -24px;}
-.rq-style .icon-road{background-position:-72px -24px;}
-.rq-style .icon-download-alt{background-position:-96px -24px;}
-.rq-style .icon-download{background-position:-120px -24px;}
-.rq-style .icon-upload{background-position:-144px -24px;}
-.rq-style .icon-inbox{background-position:-168px -24px;}
-.rq-style .icon-play-circle{background-position:-192px -24px;}
-.rq-style .icon-repeat{background-position:-216px -24px;}
-.rq-style .icon-refresh{background-position:-240px -24px;}
-.rq-style .icon-list-alt{background-position:-264px -24px;}
-.rq-style .icon-lock{background-position:-287px -24px;}
-.rq-style .icon-flag{background-position:-312px -24px;}
-.rq-style .icon-headphones{background-position:-336px -24px;}
-.rq-style .icon-volume-off{background-position:-360px -24px;}
-.rq-style .icon-volume-down{background-position:-384px -24px;}
-.rq-style .icon-volume-up{background-position:-408px -24px;}
-.rq-style .icon-qrcode{background-position:-432px -24px;}
-.rq-style .icon-barcode{background-position:-456px -24px;}
-.rq-style .icon-tag{background-position:0 -48px;}
-.rq-style .icon-tags{background-position:-25px -48px;}
-.rq-style .icon-book{background-position:-48px -48px;}
-.rq-style .icon-bookmark{background-position:-72px -48px;}
-.rq-style .icon-print{background-position:-96px -48px;}
-.rq-style .icon-camera{background-position:-120px -48px;}
-.rq-style .icon-font{background-position:-144px -48px;}
-.rq-style .icon-bold{background-position:-167px -48px;}
-.rq-style .icon-italic{background-position:-192px -48px;}
-.rq-style .icon-text-height{background-position:-216px -48px;}
-.rq-style .icon-text-width{background-position:-240px -48px;}
-.rq-style .icon-align-left{background-position:-264px -48px;}
-.rq-style .icon-align-center{background-position:-288px -48px;}
-.rq-style .icon-align-right{background-position:-312px -48px;}
-.rq-style .icon-align-justify{background-position:-336px -48px;}
-.rq-style .icon-list{background-position:-360px -48px;}
-.rq-style .icon-indent-left{background-position:-384px -48px;}
-.rq-style .icon-indent-right{background-position:-408px -48px;}
-.rq-style .icon-facetime-video{background-position:-432px -48px;}
-.rq-style .icon-picture{background-position:-456px -48px;}
-.rq-style .icon-pencil{background-position:0 -72px;}
-.rq-style .icon-map-marker{background-position:-24px -72px;}
-.rq-style .icon-adjust{background-position:-48px -72px;}
-.rq-style .icon-tint{background-position:-72px -72px;}
-.rq-style .icon-edit{background-position:-96px -72px;}
-.rq-style .icon-share{background-position:-120px -72px;}
-.rq-style .icon-check{background-position:-144px -72px;}
-.rq-style .icon-move{background-position:-168px -72px;}
-.rq-style .icon-step-backward{background-position:-192px -72px;}
-.rq-style .icon-fast-backward{background-position:-216px -72px;}
-.rq-style .icon-backward{background-position:-240px -72px;}
-.rq-style .icon-play{background-position:-264px -72px;}
-.rq-style .icon-pause{background-position:-288px -72px;}
-.rq-style .icon-stop{background-position:-312px -72px;}
-.rq-style .icon-forward{background-position:-336px -72px;}
-.rq-style .icon-fast-forward{background-position:-360px -72px;}
-.rq-style .icon-step-forward{background-position:-384px -72px;}
-.rq-style .icon-eject{background-position:-408px -72px;}
-.rq-style .icon-chevron-left{background-position:-432px -72px;}
-.rq-style .icon-chevron-right{background-position:-456px -72px;}
-.rq-style .icon-plus-sign{background-position:0 -96px;}
-.rq-style .icon-minus-sign{background-position:-24px -96px;}
-.rq-style .icon-remove-sign{background-position:-48px -96px;}
-.rq-style .icon-ok-sign{background-position:-72px -96px;}
-.rq-style .icon-question-sign{background-position:-96px -96px;}
-.rq-style .icon-info-sign{background-position:-120px -96px;}
-.rq-style .icon-screenshot{background-position:-144px -96px;}
-.rq-style .icon-remove-circle{background-position:-168px -96px;}
-.rq-style .icon-ok-circle{background-position:-192px -96px;}
-.rq-style .icon-ban-circle{background-position:-216px -96px;}
-.rq-style .icon-arrow-left{background-position:-240px -96px;}
-.rq-style .icon-arrow-right{background-position:-264px -96px;}
-.rq-style .icon-arrow-up{background-position:-289px -96px;}
-.rq-style .icon-arrow-down{background-position:-312px -96px;}
-.rq-style .icon-share-alt{background-position:-336px -96px;}
-.rq-style .icon-resize-full{background-position:-360px -96px;}
-.rq-style .icon-resize-small{background-position:-384px -96px;}
-.rq-style .icon-plus{background-position:-408px -96px;}
-.rq-style .icon-minus{background-position:-433px -96px;}
-.rq-style .icon-asterisk{background-position:-456px -96px;}
-.rq-style .icon-exclamation-sign{background-position:0 -120px;}
-.rq-style .icon-gift{background-position:-24px -120px;}
-.rq-style .icon-leaf{background-position:-48px -120px;}
-.rq-style .icon-fire{background-position:-72px -120px;}
-.rq-style .icon-eye-open{background-position:-96px -120px;}
-.rq-style .icon-eye-close{background-position:-120px -120px;}
-.rq-style .icon-warning-sign{background-position:-144px -120px;}
-.rq-style .icon-plane{background-position:-168px -120px;}
-.rq-style .icon-calendar{background-position:-192px -120px;}
-.rq-style .icon-random{background-position:-216px -120px;}
-.rq-style .icon-comment{background-position:-240px -120px;}
-.rq-style .icon-magnet{background-position:-264px -120px;}
-.rq-style .icon-chevron-up{background-position:-288px -120px;}
-.rq-style .icon-chevron-down{background-position:-313px -119px;}
-.rq-style .icon-retweet{background-position:-336px -120px;}
-.rq-style .icon-shopping-cart{background-position:-360px -120px;}
-.rq-style .icon-folder-close{background-position:-384px -120px;}
-.rq-style .icon-folder-open{background-position:-408px -120px;}
-.rq-style .icon-resize-vertical{background-position:-432px -119px;}
-.rq-style .icon-resize-horizontal{background-position:-456px -118px;}
-.rq-style .dropdown{position:relative;}
-.rq-style .dropdown-toggle{*margin-bottom:-3px;}
-.rq-style .dropdown-toggle:active,.rq-style .open .dropdown-toggle{outline:0;}
-.rq-style .caret{display:inline-block;width:0;height:0;text-indent:-99999px;*text-indent:0;vertical-align:top;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000000;opacity:0.3;filter:alpha(opacity=30);content:"\2193";}
-.rq-style .dropdown .caret{margin-top:8px;margin-left:2px;}
-.rq-style .dropdown:hover .caret,.rq-style .open.dropdown .caret{opacity:1;filter:alpha(opacity=100);}
-.rq-style .dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;float:left;display:none;min-width:160px;max-width:220px;_width:160px;padding:4px 0;margin:0;list-style:none;background-color:#ffffff;border-color:#ccc;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:1px;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;*border-right-width:2px;*border-bottom-width:2px;}.dropdown-menu.bottom-up{top:auto;bottom:100%;margin-bottom:2px;}
-.rq-style .dropdown-menu .divider{height:1px;margin:5px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;*width:100%;*margin:-5px 0 5px;}
-.rq-style .dropdown-menu a{display:block;padding:3px 15px;clear:both;font-weight:normal;line-height:18px;color:#555555;white-space:nowrap;}
-.rq-style .dropdown-menu li>a:hover,.rq-style .dropdown-menu .active>a,.rq-style .dropdown-menu .active>a:hover{color:#ffffff;text-decoration:none;background-color:#0088cc;}
-.rq-style .dropdown.open{*z-index:1000;}.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
-.rq-style .dropdown.open .dropdown-menu{display:block;}
-.rq-style .typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
-.rq-style .fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
-.rq-style .collapse{-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-ms-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;position:relative;overflow:hidden;height:0;}.collapse.in{height:auto;}
-.rq-style .close{float:right;font-size:20px;font-weight:bold;line-height:18px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover{color:#000000;text-decoration:none;opacity:0.4;filter:alpha(opacity=40);cursor:pointer;}
-.rq-style .btn{display:inline-block;padding:4px 10px 4px;font-size:13px;line-height:18px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#fafafa;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-repeat:no-repeat;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);cursor:pointer;*margin-left:.3em;}.btn:first-child{*margin-left:0;}
-.rq-style .btn:hover{color:#333333;text-decoration:none;background-color:#e6e6e6;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-ms-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
-.rq-style .btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
-.rq-style .btn.active,.rq-style .btn:active{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);background-color:#e6e6e6;background-color:#d9d9d9 \9;color:rgba(0, 0, 0, 0.5);outline:0;}
-.rq-style .btn.disabled,.rq-style .btn[disabled]{cursor:default;background-image:none;background-color:#e6e6e6;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
-.rq-style .btn-large{padding:9px 14px;font-size:15px;line-height:normal;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
-.rq-style .btn-large .icon{margin-top:1px;}
-.rq-style .btn-small{padding:5px 9px;font-size:11px;line-height:16px;}
-.rq-style .btn-small .icon{margin-top:-1px;}
-.rq-style .btn-primary,.rq-style .btn-primary:hover,.rq-style .btn-warning,.rq-style .btn-warning:hover,.rq-style .btn-danger,.rq-style .btn-danger:hover,.rq-style .btn-success,.rq-style .btn-success:hover,.rq-style .btn-info,.rq-style .btn-info:hover{text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);color:#ffffff;}
-.rq-style .btn-primary.active,.rq-style .btn-warning.active,.rq-style .btn-danger.active,.rq-style .btn-success.active,.rq-style .btn-info.active{color:rgba(255, 255, 255, 0.75);}
-.rq-style .btn-primary{background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-ms-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(top, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0088cc', endColorstr='#0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.rq-style .btn-primary:active,.rq-style .btn-primary.active,.rq-style .btn-primary.disabled,.rq-style .btn-primary[disabled]{background-color:#0044cc;}
-.rq-style .btn-primary:active,.rq-style .btn-primary.active{background-color:#003399 \9;}
-.rq-style .btn-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-ms-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(top, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.rq-style .btn-warning:active,.rq-style .btn-warning.active,.rq-style .btn-warning.disabled,.rq-style .btn-warning[disabled]{background-color:#f89406;}
-.rq-style .btn-warning:active,.rq-style .btn-warning.active{background-color:#c67605 \9;}
-.rq-style .btn-danger{background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-ms-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(top, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#bd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.rq-style .btn-danger:active,.rq-style .btn-danger.active,.rq-style .btn-danger.disabled,.rq-style .btn-danger[disabled]{background-color:#bd362f;}
-.rq-style .btn-danger:active,.rq-style .btn-danger.active{background-color:#942a25 \9;}
-.rq-style .btn-success{background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-ms-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(top, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.rq-style .btn-success:active,.rq-style .btn-success.active,.rq-style .btn-success.disabled,.rq-style .btn-success[disabled]{background-color:#51a351;}
-.rq-style .btn-success:active,.rq-style .btn-success.active{background-color:#408140 \9;}
-.rq-style .btn-info{background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-ms-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(top, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.rq-style .btn-info:active,.rq-style .btn-info.active,.rq-style .btn-info.disabled,.rq-style .btn-info[disabled]{background-color:#2f96b4;}
-.rq-style .btn-info:active,.rq-style .btn-info.active{background-color:#24748c \9;}
-.rq-style button.btn,.rq-style input[type="submit"].btn{*padding-top:2px;*padding-bottom:2px;}.rq-style button.btn::-moz-focus-inner,.rq-style input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
-.rq-style button.btn.large,.rq-style input[type="submit"].btn.large{*padding-top:7px;*padding-bottom:7px;}
-.rq-style button.btn.small,.rq-style input[type="submit"].btn.small{*padding-top:3px;*padding-bottom:3px;}
-.rq-style .btn-group{position:relative;*zoom:1;*margin-left:.3em;}.btn-group:before,.rq-style .btn-group:after{display:table;content:"";}
-.rq-style .btn-group:after{clear:both;}
-.rq-style .btn-group:first-child{*margin-left:0;}
-.rq-style .btn-group+.btn-group{margin-left:5px;}
-.rq-style .btn-toolbar{margin-top:9px;margin-bottom:9px;}.btn-toolbar .btn-group{display:inline-block;*display:inline;*zoom:1;}
-.rq-style .btn-group .btn{position:relative;float:left;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.rq-style .btn-group .btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
-.rq-style .btn-group .btn:last-child,.rq-style .btn-group .dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
-.rq-style .btn-group .btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
-.rq-style .btn-group .btn.large:last-child,.rq-style .btn-group .large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
-.rq-style .btn-group .btn:hover,.rq-style .btn-group .btn:focus,.rq-style .btn-group .btn:active,.rq-style .btn-group .btn.active{z-index:2;}
-.rq-style .btn-group .dropdown-toggle:active,.rq-style .btn-group.open .dropdown-toggle{outline:0;}
-.rq-style .btn-group .dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125),inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);*padding-top:5px;*padding-bottom:5px;}
-.rq-style .btn-group.open{*z-index:1000;}.btn-group.open .dropdown-menu{display:block;margin-top:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
-.rq-style .btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 6px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);}
-.rq-style .btn .caret{margin-top:7px;margin-left:0;}
-.rq-style .btn:hover .caret,.rq-style .open.btn-group .caret{opacity:1;filter:alpha(opacity=100);}
-.rq-style .btn-primary .caret,.rq-style .btn-danger .caret,.rq-style .btn-info .caret,.rq-style .btn-success .caret{border-top-color:#ffffff;opacity:0.75;filter:alpha(opacity=75);}
-.rq-style .btn-small .caret{margin-top:4px;}
-.rq-style .alert{padding:8px 35px 8px 14px;margin-bottom:18px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .alert,.rq-style .alert-heading{color:#c09853;}
-.rq-style .alert .close{position:relative;top:-2px;right:-21px;line-height:18px;}
-.rq-style .alert-success{background-color:#dff0d8;border-color:#d6e9c6;}
-.rq-style .alert-success,.rq-style .alert-success .alert-heading{color:#468847;}
-.rq-style .alert-danger,.rq-style .alert-error{background-color:#f2dede;border-color:#eed3d7;}
-.rq-style .alert-danger,.rq-style .alert-error,.rq-style .alert-danger .alert-heading,.rq-style .alert-error .alert-heading{color:#b94a48;}
-.rq-style .alert-info{background-color:#d9edf7;border-color:#bce8f1;}
-.rq-style .alert-info,.rq-style .alert-info .alert-heading{color:#3a87ad;}
-.rq-style .alert-block{padding-top:14px;padding-bottom:14px;}
-.rq-style .alert-block>p,.rq-style .alert-block>ul{margin-bottom:0;}
-.rq-style .alert-block p+p{margin-top:5px;}
-.rq-style .nav{margin-left:0;margin-bottom:18px;list-style:none;}
-.rq-style .nav>li>a{display:block;}
-.rq-style .nav>li>a:hover{text-decoration:none;background-color:#eeeeee;}
-.rq-style .nav-list{padding-left:14px;padding-right:14px;margin-bottom:0;}
-.rq-style .nav-list>li>a,.rq-style .nav-list .nav-header{display:block;padding:3px 15px;margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
-.rq-style .nav-list .nav-header{font-size:11px;font-weight:bold;line-height:18px;color:#999999;text-transform:uppercase;}
-.rq-style .nav-list>li+.nav-header{margin-top:9px;}
-.rq-style .nav-list .active>a,.rq-style .nav-list .active>a:hover{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
-.rq-style .nav-list [class^="icon-"]{margin-right:2px;}
-.rq-style .nav-tabs,.rq-style .nav-pills{*zoom:1;}.nav-tabs:before,.rq-style .nav-pills:before,.rq-style .nav-tabs:after,.rq-style .nav-pills:after{display:table;content:"";}
-.rq-style .nav-tabs:after,.rq-style .nav-pills:after{clear:both;}
-.rq-style .nav-tabs>li,.rq-style .nav-pills>li{float:left;}
-.rq-style .nav-tabs>li>a,.rq-style .nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
-.rq-style .nav-tabs{border-bottom:1px solid #ddd;}
-.rq-style .nav-tabs>li{margin-bottom:-1px;}
-.rq-style .nav-tabs>li>a{padding-top:9px;padding-bottom:9px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd;}
-.rq-style .nav-tabs>.active>a,.rq-style .nav-tabs>.active>a:hover{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
-.rq-style .nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
-.rq-style .nav-pills .active>a,.rq-style .nav-pills .active>a:hover{color:#ffffff;background-color:#0088cc;}
-.rq-style .nav-stacked>li{float:none;}
-.rq-style .nav-stacked>li>a{margin-right:0;}
-.rq-style .nav-tabs.nav-stacked{border-bottom:0;}
-.rq-style .nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.rq-style .nav-tabs.nav-stacked>li:first-child>a{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
-.rq-style .nav-tabs.nav-stacked>li:last-child>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
-.rq-style .nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2;}
-.rq-style .nav-pills.nav-stacked>li>a{margin-bottom:3px;}
-.rq-style .nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
-.rq-style .nav-tabs .dropdown-menu,.rq-style .nav-pills .dropdown-menu{margin-top:1px;border-width:1px;}
-.rq-style .nav-pills .dropdown-menu{-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .nav-tabs .dropdown-toggle .caret,.rq-style .nav-pills .dropdown-toggle .caret{border-top-color:#0088cc;margin-top:6px;}
-.rq-style .nav-tabs .dropdown-toggle:hover .caret,.rq-style .nav-pills .dropdown-toggle:hover .caret{border-top-color:#005580;}
-.rq-style .nav-tabs .active .dropdown-toggle .caret,.rq-style .nav-pills .active .dropdown-toggle .caret{border-top-color:#333333;}
-.rq-style .nav>.dropdown.active>a:hover{color:#000000;cursor:pointer;}
-.rq-style .nav-tabs .open .dropdown-toggle,.rq-style .nav-pills .open .dropdown-toggle,.rq-style .nav>.open.active>a:hover{color:#ffffff;background-color:#999999;border-color:#999999;}
-.rq-style .nav .open .caret,.rq-style .nav .open.active .caret,.rq-style .nav .open a:hover .caret{border-top-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
-.rq-style .tabs-stacked .open>a:hover{border-color:#999999;}
-.rq-style .tabbable{*zoom:1;}.tabbable:before,.rq-style .tabbable:after{display:table;content:"";}
-.rq-style .tabbable:after{clear:both;}
-.rq-style .tabs-below .nav-tabs,.rq-style .tabs-right .nav-tabs,.rq-style .tabs-left .nav-tabs{border-bottom:0;}
-.rq-style .tab-content>.tab-pane,.rq-style .pill-content>.pill-pane{display:none;}
-.rq-style .tab-content>.active,.rq-style .pill-content>.active{display:block;}
-.rq-style .tabs-below .nav-tabs{border-top:1px solid #ddd;}
-.rq-style .tabs-below .nav-tabs>li{margin-top:-1px;margin-bottom:0;}
-.rq-style .tabs-below .nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below .nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd;}
-.rq-style .tabs-below .nav-tabs .active>a,.rq-style .tabs-below .nav-tabs .active>a:hover{border-color:transparent #ddd #ddd #ddd;}
-.rq-style .tabs-left .nav-tabs>li,.rq-style .tabs-right .nav-tabs>li{float:none;}
-.rq-style .tabs-left .nav-tabs>li>a,.rq-style .tabs-right .nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
-.rq-style .tabs-left .nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
-.rq-style .tabs-left .nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
-.rq-style .tabs-left .nav-tabs>li>a:hover{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
-.rq-style .tabs-left .nav-tabs .active>a,.rq-style .tabs-left .nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
-.rq-style .tabs-right .nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
-.rq-style .tabs-right .nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
-.rq-style .tabs-right .nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
-.rq-style .tabs-right .nav-tabs .active>a,.rq-style .tabs-right .nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
-.rq-style .navbar{overflow:visible;margin-bottom:18px;}
-.rq-style .navbar-inner{padding-left:20px;padding-right:20px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
-.rq-style .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;background-color:#2c2c2c;background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#333333), to(#222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1),0 1px 0 rgba(255, 255, 255, 0.075);}.btn-navbar:hover,.rq-style .btn-navbar:active,.rq-style .btn-navbar.active,.rq-style .btn-navbar.disabled,.rq-style .btn-navbar[disabled]{background-color:#222222;}
-.rq-style .btn-navbar:active,.rq-style .btn-navbar.active{background-color:#080808 \9;}
-.rq-style .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
-.rq-style .btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
-.rq-style .nav-collapse.collapse{height:auto;}
-.rq-style .navbar .brand:hover{text-decoration:none;}
-.rq-style .navbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;font-size:20px;font-weight:200;line-height:1;color:#ffffff;}
-.rq-style .navbar .navbar-text{margin-bottom:0;line-height:40px;color:#999999;}.rq-style .navbar .navbar-text a:hover{color:#ffffff;background-color:transparent;}
-.rq-style .navbar .btn,.rq-style .navbar .btn-group{margin-top:5px;}
-.rq-style .navbar .btn-group .btn{margin-top:0;}
-.rq-style .navbar-form{margin-bottom:0;*zoom:1;}.rq-style .navbar-form:before,.rq-style .navbar-form:after{display:table;content:"";}
-.rq-style .navbar-form:after{clear:both;}
-.rq-style .navbar-form input,.rq-style .navbar-form select{display:inline-block;margin-top:5px;margin-bottom:0;}
-.rq-style .navbar-form .radio,.rq-style .navbar-form .checkbox{margin-top:5px;}
-.rq-style .navbar-form input[type="image"],.rq-style .navbar-form input[type="checkbox"],.rq-style .navbar-form input[type="radio"]{margin-top:3px;}
-.rq-style .navbar-search{position:relative;float:left;margin-top:6px;margin-bottom:0;}.rq-style .navbar-search .search-query{padding:4px 9px;font-family:"Helvetica Neue",Helvetica,arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;color:#ffffff;color:rgba(255, 255, 255, 0.75);background:#666;background:rgba(255, 255, 255, 0.3);border:1px solid #111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.navbar-search .search-query :-moz-placeholder{color:#eeeeee;}
-.rq-style .navbar-search .search-query::-webkit-input-placeholder{color:#eeeeee;}
-.rq-style .navbar-search .search-query:hover{color:#ffffff;background-color:#999999;background-color:rgba(255, 255, 255, 0.5);}
-.rq-style .navbar-search .search-query:focus,.rq-style .navbar-search .search-query.focused{padding:5px 10px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
-.rq-style .navbar-fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030;}
-.rq-style .navbar-fixed-top .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
-.rq-style .navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
-.rq-style .navbar .nav.pull-right{float:right;}
-.rq-style .navbar .nav>li{display:block;float:left;}
-.rq-style .navbar .nav>li>a{float:none;padding:10px 10px 11px;line-height:19px;color:#999999;text-decoration:none;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
-.rq-style .navbar .nav>li>a:hover{background-color:transparent;color:#ffffff;text-decoration:none;}
-.rq-style .navbar .nav .active>a,.rq-style .navbar .nav .active>a:hover{color:#ffffff;text-decoration:none;background-color:#222222;background-color:rgba(0, 0, 0, 0.5);}
-.rq-style .navbar .divider-vertical{height:40px;width:1px;margin:0 9px;overflow:hidden;background-color:#222222;border-right:1px solid #333333;}
-.rq-style .navbar .nav.pull-right{margin-left:10px;margin-right:0;}
-.rq-style .navbar .dropdown-menu{margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.rq-style .navbar .dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
-.rq-style .navbar .dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
-.rq-style .navbar .nav .dropdown-toggle .caret,.rq-style .navbar .nav .open.dropdown .caret{border-top-color:#ffffff;}
-.rq-style .navbar .nav .active .caret{opacity:1;filter:alpha(opacity=100);}
-.rq-style .navbar .nav .open>.dropdown-toggle,.rq-style .navbar .nav .active>.dropdown-toggle,.rq-style .navbar .nav .open.active>.dropdown-toggle{background-color:transparent;}
-.rq-style .navbar .nav .active>.dropdown-toggle:hover{color:#ffffff;}
-.rq-style .navbar .nav.pull-right .dropdown-menu{left:auto;right:0;}.navbar .nav.pull-right .dropdown-menu:before{left:auto;right:12px;}
-.rq-style .navbar .nav.pull-right .dropdown-menu:after{left:auto;right:13px;}
-.rq-style .breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#fbfbfb;background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
-.rq-style .breadcrumb .divider{padding:0 5px;color:#999999;}
-.rq-style .breadcrumb .active a{color:#333333;}
-.rq-style .pagination{height:36px;margin:18px 0;}
-.rq-style .pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
-.rq-style .pagination li{display:inline;}
-.rq-style .pagination a{float:left;padding:0 14px;line-height:34px;text-decoration:none;border:1px solid #ddd;border-left-width:0;}
-.rq-style .pagination a:hover,.rq-style .pagination .active a{background-color:#f5f5f5;}
-.rq-style .pagination .active a{color:#999999;cursor:default;}
-.rq-style .pagination .disabled a,.rq-style .pagination .disabled a:hover{color:#999999;background-color:transparent;cursor:default;}
-.rq-style .pagination li:first-child a{border-left-width:1px;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
-.rq-style .pagination li:last-child a{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
-.rq-style .pagination-centered{text-align:center;}
-.rq-style .pagination-right{text-align:right;}
-.rq-style .pager{margin-left:0;margin-bottom:18px;list-style:none;text-align:center;*zoom:1;}.pager:before,.rq-style .pager:after{display:table;content:"";}
-.rq-style .pager:after{clear:both;}
-.rq-style .pager li{display:inline;}
-.rq-style .pager a{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
-.rq-style .pager a:hover{text-decoration:none;background-color:#f5f5f5;}
-.rq-style .pager .next a{float:right;}
-.rq-style .pager .previous a{float:left;}
-.rq-style .modal-open .dropdown-menu{z-index:2050;}
-.rq-style .modal-open .dropdown.open{*z-index:2050;}
-.rq-style .modal-open .popover{z-index:2060;}
-.rq-style .modal-open .tooltip{z-index:2070;}
-.rq-style .modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
-.rq-style .modal-backdrop,.rq-style .modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
-.rq-style .modal{position:fixed;top:50%;left:50%;z-index:1050;max-height:500px;overflow:auto;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
-.rq-style .modal.fade.in{top:50%;}
-.rq-style .modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
-.rq-style .modal-body{padding:15px;}
-.rq-style .modal-footer{padding:14px 15px 15px;margin-bottom:0;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.rq-style .modal-footer:after{display:table;content:"";}
-.rq-style .modal-footer:after{clear:both;}
-.rq-style .modal-footer .btn{float:right;margin-left:5px;margin-bottom:0;}
-.rq-style .tooltip{position:absolute;z-index:1020;display:block;visibility:visible;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
-.rq-style .tooltip.top{margin-top:-2px;}
-.rq-style .tooltip.right{margin-left:2px;}
-.rq-style .tooltip.bottom{margin-top:2px;}
-.rq-style .tooltip.left{margin-left:-2px;}
-.rq-style .tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
-.rq-style .tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
-.rq-style .tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
-.rq-style .tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
-.rq-style .tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .tooltip-arrow{position:absolute;width:0;height:0;}
-.rq-style .popover{position:absolute;top:0;left:0;z-index:1010;display:none;padding:5px;}.popover.top{margin-top:-5px;}
-.rq-style .popover.right{margin-left:5px;}
-.rq-style .popover.bottom{margin-top:5px;}
-.rq-style .popover.left{margin-left:-5px;}
-.rq-style .popover.top .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
-.rq-style .popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
-.rq-style .popover.bottom .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
-.rq-style .popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
-.rq-style .popover .arrow{position:absolute;width:0;height:0;}
-.rq-style .popover-inner{padding:3px;width:280px;overflow:hidden;background:#000000;background:rgba(0, 0, 0, 0.8);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
-.rq-style .popover-title{padding:9px 15px;line-height:1;background-color:#f5f5f5;border-bottom:1px solid #eee;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;}
-.rq-style .popover-content{padding:14px;background-color:#ffffff;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover-content p,.rq-style .popover-content ul,.rq-style .popover-content ol{margin-bottom:0;}
-.rq-style .thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.rq-style .thumbnails:after{display:table;content:"";}
-.rq-style .thumbnails:after{clear:both;}
-.rq-style .thumbnails>li{float:left;margin:0 0 18px 20px;}
-.rq-style .thumbnail{display:block;padding:4px;line-height:1;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}
-.rq-style a.thumbnail:hover{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
-.rq-style .thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
-.rq-style .thumbnail .caption{padding:9px;}
-.rq-style .label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;background-color:#999999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
-.rq-style .label-important{background-color:#b94a48;}
-.rq-style .label-warning{background-color:#f89406;}
-.rq-style .label-success{background-color:#468847;}
-.rq-style .label-info{background-color:#3a87ad;}
-.rq-style @-webkit-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}.progress{overflow:hidden;height:18px;margin-bottom:18px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-ms-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(top, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .progress .bar{width:0%;height:18px;color:#ffffff;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-ms-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(top, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-ms-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
-.rq-style .progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
-.rq-style .progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
-.rq-style .progress-danger .bar{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);}
-.rq-style .progress-danger.progress-striped .bar{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.rq-style .progress-success .bar{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);}
-.rq-style .progress-success.progress-striped .bar{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.rq-style .progress-info .bar{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);}
-.rq-style .progress-info.progress-striped .bar{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-ms-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(-45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
-.rq-style .accordion{margin-bottom:18px;}
-.rq-style .accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
-.rq-style .accordion-heading{border-bottom:0;}
-.rq-style .accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
-.rq-style .accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
-.rq-style .carousel{position:relative;margin-bottom:18px;line-height:1;}
-.rq-style .carousel-inner{overflow:hidden;width:100%;position:relative;}
-.rq-style .carousel .item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-ms-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}
-.rq-style .carousel .item>img{display:block;line-height:1;}
-.rq-style .carousel .active,.rq-style .carousel .next,.rq-style .carousel .prev{display:block;}
-.rq-style .carousel .active{left:0;}
-.rq-style .carousel .next,.rq-style .carousel .prev{position:absolute;top:0;width:100%;}
-.rq-style .carousel .next{left:100%;}
-.rq-style .carousel .prev{left:-100%;}
-.rq-style .carousel .next.left,.rq-style .carousel .prev.right{left:0;}
-.rq-style .carousel .active.left{left:-100%;}
-.rq-style .carousel .active.right{left:100%;}
-.rq-style .carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
-.rq-style .carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
-.rq-style .carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:10px 15px 5px;background:#333333;background:rgba(0, 0, 0, 0.75);}
-.rq-style .carousel-caption h4,.rq-style .carousel-caption p{color:#ffffff;}
-.rq-style .hero-unit{padding:60px;margin-bottom:30px;background-color:#f5f5f5;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
-.rq-style .hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
-.rq-style .pull-right{float:right;}
-.rq-style .pull-left{float:left;}
-.rq-style .hide{display:none;}
-.rq-style .show{display:block;}
-.rq-style .invisible{visibility:hidden;}
+.rq-style {
+    @import "{{ url_for('flexmeasures_ui.static', filename='css/external/rq-dashboard-bootstrap.min.css') }}"
+}
diff --git a/flexmeasures/ui/static/css/flexmeasures.css b/flexmeasures/ui/static/css/flexmeasures.css
index c7acb09f6..1f1adfcbb 100644
--- a/flexmeasures/ui/static/css/flexmeasures.css
+++ b/flexmeasures/ui/static/css/flexmeasures.css
@@ -220,12 +220,12 @@ p.error {
     z-index: 999991;
 }
 
-.navbar-default .navbar-nav>.active>a {
+.navbar-default .navbar-nav>.active>a, .btn-info.active {
     color: var(--nav-current-color) !important;
     background-color: var(--nav-current-background-color) !important;
 }
 .navbar-default .navbar-nav>.active>a:focus,
-.navbar-default .navbar-nav>.active>a:hover {
+.navbar-default .navbar-nav>.active>a:hover, .btn-info.active:focus, .btn-info.active:hover {
     color: var(--nav-current-hover-color) !important;
     background-color: var(--nav-current-hover-background-color) !important;
 }
@@ -1326,6 +1326,9 @@ body .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover {
     -ms-transition: .4s;
     -o-transition: .4s;
 }
+.rq-style.header-action-button>div button {
+    padding: 15px 25px;
+}
 
 .header-action-button>div button:hover, .header-action-button>div button:focus {
     background: var(--primary-hover-color) !important;
diff --git a/flexmeasures/ui/templates/base.html b/flexmeasures/ui/templates/base.html
index 3de5bd202..49615365e 100644
--- a/flexmeasures/ui/templates/base.html
+++ b/flexmeasures/ui/templates/base.html
@@ -15,7 +15,7 @@
       <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
     {% endif %}
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <link rel="icon" href="favicon.ico" type="image/x-icon" />
+    <link rel="icon" href="/favicon.ico" type="image/x-icon" />
     <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
     {% block styles %}
     <!-- Leaflet -->
@@ -35,8 +35,8 @@
     <!-- Custom CSS -->
     <link href="{{ url_for('flexmeasures_ui.static', filename='css/flexmeasures.css') }}" rel="stylesheet" />
     {% if active_page == "tasks" %}
-    <link href="{{ url_for('flexmeasures_ui.static', filename='css/external/rq-dashboard-bootstrap.min.css') }}"
-        rel="stylesheet" />
+    <link href="{{ url_for('rq_dashboard.static', filename='css/main.css') }}" rel="stylesheet">
+    <link href="{{ url_for('flexmeasures_ui.static', filename='css/external/rq-dashboard-bootstrap.min.css') }}" rel="stylesheet" />
     {% elif active_page in ("assets", "users", "accounts") %}
     <link href="https://cdn.datatables.net/1.10.22/css/jquery.dataTables.min.css" rel="stylesheet" />
     {% endif %}
@@ -84,21 +84,14 @@
                             Tasks
                             <span class="caret"></span></a>
                         <ul class="dropdown-menu">
+                            {% for queue in queues %}
+                                <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
+                                    {% if not current_user.has_role('anonymous') %}href="/tasks/0/view/jobs/{{ queue }}/started/10/1"
+                                    {% endif %}>{{ queue | capitalize }}</a></li>
+                            {% endfor %}
                             <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
-                                    {% if not current_user.has_role('anonymous') %}href="/tasks/forecasting"
-                                    {% endif %}>Forecasts</a></li>
-                            <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
-                                    {% if not current_user.has_role('anonymous') %}href="/tasks/scheduling"
-                                    {% endif %}>Schedules</a></li>
-                            <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
-                                    {% if not current_user.has_role('anonymous') %}href="/tasks/labelling"
-                                    {% endif %}>Labels</a></li>
-                            <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
-                                    {% if not current_user.has_role('anonymous') %}href="/tasks/alerting"
-                                    {% endif %}>Alerts</a></li>
-                            <li {% if current_user.has_role('anonymous') %}class="disabled" {% endif %}><a
-                                    {% if not current_user.has_role('anonymous') %}href="/tasks/reporting"
-                                    {% endif %}>Reports</a></li>
+                                    {% if not current_user.has_role('anonymous') %}href="/tasks/"
+                                    {% endif %}>Overview</a></li>
                         </ul>
                     </li>
                     {% else %}
@@ -456,7 +449,7 @@
                 var offsetDifference = getOffsetBetweenTimezonesForDate(new Date(), data.timezone, jstz.determine().name());
                 if (offsetDifference != 0) {
                     document.getElementById('tzwarn').style.display = 'block';
-                    var offsetNotice = (offsetDifference > 0) ? 'which is currently ahead by ' + offsetDifference + 'minutes' : 'which is currently behind by ' + offsetDifference + ' minutes';
+                    var offsetNotice = (offsetDifference > 0) ? 'which is currently ahead by ' + offsetDifference + ' minutes' : 'which is currently behind by ' + offsetDifference + ' minutes';
                     document.getElementById('tzwarn').innerHTML = 'Please note that the sensor data you are viewing is located in a different timezone (' + offsetNotice + ').<br/>To view the data from a local perspective, set your locale timezone to ' + data.timezone + '.';
                 }
                 {% if active_page == "assets" %}
diff --git a/flexmeasures/ui/templates/rq_dashboard/base.html b/flexmeasures/ui/templates/rq_dashboard/base.html
index c2470bf8f..a698cb45e 100644
--- a/flexmeasures/ui/templates/rq_dashboard/base.html
+++ b/flexmeasures/ui/templates/rq_dashboard/base.html
@@ -6,26 +6,56 @@
 
 {% block divs %}
 
-<div class="rq-style">
-
-<div id="alert-fixed" class="alert alert-error">
-    <strong>Warning!</strong> Selection pauses information refresh
-</div>
-
 <div class="container-fluid">
+    <div class="row">
+        <div id="alert-fixed" class="alert alert-error">
+            <strong>Warning!</strong> Selection pauses information refresh
+        </div>
+    </div>
+    <div class="row on-top-md">
+        <div class="col-sm-2">
+            <div class="header-action-button rq-style">
+                <div class="">
+                    <form action="{{rq_url_prefix}}{{ current_instance }}/view/queues" method="get">
+                        <button class="btn btn-sm btn-responsive btn-info {% if active_tab == 'queues'%} active {% endif %}" type="submit">Queues</button>
+                    </form>
+                </div>
+                <div class="">
+                    <form action="{{rq_url_prefix}}{{ current_instance }}/view/jobs" method="get">
+                        <button class="btn btn-sm btn-responsive btn-info {% if active_tab == 'jobs'%} active {% endif %}" type="submit">Jobs</button>
+                    </form>
+                </div>
+                <div class="">
+                    <form action="{{rq_url_prefix}}{{ current_instance }}/view/workers" method="get">
+                        <button class="btn btn-sm btn-responsive btn-info {% if active_tab == 'workers'%} active {% endif %}" type="submit">Workers</button>
+                    </form>
+                </div>
+            </div>
+        </div>
+        <div class="rq-style col-sm-8">
 
-    {% block content %}{% endblock %}
+            <div class="card">
 
-</div>
+                {% block content %}{% endblock %}
 
-<div id="confirmation-modal" class="modal hide fade">
-    <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-        <h3>Do you really want to <span id="confirmation-modal-action"></span>?</h3>
-    </div>
-    <div class="modal-footer">
-        <button id="confirmation-modal-no" class="btn">No</button>
-        <button id="confirmation-modal-yes" class="btn btn-primary">Yes</button>
+            </div>
+
+            <div id="confirmation-modal" class="modal fade">
+                <div class="modal-dialog" role="document">
+                    <div class="modal-content">
+                        <div class="modal-header">
+                            <h3>Do you really want to <span id="confirmation-modal-action"></span>?</h3>
+                            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                        </div>
+                        <div class="modal-footer">
+                            <button type="button" id="confirmation-modal-no" class="btn">No</button>
+                            <button type="button" id="confirmation-modal-yes" class="btn btn-primary">Yes</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-sm-2"></div>
     </div>
 </div>
 
@@ -33,18 +63,16 @@ <h3>Do you really want to <span id="confirmation-modal-action"></span>?</h3>
 
 {% endblock%}
 
-</div>
-
 {% block scripts %}
 
 {#- Custom JS for rq dashboard -#}
-<script src="{{ url_for('rq_dashboard.static', filename='js/jquery.min.js') }}" type="text/javascript"></script>
-<script src="{{ url_for('rq_dashboard.static', filename='js/underscore-min.js') }}" type="text/javascript"></script>
+<script src="{{ url_for('rq_dashboard.static', filename='js/jquery-3.4.1.min.js') }}" type="text/javascript"></script>
+<script src="{{ url_for('rq_dashboard.static', filename='js/underscore-umd-min.js') }}" type="text/javascript"></script>
 <script src="{{ url_for('rq_dashboard.static', filename='js/sugar-1.2.1.min.js') }}" type="text/javascript"></script>
-<script src="{{ url_for('rq_dashboard.static', filename='js/bootstrap-tooltip.js') }}" type="text/javascript"></script>
-<script src="{{ url_for('rq_dashboard.static', filename='js/bootstrap-modal.js') }}" type="text/javascript"></script>
+<script src="{{ url_for('rq_dashboard.static', filename='js/bootstrap.bundle.min.js') }}" type="text/javascript"></script>
 <script type="text/javascript">
-{% block inline_js %}{% endblock %}
+    {% include "rq_dashboard/inline_js.html" %}
+    {% block content_scripts %}{% endblock %}
 </script>
 
 {% endblock scripts %}
diff --git a/flexmeasures/ui/templates/rq_dashboard/dashboard.html b/flexmeasures/ui/templates/rq_dashboard/dashboard.html
deleted file mode 100644
index e497717aa..000000000
--- a/flexmeasures/ui/templates/rq_dashboard/dashboard.html
+++ /dev/null
@@ -1,213 +0,0 @@
-{% extends "rq_dashboard/base.html" %}
-
-{% block content %}
-<!--<div class="row" id="rq-instances-row" style="display: none">-->
-<!--    <div class="span12">-->
-<!--        <div class="section">-->
-<!--            <h1>RQ Instances</h1>-->
-<!--            <p class="intro">Select below the RRQ instance that you want to observe.</p>-->
-<!--            <select id="rq-instances">-->
-<!--            </select>-->
-<!--        </div>-->
-<!--    </div>-->
-<!--</div>-->
-<div class="row">
-    <div class="col-sm-4">
-        <div class="span6">
-            <div class="section">
-
-                <h2>Queues <a id="queues-btn" href="#">(toggle)</a></h2>
-                <p class="fixed intro">This list below contains all the registered queues with the number of jobs currently
-                    in the queue. Select a queue from above to view all jobs currently pending on the queue.</p>
-
-                <table id="queues" class="table table-bordered">
-                    <thead>
-                    <tr>
-                        <th>Queue</th>
-                        <th class="narrow">Jobs</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    <tr data-role="loading-placeholder">
-                        <td colspan="2">Loading...</td>
-                    </tr>
-                    </tbody>
-                </table>
-
-                <script name="queue-row" type="text/template">
-                    <tr data-role="queue" <% if (d.name === 'failed' && d.count > 0) { %> class="failed"<% } %>>
-                    <td><i class="icon-inbox icon-right" style="opacity: .5;"></i> <a href="<%= d.url %>"><%= d.name %></a></td>
-                    <td class="narrow"><%= d.count %></td>
-                    </tr>
-                </script>
-
-                <script name="no-queues-row" type="text/template">
-                    <tr>
-                        <td colspan="3">No queues.</td>
-                    </tr>
-                </script>
-
-            </div>
-        </div>
-        <div class="span6">
-            <div class="section">
-
-                <h2>Workers <a id="workers-btn" href="#">(toggle)</a></h2>
-
-                <p id="workers-count" class="fixed intro">No workers registered!</p>
-
-                <table id="workers" class="table table-bordered" style="display: none;">
-                    <thead>
-                    <tr>
-                        <th style="width:48px">State</th>
-                        <th>Worker</th>
-                        <th>Queues</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    <tr data-role="loading-placeholder">
-                        <td colspan="3">Loading...</td>
-                    </tr>
-                    </tbody>
-                </table>
-
-                <script name="worker-row" type="text/template">
-                    <tr data-role="worker">
-                        <td><i class="icon-<%= d.state %> icon-right" title="Job ID: <%= d.current_job.job_id %>&#013;&#010;Description: <% if (d.current_job.description){ %> <%- d.current_job.description %> <% } %>&#013;&#010;Created at: <%= d.current_job.created_at %>&#013;&#010;"></i></td>
-                        <td <% if (d.version && d.python_version) { %>title ="RQ <%= d.version %> / Python <%= d.python_version %>" <% } %>><%= d.name %></td>
-                        <td><%= d.queues.join(', ') %></td>
-                    </tr>
-                </script>
-
-                <script name="no-workers-row" type="text/template">
-                    <tr>
-                        <td colspan="3">No workers.</td>
-                    </tr>
-                </script>
-
-            </div>
-        </div>
-    </div>
-    <div class="col-sm-4">
-        <div class="span12">
-            <div class="section">
-
-                <h2>Jobs on <strong{% if queue.name == 'failed' %} class="failed"{% endif %}>{{ queue.name }}</strong></h2>
-                <p class="intro">
-                    {% if queue.name != 'failed' %}
-                    <a href="{{ url_for('rq_dashboard.empty_queue', queue_name=queue.name) }}" id="empty-btn"
-                       class="btn btn-danger btn-small" style="float: right" data-toggle="tooltip"
-                       title="Remove all jobs from this queue (<b>destructive</b>)" data-html=true><i
-                            class="icon-trash icon-white icon-right"></i> Empty</a>
-                    <a href="{{ url_for('rq_dashboard.compact_queue', queue_name=queue.name) }}" id="compact-btn"
-                       class="btn btn-small" style="float: right; margin-right: 8px;" data-toggle="tooltip"
-                       title="Remove all stale jobs from this queue (non-destructive)"><i class="icon-resize-small icon-right"></i>
-                        Compact</a>
-                    {% else %}
-                    <a href="{{ url_for('rq_dashboard.requeue_all') }}" id="requeue-all-btn" class="btn btn-small"
-                       style="float: right; margin-right: 8px;"><i class="icon-retweet icon-right"></i> Requeue All</a>
-                    This list below contains all the registered jobs on queue <strong>{{ queue.name }}</strong>, sorted by
-                    age (oldest on top).</p>
-                    {% endif %}
-
-                <table id="jobs" class="table table-bordered">
-                    <thead>
-                    <tr>
-                        <th>Name</th>
-                        <th>Age</th>
-                        <th class="narrow">Actions</th>
-                    </tr>
-                    </thead>
-                    <tbody>
-                    <tr data-role="loading-placeholder">
-                        <td colspan="2">Loading...</td>
-                    </tr>
-                    </tbody>
-                </table>
-
-                <script name="job-row" type="text/template">
-                    <tr data-role="job" data-job-id="<%= d.id %>">
-                        <td>
-                            <i class="icon-file icon-right" style="opacity: .5;"></i>
-                            <span class="description"><%= $('<div/>').text(d.description).html() %></span>
-                            <% if (d.exc_info) { %>
-                            <span class="origin">from <strong><%= d.origin %></strong></span>
-                            <% } %>
-                            <div class="job_id"><%= d.id %></div>
-                            <% if (d.exc_info) { %>
-                            <span class="end_date">Failed <%= d.ended_at %></span>
-                            <pre class="exc_info"><%= $('<div/>').text(d.exc_info).html() %></pre>
-                            <% } %>
-                        </td>
-                        <td><span class="creation_date"><%= d.created_at %></span></td>
-                        <td class="actions narrow">
-                            <% if (d.exc_info) { %>
-                            <a href="#" data-role="requeue-job-btn" class="btn btn-small"><i class="icon-retweet icon-right"></i>
-                                Requeue</a>
-                            <% } %>
-                            <a href="#" data-role="cancel-job-btn" class="btn btn-small"><i class="icon-remove icon-right"></i> Cancel</a>
-                        </td>
-                    </tr>
-                </script>
-
-                <script name="no-jobs-row" type="text/template">
-                    <tr>
-                        <td colspan="3">No jobs.</td>
-                    </tr>
-                </script>
-
-                <div id="page-selection" class="pagination pagination-centered">
-                    <ul>
-                    </ul>
-                </div>
-
-                <script name="first-page-link" type="text/template">
-                    <li><a href="<%= url %>">&laquo;</a></li>
-                </script>
-
-                <script name="no-previous-page-link" type="text/template">
-                    <li class="disabled"><a href="#">&lsaquo;</a></li>
-                </script>
-
-                <script name="previous-page-link" type="text/template">
-                    <li><a href="<%= url %>">&lsaquo;</a></li>
-                </script>
-
-                <script name="page-link" type="text/template">
-                    <li><a href="<%= url %>"><%= number %></a></li>
-                </script>
-
-                <script name="next-page-link" type="text/template">
-                    <li><a href="<%= url %>">&rsaquo;</a></li>
-                </script>
-
-                <script name="no-next-page-link" type="text/template">
-                    <li class="disabled"><a href="#">&rsaquo;</a></li>
-                </script>
-
-                <script name="last-page-link" type="text/template">
-                    <li><a href="<%= url %>">&raquo;</a></li>
-                </script>
-
-            </div>
-        </div>
-    </div>
-</div>
-
-
-{% endblock %}
-
-{% block footer %}
-
-<!--<footer>-->
-<!--    <a href="https://github.com/Parallels/rq-dashboard">RQ-dashboard</a><span class="text-white"> version: {{ rq_dashboard_version }}</span><br>-->
-<!--    <a href="http://python-rq.org">Python RQ</a><span class="text-white"> version: {{ rq_version }}</span>-->
-<!--</footer>-->
-
-{% endblock %}
-
-
-{% block inline_js %}
-var POLL_INTERVAL = {{ poll_interval }};
-{% include "rq_dashboard/dashboard.js" with context %}
-{% endblock %}
diff --git a/flexmeasures/ui/utils/view_utils.py b/flexmeasures/ui/utils/view_utils.py
index 1323db10d..2005ef081 100644
--- a/flexmeasures/ui/utils/view_utils.py
+++ b/flexmeasures/ui/utils/view_utils.py
@@ -25,13 +25,7 @@ def render_flexmeasures_template(html_filename: str, **variables):
     ):
         variables["documentation_exists"] = True
 
-    variables["show_queues"] = False
-    if current_user.is_authenticated:
-        if (
-            user_has_admin_access(current_user, "update")
-            or current_app.config.get("FLEXMEASURES_MODE", "") == "demo"
-        ):
-            variables["show_queues"] = True
+    variables["queues"] = current_app.queues.keys()
 
     variables["event_starts_after"] = session.get("event_starts_after")
     variables["event_ends_before"] = session.get("event_ends_before")
diff --git a/requirements/3.10/app.txt b/requirements/3.10/app.txt
index 5d60332cb..34889523d 100644
--- a/requirements/3.10/app.txt
+++ b/requirements/3.10/app.txt
@@ -4,9 +4,9 @@
 #
 #    pip-compile --output-file=requirements/3.10/app.txt requirements/app.in
 #
-alembic==1.11.3
+alembic==1.12.0
     # via flask-migrate
-altair==5.0.1
+altair==5.1.1
     # via -r requirements/app.in
 arrow==1.2.3
     # via rq-dashboard
@@ -56,9 +56,9 @@ email-validator==2.0.0.post2
     # via
     #   -r requirements/app.in
     #   flask-security-too
-filelock==3.12.2
+filelock==3.12.3
     # via tldextract
-flask==2.1.2
+flask==2.2.5
     # via
     #   -r requirements/app.in
     #   flask-classful
@@ -75,19 +75,19 @@ flask==2.1.2
     #   flask-wtf
     #   rq-dashboard
     #   sentry-sdk
-flask-classful==0.14.2
+flask-classful==0.16.0
     # via -r requirements/app.in
 flask-cors==4.0.0
     # via -r requirements/app.in
-flask-json==0.3.5
+flask-json==0.4.0
     # via -r requirements/app.in
-flask-login==0.6.1
+flask-login==0.6.2
     # via
     #   -r requirements/app.in
     #   flask-security-too
 flask-mail==0.9.1
     # via -r requirements/app.in
-flask-marshmallow==0.14.0
+flask-marshmallow==0.15.0
     # via -r requirements/app.in
 flask-migrate==4.0.4
     # via -r requirements/app.in
@@ -95,7 +95,7 @@ flask-principal==0.4.0
     # via flask-security-too
 flask-security-too==5.1.2
     # via -r requirements/app.in
-flask-sqlalchemy==2.5.1
+flask-sqlalchemy==3.0.5
     # via
     #   -r requirements/app.in
     #   flask-migrate
@@ -145,7 +145,7 @@ jsonschema==4.19.0
     # via altair
 jsonschema-specifications==2023.7.1
     # via jsonschema
-kiwisolver==1.4.4
+kiwisolver==1.4.5
     # via matplotlib
 lunardate==0.2.0
     # via workalendar
@@ -156,6 +156,7 @@ markupsafe==2.1.3
     #   jinja2
     #   mako
     #   sentry-sdk
+    #   werkzeug
     #   wtforms
 marshmallow==3.20.1
     # via
@@ -168,7 +169,7 @@ marshmallow-polyfield==5.11
     # via -r requirements/app.in
 marshmallow-sqlalchemy==0.29.0
     # via -r requirements/app.in
-matplotlib==3.7.2
+matplotlib==3.7.3
     # via timetomodel
 numpy==1.24.4
     # via
@@ -190,6 +191,8 @@ openturns==1.21
     # via timely-beliefs
 packaging==23.1
     # via
+    #   altair
+    #   flask-marshmallow
     #   marshmallow
     #   marshmallow-sqlalchemy
     #   matplotlib
@@ -234,9 +237,9 @@ pyluach==2.2.0
     # via workalendar
 pymeeus==0.5.12
     # via convertdate
-pyomo==6.6.1
+pyomo==6.6.2
     # via -r requirements/app.in
-pyparsing==3.0.9
+pyparsing==3.1.1
     # via matplotlib
 python-dateutil==2.8.2
     # via
@@ -247,7 +250,7 @@ python-dateutil==2.8.2
     #   workalendar
 python-dotenv==1.0.0
     # via -r requirements/app.in
-pytz==2023.3
+pytz==2023.3.post1
     # via
     #   -r requirements/app.in
     #   pandas
@@ -258,8 +261,11 @@ pyyaml==6.0.1
 redis==4.6.0
     # via
     #   -r requirements/app.in
+    #   redis-sentinel-url
     #   rq
     #   rq-dashboard
+redis-sentinel-url==1.0.1
+    # via rq-dashboard
 referencing==0.30.2
     # via
     #   jsonschema
@@ -270,7 +276,7 @@ requests==2.31.0
     #   tldextract
 requests-file==1.5.1
     # via tldextract
-rpds-py==0.9.2
+rpds-py==0.10.2
     # via
     #   jsonschema
     #   referencing
@@ -278,7 +284,7 @@ rq==1.15.1
     # via
     #   -r requirements/app.in
     #   rq-dashboard
-rq-dashboard==0.6.1
+rq-dashboard==0.6.7
     # via -r requirements/app.in
 scikit-base==0.5.1
     # via sktime
@@ -294,11 +300,10 @@ scipy==1.11.2
     #   statsmodels
     #   timely-beliefs
     #   timetomodel
-sentry-sdk[flask]==1.29.2
+sentry-sdk[flask]==1.30.0
     # via -r requirements/app.in
 six==1.16.0
     # via
-    #   flask-marshmallow
     #   isodate
     #   patsy
     #   python-dateutil
@@ -319,11 +324,11 @@ tabulate==0.9.0
     # via -r requirements/app.in
 threadpoolctl==3.2.0
     # via scikit-learn
-timely-beliefs[forecast]==1.21.0
+timely-beliefs[forecast]==1.22.0
     # via -r requirements/app.in
 timetomodel==0.7.3
     # via -r requirements/app.in
-tldextract==3.4.4
+tldextract==3.5.0
     # via -r requirements/app.in
 toolz==0.12.0
     # via altair
@@ -331,20 +336,23 @@ typing-extensions==4.7.1
     # via
     #   alembic
     #   altair
+    #   filelock
     #   pint
     #   py-moneyed
     #   pydantic
 tzdata==2023.3
     # via pandas
-uniplot==0.10.1
+uniplot==0.10.2
     # via -r requirements/app.in
 urllib3==2.0.4
     # via
     #   requests
     #   sentry-sdk
+vl-convert-python==0.13.1
+    # via -r requirements/app.in
 webargs==8.3.0
     # via -r requirements/app.in
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -r requirements/app.in
     #   flask
diff --git a/requirements/3.10/dev.txt b/requirements/3.10/dev.txt
index 7b5629cfa..20c68124d 100644
--- a/requirements/3.10/dev.txt
+++ b/requirements/3.10/dev.txt
@@ -15,7 +15,7 @@ click==8.1.7
     #   black
 distlib==0.3.7
     # via virtualenv
-filelock==3.12.2
+filelock==3.12.3
     # via
     #   -c requirements/3.10/app.txt
     #   virtualenv
@@ -23,7 +23,7 @@ flake8==4.0.1
     # via -r requirements/dev.in
 flake8-blind-except==0.2.1
     # via -r requirements/dev.in
-identify==2.5.27
+identify==2.5.28
     # via pre-commit
 mccabe==0.6.1
     # via flake8
@@ -46,13 +46,13 @@ platformdirs==3.10.0
     # via
     #   black
     #   virtualenv
-pre-commit==3.3.3
+pre-commit==3.4.0
     # via -r requirements/dev.in
 pycodestyle==2.8.0
     # via flake8
 pyflakes==2.4.0
     # via flake8
-pyinstrument==4.5.1
+pyinstrument==4.5.3
     # via -r requirements/dev.in
 pytest-runner==6.0.0
     # via -r requirements/dev.in
@@ -73,7 +73,7 @@ typing-extensions==4.7.1
     #   -c requirements/3.10/app.txt
     #   mypy
     #   setuptools-scm
-virtualenv==20.24.3
+virtualenv==20.24.5
     # via pre-commit
 watchdog==3.0.0
     # via -r requirements/dev.in
diff --git a/requirements/3.10/docs.txt b/requirements/3.10/docs.txt
index 0df02ce9e..7ccdffa3c 100644
--- a/requirements/3.10/docs.txt
+++ b/requirements/3.10/docs.txt
@@ -55,7 +55,7 @@ six==1.16.0
     #   sphinxcontrib-httpdomain
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==7.2.2
+sphinx==7.2.5
     # via
     #   -r requirements/docs.in
     #   sphinx-copybutton
diff --git a/requirements/3.11/app.txt b/requirements/3.11/app.txt
index dc08b0f4c..3aedebf26 100644
--- a/requirements/3.11/app.txt
+++ b/requirements/3.11/app.txt
@@ -4,9 +4,9 @@
 #
 #    pip-compile --output-file=requirements/3.11/app.txt requirements/app.in
 #
-alembic==1.11.3
+alembic==1.12.0
     # via flask-migrate
-altair==5.0.1
+altair==5.1.1
     # via -r requirements/app.in
 arrow==1.2.3
     # via rq-dashboard
@@ -54,9 +54,9 @@ email-validator==2.0.0.post2
     # via
     #   -r requirements/app.in
     #   flask-security-too
-filelock==3.12.2
+filelock==3.12.3
     # via tldextract
-flask==2.1.2
+flask==2.2.5
     # via
     #   -r requirements/app.in
     #   flask-classful
@@ -73,19 +73,19 @@ flask==2.1.2
     #   flask-wtf
     #   rq-dashboard
     #   sentry-sdk
-flask-classful==0.14.2
+flask-classful==0.16.0
     # via -r requirements/app.in
 flask-cors==4.0.0
     # via -r requirements/app.in
-flask-json==0.3.5
+flask-json==0.4.0
     # via -r requirements/app.in
-flask-login==0.6.1
+flask-login==0.6.2
     # via
     #   -r requirements/app.in
     #   flask-security-too
 flask-mail==0.9.1
     # via -r requirements/app.in
-flask-marshmallow==0.14.0
+flask-marshmallow==0.15.0
     # via -r requirements/app.in
 flask-migrate==4.0.4
     # via -r requirements/app.in
@@ -93,7 +93,7 @@ flask-principal==0.4.0
     # via flask-security-too
 flask-security-too==5.1.2
     # via -r requirements/app.in
-flask-sqlalchemy==2.5.1
+flask-sqlalchemy==3.0.5
     # via
     #   -r requirements/app.in
     #   flask-migrate
@@ -143,7 +143,7 @@ jsonschema==4.19.0
     # via altair
 jsonschema-specifications==2023.7.1
     # via jsonschema
-kiwisolver==1.4.4
+kiwisolver==1.4.5
     # via matplotlib
 lunardate==0.2.0
     # via workalendar
@@ -154,6 +154,7 @@ markupsafe==2.1.3
     #   jinja2
     #   mako
     #   sentry-sdk
+    #   werkzeug
     #   wtforms
 marshmallow==3.20.1
     # via
@@ -166,7 +167,7 @@ marshmallow-polyfield==5.11
     # via -r requirements/app.in
 marshmallow-sqlalchemy==0.29.0
     # via -r requirements/app.in
-matplotlib==3.7.2
+matplotlib==3.7.3
     # via timetomodel
 numpy==1.24.4
     # via
@@ -188,6 +189,8 @@ openturns==1.21
     # via timely-beliefs
 packaging==23.1
     # via
+    #   altair
+    #   flask-marshmallow
     #   marshmallow
     #   marshmallow-sqlalchemy
     #   matplotlib
@@ -232,9 +235,9 @@ pyluach==2.2.0
     # via workalendar
 pymeeus==0.5.12
     # via convertdate
-pyomo==6.6.1
+pyomo==6.6.2
     # via -r requirements/app.in
-pyparsing==3.0.9
+pyparsing==3.1.1
     # via matplotlib
 python-dateutil==2.8.2
     # via
@@ -245,7 +248,7 @@ python-dateutil==2.8.2
     #   workalendar
 python-dotenv==1.0.0
     # via -r requirements/app.in
-pytz==2023.3
+pytz==2023.3.post1
     # via
     #   -r requirements/app.in
     #   pandas
@@ -256,8 +259,11 @@ pyyaml==6.0.1
 redis==4.6.0
     # via
     #   -r requirements/app.in
+    #   redis-sentinel-url
     #   rq
     #   rq-dashboard
+redis-sentinel-url==1.0.1
+    # via rq-dashboard
 referencing==0.30.2
     # via
     #   jsonschema
@@ -268,7 +274,7 @@ requests==2.31.0
     #   tldextract
 requests-file==1.5.1
     # via tldextract
-rpds-py==0.9.2
+rpds-py==0.10.2
     # via
     #   jsonschema
     #   referencing
@@ -276,7 +282,7 @@ rq==1.15.1
     # via
     #   -r requirements/app.in
     #   rq-dashboard
-rq-dashboard==0.6.1
+rq-dashboard==0.6.7
     # via -r requirements/app.in
 scikit-base==0.5.1
     # via sktime
@@ -292,11 +298,10 @@ scipy==1.11.2
     #   statsmodels
     #   timely-beliefs
     #   timetomodel
-sentry-sdk[flask]==1.29.2
+sentry-sdk[flask]==1.30.0
     # via -r requirements/app.in
 six==1.16.0
     # via
-    #   flask-marshmallow
     #   isodate
     #   patsy
     #   python-dateutil
@@ -317,11 +322,11 @@ tabulate==0.9.0
     # via -r requirements/app.in
 threadpoolctl==3.2.0
     # via scikit-learn
-timely-beliefs[forecast]==1.21.0
+timely-beliefs[forecast]==1.22.0
     # via -r requirements/app.in
 timetomodel==0.7.3
     # via -r requirements/app.in
-tldextract==3.4.4
+tldextract==3.5.0
     # via -r requirements/app.in
 toolz==0.12.0
     # via altair
@@ -333,17 +338,17 @@ typing-extensions==4.7.1
     #   pydantic
 tzdata==2023.3
     # via pandas
-uniplot==0.10.1
+uniplot==0.10.2
     # via -r requirements/app.in
 urllib3==2.0.4
     # via
     #   requests
     #   sentry-sdk
-vl-convert-python==0.12.0
+vl-convert-python==0.13.1
     # via -r requirements/app.in
 webargs==8.3.0
     # via -r requirements/app.in
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -r requirements/app.in
     #   flask
diff --git a/requirements/3.11/dev.txt b/requirements/3.11/dev.txt
index aecdad30a..42cc73acf 100644
--- a/requirements/3.11/dev.txt
+++ b/requirements/3.11/dev.txt
@@ -15,7 +15,7 @@ click==8.1.7
     #   black
 distlib==0.3.7
     # via virtualenv
-filelock==3.12.2
+filelock==3.12.3
     # via
     #   -c requirements/3.11/app.txt
     #   virtualenv
@@ -23,7 +23,7 @@ flake8==4.0.1
     # via -r requirements/dev.in
 flake8-blind-except==0.2.1
     # via -r requirements/dev.in
-identify==2.5.27
+identify==2.5.28
     # via pre-commit
 mccabe==0.6.1
     # via flake8
@@ -46,13 +46,13 @@ platformdirs==3.10.0
     # via
     #   black
     #   virtualenv
-pre-commit==3.3.3
+pre-commit==3.4.0
     # via -r requirements/dev.in
 pycodestyle==2.8.0
     # via flake8
 pyflakes==2.4.0
     # via flake8
-pyinstrument==4.5.1
+pyinstrument==4.5.3
     # via -r requirements/dev.in
 pytest-runner==6.0.0
     # via -r requirements/dev.in
@@ -67,7 +67,7 @@ typing-extensions==4.7.1
     #   -c requirements/3.11/app.txt
     #   mypy
     #   setuptools-scm
-virtualenv==20.24.3
+virtualenv==20.24.5
     # via pre-commit
 watchdog==3.0.0
     # via -r requirements/dev.in
diff --git a/requirements/3.11/docs.txt b/requirements/3.11/docs.txt
index 0a9d46a56..05e23084a 100644
--- a/requirements/3.11/docs.txt
+++ b/requirements/3.11/docs.txt
@@ -55,7 +55,7 @@ six==1.16.0
     #   sphinxcontrib-httpdomain
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==7.2.2
+sphinx==7.2.5
     # via
     #   -r requirements/docs.in
     #   sphinx-copybutton
diff --git a/requirements/3.8/app.txt b/requirements/3.8/app.txt
index 64f896227..0c233373f 100644
--- a/requirements/3.8/app.txt
+++ b/requirements/3.8/app.txt
@@ -4,16 +4,18 @@
 #
 #    pip-compile --output-file=requirements/3.8/app.txt requirements/app.in
 #
-alembic==1.11.1
+alembic==1.12.0
     # via flask-migrate
-altair==5.0.1
+altair==5.1.1
     # via -r requirements/app.in
 arrow==1.2.3
     # via rq-dashboard
-async-timeout==4.0.2
+async-timeout==4.0.3
     # via redis
 attrs==23.1.0
-    # via jsonschema
+    # via
+    #   jsonschema
+    #   referencing
 babel==2.12.1
     # via py-moneyed
 backports-zoneinfo==0.2.1
@@ -30,15 +32,15 @@ certifi==2023.7.22
     # via
     #   requests
     #   sentry-sdk
-charset-normalizer==3.1.0
+charset-normalizer==3.2.0
     # via requests
-click==8.1.3
+click==8.1.7
     # via
     #   -r requirements/app.in
     #   click-default-group
     #   flask
     #   rq
-click-default-group==1.2.2
+click-default-group==1.2.4
     # via -r requirements/app.in
 colour==0.1.5
     # via -r requirements/app.in
@@ -48,19 +50,17 @@ convertdate==2.4.0
     # via workalendar
 cycler==0.11.0
     # via matplotlib
-deprecated==1.2.14
-    # via sktime
-dill==0.3.6
+dill==0.3.7
     # via openturns
-dnspython==2.3.0
+dnspython==2.4.2
     # via email-validator
 email-validator==2.0.0.post2
     # via
     #   -r requirements/app.in
     #   flask-security-too
-filelock==3.12.2
+filelock==3.12.3
     # via tldextract
-flask==2.1.2
+flask==2.2.5
     # via
     #   -r requirements/app.in
     #   flask-classful
@@ -77,19 +77,19 @@ flask==2.1.2
     #   flask-wtf
     #   rq-dashboard
     #   sentry-sdk
-flask-classful==0.14.2
+flask-classful==0.16.0
     # via -r requirements/app.in
 flask-cors==4.0.0
     # via -r requirements/app.in
-flask-json==0.3.5
+flask-json==0.4.0
     # via -r requirements/app.in
-flask-login==0.6.1
+flask-login==0.6.2
     # via
     #   -r requirements/app.in
     #   flask-security-too
 flask-mail==0.9.1
     # via -r requirements/app.in
-flask-marshmallow==0.14.0
+flask-marshmallow==0.15.0
     # via -r requirements/app.in
 flask-migrate==4.0.4
     # via -r requirements/app.in
@@ -97,7 +97,7 @@ flask-principal==0.4.0
     # via flask-security-too
 flask-security-too==5.1.2
     # via -r requirements/app.in
-flask-sqlalchemy==2.5.1
+flask-sqlalchemy==3.0.5
     # via
     #   -r requirements/app.in
     #   flask-migrate
@@ -107,27 +107,28 @@ flask-wtf==1.1.1
     # via
     #   -r requirements/app.in
     #   flask-security-too
-fonttools==4.40.0
+fonttools==4.42.1
     # via matplotlib
 greenlet==2.0.2
     # via sqlalchemy
-humanize==4.7.0
+humanize==4.8.0
     # via -r requirements/app.in
 idna==3.4
     # via
     #   email-validator
     #   requests
     #   tldextract
-importlib-metadata==6.7.0
+importlib-metadata==6.8.0
     # via
     #   -r requirements/app.in
     #   alembic
     #   flask
     #   timely-beliefs
-importlib-resources==6.0.0
+importlib-resources==6.0.1
     # via
     #   alembic
     #   jsonschema
+    #   jsonschema-specifications
     #   matplotlib
 inflect==6.0.2
     # via -r requirements/app.in
@@ -148,11 +149,13 @@ jinja2==3.1.2
     # via
     #   altair
     #   flask
-joblib==1.3.1
+joblib==1.3.2
     # via scikit-learn
-jsonschema==4.17.3
+jsonschema==4.19.0
     # via altair
-kiwisolver==1.4.4
+jsonschema-specifications==2023.7.1
+    # via jsonschema
+kiwisolver==1.4.5
     # via matplotlib
 lunardate==0.2.0
     # via workalendar
@@ -163,8 +166,9 @@ markupsafe==2.1.3
     #   jinja2
     #   mako
     #   sentry-sdk
+    #   werkzeug
     #   wtforms
-marshmallow==3.19.0
+marshmallow==3.20.1
     # via
     #   -r requirements/app.in
     #   flask-marshmallow
@@ -175,9 +179,9 @@ marshmallow-polyfield==5.11
     # via -r requirements/app.in
 marshmallow-sqlalchemy==0.29.0
     # via -r requirements/app.in
-matplotlib==3.7.1
+matplotlib==3.7.3
     # via timetomodel
-numpy==1.23.5
+numpy==1.24.4
     # via
     #   -r requirements/app.in
     #   altair
@@ -197,6 +201,8 @@ openturns==1.21
     # via timely-beliefs
 packaging==23.1
     # via
+    #   altair
+    #   flask-marshmallow
     #   marshmallow
     #   marshmallow-sqlalchemy
     #   matplotlib
@@ -215,7 +221,7 @@ passlib==1.7.4
     # via flask-security-too
 patsy==0.5.3
     # via statsmodels
-pillow==9.5.0
+pillow==10.0.0
     # via matplotlib
 pint==0.21.1
     # via -r requirements/app.in
@@ -229,13 +235,13 @@ pscript==0.7.7
     # via -r requirements/app.in
 psutil==5.9.5
     # via openturns
-psycopg2-binary==2.9.6
+psycopg2-binary==2.9.7
     # via
     #   -r requirements/app.in
     #   timely-beliefs
 py-moneyed==3.0
     # via -r requirements/app.in
-pydantic==1.10.10
+pydantic==1.10.12
     # via
     #   -r requirements/app.in
     #   inflect
@@ -243,12 +249,10 @@ pyluach==2.2.0
     # via workalendar
 pymeeus==0.5.12
     # via convertdate
-pyomo==6.6.1
+pyomo==6.6.2
     # via -r requirements/app.in
-pyparsing==3.1.0
+pyparsing==3.1.1
     # via matplotlib
-pyrsistent==0.19.3
-    # via jsonschema
 python-dateutil==2.8.2
     # via
     #   arrow
@@ -258,7 +262,7 @@ python-dateutil==2.8.2
     #   workalendar
 python-dotenv==1.0.0
     # via -r requirements/app.in
-pytz==2023.3
+pytz==2023.3.post1
     # via
     #   -r requirements/app.in
     #   babel
@@ -270,23 +274,34 @@ pyyaml==6.0.1
 redis==4.6.0
     # via
     #   -r requirements/app.in
+    #   redis-sentinel-url
     #   rq
     #   rq-dashboard
+redis-sentinel-url==1.0.1
+    # via rq-dashboard
+referencing==0.30.2
+    # via
+    #   jsonschema
+    #   jsonschema-specifications
 requests==2.31.0
     # via
     #   requests-file
     #   tldextract
 requests-file==1.5.1
     # via tldextract
+rpds-py==0.10.2
+    # via
+    #   jsonschema
+    #   referencing
 rq==1.15.1
     # via
     #   -r requirements/app.in
     #   rq-dashboard
-rq-dashboard==0.6.1
+rq-dashboard==0.6.7
     # via -r requirements/app.in
-scikit-base==0.5.0
+scikit-base==0.5.1
     # via sktime
-scikit-learn==1.2.2
+scikit-learn==1.3.0
     # via
     #   sktime
     #   timetomodel
@@ -298,18 +313,17 @@ scipy==1.10.1
     #   statsmodels
     #   timely-beliefs
     #   timetomodel
-sentry-sdk[flask]==1.26.0
+sentry-sdk[flask]==1.30.0
     # via -r requirements/app.in
 six==1.16.0
     # via
-    #   flask-marshmallow
     #   isodate
     #   patsy
     #   python-dateutil
     #   requests-file
-sktime==0.20.0
+sktime==0.22.0
     # via timely-beliefs
-sqlalchemy==1.4.48
+sqlalchemy==1.4.49
     # via
     #   -r requirements/app.in
     #   alembic
@@ -321,49 +335,49 @@ statsmodels==0.14.0
     # via timetomodel
 tabulate==0.9.0
     # via -r requirements/app.in
-threadpoolctl==3.1.0
+threadpoolctl==3.2.0
     # via scikit-learn
-timely-beliefs[forecast]==1.21.0
+timely-beliefs[forecast]==1.22.0
     # via -r requirements/app.in
 timetomodel==0.7.3
     # via -r requirements/app.in
-tldextract==3.4.4
+tldextract==3.5.0
     # via -r requirements/app.in
 toolz==0.12.0
     # via altair
-typing-extensions==4.7.0
+typing-extensions==4.7.1
     # via
     #   alembic
     #   altair
+    #   filelock
     #   py-moneyed
     #   pydantic
 tzdata==2023.3
     # via pandas
-uniplot==0.10.0
+uniplot==0.10.2
     # via -r requirements/app.in
-urllib3==2.0.3
+urllib3==2.0.4
     # via
     #   requests
     #   sentry-sdk
-vl-convert-python==0.12.0
+vl-convert-python==0.13.1
     # via -r requirements/app.in
+webargs==8.3.0
     # via -r requirements/app.in
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -r requirements/app.in
     #   flask
     #   flask-login
 workalendar==17.0.0
     # via -r requirements/app.in
-wrapt==1.15.0
-    # via deprecated
 wtforms==3.0.1
     # via
     #   flask-security-too
     #   flask-wtf
 xlrd==2.0.1
     # via -r requirements/app.in
-zipp==3.15.0
+zipp==3.16.2
     # via
     #   importlib-metadata
     #   importlib-resources
diff --git a/requirements/3.8/dev.txt b/requirements/3.8/dev.txt
index a9728a57a..87145f421 100644
--- a/requirements/3.8/dev.txt
+++ b/requirements/3.8/dev.txt
@@ -6,16 +6,16 @@
 #
 black==22.3.0
     # via -r requirements/dev.in
-cfgv==3.3.1
+cfgv==3.4.0
     # via pre-commit
-click==8.1.3
+click==8.1.7
     # via
     #   -c requirements/3.8/app.txt
     #   -c requirements/3.8/test.txt
     #   black
-distlib==0.3.6
+distlib==0.3.7
     # via virtualenv
-filelock==3.12.2
+filelock==3.12.3
     # via
     #   -c requirements/3.8/app.txt
     #   virtualenv
@@ -23,11 +23,11 @@ flake8==4.0.1
     # via -r requirements/dev.in
 flake8-blind-except==0.2.1
     # via -r requirements/dev.in
-identify==2.5.24
+identify==2.5.28
     # via pre-commit
 mccabe==0.6.1
     # via flake8
-mypy==1.4.1
+mypy==1.5.1
     # via -r requirements/dev.in
 mypy-extensions==1.0.0
     # via
@@ -40,19 +40,19 @@ packaging==23.1
     #   -c requirements/3.8/app.txt
     #   -c requirements/3.8/test.txt
     #   setuptools-scm
-pathspec==0.11.1
+pathspec==0.11.2
     # via black
-platformdirs==3.8.0
+platformdirs==3.10.0
     # via
     #   black
     #   virtualenv
-pre-commit==3.3.3
+pre-commit==3.4.0
     # via -r requirements/dev.in
 pycodestyle==2.8.0
     # via flake8
 pyflakes==2.4.0
     # via flake8
-pyinstrument==4.5.0
+pyinstrument==4.5.3
     # via -r requirements/dev.in
 pytest-runner==6.0.0
     # via -r requirements/dev.in
@@ -68,13 +68,14 @@ tomli==2.0.1
     #   black
     #   mypy
     #   setuptools-scm
-typing-extensions==4.7.0
+typing-extensions==4.7.1
     # via
     #   -c requirements/3.8/app.txt
     #   black
+    #   filelock
     #   mypy
     #   setuptools-scm
-virtualenv==20.23.1
+virtualenv==20.24.5
     # via pre-commit
 watchdog==3.0.0
     # via -r requirements/dev.in
diff --git a/requirements/3.8/docs.txt b/requirements/3.8/docs.txt
index 99361d659..cb7e29d7a 100644
--- a/requirements/3.8/docs.txt
+++ b/requirements/3.8/docs.txt
@@ -14,7 +14,7 @@ certifi==2023.7.22
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-charset-normalizer==3.1.0
+charset-normalizer==3.2.0
     # via
     #   -c requirements/3.8/app.txt
     #   requests
@@ -29,7 +29,7 @@ idna==3.4
     #   requests
 imagesize==1.4.1
     # via sphinx
-importlib-metadata==6.7.0
+importlib-metadata==6.8.0
     # via
     #   -c requirements/3.8/app.txt
     #   sphinx
@@ -45,11 +45,11 @@ packaging==23.1
     # via
     #   -c requirements/3.8/app.txt
     #   sphinx
-pygments==2.15.1
+pygments==2.16.1
     # via
     #   sphinx
     #   sphinx-tabs
-pytz==2023.3
+pytz==2023.3.post1
     # via
     #   -c requirements/3.8/app.txt
     #   babel
@@ -63,7 +63,7 @@ six==1.16.0
     #   sphinxcontrib-httpdomain
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==6.2.1
+sphinx==7.1.2
     # via
     #   -r requirements/docs.in
     #   sphinx-copybutton
@@ -76,7 +76,7 @@ sphinx-copybutton==0.5.2
     # via -r requirements/docs.in
 sphinx-fontawesome==0.0.6
     # via -r requirements/docs.in
-sphinx-rtd-theme==1.2.2
+sphinx-rtd-theme==1.3.0
     # via -r requirements/docs.in
 sphinx-tabs==3.4.1
     # via -r requirements/docs.in
@@ -96,11 +96,11 @@ sphinxcontrib-qthelp==1.0.3
     # via sphinx
 sphinxcontrib-serializinghtml==1.1.5
     # via sphinx
-urllib3==2.0.3
+urllib3==2.0.4
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-zipp==3.15.0
+zipp==3.16.2
     # via
     #   -c requirements/3.8/app.txt
     #   importlib-metadata
diff --git a/requirements/3.9/app.txt b/requirements/3.9/app.txt
index d438479ad..b6fb863f2 100644
--- a/requirements/3.9/app.txt
+++ b/requirements/3.9/app.txt
@@ -4,9 +4,9 @@
 #
 #    pip-compile --output-file=requirements/3.9/app.txt requirements/app.in
 #
-alembic==1.11.2
+alembic==1.12.0
     # via flask-migrate
-altair==5.0.1
+altair==5.1.1
     # via -r requirements/app.in
 arrow==1.2.3
     # via rq-dashboard
@@ -32,7 +32,7 @@ certifi==2023.7.22
     #   sentry-sdk
 charset-normalizer==3.2.0
     # via requests
-click==8.1.6
+click==8.1.7
     # via
     #   -r requirements/app.in
     #   click-default-group
@@ -48,8 +48,6 @@ convertdate==2.4.0
     # via workalendar
 cycler==0.11.0
     # via matplotlib
-deprecated==1.2.14
-    # via sktime
 dill==0.3.7
     # via openturns
 dnspython==2.4.2
@@ -58,9 +56,9 @@ email-validator==2.0.0.post2
     # via
     #   -r requirements/app.in
     #   flask-security-too
-filelock==3.12.2
+filelock==3.12.3
     # via tldextract
-flask==2.1.2
+flask==2.2.5
     # via
     #   -r requirements/app.in
     #   flask-classful
@@ -77,19 +75,19 @@ flask==2.1.2
     #   flask-wtf
     #   rq-dashboard
     #   sentry-sdk
-flask-classful==0.14.2
+flask-classful==0.16.0
     # via -r requirements/app.in
 flask-cors==4.0.0
     # via -r requirements/app.in
-flask-json==0.3.5
+flask-json==0.4.0
     # via -r requirements/app.in
-flask-login==0.6.1
+flask-login==0.6.2
     # via
     #   -r requirements/app.in
     #   flask-security-too
 flask-mail==0.9.1
     # via -r requirements/app.in
-flask-marshmallow==0.14.0
+flask-marshmallow==0.15.0
     # via -r requirements/app.in
 flask-migrate==4.0.4
     # via -r requirements/app.in
@@ -97,7 +95,7 @@ flask-principal==0.4.0
     # via flask-security-too
 flask-security-too==5.1.2
     # via -r requirements/app.in
-flask-sqlalchemy==2.5.1
+flask-sqlalchemy==3.0.5
     # via
     #   -r requirements/app.in
     #   flask-migrate
@@ -107,11 +105,11 @@ flask-wtf==1.1.1
     # via
     #   -r requirements/app.in
     #   flask-security-too
-fonttools==4.42.0
+fonttools==4.42.1
     # via matplotlib
 greenlet==2.0.2
     # via sqlalchemy
-humanize==4.7.0
+humanize==4.8.0
     # via -r requirements/app.in
 idna==3.4
     # via
@@ -124,7 +122,9 @@ importlib-metadata==6.8.0
     #   flask
     #   timely-beliefs
 importlib-resources==6.0.1
-    # via matplotlib
+    # via
+    #   flask-security-too
+    #   matplotlib
 inflect==6.0.2
     # via -r requirements/app.in
 inflection==0.5.1
@@ -150,7 +150,7 @@ jsonschema==4.19.0
     # via altair
 jsonschema-specifications==2023.7.1
     # via jsonschema
-kiwisolver==1.4.4
+kiwisolver==1.4.5
     # via matplotlib
 lunardate==0.2.0
     # via workalendar
@@ -161,6 +161,7 @@ markupsafe==2.1.3
     #   jinja2
     #   mako
     #   sentry-sdk
+    #   werkzeug
     #   wtforms
 marshmallow==3.20.1
     # via
@@ -173,7 +174,7 @@ marshmallow-polyfield==5.11
     # via -r requirements/app.in
 marshmallow-sqlalchemy==0.29.0
     # via -r requirements/app.in
-matplotlib==3.7.2
+matplotlib==3.7.3
     # via timetomodel
 numpy==1.24.4
     # via
@@ -195,6 +196,8 @@ openturns==1.21
     # via timely-beliefs
 packaging==23.1
     # via
+    #   altair
+    #   flask-marshmallow
     #   marshmallow
     #   marshmallow-sqlalchemy
     #   matplotlib
@@ -239,9 +242,9 @@ pyluach==2.2.0
     # via workalendar
 pymeeus==0.5.12
     # via convertdate
-pyomo==6.6.1
+pyomo==6.6.2
     # via -r requirements/app.in
-pyparsing==3.0.9
+pyparsing==3.1.1
     # via matplotlib
 python-dateutil==2.8.2
     # via
@@ -252,7 +255,7 @@ python-dateutil==2.8.2
     #   workalendar
 python-dotenv==1.0.0
     # via -r requirements/app.in
-pytz==2023.3
+pytz==2023.3.post1
     # via
     #   -r requirements/app.in
     #   pandas
@@ -263,8 +266,11 @@ pyyaml==6.0.1
 redis==4.6.0
     # via
     #   -r requirements/app.in
+    #   redis-sentinel-url
     #   rq
     #   rq-dashboard
+redis-sentinel-url==1.0.1
+    # via rq-dashboard
 referencing==0.30.2
     # via
     #   jsonschema
@@ -275,7 +281,7 @@ requests==2.31.0
     #   tldextract
 requests-file==1.5.1
     # via tldextract
-rpds-py==0.9.2
+rpds-py==0.10.2
     # via
     #   jsonschema
     #   referencing
@@ -283,15 +289,15 @@ rq==1.15.1
     # via
     #   -r requirements/app.in
     #   rq-dashboard
-rq-dashboard==0.6.1
+rq-dashboard==0.6.7
     # via -r requirements/app.in
-scikit-base==0.5.0
+scikit-base==0.5.1
     # via sktime
 scikit-learn==1.3.0
     # via
     #   sktime
     #   timetomodel
-scipy==1.11.1
+scipy==1.11.2
     # via
     #   properscoring
     #   scikit-learn
@@ -299,16 +305,15 @@ scipy==1.11.1
     #   statsmodels
     #   timely-beliefs
     #   timetomodel
-sentry-sdk[flask]==1.29.2
+sentry-sdk[flask]==1.30.0
     # via -r requirements/app.in
 six==1.16.0
     # via
-    #   flask-marshmallow
     #   isodate
     #   patsy
     #   python-dateutil
     #   requests-file
-sktime==0.21.0
+sktime==0.22.0
     # via timely-beliefs
 sqlalchemy==1.4.49
     # via
@@ -324,11 +329,11 @@ tabulate==0.9.0
     # via -r requirements/app.in
 threadpoolctl==3.2.0
     # via scikit-learn
-timely-beliefs[forecast]==1.21.0
+timely-beliefs[forecast]==1.22.0
     # via -r requirements/app.in
 timetomodel==0.7.3
     # via -r requirements/app.in
-tldextract==3.4.4
+tldextract==3.5.0
     # via -r requirements/app.in
 toolz==0.12.0
     # via altair
@@ -336,30 +341,29 @@ typing-extensions==4.7.1
     # via
     #   alembic
     #   altair
+    #   filelock
     #   pint
     #   py-moneyed
     #   pydantic
 tzdata==2023.3
     # via pandas
-uniplot==0.10.0
+uniplot==0.10.2
     # via -r requirements/app.in
 urllib3==2.0.4
     # via
     #   requests
     #   sentry-sdk
-vl-convert-python==0.12.0
+vl-convert-python==0.13.1
     # via -r requirements/app.in
 webargs==8.3.0
     # via -r requirements/app.in
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -r requirements/app.in
     #   flask
     #   flask-login
 workalendar==17.0.0
     # via -r requirements/app.in
-wrapt==1.15.0
-    # via deprecated
 wtforms==3.0.1
     # via
     #   flask-security-too
diff --git a/requirements/3.9/dev.txt b/requirements/3.9/dev.txt
index a8a625aff..1bd2dba5d 100644
--- a/requirements/3.9/dev.txt
+++ b/requirements/3.9/dev.txt
@@ -6,16 +6,16 @@
 #
 black==22.3.0
     # via -r requirements/dev.in
-cfgv==3.3.1
+cfgv==3.4.0
     # via pre-commit
-click==8.1.6
+click==8.1.7
     # via
     #   -c requirements/3.9/app.txt
     #   -c requirements/3.9/test.txt
     #   black
 distlib==0.3.7
     # via virtualenv
-filelock==3.12.2
+filelock==3.12.3
     # via
     #   -c requirements/3.9/app.txt
     #   virtualenv
@@ -23,11 +23,11 @@ flake8==4.0.1
     # via -r requirements/dev.in
 flake8-blind-except==0.2.1
     # via -r requirements/dev.in
-identify==2.5.26
+identify==2.5.28
     # via pre-commit
 mccabe==0.6.1
     # via flake8
-mypy==1.5.0
+mypy==1.5.1
     # via -r requirements/dev.in
 mypy-extensions==1.0.0
     # via
@@ -46,13 +46,13 @@ platformdirs==3.10.0
     # via
     #   black
     #   virtualenv
-pre-commit==3.3.3
+pre-commit==3.4.0
     # via -r requirements/dev.in
 pycodestyle==2.8.0
     # via flake8
 pyflakes==2.4.0
     # via flake8
-pyinstrument==4.5.1
+pyinstrument==4.5.3
     # via -r requirements/dev.in
 pytest-runner==6.0.0
     # via -r requirements/dev.in
@@ -72,9 +72,10 @@ typing-extensions==4.7.1
     # via
     #   -c requirements/3.9/app.txt
     #   black
+    #   filelock
     #   mypy
     #   setuptools-scm
-virtualenv==20.24.2
+virtualenv==20.24.5
     # via pre-commit
 watchdog==3.0.0
     # via -r requirements/dev.in
diff --git a/requirements/3.9/docs.txt b/requirements/3.9/docs.txt
index 205c358fb..5984bcc91 100644
--- a/requirements/3.9/docs.txt
+++ b/requirements/3.9/docs.txt
@@ -59,7 +59,7 @@ six==1.16.0
     #   sphinxcontrib-httpdomain
 snowballstemmer==2.2.0
     # via sphinx
-sphinx==6.2.1
+sphinx==7.2.5
     # via
     #   -r requirements/docs.in
     #   sphinx-copybutton
@@ -77,15 +77,15 @@ sphinx-copybutton==0.5.2
     # via -r requirements/docs.in
 sphinx-fontawesome==0.0.6
     # via -r requirements/docs.in
-sphinx-rtd-theme==1.2.2
+sphinx-rtd-theme==1.3.0
     # via -r requirements/docs.in
 sphinx-tabs==3.4.1
     # via -r requirements/docs.in
-sphinxcontrib-applehelp==1.0.6
+sphinxcontrib-applehelp==1.0.7
     # via sphinx
-sphinxcontrib-devhelp==1.0.4
+sphinxcontrib-devhelp==1.0.5
     # via sphinx
-sphinxcontrib-htmlhelp==2.0.3
+sphinxcontrib-htmlhelp==2.0.4
     # via sphinx
 sphinxcontrib-httpdomain==1.8.1
     # via -r requirements/docs.in
@@ -93,9 +93,9 @@ sphinxcontrib-jquery==4.1
     # via sphinx-rtd-theme
 sphinxcontrib-jsmath==1.0.1
     # via sphinx
-sphinxcontrib-qthelp==1.0.5
+sphinxcontrib-qthelp==1.0.6
     # via sphinx
-sphinxcontrib-serializinghtml==1.1.7
+sphinxcontrib-serializinghtml==1.1.9
     # via sphinx
 urllib3==2.0.4
     # via
diff --git a/requirements/app.in b/requirements/app.in
index 0195f207b..dc70fbb16 100644
--- a/requirements/app.in
+++ b/requirements/app.in
@@ -24,7 +24,6 @@ click
 click-default-group
 email_validator
 rq
-# rq-dashboard-compatible # use this if compatibility with Flask 2.3 is not done
 rq-dashboard
 # the following uses environment markers (see PEP 496)
 rq-win; os_name == 'nt' or os_name == 'win'
@@ -47,16 +46,10 @@ Flask-Migrate
 Flask-WTF
 Flask-Mail
 # <5.2: https://github.com/Parallels/rq-dashboard/issues/417 as 5.2 requires Flask 2.3
-Flask-Security-Too>=5.0, <5.2
-# This pin is tough to debug, but logging in (in API) stops working at 0.6.2.
-# On repeated call to API with auth token, user is anonymous.
-# See test_get_one_user or test_api_task_run_post_unauthorized_wrong_role for a good example
-# The breaking code is introduced in https://github.com/maxcountryman/flask-login/issues/690
-# Moving to Flask>=2.2 should resolve this issue.
-Flask-Login<0.6.2
-Flask-Classful
-# <0.15: https://github.com/marshmallow-code/flask-marshmallow/issues/262
-Flask-Marshmallow<0.15
+Flask-Security-Too>=5.0
+Flask-Login
+Flask-Classful>=0.16
+Flask-Marshmallow
 Flask-Cors
 sentry-sdk[flask]
 marshmallow>=3
@@ -65,10 +58,10 @@ marshmallow-sqlalchemy>=0.23.1
 webargs
 # Minimum version that correctly aligns time series that include NaN values
 uniplot>=0.7.0
-# Maximum constraints here due to Flask-Classful not supporting Werkzeug 2.2.0 yet, see GH#595 and https://github.com/teracyhq/flask-classful/pull/145
-Flask-SQLAlchemy>=2.4.3,<3
+# >=2.5.2: https://github.com/marshmallow-code/flask-marshmallow/issues/262
+Flask-SQLAlchemy>=2.5.2
 # flask should be after all the flask plugins, because setup might find they ARE flask
-# <2.3: https://github.com/Parallels/rq-dashboard/issues/417 and https://github.com/FlexMeasures/flexmeasures/issues/754 and flask-login 0.6.1 not compatible
-flask>=1.0, <=2.1.2
-werkzeug<=2.1
+# <2.3: https://github.com/FlexMeasures/flexmeasures/issues/754
+flask>=1.0, <2.3
+werkzeug<=2.2.3
 vl-convert-python
\ No newline at end of file


Test patch:
diff --git a/flexmeasures/api/conftest.py b/flexmeasures/api/conftest.py
new file mode 100644
index 000000000..277245588
--- /dev/null
+++ b/flexmeasures/api/conftest.py
@@ -0,0 +1,39 @@
+import pytest
+
+from flask_login import login_user, logout_user
+
+from flexmeasures.api.tests.utils import UserContext
+
+
+@pytest.fixture
+def requesting_user(request):
+    """Use this fixture to log in a user for the scope of a test.
+
+    Sets the user by passing it an email address (see usage examples below), or pass None to get the AnonymousUser.
+    Passes the user object to the test.
+    Logs the user out after the test ran.
+
+    Usage:
+
+    >>> @pytest.mark.parametrize("requesting_user", ["test_prosumer_user_2@seita.nl", None], indirect=True)
+
+    Or in combination with other parameters:
+
+    @pytest.mark.parametrize(
+        "requesting_user, status_code",
+        [
+            (None, 401),
+            ("test_prosumer_user_2@seita.nl", 200),
+        ],
+        indirect=["requesting_user"],
+    )
+
+    """
+    email = request.param
+    if email is not None:
+        with UserContext(email) as user:
+            login_user(user)
+            yield user
+            logout_user()
+    else:
+        yield
diff --git a/flexmeasures/api/tests/test_auth_token.py b/flexmeasures/api/tests/test_auth_token.py
new file mode 100644
index 000000000..69aecf648
--- /dev/null
+++ b/flexmeasures/api/tests/test_auth_token.py
@@ -0,0 +1,51 @@
+from flask_login import current_user, login_user, logout_user
+from flask_security.core import AnonymousUser
+from flask_security.proxies import _security
+from flask_security import decorators as fs_decorators
+from flask_principal import Identity, identity_changed
+from flask import url_for, current_app, request
+
+from flexmeasures.api.tests.utils import UserContext
+
+
+def patched_check_token() -> bool:
+    """
+    The _check_token function in Flask-Security is successfully getting the user,
+    but it fails to stick with flask_login.
+    This happens only when testing, so our test setup might not be 100% compatible
+    with Flask >2.2 ecosystem.
+
+    See for details:
+    https://github.com/FlexMeasures/flexmeasures/pull/838#discussion_r1321692937
+    https://github.com/Flask-Middleware/flask-security/issues/834
+    """
+    user = _security.login_manager.request_callback(request)
+    if user and user.is_authenticated:
+        app = current_app._get_current_object()
+        identity_changed.send(app, identity=Identity(user.fs_uniquifier))
+
+        login_user(user)  # THIS LINE ADDED BY US
+        return True
+
+    return False
+
+
+def test_auth_token(monkeypatch, app, client, setup_api_test_data):
+    """Use an auth token to query an endpoint.
+    (we test other endpoints using the api/conftest/requesting_user fixture,
+     so they're already logged in via session)
+    """
+    with UserContext("test_admin_user@seita.nl") as admin:
+        auth_token = admin.get_auth_token()
+    assert isinstance(current_user, AnonymousUser)
+
+    monkeypatch.setattr(fs_decorators, "_check_token", patched_check_token)
+
+    print("Getting assets ...")
+    response = client.get(
+        url_for("AssetAPI:index"), headers={"Authorization": auth_token}
+    )
+    print(response)
+    assert response.status_code == 200
+    logout_user()  # undo the login made by our patch during token auth
+    assert response.json == []  # admin has no assets themselves
diff --git a/flexmeasures/api/tests/test_task_runs.py b/flexmeasures/api/tests/test_task_runs.py
index dd8be64fc..579825685 100644
--- a/flexmeasures/api/tests/test_task_runs.py
+++ b/flexmeasures/api/tests/test_task_runs.py
@@ -1,3 +1,5 @@
+import pytest
+
 from datetime import datetime, timedelta
 
 from flask import url_for
@@ -12,7 +14,10 @@
 )
 
 
-def test_api_task_run_post_unauthorized_wrong_role(client):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
+def test_api_task_run_post_unauthorized_wrong_role(client, requesting_user):
     url = url_for("flexmeasures_api_ops.post_task_run")
     auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
     post_req_params = dict(
@@ -21,12 +26,7 @@ def test_api_task_run_post_unauthorized_wrong_role(client):
     task_run = client.post(url, **post_req_params)
     assert task_run.status_code == FORBIDDEN_STATUS_CODE
     assert b"cannot be authorized" in task_run.data
-    # While we are on it, test if the unauth handler correctly returns json if we set the content-type
-    post_req_params.update(
-        headers={"Authorization": auth_token, "Content-Type": "application/json"}
-    )
-    task_run = client.post(url, **post_req_params)
-    assert task_run.status_code == FORBIDDEN_STATUS_CODE
+    # While we are on it, test if the unauth handler correctly returns json even if we don't explicitly set the content-type
     assert task_run.json["status"] == FORBIDDEN_ERROR_STATUS
 
 
@@ -58,7 +58,8 @@ def test_api_task_run_get_nonexistent_task(client):
     assert "has no last run time" in task_run.json["reason"]
 
 
-def test_api_task_run_post_no_name(client):
+@pytest.mark.parametrize("requesting_user", ["task_runner@seita.nl"], indirect=True)
+def test_api_task_run_post_no_name(client, requesting_user):
     task_run = post_task_run(client, "")
     assert task_run.status_code == 400
     assert task_run.json["status"] == "ERROR"
@@ -78,7 +79,8 @@ def test_api_task_run_get_recent_entry(client):
     assert task_run.json.get("status") == "ERROR"
 
 
-def test_api_task_run_get_older_entry_then_update(client):
+@pytest.mark.parametrize("requesting_user", ["task_runner@seita.nl"], indirect=True)
+def test_api_task_run_get_older_entry_then_update(client, requesting_user):
     task_run = get_task_run(client, "task-A")
     assert task_run.status_code == 200
     task_time = isodate.parse_datetime(task_run.json.get("lastrun"))
diff --git a/flexmeasures/api/v3_0/tests/test_accounts_api.py b/flexmeasures/api/v3_0/tests/test_accounts_api.py
index 452cce42a..928f5bad6 100644
--- a/flexmeasures/api/v3_0/tests/test_accounts_api.py
+++ b/flexmeasures/api/v3_0/tests/test_accounts_api.py
@@ -4,62 +4,63 @@
 import pytest
 
 from flexmeasures.data.services.users import find_user_by_email
-from flexmeasures.api.tests.utils import get_auth_token
 
 
-def test_get_accounts_missing_auth(client):
+@pytest.mark.parametrize(
+    "requesting_user, status_code",
+    [
+        (None, 401),
+    ],
+    indirect=["requesting_user"],
+)
+def test_get_accounts_missing_auth(client, requesting_user, status_code):
     """
     Attempt to get accounts with missing auth.
     """
     # the case without auth: authentication will fail
-    get_accounts_response = client.get(
-        url_for("AccountAPI:index"), headers=make_headers_for(None, client)
-    )
+    get_accounts_response = client.get(url_for("AccountAPI:index"))
     print("Server responded with:\n%s" % get_accounts_response.data)
-    assert get_accounts_response.status_code == 401
+    assert get_accounts_response.status_code == status_code
 
 
-@pytest.mark.parametrize("as_admin", [True, False])
-def test_get_accounts(client, setup_api_test_data, as_admin):
+@pytest.mark.parametrize(
+    "requesting_user, num_accounts",
+    [
+        ("test_admin_user@seita.nl", 5),
+        ("test_prosumer_user@seita.nl", 1),
+    ],
+    indirect=["requesting_user"],
+)
+def test_get_accounts(client, setup_api_test_data, requesting_user, num_accounts):
     """
     Get accounts
     """
-    if as_admin:
-        headers = make_headers_for("test_admin_user@seita.nl", client)
-    else:
-        headers = make_headers_for("test_prosumer_user@seita.nl", client)
     get_accounts_response = client.get(
         url_for("AccountAPI:index"),
-        headers=headers,
     )
     print("Server responded with:\n%s" % get_accounts_response.data)
-    if as_admin:
-        assert len(get_accounts_response.json) == 5
-
-    else:
-        assert len(get_accounts_response.json) == 1
-        get_accounts_response.json[0]["name"] == "Test Prosumer Account"
+    assert len(get_accounts_response.json) == num_accounts
+    account_names = [a["name"] for a in get_accounts_response.json]
+    assert requesting_user.account.name in account_names
 
 
 @pytest.mark.parametrize(
-    "requesting_user_email,status_code",
+    "requesting_user, status_code",
     [
         (None, 401),  # no auth is not allowed
         ("test_prosumer_user_2@seita.nl", 200),  # gets their own account, okay
         ("test_dummy_user_3@seita.nl", 403),  # gets from other account
         ("test_admin_user@seita.nl", 200),  # admin can do this from another account
     ],
+    indirect=["requesting_user"],
 )
-def test_get_one_account(
-    client, setup_api_test_data, requesting_user_email, status_code
-):
+def test_get_one_account(client, setup_api_test_data, requesting_user, status_code):
     """Get one account"""
     test_user2_account_id = find_user_by_email(
         "test_prosumer_user_2@seita.nl"
     ).account.id
     get_account_response = client.get(
         url_for("AccountAPI:get", id=test_user2_account_id),
-        headers=make_headers_for(requesting_user_email, client),
     )
     print("Server responded with:\n%s" % get_account_response.data)
     assert get_account_response.status_code == status_code
@@ -68,10 +69,3 @@ def test_get_one_account(
         assert get_account_response.json["account_roles"] == [
             {"id": 1, "name": "Prosumer"}
         ]
-
-
-def make_headers_for(user_email: str | None, client) -> dict:
-    headers = {"content-type": "application/json"}
-    if user_email:
-        headers["Authorization"] = get_auth_token(client, user_email, "testtest")
-    return headers
diff --git a/flexmeasures/api/v3_0/tests/test_api_v3_0_users.py b/flexmeasures/api/v3_0/tests/test_api_v3_0_users.py
index a70072eb4..4e35b1d0d 100644
--- a/flexmeasures/api/v3_0/tests/test_api_v3_0_users.py
+++ b/flexmeasures/api/v3_0/tests/test_api_v3_0_users.py
@@ -1,51 +1,46 @@
 from flask import url_for
+from flask_login import current_user, logout_user
 import pytest
 
 from flexmeasures.data.services.users import find_user_by_email
-from flexmeasures.api.tests.utils import get_auth_token, UserContext
+from flexmeasures.api.tests.utils import UserContext
 
 
-@pytest.mark.parametrize("use_auth", [False, True])
-def test_get_users_bad_auth(client, setup_api_test_data, use_auth):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user_2@seita.nl", None], indirect=True
+)
+def test_get_users_bad_auth(requesting_user, client, setup_api_test_data):
     """
     Attempt to get users with insufficient or missing auth.
     """
     # the case without auth: authentication will fail
-    headers = {"content-type": "application/json"}
     query = {}
-    if use_auth:
+    if requesting_user:
         # in this case, we successfully authenticate,
         # but fail authorization (non-admin accessing another account)
-        headers["Authorization"] = get_auth_token(
-            client, "test_prosumer_user_2@seita.nl", "testtest"
-        )
         query = {"account_id": 2}
 
-    get_users_response = client.get(
-        url_for("UserAPI:index"), headers=headers, query_string=query
-    )
+    get_users_response = client.get(url_for("UserAPI:index"), query_string=query)
     print("Server responded with:\n%s" % get_users_response.data)
-    if use_auth:
+    if requesting_user:
         assert get_users_response.status_code == 403
     else:
         assert get_users_response.status_code == 401
 
 
 @pytest.mark.parametrize("include_inactive", [False, True])
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user_2@seita.nl"], indirect=True
+)
 def test_get_users_inactive(
-    client, setup_api_test_data, setup_inactive_user, include_inactive
+    requesting_user, client, setup_api_test_data, setup_inactive_user, include_inactive
 ):
-    headers = {
-        "content-type": "application/json",
-        "Authorization": get_auth_token(
-            client, "test_prosumer_user_2@seita.nl", "testtest"
-        ),
-    }
     query = {}
     if include_inactive in (True, False):
         query["include_inactive"] = include_inactive
     get_users_response = client.get(
-        url_for("UserAPI:index"), query_string=query, headers=headers
+        url_for("UserAPI:index"),
+        query_string=query,
     )
     print("Server responded with:\n%s" % get_users_response.json)
     assert get_users_response.status_code == 200
@@ -57,7 +52,7 @@ def test_get_users_inactive(
 
 
 @pytest.mark.parametrize(
-    "requesting_user,status_code",
+    "requesting_user, status_code",
     [
         (None, 401),  # no auth is not allowed
         ("test_prosumer_user_2@seita.nl", 200),  # gets themselves
@@ -65,70 +60,56 @@ def test_get_users_inactive(
         ("test_dummy_user_3@seita.nl", 403),  # gets from other account
         ("test_admin_user@seita.nl", 200),  # admin can do this from another account
     ],
+    indirect=["requesting_user"],
 )
 def test_get_one_user(client, setup_api_test_data, requesting_user, status_code):
     test_user2_id = find_user_by_email("test_prosumer_user_2@seita.nl").id
-    headers = {"content-type": "application/json"}
-    if requesting_user:
-        headers["Authorization"] = get_auth_token(client, requesting_user, "testtest")
 
-    get_user_response = client.get(
-        url_for("UserAPI:get", id=test_user2_id),
-        headers=headers,
-    )
+    get_user_response = client.get(url_for("UserAPI:get", id=test_user2_id))
     print("Server responded with:\n%s" % get_user_response.data)
     assert get_user_response.status_code == status_code
     if status_code == 200:
         assert get_user_response.json["username"] == "Test Prosumer User 2"
 
 
-def test_edit_user(client, setup_api_test_data):
-    with UserContext("test_prosumer_user_2@seita.nl") as user2:
-        user2_auth_token = user2.get_auth_token()  # user2 is no admin
-        user2_id = user2.id
-    with UserContext("test_admin_user@seita.nl") as admin:
-        admin_auth_token = admin.get_auth_token()
-        admin_id = admin.id
-    # without being the user themselves or an admin, the user cannot be edited
-    user_edit_response = client.patch(
-        url_for("UserAPI:patch", id=admin_id),
-        headers={
-            "content-type": "application/json",
-            "Authorization": user2_auth_token,
-        },
-        json={},
-    )
-    assert user_edit_response.status_code == 403
-    user_edit_response = client.patch(
-        url_for("UserAPI:patch", id=user2_id),
-        headers={"content-type": "application/json"},
-        json={},
-    )
-    assert user_edit_response.status_code == 401
-    # admin can deactivate user2
-    admin_headers = {
-        "content-type": "application/json",
-        "Authorization": admin_auth_token,
-    }
-    user_edit_response = client.patch(
-        url_for("UserAPI:patch", id=user2_id),
-        headers=admin_headers,
-        json={"active": False},
-    )
-    print("Server responded with:\n%s" % user_edit_response.json)
-    assert user_edit_response.status_code == 200
-    assert user_edit_response.json["active"] is False
-    user2 = find_user_by_email("test_prosumer_user_2@seita.nl")
-    assert user2.active is False
-    assert user2.id == user2_id
-    # admin can edit themselves but not sensitive fields
+@pytest.mark.parametrize(
+    "requesting_user, requested_user, status_code",
+    [
+        (
+            "test_prosumer_user_2@seita.nl",
+            "test_admin_user@seita.nl",
+            403,
+        ),  # without being the user themselves or an admin, the user cannot be edited
+        (None, "test_prosumer_user_2@seita.nl", 401),  # anonymous user cannot edit
+        (
+            "test_admin_user@seita.nl",
+            "test_prosumer_user_2@seita.nl",
+            200,
+        ),  # admin can deactivate user2
+        (
+            "test_admin_user@seita.nl",
+            "test_admin_user@seita.nl",
+            403,
+        ),  # admin can edit themselves but not sensitive fields
+    ],
+    indirect=["requesting_user"],
+)
+def test_edit_user(
+    requesting_user, requested_user, status_code, client, setup_api_test_data
+):
+    with UserContext(requested_user) as u:
+        requested_user_id = u.id
+
     user_edit_response = client.patch(
-        url_for("UserAPI:patch", id=admin_id),
-        headers=admin_headers,
+        url_for("UserAPI:patch", id=requested_user_id),
         json={"active": False},
     )
-    print("Server responded with:\n%s" % user_edit_response.json)
-    assert user_edit_response.status_code == 403
+    assert user_edit_response.status_code == status_code
+    if status_code == 200:
+        assert user_edit_response.json["active"] is False
+        user = find_user_by_email(requested_user)
+        assert user.active is False
+        assert user.id == requested_user_id
 
 
 @pytest.mark.parametrize(
@@ -139,34 +120,58 @@ def test_edit_user(client, setup_api_test_data):
         dict(account_id=10),  # account_id is a dump_only field
     ],
 )
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
 def test_edit_user_with_unexpected_fields(
-    client, setup_api_test_data, unexpected_fields: dict
+    requesting_user, client, setup_api_test_data, unexpected_fields: dict
 ):
     """Sending unexpected fields (not in Schema) is an Unprocessable Entity error."""
     with UserContext("test_prosumer_user_2@seita.nl") as user2:
         user2_id = user2.id
-    with UserContext("test_admin_user@seita.nl") as admin:
-        admin_auth_token = admin.get_auth_token()
     user_edit_response = client.patch(
         url_for("UserAPI:patch", id=user2_id),
-        headers={
-            "content-type": "application/json",
-            "Authorization": admin_auth_token,
-        },
         json={**{"active": False}, **unexpected_fields},
     )
     print("Server responded with:\n%s" % user_edit_response.json)
     assert user_edit_response.status_code == 422
 
 
-def test_logout(client, setup_api_test_data):
+@pytest.mark.parametrize(
+    "email, status_code",
+    [
+        ("test_admin_user@seita.nl", 200),
+        ("inactive_admin@seita.nl", 400),
+    ],
+)
+def test_login(client, setup_api_test_data, email, status_code):
+    """Tries to log in."""
+
+    assert current_user.is_anonymous
+
+    # log in
+    login_response = client.post(
+        url_for("security.login"),
+        json={
+            "email": email,
+            "password": "testtest",
+        },
+    )
+    print(login_response.json)
+
+    assert login_response.status_code == status_code
+
+    if status_code == 200:
+        assert not current_user.is_anonymous
+        logout_user()
+
+
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_logout(client, setup_api_test_data, requesting_user):
     """Tries to log out, which should succeed as a url direction."""
 
+    assert not current_user.is_anonymous
+
     # log out
-    with UserContext("test_admin_user@seita.nl") as admin:
-        auth_token = admin.get_auth_token()
-    logout_response = client.get(
-        url_for("security.logout"),
-        headers={"Authorization ": auth_token, "content-type": "application/json"},
-    )
+    logout_response = client.get(url_for("security.logout"))
     assert logout_response.status_code == 302
+
+    assert current_user.is_anonymous
diff --git a/flexmeasures/api/v3_0/tests/test_api_v3_0_users_fresh_db.py b/flexmeasures/api/v3_0/tests/test_api_v3_0_users_fresh_db.py
index 78bc8b2d2..03f6bd43b 100644
--- a/flexmeasures/api/v3_0/tests/test_api_v3_0_users_fresh_db.py
+++ b/flexmeasures/api/v3_0/tests/test_api_v3_0_users_fresh_db.py
@@ -1,22 +1,25 @@
 import pytest
 from flask import url_for, request
 
-from flexmeasures.api.tests.utils import UserContext, get_auth_token
+from flexmeasures.api.tests.utils import UserContext
 from flexmeasures.data.services.users import find_user_by_email
 
 
 @pytest.mark.parametrize(
-    "sender",
+    "requesting_user, status_code",
     [
-        "",
-        "test_prosumer_user@seita.nl",
-        "test_prosumer_user_2@seita.nl",
-        "test_admin_user@seita.nl",
-        "inactive_user@seita.nl",
-        "inactive_admin@seita.nl",
+        (None, 401),
+        ("test_prosumer_user@seita.nl", 403),
+        ("test_prosumer_user_2@seita.nl", 200),
+        ("test_admin_user@seita.nl", 200),
+        ("inactive_user@seita.nl", 401),
+        ("inactive_admin@seita.nl", 401),
     ],
+    indirect=["requesting_user"],
 )
-def test_user_reset_password(app, client, setup_inactive_user, sender):
+def test_user_reset_password(
+    app, client, setup_inactive_user, requesting_user, status_code
+):
     """
     Reset the password of User 2.
     Only the admin user and User 2 themselves are allowed to do that.
@@ -24,25 +27,16 @@ def test_user_reset_password(app, client, setup_inactive_user, sender):
     with UserContext("test_prosumer_user_2@seita.nl") as user2:
         user2_id = user2.id
         old_password = user2.password
-    headers = {"content-type": "application/json"}
-    if sender != "":
-        headers["Authorization"] = (get_auth_token(client, sender, "testtest"),)
     with app.mail.record_messages() as outbox:
         pwd_reset_response = client.patch(
             url_for("UserAPI:reset_user_password", id=user2_id),
             query_string={},
-            headers=headers,
         )
         print("Server responded with:\n%s" % pwd_reset_response.json)
 
-        if sender in ("", "inactive_user@seita.nl", "inactive_admin@seita.nl"):
-            assert pwd_reset_response.status_code == 401
+        assert pwd_reset_response.status_code == status_code
+        if status_code != 200:
             return
-        if sender == "test_prosumer_user@seita.nl":
-            assert pwd_reset_response.status_code == 403
-            return
-
-        assert pwd_reset_response.status_code == 200
 
         user2 = find_user_by_email("test_prosumer_user_2@seita.nl")
         assert len(outbox) == 2
diff --git a/flexmeasures/api/v3_0/tests/test_assets_api.py b/flexmeasures/api/v3_0/tests/test_assets_api.py
index 01df9cf77..9bb6ff3a5 100644
--- a/flexmeasures/api/v3_0/tests/test_assets_api.py
+++ b/flexmeasures/api/v3_0/tests/test_assets_api.py
@@ -9,85 +9,85 @@
 from flexmeasures.api.v3_0.tests.utils import get_asset_post_data
 
 
-@pytest.mark.parametrize("use_auth", [False, True])
-def test_get_assets_badauth(client, setup_api_test_data, use_auth):
+@pytest.mark.parametrize(
+    "requesting_user, status_code",
+    [
+        (None, 401),  # the case without auth: authentication will fail
+        (
+            "test_dummy_user_3@seita.nl",
+            403,
+        ),  # fails authorization to get assets on another account
+    ],
+    indirect=["requesting_user"],
+)
+def test_get_assets_badauth(client, setup_api_test_data, requesting_user, status_code):
     """
     Attempt to get assets with wrong or missing auth.
     """
-    # the case without auth: authentication will fail
-    headers = {"content-type": "application/json"}
-    query = {}
-    if use_auth:
-        # in this case, we successfully authenticate, but fail authorization
-        headers["Authorization"] = get_auth_token(
-            client, "test_dummy_user_3@seita.nl", "testtest"
-        )
-        test_prosumer = find_user_by_email("test_prosumer_user@seita.nl")
-        query = {"account_id": test_prosumer.account.id}
+    test_prosumer = find_user_by_email("test_prosumer_user@seita.nl")
+    query = {"account_id": test_prosumer.account.id}
 
-    get_assets_response = client.get(
-        url_for("AssetAPI:index"), query_string=query, headers=headers
-    )
+    get_assets_response = client.get(url_for("AssetAPI:index"), query_string=query)
     print("Server responded with:\n%s" % get_assets_response.json)
-    if use_auth:
-        assert get_assets_response.status_code == 403
-    else:
-        assert get_assets_response.status_code == 401
+    assert get_assets_response.status_code == status_code
 
 
-def test_get_asset_nonaccount_access(client, setup_api_test_data):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
+def test_get_asset_nonaccount_access(client, setup_api_test_data, requesting_user):
     """Without being on the same account, test correct responses when accessing one asset."""
     with UserContext("test_prosumer_user@seita.nl") as prosumer1:
         prosumer1_assets = prosumer1.account.generic_assets
     with UserContext("test_supplier_user_4@seita.nl") as supplieruser4:
         supplieruser4_assets = supplieruser4.account.generic_assets
-    headers = {
-        "content-type": "application/json",
-        "Authorization": get_auth_token(
-            client, "test_supplier_user_4@seita.nl", "testtest"
-        ),
-    }
 
     # okay to look at assets in own account
     asset_response = client.get(
         url_for("AssetAPI:fetch_one", id=supplieruser4_assets[0].id),
-        headers=headers,
         follow_redirects=True,
     )
     assert asset_response.status_code == 200
     # not okay to see assets owned by other accounts
     asset_response = client.get(
         url_for("AssetAPI:fetch_one", id=prosumer1_assets[0].id),
-        headers=headers,
         follow_redirects=True,
     )
     assert asset_response.status_code == 403
     # proper 404 for non-existing asset
     asset_response = client.get(
         url_for("AssetAPI:fetch_one", id=8171766575),
-        headers=headers,
         follow_redirects=True,
     )
     assert asset_response.status_code == 404
     assert "not found" in asset_response.json["message"]
 
 
-@pytest.mark.parametrize("account_name, num_assets", [("Prosumer", 1), ("Supplier", 2)])
+@pytest.mark.parametrize(
+    "requesting_user, account_name, num_assets",
+    [
+        ("test_admin_user@seita.nl", "Prosumer", 1),
+        ("test_admin_user@seita.nl", "Supplier", 2),
+    ],
+    indirect=["requesting_user"],
+)
 def test_get_assets(
-    client, setup_api_test_data, setup_accounts, account_name, num_assets
+    client,
+    setup_api_test_data,
+    setup_accounts,
+    account_name,
+    num_assets,
+    requesting_user,
 ):
     """
     Get assets per account.
     Our user here is admin, so is allowed to see all assets.
     """
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
-
     query = {"account_id": setup_accounts[account_name].id}
 
     get_assets_response = client.get(
         url_for("AssetAPI:index"),
         query_string=query,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % get_assets_response.json)
     assert get_assets_response.status_code == 200
@@ -102,19 +102,22 @@ def test_get_assets(
         assert turbine["account_id"] == setup_accounts["Supplier"].id
 
 
-def test_get_public_assets_noauth(client, setup_api_test_data, setup_accounts):
-    get_assets_response = client.get(
-        url_for("AssetAPI:public"), headers={"content-type": "application/json"}
-    )
+@pytest.mark.parametrize("requesting_user", [None], indirect=True)
+def test_get_public_assets_noauth(
+    client, setup_api_test_data, setup_accounts, requesting_user
+):
+    get_assets_response = client.get(url_for("AssetAPI:public"))
     print("Server responded with:\n%s" % get_assets_response.json)
     assert get_assets_response.status_code == 401
 
 
-def test_get_public_assets(client, setup_api_test_data, setup_accounts):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_get_public_assets(
+    client, setup_api_test_data, setup_accounts, requesting_user
+):
     auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     get_assets_response = client.get(
-        url_for("AssetAPI:public"),
-        headers={"content-type": "application/json", "Authorization": auth_token},
+        url_for("AssetAPI:public"), headers={"Authorization": auth_token}
     )
     print("Server responded with:\n%s" % get_assets_response.json)
     assert get_assets_response.status_code == 200
@@ -122,15 +125,15 @@ def test_get_public_assets(client, setup_api_test_data, setup_accounts):
     assert get_assets_response.json[0]["name"] == "troposphere"
 
 
-def test_alter_an_asset(client, setup_api_test_data, setup_accounts):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
+def test_alter_an_asset(client, setup_api_test_data, setup_accounts, requesting_user):
     # without being an account-admin, no asset can be created ...
-    with UserContext("test_prosumer_user@seita.nl") as prosumer1:
-        auth_token = prosumer1.get_auth_token()  # not an account admin
     with AccountContext("Test Prosumer Account") as prosumer:
         prosumer_asset = prosumer.generic_assets[0]
     asset_creation_response = client.post(
         url_for("AssetAPI:post"),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={},
     )
     print(f"Creation Response: {asset_creation_response.json}")
@@ -138,7 +141,6 @@ def test_alter_an_asset(client, setup_api_test_data, setup_accounts):
     # ... or deleted ...
     asset_delete_response = client.delete(
         url_for("AssetAPI:delete", id=prosumer_asset.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={},
     )
     print(f"Deletion Response: {asset_delete_response.json}")
@@ -146,7 +148,6 @@ def test_alter_an_asset(client, setup_api_test_data, setup_accounts):
     # ... but editing is allowed.
     asset_edit_response = client.patch(
         url_for("AssetAPI:patch", id=prosumer_asset.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={
             "latitude": prosumer_asset.latitude,
         },  # we're not changing values to keep other tests clean here
@@ -173,17 +174,22 @@ def test_alter_an_asset(client, setup_api_test_data, setup_accounts):
         ),  # non-integer sensor ID
     ],
 )
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_alter_an_asset_with_bad_json_attributes(
-    client, setup_api_test_data, setup_accounts, bad_json_str, error_msg
+    client,
+    setup_api_test_data,
+    setup_accounts,
+    bad_json_str,
+    error_msg,
+    requesting_user,
 ):
     """Check whether updating an asset's attributes with a badly structured JSON fails."""
-    with UserContext("test_prosumer_user@seita.nl") as prosumer1:
-        auth_token = prosumer1.get_auth_token()
     with AccountContext("Test Prosumer Account") as prosumer:
         prosumer_asset = prosumer.generic_assets[0]
     asset_edit_response = client.patch(
         url_for("AssetAPI:patch", id=prosumer_asset.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={"attributes": bad_json_str},
     )
     print(f"Editing Response: {asset_edit_response.json}")
@@ -191,12 +197,13 @@ def test_alter_an_asset_with_bad_json_attributes(
     assert error_msg in asset_edit_response.json["message"]["json"]["attributes"][0]
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_alter_an_asset_with_json_attributes(
-    client, setup_api_test_data, setup_accounts
+    client, setup_api_test_data, setup_accounts, requesting_user
 ):
     """Check whether updating an asset's attributes with a properly structured JSON succeeds."""
-    with UserContext("test_prosumer_user@seita.nl") as prosumer1:
-        auth_token = prosumer1.get_auth_token()
     with AccountContext("Test Prosumer Account") as prosumer:
         prosumer_asset = prosumer.generic_assets[0]
         assert prosumer_asset.attributes[
@@ -204,7 +211,6 @@ def test_alter_an_asset_with_json_attributes(
         ]  # make sure we run this test on an asset with a non-empty sensors_to_show attribute
     asset_edit_response = client.patch(
         url_for("AssetAPI:patch", id=prosumer_asset.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={
             "attributes": json.dumps(prosumer_asset.attributes)
         },  # we're not changing values to keep other tests clean here
@@ -213,10 +219,9 @@ def test_alter_an_asset_with_json_attributes(
     assert asset_edit_response.status_code == 200
 
 
-def test_post_an_asset_with_existing_name(client, setup_api_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_post_an_asset_with_existing_name(client, setup_api_test_data, requesting_user):
     """Catch DB error (Unique key violated) correctly"""
-    with UserContext("test_admin_user@seita.nl") as admin_user:
-        auth_token = admin_user.get_auth_token()
     with AccountContext("Test Prosumer Account") as prosumer:
         prosumer_id = prosumer.id
         existing_asset = prosumer.generic_assets[0]
@@ -226,7 +231,6 @@ def test_post_an_asset_with_existing_name(client, setup_api_test_data):
     asset_creation_response = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print(f"Creation Response: {asset_creation_response.json}")
     assert asset_creation_response.status_code == 422
@@ -235,10 +239,11 @@ def test_post_an_asset_with_existing_name(client, setup_api_test_data):
     )
 
 
-def test_post_an_asset_with_other_account(client, setup_api_test_data):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user_2@seita.nl"], indirect=True
+)
+def test_post_an_asset_with_other_account(client, setup_api_test_data, requesting_user):
     """Catch auth error, when account-admin posts an asset for another account"""
-    with UserContext("test_prosumer_user_2@seita.nl") as account_admin_user:
-        auth_token = account_admin_user.get_auth_token()
     with AccountContext("Test Supplier Account") as supplier:
         supplier_id = supplier.id
     post_data = get_asset_post_data()
@@ -246,7 +251,6 @@ def test_post_an_asset_with_other_account(client, setup_api_test_data):
     asset_creation_response = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print(f"Creation Response: {asset_creation_response.json}")
     assert asset_creation_response.status_code == 422
@@ -256,47 +260,43 @@ def test_post_an_asset_with_other_account(client, setup_api_test_data):
     )
 
 
-def test_post_an_asset_with_nonexisting_field(client, setup_api_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_post_an_asset_with_nonexisting_field(
+    client, setup_api_test_data, requesting_user
+):
     """Posting a field that is unexpected leads to a 422"""
-    with UserContext("test_admin_user@seita.nl") as prosumer:
-        auth_token = prosumer.get_auth_token()
     post_data = get_asset_post_data()
     post_data["nnname"] = "This field does not exist"
     asset_creation = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert asset_creation.status_code == 422
     assert asset_creation.json["message"]["json"]["nnname"][0] == "Unknown field."
 
 
-def test_posting_multiple_assets(client, setup_api_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_posting_multiple_assets(client, setup_api_test_data, requesting_user):
     """We can only send one at a time"""
-    with UserContext("test_admin_user@seita.nl") as prosumer:
-        auth_token = prosumer.get_auth_token()
     post_data1 = get_asset_post_data()
     post_data2 = get_asset_post_data()
     post_data2["name"] = "Test battery 3"
     asset_creation = client.post(
         url_for("AssetAPI:post"),
         json=[post_data1, post_data2],
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print(f"Response: {asset_creation.json}")
     assert asset_creation.status_code == 422
     assert asset_creation.json["message"]["json"]["_schema"][0] == "Invalid input type."
 
 
-def test_post_an_asset_with_invalid_data(client, setup_api_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_post_an_asset_with_invalid_data(client, setup_api_test_data, requesting_user):
     """
     Add an asset with some fields having invalid data and one field missing.
     The right error messages should be in the response and the number of assets has not increased.
     """
-    with UserContext("test_admin_user@seita.nl") as prosumer:
-        num_assets_before = len(prosumer.account.generic_assets)
-
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
+    num_assets_before = len(requesting_user.account.generic_assets)
 
     post_data = get_asset_post_data()
     post_data["name"] = "Something new"
@@ -306,7 +306,6 @@ def test_post_an_asset_with_invalid_data(client, setup_api_test_data):
     post_asset_response = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % post_asset_response.json)
     assert post_asset_response.status_code == 422
@@ -321,24 +320,23 @@ def test_post_an_asset_with_invalid_data(client, setup_api_test_data):
     )
 
     assert (
-        GenericAsset.query.filter_by(account_id=prosumer.id).count()
+        GenericAsset.query.filter_by(account_id=requesting_user.account.id).count()
         == num_assets_before
     )
 
 
-def test_post_an_asset(client, setup_api_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_post_an_asset(client, setup_api_test_data, requesting_user):
     """
     Post one extra asset, as an admin user.
     TODO: Soon we'll allow creating assets on an account-basis, i.e. for users
           who have the user role "account-admin" or something similar. Then we'll
           test that here.
     """
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     post_data = get_asset_post_data()
     post_assets_response = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % post_assets_response.json)
     assert post_assets_response.status_code == 201
@@ -351,14 +349,12 @@ def test_post_an_asset(client, setup_api_test_data):
     assert asset.latitude == 30.1
 
 
-def test_delete_an_asset(client, setup_api_test_data):
-
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_delete_an_asset(client, setup_api_test_data, requesting_user):
     existing_asset_id = setup_api_test_data["some gas sensor"].generic_asset.id
 
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     delete_asset_response = client.delete(
         url_for("AssetAPI:delete", id=existing_asset_id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert delete_asset_response.status_code == 204
     deleted_asset = GenericAsset.query.filter_by(id=existing_asset_id).one_or_none()
diff --git a/flexmeasures/api/v3_0/tests/test_assets_api_fresh_db.py b/flexmeasures/api/v3_0/tests/test_assets_api_fresh_db.py
index 2a1729a28..72d993017 100644
--- a/flexmeasures/api/v3_0/tests/test_assets_api_fresh_db.py
+++ b/flexmeasures/api/v3_0/tests/test_assets_api_fresh_db.py
@@ -1,13 +1,20 @@
 from flask import url_for
 import pytest
 
-from flexmeasures.api.tests.utils import get_auth_token, AccountContext
+from flexmeasures.api.tests.utils import AccountContext
 from flexmeasures.data.models.generic_assets import GenericAsset
 from flexmeasures.api.v3_0.tests.utils import get_asset_post_data
 
 
-@pytest.mark.parametrize("admin_kind", ["site-admin", "account-admin"])
-def test_post_an_asset_as_admin(client, setup_api_fresh_test_data, admin_kind):
+@pytest.mark.parametrize(
+    "requesting_user",
+    [
+        "test_admin_user@seita.nl",  # has the "site-admin" role
+        "test_prosumer_user_2@seita.nl",  # has the "account-admin" role
+    ],
+    indirect=True,
+)
+def test_post_an_asset_as_admin(client, setup_api_fresh_test_data, requesting_user):
     """
     Post one extra asset, as an admin user.
     """
@@ -16,15 +23,11 @@ def test_post_an_asset_as_admin(client, setup_api_fresh_test_data, admin_kind):
             account_id=prosumer.id,
             asset_type_id=prosumer.generic_assets[0].generic_asset_type.id,
         )
-    if admin_kind == "site-admin":
-        auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
-    else:
-        auth_token = get_auth_token(client, "test_prosumer_user_2@seita.nl", "testtest")
+    if requesting_user.email == "test_prosumer_user_2@seita.nl":
         post_data["name"] = "Test battery 3"
     post_assets_response = client.post(
         url_for("AssetAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % post_assets_response.json)
     assert post_assets_response.status_code == 201
@@ -37,16 +40,15 @@ def test_post_an_asset_as_admin(client, setup_api_fresh_test_data, admin_kind):
     assert asset.latitude == 30.1
 
 
-def test_edit_an_asset(client, setup_api_fresh_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_edit_an_asset(client, setup_api_fresh_test_data, requesting_user):
     with AccountContext("Test Supplier Account") as supplier:
         existing_asset = supplier.generic_assets[0]
 
     post_data = dict(latitude=10)
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     edit_asset_response = client.patch(
         url_for("AssetAPI:patch", id=existing_asset.id),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert edit_asset_response.status_code == 200
     updated_asset = GenericAsset.query.filter_by(id=existing_asset.id).one_or_none()
@@ -55,14 +57,13 @@ def test_edit_an_asset(client, setup_api_fresh_test_data):
     assert updated_asset.name == existing_asset.name
 
 
-def test_delete_an_asset(client, setup_api_fresh_test_data):
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_delete_an_asset(client, setup_api_fresh_test_data, requesting_user):
     with AccountContext("Test Prosumer Account") as prosumer:
         existing_asset_id = prosumer.generic_assets[0].id
 
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     delete_asset_response = client.delete(
         url_for("AssetAPI:delete", id=existing_asset_id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert delete_asset_response.status_code == 204
     deleted_asset = GenericAsset.query.filter_by(id=existing_asset_id).one_or_none()
diff --git a/flexmeasures/api/v3_0/tests/test_sensor_data.py b/flexmeasures/api/v3_0/tests/test_sensor_data.py
index e570a33be..11b59257c 100644
--- a/flexmeasures/api/v3_0/tests/test_sensor_data.py
+++ b/flexmeasures/api/v3_0/tests/test_sensor_data.py
@@ -6,13 +6,16 @@
 import pytest
 
 from flexmeasures import Sensor, Source, User
-from flexmeasures.api.tests.utils import get_auth_token
 from flexmeasures.api.v3_0.tests.utils import make_sensor_data_request_for_gas_sensor
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
 def test_get_no_sensor_data(
     client,
     setup_api_test_data: dict[str, Sensor],
+    requesting_user,
 ):
     """Check the /sensors/data endpoint for fetching data for a period without any data."""
     sensor = setup_api_test_data["some gas sensor"]
@@ -24,11 +27,9 @@ def test_get_no_sensor_data(
         "unit": "m³/h",
         "resolution": "PT20M",
     }
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
     response = client.get(
         url_for("SensorAPI:get_data"),
         query_string=message,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 200
@@ -37,10 +38,14 @@ def test_get_no_sensor_data(
     assert all(a == b for a, b in zip(values, [None, None, None, None]))
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
 def test_get_sensor_data(
     client,
     setup_api_test_data: dict[str, Sensor],
     setup_roles_users: dict[str, User],
+    requesting_user,
 ):
     """Check the /sensors/data endpoint for fetching 1 hour of data of a 10-minute resolution sensor."""
     sensor = setup_api_test_data["some gas sensor"]
@@ -57,11 +62,9 @@ def test_get_sensor_data(
         "source": source.id,
         "resolution": "PT20M",
     }
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
     response = client.get(
         url_for("SensorAPI:get_data"),
         query_string=message,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 200
@@ -71,10 +74,14 @@ def test_get_sensor_data(
     assert all(a == b for a, b in zip(values, [91.5, 92.1, None, None]))
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
 def test_get_instantaneous_sensor_data(
     client,
     setup_api_test_data: dict[str, Sensor],
     setup_roles_users: dict[str, User],
+    requesting_user,
 ):
     """Check the /sensors/data endpoint for fetching 1 hour of data of an instantaneous sensor."""
     sensor = setup_api_test_data["some temperature sensor"]
@@ -91,11 +98,9 @@ def test_get_instantaneous_sensor_data(
         "source": source.id,
         "resolution": "PT20M",
     }
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
     response = client.get(
         url_for("SensorAPI:get_data"),
         query_string=message,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 200
@@ -105,31 +110,30 @@ def test_get_instantaneous_sensor_data(
     assert all(a == b for a, b in zip(values, [815, 818, None, None]))
 
 
-@pytest.mark.parametrize("use_auth", [False, True])
-def test_post_sensor_data_bad_auth(client, setup_api_test_data, use_auth):
+@pytest.mark.parametrize(
+    "requesting_user, status_code",
+    [
+        (None, 401),  # the case without auth: authentication will fail
+        (
+            "test_dummy_user_3@seita.nl",
+            403,
+        ),  # in this case, we successfully authenticate, but fail authorization (not member of the account in which the sensor lies)
+    ],
+    indirect=["requesting_user"],
+)
+def test_post_sensor_data_bad_auth(
+    client, setup_api_test_data, requesting_user, status_code
+):
     """
     Attempt to post sensor data with insufficient or missing auth.
     """
-    # the case without auth: authentication will fail
-    headers = {"content-type": "application/json"}
-    if use_auth:
-        # in this case, we successfully authenticate,
-        # but fail authorization (not member of the account in which the sensor lies)
-        headers["Authorization"] = get_auth_token(
-            client, "test_dummy_user_3@seita.nl", "testtest"
-        )
-
     post_data = make_sensor_data_request_for_gas_sensor()
     post_data_response = client.post(
         url_for("SensorAPI:post_data"),
-        headers=headers,
         json=post_data,
     )
     print("Server responded with:\n%s" % post_data_response.data)
-    if use_auth:
-        assert post_data_response.status_code == 403
-    else:
-        assert post_data_response.status_code == 401
+    assert post_data_response.status_code == status_code
 
 
 @pytest.mark.parametrize(
@@ -147,32 +151,44 @@ def test_post_sensor_data_bad_auth(client, setup_api_test_data, use_auth):
         ("type", "GetSensorDataRequest", "type", "Must be one of"),
     ],
 )
+@pytest.mark.parametrize(
+    "requesting_user",
+    [
+        "test_supplier_user_4@seita.nl",  # this guy is allowed to post sensorData
+    ],
+    indirect=True,
+)
 def test_post_invalid_sensor_data(
-    client, setup_api_test_data, request_field, new_value, error_field, error_text
+    client,
+    setup_api_test_data,
+    request_field,
+    new_value,
+    error_field,
+    error_text,
+    requesting_user,
 ):
     post_data = make_sensor_data_request_for_gas_sensor()
     post_data[request_field] = new_value
-    # this guy is allowed to post sensorData
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
+
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     print(response.json)
     assert response.status_code == 422
     assert error_text in response.json["message"]["json"][error_field][0]
 
 
-def test_post_sensor_data_twice(client, setup_api_test_data):
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
+def test_post_sensor_data_twice(client, setup_api_test_data, requesting_user):
     post_data = make_sensor_data_request_for_gas_sensor()
 
     # Check that 1st time posting the data succeeds
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     print(response.json)
     assert response.status_code == 200
@@ -181,7 +197,6 @@ def test_post_sensor_data_twice(client, setup_api_test_data):
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     print(response.json)
     assert response.status_code == 200
@@ -192,7 +207,6 @@ def test_post_sensor_data_twice(client, setup_api_test_data):
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     print(response.json)
     assert response.status_code == 403
diff --git a/flexmeasures/api/v3_0/tests/test_sensor_data_fresh_db.py b/flexmeasures/api/v3_0/tests/test_sensor_data_fresh_db.py
index e082275f0..1fe8116ac 100644
--- a/flexmeasures/api/v3_0/tests/test_sensor_data_fresh_db.py
+++ b/flexmeasures/api/v3_0/tests/test_sensor_data_fresh_db.py
@@ -5,7 +5,6 @@
 from timely_beliefs.tests.utils import equal_lists
 
 from flexmeasures import Sensor, Source
-from flexmeasures.api.tests.utils import get_auth_token
 from flexmeasures.api.v3_0.tests.utils import make_sensor_data_request_for_gas_sensor
 from flexmeasures.data.models.time_series import TimedBelief
 
@@ -44,6 +43,9 @@
         ),  # failed to resample from 6-min intervals to 10-min intervals
     ],
 )
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
 def test_post_sensor_data(
     client,
     setup_api_fresh_test_data,
@@ -53,6 +55,7 @@ def test_post_sensor_data(
     include_a_null,
     expected_value,
     expected_status,
+    requesting_user,
 ):
     post_data = make_sensor_data_request_for_gas_sensor(
         num_values=num_values, unit=unit, include_a_null=include_a_null
@@ -66,11 +69,9 @@ def test_post_sensor_data(
     print(f"BELIEFS BEFORE: {beliefs_before}")
     assert len(beliefs_before) == 0
 
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     print(response.json)
     assert response.status_code == expected_status
@@ -83,10 +84,12 @@ def test_post_sensor_data(
     )
 
 
+@pytest.mark.parametrize("requesting_user", ["improper_user@seita.nl"], indirect=True)
 def test_auto_fix_missing_registration_of_user_as_data_source(
     client,
     setup_api_fresh_test_data,
     setup_user_without_data_source,
+    requesting_user,
 ):
     """Try to post sensor data as a user that has not been properly registered as a data source.
     The API call should succeed and the user should be automatically registered as a data source.
@@ -101,11 +104,9 @@ def test_auto_fix_missing_registration_of_user_as_data_source(
     post_data = make_sensor_data_request_for_gas_sensor(
         num_values=6, unit="m³/h", include_a_null=False
     )
-    auth_token = get_auth_token(client, "improper_user@seita.nl", "testtest")
     response = client.post(
         url_for("SensorAPI:post_data"),
         json=post_data,
-        headers={"Authorization": auth_token},
     )
     assert response.status_code == 200
 
diff --git a/flexmeasures/api/v3_0/tests/test_sensor_schedules.py b/flexmeasures/api/v3_0/tests/test_sensor_schedules.py
index d3bec8e5c..d75701670 100644
--- a/flexmeasures/api/v3_0/tests/test_sensor_schedules.py
+++ b/flexmeasures/api/v3_0/tests/test_sensor_schedules.py
@@ -6,7 +6,7 @@
 from rq.job import Job
 
 from flexmeasures.api.common.responses import unknown_schedule, unrecognized_event
-from flexmeasures.api.tests.utils import check_deprecation, get_auth_token
+from flexmeasures.api.tests.utils import check_deprecation
 from flexmeasures.api.v3_0.tests.utils import message_for_trigger_schedule
 from flexmeasures.data.models.data_sources import DataSource
 from flexmeasures.data.models.generic_assets import GenericAsset
@@ -19,6 +19,9 @@
 from flexmeasures.utils.calculations import integrate_time_series
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_get_schedule_wrong_job_id(
     app,
     add_market_prices,
@@ -26,14 +29,13 @@ def test_get_schedule_wrong_job_id(
     battery_soc_sensor,
     add_charging_station_assets,
     keep_scheduling_queue_empty,
+    requesting_user,
 ):
     wrong_job_id = 9999
     sensor = add_battery_assets["Test battery"].sensors[0]
     with app.test_client() as client:
-        auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
         get_schedule_response = client.get(
             url_for("SensorAPI:get_schedule", id=sensor.id, uuid=wrong_job_id),
-            headers={"content-type": "application/json", "Authorization": auth_token},
         )
     print("Server responded with:\n%s" % get_schedule_response.json)
     check_deprecation(get_schedule_response, deprecation=None, sunset=None)
@@ -63,6 +65,9 @@ def test_get_schedule_wrong_job_id(
         # ),
     ],
 )
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_trigger_schedule_with_invalid_flexmodel(
     app,
     add_battery_assets,
@@ -71,17 +76,16 @@ def test_trigger_schedule_with_invalid_flexmodel(
     field,
     sent_value,
     err_msg,
+    requesting_user,
 ):
     sensor = add_battery_assets["Test battery"].sensors[0]
     with app.test_client() as client:
         if sent_value:  # if None, field is a term we expect in the response, not more
             message["flex-model"][field] = sent_value
 
-        auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
         trigger_schedule_response = client.post(
             url_for("SensorAPI:trigger_schedule", id=sensor.id),
             json=message,
-            headers={"Authorization": auth_token},
         )
         print("Server responded with:\n%s" % trigger_schedule_response.json)
         check_deprecation(trigger_schedule_response, deprecation=None, sunset=None)
@@ -98,66 +102,65 @@ def test_trigger_schedule_with_invalid_flexmodel(
 
 
 @pytest.mark.parametrize("message", [message_for_trigger_schedule(unknown_prices=True)])
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_trigger_and_get_schedule_with_unknown_prices(
     app,
+    client,
     add_market_prices,
     add_battery_assets,
     battery_soc_sensor,
     add_charging_station_assets,
     keep_scheduling_queue_empty,
     message,
+    requesting_user,
 ):
-    auth_token = None
-    with app.test_client() as client:
-        sensor = add_battery_assets["Test battery"].sensors[0]
+    sensor = add_battery_assets["Test battery"].sensors[0]
 
-        # trigger a schedule through the /sensors/<id>/schedules/trigger [POST] api endpoint
-        auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
-        trigger_schedule_response = client.post(
-            url_for("SensorAPI:trigger_schedule", id=sensor.id),
-            json=message,
-            headers={"Authorization": auth_token},
-        )
-        print("Server responded with:\n%s" % trigger_schedule_response.json)
-        check_deprecation(trigger_schedule_response, deprecation=None, sunset=None)
-        assert trigger_schedule_response.status_code == 200
-        job_id = trigger_schedule_response.json["schedule"]
+    # trigger a schedule through the /sensors/<id>/schedules/trigger [POST] api endpoint
+    trigger_schedule_response = client.post(
+        url_for("SensorAPI:trigger_schedule", id=sensor.id),
+        json=message,
+    )
+    print("Server responded with:\n%s" % trigger_schedule_response.json)
+    check_deprecation(trigger_schedule_response, deprecation=None, sunset=None)
+    assert trigger_schedule_response.status_code == 200
+    job_id = trigger_schedule_response.json["schedule"]
 
-        # look for scheduling jobs in queue
-        assert (
-            len(app.queues["scheduling"]) == 1
-        )  # only 1 schedule should be made for 1 asset
-        job = app.queues["scheduling"].jobs[0]
-        assert job.kwargs["sensor_id"] == sensor.id
-        assert job.kwargs["start"] == parse_datetime(message["start"])
-        assert job.id == job_id
+    # look for scheduling jobs in queue
+    assert (
+        len(app.queues["scheduling"]) == 1
+    )  # only 1 schedule should be made for 1 asset
+    job = app.queues["scheduling"].jobs[0]
+    assert job.kwargs["sensor_id"] == sensor.id
+    assert job.kwargs["start"] == parse_datetime(message["start"])
+    assert job.id == job_id
 
-        # process the scheduling queue
-        work_on_rq(app.queues["scheduling"], exc_handler=handle_scheduling_exception)
-        assert (
-            Job.fetch(job_id, connection=app.queues["scheduling"].connection).is_failed
-            is True
-        )
+    # process the scheduling queue
+    work_on_rq(app.queues["scheduling"], exc_handler=handle_scheduling_exception)
+    assert (
+        Job.fetch(job_id, connection=app.queues["scheduling"].connection).is_failed
+        is True
+    )
 
-        # check results are not in the database
-        scheduler_source = DataSource.query.filter_by(
-            name="Seita", type="scheduler"
-        ).one_or_none()
-        assert (
-            scheduler_source is None
-        )  # Make sure the scheduler data source is still not there
+    # check results are not in the database
+    scheduler_source = DataSource.query.filter_by(
+        name="Seita", type="scheduler"
+    ).one_or_none()
+    assert (
+        scheduler_source is None
+    )  # Make sure the scheduler data source is still not there
 
-        # try to retrieve the schedule through the /sensors/<id>/schedules/<job_id> [GET] api endpoint
-        auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
-        get_schedule_response = client.get(
-            url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
-            headers={"content-type": "application/json", "Authorization": auth_token},
-        )
-        print("Server responded with:\n%s" % get_schedule_response.json)
-        check_deprecation(get_schedule_response, deprecation=None, sunset=None)
-        assert get_schedule_response.status_code == 400
-        assert get_schedule_response.json["status"] == unknown_schedule()[0]["status"]
-        assert "prices unknown" in get_schedule_response.json["message"].lower()
+    # try to retrieve the schedule through the /sensors/<id>/schedules/<job_id> [GET] api endpoint
+    get_schedule_response = client.get(
+        url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
+    )
+    print("Server responded with:\n%s" % get_schedule_response.json)
+    check_deprecation(get_schedule_response, deprecation=None, sunset=None)
+    assert get_schedule_response.status_code == 400
+    assert get_schedule_response.json["status"] == unknown_schedule()[0]["status"]
+    assert "prices unknown" in get_schedule_response.json["message"].lower()
 
 
 @pytest.mark.parametrize(
@@ -167,6 +170,9 @@ def test_trigger_and_get_schedule_with_unknown_prices(
         (message_for_trigger_schedule(with_targets=True), "Test charging station"),
     ],
 )
+@pytest.mark.parametrize(
+    "requesting_user", ["test_prosumer_user@seita.nl"], indirect=True
+)
 def test_trigger_and_get_schedule(
     app,
     add_market_prices,
@@ -176,6 +182,7 @@ def test_trigger_and_get_schedule(
     keep_scheduling_queue_empty,
     message,
     asset_name,
+    requesting_user,
 ):
     # Include the price sensor and site-power-capacity in the flex-context explicitly, to test deserialization
     price_sensor_id = add_market_prices["epex_da"].id
@@ -196,11 +203,9 @@ def test_trigger_and_get_schedule(
         .one_or_none()
     )
     with app.test_client() as client:
-        auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
         trigger_schedule_response = client.post(
             url_for("SensorAPI:trigger_schedule", id=sensor.id),
             json=message,
-            headers={"Authorization": auth_token},
         )
         print("Server responded with:\n%s" % trigger_schedule_response.json)
         assert trigger_schedule_response.status_code == 200
@@ -293,11 +298,9 @@ def test_trigger_and_get_schedule(
             assert soc_schedule[target["datetime"]] == target["value"] / 1000
 
     # try to retrieve the schedule through the /sensors/<id>/schedules/<job_id> [GET] api endpoint
-    auth_token = get_auth_token(client, "test_prosumer_user@seita.nl", "testtest")
     get_schedule_response = client.get(
         url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
         query_string={"duration": "PT48H"},
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % get_schedule_response.json)
     assert get_schedule_response.status_code == 200
@@ -308,7 +311,6 @@ def test_trigger_and_get_schedule(
     get_schedule_response_short = client.get(
         url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
         query_string={"duration": "PT6H"},
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert (
         get_schedule_response_short.json["values"]
@@ -319,7 +321,6 @@ def test_trigger_and_get_schedule(
     get_schedule_response_long = client.get(
         url_for("SensorAPI:get_schedule", id=sensor.id, uuid=job_id),
         query_string={"duration": "PT1000H"},
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     assert (
         get_schedule_response_long.json["values"][0:192]
diff --git a/flexmeasures/api/v3_0/tests/test_sensors_api.py b/flexmeasures/api/v3_0/tests/test_sensors_api.py
index 8704518bf..c48bc72df 100644
--- a/flexmeasures/api/v3_0/tests/test_sensors_api.py
+++ b/flexmeasures/api/v3_0/tests/test_sensors_api.py
@@ -6,22 +6,23 @@
 
 from flexmeasures.data.models.time_series import TimedBelief
 from flexmeasures import Sensor
-from flexmeasures.api.tests.utils import get_auth_token
 from flexmeasures.api.v3_0.tests.utils import get_sensor_post_data
 from flexmeasures.data.schemas.sensors import SensorSchema
 
 sensor_schema = SensorSchema()
 
 
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
 def test_fetch_one_sensor(
     client,
     setup_api_test_data: dict[str, Sensor],
+    requesting_user,
 ):
     sensor_id = 1
-    headers = make_headers_for("test_supplier_user_4@seita.nl", client)
     response = client.get(
         url_for("SensorAPI:fetch_one", id=sensor_id),
-        headers=headers,
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 200
@@ -32,54 +33,43 @@ def test_fetch_one_sensor(
     assert response.json["event_resolution"] == "PT10M"
 
 
-@pytest.mark.parametrize("use_auth", [False, True])
+@pytest.mark.parametrize(
+    "requesting_user, status_code",
+    [(None, 401), ("test_prosumer_user_2@seita.nl", 403)],
+    indirect=["requesting_user"],
+)
 def test_fetch_one_sensor_no_auth(
-    client, setup_api_test_data: dict[str, Sensor], use_auth
+    client, setup_api_test_data: dict[str, Sensor], requesting_user, status_code
 ):
     """Test 1: Sensor with id 1 is not in the test_prosumer_user_2@seita.nl's account.
     The Supplier Account as can be seen in flexmeasures/api/v3_0/tests/conftest.py
     Test 2: There is no authentication int the headers"""
     sensor_id = 1
-    if use_auth:
-        headers = make_headers_for("test_prosumer_user_2@seita.nl", client)
-        response = client.get(
-            url_for("SensorAPI:fetch_one", id=sensor_id),
-            headers=headers,
-        )
-        assert response.status_code == 403
+
+    response = client.get(url_for("SensorAPI:fetch_one", id=sensor_id))
+    assert response.status_code == status_code
+    if status_code == 403:
         assert (
             response.json["message"]
             == "You cannot be authorized for this content or functionality."
         )
         assert response.json["status"] == "INVALID_SENDER"
-    else:
-        headers = make_headers_for(None, client)
-        response = client.get(
-            url_for("SensorAPI:fetch_one", id=sensor_id),
-            headers=headers,
-        )
-        assert response.status_code == 401
+    elif status_code == 401:
         assert (
             response.json["message"]
             == "You could not be properly authenticated for this content or functionality."
         )
         assert response.json["status"] == "UNAUTHORIZED"
+    else:
+        raise NotImplementedError(f"Test did not expect status code {status_code}")
 
 
-def make_headers_for(user_email: str | None, client) -> dict:
-    headers = {"content-type": "application/json"}
-    if user_email:
-        headers["Authorization"] = get_auth_token(client, user_email, "testtest")
-    return headers
-
-
-def test_post_a_sensor(client, setup_api_test_data):
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_post_a_sensor(client, setup_api_test_data, requesting_user):
     post_data = get_sensor_post_data()
     response = client.post(
         url_for("SensorAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 201
@@ -92,14 +82,17 @@ def test_post_a_sensor(client, setup_api_test_data):
     assert sensor.attributes["capacity_in_mw"] == 0.0074
 
 
-def test_post_sensor_to_asset_from_unrelated_account(client, setup_api_test_data):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
+def test_post_sensor_to_asset_from_unrelated_account(
+    client, setup_api_test_data, requesting_user
+):
     """Tries to add sensor to account the user doesn't have access to"""
-    auth_token = get_auth_token(client, "test_supplier_user_4@seita.nl", "testtest")
     post_data = get_sensor_post_data()
     response = client.post(
         url_for("SensorAPI:post"),
         json=post_data,
-        headers={"content-type": "application/json", "Authorization": auth_token},
     )
     print("Server responded with:\n%s" % response.json)
     assert response.status_code == 403
@@ -110,13 +103,12 @@ def test_post_sensor_to_asset_from_unrelated_account(client, setup_api_test_data
     assert response.json["status"] == "INVALID_SENDER"
 
 
-def test_patch_sensor(client, setup_api_test_data):
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_patch_sensor(client, setup_api_test_data, requesting_user):
     sensor = Sensor.query.filter(Sensor.name == "some gas sensor").one_or_none()
 
     response = client.patch(
         url_for("SensorAPI:patch", id=sensor.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={
             "name": "Changed name",
             "attributes": '{"test_attribute": "test_attribute_value"}',
@@ -137,17 +129,16 @@ def test_patch_sensor(client, setup_api_test_data):
         ("id", 7),
     ],
 )
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
 def test_patch_sensor_for_excluded_attribute(
-    client, setup_api_test_data, attribute, value
+    client, setup_api_test_data, attribute, value, requesting_user
 ):
     """Test to change the generic_asset_id that should not be allowed.
     The generic_asset_id is excluded in the partial_sensor_schema"""
-    auth_token = get_auth_token(client, "test_admin_user@seita.nl", "testtest")
     sensor = Sensor.query.filter(Sensor.name == "some temperature sensor").one_or_none()
 
     response = client.patch(
         url_for("SensorAPI:patch", id=sensor.id),
-        headers={"content-type": "application/json", "Authorization": auth_token},
         json={
             attribute: value,
         },
@@ -159,15 +150,16 @@ def test_patch_sensor_for_excluded_attribute(
     assert response.json["message"]["json"][attribute] == ["Unknown field."]
 
 
-def test_patch_sensor_non_admin(client, setup_api_test_data):
+@pytest.mark.parametrize(
+    "requesting_user", ["test_supplier_user_4@seita.nl"], indirect=True
+)
+def test_patch_sensor_non_admin(client, setup_api_test_data, requesting_user):
     """Try to change the name of a sensor with a non admin account"""
-    headers = make_headers_for("test_supplier_user_4@seita.nl", client)
 
     sensor = Sensor.query.filter(Sensor.name == "some temperature sensor").one_or_none()
 
     response = client.patch(
         url_for("SensorAPI:patch", id=sensor.id),
-        headers=headers,
         json={
             "name": "try to change the name",
         },
@@ -177,10 +169,9 @@ def test_patch_sensor_non_admin(client, setup_api_test_data):
     assert response.json["status"] == "INVALID_SENDER"
 
 
-def test_delete_a_sensor(client, setup_api_test_data):
-
+@pytest.mark.parametrize("requesting_user", ["test_admin_user@seita.nl"], indirect=True)
+def test_delete_a_sensor(client, setup_api_test_data, requesting_user):
     existing_sensor_id = setup_api_test_data["some temperature sensor"].id
-    headers = make_headers_for("test_admin_user@seita.nl", client)
     sensor_data = TimedBelief.query.filter(
         TimedBelief.sensor_id == existing_sensor_id
     ).all()
@@ -190,7 +181,6 @@ def test_delete_a_sensor(client, setup_api_test_data):
 
     delete_sensor_response = client.delete(
         url_for("SensorAPI:delete", id=existing_sensor_id),
-        headers=headers,
     )
     assert delete_sensor_response.status_code == 204
     deleted_sensor = Sensor.query.filter_by(id=existing_sensor_id).one_or_none()
diff --git a/flexmeasures/conftest.py b/flexmeasures/conftest.py
index 78f3cfc61..ffe23d089 100644
--- a/flexmeasures/conftest.py
+++ b/flexmeasures/conftest.py
@@ -64,13 +64,8 @@ def app():
     print("APP FIXTURE")
     test_app = create_app(env="testing")
 
-    # Establish an application context before running the tests.
-    ctx = test_app.app_context()
-    ctx.push()
-
-    yield test_app
-
-    ctx.pop()
+    with test_app.app_context():
+        yield test_app
 
     print("DONE WITH APP FIXTURE")
 
diff --git a/requirements/3.10/test.txt b/requirements/3.10/test.txt
index f4726f482..28b42d24d 100644
--- a/requirements/3.10/test.txt
+++ b/requirements/3.10/test.txt
@@ -20,13 +20,13 @@ click==8.1.7
     # via
     #   -c requirements/3.10/app.txt
     #   flask
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
     # via pytest-cov
 exceptiongroup==1.1.3
     # via pytest
-fakeredis==2.18.0
+fakeredis==2.18.1
     # via -r requirements/test.in
-flask==2.1.2
+flask==2.2.5
     # via
     #   -c requirements/3.10/app.txt
     #   pytest-flask
@@ -52,14 +52,15 @@ markupsafe==2.1.3
     # via
     #   -c requirements/3.10/app.txt
     #   jinja2
+    #   werkzeug
 packaging==23.1
     # via
     #   -c requirements/3.10/app.txt
     #   pytest
     #   pytest-sugar
-pluggy==1.2.0
+pluggy==1.3.0
     # via pytest
-pytest==7.4.0
+pytest==7.4.2
     # via
     #   -r requirements/test.in
     #   pytest-cov
@@ -98,7 +99,7 @@ urllib3==2.0.4
     # via
     #   -c requirements/3.10/app.txt
     #   requests
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -c requirements/3.10/app.txt
     #   flask
diff --git a/requirements/3.11/test.txt b/requirements/3.11/test.txt
index f485aab92..8f41c53e1 100644
--- a/requirements/3.11/test.txt
+++ b/requirements/3.11/test.txt
@@ -16,11 +16,11 @@ click==8.1.7
     # via
     #   -c requirements/3.11/app.txt
     #   flask
-coverage[toml]==7.3.0
+coverage[toml]==7.3.1
     # via pytest-cov
-fakeredis==2.18.0
+fakeredis==2.18.1
     # via -r requirements/test.in
-flask==2.1.2
+flask==2.2.5
     # via
     #   -c requirements/3.11/app.txt
     #   pytest-flask
@@ -46,14 +46,15 @@ markupsafe==2.1.3
     # via
     #   -c requirements/3.11/app.txt
     #   jinja2
+    #   werkzeug
 packaging==23.1
     # via
     #   -c requirements/3.11/app.txt
     #   pytest
     #   pytest-sugar
-pluggy==1.2.0
+pluggy==1.3.0
     # via pytest
-pytest==7.4.0
+pytest==7.4.2
     # via
     #   -r requirements/test.in
     #   pytest-cov
@@ -88,7 +89,7 @@ urllib3==2.0.4
     # via
     #   -c requirements/3.11/app.txt
     #   requests
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -c requirements/3.11/app.txt
     #   flask
diff --git a/requirements/3.8/test.txt b/requirements/3.8/test.txt
index 681049eb0..28b1c0488 100644
--- a/requirements/3.8/test.txt
+++ b/requirements/3.8/test.txt
@@ -4,7 +4,7 @@
 #
 #    pip-compile --constraint=requirements/3.8/app.txt --output-file=requirements/3.8/test.txt requirements/test.in
 #
-async-timeout==4.0.2
+async-timeout==4.0.3
     # via
     #   -c requirements/3.8/app.txt
     #   redis
@@ -12,21 +12,21 @@ certifi==2023.7.22
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-charset-normalizer==3.1.0
+charset-normalizer==3.2.0
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-click==8.1.3
+click==8.1.7
     # via
     #   -c requirements/3.8/app.txt
     #   flask
-coverage[toml]==7.2.7
+coverage[toml]==7.3.1
     # via pytest-cov
-exceptiongroup==1.1.1
+exceptiongroup==1.1.3
     # via pytest
-fakeredis==2.15.0
+fakeredis==2.18.1
     # via -r requirements/test.in
-flask==2.1.2
+flask==2.2.5
     # via
     #   -c requirements/3.8/app.txt
     #   pytest-flask
@@ -36,7 +36,7 @@ idna==3.4
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-importlib-metadata==6.7.0
+importlib-metadata==6.8.0
     # via
     #   -c requirements/3.8/app.txt
     #   flask
@@ -56,14 +56,15 @@ markupsafe==2.1.3
     # via
     #   -c requirements/3.8/app.txt
     #   jinja2
+    #   werkzeug
 packaging==23.1
     # via
     #   -c requirements/3.8/app.txt
     #   pytest
     #   pytest-sugar
-pluggy==1.2.0
+pluggy==1.3.0
     # via pytest
-pytest==7.4.0
+pytest==7.4.2
     # via
     #   -r requirements/test.in
     #   pytest-cov
@@ -98,16 +99,16 @@ tomli==2.0.1
     # via
     #   coverage
     #   pytest
-urllib3==2.0.3
+urllib3==2.0.4
     # via
     #   -c requirements/3.8/app.txt
     #   requests
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -c requirements/3.8/app.txt
     #   flask
     #   pytest-flask
-zipp==3.15.0
+zipp==3.16.2
     # via
     #   -c requirements/3.8/app.txt
     #   importlib-metadata
diff --git a/requirements/3.9/test.txt b/requirements/3.9/test.txt
index cb066ca41..8b273d9fb 100644
--- a/requirements/3.9/test.txt
+++ b/requirements/3.9/test.txt
@@ -16,17 +16,17 @@ charset-normalizer==3.2.0
     # via
     #   -c requirements/3.9/app.txt
     #   requests
-click==8.1.6
+click==8.1.7
     # via
     #   -c requirements/3.9/app.txt
     #   flask
-coverage[toml]==7.2.7
+coverage[toml]==7.3.1
     # via pytest-cov
-exceptiongroup==1.1.2
+exceptiongroup==1.1.3
     # via pytest
-fakeredis==2.17.0
+fakeredis==2.18.1
     # via -r requirements/test.in
-flask==2.1.2
+flask==2.2.5
     # via
     #   -c requirements/3.9/app.txt
     #   pytest-flask
@@ -56,14 +56,15 @@ markupsafe==2.1.3
     # via
     #   -c requirements/3.9/app.txt
     #   jinja2
+    #   werkzeug
 packaging==23.1
     # via
     #   -c requirements/3.9/app.txt
     #   pytest
     #   pytest-sugar
-pluggy==1.2.0
+pluggy==1.3.0
     # via pytest
-pytest==7.4.0
+pytest==7.4.2
     # via
     #   -r requirements/test.in
     #   pytest-cov
@@ -102,7 +103,7 @@ urllib3==2.0.4
     # via
     #   -c requirements/3.9/app.txt
     #   requests
-werkzeug==2.1.0
+werkzeug==2.2.3
     # via
     #   -c requirements/3.9/app.txt
     #   flask