Improve pedagogy permissions

This commit is contained in:
imperosol 2025-01-16 15:10:46 +01:00
parent 61170c0918
commit 0d95c3b9c9
9 changed files with 167 additions and 210 deletions

View File

@ -256,7 +256,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["request_user"] = self.request.user kwargs["request_user"] = self.request.user
kwargs["club"] = self.get_object() kwargs["club"] = self.object
kwargs["club_members"] = self.members kwargs["club_members"] = self.members
return kwargs return kwargs
@ -273,9 +273,9 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
users = data.pop("users", []) users = data.pop("users", [])
users_old = data.pop("users_old", []) users_old = data.pop("users_old", [])
for user in users: for user in users:
Membership(club=self.get_object(), user=user, **data).save() Membership(club=self.object, user=user, **data).save()
for user in users_old: for user in users_old:
membership = self.get_object().get_membership_for(user) membership = self.object.get_membership_for(user)
membership.end_date = timezone.now() membership.end_date = timezone.now()
membership.save() membership.save()
return resp return resp
@ -285,9 +285,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
return reverse_lazy( return reverse_lazy("club:club_members", kwargs={"club_id": self.object.id})
"club:club_members", kwargs={"club_id": self.get_object().id}
)
class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView): class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):

View File

@ -895,13 +895,16 @@ Welcome to the wiki page!
subscribers = Group.objects.create(name="Subscribers") subscribers = Group.objects.create(name="Subscribers")
subscribers.permissions.add( subscribers.permissions.add(
*list(perms.filter(codename__in=["add_news", "add_uvcommentreport"])) *list(perms.filter(codename__in=["add_news", "add_uvcomment"]))
) )
old_subscribers = Group.objects.create(name="Old subscribers") old_subscribers = Group.objects.create(name="Old subscribers")
old_subscribers.permissions.add( old_subscribers.permissions.add(
*list( *list(
perms.filter( perms.filter(
codename__in=[ codename__in=[
"view_uv",
"view_uvcomment",
"add_uvcommentreport",
"view_user", "view_user",
"view_picture", "view_picture",
"view_album", "view_album",
@ -973,9 +976,9 @@ Welcome to the wiki page!
) )
pedagogy_admin.permissions.add( pedagogy_admin.permissions.add(
*list( *list(
perms.filter(content_type__app_label="pedagogy").values_list( perms.filter(content_type__app_label="pedagogy")
"pk", flat=True .exclude(codename__in=["change_uvcomment"])
) .values_list("pk", flat=True)
) )
) )
self.reset_index("core", "auth") self.reset_index("core", "auth")

View File

@ -28,8 +28,7 @@ from django.http import (
HttpResponseServerError, HttpResponseServerError,
) )
from django.shortcuts import render from django.shortcuts import render
from django.utils.functional import cached_property from django.views.generic.detail import BaseDetailView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from sentry_sdk import last_event_id from sentry_sdk import last_event_id
@ -54,17 +53,12 @@ def internal_servor_error(request):
return HttpResponseServerError(render(request, "core/500.jinja")) return HttpResponseServerError(render(request, "core/500.jinja"))
class DetailFormView(SingleObjectMixin, FormView): class DetailFormView(FormView, BaseDetailView):
"""Class that allow both a detail view and a form view.""" """Class that allow both a detail view and a form view."""
def get_object(self): def post(self, request, *args, **kwargs):
"""Get current group from id in url.""" self.object = self.get_object()
return self.cached_object return super().post(request, *args, **kwargs)
@cached_property
def cached_object(self):
"""Optimisation on group retrieval."""
return super().get_object()
# F403: those star-imports would be hellish to refactor # F403: those star-imports would be hellish to refactor

View File

@ -66,7 +66,6 @@ from core.views.forms import (
) )
from core.views.mixins import QuickNotifMixin, TabedViewMixin from core.views.mixins import QuickNotifMixin, TabedViewMixin
from counter.models import Refilling, Selling from counter.models import Refilling, Selling
from counter.views.student_card import StudentCardFormView
from eboutic.models import Invoice from eboutic.models import Invoice
from subscription.models import Subscription from subscription.models import Subscription
from trombi.views import UserTrombiForm from trombi.views import UserTrombiForm
@ -566,6 +565,8 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
if not hasattr(self.object, "trombi_user"): if not hasattr(self.object, "trombi_user"):
kwargs["trombi_form"] = UserTrombiForm() kwargs["trombi_form"] = UserTrombiForm()
if hasattr(self.object, "customer"): if hasattr(self.object, "customer"):
from counter.views.student_card import StudentCardFormView
kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( kwargs["student_card_fragment"] = StudentCardFormView.get_template_data(
self.object.customer self.object.customer
).render(self.request) ).render(self.request)

View File

@ -20,10 +20,12 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from typing import Self
from django.conf import settings from django.conf import settings
from django.core import validators from django.core import validators
from django.db import models from django.db import models
from django.db.models import Exists, OuterRef
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
@ -145,14 +147,6 @@ class UV(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.id}) return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.id})
def is_owned_by(self, user):
"""Can be created by superuser, root or pedagogy admin user."""
return user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
def can_be_viewed_by(self, user):
"""Only visible by subscribers."""
return user.is_subscribed
def __grade_average_generic(self, field): def __grade_average_generic(self, field):
comments = self.comments.filter(**{field + "__gte": 0}) comments = self.comments.filter(**{field + "__gte": 0})
if not comments.exists(): if not comments.exists():
@ -191,6 +185,22 @@ class UV(models.Model):
return self.__grade_average_generic("grade_work_load") return self.__grade_average_generic("grade_work_load")
class UVCommentQuerySet(models.QuerySet):
def viewable_by(self, user: User) -> Self:
if user.has_perms(["pedagogy.view_uvcomment", "pedagogy.view_uvcommentreport"]):
# the user can view uv comment reports,
# so he can view non-moderated comments
return self
if user.has_perm("pedagogy.view_uvcomment"):
return self.filter(reports=None)
return self.filter(author=user)
def annotate_is_reported(self) -> Self:
return self.annotate(
is_reported=Exists(UVCommentReport.objects.filter(comment=OuterRef("pk")))
)
class UVComment(models.Model): class UVComment(models.Model):
"""A comment about an UV.""" """A comment about an UV."""
@ -243,6 +253,8 @@ class UVComment(models.Model):
) )
publish_date = models.DateTimeField(_("publish date"), blank=True) publish_date = models.DateTimeField(_("publish date"), blank=True)
objects = UVCommentQuerySet.as_manager()
def __str__(self): def __str__(self):
return f"{self.uv} - {self.author}" return f"{self.uv} - {self.author}"
@ -251,15 +263,6 @@ class UVComment(models.Model):
self.publish_date = timezone.now() self.publish_date = timezone.now()
super().save(*args, **kwargs) super().save(*args, **kwargs)
def is_owned_by(self, user):
"""Is owned by a pedagogy admin, a superuser or the author himself."""
return self.author == user or user.is_owner(self.uv)
@cached_property
def is_reported(self):
"""Return True if someone reported this UV."""
return self.reports.exists()
# TODO : it seems that some views were meant to be implemented # TODO : it seems that some views were meant to be implemented
# to use this model. # to use this model.
@ -323,7 +326,3 @@ class UVCommentReport(models.Model):
@cached_property @cached_property
def uv(self): def uv(self):
return self.comment.uv return self.comment.uv
def is_owned_by(self, user):
"""Can be created by a pedagogy admin, a superuser or a subscriber."""
return user.is_subscribed or user.is_owner(self.comment.uv)

