Skip to content
Snippets Groups Projects
Commit 901e9707 authored by chrissi^'s avatar chrissi^
Browse files

Make website output relocateable

With this change the website is prepared to be served from a
sub-directory on the webserver, e.g. from Gitlab Pages.

To archive this we need to do the following:

* Load our static assets from a relative URL.
* Make sure thumbnails and images are loaded from a relative URL.

But: If running from the live-server media is presented from a slightly
different URL. In this case we do not want to modify the thumbnail URLs.
parent a7277c8a
Branches chrissi/pages
No related tags found
1 merge request!14Chrissi/pages
Pipeline #3094 passed
......@@ -7,7 +7,7 @@ WEBSERVER_HOST=0.0.0.0
FLAMINGO_OUTPUT=output
FLAMINGO_ARGS=-s settings.py
FLAMINGO_SERVER_ARGS=$(FLAMINGO_ARGS) --port=$(WEBSERVER_PORT) --host=$(WEBSERVER_HOST)
FLAMINGO_SERVER_ARGS=--port=$(WEBSERVER_PORT) --host=$(WEBSERVER_HOST) -s settings.py settings-server.py
all: server
......
......@@ -3,7 +3,7 @@ template: raw.html
<div class="main">
<div>
<a href="/">Zurück zur Hauptseite</a><br><br>
<a href="./index.html">Zurück zur Hauptseite</a><br><br>
<h3>Impressum</h3>
Verantwortlich für den Inhalt dieser Seite nach §6Abs.2 MDStV ist die SFMW UG (haftungsbeschränkt) vertreten durch ihren Geschäftsführer René Stegmaier. Die SMFW UG (haftungsbeschränkt) ist beim Amtsgericht Hildesheim unter der Registernummer 204219 ins Handelsregister eingetragen.<br><br>
......
class RelativeMediaUrls:
def media_added(self, context, content, media):
media["url"] = f"{context.settings.MEDIA_PREFIX}{media['url']}"
from collections import OrderedDict
import hashlib
import logging
import re
import os
try:
from docutils.parsers.rst import directives
RST = True
except ImportError:
RST = False
from PIL import Image as PillowImage, UnidentifiedImageError
from flamingo.core.data_model import Content
logger = logging.getLogger('flamingo.plugins.Thumbnails')
DEFAULT_THUMBNAIL_CACHE = 'thumbs'
DIMENSION_RE = re.compile('^([0-9]{1,})(px)?$')
def parse_bool(value):
value = value.lower()
values = {
'false': False,
'true': True,
'0': False,
'1': True,
}
if value not in values:
return False
return values[value]
def parse_dimensions(dimension):
if isinstance(dimension, int):
return dimension
try:
return int(DIMENSION_RE.search(dimension).groups()[0])
except Exception:
return 0
def hash_image(path, options):
options = OrderedDict(options)
stream = b''
for k, v in options.items():
stream += '{}={},'.format(k, v).encode()
stream += open(path, 'rb').read()
return hashlib.md5(stream).hexdigest()
def scale_image(original_size, width=None, height=None):
if width and height:
return int(width), int(height)
original_width, original_height = original_size
if width:
width_percentage = (width / original_width) * 100
height = original_height * (width_percentage / 100)
return width, int(height)
if height:
height_percentage = (height / original_height) * 100
width = original_width * (height_percentage / 100)
return int(width), height
return original_size
class Thumbnails:
THEME_PATHS = [os.path.join(os.path.dirname(__file__), 'theme')]
def settings_setup(self, context):
THUMBNAIL_CACHE = getattr(context.settings, 'THUMBNAIL_CACHE',
DEFAULT_THUMBNAIL_CACHE)
context.settings.LIVE_SERVER_IGNORE_PREFIX.append(
os.path.join(context.settings.CONTENT_ROOT, THUMBNAIL_CACHE))
if RST:
logger.debug("setting up 'thumbnail' option for rst images")
if not hasattr(context.settings, 'RST_IMAGE_EXTRA_OPTION_SPEC'):
context.settings.RST_IMAGE_EXTRA_OPTION_SPEC = {}
context.settings.RST_IMAGE_EXTRA_OPTION_SPEC['thumbnail'] = \
directives.unchanged
else:
logger.debug('docutils seems to be not installed. setup skipped')
def media_added(self, context, content, media_content):
logger.debug('processing %s:%s',
content['path'] or content,
media_content['path'] or media_content)
if media_content['is_thumbnail'] or media_content['original']:
logger.debug(
"setup of thumbnail for %s:%s skipped: image is already a thumbnail", # NOQA
content['path'], media_content['path'])
return
if media_content['type'] != 'media/image':
logger.debug(
"setup of thumbnail for %s:%s skipped: type is not 'media/image'", # NOQA
content['path'], media_content['path'])
return
if('thumbnail' in media_content and
not parse_bool(media_content['thumbnail'])):
logger.debug(
'setup of thumbnail for %s:%s skipped: disabled by option',
content['path'], media_content['path'])
return
if not media_content['width'] and not media_content['height']:
logger.debug(
'setup of thumbnail for %s:%s skipped: no dimensions set',
content['path'], media_content['path'])
return
# parse dimensions
width = parse_dimensions(media_content['width'])
height = parse_dimensions(media_content['height'])
# scale image
path = os.path.join(context.settings.CONTENT_ROOT,
media_content['path'])
try:
image = PillowImage.open(path)
except UnidentifiedImageError:
logger.debug(
"setup of thumbnail for %s:%s skipped: image type can not be identified", # NOQA
content['path'], media_content['path'])
return
except FileNotFoundError:
logger.error('%s not found. Used in %s',
path, media_content['content']['path'])
return
width, height = scale_image(image.size, width=width, height=height)
media_content['width'] = width
media_content['height'] = height
# setup thumbnail media content
image_hash = hash_image(
os.path.join(context.settings.CONTENT_ROOT,
media_content['path']),
{
'width': media_content['width'],
'height': media_content['height'],
}
)
logger.debug('setup thumbnail for %s:%s (%s)',
content['path'], media_content['path'], image_hash)
# gen thumbnail paths
image_name, image_extension = os.path.splitext(media_content['output'])
thumbnail_path = os.path.join(
getattr(context.settings, 'THUMBNAIL_CACHE',
DEFAULT_THUMBNAIL_CACHE),
'{}{}'.format(image_hash, image_extension))
thumbnail_output = '{}.thumb.{}{}'.format(
image_name,
image_hash,
image_extension,
)
thumbnail_url = f'{context.settings.MEDIA_PREFIX}/' + thumbnail_output
# inject thumbnail paths into original media content
original_content = Content(
url=media_content['url'],
path=media_content['path'],
output=media_content['output'],
thumbnail=False,
)
content['media'].add(original_content)
media_content['path'] = thumbnail_path
media_content['output'] = thumbnail_output
media_content['url'] = thumbnail_url
media_content['is_thumbnail'] = True
# link original and thumbnail together
media_content['original'] = original_content
original_content['thumbnail'] = media_content
# generate thumbnail file
if context.settings.LIVE_SERVER_RUNNING:
return
self.gen_thumbnail(context, media_content, image=image)
def gen_thumbnail(self, context, media_content, image=None):
if not image:
path = os.path.join(context.settings.CONTENT_ROOT,
media_content['original']['path'])
try:
image = PillowImage.open(path)
except FileNotFoundError:
logger.error(
'%s not found. Used in %s',
path,
media_content['original']['content']['path'],
)
return
# check if thumbnail already exists
output = os.path.join(context.settings.CONTENT_ROOT,
media_content['path'])
if os.path.exists(output):
return
# generate thumbnail
if not media_content['width']:
logger.error("%s: invalid width '%s'",
media_content['original']['path'],
media_content['width'])
if not media_content['height']:
logger.error("%s: invalid height '%s'",
media_content['original']['path'],
media_content['height'])
if not media_content['width']:
return
context.mkdir_p(output, force=True)
image.thumbnail((media_content['width'], media_content['height'],))
image.save(output)
def render_media_content(self, context, media_content):
if not media_content['is_thumbnail']:
return
self.gen_thumbnail(context, media_content)
def post_build(self, context):
THUMBNAIL_CACHE = os.path.join(
context.settings.CONTENT_ROOT,
context.settings.get(
'THUMBNAIL_CACHE',
DEFAULT_THUMBNAIL_CACHE,
),
)
THUMBNAIL_REMOVE_ORPHANED = context.settings.get(
'THUMBNAIL_REMOVE_ORPHANED', True)
if not THUMBNAIL_REMOVE_ORPHANED:
return
if not os.path.exists(THUMBNAIL_CACHE):
return
# find generated thumbnails
thumbnails = []
for content in context.contents:
if not content['media']:
continue
for media_content in content['media']:
if media_content['is_thumbnail']:
path = os.path.join(
context.settings.CONTENT_ROOT,
media_content['path'],
)
thumbnails.append(path)
# remove obsolete thumbnails
for path in os.listdir(THUMBNAIL_CACHE):
path = os.path.join(THUMBNAIL_CACHE, path)
if path not in thumbnails:
logger.info('removing orphaned %s', path)
context.rm_rf(path, force=True)
MEDIA_PREFIX = ""
\ No newline at end of file
......@@ -9,9 +9,12 @@ PLUGINS = [
'flamingo.plugins.Redirects',
'flamingo.plugins.rstPygments',
'plugins/title.py::Title',
'flamingo.plugins.Thumbnails',
'plugins/relative_media_urls.py::RelativeMediaUrls',
'plugins/thumbnails.py::Thumbnails',
]
MEDIA_PREFIX = "."
HTML_PARSER_RAW_HTML = True
@flamingo.hook("contents_parsed")
......
......@@ -5,11 +5,11 @@ $link: #979797;
@font-face {
font-family: "Geo";
src: url("/static/fonts/Geo-Regular.ttf")
src: url("../fonts/Geo-Regular.ttf")
}
@font-face {
font-family: "Fira Sans";
src: url("/static/fonts/FiraSans-Regular.ttf")
src: url("../fonts/FiraSans-Regular.ttf")
}
body {
font-family: 'Geo', sans-serif;
......
......@@ -4,11 +4,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:image" content="https://hackenopenair.de/media/pool.jpeg">
<link rel="icon" href="/static/img/favicon.png" type="image/png">
<link rel="icon" href="./static/img/favicon.png" type="image/png">
<title>
{% block title %}Hacken Open Air 2::24{% endblock %}
</title>
<link rel="stylesheet" href="/static/css/hoa.css">
<link rel="stylesheet" href="./static/css/hoa.css">
</head>
<body>
{% block content %}{% endblock %}
......
......@@ -23,11 +23,11 @@
{% if content.group == "en" %}
<li><a href="https://wiki.hackenopenair.de">Wiki</a></li>
<li><a href="https://smaut.stratum0.org/hoa/hoa2023/">Tickets</a></li>
<li><a href="/index.html" aria-label="diese Seite auf Deutsch"><img src="/static/img/de.png" alt="german"></a></li>
<li><a href="./index.html" aria-label="diese Seite auf Deutsch"><img src="./static/img/de.png" alt="german"></a></li>
{% else %}
<li><a href="https://wiki.hackenopenair.de">Wiki</a></li>
<li><a href="https://smaut.stratum0.org/hoa/hoa2023/">Tickets</a></li>
<li><a href="/en.html" aria-label="this page in english"><img src="/static/img/gb.png" alt="englisch"></a></li>
<li><a href="./en.html" aria-label="this page in english"><img src="./static/img/gb.png" alt="englisch"></a></li>
{% endif %}
</ul>
</nav>
......@@ -37,7 +37,7 @@
<article>
<div align="center">
<h3>09.07 bis 13.07.2024 in Gifhorn</h3>
<img id="specht" src="/static/img/specht.svg" alt="logo">
<img id="specht" src="./static/img/specht.svg" alt="logo">
</div>
{{ content.content_body }}
{% if content.group is defined %}
......@@ -61,7 +61,7 @@
<li>Matrix: <a href="https://matrix.to/#/#hackenopenair:stratum0.org">#hackenopenair:stratum0.org</a></li>
<li>IRC: <a href="irc://irc.libera.chat/#hackenopenair">irc.libera.chat/#hackenopenair</a></li>
<li>Mastodon: <a href="https://chaos.social/@HackenOpenAir">@HackenOpenAir@chaos.social</a></li>
<li><a href="/impressum.html">Impressum und Datenschutzerklärung</a></li>
<li><a href="./impressum.html">Impressum und Datenschutzerklärung</a></li>
</ul>
<footer>
<h2 id="footnote">An Event by <a href="https://stratum0.org">Stratum 0</a></h2>
......@@ -73,7 +73,7 @@
<li>Matrix: <a href="https://matrix.to/#/#hackenopenair:stratum0.org">#hackenopenair:stratum0.org</a></li>
<li>IRC: <a href="irc://irc.libera.chat/#hackenopenair">irc.libera.chat/#hackenopenair</a></li>
<li>Mastodon: <a href="https://chaos.social/@HackenOpenAir">@HackenOpenAir@chaos.social</a></li>
<li><a href="/impressum.html">Impressum und Datenschutzerklärung</a></li>
<li><a href="./impressum.html">Impressum und Datenschutzerklärung</a></li>
</ul>
<footer>
<h2 id="footnote">Eine Veranstaltung des <a href="https://stratum0.org">Stratum&nbsp;0</a></h2>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment