# Episodes.Community - Community-Driven TV Show Episode Link Sharing Site # Copyright (C) 2017 Evert "Diamond" Prants , Taizo "Tsa6" Simpson # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from django.template import RequestContext from django.shortcuts import render, redirect, get_list_or_404, get_object_or_404 from django.views import View from django.views.generic.base import TemplateView from django.contrib.auth.decorators import login_required from django.conf import settings from django.http import Http404, HttpResponseForbidden, HttpResponse, HttpResponseRedirect from django.db.models import Case, When, Value, IntegerField, Count, F, Q, Max from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage from django.utils.text import slugify from guardian.decorators import permission_required_or_403 from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban, Report from . import forms import datetime import re # Append common values to context def get_show_url(abbr): return '/show/%s' % (abbr) class Boards(TemplateView): template_name = "boards.html" def get_context_data(self, abbr, **kwargs): ctx = super().get_context_data() ctx['showurl'] = get_show_url(abbr) show = get_object_or_404(Show, abbr=abbr) page = self.request.GET.get('page', 1) boards_list = DiscussionBoard.objects.filter(show=show).annotate( num_replies=Count('replies'), recency=Case( When( num_replies=0, then=Max('timestamp') ), When( num_replies__gt=0, then=Max('replies__timestamp') ) ), ).order_by('-pinned','-recency') paginator = Paginator(boards_list, getattr(settings, "DISCUSSIONS_PER_PAGE", 26)) try: boards = paginator.page(page) except PageNotAnInteger: boards = paginator.page(1) except EmptyPage: boards = paginator.page(paginator.num_pages) ctx['boards'] = boards ctx['show'] = show return ctx class Board(TemplateView): template_name = "board.html" def get_context_data(self, abbr, bid, **kwargs): ctx = super().get_context_data() ctx['showurl'] = get_show_url(abbr) show = get_object_or_404(Show, abbr=abbr) board = get_object_or_404(DiscussionBoard, pk=bid) page = self.request.GET.get('page', 1) find = self.request.GET.get('findReply', None) reply_list = DiscussionReply.objects.filter(board=board).order_by('timestamp').annotate( positives=Count( Case( When( votes__positive=True, then=Value(1) ) ) ), negatives=Count('votes') - F('positives'), score=F('positives') - F('negatives') ) perpage = getattr(settings, "DISCUSSIONS_REPLIES_PER_PAGE", 10) paginator = Paginator(reply_list, perpage) if find and find.isnumeric(): item = get_object_or_404(DiscussionReply, pk=find) if item.board == board: found = DiscussionReply.objects.filter(timestamp__lt=item.timestamp,board=board).count() page = int(found / perpage) + 1 index = int(found % perpage) + 1 ctx['url'] = ctx['showurl'] + '/discuss/board/%d-%s?page=%d#reply-%d'%(board.pk, slugify(board.title), page, index) return ctx try: replies = paginator.page(page) except PageNotAnInteger: replies = paginator.page(1) except EmptyPage: replies = paginator.page(paginator.num_pages) ctx['board'] = board ctx['replies'] = replies ctx['show'] = show ctx['form'] = forms.ReplyForm() return ctx def render_to_response(self, context): if 'url' in context: return redirect(context['url']) return super(Board, self).render_to_response(context) # Board form GET and POST @login_required def BoardForm(req, abbr): show = get_object_or_404(Show, abbr=abbr) user = req.user form = forms.BoardForm() # Request context ctx = { 'form': form, 'show': show, 'showurl': get_show_url(abbr) } # Get bans for this user regarding this show bans = Ban.objects.filter(Q(scope=show) | Q(site_wide=True), Q(expiration__gte=datetime.datetime.now()) | Q(permanent=True), user=user) if bans.count() > 0: return HttpResponseForbidden('You are banned from discussing this show.
Reason: %s'%(bans.first().reason)) # Handle POST if req.method == 'POST': form = forms.BoardForm(req.POST) ctx['form'] = form if form.is_valid(): form_data = form.cleaned_data # Check if the Title has already been posted if DiscussionBoard.objects.filter(show=show,title=form_data['title']).count() > 0: ctx['error'] = 'A board with this title already exists!' return render(req, "boards_new.html", ctx) if not user.has_perm('LandingPage.can_moderate_board', show): # Check if there has been a board by this user for this show within the last 24 hours if DiscussionBoard.objects.filter(user=user,show=show,timestamp__gte=datetime.datetime.now() - datetime.timedelta(hours=24)).count() > 8: ctx['error'] = 'You can only create 8 boards for a show in 24 hours!' return render(req, "boards_new.html", ctx) new_board = form.save(commit=False) new_board.user = user new_board.show = show new_board.save() new_post = DiscussionReply(user=user,board=new_board,body=form_data['body']) new_post.save() return HttpResponseRedirect(ctx['showurl'] + '/discuss/board/%d-%s'%(new_board.pk, slugify(form_data['title']))) else: ctx['error'] = 'Invalid fields!' return render(req, "boards_new.html", ctx) # Reply form GET and POST @login_required def BoardReplyForm(req, abbr, bid): show = get_object_or_404(Show, abbr=abbr) board = get_object_or_404(DiscussionBoard, pk=bid) user = req.user form = forms.ReplyForm() # Request context ctx = { 'form': form, 'board': board, 'show': show, 'showurl': get_show_url(abbr) } # Get bans for this user regarding this show bans = Ban.objects.filter(Q(scope=show) | Q(site_wide=True), Q(expiration__gte=datetime.datetime.now()) | Q(permanent=True), user=user) if bans.count() > 0: return HttpResponseForbidden('You are banned from discussing this show.
Reason: %s'%(bans.first().reason)) # Handle POST if req.method == 'POST': form = forms.ReplyForm(req.POST) ctx['form'] = form if form.is_valid(): form_data = form.cleaned_data # Body Content Filter real_content = re.sub(r'[\s\W]+', '', form_data['body']) err_res = False if len(real_content) < 10: ctx['error'] = 'The content is too small! Please write more meaningful replies.' err_res = True elif len(real_content) > 4000: ctx['error'] = 'The content body is too large! Please write less in a single reply.' err_res = True # TODO: Apply word filtering here # TODO: Apply markdown if err_res: return render(req, "board_reply.html", ctx) print(form_data['body']) new_reply = form.save(commit=False) new_reply.user = user new_reply.board = board new_reply.save() return HttpResponseRedirect(ctx['showurl'] + '/discuss/board/%d-%s?findReply=%d'%(board.pk, slugify(board.title), new_reply.pk)) else: ctx['error'] = 'Invalid fields!' return render(req, "board_reply.html", ctx) # Vote request # /show/{{abbr}}/vote/{{submission id}}/{{positive == 1}} class BoardVoteSubmit(LoginRequiredMixin, View): def post (self, req, abbr, replyid, positive): # Convert positive parameter into a boolean pos_bool = int(positive) == 1 user = req.user showurl = get_show_url(abbr) # Get the reply from the database reply = get_object_or_404(DiscussionReply, id=replyid) # Prevent voting for own reply if reply.user == user: return HttpResponse('

