Compare commits

..

No commits in common. "6f98cba65e957b568f9430305f12ddcaca73374f" and "53afccf2a33fe828ba3bb52e8538fd50184abb7f" have entirely different histories.

34 changed files with 165 additions and 296 deletions

View File

@ -1,8 +1,10 @@
# This file is a list of forums to create when setting up Planète Casio.
#
# * Keys are used as URLs paths and for unique identification.
# * Prefixes are used to identify privileges for each forum, see groups.yaml
# for details.
# * Prefixes represent the privilege category for a forum. Owning privileges
# with this prefix allows the user to post in this forum and all its
# sub-forum regardless of their settings ("forum-root-*" are hyper powerful).
# * For open forums, use the prefix "open".
/:
name: Forum de Planète Casio
@ -102,8 +104,8 @@
prefix: discussion
descr: Sujets hors-sujet et discussion libre.
# Limited-access boards
# Prefixes "admin" and "creativecalc" are reserved for this and require special
# Limited-access board
# Prefixes "admin" and "assoc" are reserved for this and require special
# privileges to list, read and edit topics and messages.
/admin:
@ -114,5 +116,5 @@
/creativecalc:
name: CreativeCalc
prefix: creativecalc
prefix: assoc
descr: Forum privé de l'association CreativeCalc, réservé aux membres.

View File

@ -1,109 +1,63 @@
# LIST OF PRIVILEGES:
#
# Access to specific forums (see forums.yaml for prefix values):
# forum.access.<prefix>
# forum.post.<prefix>
# forum.post-news
# forum.post-anywhere
# -> All forums are readable by default except <admin> and <creativecalc>
# -> All forums are writable by default except <admin>, <creativecalc>,
# children of <news>, and forums with children ("categories")
# -> Use member.can_access_forum(forum) and member.can_post_in_forum(forum)
#
# Access to extended publication methods:
# publish.schedule-posts
# publish.pin-posts
# publish.shared-files
#
# Moderation:
# edit.posts
# edit.tests
# edit.accounts
# edit.trophies
# delete.posts
# delete.tests
# delete.accounts
# delete.shared-files
# move.posts
#
# Shoutbox:
# shoutbox.kick
# shoutbox.ban
#
# Miscellaneous:
# misc.unlimited-pms
# misc.dev-infos
# misc.community-login
# misc.admin-panel
# misc.no-upload-limits
#
# TODO: PRIVILEGES NOT YET IMPLEMENTED:
# The features that these privileges control are not implemented yet, or the
# privilege checks are missing.
#
# publish.*
# edit.tests
# delete.tests delete.shared-files
# move.posts
# shoutbox.*
# misc.unlimited-pms
# misc.community-login
-
name: Administrateur
css: "color: #ee0000;"
descr: "Vous voyez Chuck Norris? Pareil."
privs: forum.access.admin forum.access.creativecalc forum.post-news
forum.post-anywhere
publish.schedule-posts publish.pin-posts publish.shared-files
edit.posts edit.tests edit.accounts edit.trophies
delete.posts delete.tests delete.accounts delete.shared-files
move.posts
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.dev-infos misc.community-login misc.admin-panel
misc.no-upload-limits
privs: access-admin-board access-assoc-board write-news write-anywhere
upload-shared-files delete-shared-files
edit-posts delete-posts scheduled-posting
delete-content move-public-content move-private-content showcase-content
edit-static-content extract-posts
delete-notes delete-tests
shoutbox-kick shoutbox-ban
unlimited-pms footer-statistics community-login
access-admin-panel edit-account delete-account edit-trophies
delete_notification no-upload-limits
-
name: Modérateur
css: "color: green;"
descr: "Maîtres du kick, ils sont là pour faire respecter un semblant d'ordre."
privs: forum.access.admin
edit.posts edit.tests
delete.posts delete.tests
move.posts
shoutbox.kick shoutbox.ban
misc.unlimited-pms misc.no-upload-limits
privs: access-admin-board
edit-posts delete-posts
move-public-content extract-posts
delete-notes delete-tests
shoutbox-kick shoutbox-ban
unlimited-pms no-upload-limits
-
name: Développeur
css: "color: #4169e1;"
descr: "Les développeurs maintiennent et améliorent le code du site."
privs: forum.access.admin forum.post-anywhere
publish.schedule-posts publish.shared-files
delete.shared-files
misc.unlimited-pms misc.dev-infos misc.community-login misc.admin-panel
privs: access-admin-board
upload-shared-files delete-shared-files
scheduled-posting
edit-static-content
unlimited-pms footer-statistics community-login
access-admin-panel no-upload-limits
-
name: Rédacteur
css: "color: blue;"
descr: "Rédigent les meilleurs articles de la page d'accueil, rien que pour
vous <3"
privs: forum.access.admin forum.post-news
publish.schedule-posts publish.pin-posts publish.shared-files
delete.shared-files
misc.no-upload-limits
privs: access-admin-board write-news
upload-shared-files delete-shared-files
scheduled-posting
showcase-content edit-static-content
no-upload-limits
-
name: Responsable communauté
css: "color: DarkOrange;"
descr: "Anime les pages Twitter et Facebook de Planète Casio et surveille
l'évolution du monde autour de nous !"
privs: forum.access.admin forum.post-news
publish.schedule-posts publish.pin-posts publish.shared-files
delete.shared-files
privs: access-admin-board write-news
upload-shared-files delete-shared-files
scheduled-posting
showcase-content
-
name: Partenaire
css: "color: purple;"
descr: "Membres de l'équipe d'administration des sites partenaires."
privs: forum.post-news
publish.schedule-posts publish.shared-files
delete.shared-files
privs: write-news
upload-shared-files delete-shared-files
scheduled-posting
-
name: Compte communautaire
css: "background:#d8d8d8; border-radius:4px; color:#303030; padding:1px 2px;"
@ -112,13 +66,13 @@
name: Robot
css: "color: #cf25d0;"
descr: "♫ Je suis Nono, le petit robot, l'ami d'Ulysse ♫"
privs: shoutbox.kick shoutbox.ban
privs: shoutbox-post shoutbox-kick shoutbox-ban
-
name: Membre de CreativeCalc
css: "color: #222222;"
descr: "CreativeCalc est l'association qui gère Planète Casio."
privs: forum.access.creativecalc
privs: access-assoc-board
-
name: No login
css: "color: #888888;"
descr: "Compte dont l'accès au site est désactivé."
name: No login
css: "color: #888888;"
descr: "Compte dont l'accès au site est désactivé."

