Compare commits

...

2 Commits

10 changed files with 144 additions and 24 deletions

View File

@ -646,19 +646,55 @@ def register_administerable(cls):
# for each model (otherwise the last view function definitions would # for each model (otherwise the last view function definitions would
# get registered to all routes). # get registered to all routes).
@auth Commentable = Administerable.__class_descendants__['Commentable'] # avoids circular dependency
@rendering.page(title_show=False)
def view_listing():
menu = rendering.Menu( if issubclass(cls, Commentable):
f'{cls._lowerclass}-listing-actions',
[{
'endpoint': f'admin.{cls._lowerclass}_create',
'label': 'Create new',
}]
)
return rendering.Listing(cls.select(), title=cls.__name__, mode='admin-teaser', menu=menu) @auth
@rendering.page(title_show=False)
def view_listing(offset=0):
menu = rendering.Menu(
f'{cls._lowerclass}-listing-actions',
[{
'endpoint': f'admin.{cls._lowerclass}_create',
'label': 'Create new',
}]
)
return rendering.Listing(cls.select().order_by(cls.created.desc(), cls.name, cls.id.desc()), title=cls.__name__, mode='teaser', menu=menu, endpoint=f'admin.{cls._lowerclass}_listing', offset=offset)
elif issubclass(cls, Named):
@auth
@rendering.page(title_show=False)
def view_listing(offset=0):
menu = rendering.Menu(
f'{cls._lowerclass}-listing-actions',
[{
'endpoint': f'admin.{cls._lowerclass}_create',
'label': 'Create new',
}]
)
return rendering.Listing(cls.select().order_by(cls.name, cls.id.desc()), title=cls.__name__, mode='teaser', menu=menu, endpoint=f'admin.{cls._lowerclass}_listing', offset=offset)
else:
@auth
@rendering.page(title_show=False)
def view_listing(offset=0):
menu = rendering.Menu(
f'{cls._lowerclass}-listing-actions',
[{
'endpoint': f'admin.{cls._lowerclass}_create',
'label': 'Create new',
}]
)
return rendering.Listing(cls.select().order_by(cls.id.desc()), title=cls.__name__, mode='teaser', menu=menu, endpoint=f'admin.{cls._lowerclass}_listing', offset=offset)
@auth @auth
def view_create(*args, **kwargs): def view_create(*args, **kwargs):
@ -682,6 +718,7 @@ def register_administerable(cls):
return cls.view(*args, **kwargs) return cls.view(*args, **kwargs)
app.admin.add_url_rule(f'/{cls._lowerclass}/', f'{cls._lowerclass}_listing', view_listing) app.admin.add_url_rule(f'/{cls._lowerclass}/', f'{cls._lowerclass}_listing', view_listing)
app.admin.add_url_rule(f'/{cls._lowerclass}/+<int:offset>/', f'{cls._lowerclass}_listing', view_listing)
app.admin.add_url_rule(f'/{cls._lowerclass}/create/', f'{cls._lowerclass}_create', view_create, methods=['GET', 'POST']) app.admin.add_url_rule(f'/{cls._lowerclass}/create/', f'{cls._lowerclass}_create', view_create, methods=['GET', 'POST'])
if issubclass(cls, Named): if issubclass(cls, Named):

View File

@ -135,7 +135,6 @@ class Application(flask.Flask, MenuMixin, BlockMixin):
# public routes # public routes
@rendering.page(title_show=False) @rendering.page(title_show=False)
def listing(offset=0): def listing(offset=0):
# TODO: Menu?
return rendering.Listing(cls.list(), title=cls.__name__, endpoint=f'{cls._lowerclass}_listing', offset=offset) return rendering.Listing(cls.list(), title=cls.__name__, endpoint=f'{cls._lowerclass}_listing', offset=offset)
@rendering.page(format='atom') @rendering.page(format='atom')

18
main.py
View File

@ -122,6 +122,20 @@ def menu_footer():
'endpoint': 'the_button', 'endpoint': 'the_button',
'label': 'The Button', 'label': 'The Button',
}, },
{
'endpoint': 'view_page',
'params': {
'path': '/donate',
},
'label': 'Donate',
},
{
'endpoint': 'view_page',
'params': {
'path': '/hire',
},
'label': 'Hire',
},
] ]
return rendering.Menu('secondary', items) return rendering.Menu('secondary', items)
@ -540,10 +554,10 @@ class ScoredLink(admin.Administerable):
last_scrape = peewee.DateTimeField(null=True, default=None) last_scrape = peewee.DateTimeField(null=True, default=None)
explanation_external_site_count = markdown.SafeMarkdownString(""" explanation_external_site_count = markdown.SafeMarkdownString("""
How many **third-party sites** the linked content loads data from. How many **third-party sites** the linked page loads data from.
This is relevant to your privacy because each of those third-party sites This is relevant to your privacy because each of those third-party sites
gets at the very least to see that you visited the linked content. gets at the very least to see that you visited this page.
*Lower* is better, **0** is *ideal*.""") *Lower* is better, **0** is *ideal*.""")

View File

