Compare commits
2 Commits
69f208fb8b
...
1b0e30ff9e
Author | SHA1 | Date | |
---|---|---|---|
1b0e30ff9e | |||
2538c7ea7f |
59
admin.py
59
admin.py
@ -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):
|
||||||
|
@ -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
18
main.py
@ -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*.""")
|
||||||
|
|
||||||
|
55
tagging.py
55
tagging.py
@ -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
|
||||||
|
@ -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%;
|
||||||
|
@ -10,6 +10,14 @@
|
|||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block meta_header %}
|
||||||
|
|
||||||
|
<span class="created">{{ content.created|prettydate }}</span>
|
||||||
|
|
||||||
|
{{ super() }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block footer %}
|
{% block footer %}
|
||||||
|
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
@ -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 %}
|
||||||
|
@ -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 %}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{% extends 'default/templates/renderable/administerable.html' %}
|
{% extends 'default/templates/renderable/administerable.html' %}
|
||||||
|
|
||||||
{% block meta %}
|
{% block meta_footer %}
|
||||||
|
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
|
2
util.py
2
util.py
@ -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'
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user