View File

@ -19,7 +19,7 @@
{% endblock head %} {% endblock head %}
{% block content %} {% block content %}
{% if can_create_uv %} {% if user.has_perm("pedagogy.add_uv") %}
<div class="action-bar"> <div class="action-bar">
<p> <p>
<a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a> <a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a>
@ -94,8 +94,10 @@
<td>{% trans %}Credit type{% endtrans %}</td> <td>{% trans %}Credit type{% endtrans %}</td>
<td><i class="fa fa-leaf"></i></td> <td><i class="fa fa-leaf"></i></td>
<td><i class="fa-regular fa-sun"></i></td> <td><i class="fa-regular fa-sun"></i></td>
{% if can_create_uv %} {%- if user.has_perm("pedagogy.change_uv") -%}
<td>{% trans %}Edit{% endtrans %}</td> <td>{% trans %}Edit{% endtrans %}</td>
{%- endif -%}
{%- if user.has_perm("pedagogy.delete_uv") -%}
<td>{% trans %}Delete{% endtrans %}</td> <td>{% trans %}Delete{% endtrans %}</td>
{% endif %} {% endif %}
</tr> </tr>
@ -109,8 +111,10 @@
<td x-text="uv.credit_type"></td> <td x-text="uv.credit_type"></td>
<td><i :class="uv.semester.includes('AUTUMN') && 'fa fa-leaf'"></i></td> <td><i :class="uv.semester.includes('AUTUMN') && 'fa fa-leaf'"></i></td>
<td><i :class="uv.semester.includes('SPRING') && 'fa-regular fa-sun'"></i></td> <td><i :class="uv.semester.includes('SPRING') && 'fa-regular fa-sun'"></i></td>
{% if can_create_uv -%} {%- if user.has_perm("pedagogy.change_uv") -%}
<td><a :href="`/pedagogy/uv/${uv.id}/edit`">{% trans %}Edit{% endtrans %}</a></td> <td><a :href="`/pedagogy/uv/${uv.id}/edit`">{% trans %}Edit{% endtrans %}</a></td>
{%- endif -%}
{%- if user.has_perm("pedagogy.delete_uv") -%}
<td><a :href="`/pedagogy/uv/${uv.id}/delete`">{% trans %}Delete{% endtrans %}</a></td> <td><a :href="`/pedagogy/uv/${uv.id}/delete`">{% trans %}Delete{% endtrans %}</a></td>
{%- endif -%} {%- endif -%}
</tr> </tr>

