added /manage/ and /manage/accounts/ views, added TOC to template

This commit is contained in:
Yusur 2023-03-22 09:49:38 +01:00
parent 4536f7fbd9
commit 191e235137
9 changed files with 162 additions and 21 deletions

View file

@ -10,6 +10,9 @@
caution message when viewing them. caution message when viewing them.
+ Added Terms and Privacy Policy. + Added Terms and Privacy Policy.
+ Changed user page URLs (contributions page) from `/u/user` to `/@user`. + Changed user page URLs (contributions page) from `/u/user` to `/@user`.
+ `/manage/` is now a list of all managing options, including export/import and the brand new
`/manage/accounts`.
+ TOC is now shown in pages where screen width is greater than 960 pixels.
+ Style changes: added a top bar with the site title. It replaces the floating menu on the top right. + Style changes: added a top bar with the site title. It replaces the floating menu on the top right.
+ Added a built-in installer (`app_init.py`). You still need to manually create `site.conf`. + Added a built-in installer (`app_init.py`). You still need to manually create `site.conf`.

62
app.py
View file

@ -161,6 +161,12 @@ class User(BaseModel):
def is_authenticated(self): def is_authenticated(self):
return True return True
def groups(self):
return (
UserGroup.select().join(UserGroupMembership, on=UserGroupMembership.group)
.where(UserGroupMembership.user == self)
)
class UserGroup(BaseModel): class UserGroup(BaseModel):
name = CharField(32, unique=True) name = CharField(32, unique=True)
permissions = BitField() permissions = BitField()
@ -295,6 +301,8 @@ class PageRevision(BaseModel):
return self.textref.get_content() return self.textref.get_content()
def html(self, *, math=True): def html(self, *, math=True):
return md(self.text, math=self.page.is_math_enabled and math) return md(self.text, math=self.page.is_math_enabled and math)
def html_and_toc(self, *, math=True):
return md_and_toc(self.text, math=self.page.is_math_enabled and math)
def human_pub_date(self): def human_pub_date(self):
delta = datetime.datetime.now() - self.pub_date delta = datetime.datetime.now() - self.pub_date
T = partial(get_string, g.lang) T = partial(get_string, g.lang)
@ -484,7 +492,7 @@ def init_db_and_create_first_user():
#### WIKI SYNTAX #### #### WIKI SYNTAX ####
def md(text, expand_magic=False, toc=True, math=True): def md_and_toc(text, expand_magic=False, toc=True, math=True):
if expand_magic: if expand_magic:
# DEPRECATED seeking for a better solution. # DEPRECATED seeking for a better solution.
warnings.warn('Magic words are no more supported.', DeprecationWarning) warnings.warn('Magic words are no more supported.', DeprecationWarning)
@ -502,9 +510,16 @@ def md(text, expand_magic=False, toc=True, math=True):
'insert_fonts_css': not _getconf('site', 'katex_url') 'insert_fonts_css': not _getconf('site', 'katex_url')
} }
try: try:
return markdown.Markdown(extensions=extensions, extension_configs=extension_configs).convert(text) converter = markdown.Markdown(extensions=extensions, extension_configs=extension_configs)
if toc:
return converter.convert(text), converter.toc
else:
return converter.convert(text), ''
except Exception as e: except Exception as e:
return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e) return '<p class="error">There was an error during rendering: {e.__class__.__name__}: {e}</p>'.format(e=e), ''
def md(text, expand_magic=False, toc=True, math=True):
return md_and_toc(text, expand_magic=expand_magic, toc=toc, math=math)[0]
def remove_tags(text, convert=True, headings=True): def remove_tags(text, convert=True, headings=True):
if headings: if headings:
@ -538,7 +553,7 @@ def is_url_available(url):
forbidden_urls = [ forbidden_urls = [
'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create', 'about', 'accounts', 'ajax', 'backlinks', 'calendar', 'circles', 'create',
'easter', 'edit', 'embed', 'help', 'history', 'init-config', 'kt', 'easter', 'edit', 'embed', 'group', 'help', 'history', 'init-config', 'kt',
'manage', 'media', 'p', 'privacy', 'protect', 'search', 'static', 'stats', 'manage', 'media', 'p', 'privacy', 'protect', 'search', 'static', 'stats',
'tags', 'terms', 'u', 'upload', 'upload-info' 'tags', 'terms', 'u', 'upload', 'upload-info'
] ]
@ -783,11 +798,7 @@ def edit(id):
@app.route("/__sync_start") @app.route("/__sync_start")
def __sync_start(): def __sync_start():
if _getconf("sync", "master", "this") == "this": flash("Sync is unavailable. Please import and export pages manually.")
abort(403)
from app_sync import main
main()
flash("Successfully synced messages.")
return redirect("/") return redirect("/")
@app.route('/_jsoninfo/<int:id>', methods=['GET', 'POST']) @app.route('/_jsoninfo/<int:id>', methods=['GET', 'POST'])
@ -895,7 +906,15 @@ def contributions(username):
user = User.get(User.username == username) user = User.get(User.username == username)
except User.DoesNotExist: except User.DoesNotExist:
abort(404) abort(404)
return render_template('contributions.jinja2', u=user, contributions=user.contributions.order_by(PageRevision.pub_date.desc())) contributions = user.contributions.order_by(PageRevision.pub_date.desc())
page = int(request.args.get('page', 1))
return render_template('contributions.jinja2',
u=user,
contributions=contributions.paginate(page),
page_n=page,
total_count=contributions.count(),
min=min
)
@app.route('/calendar/') @app.route('/calendar/')
def calendar_view(): def calendar_view():
@ -1083,6 +1102,12 @@ def easter_y(y=None):
else: else:
return render_template('easter.jinja2') return render_template('easter.jinja2')
## administration ##
@app.route('/manage/')
def manage_main():
return render_template('administration.jinja2')
## import / export ## ## import / export ##
class Exporter(object): class Exporter(object):
@ -1214,6 +1239,23 @@ def importpages():
flash('Pages can be imported by Administrators only!') flash('Pages can be imported by Administrators only!')
return render_template('importpages.jinja2') return render_template('importpages.jinja2')
@app.route('/manage/accounts/', methods=['GET', 'POST'])
@login_required
def manage_accounts():
users = User.select().order_by(User.join_date.desc())
page = int(request.args.get('page', 1))
if request.method == 'POST':
if current_user.is_admin:
pass
else:
flash('Operation not permitted!')
return render_template('manageaccounts.jinja2',
users=users.paginate(page),
page_n=page,
total_count=users.count(),
min=min
)
## terms / privacy ## ## terms / privacy ##
@app.route('/terms/') @app.route('/terms/')