Error

You cannot vote for your own reply.

' 'Return to board

' % (showurl, reply.board.pk, slugify(reply.board.title)), status=400) show = reply.board.show # Get bans for this user regarding this show bans = Ban.objects.filter(Q(scope=show) | Q(site_wide=True), Q(expiration__gte=datetime.datetime.now()) | Q(permanent=True), user=user) if bans.count() > 0: return HttpResponseForbidden('You are banned from voting on this show\'s discussion boards.
Reason: %s'%(bans.first().reason)) # Allow changing a vote from positive to negative or vice-versa. Delete vote if its a re-vote vote = reply.votes.filter(user=user,reply=reply).first() if vote: if not vote.positive == pos_bool: vote.positive = pos_bool vote.save() else: vote.delete() else: new_vote = DiscussionVote( user=user, reply=reply, positive=pos_bool ) new_vote.save() return HttpResponseRedirect('%s/discuss/board/%d-%s?findReply=%d'%(showurl, reply.board.pk, slugify(reply.board.title), reply.pk)) @login_required def ReportForm(req, abbr, rid): show = get_object_or_404(Show, abbr=abbr) reply = get_object_or_404(DiscussionReply, pk=rid,board__show=show) user = req.user form = forms.ReportForm() # Get bans for this user regarding this show bans = Ban.objects.filter(Q(expiration__gte=datetime.datetime.now()) | Q(permanent=True), user=user, site_wide=True) if bans.count() > 0: return HttpResponseForbidden('You are banned from the site and therefor not allowed to create reports.
Reason: %s'%(bans.first().reason)) # Request context ctx = { 'form': form, 'show': show, 'reply': reply, 'showurl': get_show_url(abbr) } url = '%s/discuss/board/%d-%s?findReply=%d'%(ctx['showurl'], reply.board.pk, slugify(reply.board.title), reply.pk) # Handle POST if req.method == 'POST': form = forms.ReportForm(req.POST) ctx['form'] = form if form.is_valid(): form_data = form.cleaned_data if not user.has_perm('LandingPage.can_moderate_board', show): # Check if there have been many reports by this user within the last 12 hours if Report.objects.filter(user=user,timestamp__gte=datetime.datetime.now() - datetime.timedelta(hours=12)).count() > 5: ctx['error'] = 'You\'ve created too many reports recently!' return render(req, "report_reply.html", ctx) if Report.objects.filter(url=url).count() > 1: ctx['error'] = 'This reply has already been brought to our attention! Thank you for reporting.' return render(req, "report_reply.html", ctx) # Save new_report = form.save(commit=False) new_report.reporter = user new_report.url = url new_report.save() return HttpResponseRedirect('%s/discuss/board/%d-%s'%(ctx['showurl'], reply.board.pk, slugify(reply.board.title))) else: ctx['error'] = 'Invalid fields!' return render(req, "report_reply.html", ctx) @login_required def BoardLock(req, abbr, bid): user = req.user board = get_object_or_404(DiscussionBoard, pk=bid) showurl = get_show_url(abbr) if not user.has_perm('LandingPage.can_moderate_board', board.show) and not board.user == user: return HttpResponse('

