Skip to content

Commit fba2a1a

Browse files
Moderation part 2: drop course-scoped moderation + moderation tree + explanation texts everywhere (#382)
1 parent 07ee27e commit fba2a1a

38 files changed

Lines changed: 950 additions & 317 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
This page tries to contain all use facing changes made on DocHub.
44

5+
# Unreleased
6+
7+
Moderation:
8+
* moderators can now edit any document
9+
* moderators can now approve new moderators
10+
* admins can now add and remove moderators
11+
* add a moderation tree view showing who promoted whom
12+
* add a public moderation transparency log
13+
514
# 2026.4.0
615

716
* Add a institutional disclaimer and contribution call on the login page

catalog/templates/catalog/course.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ <h4 class="alert-heading">
246246
mais il se pourrait que certains documents soient mal rangés.
247247
<br>
248248
Si tu ne trouves plus des documents des années précédentes,
249-
<a href="/catalog/f/archives/">regarde dans les archives</a> !
249+
<a href="{% url 'catalog:finder' slugs='archives' %}">regarde dans les archives</a> !
250250
</div>
251251
<div>
252252
<h3>Il n’y a encore rien dans ce cours…</h3>

catalog/templates/catalog/finder.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h4 class="alert-heading">
3333
</h4>
3434
Le programme de cours de l'ULB a parfois beaucoup changé au cours des années.
3535
Si tu ne trouves plus des documents des années précédentes,
36-
<a href="/catalog/f/archives/">regarde dans les archives</a> !
36+
<a href="{% url 'catalog:finder' slugs='archives' %}">regarde dans les archives</a> !
3737
</div>
3838
{% endif %}
3939
<div class="d-flex gap-2">

documents/models.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,15 +154,6 @@ def add_to_queue(self) -> None:
154154
def get_absolute_url(self) -> str:
155155
return reverse("document_show", args=(self.id,))
156156

157-
def write_perm(self, user, moderated_courses) -> bool:
158-
if user.id == self.user_id:
159-
return True
160-
161-
if self.course_id in moderated_courses:
162-
return True
163-
164-
return False
165-
166157
def tag_from_name(self) -> None:
167158
tags = logic.tags_from_name(self.name)
168159
self.tags.add(*tags)

documents/templates/documents/document_edit.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ <h5 class="card-header">Modération</h5>
6767
<input type="input" class="form-control" id="added" value="{{ doc.user }}" disabled>
6868
</div>
6969
-->
70+
{% if request.user != doc.user and request.user|has_moderation_perm_on:doc %}
71+
<p class="text-muted mt-3 mb-2">
72+
⚠️ Tu modifies ici un document qui ne t'appartient pas. En tant que modérateur·trice,
73+
tu peux corriger, masquer ou mettre en avant des documents pour garder DocHub propre et utile,
74+
et ces actions sont enregistrées publiquement dans le
75+
<a href="{% url 'public_logs' %}">journal de modération</a>.
76+
</p>
77+
{% endif %}
7078
<div class="mt-3">
7179
<input type="submit" class="btn btn-success" name="update" value="Mettre à jour"/>
7280
{% if doc.hidden %}

documents/views.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ def document_edit(request, pk):
125125
reverse("catalog:course_show", args=[doc.course.slug])
126126
)
127127

128+
old_name = doc.name
129+
old_description = doc.description
130+
old_tags = list(doc.tags.all())
131+
128132
form = DocumentForm(request.POST, instance=doc)
129133

130134
if form.is_valid():
@@ -133,12 +137,12 @@ def document_edit(request, pk):
133137
user=request.user,
134138
content_object=doc,
135139
values={
136-
"name": (doc.name, form.cleaned_data["name"]),
140+
"name": (old_name, form.cleaned_data["name"]),
137141
"description": (
138-
doc.description,
142+
old_description,
139143
form.cleaned_data["description"],
140144
),
141-
"tags": (doc.tags.all(), form.cleaned_data["tags"]),
145+
"tags": (old_tags, form.cleaned_data["tags"]),
142146
},
143147
)
144148

@@ -169,7 +173,6 @@ def document_reupload(request, pk):
169173

170174
if not request.user.write_perm(obj=document):
171175
return HttpResponse("You may not edit this document.", status=403)
172-
# FIXME: log moderation action
173176

