diff --git a/afpy.py b/afpy.py index a42d977..820782b 100644 --- a/afpy.py +++ b/afpy.py @@ -1,9 +1,11 @@ +import os import email import locale import time from pathlib import Path from xml.etree import ElementTree +from itsdangerous import BadSignature, URLSafeSerializer import docutils.core import docutils.writers.html5_polyglot import feedparser @@ -17,6 +19,7 @@ from jinja2 import TemplateNotFound locale.setlocale(locale.LC_ALL, 'fr_FR.UTF-8') cache = Cache(config={'CACHE_TYPE': 'simple', 'CACHE_DEFAULT_TIMEOUT': 600}) +signer = URLSafeSerializer(os.environ.get('SECRET', 'changeme!')) app = Flask(__name__) cache.init_app(app) @@ -97,34 +100,53 @@ def rest(name): 'rst.html', body_id=name, html=parts['body'], title=parts['title']) -@app.route('/post/edit/') -@app.route('/admin/post/edit//') -def edit_post(name, timestamp=None): - if name not in POSTS: - abort(404) - if timestamp is None: - state = 'waiting' - post = {} +def _get_post(name, timestamp): + for state in ('waiting', 'published'): + path = (root / name / state / timestamp / 'post.xml') + if path.is_file(): + break else: - for state in ('published', 'waiting'): - path = (root / name / state / timestamp / 'post.xml') - if path.is_file(): - break - else: - abort(404) - tree = ElementTree.parse(path) - post = {item.tag: (item.text or '').strip() for item in tree.iter()} - return render_template( - 'edit_post.html', body_id='edit-post', post=post, name=name, - state=state) + return None + tree = ElementTree.parse(path) + post = {item.tag: (item.text or '').strip() for item in tree.iter()} + post['state'] = state + post['timestamp'] = timestamp + return post -@app.route('/post/edit/', methods=['post']) -@app.route('/admin/post/edit//', methods=['post']) -def save_post(name, timestamp=None): - original_timestamp = timestamp +@app.route('/post/edit/') +@app.route('/post/edit//token/') +def edit_post(name, token=None): if name not in POSTS: abort(404) + if token: + try: + timestamp = signer.loads(token) + except BadSignature: + abort(401) + post = _get_post(name, timestamp) + if not post: + abort(404) + else: + post = {'state': 'waiting'} + if post['state'] != 'waiting': + return redirect(url_for('rest', name='already_published')) + return render_template( + 'edit_post.html', body_id='edit-post', post=post, name=name, admin=False) + + +@app.route('/admin/post/edit//') +def edit_post_admin(name, timestamp): + if name not in POSTS: + abort(404) + post = _get_post(name, timestamp) + if not post: + abort(404) + return render_template( + 'edit_post.html', body_id='edit-post', post=post, name=name, admin=True) + + +def _save_post(name, timestamp, admin): if timestamp is None: timestamp = str(int(time.time())) status = 'waiting' @@ -137,6 +159,10 @@ def save_post(name, timestamp=None): status = 'published' else: abort(404) + + if status == 'published' and not admin: + abort(401) + post = root / name / status / timestamp / 'post.xml' tree = ElementTree.Element('entry') for key, value in request.form.items(): @@ -146,15 +172,42 @@ def save_post(name, timestamp=None): element.text = email.utils.formatdate( int(timestamp) if timestamp else time.time()) ElementTree.ElementTree(tree).write(post) - if original_timestamp: + + if admin: if 'publish' in request.form and status == 'waiting': (root / name / 'waiting' / timestamp).rename( root / name / 'published' / timestamp) elif 'unpublish' in request.form and status == 'published': (root / name / 'published' / timestamp).rename( root / name / 'waiting' / timestamp) - return redirect(url_for('admin', name=name)) - return redirect(url_for('rest', name='confirmation')) + + return _get_post(name, timestamp) + + +@app.route('/post/edit/', methods=['post']) +@app.route('/post/edit//token/', methods=['post']) +def save_post(name, token=None): + if name not in POSTS: + abort(404) + if token: + try: + timestamp = signer.loads(token) + except BadSignature: + abort(401) + else: + timestamp = None + post = _save_post(name, timestamp=timestamp, admin=False) + edit_post_url = url_for( + 'edit_post', name=name, token=signer.dumps(post['timestamp'])) + return render_template('confirmation.html', edit_post_url=edit_post_url) + + +@app.route('/admin/post/edit//', methods=['post']) +def save_post_admin(name, timestamp): + if name not in POSTS: + abort(404) + _save_post(name, timestamp=timestamp, admin=True) + return redirect(url_for('admin', name=name)) @app.route('/posts/') diff --git a/templates/admin.html b/templates/admin.html index 8608442..6254e3d 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -24,7 +24,7 @@ {{ post.title }} {{ post.published | parse_iso_datetime('%x') }} - Éditer + Éditer {% endfor %} diff --git a/templates/already_published.rst b/templates/already_published.rst new file mode 100644 index 0000000..d69e40f --- /dev/null +++ b/templates/already_published.rst @@ -0,0 +1,9 @@ +=================================== +Cet article ne peut plus être édité +=================================== + +Article déjà publié +=================== + +Seuls les administrateurs ont le droit d'éditer un article publié. +En cas de besoin, n'hésitez pas à nous contacter sur la page `Discussion `_. diff --git a/templates/confirmation.html b/templates/confirmation.html new file mode 100644 index 0000000..f1ecc69 --- /dev/null +++ b/templates/confirmation.html @@ -0,0 +1,31 @@ +{% extends '_layout.jinja2' %} + +{% block header %} +

Confirmation de l'enregistrement de l'article

+{% endblock header %} + +{% block main %} + +

Merci de votre participation

+ +

+ Votre article a bien été enregistré. Il sera mis en ligne après acceptation + de l'un des modérateurs. +

+

+ En attendant, vous pouvez toujours la modifier en utilisant le lien + {{ edit_post_url }}. + Attention à conservent celui-ci secret ! +

+ + +

Demande d'informations complémentaires

+ +

+ Si vous avez besoin d'informations complémentaires concernant votre article, + ou si vous ne comprenez pas pourquoi votre article n'apparaît pas encore en + ligne plusieurs jours après avoir été posté, n'hésitez pas à nous contacter + sur la page Discussion. +

+ +{% endblock main %} diff --git a/templates/confirmation.rst b/templates/confirmation.rst deleted file mode 100644 index 8faa624..0000000 --- a/templates/confirmation.rst +++ /dev/null @@ -1,18 +0,0 @@ -============================================= -Confirmation de l'enregistrement de l'article -============================================= - -Merci de votre participation -============================ - -Votre article a bien été enregistré. Il sera mis en ligne après acceptation de -l'un des modérateurs. - - -Demande d'informations complémentaires -====================================== - -Si vous avez besoin d'informations complémentaires concernant votre article, ou -si vous ne comprenez pas pourquoi votre article n'apparaît pas encore en ligne -plusieurs jours après avoir été posté, n'hésitez pas à nous contacter sur la -page `Discussion `_. diff --git a/templates/edit_post.html b/templates/edit_post.html index ed42c76..a2f4245 100644 --- a/templates/edit_post.html +++ b/templates/edit_post.html @@ -51,8 +51,8 @@ - {% if post %} - {% if state == 'waiting' %} + {% if admin %} + {% if post.state == 'waiting' %} {% else %}