Error

You do not have permission to lock this show.

' 'Return to board

' % (showurl, board.pk, slugify(board.title)), status=400) lock = not board.locked DiscussionBoard.objects.filter(pk=board.pk).update(locked=lock) return HttpResponseRedirect('%s/discuss/board/%d-%s'%(showurl, board.pk, slugify(board.title))) @permission_required_or_403('LandingPage.can_moderate_board', (Show, 'abbr', 'abbr'), accept_global_perms=True) def BoardPin(req, abbr, bid): board = get_object_or_404(DiscussionBoard, pk=bid) showurl = get_show_url(abbr) pin = not board.pinned DiscussionBoard.objects.filter(pk=board.pk).update(pinned=pin) return HttpResponseRedirect('%s/discuss/board/%d-%s'%(showurl, board.pk, slugify(board.title))) @permission_required_or_403('LandingPage.can_moderate_board', (Show, 'abbr', 'abbr'), accept_global_perms=True) def BoardDelete(req, abbr, bid): board = get_object_or_404(DiscussionBoard, pk=bid) showurl = get_show_url(abbr) DiscussionBoard.objects.filter(pk=board.pk).delete() return HttpResponseRedirect('%s/discuss' % (showurl)) @permission_required_or_403('LandingPage.can_moderate_board', (Show, 'abbr', 'abbr'), accept_global_perms=True) def BoardDeleteReply(req, abbr, rid): reply = get_object_or_404(DiscussionReply, pk=rid) showurl = get_show_url(abbr) delete = not reply.deleted DiscussionReply.objects.filter(pk=reply.pk).update(deleted=delete) return HttpResponseRedirect('%s/discuss/board/%d-%s'%(showurl, reply.board.pk, slugify(reply.board.title)))