@ -1,3 +1,6 @@
# builtins
import functools
# third-party # third-party
import werkzeug import werkzeug
import flask import flask
@ -13,12 +16,40 @@ import markdown
import form import form
import admin import admin
@app.expose('/tag/')
class Tag(admin.Named): class Tag(admin.Named):
title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title') title = markdown.SafeMarkdownCharField(null=False, verbose_name='Title')
text = markdown.MarkdownTextField() text = markdown.MarkdownTextField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.listing_offset = 0
@classmethod
def view(cls, mode='full', offset=0, **kwargs):
if mode != 'full':
return super().view(mode=mode, **kwargs)
# NOTE: copied over from Named.view, added setting of listing_offset
# TODO: there should be a more generalized solution for this.
# decorated `wrapped` functions should be moved into the
# classes themselves, so they can be easily overriden
@rendering.page(mode=mode, title_show=False) # show title in own template, gives better flexibility
def wrapped(**kw):
instance = cls.load(kw['name'])
instance.listing_offset = offset
if not instance.access(mode):
flask.abort(404)
return instance
return wrapped(**kwargs)
@werkzeug.utils.cached_property @werkzeug.utils.cached_property
def listing(self): def listing(self):
@ -60,7 +91,7 @@ class Tag(admin.Named):
stmt = stmt.order_by(stmt.c.created.desc(), stmt.c.name.asc()) stmt = stmt.order_by(stmt.c.created.desc(), stmt.c.name.asc())
return rendering.Listing(stmt.dicts(), item_constructor=taggable_constructor) return rendering.Listing(stmt.dicts(), offset=self.listing_offset, endpoint='tag_full', endpoint_params={'name': self.name}, item_constructor=taggable_constructor)
class TagCollectionForm(admin.AutoForm): class TagCollectionForm(admin.AutoForm):
@ -277,3 +308,23 @@ def taggable_constructor(item):
taggable = Taggable.__class_descendants__[ item['type'] ] taggable = Taggable.__class_descendants__[ item['type'] ]
return taggable.load(item['name']) return taggable.load(item['name'])
# Fake @app.expose, but with extra offset for tag_full
@rendering.page(title_show=False)
def tag_listing(offset=0):
return rendering.Listing(Tag.list(), title='Tag', endpoint='tag_listing', offset=offset)
@rendering.page(format='atom')
def tag_feed():
return rendering.Listing(Tag.list(), title=cls.__name__)
tag_full = functools.partial(Tag.view, mode='full')
app.add_url_rule('/tag/', 'tag_listing', tag_listing)
app.add_url_rule('/tag/+<int:offset>/', 'tag_listing', tag_listing)
app.add_url_rule('/feed/tag', 'tag_feed', tag_feed)
app.add_url_rule('/tag/<string:name>/', 'tag_full', tag_full)
app.add_url_rule('/tag/<string:name>/+<int:offset>/', 'tag_full', tag_full)
app.models_exposed['Tag'] = Tag

View File

@ -1114,6 +1114,11 @@ article.renderable > .content > h6 {
margin: 0 auto; margin: 0 auto;
} }
article.renderable.mode-full > footer .meta {
max-width: var(--distance-typographic-width);
margin: 0 auto;
}
article.administerable > .content section { article.administerable > .content section {
background: hsla(0 0 5 / 90%); background: hsla(0 0 5 / 90%);
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
@ -1180,7 +1185,7 @@ article.audio audio {
background: #444; background: #444;
} }
article.image .description { article.image.mode-inline .description {
position: absolute; position: absolute;
left: 50vw; left: 50vw;
translate: -50% -100%; translate: -50% -100%;

View File

@ -10,6 +10,14 @@
{% endblock %} {% endblock %}
{% block meta_header %}
<span class="created">{{ content.created|prettydate }}</span>
{{ super() }}
{% endblock %}
{% block footer %} {% block footer %}
{{ super() }} {{ super() }}

View File

@ -2,8 +2,6 @@
{% block header %} {% block header %}
{{ super() }}
{% if mode == 'full' %} {% if mode == 'full' %}
{% if content.lead_image_id %} {% if content.lead_image_id %}
@ -18,6 +16,8 @@
{% endif %} {% endif %}
{{ super() }}
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -3,7 +3,13 @@
{% if self.header()|trim %} {% if self.header()|trim %}
<header> <header>
{% block header %}{% endblock %} {% block header %}
{% if self.meta_header() %}
<div class="meta">
{% block meta_header %}{% endblock %}
</div>
{% endif %}
{% endblock %}
</header> </header>
{% endif %} {% endif %}
@ -22,9 +28,9 @@
{% if self.footer()|trim %} {% if self.footer()|trim %}
<footer> <footer>
{% block footer %} {% block footer %}
{% if self.meta()|trim %} {% if self.meta_footer()|trim %}
<div class="meta"> <div class="meta">
{% block meta %}{% endblock %} {% block meta_footer %}{% endblock %}
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
{% extends 'default/templates/renderable/administerable.html' %} {% extends 'default/templates/renderable/administerable.html' %}
{% block meta %} {% block meta_footer %}
{{ super() }} {{ super() }}

View File

@ -28,7 +28,7 @@ class class_or_instance_method(classmethod):
def prettydate(value): def prettydate(value):
if isinstance(value, datetime.datetime): if isinstance(value, datetime.datetime):
return value.strftime('%a %b %d %Y - %H:%M:%S') return value.strftime('%a, %b %d %Y - %H:%M')
return 'Lost in Time' return 'Lost in Time'