View file

@ -23,7 +23,7 @@
/* basic styles */ /* basic styles */
* {box-sizing: border-box;} * {box-sizing: border-box;}
body{font-family:sans-serif;background-color:var(--bg-main); color: var(--fg-main);margin:0;position:relative} body{font-family:sans-serif;background-color:var(--bg-main); color: var(--fg-main);margin:0;position:relative}
.content{margin: 3em 1.3em} .content{margin: 3em 1.3em; position: relative}
.footer{text-align:center;} .footer{text-align:center;}
/* header styles */ /* header styles */
@ -112,9 +112,10 @@ ul.inline > li:last-child::after {content: ""}
/* floating elements */ /* floating elements */
.toc{float:right} nav.toc{display:none}
@media (max-width:639px){ @media only screen and (min-width:960px){
.toc{display:none} nav.toc{display:block;position:absolute; top: 0; right: 0; width: 320px;}
.inner-content {margin-right: 320px}
} }
.backontop{position:fixed;bottom:0;right:0} .backontop{position:fixed;bottom:0;right:0}
@media print{ @media print{

View file

@ -0,0 +1,23 @@
{% extends "base.jinja2" %}
{% block title %}Administrative tools — {{ app_name }}{% endblock %}
{% block content %}
<h1>Administrative tools</h1>
{% if current_user and current_user.is_admin %}
<ul>
<li>
<a href="/manage/export">Export pages</a>
</li>
<li>
<a href="/manage/import">Import pages</a>
</li>
<li>
<a href="/manage/accounts">Manage accounts</a>
</li>
</ul>
{% else %}
<p>Administrative tools can be accessed by administrator users only.</p>
{% endif %}
{% endblock %}

View file

@ -52,6 +52,7 @@
<div class="flash">{{ msg }}</div> <div class="flash">{{ msg }}</div>
{% endfor %} {% endfor %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% block toc %}{% endblock %}
</div> </div>
<footer class="footer"> <footer class="footer">
<div class="footer-copyright">&copy; 20202023 Sakuragasaki46.</div> <div class="footer-copyright">&copy; 20202023 Sakuragasaki46.</div>

View file

@ -7,10 +7,17 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h1>Contributions of {{ u.username }}</div> <h1>Contributions of {{ u.username }}</h1>
<p class="nl-pagination">Showing results <strong>{{ page_n * 20 - 19 }}</strong> to <strong>{{ min(page_n * 20, total_count) }}</strong> of <strong>{{ total_count }}</strong> total.</p>
<ul> <ul>
{% for rev in contributions %} {% if page_n > 1 %}
<li class="nl-prev"><a href="?page={{ page_n - 1}}">&laquo; Previous page</a></li>
{% endif %}
{% for rev in contributions %}
<li> <li>
<a href="/history/revision/{{ rev.id }}/"> <a href="/history/revision/{{ rev.id }}/">
#{{ rev.id }} #{{ rev.id }}
@ -25,6 +32,10 @@
{{ rev.page.title }} {{ rev.page.title }}
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
{% if page_n < total_count // 20 %}
<li class="nl-next"><a href="?page={{ page_n + 1 }}">Next page &raquo;</a></li>
{% endif %}
</ul> </ul>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,52 @@
{% extends "base.jinja2" %}
{% block title %}Manage accounts - {{ app_name }}{% endblock %}
{% block content %}
<h1>Manage accounts</h1>
{% if current_user.is_admin %}
<p>
Here is the list of users registered on {{ app_name }}, in reverse chronological order.
<strong>Beware: you are managing sensitive informations.</strong>
</p>
<p class="nl-pagination">Showing results <strong>{{ page_n * 20 - 19 }}</strong> to <strong>{{ min(page_n * 20, total_count) }}</strong> of <strong>{{ total_count }}</strong> total.</p>
<form enctype="multipart/form-data" method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<ul>
{% if page_n > 1 %}
<li class="nl-prev"><a href="?page={{ page_n - 1}}">&laquo; Previous page</a></li>
{% endif %}
{% for u in users %}
<li>
<input type="checkbox" name="u{{ u.id }}">
<a href="/@{{ u.username }}">{{ u.username }}</a>
{% if u == current_user %}<strong>(you)</strong>{% endif %}
-
Groups:
<ul class="inline">
{% for ug in u.groups() %}
<li>{{ ug.name }}</li>
{% endfor %}
</ul>
-
Registered on:
{{ u.join_date }}
</li>
{% endfor %}
{% if page_n < total_count // 20 %}
<li class="nl-next"><a href="?page={{ page_n + 1 }}">Next page &raquo;</a></li>
{% endif %}
</ul>
</form>
{% else %}
<p>Managing accounts can be done by users with Admin permissions only.</p>
{% endif %}
{% endblock %}

View file

@ -1,9 +1,9 @@
{% extends "base.jinja2" %} {% extends "base.jinja2" %}
{% block title %}Not found - {{ app_name }}{% endblock %} {% block title %}{{ T('not-found') }} - {{ app_name }}{% endblock %}
{% block content %} {% block content %}
<h1>{{ T('not-found') }}</h1> <h1>{{ T('not-found') }}</h1>
<p>{{ T('not-found-text-1')}} <strong>{{ request.path }}</strong> {{ T('not-found-text-2' )}}.</p> <p>{{ T('not-found-text-1') }} <strong>{{ request.path }}</strong> {{ T('not-found-text-2') }}.</p>
{% endblock %} {% endblock %}

View file

@ -4,12 +4,14 @@
{% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %} {% block json_info %}<script>window.page_info={{ p.js_info()|tojson|safe }};</script>{% endblock %}
{% set html_and_toc = rev.html_and_toc(math = request.args.get('math') not in ['0', 'false', 'no', 'off']) %}
{% block content %} {% block content %}
<article> <article>
<h1 id="firstHeading">{{ p.title }}</h1> <h1 id="firstHeading">{{ p.title }}</h1>
{% if p.calendar %} {% if p.calendar %}
<p class="calendar-subtitle"><span class="material-icons">calendar_today</span>{{ p.calendar.strftime('%B %-d, %Y') }}</div> <p class="calendar-subtitle"><span class="material-icons">calendar_today</span>{{ p.calendar.strftime('%B %-d, %Y') }}</p>
{% endif %} {% endif %}
<div class="jump-to-actions"> <div class="jump-to-actions">
@ -28,7 +30,7 @@
{% endif %} {% endif %}
<div class="inner-content"> <div class="inner-content">
{{ rev.html(math = request.args.get('math') not in ['0', 'false', 'no', 'off'])|safe }} {{ html_and_toc[0]|safe }}
</div> </div>
{% if p.tags %} {% if p.tags %}
@ -54,6 +56,12 @@
{{ T('owner') }}: {{ p.owner.username }} {{ T('owner') }}: {{ p.owner.username }}
{% endblock %} {% endblock %}
{% block toc %}
<nav class="toc">
{{ html_and_toc[1] }}
</nav>
{% endblock %}
{% block scripts %} {% block scripts %}
<script src="/static/content.js"></script> <script src="/static/content.js"></script>
{% endblock %} {% endblock %}