This repository has been archived on 2022-11-26. You can view files and clone it, but cannot push or open issues or pull requests.
Episodes.Community/Discussions/views.py

383 lines
14 KiB
Python

# Episodes.Community - Community-Driven TV Show Episode Link Sharing Site
# Copyright (C) 2017 Evert "Diamond" Prants <evert@lunasqu.ee>, Taizo "Tsa6" Simpson <taizo@tsa6.net>
#
# 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 <https://www.gnu.org/licenses/>.
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
class Boards(TemplateView):
template_name = "boards.html"
def get_context_data(self, abbr, **kwargs):
ctx = super().get_context_data()
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()
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'] = show.url() + '/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
}
# 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.<br>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(show.url() + '/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
}
# 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.<br>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(show.url() + '/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
# Get the reply from the database
reply = get_object_or_404(DiscussionReply, id=replyid)
showurl = reply.board.show.url()
# Prevent voting for own reply
if reply.user == user:
return HttpResponse('<h1>Error</h1><p>You cannot vote for your own reply.</p><p>'
'<a href="%s/discuss/board/%d-%s">Return to board</a></p>'
% (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.<br>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.<br>Reason: %s'%(bans.first().reason))
# Request context
ctx = {
'form': form,
'show': show,
'reply': reply
}
url = '%s/discuss/board/%d-%s?findReply=%d'%(show.url(), 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'%(show.url(), 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)
if not user.has_perm('LandingPage.can_moderate_board', board.show) and not board.user == user:
return HttpResponse('<h1>Error</h1><p>You do not have permission to lock this show.</p><p>'
'<a href="%s/discuss/board/%d-%s">Return to board</a></p>'
% (board.show.url(), 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'%(board.show.url(), 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)
pin = not board.pinned
DiscussionBoard.objects.filter(pk=board.pk).update(pinned=pin)
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(board.show.url(), 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' % (board.show.url()))
@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)
delete = not reply.deleted
DiscussionReply.objects.filter(pk=reply.pk).update(deleted=delete)
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(reply.show.url(), reply.board.pk, slugify(reply.board.title)))