123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- from django.contrib import messages
- from django.contrib.auth import get_user_model
- from django.db.transaction import atomic
- from django.http import Http404, JsonResponse
- from django.shortcuts import get_object_or_404, redirect, render
- from django.utils.translation import ugettext as _, ungettext
- from misago.acl import add_acl
- from misago.core.exceptions import AjaxError
- from misago.forums.models import Forum
- from misago.threads import participants
- from misago.threads.events import record_event
- from misago.threads.forms.posting import ThreadParticipantsForm
- from misago.threads.models import Thread, ThreadParticipant
- from misago.threads.permissions import (allow_use_private_threads,
- allow_see_private_thread,
- allow_see_private_post,
- exclude_invisible_private_threads)
- from misago.threads.views import generic
- def private_threads_view(klass):
- """
- decorator for making views check allow_use_private_threads
- """
- def decorator(f):
- def dispatch(self, request, *args, **kwargs):
- allow_use_private_threads(request.user)
- return f(self, request, *args, **kwargs)
- return dispatch
- klass.dispatch = decorator(klass.dispatch)
- return klass
- class PrivateThreadsMixin(object):
- """
- Mixin is used to make views use different permission tests
- """
- def get_forum(self, request, lock=False, **kwargs):
- forum = Forum.objects.private_threads()
- add_acl(request.user, forum)
- return forum
- def check_forum_permissions(self, request, forum):
- add_acl(request.user, forum)
- allow_use_private_threads(request.user)
- def fetch_thread(self, request, lock=False, select_related=None,
- queryset=None, **kwargs):
- queryset = queryset or Thread.objects
- if lock:
- queryset = queryset.select_for_update()
- select_related = select_related or []
- if not 'forum' in select_related:
- select_related.append('forum')
- queryset = queryset.select_related(*select_related)
- where = {'id': kwargs.get('thread_id')}
- thread = get_object_or_404(queryset, **where)
- if thread.forum.special_role != 'private_threads':
- raise Http404()
- return thread
- def check_thread_permissions(self, request, thread):
- add_acl(request.user, thread.forum)
- add_acl(request.user, thread)
- participants.make_thread_participants_aware(request.user, thread)
- allow_see_private_thread(request.user, thread)
- allow_use_private_threads(request.user)
- def check_post_permissions(self, request, post):
- add_acl(request.user, post.forum)
- add_acl(request.user, post.thread)
- add_acl(request.user, post)
- participants.make_thread_participants_aware(request.user, thread)
- allow_see_private_post(request.user, post)
- allow_see_private_thread(request.user, post.thread)
- allow_use_private_threads(request.user)
- def exclude_invisible_posts(self, queryset, user, forum, thread):
- return queryset
- class PrivateThreads(generic.Threads):
- def get_queryset(self):
- threads_qs = Forum.objects.private_threads().thread_set
- return exclude_invisible_private_threads(threads_qs, self.user)
- class PrivateThreadsFiltering(generic.ThreadsFiltering):
- def get_available_filters(self):
- filters = super(PrivateThreadsFiltering, self).get_available_filters()
- if self.user.acl['can_moderate_private_threads']:
- filters.append({
- 'type': 'reported',
- 'name': _("With reported posts"),
- 'is_label': False,
- })
- return filters
- @private_threads_view
- class PrivateThreadsView(generic.ThreadsView):
- link_name = 'misago:private_threads'
- template = 'misago/privatethreads/list.html'
- Threads = PrivateThreads
- Filtering = PrivateThreadsFiltering
- class PrivateThreadActions(generic.ThreadActions):
- def get_available_actions(self, kwargs):
- user = kwargs['user']
- thread = kwargs['thread']
- is_moderator = user.acl['can_moderate_private_threads']
- if thread.participant and thread.participant.is_owner:
- is_owner = True
- else:
- is_owner = False
- actions = []
- if is_moderator and not is_owner:
- actions.append({
- 'action': 'takeover',
- 'icon': 'level-up',
- 'name': _("Takeover thread")
- })
- if is_owner:
- actions.append({
- 'action': 'participants',
- 'icon': 'users',
- 'name': _("Edit participants"),
- 'is_button': True
- })
- if is_moderator:
- if thread.is_closed:
- actions.append({
- 'action': 'open',
- 'icon': 'unlock-alt',
- 'name': _("Open thread")
- })
- else:
- actions.append({
- 'action': 'close',
- 'icon': 'lock',
- 'name': _("Close thread")
- })
- actions.append({
- 'action': 'delete',
- 'icon': 'times',
- 'name': _("Delete thread"),
- 'confirmation': _("Are you sure you want to delete this "
- "thread? This action can't be undone.")
- })
- return actions
- def action_takeover(self, request, thread):
- participants.set_thread_owner(thread, request.user)
- messages.success(request, _("You are now owner of this thread."))
- @private_threads_view
- class ThreadView(PrivateThreadsMixin, generic.ThreadView):
- template = 'misago/privatethreads/thread.html'
- ThreadActions = PrivateThreadActions
- @private_threads_view
- class ThreadParticipantsView(PrivateThreadsMixin, generic.ViewBase):
- template = 'misago/privatethreads/participants.html'
- def dispatch(self, request, *args, **kwargs):
- thread = self.get_thread(request, **kwargs)
- if not request.is_ajax():
- response = render(request, 'misago/errorpages/wrong_way.html')
- response.status_code = 405
- return response
- participants_qs = thread.threadparticipant_set
- participants_qs = participants_qs.select_related('user', 'user__rank')
- return self.render(request, {
- 'forum': thread.forum,
- 'thread': thread,
- 'participants': participants_qs.order_by('-is_owner', 'user__slug')
- })
- @private_threads_view
- class EditThreadParticipantsView(ThreadParticipantsView):
- template = 'misago/privatethreads/participants_modal.html'
- @private_threads_view
- class BaseEditThreadParticipantView(PrivateThreadsMixin, generic.ViewBase):
- @atomic
- def dispatch(self, request, *args, **kwargs):
- thread = self.get_thread(request, lock=True, **kwargs)
- if not request.is_ajax():
- response = render(request, 'misago/errorpages/wrong_way.html')
- response.status_code = 405
- return response
- if not request.method == "POST":
- raise AjaxError(_("Wrong action received."))
- if not thread.participant or not thread.participant.is_owner:
- raise AjaxError(_("Only thread owner can add or "
- "remove participants from thread."))
- return self.action(request, thread, kwargs)
- def action(self, request, thread, kwargs):
- raise NotImplementedError("views extending EditThreadParticipantView "
- "need to define custom action method")
- @private_threads_view
- class AddThreadParticipantsView(BaseEditThreadParticipantView):
- template = 'misago/privatethreads/participants_modal_list.html'
- def action(self, request, thread, kwargs):
- form = ThreadParticipantsForm(request.POST, user=request.user)
- if not form.is_valid():
- errors = []
- for field_errors in form.errors.as_data().values():
- errors.extend([unicode(e[0]) for e in field_errors])
- return JsonResponse({'message': errors[0], 'is_error': True})
- event_message = _("%(user)s added %(participant)s to this thread.")
- participants_list = [p.user for p in thread.participants_list]
- for user in form.users_cache:
- if user not in participants_list:
- participants.add_participant(request, thread, user)
- record_event(request.user, thread, 'user', event_message, {
- 'user': request.user,
- 'participant': user
- })
- thread.save(update_fields=['has_events'])
- participants_qs = thread.threadparticipant_set
- participants_qs = participants_qs.select_related('user', 'user__rank')
- participants_qs = participants_qs.order_by('-is_owner', 'user__slug')
- participants_list = [p for p in participants_qs]
- participants_list_html = self.render(request, {
- 'forum': thread.forum,
- 'thread': thread,
- 'participants': participants_list,
- }).content
- message = ungettext("%(users)s participant",
- "%(users)s participants",
- len(participants_list))
- message = message % {'users': len(participants_list)}
- return JsonResponse({
- 'is_error': False,
- 'message': message,
- 'list_html': participants_list_html
- })
- @private_threads_view
- class RemoveThreadParticipantView(BaseEditThreadParticipantView):
- def action(self, request, thread, kwargs):
- user_qs = thread.threadparticipant_set.select_related('user')
- try:
- participant = user_qs.get(user_id=kwargs['user_id'])
- except ThreadParticipant.DoesNotExist:
- return JsonResponse({
- 'message': _("Requested participant couldn't be found."),
- 'is_error': True,
- })
- if participant.user == request.user:
- return JsonResponse({
- 'message': _('To leave thread use "Leave thread" option.'),
- 'is_error': True,
- })
- participants_count = len(thread.participants_list) - 1
- if participants_count == 0:
- return JsonResponse({
- 'message': _("You can't remove last thread participant."),
- 'is_error': True,
- })
- participants.remove_participant(thread, participant.user)
- if not participants.thread_has_participants(thread):
- thread.delete()
- else:
- message = _("%(user)s removed %(participant)s from this thread.")
- record_event(request.user, thread, 'user', message, {
- 'user': request.user,
- 'participant': participant.user
- })
- thread.save(update_fields=['has_events'])
- participants_count = len(thread.participants_list) - 1
- message = ungettext("%(users)s participant",
- "%(users)s participants",
- participants_count)
- message = message % {'users': participants_count}
- return JsonResponse({'is_error': False, 'message': message})
- @private_threads_view
- class LeaveThreadView(BaseEditThreadParticipantView):
- @atomic
- def dispatch(self, request, *args, **kwargs):
- thread = self.get_thread(request, lock=True, **kwargs)
- try:
- if not request.method == "POST":
- raise RuntimeError(_("Wrong action received."))
- if not thread.participant:
- raise RuntimeError(_("You have to be thread participant in "
- "order to be able to leave thread."))
- user_qs = thread.threadparticipant_set.select_related('user')
- try:
- participant = user_qs.get(user_id=request.user.id)
- except ThreadParticipant.DoesNotExist:
- raise RuntimeError(_("You need to be thread "
- "participant to leave it."))
- except RuntimeError as e:
- messages.error(request, unicode(e))
- return redirect(thread.get_absolute_url())
- participants.remove_participant(thread, request.user)
- if not thread.threadparticipant_set.exists():
- thread.delete()
- elif thread.participant.is_owner:
- new_owner = user_qs.order_by('id')[:1][0].user
- participants.set_thread_owner(thread, new_owner)
- message = _("%(user)s left this thread. "
- "%(new_owner)s is now thread owner.")
- record_event(request.user, thread, 'user', message, {
- 'user': request.user,
- 'new_owner': new_owner
- })
- thread.save(update_fields=['has_events'])
- else:
- message = _("%(user)s left this thread.")
- record_event(request.user, thread, 'user', message, {
- 'user': request.user,
- })
- thread.save(update_fields=['has_events'])
- message = _('You have left "%(thread)s" thread.')
- message = message % {'thread': thread.title}
- messages.info(request, message)
- return redirect('misago:private_threads')
- """
- Generics
- """
- @private_threads_view
- class GotoLastView(PrivateThreadsMixin, generic.GotoLastView):
- pass
- @private_threads_view
- class GotoNewView(PrivateThreadsMixin, generic.GotoNewView):
- pass
- @private_threads_view
- class GotoPostView(PrivateThreadsMixin, generic.GotoPostView):
- pass
- @private_threads_view
- class ReportedPostsListView(PrivateThreadsMixin, generic.ReportedPostsListView):
- pass
- @private_threads_view
- class QuotePostView(PrivateThreadsMixin, generic.QuotePostView):
- pass
- @private_threads_view
- class UnhidePostView(PrivateThreadsMixin, generic.UnhidePostView):
- pass
- @private_threads_view
- class HidePostView(PrivateThreadsMixin, generic.HidePostView):
- pass
- @private_threads_view
- class DeletePostView(PrivateThreadsMixin, generic.DeletePostView):
- pass
- @private_threads_view
- class EventsView(PrivateThreadsMixin, generic.EventsView):
- pass
- @private_threads_view
- class PostingView(PrivateThreadsMixin, generic.PostingView):
- pass
|