View File

@ -89,7 +89,7 @@
<div id="leave_comment_not_allowed"> <div id="leave_comment_not_allowed">
<p>{% trans %}You already posted a comment on this UV. If you want to comment again, please modify or delete your previous comment.{% endtrans %}</p> <p>{% trans %}You already posted a comment on this UV. If you want to comment again, please modify or delete your previous comment.{% endtrans %}</p>
</div> </div>
{% else %} {% elif user.has_perm("pedagogy.add_uvcomment") %}
<div id="leave_comment"> <div id="leave_comment">
<h2>{% trans %}Leave comment{% endtrans %}</h2> <h2>{% trans %}Leave comment{% endtrans %}</h2>
<div> <div>
@ -146,9 +146,9 @@
{% endif %} {% endif %}
<br> <br>
{% if object.comments.exists() %} {% if comments %}
<h2>{% trans %}Comments{% endtrans %}</h2> <h2>{% trans %}Comments{% endtrans %}</h2>
{% for comment in object.comments.order_by("-publish_date").all() %} {% for comment in comments %}
<div id="{{ comment.id }}" class="comment-container"> <div id="{{ comment.id }}" class="comment-container">
<div class="grade-block"> <div class="grade-block">
@ -183,16 +183,28 @@
</p> </p>
{% endif %} {% endif %}
{% if user.is_owner(comment) %} {% if comment.author_id == user.id or user.has_perm("pedagogy.change_comment") %}
<p class="actions"> <p class="actions">
<a href="{{ url('pedagogy:comment_update', comment_id=comment.id) }}">{% trans %}Edit{% endtrans %}</a> <a href="{{ url('pedagogy:comment_update', comment_id=comment.id) }}">
<a href="{{ url('pedagogy:comment_delete', comment_id=comment.id) }}">{% trans %}Delete{% endtrans %}</a> {% trans %}Edit{% endtrans %}
</a>
{% endif %}
{% if comment.author_id == user.id or user.has_perm("pedagogy.delete_comment") %}
<a href="{{ url('pedagogy:comment_delete', comment_id=comment.id) }}">
{% trans %}Delete{% endtrans %}
</a>
</p> </p>
{% endif %} {% endif %}
</div> </div>
<div class="comment-end-bar"> <div class="comment-end-bar">
<div class="report"><p><a href="{{ url('pedagogy:comment_report', comment_id=comment.id) }}">{% trans %}Report this comment{% endtrans %}</a></p></div> <div class="report">
<p>
<a href="{{ url('pedagogy:comment_report', comment_id=comment.id) }}">
{% trans %}Report this comment{% endtrans %}
</a>
</p>
</div>
<div class="date"><p>{{ comment.publish_date.strftime('%d/%m/%Y') }}</p></div> <div class="date"><p>{{ comment.publish_date.strftime('%d/%m/%Y') }}</p></div>
@ -209,7 +221,7 @@
<script type="text/javascript"> <script type="text/javascript">
$("#return_noscript").hide(); $("#return_noscript").hide();
$("#return_js").show(); $("#return_js").show();
var icons = { const icons = {
header: "fa fa-toggle-right", header: "fa fa-toggle-right",
activeHeader: "fa fa-toggle-down" activeHeader: "fa fa-toggle-down"
}; };

