Compare commits

...

2 Commits

Author SHA1 Message Date
cd4631d0e6 implemented generic redirection type (closes #35) 2024-05-22 03:32:17 +02:00
cb0b7a3a7c enabled peewee autorollback 2024-05-22 03:31:27 +02:00
4 changed files with 103 additions and 1 deletions

View File

@ -2,11 +2,29 @@
import functools
# third-party
import werkzeug.routing.map
import flask
# internals
import util
class URLMap(werkzeug.routing.map.Map):
def remove_endpoint(self, endpoint):
rules = []
for rule in self._rules:
if rule.endpoint == endpoint:
rules.append(rule)
for rule in rules:
self._rules.remove(rule)
del(self._rules_by_endpoint[endpoint])
self._remap = True
self.update()
class MenuMixin:
def menu(self, name):
@ -53,6 +71,8 @@ class Blueprint(flask.Blueprint, MenuMixin, BlockMixin):
class Application(flask.Flask, MenuMixin, BlockMixin):
url_map_class = URLMap
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@ -10,7 +10,8 @@ from application import app
import util
import rendering
app.db = playhouse.db_url.connect(app.config['DATABASE'])
# NOTE: autorollback is deprecated in most current release of peewee, but development is done with 3.15.0
app.db = playhouse.db_url.connect(app.config['DATABASE'], autorollback=True)
@app.before_request
def db_connect():

71
main.py
View File

@ -3,6 +3,7 @@
# builtins
import math
import datetime
import functools
# third-party
import markupsafe
@ -580,6 +581,76 @@ class CuratedArt(commenting.Commentable):
text = markdown.MarkdownField(null=False, verbose_name='Text')
#link = peewee.ForeignKeyField(ScoredLink, null=True)
class PermaRedirect(admin.Administerable):
# WARNING: This entire class is hacky and might break with virtually any flask or werkzeug update
__cache__ = {} # collect simplified instances in memory, keyed by id
match_path = peewee.CharField(null=False, unique=True, verbose_name='Match path', help_text='The path to match (i.e. as it was in URL on the old site')
redirect_path = peewee.CharField(null=False, verbose_name='Redirect path', help_text='The path to redirect to')
@classmethod
def view(cls, mode='full', **kwargs):
if mode != 'full':
return super().view(mode=mode, **kwargs)
info = cls.__cache__[kwargs['id']]
return flask.redirect(info['redirect_path'])
@classmethod
def boot(cls):
try:
for instance in cls.select():
cls.__cache__[instance.id] = {'match_path': instance.match_path, 'redirect_path': instance.redirect_path}
cls.update_routes()
except Exception as e:
app.logger.warning('Could not initialize PermaRedirect cache.')
@classmethod
def update_routes(cls):
for id, item in cls.__cache__.items():
# this is a simplified version of add_url_rule's logic
endpoint = f'permaredirect-{id}'
if endpoint in app.url_map._rules_by_endpoint:
app.url_map.remove_endpoint(endpoint)
#app.add_url_rule(item['match_path'], endpoint, functools.partial(cls.view, mode='full', id=id))
rule = app.url_rule_class(item['match_path'], methods=('GET', 'OPTIONS'), endpoint=endpoint)
rule.provide_automatic_options = True
app.url_map.add(rule)
app.view_functions[endpoint] = functools.partial(cls.view, mode='full', id=id)
def save(self, **kwargs):
# NOTE/TODO: This is an ugly hack (but so is the rest of this class).
# Should™ (also) be checked/handled via form validation
for path in (self.match_path, self.redirect_path):
if '<' in path or '>' in path:
raise ValueError(f"PermaRedirect paths MUST NOT contain '<' or '>' but got: {path}")
super().save(**kwargs)
self.__class__.__cache__[self.id] = {'match_path': self.match_path, 'redirect_path': self.redirect_path}
self.__class__.update_routes()
def delete_instance(self, **kwargs):
del(self.__class__.__cache__[self.id])
self.__class__.update_routes()
super().delete_instance(**kwargs)
app.boot(PermaRedirect.boot)
app.boot_run()
if __name__ == '__main__':

View File

@ -0,0 +1,10 @@
{% extends 'default/templates/renderable/administerable.html' %}
{% block content %}
<div>
<span>Match path:</span> <span class="match-path path">{{ content.match_path }}</span>
</div>
<div>
<span>Redirect path:</span> <span class="redirect-path path">{{ content.redirect_path }}</span>
</div>
{% endblock %}