View File

@ -20,8 +20,7 @@ class Forum(db.Model):
lazy=True, foreign_keys=parent_id)
# Other fields populated automatically through relations:
# <sub_forums> Children forums
# <topics> List of topics in this exact forum (of type Topic)
# <topics> List of topics in this exact forum (of type Topic)
# Some configuration
TOPICS_PER_PAGE = 30
@ -37,19 +36,6 @@ class Forum(db.Model):
else:
self.parent = parent
def is_news(self):
"""Whether this forum is a news board."""
return (self.parent is not None) and (self.parent.prefix == "news")
def is_default_accessible(self):
"""Whether this forum can be read without privileges."""
return (self.prefix != "admin") and (self.prefix != "creativecalc")
def is_default_postable(self):
"""Whether this forum can be posted to without privileges."""
return self.is_default_accessible() and (not self.is_news()) and \
(self.sub_forums == [])
def post_count(self):
"""Number of posts in every topic of the forum, without subforums."""
# TODO: optimize this with real ORM

View File

@ -9,8 +9,6 @@ class Topic(Post):
__mapper_args__ = {
'polymorphic_identity': __tablename__,
# Because there is an extra relation to Post (promotion), SQLAlchemy
# cannot guess which Post we inherit from; specify here.
'inherit_condition': id == Post.id
}

View File

