Merge pull request #1006 from ae-utbm/pedagogy-perms

Improve pedagogy permissions
This commit is contained in:
thomas girod 2025-01-17 18:08:31 +01:00 committed by GitHub
commit 17cf0c67b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 219 additions and 215 deletions

View File

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

View File

@ -37,8 +37,11 @@ Example:
```
"""
import operator
from functools import reduce
from typing import Any
from django.contrib.auth.models import Permission
from django.http import HttpRequest
from ninja_extra import ControllerBase
from ninja_extra.permissions import BasePermission
@ -56,6 +59,46 @@ class IsInGroup(BasePermission):
return request.user.is_in_group(pk=self._group_pk)
class HasPerm(BasePermission):
"""Check that the user has the required perm.
If multiple perms are given, a comparer function can also be passed,
in order to change the way perms are checked.
Example:
```python
# this route will require both permissions
@route.put("/foo", permissions=[HasPerm(["foo.change_foo", "foo.add_foo"])]
def foo(self): ...
# This route will require at least one of the perm,
# but it's not mandatory to have all of them
@route.put(
"/bar",
permissions=[HasPerm(["foo.change_bar", "foo.add_bar"], op=operator.or_)],
)
def bar(self): ...
"""
def __init__(
self, perms: str | Permission | list[str | Permission], op=operator.and_
):
"""
Args:
perms: a permission or a list of permissions the user must have
op: An operator to combine multiple permissions (in most cases,
it will be either `operator.and_` or `operator.or_`)
"""
super().__init__()
if not isinstance(perms, (list, tuple, set)):
perms = [perms]
self._operator = op
self._perms = perms
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
return reduce(self._operator, (request.user.has_perm(p) for p in self._perms))
class IsRoot(BasePermission):
"""Check that the user is root."""

View File

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

View File

@ -28,8 +28,7 @@ from django.http import (
HttpResponseServerError,
)
from django.shortcuts import render
from django.utils.functional import cached_property
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import FormView
from sentry_sdk import last_event_id
@ -54,17 +53,12 @@ def internal_servor_error(request):
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."""
def get_object(self):
"""Get current group from id in url."""
return self.cached_object
@cached_property
def cached_object(self):
"""Optimisation on group retrieval."""
return super().get_object()
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
# 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 counter.models import Refilling, Selling
from counter.views.student_card import StudentCardFormView
from eboutic.models import Invoice
from subscription.models import Subscription
from trombi.views import UserTrombiForm
@ -566,6 +565,8 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
if not hasattr(self.object, "trombi_user"):
kwargs["trombi_form"] = UserTrombiForm()
if hasattr(self.object, "customer"):
from counter.views.student_card import StudentCardFormView
kwargs["student_card_fragment"] = StudentCardFormView.get_template_data(
self.object.customer
).render(self.request)

View File

@ -1,13 +1,13 @@
import operator
from typing import Annotated
from annotated_types import Ge
from django.conf import settings
from ninja import Query
from ninja_extra import ControllerBase, api_controller, paginate, route
from ninja_extra.exceptions import NotFound
from ninja_extra.pagination import PageNumberPaginationExtra, PaginatedResponseSchema
from core.auth.api_permissions import IsInGroup, IsRoot, IsSubscriber
from core.auth.api_permissions import HasPerm
from pedagogy.models import UV
from pedagogy.schemas import SimpleUvSchema, UvFilterSchema, UvSchema
from pedagogy.utbm_api import find_uv
@ -17,7 +17,11 @@ from pedagogy.utbm_api import find_uv
class UvController(ControllerBase):
@route.get(
"/{year}/{code}",
permissions=[IsRoot | IsInGroup(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)],
permissions=[
# this route will almost always be called in the context
# of a UV creation/edition
HasPerm(["pedagogy.add_uv", "pedagogy.change_uv"], op=operator.or_)
],
url_name="fetch_uv_from_utbm",
response=UvSchema,
)
@ -34,8 +38,8 @@ class UvController(ControllerBase):
"",
response=PaginatedResponseSchema[SimpleUvSchema],
url_name="fetch_uvs",
permissions=[IsSubscriber | IsInGroup(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID)],
permissions=[HasPerm("pedagogy.view_uv")],
)
@paginate(PageNumberPaginationExtra, page_size=100)
def fetch_uv_list(self, search: Query[UvFilterSchema]):
return search.filter(UV.objects.all())
return search.filter(UV.objects.values())