View File

@ -20,14 +20,18 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
from typing import Callable
import pytest import pytest
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Permission
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from model_bakery import baker
from pytest_django.asserts import assertRedirects from pytest_django.asserts import assertRedirects
from core.baker_recipes import old_subscriber_user, subscriber_user
from core.models import Notification, User from core.models import Notification, User
from pedagogy.models import UV, UVComment, UVCommentReport from pedagogy.models import UV, UVComment, UVCommentReport
@ -144,17 +148,17 @@ class TestUVCreation(TestCase):
@pytest.mark.django_db @pytest.mark.django_db
@pytest.mark.parametrize( @pytest.mark.parametrize(
("username", "expected_code"), ("user_factory", "expected_code"),
[ [
("root", 200), (subscriber_user.make, 200),
("tutu", 200), (old_subscriber_user.make, 200),
("sli", 200), (lambda: baker.make(User), 403),
("old_subscriber", 200),
("public", 403),
], ],
) )
def test_guide_permissions(client: Client, username: str, expected_code: int): def test_guide_permissions(
client.force_login(User.objects.get(username=username)) client: Client, user_factory: Callable[[], User], expected_code: int
):
client.force_login(user_factory())
res = client.get(reverse("pedagogy:guide")) res = client.get(reverse("pedagogy:guide"))
assert res.status_code == expected_code assert res.status_code == expected_code
@ -190,17 +194,12 @@ class TestUVDelete(TestCase):
def test_uv_delete_pedagogy_unauthorized_fail(self): def test_uv_delete_pedagogy_unauthorized_fail(self):
# Anonymous user # Anonymous user
response = self.client.post(self.delete_uv_url) response = self.client.post(self.delete_uv_url)
assert response.status_code == 403 assertRedirects(response, reverse("core:login") + f"?next={self.delete_uv_url}")
assert UV.objects.filter(pk=self.uv.pk).exists() assert UV.objects.filter(pk=self.uv.pk).exists()
# Not subscribed user for user in baker.make(User), subscriber_user.make():
self.client.force_login(self.guy) with self.subTest():
response = self.client.post(self.delete_uv_url) self.client.force_login(user)
assert response.status_code == 403
assert UV.objects.filter(pk=self.uv.pk).exists()
# Simply subscribed user
self.client.force_login(self.sli)
response = self.client.post(self.delete_uv_url) response = self.client.post(self.delete_uv_url)
assert response.status_code == 403 assert response.status_code == 403
assert UV.objects.filter(pk=self.uv.pk).exists() assert UV.objects.filter(pk=self.uv.pk).exists()
@ -249,7 +248,7 @@ class TestUVUpdate(TestCase):
response = self.client.post( response = self.client.post(
self.update_uv_url, create_uv_template(self.bibou.id, code="PA00") self.update_uv_url, create_uv_template(self.bibou.id, code="PA00")
) )
assert response.status_code == 403 assertRedirects(response, reverse("core:login") + f"?next={self.update_uv_url}")
# Not subscribed user # Not subscribed user
self.client.force_login(self.guy) self.client.force_login(self.guy)
@ -312,7 +311,7 @@ class TestUVCommentCreationAndDisplay(TestCase):
response = self.client.post( response = self.client.post(
self.uv_url, create_uv_comment_template(self.bibou.id) self.uv_url, create_uv_comment_template(self.bibou.id)
) )
self.assertRedirects(response, self.uv_url) assertRedirects(response, self.uv_url)
response = self.client.get(self.uv_url) response = self.client.get(self.uv_url)
self.assertContains(response, text="Superbe UV") self.assertContains(response, text="Superbe UV")
@ -338,7 +337,7 @@ class TestUVCommentCreationAndDisplay(TestCase):
nb_comments = self.uv.comments.count() nb_comments = self.uv.comments.count()
# Test with anonymous user # Test with anonymous user
response = self.client.post(self.uv_url, create_uv_comment_template(0)) response = self.client.post(self.uv_url, create_uv_comment_template(0))
assert response.status_code == 403 assertRedirects(response, reverse("core:login") + f"?next={self.uv_url}")
# Test with non subscribed user # Test with non subscribed user
self.client.force_login(self.guy) self.client.force_login(self.guy)
@ -405,61 +404,34 @@ class TestUVCommentDelete(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.bibou = User.objects.get(username="root") cls.comment = baker.make(UVComment)
cls.tutu = User.objects.get(username="tutu")
cls.sli = User.objects.get(username="sli")
cls.guy = User.objects.get(username="guy")
cls.krophil = User.objects.get(username="krophil")
def setUp(self): def test_uv_comment_delete_success(self):
comment_kwargs = create_uv_comment_template( url = reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
User.objects.get(username="krophil").id for user in (
) baker.make(User, is_superuser=True),
comment_kwargs["author"] = User.objects.get(id=comment_kwargs["author"]) baker.make(
comment_kwargs["uv"] = UV.objects.get(id=comment_kwargs["uv"]) User, user_permissions=[Permission.objects.get(codename="view_uv")]
self.comment = UVComment(**comment_kwargs) ),
self.comment.save() self.comment.author,
):
def test_uv_comment_delete_root_success(self): with self.subTest():
self.client.force_login(self.bibou) self.client.force_login(user)
self.client.post( self.client.post(url)
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
assert not UVComment.objects.filter(id=self.comment.id).exists()
def test_uv_comment_delete_pedagogy_admin_success(self):
self.client.force_login(self.tutu)
self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
assert not UVComment.objects.filter(id=self.comment.id).exists()
def test_uv_comment_delete_author_success(self):
self.client.force_login(self.krophil)
self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
assert not UVComment.objects.filter(id=self.comment.id).exists() assert not UVComment.objects.filter(id=self.comment.id).exists()
def test_uv_comment_delete_unauthorized_fail(self): def test_uv_comment_delete_unauthorized_fail(self):
url = reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
# Anonymous user # Anonymous user
response = self.client.post( response = self.client.post(url)
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) assertRedirects(response, reverse("core:login") + f"?next={url}")
)
assert response.status_code == 403
# Unsbscribed user # Unsbscribed user
self.client.force_login(self.guy) for user in baker.make(User), subscriber_user.make():
response = self.client.post( with self.subTest():
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id}) self.client.force_login(user)
) response = self.client.post(url)
assert response.status_code == 403
# Subscribed user (not author of the comment)
self.client.force_login(self.sli)
response = self.client.post(
reverse("pedagogy:comment_delete", kwargs={"comment_id": self.comment.id})
)
assert response.status_code == 403 assert response.status_code == 403
# Check that the comment still exists # Check that the comment still exists
@ -499,16 +471,6 @@ class TestUVCommentUpdate(TestCase):
self.comment.refresh_from_db() self.comment.refresh_from_db()
self.assertEqual(self.comment.comment, self.comment_edit["comment"]) self.assertEqual(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_pedagogy_admin_success(self):
self.client.force_login(self.tutu)
response = self.client.post(
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
assert response.status_code == 302
self.comment.refresh_from_db()
self.assertEqual(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_author_success(self): def test_uv_comment_update_author_success(self):
self.client.force_login(self.krophil) self.client.force_login(self.krophil)
response = self.client.post( response = self.client.post(
@ -520,25 +482,18 @@ class TestUVCommentUpdate(TestCase):
self.assertEqual(self.comment.comment, self.comment_edit["comment"]) self.assertEqual(self.comment.comment, self.comment_edit["comment"])
def test_uv_comment_update_unauthorized_fail(self): def test_uv_comment_update_unauthorized_fail(self):
url = reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id})
# Anonymous user # Anonymous user
response = self.client.post( response = self.client.post(url, self.comment_edit)
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), assertRedirects(response, reverse("core:login") + f"?next={url}")
self.comment_edit,
)
assert response.status_code == 403
# Unsbscribed user # Unsbscribed user
response = self.client.post( self.client.force_login(baker.make(User))
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}), response = self.client.post(url, self.comment_edit)
self.comment_edit,
)
assert response.status_code == 403 assert response.status_code == 403
# Subscribed user (not author of the comment) # Subscribed user (not author of the comment)
response = self.client.post( response = self.client.post(url, self.comment_edit)
reverse("pedagogy:comment_update", kwargs={"comment_id": self.comment.id}),
self.comment_edit,
)
assert response.status_code == 403 assert response.status_code == 403
# Check that the comment hasn't change # Check that the comment hasn't change
@ -611,18 +566,19 @@ class TestUVModerationForm(TestCase):
assert response.status_code == 200 assert response.status_code == 200
def test_access_unauthorized_fail(self): def test_access_unauthorized_fail(self):
url = reverse("pedagogy:moderation")
# Test with anonymous user # Test with anonymous user
response = self.client.get(reverse("pedagogy:moderation")) response = self.client.get(url)
assert response.status_code == 403 assertRedirects(response, reverse("core:login") + f"?next={url}")
# Test with unsubscribed user # Test with unsubscribed user
self.client.force_login(self.guy) self.client.force_login(self.guy)
response = self.client.get(reverse("pedagogy:moderation")) response = self.client.get(url)
assert response.status_code == 403 assert response.status_code == 403
# Test with subscribed user # Test with subscribed user
self.client.force_login(self.sli) self.client.force_login(self.sli)
response = self.client.get(reverse("pedagogy:moderation")) response = self.client.get(url)
assert response.status_code == 403 assert response.status_code == 403
def test_do_nothing(self): def test_do_nothing(self):

