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

284 lines
10 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-02-28 16:34:10 +00:00
from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban
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()
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')
)
),
).order_by('-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
ctx['url'] = '/show/%s/discuss/board/%d-%s?page=%d#reply-%d'%(abbr, 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/%s/discuss/board/%d-%s'%(abbr, 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)
new_reply = form.save(commit=False)
new_reply.user = user
new_reply.board = board
new_reply.save()
return HttpResponseRedirect('/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, 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)
# 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="/show/%s/discuss/board/%d-%s">Return to board</a></p>'
% (abbr, 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('/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, reply.board.pk, slugify(reply.board.title), reply.pk))