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

384 lines
14 KiB
Python
Raw Normal View History

2017-12-28 10:39:02 +00:00
# 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/>.
2018-02-27 22:06:41 +00:00
from django.template import RequestContext
2018-02-28 16:34:10 +00:00
from django.shortcuts import render, redirect, get_list_or_404, get_object_or_404
2018-02-27 22:06:41 +00:00
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
2018-02-28 16:34:10 +00:00
from django.db.models import Case, When, Value, IntegerField, Count, F, Q, Max
2018-02-27 22:06:41 +00:00
from django.contrib.auth.mixins import LoginRequiredMixin
2017-08-25 18:03:37 +00:00
from django.shortcuts import render
2018-02-28 16:34:10 +00:00
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.utils.text import slugify
2018-02-27 22:06:41 +00:00
from guardian.decorators import permission_required_or_403
2018-03-01 13:48:20 +00:00
from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban, Report
2018-02-28 16:34:10 +00:00
from . import forms
import datetime
import re
2018-02-27 22:06:41 +00:00
class Boards(TemplateView):
template_name = "boards.html"
def get_context_data(self, abbr, **kwargs):
ctx = super().get_context_data()
2018-03-01 17:22:34 +00:00
2018-02-27 22:06:41 +00:00
show = get_object_or_404(Show, abbr=abbr)
page = self.request.GET.get('page', 1)
2018-02-28 16:34:10 +00:00
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')
)
),
2018-03-01 17:22:34 +00:00
).order_by('-pinned','-recency')
2018-02-27 22:06:41 +00:00
paginator = Paginator(boards_list, getattr(settings, "DISCUSSIONS_PER_PAGE", 26))
2018-02-28 16:34:10 +00:00
2018-02-27 22:06:41 +00:00
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
2017-08-25 18:03:37 +00:00
2018-02-27 22:06:41 +00:00
return ctx
2018-02-28 16:34:10 +00:00
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
2018-03-02 13:46:25 +00:00
ctx['url'] = show.url() + '/discuss/board/%d-%s?page=%d#reply-%d'%(board.pk, slugify(board.title), page, index)
2018-02-28 16:34:10 +00:00
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,
2018-03-02 14:36:01 +00:00
'show': show
2018-02-28 16:34:10 +00:00
}
# 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()
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s'%(new_board.pk, slugify(form_data['title'])))
2018-02-28 16:34:10 +00:00
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,
2018-03-02 13:46:25 +00:00
'show': show
2018-02-28 16:34:10 +00:00
}
# 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)
2018-02-28 20:04:15 +00:00
print(form_data['body'])
2018-02-28 16:34:10 +00:00
new_reply = form.save(commit=False)
new_reply.user = user
new_reply.board = board
new_reply.save()
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s?findReply=%d'%(board.pk, slugify(board.title), new_reply.pk))
2018-02-28 16:34:10 +00:00
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)
2018-03-02 13:46:25 +00:00
showurl = reply.board.show.url()
2018-02-28 16:34:10 +00:00
# Prevent voting for own reply
if reply.user == user:
return HttpResponse('<h1>Error</h1><p>You cannot vote for your own reply.</p><p>'
2018-03-01 17:22:34 +00:00
'<a href="%s/discuss/board/%d-%s">Return to board</a></p>'
% (showurl, reply.board.pk, slugify(reply.board.title)), status=400)
2018-02-28 16:34:10 +00:00
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()
2018-03-01 17:22:34 +00:00
return HttpResponseRedirect('%s/discuss/board/%d-%s?findReply=%d'%(showurl, reply.board.pk, slugify(reply.board.title), reply.pk))
2018-03-01 13:48:20 +00:00
@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,
2018-03-02 13:46:25 +00:00
'reply': reply
2018-03-01 13:48:20 +00:00
}
2018-03-02 13:46:25 +00:00
url = '%s/discuss/board/%d-%s?findReply=%d'%(show.url(), reply.board.pk, slugify(reply.board.title), reply.pk)
2018-03-01 13:48:20 +00:00
# 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
2018-03-03 09:39:56 +00:00
new_report.show = show
2018-03-01 13:48:20 +00:00
new_report.save()
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(show.url(), reply.board.pk, slugify(reply.board.title)))
2018-03-01 13:48:20 +00:00
else:
ctx['error'] = 'Invalid fields!'
return render(req, "report_reply.html", ctx)
2018-03-01 17:22:34 +00:00
@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>'
2018-03-02 13:46:25 +00:00
% (board.show.url(), board.pk, slugify(board.title)), status=400)
2018-03-01 17:22:34 +00:00
lock = not board.locked
DiscussionBoard.objects.filter(pk=board.pk).update(locked=lock)
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(board.show.url(), board.pk, slugify(board.title)))
2018-03-01 17:22:34 +00:00
@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)
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(board.show.url(), board.pk, slugify(board.title)))
2018-03-01 17:22:34 +00:00
@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()
2018-03-02 13:46:25 +00:00
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)
2018-03-02 13:46:25 +00:00
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(reply.show.url(), reply.board.pk, slugify(reply.board.title)))