Skip to content

Commit 40cfeac

Browse files
committed
feat(webserver): load custom css
1 parent f725f13 commit 40cfeac

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

questionpy_sdk/webserver/controllers/attempt/controller.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# This file is part of the QuestionPy SDK. (https://questionpy.org)
22
# The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
33
# (c) Technische Universität Berlin, innoCampus <[email protected]>
4-
4+
import logging
55
import random
66
import re
77
from enum import StrEnum
@@ -24,6 +24,10 @@
2424
from questionpy_server.worker import Worker
2525

2626

27+
_log = logging.getLogger(__name__)
28+
_QPY_URL_PATTERN = re.compile(r"^qpy://static/([a-z_]\w{0,126})/([a-z_]\w{0,126})((?:/[\w\-@:%+.~=]+)+)$")
29+
30+
2731
class AttemptStatus(StrEnum):
2832
STARTED = "STARTED"
2933
IN_PROGRESS = "IN_PROGRESS"
@@ -46,6 +50,7 @@ class AttemptTemplateContext(TypedDict):
4650
display_options: QuestionDisplayOptions
4751
import_map: dict[str, str]
4852
javascript_calls: list[JsModuleCall]
53+
stylesheet_urls: list[str]
4954

5055

5156
@dataclass
@@ -154,6 +159,7 @@ async def _render_ui(
154159
"display_options": display_options,
155160
"import_map": await self._get_import_map(),
156161
"javascript_calls": self._get_js_calls(attempt, display_options),
162+
"stylesheet_urls": self._get_stylesheet_urls(attempt),
157163
}
158164

159165
render_errors: SectionErrorMap = {}
@@ -199,6 +205,26 @@ def _get_js_calls(self, attempt: AttemptModel, display_options: QuestionDisplayO
199205
and (call.if_feedback_type is None or feedback_map[call.if_feedback_type])
200206
]
201207

208+
def _get_stylesheet_urls(self, attempt: AttemptModel) -> list[str]:
209+
urls = []
210+
211+
for url in set(attempt.ui.css_files):
212+
if match := _QPY_URL_PATTERN.match(url):
213+
namespace, short_name, path = match.group(1, 2, 3)
214+
static_path = f"static{path}"
215+
api_url = self.generate_api_url("file", namespace=namespace, short_name=short_name, path=static_path)
216+
urls.append(str(api_url))
217+
continue
218+
if url.startswith("qpy://"):
219+
_log.warning("Stylesheet URL '%s' looks like a QPy-URL, but could not be parsed.", url)
220+
continue
221+
if not url.startswith("https://"):
222+
_log.warning("Stylesheet URL '%s' does not use a supported scheme.", url)
223+
continue
224+
urls.append(url)
225+
226+
return urls
227+
202228
@property
203229
def _attempt_template(self) -> jinja2.Template:
204230
loader = jinja2.PackageLoader("questionpy_sdk.webserver")

questionpy_sdk/webserver/templates/attempt.html.jinja2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
border-color: var(--invalid-input-color);
3131
}
3232
</style>
33+
34+
{% for stylesheet_url in stylesheet_urls %}
35+
<link rel="stylesheet" href="{{ stylesheet_url|safe }}" />
36+
{% endfor %}
3337
{% endblock %}
3438

3539
{% block content %}

0 commit comments

Comments
 (0)