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.
+ Added Terms and Privacy Policy.
+ 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.
+ 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):
return True
def groups(self):
return (
UserGroup.select().join(UserGroupMembership, on=UserGroupMembership.group)
.where(UserGroupMembership.user == self)
)
class UserGroup(BaseModel):
name = CharField(32, unique=True)
permissions = BitField()
@ -295,6 +301,8 @@ class PageRevision(BaseModel):
return self.textref.get_content()
def html(self, *, math=True):
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):
delta = datetime.datetime.now() - self.pub_date
T = partial(get_string, g.lang)
@ -484,7 +492,7 @@ def init_db_and_create_first_user():
#### 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:
# DEPRECATED seeking for a better solution.
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')
}
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:
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):
if headings:
@ -538,7 +553,7 @@ def is_url_available(url):
forbidden_urls = [
'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',
'tags', 'terms', 'u', 'upload', 'upload-info'
]
@ -783,11 +798,7 @@ def edit(id):
@app.route("/__sync_start")
def __sync_start():
if _getconf("sync", "master", "this") == "this":
abort(403)
from app_sync import main
main()
flash("Successfully synced messages.")
flash("Sync is unavailable. Please import and export pages manually.")
return redirect("/")
@app.route('/_jsoninfo/<int:id>', methods=['GET', 'POST'])
@ -895,7 +906,15 @@ def contributions(username):
user = User.get(User.username == username)
except User.DoesNotExist:
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/')
def calendar_view():
@ -1083,6 +1102,12 @@ def easter_y(y=None):
else:
return render_template('easter.jinja2')
## administration ##
@app.route('/manage/')
def manage_main():
return render_template('administration.jinja2')
## import / export ##
class Exporter(object):
@ -1214,6 +1239,23 @@ def importpages():
flash('Pages can be imported by Administrators only!')
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 ##
@app.route('/terms/')

View file

@ -23,7 +23,7 @@
/* basic styles */
* {box-sizing: border-box;}
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;}
/* header styles */
@ -112,9 +112,10 @@ ul.inline > li:last-child::after {content: ""}
/* floating elements */
.toc{float:right}
@media (max-width:639px){
.toc{display:none}
nav.toc{display:none}
@media only screen and (min-width:960px){
nav.toc{display:block;position:absolute; top: 0; right: 0; width: 320px;}
.inner-content {margin-right: 320px}
}
.backontop{position:fixed;bottom:0;right:0}
@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>
{% endfor %}
{% block content %}{% endblock %}
{% block toc %}{% endblock %}
</div>
<footer class="footer">
<div class="footer-copyright">&copy; 20202023 Sakuragasaki46.</div>

View file

@ -7,9 +7,16 @@
{% endblock %}
{% 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>
{% 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>
<a href="/history/revision/{{ rev.id }}/">
@ -26,5 +33,9 @@
</a>
</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>
{% 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,6 +1,6 @@
{% extends "base.jinja2" %}
{% block title %}Not found - {{ app_name }}{% endblock %}
{% block title %}{{ T('not-found') }} - {{ app_name }}{% endblock %}
{% block content %}
<h1>{{ T('not-found') }}</h1>

View file

@ -4,12 +4,14 @@
{% 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 %}
<article>
<h1 id="firstHeading">{{ p.title }}</h1>
{% 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 %}
<div class="jump-to-actions">
@ -28,7 +30,7 @@
{% endif %}
<div class="inner-content">
{{ rev.html(math = request.args.get('math') not in ['0', 'false', 'no', 'off'])|safe }}
{{ html_and_toc[0]|safe }}
</div>
{% if p.tags %}
@ -54,6 +56,12 @@
{{ T('owner') }}: {{ p.owner.username }}
{% endblock %}
{% block toc %}
<nav class="toc">
{{ html_and_toc[1] }}
</nav>
{% endblock %}
{% block scripts %}
<script src="/static/content.js"></script>
{% endblock %}