__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)" __license__ = "AGPL v3" from django.contrib import messages from django.contrib.auth.models import Group from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.http import Http404 from django.shortcuts import get_object_or_404, redirect from django.urls import reverse, reverse_lazy from django.utils import timezone from django.views.generic.detail import DetailView from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.views.generic.list import ListView from guardian.mixins import PermissionRequiredMixin from guardian.shortcuts import (assign_perm, remove_perm, get_objects_for_user, get_perms, get_users_with_perms, get_groups_with_perms) from .models import Forum, Post from .forms import (ForumForm, ForumGroupPermissionsForm, ForumOrganizationPermissionsForm, PostForm, PostFormHidden) from scipost.mixins import PermissionsMixin class ForumCreateView(PermissionsMixin, CreateView): permission_required = 'forums.add_forum' model = Forum form_class = ForumForm template_name = 'forums/forum_form.html' success_url = reverse_lazy('forums:forums') def get_initial(self): initial = super().get_initial() parent_model = self.kwargs.get('parent_model') parent_content_type = None parent_object_id = self.kwargs.get('parent_id') if parent_model == 'forum': parent_content_type = ContentType.objects.get(app_label='forums', model='forum') initial.update({ 'moderators': self.request.user, 'parent_content_type': parent_content_type, 'parent_object_id': parent_object_id, }) return initial class ForumDeleteView(PermissionRequiredMixin, DeleteView): permission_required = 'forums.delete_forum' model = Forum success_url = reverse_lazy('forums:forums') def delete(self, request, *args, **kwargs): """ A Forum can only be deleted if it does not have any descendants. Upon deletion, all object-level permissions associated to the Forum are explicitly removed, to avoid orphaned permissions. """ forum = get_object_or_404(Forum, slug=self.kwargs.get('slug')) groups_perms_dict = get_groups_with_perms(forum, attach_perms=True) if forum.child_forums.all().count() > 0: messages.warning(request, 'A Forum with descendants cannot be deleted.') return redirect(forum.get_absolute_url()) for group, perms_list in groups_perms_dict.items(): for perm in perms_list: remove_perm(perm, group, forum) return super().delete(request, *args, **kwargs) class ForumDetailView(PermissionRequiredMixin, DetailView): permission_required = 'forums.can_view_forum' model = Forum template_name = 'forums/forum_detail.html' def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) context['groups_with_perms'] = get_groups_with_perms(self.object).order_by('name') context['users_with_perms'] = get_users_with_perms(self.object) context['group_permissions_form'] = ForumGroupPermissionsForm() context['organization_permissions_form'] = ForumOrganizationPermissionsForm() return context class ForumPermissionsView(PermissionRequiredMixin, UpdateView): permission_required = 'forums.can_change_forum' model = Forum form_class = ForumGroupPermissionsForm template_name = 'forums/forum_permissions.html' def get_context_data(self, *args, **kwargs): context = super().get_context_data(*args, **kwargs) try: context['group'] = Group.objects.get(pk=self.kwargs.get('group_id')) except Group.DoesNotExist: pass return context def get_initial(self, *args, **kwargs): initial = super().get_initial(*args, **kwargs) try: group = Group.objects.get(pk=self.kwargs.get('group_id')) perms = get_perms (group, self.object) initial['group'] = group.id initial['can_view'] = 'can_view_forum' in perms initial['can_post'] = 'can_post_to_forum' in perms except Group.DoesNotExist: pass return initial def form_valid(self, form): if form.cleaned_data['can_view']: assign_perm('can_view_forum', form.cleaned_data['group'], self.object) else: remove_perm('can_view_forum', form.cleaned_data['group'], self.object) if form.cleaned_data['can_post']: assign_perm('can_post_to_forum', form.cleaned_data['group'], self.object) else: remove_perm('can_post_to_forum', form.cleaned_data['group'], self.object) return super().form_valid(form) class ForumListView(LoginRequiredMixin, ListView): model = Forum template_name = 'forum_list.html' def get_queryset(self): queryset = get_objects_for_user(self.request.user, 'forums.can_view_forum').anchors() return queryset class PostCreateView(UserPassesTestMixin, CreateView): model = Post form_class= PostForm def test_func(self): if self.request.user.has_perm('forums.add_forum'): return True forum = get_object_or_404(Forum, slug=self.kwargs.get('slug')) if self.request.user.has_perm('can_post_to_forum', forum): return True else: raise PermissionDenied def get_initial(self, *args, **kwargs): initial = super().get_initial(*args, **kwargs) parent_model = self.kwargs.get('parent_model') parent_object_id = self.kwargs.get('parent_id') subject = '' if parent_model == 'forum': parent_content_type = ContentType.objects.get(app_label='forums', model='forum') elif parent_model == 'post': parent_content_type = ContentType.objects.get(app_label='forums', model='post') parent = parent_content_type.get_object_for_this_type(pk=parent_object_id) subject = 'Reply to %s' % parent.subject else: raise Http404 initial.update({ 'posted_by': self.request.user, 'posted_on': timezone.now(), 'parent_content_type': parent_content_type, 'parent_object_id': parent_object_id, 'subject': subject, }) return initial def form_valid(self, form): """ Save the form data to session variables only, redirect to confirmation view. """ self.request.session['post_subject'] = form.cleaned_data['subject'] self.request.session['post_text'] = form.cleaned_data['text'] return redirect(reverse('forums:post_confirm_create', kwargs={'slug': self.kwargs.get('slug'), 'parent_model': self.kwargs.get('parent_model'), 'parent_id': self.kwargs.get('parent_id')})) class PostConfirmCreateView(UserPassesTestMixin, CreateView): form_class = PostFormHidden template_name = 'forums/post_confirm_create.html' def test_func(self): if self.request.user.has_perm('forums.add_forum'): return True forum = get_object_or_404(Forum, slug=self.kwargs.get('slug')) if self.request.user.has_perm('can_post_to_forum', forum): return True else: raise PermissionDenied def get_initial(self, *args, **kwargs): initial = super().get_initial(*args, **kwargs) parent_model = self.kwargs.get('parent_model') parent_object_id = self.kwargs.get('parent_id') if parent_model == 'forum': parent_content_type = ContentType.objects.get(app_label='forums', model='forum') elif parent_model == 'post': parent_content_type = ContentType.objects.get(app_label='forums', model='post') parent = parent_content_type.get_object_for_this_type(pk=parent_object_id) else: raise Http404 initial.update({ 'posted_by': self.request.user, 'posted_on': timezone.now(), 'parent_content_type': parent_content_type, 'parent_object_id': parent_object_id, 'subject': self.request.session['post_subject'], 'text': self.request.session['post_text'], }) return initial