View File

@ -22,8 +22,7 @@
# #
from django.conf import settings from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.contrib.auth.mixins import PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Exists, OuterRef from django.db.models import Exists, OuterRef
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@ -35,7 +34,7 @@ from django.views.generic import (
UpdateView, UpdateView,
) )
from core.auth.mixins import CanEditPropMixin, CanViewMixin, FormerSubscriberMixin from core.auth.mixins import PermissionOrAuthorRequiredMixin
from core.models import Notification, User from core.models import Notification, User
from core.views import DetailFormView from core.views import DetailFormView
from pedagogy.forms import ( from pedagogy.forms import (
@ -47,7 +46,7 @@ from pedagogy.forms import (
from pedagogy.models import UV, UVComment, UVCommentReport from pedagogy.models import UV, UVComment, UVCommentReport
class UVDetailFormView(CanViewMixin, DetailFormView): class UVDetailFormView(PermissionRequiredMixin, DetailFormView):
"""Display every comment of an UV and detailed infos about it. """Display every comment of an UV and detailed infos about it.
Allow to comment the UV. Allow to comment the UV.
@ -57,11 +56,21 @@ class UVDetailFormView(CanViewMixin, DetailFormView):
pk_url_kwarg = "uv_id" pk_url_kwarg = "uv_id"
template_name = "pedagogy/uv_detail.jinja" template_name = "pedagogy/uv_detail.jinja"
form_class = UVCommentForm form_class = UVCommentForm
permission_required = "pedagogy.view_uv"
def has_permission(self):
if self.request.method == "POST" and not self.request.user.has_perm(
"pedagogy.add_uvcomment"
):
# if it's a POST request, the user is trying to add a new UVComment
# thus he also needs the "add_uvcomment" permission
return False
return super().has_permission()
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
kwargs["author_id"] = self.request.user.id kwargs["author_id"] = self.request.user.id
kwargs["uv_id"] = self.get_object().id kwargs["uv_id"] = self.object.id
kwargs["is_creation"] = True kwargs["is_creation"] = True
return kwargs return kwargs
@ -69,66 +78,61 @@ class UVDetailFormView(CanViewMixin, DetailFormView):
form.save() form.save()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self):
return reverse_lazy(
"pedagogy:uv_detail", kwargs={"uv_id": self.get_object().id}
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
user = self.request.user
return super().get_context_data(**kwargs) | { return super().get_context_data(**kwargs) | {
"can_create_uv": ( "comments": list(
user.is_root self.object.comments.viewable_by(self.request.user)
or user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) .annotate_is_reported()
.select_related("author")
.order_by("-publish_date")
) )
} }
def get_success_url(self):
# once the new uv comment has been saved
# redirect to the same page we are currently
return self.request.path
class UVCommentUpdateView(CanEditPropMixin, UpdateView):
class UVCommentUpdateView(PermissionOrAuthorRequiredMixin, UpdateView):
"""Allow edit of a given comment.""" """Allow edit of a given comment."""
model = UVComment model = UVComment
form_class = UVCommentForm form_class = UVCommentForm
pk_url_kwarg = "comment_id" pk_url_kwarg = "comment_id"
template_name = "core/edit.jinja" template_name = "core/edit.jinja"
permission_required = "pedagogy.change_uvcomment"
author_field = "author"
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
obj = self.get_object() kwargs["author_id"] = self.object.author_id
kwargs["author_id"] = obj.author.id kwargs["uv_id"] = self.object.uv_id
kwargs["uv_id"] = obj.uv.id
kwargs["is_creation"] = False kwargs["is_creation"] = False
return kwargs return kwargs
def get_success_url(self): def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv.id}) return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv_id})
class UVCommentDeleteView(CanEditPropMixin, DeleteView): class UVCommentDeleteView(PermissionOrAuthorRequiredMixin, DeleteView):
"""Allow delete of a given comment.""" """Allow delete of a given comment."""
model = UVComment model = UVComment
pk_url_kwarg = "comment_id" pk_url_kwarg = "comment_id"
template_name = "core/delete_confirm.jinja" template_name = "core/delete_confirm.jinja"
permission_required = "pedagogy.delete_uvcomment"
author_field = "author"
def get_success_url(self): def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv.id}) return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.object.uv_id})
class UVGuideView(LoginRequiredMixin, FormerSubscriberMixin, TemplateView): class UVGuideView(PermissionRequiredMixin, TemplateView):
"""UV guide main page.""" """UV guide main page."""
template_name = "pedagogy/guide.jinja" template_name = "pedagogy/guide.jinja"
permission_required = "pedagogy.view_uv"
def get_context_data(self, **kwargs):
user = self.request.user
return super().get_context_data(**kwargs) | {
"can_create_uv": (
user.is_root
or user.is_in_group(pk=settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)
)
}
class UVCommentReportCreateView(PermissionRequiredMixin, CreateView): class UVCommentReportCreateView(PermissionRequiredMixin, CreateView):
@ -168,21 +172,16 @@ class UVCommentReportCreateView(PermissionRequiredMixin, CreateView):
return resp return resp
def get_success_url(self): def get_success_url(self):
return reverse_lazy( return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv_comment.uv_id})
"pedagogy:uv_detail", kwargs={"uv_id": self.uv_comment.uv.id}
)
class UVModerationFormView(FormView): class UVModerationFormView(PermissionRequiredMixin, FormView):
"""Moderation interface (Privileged).""" """Moderation interface (Privileged)."""
form_class = UVCommentModerationForm form_class = UVCommentModerationForm
template_name = "pedagogy/moderation.jinja" template_name = "pedagogy/moderation.jinja"
permission_required = "pedagogy.delete_uvcomment"
def dispatch(self, request, *args, **kwargs): success_url = reverse_lazy("pedagogy:moderation")
if not request.user.is_owner(UV()):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form): def form_valid(self, form):
form_clean = form.clean() form_clean = form.clean()
@ -194,9 +193,6 @@ class UVModerationFormView(FormView):
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete() UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("pedagogy:moderation")
class UVCreateView(PermissionRequiredMixin, CreateView): class UVCreateView(PermissionRequiredMixin, CreateView):
"""Add a new UV (Privileged).""" """Add a new UV (Privileged)."""
@ -211,34 +207,28 @@ class UVCreateView(PermissionRequiredMixin, CreateView):
kwargs["author_id"] = self.request.user.id kwargs["author_id"] = self.request.user.id
return kwargs return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})
class UVDeleteView(PermissionRequiredMixin, DeleteView):
class UVDeleteView(CanEditPropMixin, DeleteView):
"""Allow to delete an UV (Privileged).""" """Allow to delete an UV (Privileged)."""
model = UV model = UV
pk_url_kwarg = "uv_id" pk_url_kwarg = "uv_id"
template_name = "core/delete_confirm.jinja" template_name = "core/delete_confirm.jinja"
permission_required = "pedagogy.delete_uv"
def get_success_url(self): success_url = reverse_lazy("pedagogy:guide")
return reverse_lazy("pedagogy:guide")
class UVUpdateView(CanEditPropMixin, UpdateView): class UVUpdateView(PermissionRequiredMixin, UpdateView):
"""Allow to edit an UV (Privilegied).""" """Allow to edit an UV (Privilegied)."""
model = UV model = UV
form_class = UVForm form_class = UVForm
pk_url_kwarg = "uv_id" pk_url_kwarg = "uv_id"
template_name = "pedagogy/uv_edit.jinja" template_name = "pedagogy/uv_edit.jinja"
permission_required = "pedagogy.change_uv"
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super().get_form_kwargs() kwargs = super().get_form_kwargs()
obj = self.get_object() obj = self.get_object()
kwargs["author_id"] = obj.author.id kwargs["author_id"] = obj.author_id
return kwargs return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})