174177
if document.state != Document.DocumentState.DONE:
175178
return HttpResponse(
@@ -196,13 +199,12 @@ def document_reupload(request, pk):
196199

197200
document.reprocess(force=True)
198201

199-
# TODO Log new version upload
200-
# action.send(
201-
# request.user,
202-
# verb="a uploadé une nouvelle version de",
203-
# action_object=document,
204-
# target=document.course,
205-
# )
202+
if request.user != document.user:
203+
ModerationLog.track(
204+
user=request.user,
205+
content_object=document,
206+
values={"reupload": ("", file.name)},
207+
)
206208

207209
return HttpResponseRedirect(
208210
reverse("catalog:course_show", args=(document.course.slug,))

moderation/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ def action_text(self):
8181
return "a accepté la demande de"
8282
elif self.target_field == "action_rejeter":
8383
return "a refusé la demande de"
84+
elif self.target_field == "reupload":
85+
return "a re-uploadé"
8486
return f"a modifié '{self.target_field}' sur"
8587

8688
@property
@@ -92,6 +94,8 @@ def action_color(self):
9294
return "success"
9395
elif self.target_field == "action_rejeter":
9496
return "warning"
97+
elif self.target_field == "reupload":
98+
return "info"
9599
return "secondary"
96100

97101
@property
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
{% extends "base.html" %}
2+
3+
{% block title %}À propos de la modération{% endblock %}
4+
5+
{% block content %}
6+
<div class="container-xl mt-4" style="max-width: 800px;">
7+
<h1 id="moderation" class="mb-4">La modération sur DocHub</h1>
8+
9+
<h2 id="role">Que peuvent faire les modérateur·trices ?</h2>
10+
<p>
11+
Les modérateur·trices sont des étudiant·es bénévoles qui aident à garder DocHub propre et bien organisé.
12+
Elles et ils peuvent :
13+
</p>
14+
<ul class="mb-4">
15+
<li>Modifier le titre, la description et les tags de n'importe quel document</li>
16+
<li>Cacher ou rendre visible un document (par exemple si le contenu n'est pas approprié)</li>
17+
<li>Re-uploader une nouvelle version d'un document</li>
18+
<li>Marquer un document comme "Staff pick"</li>
19+
</ul>
20+
21+
<h2 id="transparence">Transparence</h2>
22+
<p>
23+
Toutes les actions de modération sont enregistrées publiquement.
24+
N'importe quel utilisateur connecté peut consulter le journal de modération
25+
pour voir qui a fait quoi et quand.
26+
</p>
27+
<p class="mb-4"><a href="{% url 'public_logs' %}">Consulter le journal de modération</a></p>
28+
29+
{% if request.user.is_moderator or request.user.is_staff %}
30+
<h2 id="ajouter-moderateur">Ajouter un·e modérateur·trice</h2>
31+
<p>
32+
En tant que modérateur·trice, tu peux promouvoir un·e étudiant·e en lui accordant les droits de modération depuis la liste des modérateur·trices.
33+
Attention, en ajoutant quelqu'un·e comme modérateur·trice, <b>tu en es responsable</b>, n'accorde pas ce droit à la légère.
34+
</p>
35+
<p class="mb-4"><a href="{% url 'manage_moderators' %}">Ajouter des modérateur·trices</a></p>
36+
{% else %}
37+
<h2 id="devenir-moderateur">Comment devenir modérateur·trice ?</h2>
38+
<p>
39+
Tu es délégué·e d'année, délégué·e cours de ton cercle, membre d'un bureau étudiant·e
40+
ou simplement un·e étudiant·e motivé·e ? Tu peux demander les droits de modération !
41+
</p>
42+
<p>
43+
Remplis le formulaire de demande, et un·e modérateur·trice examinera ta candidature.
44+
Si elle est acceptée, tu recevras immédiatement les droits.
45+
</p>
46+
<p class="mb-4"><a href="{% url 'representative_request' %}">Demander les droits de modération</a></p>
47+
{% endif %}
48+
49+
<h2 id="arbre">L'arbre de modération</h2>
50+
<p>
51+
Tu peux voir qui a promu qui dans l'arbre de modération.
52+
</p>
53+
<p><a href="{% url 'moderation_tree' %}">Voir l'arbre de modération</a></p>
54+
</div>
55+
{% endblock content %}

moderation/templates/moderation/home.html

Lines changed: 12 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,90 +7,20 @@
77
<h1 class="d-flex align-items-center gap-2">
88
Modération
99
</h1>
10-
<a class="btn btn-outline-primary btn-sm d-inline-flex align-items-center gap-1"
11-
href="{% url 'moderators_list' %}">
12-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16">
13-
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7Zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm-5.784 6A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216ZM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/>
14-
</svg>
15-
<span>Gérer les modérateurs</span>
16-
</a>
10+
<div class="d-flex gap-2 flex-wrap">
11+
<a class="btn btn-outline-primary btn-sm d-inline-flex align-items-center gap-1"
12+
href="{% url 'manage_moderators' %}">
13+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16">
14+
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7Zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm-5.784 6A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216ZM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z"/>
15+
</svg>
16+
<span>Gérer les modérateurs</span>
17+
</a>
18+
<a class="btn btn-outline-secondary btn-sm" href="{% url 'moderation_tree' %}">Arbre</a>
19+
<a class="btn btn-outline-secondary btn-sm" href="{% url 'public_logs' %}">Journal</a>
20+
<a class="btn btn-outline-secondary btn-sm" href="{% url 'moderation_about' %}">À propos</a>
21+
</div>
1722
</header>
1823
{% endblock header %}
1924

2025
{% block content %}
21-
<div class="container-xl mt-4">
22-
23-
{% if request.GET.error == 'reason' %}
24-
<div class="alert alert-danger alert-dismissible fade show shadow-sm" role="alert">
25-
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" viewBox="0 0 16 16">
26-
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
27-
</svg>
28-
Veuillez fournir une raison d'au moins 10 caractères pour justifier le refus.
29-
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
30-
</div>
31-
{% endif %}
32-
<div class="d-flex align-items-center justify-content-between mb-4">
33-
<h2 class="h4 mb-0">Demandes en attente ({{ pending_requests.count }})</h2>
34-
</div>
35-
36-
<div class="row">
37-
{% for req in pending_requests %}
38-
<div class="col-md-6 col-lg-4 mb-4">
39-
<div class="card shadow-sm border-0 h-100">
40-
<div class="card-body d-flex flex-column">
41-
42-
<div class="d-flex align-items-center gap-3 mb-3">
43-
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center shadow-sm" style="width: 45px; height: 45px; font-size: 1.1rem; font-weight: bold;">
44-
{{ req.user.initials|upper }}
45-
</div>
46-
<div>
47-
<h5 class="card-title h6 mb-0">{{ req.user.fullname }}</h5>
48-
<small class="text-muted">{{ req.user.netid }}</small>
49-
</div>
50-
</div>
51-
52-
<div class="mb-3">
53-
<span class="badge bg-light text-dark border border-secondary">{{ req.get_faculty_display }}</span>
54-
<span class="badge bg-light text-dark border border-secondary">{{ req.get_role_display }}</span>
55-
</div>
56-
57-
{% if req.comment %}
58-
<div class="bg-light p-3 rounded mb-3 flex-grow-1">
59-
<p class="card-text small fst-italic mb-0 text-muted">
60-
"{{ req.comment }}"
61-
</p>
62-
</div>
63-
{% else %}
64-
<div class="flex-grow-1"></div>
65-
{% endif %}
66-
67-
<p class="text-muted small mb-3">
68-
📅 Reçue le {{ req.created|date:"d/m/Y à H:i" }}
69-
</p>
70-
71-
<div class="pt-3 border-top mt-auto">
72-
<form method="post" action="{% url 'process_representative_request' req.id %}" class="w-100">
73-
{% csrf_token %}
74-
<input type="text" name="rejection_reason" class="form-control form-control-sm mb-2" placeholder="Message de refus (10 caractères min.)" minlength="10">
75-
76-
<div class="d-flex gap-2">
77-
<button type="submit" name="action" value="accept" class="btn btn-success btn-sm flex-grow-1 fw-bold">Accepter</button>
78-
<button type="submit" name="action" value="reject" class="btn btn-outline-danger btn-sm flex-grow-1" data-turbo-confirm="Es-tu sûr de vouloir refuser cette demande ?">Refuser</button>
79-
</div>
80-
</form>
81-
</div>
82-
83-
</div>
84-
</div>
85-
</div>
86-
{% empty %}
87-
<div class="col-12">
88-
<div class="alert alert-light border border-secondary text-center py-5 text-muted shadow-sm">
89-
<h5 class="mb-2">Tout est calme ! ☕</h5>
90-
<p class="mb-0">Il n'y a aucune demande d'accès modérateur en attente pour le moment.</p>
91-
</div>
92-
</div>
93-
{% endfor %}
94-
</div>
95-
</div>
9626
{% endblock content %}

0 commit comments

Comments
 (0)