@ -20,11 +20,11 @@ import os
class User(UserMixin, db.Model):
""" Any website user, logged in (Member) or not (Guest) """
""" Website user that performs actions on the post """
__tablename__ = 'user'
# User ID, should be used to refer to any user. The actual user can either
# User ID, should be used to refer to any user. Thea actual user can either
# be a guest (with IP as key) or a member (with this ID as key).
id = db.Column(db.Integer, primary_key=True)
# User type (polymorphic discriminator)
@ -55,7 +55,7 @@ class Guest(User):
# ID of the [User] entry
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
# Reusable username, cannot be chosen as the name of a member
# but will be distinguished at rendering time if a member takes it later
# but will be distinguished at rendering time if a member take it later
name = db.Column(db.Unicode(User.NAME_MAXLEN))
def __init__(self, name):
@ -66,7 +66,7 @@ class Guest(User):
class Member(User):
""" Registered user with full access to the site's features """
""" Registered user with full access to the website's services """
__tablename__ = 'member'
__mapper_args__ = {'polymorphic_identity': __tablename__}
@ -116,6 +116,10 @@ class Member(User):
programs = db.relationship('Program')
comments = db.relationship('Comment')
# Displayed title
# title_id = db.Column(db.Integer, db.ForeignKey('title.id'))
# title = db.relationship('Title', foreign_keys=title_id)
# Other fields populated automatically through relations:
# <notifications> List of unseen notifications (of type Notification)
# <polls> Polls created by the member (of class Poll)
@ -148,8 +152,6 @@ class Member(User):
db.session.delete(self)
db.session.commit()
# Privilege checks
def priv(self, priv):
"""Check whether the member has the specified privilege."""
if SpecialPrivilege.query.filter_by(mid=self.id, priv=priv).first():
@ -164,35 +166,6 @@ class Member(User):
sp = SpecialPrivilege.query.filter_by(mid=self.id).all()
return sorted(row.priv for row in sp)
def can_access_forum(self, forum):
"""Whether this member can read the forum's contents."""
return forum.is_default_accessible() or \
self.priv(f"forum.access.{forum.prefix}")
def can_post_in_forum(self, forum):
"""Whether this member can post in the forum."""
return forum.is_default_postable() or \
(forum.is_news() and self.priv("forum.post-news")) or \
self.priv("forum.post.{forum.prefix}") or \
self.priv("forum.post-anywhere")
def can_access_post(self, post):
"""Whether this member can access the post's forum (if any)."""
if post.type == "comment" and post.thread.owner_topic:
return self.can_access_forum(post.thread.owner_post.forum)
# Posts from other types of content are all public
return True
def can_edit_post(self, post):
"""Whether this member can edit the post."""
return self.can_access_post(post) and \
((post.author == self) or self.priv("edit.posts"))
def can_delete_post(self, post):
"""Whether this member can delete the post."""
return self.can_access_post(post) and \
((post.author == self) or self.priv("delete.posts"))
def update(self, **data):
"""
Update all or part of the user's metadata. The [data] dictionary

View File

@ -14,7 +14,7 @@ from config import V5Config
@app.route('/admin/compte/<user_id>/editer', methods=['GET', 'POST'])
@priv_required('misc.admin-panel', 'edit.accounts')
@priv_required('access-admin-panel', 'edit-account')
def adm_edit_account(user_id):
user = Member.query.filter_by(id=user_id).first_or_404()
@ -119,7 +119,7 @@ def adm_edit_account(user_id):
@app.route('/admin/compte/<user_id>/supprimer', methods=['GET', 'POST'])
@priv_required('misc.admin-panel', 'delete.accounts')
@priv_required('access-admin-panel', 'delete-account')
def adm_delete_account(user_id):
user = Member.query.filter_by(id=user_id).first_or_404()

View File

@ -6,7 +6,7 @@ from app.utils.render import render
# TODO: add pagination & moderation tools (deletion)
@app.route('/admin/fichiers', methods=['GET'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm_attachments():
attachments = Attachment.query.all()

View File

@ -4,7 +4,7 @@ from app import app
from config import V5Config
@app.route('/admin/config', methods=['GET'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm_config():
config = {k: getattr(V5Config, k) for k in [
"DOMAIN", "DB_NAME", "USE_LDAP", "LDAP_ROOT", "LDAP_ENV",

View File

@ -4,7 +4,7 @@ from app.models.forum import Forum
from app import app, db
@app.route('/admin/forums', methods=['GET'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm_forums():
main_forum = Forum.query.filter_by(parent=None).first()

View File

@ -10,7 +10,7 @@ import os
@app.route('/admin/groupes', methods=['GET', 'POST'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm_groups():
# Users with either groups or special privileges
users_groups = Member.query.join(GroupMember)

View File

@ -4,6 +4,6 @@ from app import app
@app.route('/admin', methods=['GET'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm():
return render('admin/index.html')

View File

@ -6,7 +6,7 @@ from app import app, db
@app.route('/admin/membres', methods=['GET', 'POST'])
@priv_required('misc.admin-panel')
@priv_required('access-admin-panel')
def adm_members():
users = Member.query.all()
users = sorted(users, key = lambda x: x.name)

View File

@ -7,7 +7,7 @@ from app import app, db
@app.route('/admin/trophees', methods=['GET', 'POST'])
@priv_required('misc.admin-panel', 'edit.trophies')
@priv_required('access-admin-panel', 'edit-trophies')
def adm_trophies():
form = TrophyForm()
if request.method == "POST":
@ -31,7 +31,7 @@ def adm_trophies():
@app.route('/admin/trophees/<trophy_id>/editer', methods=['GET', 'POST'])
@priv_required('misc.admin-panel', 'edit.trophies')
@priv_required('access-admin-panel', 'edit-trophies')
def adm_edit_trophy(trophy_id):
trophy = Trophy.query.filter_by(id=trophy_id).first_or_404()
@ -59,7 +59,7 @@ def adm_edit_trophy(trophy_id):
@app.route('/admin/trophees/<trophy_id>/supprimer', methods=['GET', 'POST'])
@priv_required('misc.admin-panel', 'edit.trophies')
@priv_required('access-admin-panel', 'edit-trophies')
def adm_delete_trophy(trophy_id):
trophy = Trophy.query.filter_by(id=trophy_id).first_or_404()

View File

@ -20,18 +20,21 @@ def forum_index():
@app.route('/forum/<forum:f>/', methods=['GET', 'POST'])
@app.route('/forum/<forum:f>/p/<int:page>', methods=['GET', 'POST'])
def forum_page(f, page=1):
if not f.is_default_accessible() and not (
current_user.is_authenticated and current_user.can_access_forum(f)):
abort(403)
if current_user.is_authenticated:
form = TopicCreationForm()
else:
form = AnonymousTopicCreationForm()
# TODO: do not hardcode name of news forums
if form.validate_on_submit() and (
(V5Config.ENABLE_GUEST_POST and f.is_default_postable()) or \
(current_user.is_authenticated and current_user.can_post_in_forum(f))):
# User can write anywhere
(current_user.is_authenticated and current_user.priv('write-anywhere'))
# Forum is news forum TODO: add good condition to check if it's news
or ("/actus" in f.url and current_user.is_authenticated
and current_user.priv('write-news'))
# Forum is not news and is a leaf:
or ("/actus" not in f.url and not f.sub_forums)) and (
V5Config.ENABLE_GUEST_POST or current_user.is_authenticated):
# Manage author
if current_user.is_authenticated:

View File

@ -18,10 +18,6 @@ from datetime import datetime
def forum_topic(f, page):
t, page = page
if not f.is_default_accessible() and not (
current_user.is_authenticated and current_user.can_access_forum(f)):
abort(403)
# Quick n' dirty workaround to converters
if f != t.forum:
abort(404)
@ -31,10 +27,8 @@ def forum_topic(f, page):
else:
form = AnonymousCommentForm()
if form.validate_on_submit() and (
V5Config.ENABLE_GUEST_POST or \
(current_user.is_authenticated and current_user.can_post_in_forum(f))):
if form.validate_on_submit() and \
(V5Config.ENABLE_GUEST_POST or current_user.is_authenticated):
# Manage author
if current_user.is_authenticated:
author = current_user
@ -76,8 +70,8 @@ def forum_topic(f, page):
if page == -1:
page = (t.thread.comments.count() - 1) // Thread.COMMENTS_PER_PAGE + 1
comments = t.thread.comments.order_by(Comment.date_created.asc()) \
.paginate(page, Thread.COMMENTS_PER_PAGE, True)
comments = t.thread.comments.paginate(page,
Thread.COMMENTS_PER_PAGE, True)
# Anti-necropost
last_com = t.thread.comments.order_by(desc(Comment.date_modified)).first()

View File

@ -8,6 +8,7 @@ from flask import redirect, url_for, abort, request
from flask_login import login_required, current_user
@app.route('/post/<int:postid>', methods=['GET','POST'])
# TODO: Allow guest edit of posts
@login_required
def edit_post(postid):
# TODO: Maybe not safe
@ -16,8 +17,8 @@ def edit_post(postid):
p = Post.query.filter_by(id=postid).first_or_404()
# Check permissions. TODO: Allow guests to edit their posts
if current_user.is_anonymous or not current_user.can_edit_post(p):
# TODO: Check whether privileged user has access to board
if p.author != current_user and not current_user.priv("edit-posts"):
abort(403)
if p.type == "comment":
@ -43,7 +44,8 @@ def edit_post(postid):
def delete_post(postid):
p = Post.query.filter_by(id=postid).first_or_404()
if current_user.is_anonymous or not current_user.can_delete_post(p):
# TODO: Check whether privileged user has access to board
if p.author != current_user and not current_user.priv("delete-posts"):
abort(403)
for a in p.attachments:

View File

@ -13,7 +13,7 @@
{% if current_user.is_authenticated %}
{% if current_user == member %}
<div><a href="{{ url_for('edit_account') }}">Modifier le compte</a></div>
{% elif current_user.priv('edit.accounts') %}
{% elif current_user.priv('access-admin-panel') %}
<div><a href="{{ url_for('adm_edit_account', user_id=member.id) }}">Modifier le compte</a></div>
{% endif %}
{% endif %}

View File

@ -1,7 +1,7 @@
<footer>
<p>Planète Casio est un site communautaire non affilié à CASIO. Toute reproduction de Planète Casio, même partielle, est interdite.</p>
<p>Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou des copyrights.</p>
{% if current_user.is_authenticated and current_user.priv('misc.dev-infos') %}
{% if current_user.is_authenticated and current_user.priv('footer-statistics') %}
<p>Page générée en {{ g.request_time() }}</p>
{% endif %}
<p>Ceci est un environnement de test. Tout contenu peut être supprimé sans avertissement préalable.</p>

View File

@ -26,7 +26,7 @@
<path fill="#ffffff" d="M12,15.39L8.24,17.66L9.23,13.38L5.91,10.5L10.29,10.13L12,6.09L13.71,10.13L18.09,10.5L14.77,13.38L15.76,17.66M22,9.24L14.81,8.63L12,2L9.19,8.63L2,9.24L7.45,13.97L5.82,21L12,17.27L18.18,21L16.54,13.97L22,9.24Z"></path>
</svg>Topics favoris
</a>
{% if current_user.priv('misc.admin-panel') %}
{% if current_user.priv('access-admin-panel') %}
<a href="{{ url_for('adm') }}">
<svg viewBox="0 0 24 24">
<path fill="#ffffff" d="M3,1H19A1,1 0 0,1 20,2V6A1,1 0 0,1 19,7H3A1,1 0 0,1 2,6V2A1,1 0 0,1 3,1M3,9H19A1,1 0 0,1 20,10V10.67L17.5,9.56L11,12.44V15H3A1,1 0 0,1 2,14V10A1,1 0 0,1 3,9M3,17H11C11.06,19.25 12,21.4 13.46,23H3A1,1 0 0,1 2,22V18A1,1 0 0,1 3,17M8,5H9V3H8V5M8,13H9V11H8V13M8,21H9V19H8V21M4,3V5H6V3H4M4,11V13H6V11H4M4,19V21H6V19H4M17.5,12L22,14V17C22,19.78 20.08,22.37 17.5,23C14.92,22.37 13,19.78 13,17V14L17.5,12M17.5,13.94L15,15.06V17.72C15,19.26 16.07,20.7 17.5,21.06V13.94Z"></path>

View File

@ -10,10 +10,7 @@
<hr>
{% for f in main_forum.sub_forums %}
{% if f.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(f)) %}
<a href="{{ url_for('forum_page', f=f) }}">{{ f.name }}</a>
{% endif %}
<a href="{{ url_for('forum_page', f=f) }}">{{ f.name }}</a>
{% endfor %}
<hr>

View File

@ -48,8 +48,10 @@
</table>
{% endif %}
{% if (V5Config.ENABLE_GUEST_POST and f.is_default_postable())
or (current_user.is_authenticated and current_user.can_post_in_forum(f)) %}
{% if (current_user.is_authenticated and current_user.priv('write-anywhere'))
or ("/actus" in f.url and current_user.is_authenticated and current_user.priv('write-news'))
or ("/actus" not in f.url and not f.sub_forums)
and (current_user.is_authenticated or V5Config.ENABLE_GUEST_POST) %}
<div class=form>
<h2>Créer un nouveau sujet</h2>
<form action="" method="post" enctype="multipart/form-data">

View File

@ -9,9 +9,9 @@
<p>
Bienvenue sur le forum de Planète Casio ! Vous pouvez créer des
nouveaux sujets ou poster des réponses avec un compte
{%- if not current_user.is_authenticated %}
{% if not current_user.is_authenticated %}
ou en postant en tant qu'invité
{%- endif -%}
{% endif %}
.
</p>
@ -20,28 +20,22 @@
{% else %}
{% for l1 in main_forum.sub_forums %}
{% if l1.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(l1)) %}
<table class=forumlist>
<tr><th>{{ l1.name }}</th><th>Nombre de sujets</th></tr>
<table class=forumlist>
<tr><th>{{ l1.name }}</th><th>Nombre de sujets</th></tr>
{% if l1.sub_forums == [] %}
<tr><td><a href='/forum{{ l1.url }}'>{{ l1.name }}</a></td>
<td>{{ l1.topics.count() }}</td></tr>
<tr><td>{{ l1.descr }}</td><td></td></tr>
{% endif %}
{% if l1.sub_forums == [] %}
<tr><td><a href='/forum{{ l1.url }}'>{{ l1.name }}</a></td>
<td>{{ l1.topics.count() }}</td></tr>
<tr><td>{{ l1.descr }}</td><td></td></tr>
{% endif %}
{% for l2 in l1.sub_forums %}
{% if l2.is_default_accessible() or
(current_user.is_authenticated and current_user.can_access_forum(l2)) %}
<tr><td><a href='/forum{{ l2.url }}'>{{ l2.name }}</td>
<td>{{ l2.topics.count() }}</td></tr>
<tr><td>{{ l2.descr }}</td><td></td></tr>
{% endif %}
{% endfor %}
{% for l2 in l1.sub_forums %}
<tr><td><a href='/forum{{ l2.url }}'>{{ l2.name }}</td>
<td>{{ l2.topics.count() }}</td></tr>
<tr><td>{{ l2.descr }}</td><td></td></tr>
{% endfor %}
</table>
{% endif %}
</table>
{% endfor %}
{% endif %}

View File

@ -1,6 +1,6 @@
{% extends "base/base.html" %}
{% import "widgets/editor.html" as widget_editor %}
{% import "widgets/thread.html" as widget_thread with context %}
{% import "widgets/thread.html" as widget_thread %}
{% import "widgets/user.html" as widget_user %}
{% import "widgets/pagination.html" as widget_pagination with context %}
@ -25,8 +25,7 @@
</div>
{% endif %}
{% if V5Config.ENABLE_GUEST_POST
or (current_user.is_authenticated and current_user.can_post_in_forum(t.forum)) %}
{% if current_user.is_authenticated or V5Config.ENABLE_GUEST_POST %}
<div class=form>
<h3>Commenter le sujet</h3>
<form action="" method="post" enctype="multipart/form-data">
@ -49,7 +48,7 @@
<div>{{ form.submit(class_='bg-ok') }}</div>
</form>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -13,14 +13,9 @@
{% if c.date_created != c.date_modified %}
<div>Modifié le {{ c.date_modified|date }}</div>
{% endif %}
<div><a href="{{ request.path }}#{{ c.id }}">Permalien</a></div>
{# TODO: Let guests edit their posts #}
{% if current_user.is_authenticated and current_user.can_edit_post(c) %}
<div><a href="{{ url_for('edit_post', postid=c.id, r=request.path) }}">Modifier</a></div>
{% endif %}
{% if current_user.is_authenticated and current_user.can_delete_post(c) %}
<div><a href="{{ url_for('delete_post', postid=c.id, csrf_token=csrf_token()) }}" onclick="return confirm('Le message sera supprimé')">Supprimer</a></div>
{% endif %}
<div><a href="{{ request.path }}#{{ c.id }}">Permalink</a></div>
<div><a href="{{ url_for('edit_post', postid=c.id, r=request.path) }}">Modifier</a></div>
<div><a href="{{ url_for('delete_post', postid=c.id, csrf_token=csrf_token()) }}" onclick="return confirm('Le message sera supprimé')">Supprimer</a></div>
</div>
{{ c.text|md }}

View File

@ -8,7 +8,7 @@ markdown_tags = [
"img",
"a",
"sub", "sup",
"table", "thead", "tbody", "tr", "th", "td",
"table", "tbody", "tr", "th", "td",
"form", "fieldset", "input", "textarea",
"label", "progress"
]
@ -16,8 +16,8 @@ markdown_tags = [
markdown_attrs = {
"*": ["id", "class"],
"img": ["src", "alt", "title"],
"a": ["href", "alt", "title", "rel"],
"form": ["action", "method", "enctype"],
"a": ["href", "alt", "title"],
"form": ["action", "method"],
"input": ["id", "name", "type", "value"],
"label": ["for"],
"progress": ["value", "min", "max"],

View File

@ -8,9 +8,7 @@ from bleach import clean
from app.utils.bleach_allowlist import markdown_tags, markdown_attrs
from app.utils.markdown_extensions.pclinks import PCLinkExtension
from app.utils.markdown_extensions.hardbreaks import HardBreakExtension
from app.utils.markdown_extensions.escape_html import EscapeHtmlExtension
from app.utils.markdown_extensions.linkify import LinkifyExtension
from app.utils.markdown_extensions.escape_html import EscapeHtml
@app.template_filter('md')
@ -27,10 +25,8 @@ def md(text):
'sane_lists',
'tables',
CodeHiliteExtension(linenums=True, use_pygments=True),
EscapeHtmlExtension(),
EscapeHtml(),
FootnoteExtension(UNIQUE_IDS=True),
HardBreakExtension(),
LinkifyExtension(),
TocExtension(baselevel=2),
PCLinkExtension(),
]

View File

@ -1,7 +1,7 @@
from markdown.extensions import Extension
class EscapeHtmlExtension(Extension):
class EscapeHtml(Extension):
def extendMarkdown(self, md):
md.preprocessors.deregister('html_block')
md.inlinePatterns.deregister('html')

View File

@ -1,9 +0,0 @@
from markdown.extensions import Extension
from markdown.inlinepatterns import SubstituteTagPattern
class HardBreakExtension(Extension):
def extendMarkdown(self, md):
BREAK_RE = r' *\\\\\n'
breakPattern = SubstituteTagPattern(BREAK_RE, 'br')
md.inlinePatterns.register(breakPattern, 'hardbreak', 185)

View File

@ -1,56 +0,0 @@
# The MIT License (MIT)
#
# Copyright (c) 2013 Raitis Stengrevics
# https://github.com/daGrevis/mdx_linkify
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from bleach.linkifier import Linker
from markdown.postprocessors import Postprocessor
from markdown.extensions import Extension
class LinkifyExtension(Extension):
def __init__(self, **kwargs):
self.config = {
'linker_options': [{}, 'Options for bleach.linkifier.Linker'],
}
super(LinkifyExtension, self).__init__(**kwargs)
def extendMarkdown(self, md):
md.postprocessors.register(
LinkifyPostprocessor(
md,
self.getConfig('linker_options'),
),
"linkify",
50,
)
class LinkifyPostprocessor(Postprocessor):
def __init__(self, md, linker_options):
super(LinkifyPostprocessor, self).__init__(md)
linker_options.setdefault("skip_tags", ["code"])
self._linker_options = linker_options
def run(self, text):
linker = Linker(**self._linker_options)
return linker.linkify(text)

View File

@ -20,7 +20,6 @@ from app.models.poll import Poll
from app.models.topic import Topic
from app.models.user import Member
class PCLinkExtension(Extension):
def __init__(self, **kwargs):
self.config = {
@ -37,7 +36,7 @@ class PCLinkExtension(Extension):
PCLINK_RE = r'\[\[([a-z]+): ?(\w+)\]\]'
pclinkPattern = PCLinksInlineProcessor(PCLINK_RE, self.getConfigs())
pclinkPattern.md = md
md.inlinePatterns.register(pclinkPattern, 'pclink', 135)
md.inlinePatterns.register(pclinkPattern, 'pclink', 75)
class PCLinksInlineProcessor(InlineProcessor):

View File

@ -13,7 +13,7 @@ def priv_required(*perms):
Example:
@app.route('/admin')
@priv_required('forum.access.admin')
@priv_required('access-admin-board')
def admin_board():
pass

View File

@ -13,7 +13,7 @@ def optional(form, files):
def count(form, files):
if current_user.is_authenticated:
if current_user.priv("misc.no-upload-limits"):
if current_user.priv("no-upload-limits"):
return
if len(files.data) > 100: # 100 files for a authenticated user
raise ValidationError("100 fichiers maximum autorisés")
@ -48,7 +48,7 @@ def size(form, files):
"""There is no global limit to file sizes"""
size = sum([filesize(f) for f in files.data])
if current_user.is_authenticated:
if current_user.priv("misc.no-upload-limits"):
if current_user.priv("no-upload-limits"):
return
if size > 5e6: # 5 Mo per comment for an authenticated user
raise ValidationError("Fichiers trop lourds (max 5Mo)")

42
assets/privs.txt Normal file
View File

@ -0,0 +1,42 @@
# Privileges
Read/write access to forum boards:
access-admin-board Administration board of the forum
access-assoc-board CreativeCalc discussion board
write-news Post articles on the news board
Shared file upload (like /Fr/adpc/img.php for any file):
upload-shared-files Upload files and images on the website server
delete-shared-files Delete files uploaded with upload-shared-files
Post management:
edit-posts Edit any post on the website
delete-posts Remove any post from the website
scheduled-posting Schedule a post or content creation in the future
Content (topic, progs, tutos, etc) management:
delete-content Delete whole topics, program pages, or tutorials
move-public-content Change the section of a page in a public section
move-private-content Change the section of a page in a private section
showcase-content Manage stocky content (post-its)
edit-static-content Edit static content pages
extract-posts Move out-of-topic posts to a new places
Program evaluation:
delete-notes Delete program notes
delete-tests Delete program tests
Shoutbox:
shoutbox-post Write messages in the shoutbox
shoutbox-kick Kick people using the shoutbox
shoutbox-ban Ban people using the shoutbox
Miscellaenous:
unlimited-pms Removes the limit on the number of private messages
footer-statistics View performance statistics in the page footer
community-login Automatically login as a community account
Administration panel:
access-admin-panel Administration panel of website
edit-account Edit details of any account
delete-account Remove member accounts

View File

@ -148,8 +148,6 @@ def update_groups():
print(f"[group] Created {g.name}")
db.session.commit()
def create_common_accounts():
# Clean up common accounts