View File

@ -20,10 +20,12 @@
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from typing import Self
from django.conf import settings
from django.core import validators
from django.db import models
from django.db.models import Exists, OuterRef
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property
@ -145,14 +147,6 @@ class UV(models.Model):
def get_absolute_url(self):
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):
comments = self.comments.filter(**{field + "__gte": 0})
if not comments.exists():
@ -191,6 +185,22 @@ class UV(models.Model):
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):
"""A comment about an UV."""
@ -243,6 +253,8 @@ class UVComment(models.Model):
)
publish_date = models.DateTimeField(_("publish date"), blank=True)
objects = UVCommentQuerySet.as_manager()
def __str__(self):
return f"{self.uv} - {self.author}"
@ -251,15 +263,6 @@ class UVComment(models.Model):
self.publish_date = timezone.now()
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
# to use this model.
@ -323,7 +326,3 @@ class UVCommentReport(models.Model):
@cached_property
def uv(self):
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 %}
{% block content %}
{% if can_create_uv %}
{% if user.has_perm("pedagogy.add_uv") %}
<div class="action-bar">
<p>
<a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a>
@ -94,8 +94,10 @@
<td>{% trans %}Credit type{% endtrans %}</td>
<td><i class="fa fa-leaf"></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>
{%- endif -%}
{%- if user.has_perm("pedagogy.delete_uv") -%}
<td>{% trans %}Delete{% endtrans %}</td>
{% endif %}
</tr>
@ -109,8 +111,10 @@
<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('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>
{%- endif -%}
{%- if user.has_perm("pedagogy.delete_uv") -%}
<td><a :href="`/pedagogy/uv/${uv.id}/delete`">{% trans %}Delete{% endtrans %}</a></td>
{%- endif -%}
</tr>

View File

@ -89,7 +89,7 @@
<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>
</div>
{% else %}
{% elif user.has_perm("pedagogy.add_uvcomment") %}
<div id="leave_comment">
<h2>{% trans %}Leave comment{% endtrans %}</h2>
<div>
@ -146,9 +146,9 @@
{% endif %}
<br>
{% if object.comments.exists() %}
{% if comments %}
<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 class="grade-block">
@ -183,16 +183,28 @@
</p>
{% endif %}
{% if user.is_owner(comment) %}
{% if comment.author_id == user.id or user.has_perm("pedagogy.change_comment") %}
<p class="actions">
<a href="{{ url('pedagogy:comment_update', comment_id=comment.id) }}">{% trans %}Edit{% endtrans %}</a>
<a href="{{ url('pedagogy:comment_delete', comment_id=comment.id) }}">{% trans %}Delete{% endtrans %}</a>
<a href="{{ url('pedagogy:comment_update', comment_id=comment.id) }}">
{% 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>
{% endif %}
</div>
<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>
@ -209,7 +221,7 @@
<script type="text/javascript">
$("#return_noscript").hide();
$("#return_js").show();
var icons = {
const icons = {
header: "fa fa-toggle-right",
activeHeader: "fa fa-toggle-down"
};

View File

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

View File

@ -22,8 +22,7 @@
#
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Exists, OuterRef
from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy
@ -35,7 +34,7 @@ from django.views.generic import (
UpdateView,
)
from core.auth.mixins import CanEditPropMixin, CanViewMixin, FormerSubscriberMixin
from core.auth.mixins import PermissionOrAuthorRequiredMixin
from core.models import Notification, User
from core.views import DetailFormView
from pedagogy.forms import (
@ -47,7 +46,7 @@ from pedagogy.forms import (
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.
Allow to comment the UV.
@ -57,11 +56,21 @@ class UVDetailFormView(CanViewMixin, DetailFormView):
pk_url_kwarg = "uv_id"
template_name = "pedagogy/uv_detail.jinja"
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):
kwargs = super().get_form_kwargs()
kwargs["author_id"] = self.request.user.id
kwargs["uv_id"] = self.get_object().id
kwargs["uv_id"] = self.object.id
kwargs["is_creation"] = True
return kwargs
@ -69,66 +78,61 @@ class UVDetailFormView(CanViewMixin, DetailFormView):
form.save()
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):
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)
"comments": list(
self.object.comments.viewable_by(self.request.user)
.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."""
model = UVComment
form_class = UVCommentForm
pk_url_kwarg = "comment_id"
template_name = "core/edit.jinja"
permission_required = "pedagogy.change_uvcomment"
author_field = "author"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
obj = self.get_object()
kwargs["author_id"] = obj.author.id
kwargs["uv_id"] = obj.uv.id
kwargs["author_id"] = self.object.author_id
kwargs["uv_id"] = self.object.uv_id
kwargs["is_creation"] = False
return kwargs
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."""
model = UVComment
pk_url_kwarg = "comment_id"
template_name = "core/delete_confirm.jinja"
permission_required = "pedagogy.delete_uvcomment"
author_field = "author"
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."""
template_name = "pedagogy/guide.jinja"
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)
)
}
permission_required = "pedagogy.view_uv"
class UVCommentReportCreateView(PermissionRequiredMixin, CreateView):
@ -168,21 +172,16 @@ class UVCommentReportCreateView(PermissionRequiredMixin, CreateView):
return resp
def get_success_url(self):
return reverse_lazy(
"pedagogy:uv_detail", kwargs={"uv_id": self.uv_comment.uv.id}
)
return reverse("pedagogy:uv_detail", kwargs={"uv_id": self.uv_comment.uv_id})
class UVModerationFormView(FormView):
class UVModerationFormView(PermissionRequiredMixin, FormView):
"""Moderation interface (Privileged)."""
form_class = UVCommentModerationForm
template_name = "pedagogy/moderation.jinja"
def dispatch(self, request, *args, **kwargs):
if not request.user.is_owner(UV()):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
permission_required = "pedagogy.delete_uvcomment"
success_url = reverse_lazy("pedagogy:moderation")
def form_valid(self, form):
form_clean = form.clean()
@ -194,9 +193,6 @@ class UVModerationFormView(FormView):
UVCommentReport.objects.filter(id__in={d.id for d in denied}).delete()
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy("pedagogy:moderation")
class UVCreateView(PermissionRequiredMixin, CreateView):
"""Add a new UV (Privileged)."""
@ -211,34 +207,28 @@ class UVCreateView(PermissionRequiredMixin, CreateView):
kwargs["author_id"] = self.request.user.id
return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})
class UVDeleteView(CanEditPropMixin, DeleteView):
class UVDeleteView(PermissionRequiredMixin, DeleteView):
"""Allow to delete an UV (Privileged)."""
model = UV
pk_url_kwarg = "uv_id"
template_name = "core/delete_confirm.jinja"
def get_success_url(self):
return reverse_lazy("pedagogy:guide")
permission_required = "pedagogy.delete_uv"
success_url = reverse_lazy("pedagogy:guide")
class UVUpdateView(CanEditPropMixin, UpdateView):
class UVUpdateView(PermissionRequiredMixin, UpdateView):
"""Allow to edit an UV (Privilegied)."""
model = UV
form_class = UVForm
pk_url_kwarg = "uv_id"
template_name = "pedagogy/uv_edit.jinja"
permission_required = "pedagogy.change_uv"
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
obj = self.get_object()
kwargs["author_id"] = obj.author.id
kwargs["author_id"] = obj.author_id
return kwargs
def get_success_url(self):
return reverse_lazy("pedagogy:uv_detail", kwargs={"uv_id": self.object.id})