From 22ca7de96b9f87fd7b29dc1197e22a97e268fed9 Mon Sep 17 00:00:00 2001 From: Evert Date: Wed, 28 Feb 2018 00:06:41 +0200 Subject: [PATCH 01/10] Start with discussion boards --- Discussions/templates/boards.html | 76 +++++++++++++++++++++++++++++++ Discussions/urls.py | 24 ++++++++++ Discussions/views.py | 39 +++++++++++++++- EpisodesCommunity/settings.py | 2 + EpisodesCommunity/urls.py | 1 + 5 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 Discussions/templates/boards.html create mode 100644 Discussions/urls.py diff --git a/Discussions/templates/boards.html b/Discussions/templates/boards.html new file mode 100644 index 0000000..ff51386 --- /dev/null +++ b/Discussions/templates/boards.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} +{% block title %} + {{show.name}} Discussions - Episodes.Community +{% endblock %} +{% block content %} +
+

{{show.name}} Discussion Boards

+
+ {% if user.is_authenticated %} +  Create New Board + {% else %} + Log in to create boards + {% endif %} +
+
+
Board Name
+
Latest Reply
+
+ {% for board in boards %} +
+
+
+

{{board.title}}

+ Submitted {{board.timestamp}} by + {% if board.user.is_staff %} + + {% endif %} + {{board.user.display_name}} + +
+
+ No replies +
+
+
+ {% empty %} +

Nobody has started any discussions for this show!

+ {% endfor %} + + {% if boards.has_other_pages %} + + {% endif %} +
+{% endblock %} diff --git a/Discussions/urls.py b/Discussions/urls.py new file mode 100644 index 0000000..69472ff --- /dev/null +++ b/Discussions/urls.py @@ -0,0 +1,24 @@ +# Episodes.Community - Community-Driven TV Show Episode Link Sharing Site +# Copyright (C) 2018 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.conf.urls import url + +from . import views + +urlpatterns = [ + url(r'^$', views.Boards.as_view()), +] + diff --git a/Discussions/views.py b/Discussions/views.py index f368b15..cc4b16c 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -13,7 +13,42 @@ # # 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, 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 +from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render +from django.core.paginator import Paginator -# Create your views here. +from guardian.decorators import permission_required_or_403 + +from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote + +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) + 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 diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index a78521c..e426da0 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -171,3 +171,5 @@ AUTH_TOKEN_ENDPOINT = oauth_options.get('token_endpoint','https://icynet.eu/oaut AUTH_CLIENT_ID = oauth_options.get('client_id') AUTH_B64 = base64.b64encode(bytearray('%s:%s'%(AUTH_CLIENT_ID,oauth_options.get('client_secret')),'utf-8')).decode("utf-8") AUTH_REDIRECT_URL = oauth_options.get('redirect_url') + +DISCUSSIONS_PER_PAGE = 26 diff --git a/EpisodesCommunity/urls.py b/EpisodesCommunity/urls.py index 38fc82b..1a6d679 100644 --- a/EpisodesCommunity/urls.py +++ b/EpisodesCommunity/urls.py @@ -36,6 +36,7 @@ from django.conf.urls.static import static urlpatterns = [ url(r'^admin/', admin.site.urls), + url(r'^show/(?P\w{1,16})/discuss', include('Discussions.urls')), url(r'^show/(?P\w{1,16})/', include('Show.urls')), url(r'^', include('LandingPage.urls')) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) From 8230e83528f0b7f25990f27593d1ba3ccf3b3115 Mon Sep 17 00:00:00 2001 From: Evert Date: Wed, 28 Feb 2018 18:34:10 +0200 Subject: [PATCH 02/10] Boards, Replies and Votes --- Discussions/forms.py | 37 ++++ Discussions/templates/board.html | 113 ++++++++++++ Discussions/templates/board_reply.html | 26 +++ Discussions/templates/boards.html | 23 ++- Discussions/templates/boards_new.html | 25 +++ Discussions/urls.py | 4 + Discussions/views.py | 239 ++++++++++++++++++++++++- EpisodesCommunity/settings.py | 1 + EpisodesCommunity/urls.py | 2 +- LandingPage/models.py | 23 ++- LandingPage/static/css/style.css | 24 +++ 11 files changed, 501 insertions(+), 16 deletions(-) create mode 100644 Discussions/forms.py create mode 100644 Discussions/templates/board.html create mode 100644 Discussions/templates/board_reply.html create mode 100644 Discussions/templates/boards_new.html diff --git a/Discussions/forms.py b/Discussions/forms.py new file mode 100644 index 0000000..b66a9dd --- /dev/null +++ b/Discussions/forms.py @@ -0,0 +1,37 @@ +# 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 import forms +from LandingPage.models import DiscussionBoard, DiscussionReply + +class BoardForm(forms.ModelForm): + body = forms.CharField(widget=forms.Textarea) + + class Meta(): + model = DiscussionBoard + fields = ('title','body',) + help_texts = { + 'title': 'Name of the board', + 'body': 'Enter your message here' + } + +class ReplyForm(forms.ModelForm): + class Meta(): + model = DiscussionReply + fields = ('body',) + help_texts = { + 'body': 'Enter your message here' + } diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html new file mode 100644 index 0000000..c05e533 --- /dev/null +++ b/Discussions/templates/board.html @@ -0,0 +1,113 @@ +{% extends "base.html" %} +{% block title %} + {{board.title}} - {{show.name}} Discussions - Episodes.Community +{% endblock %} +{% block content %} +
+ +
+

{{board.title}}

+
+
+ {% if board.locked %} +

This board is locked

+ {% else %} + {% if user.is_authenticated %} +  Reply + {% else %} +

Log in to reply

+ {% endif %} + {% endif %} +
+
+
+

Created {{board.timestamp}} by {{board.user.display_name}}

+ {% for reply in replies %} +
+
+
+ +

{{reply.user.display_name}}

+
+
+

Submitted {{board.timestamp}}

+
+ {{reply.body}} +
+
+
+
+ {% csrf_token %} + +
+
+ {% csrf_token %} + +
+
+ +
+
+
+
+ {% empty %} +

Nobody has replied to this board!

+ {% endfor %} + {% if replies.has_other_pages %} + + {% endif %} + {% if user.is_authenticated and not board.locked %} +

Quick Reply

+
+
+ {% include "form.html" %} +
+ +
+
+
+ {% endif %} +
+{% endblock %} diff --git a/Discussions/templates/board_reply.html b/Discussions/templates/board_reply.html new file mode 100644 index 0000000..2429352 --- /dev/null +++ b/Discussions/templates/board_reply.html @@ -0,0 +1,26 @@ +{% extends "base.html" %} +{% block title %} + Reply - {{show.name}} Discussions - Episodes.Community +{% endblock %} +{% block content %} +
+ +

New Reply

+ {% if error %} +
{{error}}
+ {% endif %} +
+ {% include "form.html" %} +
+ +
+
+
+{% endblock %} diff --git a/Discussions/templates/boards.html b/Discussions/templates/boards.html index ff51386..4ad937b 100644 --- a/Discussions/templates/boards.html +++ b/Discussions/templates/boards.html @@ -4,12 +4,19 @@ {% endblock %} {% block content %}
+

{{show.name}} Discussion Boards

+

Discuss {{show.name}} with your fellow community members!

{% if user.is_authenticated %}  Create New Board {% else %} - Log in to create boards +

Log in to create boards

{% endif %}
@@ -29,7 +36,17 @@
+ {% if board.num_replies > 0 %} +
+
+ by + {{board.latest_reply.user.display_name}} +
+ {{board.latest_reply.timestamp}} +
+ {% else %} No replies + {% endif %}
@@ -56,11 +73,11 @@ {% else %}
  • - {{ i }} + {{ i }}
  • {% endif %} {% endfor %} - {% if users.has_next %} + {% if boards.has_next %}
  • Next
  • diff --git a/Discussions/templates/boards_new.html b/Discussions/templates/boards_new.html new file mode 100644 index 0000000..c4a3736 --- /dev/null +++ b/Discussions/templates/boards_new.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% block title %} + Create a Board - {{show.name}} Discussions - Episodes.Community +{% endblock %} +{% block content %} +
    + +

    Create a Board

    + {% if error %} +
    {{error}}
    + {% endif %} +
    + {% include "form.html" %} +
    + +
    +
    +
    +{% endblock %} diff --git a/Discussions/urls.py b/Discussions/urls.py index 69472ff..c138c66 100644 --- a/Discussions/urls.py +++ b/Discussions/urls.py @@ -20,5 +20,9 @@ from . import views urlpatterns = [ url(r'^$', views.Boards.as_view()), + url(r'^vote/(?P\d+)/(?P[0-1])/?$', views.BoardVoteSubmit.as_view()), + url(r'^board/new$', views.BoardForm), + url(r'^board/reply/(?P\d{1,4})/?$', views.BoardReplyForm), + url(r'^board/(?P\d{1,4})(-[\w-]+)?/?$', views.Board.as_view()), ] diff --git a/Discussions/views.py b/Discussions/views.py index cc4b16c..0802631 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -14,20 +14,25 @@ # 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, get_list_or_404, get_object_or_404 +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 +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 +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 +from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban +from . import forms + +import datetime +import re class Boards(TemplateView): @@ -39,8 +44,21 @@ class Boards(TemplateView): page = self.request.GET.get('page', 1) - boards_list = DiscussionBoard.objects.filter(show=show) + 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') paginator = Paginator(boards_list, getattr(settings, "DISCUSSIONS_PER_PAGE", 26)) + try: boards = paginator.page(page) except PageNotAnInteger: @@ -52,3 +70,214 @@ class Boards(TemplateView): 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/%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.
    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.
    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('

    Error

    You cannot vote for your own reply.

    ' + 'Return to board

    ' + % (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.
    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)) diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index e426da0..d1fed2c 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -173,3 +173,4 @@ AUTH_B64 = base64.b64encode(bytearray('%s:%s'%(AUTH_CLIENT_ID,oauth_options.get( AUTH_REDIRECT_URL = oauth_options.get('redirect_url') DISCUSSIONS_PER_PAGE = 26 +DISCUSSIONS_REPLIES_PER_PAGE = 10 diff --git a/EpisodesCommunity/urls.py b/EpisodesCommunity/urls.py index 1a6d679..4dd5832 100644 --- a/EpisodesCommunity/urls.py +++ b/EpisodesCommunity/urls.py @@ -36,7 +36,7 @@ from django.conf.urls.static import static urlpatterns = [ url(r'^admin/', admin.site.urls), - url(r'^show/(?P\w{1,16})/discuss', include('Discussions.urls')), + url(r'^show/(?P\w{1,16})/discuss/', include('Discussions.urls')), url(r'^show/(?P\w{1,16})/', include('Show.urls')), url(r'^', include('LandingPage.urls')) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/LandingPage/models.py b/LandingPage/models.py index 90cc515..8d06e9c 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -92,6 +92,7 @@ class Show(TimestampedModel): permissions = ( ('can_create_show_ban', 'Can ban an user from submitting to this show'), ('can_moderate_show', 'Can add episodes, seasons and unrestricted submissions'), + ('can_moderate_board', 'Can delete and edit boards and replies of this show'), ) def __str__(self): @@ -387,10 +388,6 @@ class DiscussionBoard(TimestampedModel): max_length=100, help_text='The title of the discussion' ) - body = models.TextField( - help_text='The body of the post', - verbose_name='Body' - ) views = models.IntegerField( help_text='The amount of times this board has been viewed', default=0 @@ -399,6 +396,14 @@ class DiscussionBoard(TimestampedModel): help_text='Whether or not this board is pinned', default=False ) + locked = models.BooleanField( + help_text='Whether or not this board is locked for further replies', + default=False + ) + + def latest_reply(self): + return self.replies.latest('timestamp') + def __str__(self): return '[%s] "%s" by %s'%(self.show.abbr, self.title, self.user) @@ -420,6 +425,10 @@ class DiscussionReply(TimestampedModel): help_text='The body of the response', verbose_name='Body' ) + deleted = models.BooleanField( + help_text='Whether or not the content has been deleted by a moderator', + default=False + ) def __str__(self): return '[%s] %s\'s response to "%s"'%(self.board.show.abbr,self.user, self.board.title) @@ -430,11 +439,11 @@ class DiscussionVote(TimestampedModel): related_name='discussion_votes', help_text='The user which cast this vote' ) - board = models.ForeignKey( - DiscussionBoard, + reply = models.ForeignKey( + DiscussionReply, on_delete=models.CASCADE, related_name='votes', - help_text='The board this vote was cast on' + help_text='The reply this vote was cast on' ) positive = models.BooleanField( help_text='If true, the vote is an upvote. Otherwise, it is a downvote. Neutral votes are not recorded' diff --git a/LandingPage/static/css/style.css b/LandingPage/static/css/style.css index 5294b35..40b2d15 100644 --- a/LandingPage/static/css/style.css +++ b/LandingPage/static/css/style.css @@ -187,6 +187,30 @@ footer .logo .part1 { text-shadow: 2px 2px 1px #0059a0; margin-right: 5px; } +.avatar { + -webkit-box-flex: 0; + -ms-flex: 0 0 180px; + flex: 0 0 180px; + max-width: 180px; +} +.avatar img { + max-width: 150px; + max-height: 150px; +} +.mini_avatar { + -webkit-box-flex: 0; + -ms-flex: 0 0 50px; + flex: 0 0 45px; + max-width: 45px; +} +.mini_avatar img { + max-width: 45px; + max-height: 45px; +} +.user-content { + word-wrap: break-word; + word-break: break-all; +} @media all and (max-width: 800px) { .logo { font-size: 5vw !important; From d7e7d5dc2c671e2a069ac475b21d5cf4b1150941 Mon Sep 17 00:00:00 2001 From: Evert Date: Wed, 28 Feb 2018 22:04:15 +0200 Subject: [PATCH 03/10] Markdown support --- Discussions/templates/board.html | 8 +++++--- Discussions/templates/board_reply.html | 3 +++ Discussions/templates/boards_new.html | 3 +++ Discussions/templatetags/markdown.py | 28 ++++++++++++++++++++++++++ Discussions/views.py | 1 + LandingPage/static/css/style.css | 5 +++++ LandingPage/templates/base.html | 3 ++- requirements.txt | 2 ++ 8 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 Discussions/templatetags/markdown.py diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index c05e533..7edb6d4 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -2,6 +2,7 @@ {% block title %} {{board.title}} - {{show.name}} Discussions - Episodes.Community {% endblock %} +{% load markdown %} {% block content %}

    Submitted {{board.timestamp}}

    -
    - {{reply.body}} -
    +
    {{reply.body|markdown|safe}}
    @@ -106,6 +105,9 @@
    +
    {% endif %} diff --git a/Discussions/templates/board_reply.html b/Discussions/templates/board_reply.html index 2429352..1e49e52 100644 --- a/Discussions/templates/board_reply.html +++ b/Discussions/templates/board_reply.html @@ -21,6 +21,9 @@
    + {% endblock %} diff --git a/Discussions/templates/boards_new.html b/Discussions/templates/boards_new.html index c4a3736..04bfbeb 100644 --- a/Discussions/templates/boards_new.html +++ b/Discussions/templates/boards_new.html @@ -20,6 +20,9 @@
    + {% endblock %} diff --git a/Discussions/templatetags/markdown.py b/Discussions/templatetags/markdown.py new file mode 100644 index 0000000..4946523 --- /dev/null +++ b/Discussions/templatetags/markdown.py @@ -0,0 +1,28 @@ +# 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 import template + +import bleach +import markdown as md +import re + +register = template.Library() + +def markdown(value): + return md.markdown(re.sub(r'\>\;', '>', bleach.clean(value)), output_format="html5") + +register.filter('markdown', markdown) diff --git a/Discussions/views.py b/Discussions/views.py index 0802631..f15f9b8 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -226,6 +226,7 @@ def BoardReplyForm(req, abbr, bid): if err_res: return render(req, "board_reply.html", ctx) + print(form_data['body']) new_reply = form.save(commit=False) new_reply.user = user diff --git a/LandingPage/static/css/style.css b/LandingPage/static/css/style.css index 40b2d15..a928774 100644 --- a/LandingPage/static/css/style.css +++ b/LandingPage/static/css/style.css @@ -210,6 +210,11 @@ footer .logo .part1 { .user-content { word-wrap: break-word; word-break: break-all; + white-space: pre; +} +blockquote { + padding-left: 16px; + border-left: 5px solid #ddd; } @media all and (max-width: 800px) { .logo { diff --git a/LandingPage/templates/base.html b/LandingPage/templates/base.html index 4bd1f6e..e798a6d 100644 --- a/LandingPage/templates/base.html +++ b/LandingPage/templates/base.html @@ -5,7 +5,8 @@ - + + diff --git a/requirements.txt b/requirements.txt index ea18de1..f861e02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ dj-database-url==0.4.2 requests==2.18.4 django-guardian==1.4.9 django-widget-tweaks==1.4.1 +markdown==2.6.11 +bleach==2.1.2 From c3ce46b94af175a79ce34b3a0b0ebbf6dcb6c68e Mon Sep 17 00:00:00 2001 From: Evert Date: Thu, 1 Mar 2018 15:48:20 +0200 Subject: [PATCH 04/10] Report replies --- Discussions/forms.py | 7 +++- Discussions/templates/board.html | 1 + Discussions/templates/report_reply.html | 36 ++++++++++++++++ Discussions/urls.py | 1 + Discussions/views.py | 55 ++++++++++++++++++++++++- LandingPage/models.py | 2 +- Show/views.py | 16 ++++++- 7 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 Discussions/templates/report_reply.html diff --git a/Discussions/forms.py b/Discussions/forms.py index b66a9dd..237908b 100644 --- a/Discussions/forms.py +++ b/Discussions/forms.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from django import forms -from LandingPage.models import DiscussionBoard, DiscussionReply +from LandingPage.models import DiscussionBoard, DiscussionReply, Report class BoardForm(forms.ModelForm): body = forms.CharField(widget=forms.Textarea) @@ -35,3 +35,8 @@ class ReplyForm(forms.ModelForm): help_texts = { 'body': 'Enter your message here' } + +class ReportForm(forms.ModelForm): + class Meta(): + model = Report + fields = ('title','details',) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index 7edb6d4..b4a85e5 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -40,6 +40,7 @@

    Submitted {{board.timestamp}}

    {{reply.body|markdown|safe}}
    +
    {% csrf_token %} diff --git a/Discussions/templates/report_reply.html b/Discussions/templates/report_reply.html new file mode 100644 index 0000000..42dc6c4 --- /dev/null +++ b/Discussions/templates/report_reply.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} +{% block title %} + Report a Post - {{reply.board.title}} - {{show.name}} Discussions - Episodes.Community +{% endblock %} +{% block content %} +
    + +

    Report a Post

    + {% if error %} +
    {{error}}
    + {% endif %} + +
    +
    Posted by {{ reply.user.display_name }}
    +
    {{ reply.body }}
    + {% if reply.user.is_staff %} +
    + Warning +

    This reply is made by a staff member. Unnecessary reporters will be banned.

    +
    + {% endif %} +
    + {% include "form.html" %} +
    + +
    + +
    +{% endblock %} diff --git a/Discussions/urls.py b/Discussions/urls.py index c138c66..1949177 100644 --- a/Discussions/urls.py +++ b/Discussions/urls.py @@ -22,6 +22,7 @@ urlpatterns = [ url(r'^$', views.Boards.as_view()), url(r'^vote/(?P\d+)/(?P[0-1])/?$', views.BoardVoteSubmit.as_view()), url(r'^board/new$', views.BoardForm), + url(r'^board/report/(?P\d{1,4})/?$', views.ReportForm), url(r'^board/reply/(?P\d{1,4})/?$', views.BoardReplyForm), url(r'^board/(?P\d{1,4})(-[\w-]+)?/?$', views.Board.as_view()), ] diff --git a/Discussions/views.py b/Discussions/views.py index f15f9b8..be30c8b 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -28,7 +28,7 @@ from django.utils.text import slugify from guardian.decorators import permission_required_or_403 -from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban +from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban, Report from . import forms import datetime @@ -282,3 +282,56 @@ class BoardVoteSubmit(LoginRequiredMixin, View): new_vote.save() return HttpResponseRedirect('/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, 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 + } + + url = '/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, 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('/show/%s/discuss/board/%d-%s'%(abbr, reply.board.pk, slugify(reply.board.title))) + else: + ctx['error'] = 'Invalid fields!' + + return render(req, "report_reply.html", ctx) diff --git a/LandingPage/models.py b/LandingPage/models.py index 8d06e9c..1daf6f6 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -449,4 +449,4 @@ class DiscussionVote(TimestampedModel): help_text='If true, the vote is an upvote. Otherwise, it is a downvote. Neutral votes are not recorded' ) def __str__(self): - return "%s %s %s"%(self.user, '\U0001f592' if self.positive else '\U0001f44e', self.board.title) + return "%s %s reply %d"%(self.user, '\U0001f592' if self.positive else '\U0001f44e', self.reply.pk) diff --git a/Show/views.py b/Show/views.py index cbff335..052b558 100644 --- a/Show/views.py +++ b/Show/views.py @@ -26,7 +26,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from guardian.decorators import permission_required_or_403 -from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban +from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban, Report from . import forms @@ -390,6 +390,8 @@ def ReportSubmission(req, abbr, submission): 'submission': submission } + url = '/show/%s/episode/%d/%d?submission=%s'%(abbr, episode.season.number, episode.episode,submission.pk) + # Handle POST if req.method == 'POST': form = forms.ReportForm(req.POST) @@ -398,10 +400,20 @@ def ReportSubmission(req, abbr, submission): if form.is_valid(): form_data = form.cleaned_data + if not user.has_perm('LandingPage.can_moderate_show', 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 submission 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 = '/show/%s/episode/%d/%d?submission=%s'%(abbr, episode.season.number, episode.episode,submission.pk) + new_report.url = url new_report.save() return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) From d006598fe6ef47b7f4be28f7208b1b73382af8bf Mon Sep 17 00:00:00 2001 From: Evert Date: Thu, 1 Mar 2018 19:22:34 +0200 Subject: [PATCH 05/10] Board moderation tools --- Discussions/templates/board.html | 33 +++++++++--- Discussions/templates/board_reply.html | 6 +-- Discussions/templates/boards.html | 6 +-- Discussions/templates/boards_new.html | 4 +- Discussions/templates/report_reply.html | 4 +- Discussions/urls.py | 3 ++ Discussions/views.py | 71 ++++++++++++++++++++----- 7 files changed, 97 insertions(+), 30 deletions(-) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index b4a85e5..89cd053 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -3,24 +3,26 @@ {{board.title}} - {{show.name}} Discussions - Episodes.Community {% endblock %} {% load markdown %} +{% load guardian_tags %} {% block content %} +{% get_obj_perms request.user for show as "show_perms" %}
    -

    {{board.title}}

    +

    {% if board.locked %}{% endif %}{% if board.pinned %}{% endif %}{{board.title}}

    {% if board.locked %}

    This board is locked

    {% else %} {% if user.is_authenticated %} -  Reply +  Reply {% else %}

    Log in to reply

    {% endif %} @@ -40,15 +42,15 @@

    Submitted {{board.timestamp}}

    {{reply.body|markdown|safe}}
    - +
    -
    + {% csrf_token %}
    -
    + {% csrf_token %} @@ -112,5 +114,20 @@
    {% endif %} + {% if user.is_authenticated %} +

    Board Tools

    + + {% endif %}
    {% endblock %} diff --git a/Discussions/templates/board_reply.html b/Discussions/templates/board_reply.html index 1e49e52..83b689d 100644 --- a/Discussions/templates/board_reply.html +++ b/Discussions/templates/board_reply.html @@ -6,9 +6,9 @@
    diff --git a/Discussions/templates/boards.html b/Discussions/templates/boards.html index 4ad937b..b900d1b 100644 --- a/Discussions/templates/boards.html +++ b/Discussions/templates/boards.html @@ -6,7 +6,7 @@
    @@ -14,7 +14,7 @@

    Discuss {{show.name}} with your fellow community members!

    {% if user.is_authenticated %} -  Create New Board +  Create New Board {% else %}

    Log in to create boards

    {% endif %} @@ -27,7 +27,7 @@
    -

    {{board.title}}

    +

    {% if board.locked %}{% endif %}{% if board.pinned %}{% endif %}{{board.title}}

    Submitted {{board.timestamp}} by {% if board.user.is_staff %} diff --git a/Discussions/templates/boards_new.html b/Discussions/templates/boards_new.html index 04bfbeb..f489a15 100644 --- a/Discussions/templates/boards_new.html +++ b/Discussions/templates/boards_new.html @@ -6,8 +6,8 @@
    diff --git a/Discussions/templates/report_reply.html b/Discussions/templates/report_reply.html index 42dc6c4..1a863ed 100644 --- a/Discussions/templates/report_reply.html +++ b/Discussions/templates/report_reply.html @@ -7,8 +7,8 @@ diff --git a/Discussions/urls.py b/Discussions/urls.py index 1949177..f11fab9 100644 --- a/Discussions/urls.py +++ b/Discussions/urls.py @@ -23,6 +23,9 @@ urlpatterns = [ url(r'^vote/(?P\d+)/(?P[0-1])/?$', views.BoardVoteSubmit.as_view()), url(r'^board/new$', views.BoardForm), url(r'^board/report/(?P\d{1,4})/?$', views.ReportForm), + url(r'^board/pin/(?P\d{1,4})/?$', views.BoardPin), + url(r'^board/delete/(?P\d{1,4})/?$', views.BoardDelete), + url(r'^board/lock/(?P\d{1,4})/?$', views.BoardLock), url(r'^board/reply/(?P\d{1,4})/?$', views.BoardReplyForm), url(r'^board/(?P\d{1,4})(-[\w-]+)?/?$', views.Board.as_view()), ] diff --git a/Discussions/views.py b/Discussions/views.py index be30c8b..e523363 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -34,12 +34,18 @@ 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) @@ -56,7 +62,7 @@ class Boards(TemplateView): then=Max('replies__timestamp') ) ), - ).order_by('-recency') + ).order_by('-pinned','-recency') paginator = Paginator(boards_list, getattr(settings, "DISCUSSIONS_PER_PAGE", 26)) try: @@ -77,6 +83,8 @@ class Board(TemplateView): 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) @@ -104,7 +112,7 @@ class Board(TemplateView): 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) + ctx['url'] = ctx['showurl'] + '/discuss/board/%d-%s?page=%d#reply-%d'%(board.pk, slugify(board.title), page, index) return ctx @@ -139,7 +147,8 @@ def BoardForm(req, abbr): # Request context ctx = { 'form': form, - 'show': show + 'show': show, + 'showurl': get_show_url(abbr) } # Get bans for this user regarding this show @@ -175,7 +184,7 @@ def BoardForm(req, abbr): 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']))) + return HttpResponseRedirect(ctx['showurl'] + '/discuss/board/%d-%s'%(new_board.pk, slugify(form_data['title']))) else: ctx['error'] = 'Invalid fields!' @@ -194,7 +203,8 @@ def BoardReplyForm(req, abbr, bid): ctx = { 'form': form, 'board': board, - 'show': show + 'show': show, + 'showurl': get_show_url(abbr) } # Get bans for this user regarding this show @@ -233,7 +243,7 @@ def BoardReplyForm(req, abbr, bid): 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)) + return HttpResponseRedirect(ctx['showurl'] + '/discuss/board/%d-%s?findReply=%d'%(board.pk, slugify(board.title), new_reply.pk)) else: ctx['error'] = 'Invalid fields!' @@ -247,6 +257,7 @@ class BoardVoteSubmit(LoginRequiredMixin, View): 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) @@ -254,8 +265,8 @@ class BoardVoteSubmit(LoginRequiredMixin, View): # Prevent voting for own reply if reply.user == user: return HttpResponse('

    Error

    You cannot vote for your own reply.

    ' - 'Return to board

    ' - % (abbr, reply.board.pk, slugify(reply.board.title)), status=400) + 'Return to board

    ' + % (showurl, reply.board.pk, slugify(reply.board.title)), status=400) show = reply.board.show @@ -281,7 +292,7 @@ class BoardVoteSubmit(LoginRequiredMixin, View): ) new_vote.save() - return HttpResponseRedirect('/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, reply.board.pk, slugify(reply.board.title), reply.pk)) + 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): @@ -301,10 +312,11 @@ def ReportForm(req, abbr, rid): ctx = { 'form': form, 'show': show, - 'reply': reply + 'reply': reply, + 'showurl': get_show_url(abbr) } - url = '/show/%s/discuss/board/%d-%s?findReply=%d'%(abbr, reply.board.pk, slugify(reply.board.title), reply.pk) + 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': @@ -330,8 +342,43 @@ def ReportForm(req, abbr, rid): new_report.url = url new_report.save() - return HttpResponseRedirect('/show/%s/discuss/board/%d-%s'%(abbr, reply.board.pk, slugify(reply.board.title))) + 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)) From ed720adbc5322460f417d4f8d6845183460d27e0 Mon Sep 17 00:00:00 2001 From: Evert Date: Thu, 1 Mar 2018 19:36:34 +0200 Subject: [PATCH 06/10] Delete reply contents and add ban button to replies --- Discussions/templates/board.html | 17 +++++++++++++++++ Discussions/urls.py | 1 + Discussions/views.py | 11 +++++++++++ 3 files changed, 29 insertions(+) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index 89cd053..2207024 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -33,6 +33,15 @@

    Created {{board.timestamp}} by {{board.user.display_name}}

    {% for reply in replies %}
    + {% if reply.deleted %} +

    This reply has been deleted by a moderator.

    + ID: {{reply.pk}} + {% if "can_moderate_board" in show_perms or board.user == user %} +
    {{reply.body}}
    + Restore + {% endif %} +
    + {% else %}
    @@ -42,7 +51,14 @@

    Submitted {{board.timestamp}}

    {{reply.body|markdown|safe}}
    + {% if user.is_authenticated %} + {% if "can_moderate_board" in show_perms or board.user == user %} + Delete Content + Ban + {% else %} + {% endif %} + {% endif %}
    {% csrf_token %} @@ -61,6 +77,7 @@
    + {% endif %}
    {% empty %}

    Nobody has replied to this board!

    diff --git a/Discussions/urls.py b/Discussions/urls.py index f11fab9..a0b8179 100644 --- a/Discussions/urls.py +++ b/Discussions/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ url(r'^board/new$', views.BoardForm), url(r'^board/report/(?P\d{1,4})/?$', views.ReportForm), url(r'^board/pin/(?P\d{1,4})/?$', views.BoardPin), + url(r'^board/delete/reply/(?P\d{1,4})/?$', views.BoardDeleteReply), url(r'^board/delete/(?P\d{1,4})/?$', views.BoardDelete), url(r'^board/lock/(?P\d{1,4})/?$', views.BoardLock), url(r'^board/reply/(?P\d{1,4})/?$', views.BoardReplyForm), diff --git a/Discussions/views.py b/Discussions/views.py index e523363..ebb8d79 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -382,3 +382,14 @@ def BoardDelete(req, abbr, bid): 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))) From adb14fe486c17f5d621884f5264006050b2c210d Mon Sep 17 00:00:00 2001 From: Evert Date: Thu, 1 Mar 2018 19:45:45 +0200 Subject: [PATCH 07/10] Use a function to create show urls instead of having them static --- Discussions/templates/board.html | 1 + Discussions/views.py | 5 +--- LandingPage/views.py | 5 +++- Show/templates/create_ban.html | 2 +- Show/templates/episode.html | 14 +++++----- Show/templates/episode_add.html | 2 +- Show/templates/report.html | 4 +-- Show/templates/season_add.html | 2 +- Show/templates/show.html | 4 +-- Show/templates/submit.html | 4 +-- Show/templates/submit_mod.html | 4 +-- Show/views.py | 45 ++++++++++++++++++++------------ 12 files changed, 52 insertions(+), 40 deletions(-) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index 2207024..53eebf4 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -39,6 +39,7 @@ {% if "can_moderate_board" in show_perms or board.user == user %}
    {{reply.body}}
    Restore + Ban {% endif %}
    {% else %} diff --git a/Discussions/views.py b/Discussions/views.py index ebb8d79..6fc1b45 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -29,15 +29,12 @@ 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 LandingPage.views import get_show_url 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" diff --git a/LandingPage/views.py b/LandingPage/views.py index 946f09b..58253db 100644 --- a/LandingPage/views.py +++ b/LandingPage/views.py @@ -31,7 +31,10 @@ from .models import Show from .models import Submission from .models import DiscussionBoard -# Create your views here. +# Get a show's URL by its abbreviation +def get_show_url(abbr): + return '/show/%s' % (abbr) + # Redirect url should point to this view class LoginRedirect(View): def get(self, req): diff --git a/Show/templates/create_ban.html b/Show/templates/create_ban.html index dfdb19e..e798897 100644 --- a/Show/templates/create_ban.html +++ b/Show/templates/create_ban.html @@ -7,7 +7,7 @@
    diff --git a/Show/templates/episode.html b/Show/templates/episode.html index 9a9d551..2502bc3 100644 --- a/Show/templates/episode.html +++ b/Show/templates/episode.html @@ -46,7 +46,7 @@
    @@ -68,13 +68,13 @@
    - + {% csrf_token %} -
    + {% csrf_token %}
    @@ -106,9 +106,9 @@
    {% if user.is_authenticated %} {% if "can_moderate_show" in show_perms %} -  Add New Link +  Add New Link {% else %} -  Submit New Link +  Submit New Link {% endif %} {% else %} Log in to submit a link diff --git a/Show/templates/episode_add.html b/Show/templates/episode_add.html index 0c5dac9..27e3802 100644 --- a/Show/templates/episode_add.html +++ b/Show/templates/episode_add.html @@ -33,7 +33,7 @@
    diff --git a/Show/templates/report.html b/Show/templates/report.html index 394d43b..acf5e6d 100644 --- a/Show/templates/report.html +++ b/Show/templates/report.html @@ -33,8 +33,8 @@
    diff --git a/Show/templates/season_add.html b/Show/templates/season_add.html index f7e250e..70a0aa1 100644 --- a/Show/templates/season_add.html +++ b/Show/templates/season_add.html @@ -7,7 +7,7 @@
    diff --git a/Show/templates/show.html b/Show/templates/show.html index 03d484a..0c106d0 100644 --- a/Show/templates/show.html +++ b/Show/templates/show.html @@ -41,14 +41,14 @@
    {% if "can_moderate_show" in show_perms %}
    - +
    {% endif %}
    {% endfor %} {% if "can_moderate_show" in show_perms %} {% endif %}
    diff --git a/Show/templates/submit.html b/Show/templates/submit.html index 6c01cda..30634e1 100644 --- a/Show/templates/submit.html +++ b/Show/templates/submit.html @@ -33,8 +33,8 @@
    diff --git a/Show/templates/submit_mod.html b/Show/templates/submit_mod.html index 4ed99f0..cc4b8e1 100644 --- a/Show/templates/submit_mod.html +++ b/Show/templates/submit_mod.html @@ -35,8 +35,8 @@ {% get_obj_perms request.user for show as "show_perms" %} diff --git a/Show/views.py b/Show/views.py index 052b558..f0cdebd 100644 --- a/Show/views.py +++ b/Show/views.py @@ -27,6 +27,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from guardian.decorators import permission_required_or_403 from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban, Report +from LandingPage.views import get_show_url from . import forms @@ -50,6 +51,7 @@ class IndexView(TemplateView): # Add fields to context ctx['show'] = show ctx['seasons'] = seasons + ctx['showurl'] = get_show_url(abbr) return ctx @@ -59,6 +61,7 @@ class EpisodeView(TemplateView): def get_context_data(self, abbr, season, episode, **kwargs): ctx = super().get_context_data() + ctx['showurl'] = get_show_url(abbr) highlight = self.request.GET.get('submission', None) if not highlight == None: @@ -93,10 +96,11 @@ class EpisodeView(TemplateView): def EpisodeFindSubmission(req, abbr, submission): show = get_object_or_404(Show, abbr=abbr) submission = int(submission) + showurl = get_show_url(abbr) episode = get_object_or_404(Episode, submissions__id=submission) - return HttpResponseRedirect('/show/%s/episode/%d/%d?submission=%d'%(abbr, episode.season.number, episode.episode, submission)) + return HttpResponseRedirect('%s/episode/%d/%d?submission=%d'%(showurl, episode.season.number, episode.episode, submission)) # Submission form GET and POST @login_required @@ -111,7 +115,8 @@ def SubmissionForm(req, abbr, season, episode): ctx = { 'form': form, 'show': show, - 'episode': episode + 'episode': episode, + 'showurl': get_show_url(abbr) } # Get bans for this user regarding this show @@ -144,7 +149,7 @@ def SubmissionForm(req, abbr, season, episode): new_submission.episode = episode new_submission.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' @@ -164,18 +169,19 @@ def SubmissionModForm(req, abbr, submission): ctx = { 'form': form, 'show': show, - 'episode': episode + 'episode': episode, + 'showurl': get_show_url(abbr) } # Handle POST if req.method == 'POST': if 'delete' in req.POST: submission.delete() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) if 'delete_ban' in req.POST: submission.delete() - return HttpResponseRedirect('/show/%s/create_ban?user=%s'%(abbr,submission.user.username)) + return HttpResponseRedirect('%s/create_ban?user=%s'%(ctx['showurl'],submission.user.username)) form = forms.SubmissionFormAdmin(req.POST, instance=submission) ctx['form'] = form @@ -184,7 +190,7 @@ def SubmissionModForm(req, abbr, submission): form_data = form.cleaned_data form.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' @@ -201,7 +207,8 @@ def SeasonSubmitForm(req, abbr): # Request context ctx = { 'form': form, - 'show': show + 'show': show, + 'showurl': get_show_url(abbr) } # Handle POST @@ -221,7 +228,7 @@ def SeasonSubmitForm(req, abbr): new_season.show = show new_season.save() - return HttpResponseRedirect('/show/%s'%(abbr)) + return HttpResponseRedirect(ctx['showurl']) else: ctx['error'] = 'Invalid fields!' @@ -240,7 +247,8 @@ def EpisodeSubmitForm(req, abbr, season): ctx = { 'form': form, 'season': season, - 'show': show + 'show': show, + 'showurl': get_show_url(abbr) } # Handle POST @@ -261,7 +269,7 @@ def EpisodeSubmitForm(req, abbr, season): new_episode.season = season new_episode.save() - return HttpResponseRedirect('/show/%s'%(abbr)) + return HttpResponseRedirect(ctx['showurl']) else: ctx['error'] = 'Invalid fields!' @@ -275,6 +283,7 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View): pos_bool = int(positive) == 1 user = req.user + showurl = get_show_url(abbr) # Get the submission from the database submission = get_object_or_404(Submission, id=subid) @@ -307,7 +316,7 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View): ) new_vote.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, submission.episode.season.number, submission.episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(showurl, submission.episode.season.number, submission.episode.episode)) # Episode form GET and POST @permission_required_or_403('LandingPage.can_create_show_ban', (Show, 'abbr', 'abbr'), accept_global_perms=True) @@ -332,7 +341,8 @@ def BanFromShowForm(req, abbr): ctx = { 'form': form, 'show': show, - 'target': banTarget + 'target': banTarget, + 'showurl': get_show_url(abbr) } # Handle POST @@ -361,7 +371,7 @@ def BanFromShowForm(req, abbr): if 'delete' in req.POST: Submission.objects.filter(episode__show=show,user=banTarget).delete() - return HttpResponseRedirect('/show/%s'%(abbr)) + return HttpResponseRedirect(ctx['showurl']) else: ctx['error'] = 'Invalid fields!' @@ -387,10 +397,11 @@ def ReportSubmission(req, abbr, submission): 'form': form, 'show': show, 'episode': episode, - 'submission': submission + 'submission': submission, + 'showurl': get_show_url(abbr) } - url = '/show/%s/episode/%d/%d?submission=%s'%(abbr, episode.season.number, episode.episode,submission.pk) + url = '%s/episode/%d/%d?submission=%s'%(ctx['showurl'], episode.season.number, episode.episode,submission.pk) # Handle POST if req.method == 'POST': @@ -416,7 +427,7 @@ def ReportSubmission(req, abbr, submission): new_report.url = url new_report.save() - return HttpResponseRedirect('/show/%s/episode/%d/%d'%(abbr, episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' From 028b1ab279b006089f5837e38eecc791676da8d3 Mon Sep 17 00:00:00 2001 From: Evert Date: Fri, 2 Mar 2018 11:59:44 +0200 Subject: [PATCH 08/10] Add next and previous buttons to episode page --- EpisodesCommunity/settings.py | 6 ++++++ LandingPage/views.py | 6 ++++++ Show/templates/episode.html | 26 ++++++++++++++++++++++++ Show/templates/show.html | 1 + Show/views.py | 38 ++++++++++++++++++++++++++++++++++- 5 files changed, 76 insertions(+), 1 deletion(-) diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index d1fed2c..e646da2 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -174,3 +174,9 @@ AUTH_REDIRECT_URL = oauth_options.get('redirect_url') DISCUSSIONS_PER_PAGE = 26 DISCUSSIONS_REPLIES_PER_PAGE = 10 + +# Domain of this app +DOMAIN='localhost' + +# Use subdomains for each show +DOMAIN_SUBDOMAIN_SHOWS=False diff --git a/LandingPage/views.py b/LandingPage/views.py index 58253db..cf848ce 100644 --- a/LandingPage/views.py +++ b/LandingPage/views.py @@ -33,6 +33,12 @@ from .models import DiscussionBoard # Get a show's URL by its abbreviation def get_show_url(abbr): + use_sdms = getattr(settings, "DOMAIN_SUBDOMAIN_SHOWS", False) + domain = getattr(settings, "DOMAIN", 'localhost') + + if use_sdms: + return '%s.%s' % (abbr, domain) + return '/show/%s' % (abbr) # Redirect url should point to this view diff --git a/Show/templates/episode.html b/Show/templates/episode.html index 2502bc3..5a94280 100644 --- a/Show/templates/episode.html +++ b/Show/templates/episode.html @@ -55,6 +55,11 @@
    {% for sbm in submissions %}
    + {% if forloop.counter0 == 0 and sbm.embed and not sbm.positives < sbm.negatives %} +
    + +
    + {% endif %}
    @@ -114,5 +119,26 @@ Log in to submit a link {% endif %}
    +

    Discuss {{episode.name}} on the discussion boards!

    +
    {% endblock %} diff --git a/Show/templates/show.html b/Show/templates/show.html index 0c106d0..bdb1df0 100644 --- a/Show/templates/show.html +++ b/Show/templates/show.html @@ -21,6 +21,7 @@ +

    Discuss {{show.name}} on the discussion boards!

    {% for season in seasons %}
    diff --git a/Show/views.py b/Show/views.py index f0cdebd..8c1a966 100644 --- a/Show/views.py +++ b/Show/views.py @@ -15,7 +15,7 @@ # along with this program. If not, see . from django.template import RequestContext -from django.shortcuts import render, get_list_or_404, get_object_or_404 +from django.shortcuts import render, get_list_or_404, get_object_or_404, redirect from django.views import View from django.views.generic.base import TemplateView from django.contrib.auth.decorators import login_required @@ -69,6 +69,34 @@ class EpisodeView(TemplateView): # Get show by abbr show = get_object_or_404(Show, abbr=abbr) + + # Check next or previous + season_number = int(season) + episode_number = int(episode) + + lastep = Episode.objects.filter(season__number=season_number,show=show).order_by('episode').last() + season_count = Season.objects.filter(show=show).count() + + if season_count == 0: + raise Http404('This show has no episodes.') + + if episode_number == 0 and season_number > 1: + season_number -= 1 + epobj = Episode.objects.filter(season__number=season_number,show=show).order_by('episode').last() + + if not epobj: + raise Http404('No Episode matches the given query.') + + episode_number = int(epobj.episode) + ctx['url'] = '%s/episode/%d/%d'%(ctx['showurl'], season_number, episode_number) + elif episode_number > int(lastep.episode): + season_number += 1 + episode_number = 1 + ctx['url'] = '%s/episode/%d/%d'%(ctx['showurl'], season_number, episode_number) + + if 'url' in ctx: + return ctx + episode = get_object_or_404(Episode, show=show,season__number=season,episode=episode) # I acknowledge that this is a mess. A functional mess. But a mess nonetheless. Hey, that rhymed! @@ -90,9 +118,17 @@ class EpisodeView(TemplateView): ctx['episode'] = episode ctx['submissions'] = submissions ctx['highlight'] = highlight + ctx['has_previous'] = episode_number > 1 or season_number > 1 + ctx['has_next'] = episode_number < int(lastep.episode) or season_number < season_count return ctx + def render_to_response(self, context): + if 'url' in context: + return redirect(context['url']) + + return super(EpisodeView, self).render_to_response(context) + def EpisodeFindSubmission(req, abbr, submission): show = get_object_or_404(Show, abbr=abbr) submission = int(submission) From e59c03539de04b44e3986b18298b08233586f193 Mon Sep 17 00:00:00 2001 From: Evert Date: Fri, 2 Mar 2018 15:46:25 +0200 Subject: [PATCH 09/10] Add better show url formation method --- Discussions/templates/board.html | 30 ++++++++--------- Discussions/templates/board_reply.html | 6 ++-- Discussions/templates/boards.html | 6 ++-- Discussions/templates/boards_new.html | 4 +-- Discussions/templates/report_reply.html | 6 ++-- Discussions/views.py | 35 ++++++++------------ EpisodesCommunity/settings.py | 4 +-- LandingPage/models.py | 12 ++++++- LandingPage/templates/landing_page.html | 2 +- LandingPage/templates/shows.html | 2 +- LandingPage/views.py | 12 +------ Show/templates/create_ban.html | 2 +- Show/templates/episode.html | 20 ++++++------ Show/templates/episode_add.html | 2 +- Show/templates/report.html | 4 +-- Show/templates/season_add.html | 2 +- Show/templates/show.html | 4 +-- Show/templates/submit.html | 4 +-- Show/templates/submit_mod.html | 4 +-- Show/views.py | 43 ++++++++++--------------- options_example.ini | 4 +++ 21 files changed, 97 insertions(+), 111 deletions(-) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index 53eebf4..bf032e5 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -9,8 +9,8 @@
    @@ -22,7 +22,7 @@

    This board is locked

    {% else %} {% if user.is_authenticated %} -  Reply +  Reply {% else %}

    Log in to reply

    {% endif %} @@ -38,8 +38,8 @@ ID: {{reply.pk}} {% if "can_moderate_board" in show_perms or board.user == user %}
    {{reply.body}}
    - Restore - Ban + Restore + Ban {% endif %}
    {% else %} @@ -54,20 +54,20 @@
    {% if user.is_authenticated %} {% if "can_moderate_board" in show_perms or board.user == user %} - Delete Content - Ban + Delete Content + Ban {% else %} - + {% endif %} {% endif %}
    - + {% csrf_token %} -
    + {% csrf_token %} @@ -136,14 +136,14 @@

    Board Tools

    {% endif %} diff --git a/Discussions/templates/board_reply.html b/Discussions/templates/board_reply.html index 83b689d..10c5fbb 100644 --- a/Discussions/templates/board_reply.html +++ b/Discussions/templates/board_reply.html @@ -6,9 +6,9 @@
    diff --git a/Discussions/templates/boards.html b/Discussions/templates/boards.html index b900d1b..dabc7e3 100644 --- a/Discussions/templates/boards.html +++ b/Discussions/templates/boards.html @@ -6,7 +6,7 @@
    @@ -14,7 +14,7 @@

    Discuss {{show.name}} with your fellow community members!

    {% if user.is_authenticated %} -  Create New Board +  Create New Board {% else %}

    Log in to create boards

    {% endif %} @@ -27,7 +27,7 @@
    -

    {% if board.locked %}{% endif %}{% if board.pinned %}{% endif %}{{board.title}}

    +

    {% if board.locked %}{% endif %}{% if board.pinned %}{% endif %}{{board.title}}

    Submitted {{board.timestamp}} by {% if board.user.is_staff %} diff --git a/Discussions/templates/boards_new.html b/Discussions/templates/boards_new.html index f489a15..db51c55 100644 --- a/Discussions/templates/boards_new.html +++ b/Discussions/templates/boards_new.html @@ -6,8 +6,8 @@
    diff --git a/Discussions/templates/report_reply.html b/Discussions/templates/report_reply.html index 1a863ed..c15564d 100644 --- a/Discussions/templates/report_reply.html +++ b/Discussions/templates/report_reply.html @@ -6,9 +6,9 @@
    diff --git a/Discussions/views.py b/Discussions/views.py index 6fc1b45..32fb37f 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -29,7 +29,6 @@ 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 LandingPage.views import get_show_url from . import forms import datetime @@ -41,7 +40,6 @@ class Boards(TemplateView): 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) @@ -80,8 +78,6 @@ class Board(TemplateView): 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) @@ -109,7 +105,7 @@ class Board(TemplateView): 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) + ctx['url'] = show.url() + '/discuss/board/%d-%s?page=%d#reply-%d'%(board.pk, slugify(board.title), page, index) return ctx @@ -181,7 +177,7 @@ def BoardForm(req, abbr): 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']))) + return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s'%(new_board.pk, slugify(form_data['title']))) else: ctx['error'] = 'Invalid fields!' @@ -200,8 +196,7 @@ def BoardReplyForm(req, abbr, bid): ctx = { 'form': form, 'board': board, - 'show': show, - 'showurl': get_show_url(abbr) + 'show': show } # Get bans for this user regarding this show @@ -240,7 +235,7 @@ def BoardReplyForm(req, abbr, bid): 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)) + return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s?findReply=%d'%(board.pk, slugify(board.title), new_reply.pk)) else: ctx['error'] = 'Invalid fields!' @@ -254,10 +249,10 @@ class BoardVoteSubmit(LoginRequiredMixin, View): 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) + showurl = reply.board.show.url() # Prevent voting for own reply if reply.user == user: @@ -309,11 +304,10 @@ def ReportForm(req, abbr, rid): ctx = { 'form': form, 'show': show, - 'reply': reply, - 'showurl': get_show_url(abbr) + 'reply': reply } - url = '%s/discuss/board/%d-%s?findReply=%d'%(ctx['showurl'], reply.board.pk, slugify(reply.board.title), reply.pk) + 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': @@ -339,7 +333,7 @@ def ReportForm(req, abbr, rid): new_report.url = url new_report.save() - return HttpResponseRedirect('%s/discuss/board/%d-%s'%(ctx['showurl'], reply.board.pk, slugify(reply.board.title))) + return HttpResponseRedirect('%s/discuss/board/%d-%s'%(show.url(), reply.board.pk, slugify(reply.board.title))) else: ctx['error'] = 'Invalid fields!' @@ -349,27 +343,25 @@ def ReportForm(req, abbr, rid): 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) + % (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'%(showurl, board.pk, slugify(board.title))) + 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) - 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))) + 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): @@ -378,15 +370,14 @@ def BoardDelete(req, abbr, bid): DiscussionBoard.objects.filter(pk=board.pk).delete() - return HttpResponseRedirect('%s/discuss' % (showurl)) + 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) - 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))) + return HttpResponseRedirect('%s/discuss/board/%d-%s'%(reply.show.url(), reply.board.pk, slugify(reply.board.title))) diff --git a/EpisodesCommunity/settings.py b/EpisodesCommunity/settings.py index e646da2..9620082 100644 --- a/EpisodesCommunity/settings.py +++ b/EpisodesCommunity/settings.py @@ -176,7 +176,7 @@ DISCUSSIONS_PER_PAGE = 26 DISCUSSIONS_REPLIES_PER_PAGE = 10 # Domain of this app -DOMAIN='localhost' +DOMAIN=options.get('domain') # Use subdomains for each show -DOMAIN_SUBDOMAIN_SHOWS=False +DOMAIN_SUBDOMAIN_SHOWS=options.get('use_subdomain_paths') == 'true' diff --git a/LandingPage/models.py b/LandingPage/models.py index 1daf6f6..78f4e76 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from django.db import models +from django.conf import settings from django.contrib.auth.models import AbstractUser from django.core.files.storage import FileSystemStorage from django.conf import settings @@ -95,6 +96,15 @@ class Show(TimestampedModel): ('can_moderate_board', 'Can delete and edit boards and replies of this show'), ) + def url(self): + use_sdms = getattr(settings, "DOMAIN_SUBDOMAIN_SHOWS", False) + domain = getattr(settings, "DOMAIN", 'localhost') + + if use_sdms: + return domain.format(sub=self.abbr + '.',path='') + + return '/show/%s' % (self.abbr) + def __str__(self): return '%s [%s]'%(self.name,self.abbr) @@ -253,7 +263,7 @@ class Season(models.Model): help_text="The artwork associated with the season. Should display the name of the show in a movie-poster esque format. Aspect ration should be about 2:3", verbose_name="Artwork", blank=True - ) + ) def __str__(self): return self.show.name + " S%d"%self.number diff --git a/LandingPage/templates/landing_page.html b/LandingPage/templates/landing_page.html index 933935e..16bf0c9 100644 --- a/LandingPage/templates/landing_page.html +++ b/LandingPage/templates/landing_page.html @@ -16,7 +16,7 @@ {% if not recent %} Nothing to show {% endif %}
    {% for show in recent %} - + {{show.name}} diff --git a/LandingPage/templates/shows.html b/LandingPage/templates/shows.html index 969774e..6880c90 100644 --- a/LandingPage/templates/shows.html +++ b/LandingPage/templates/shows.html @@ -5,7 +5,7 @@
    {% for show in shows %} {% if forloop.counter|divisibleby:3 %}
    diff --git a/LandingPage/views.py b/LandingPage/views.py index cf848ce..d0a36e2 100644 --- a/LandingPage/views.py +++ b/LandingPage/views.py @@ -21,7 +21,7 @@ from django.contrib.auth import login as auth_login, authenticate from django.conf import settings from django.http import HttpResponse from django.http import HttpResponseRedirect -from django.db.models import Max +from django.db.models import Max, F from django.contrib.auth.views import logout import requests import hashlib @@ -31,16 +31,6 @@ from .models import Show from .models import Submission from .models import DiscussionBoard -# Get a show's URL by its abbreviation -def get_show_url(abbr): - use_sdms = getattr(settings, "DOMAIN_SUBDOMAIN_SHOWS", False) - domain = getattr(settings, "DOMAIN", 'localhost') - - if use_sdms: - return '%s.%s' % (abbr, domain) - - return '/show/%s' % (abbr) - # Redirect url should point to this view class LoginRedirect(View): def get(self, req): diff --git a/Show/templates/create_ban.html b/Show/templates/create_ban.html index e798897..57981b2 100644 --- a/Show/templates/create_ban.html +++ b/Show/templates/create_ban.html @@ -7,7 +7,7 @@
    diff --git a/Show/templates/episode.html b/Show/templates/episode.html index 5a94280..9273541 100644 --- a/Show/templates/episode.html +++ b/Show/templates/episode.html @@ -46,7 +46,7 @@
    @@ -73,13 +73,13 @@
    - + {% csrf_token %} -
    + {% csrf_token %}
    @@ -111,19 +111,19 @@
    {% if user.is_authenticated %} {% if "can_moderate_show" in show_perms %} -  Add New Link +  Add New Link {% else %} -  Submit New Link +  Submit New Link {% endif %} {% else %} Log in to submit a link {% endif %}
    -

    Discuss {{episode.name}} on the discussion boards!

    +

    Discuss {{episode.name}} on the discussion boards!

    {% if "can_moderate_show" in show_perms %}
    - +
    {% endif %}
    {% endfor %} {% if "can_moderate_show" in show_perms %} {% endif %}
    diff --git a/Show/templates/submit.html b/Show/templates/submit.html index 30634e1..2f3dc18 100644 --- a/Show/templates/submit.html +++ b/Show/templates/submit.html @@ -33,8 +33,8 @@
    diff --git a/Show/templates/submit_mod.html b/Show/templates/submit_mod.html index cc4b8e1..5c054bd 100644 --- a/Show/templates/submit_mod.html +++ b/Show/templates/submit_mod.html @@ -35,8 +35,8 @@ {% get_obj_perms request.user for show as "show_perms" %} diff --git a/Show/views.py b/Show/views.py index 8c1a966..ab093f4 100644 --- a/Show/views.py +++ b/Show/views.py @@ -27,7 +27,6 @@ from django.contrib.auth.mixins import LoginRequiredMixin from guardian.decorators import permission_required_or_403 from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban, Report -from LandingPage.views import get_show_url from . import forms @@ -51,7 +50,6 @@ class IndexView(TemplateView): # Add fields to context ctx['show'] = show ctx['seasons'] = seasons - ctx['showurl'] = get_show_url(abbr) return ctx @@ -61,7 +59,6 @@ class EpisodeView(TemplateView): def get_context_data(self, abbr, season, episode, **kwargs): ctx = super().get_context_data() - ctx['showurl'] = get_show_url(abbr) highlight = self.request.GET.get('submission', None) if not highlight == None: @@ -88,11 +85,11 @@ class EpisodeView(TemplateView): raise Http404('No Episode matches the given query.') episode_number = int(epobj.episode) - ctx['url'] = '%s/episode/%d/%d'%(ctx['showurl'], season_number, episode_number) + ctx['url'] = '%s/episode/%d/%d'%(show.url(), season_number, episode_number) elif episode_number > int(lastep.episode): season_number += 1 episode_number = 1 - ctx['url'] = '%s/episode/%d/%d'%(ctx['showurl'], season_number, episode_number) + ctx['url'] = '%s/episode/%d/%d'%(show.url(), season_number, episode_number) if 'url' in ctx: return ctx @@ -132,11 +129,10 @@ class EpisodeView(TemplateView): def EpisodeFindSubmission(req, abbr, submission): show = get_object_or_404(Show, abbr=abbr) submission = int(submission) - showurl = get_show_url(abbr) episode = get_object_or_404(Episode, submissions__id=submission) - return HttpResponseRedirect('%s/episode/%d/%d?submission=%d'%(showurl, episode.season.number, episode.episode, submission)) + return HttpResponseRedirect('%s/episode/%d/%d?submission=%d'%(show.url(), episode.season.number, episode.episode, submission)) # Submission form GET and POST @login_required @@ -151,8 +147,7 @@ def SubmissionForm(req, abbr, season, episode): ctx = { 'form': form, 'show': show, - 'episode': episode, - 'showurl': get_show_url(abbr) + 'episode': episode } # Get bans for this user regarding this show @@ -185,7 +180,7 @@ def SubmissionForm(req, abbr, season, episode): new_submission.episode = episode new_submission.save() - return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' @@ -205,19 +200,18 @@ def SubmissionModForm(req, abbr, submission): ctx = { 'form': form, 'show': show, - 'episode': episode, - 'showurl': get_show_url(abbr) + 'episode': episode } # Handle POST if req.method == 'POST': if 'delete' in req.POST: submission.delete() - return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode)) if 'delete_ban' in req.POST: submission.delete() - return HttpResponseRedirect('%s/create_ban?user=%s'%(ctx['showurl'],submission.user.username)) + return HttpResponseRedirect('%s/create_ban?user=%s'%(show.url(),submission.user.username)) form = forms.SubmissionFormAdmin(req.POST, instance=submission) ctx['form'] = form @@ -226,7 +220,7 @@ def SubmissionModForm(req, abbr, submission): form_data = form.cleaned_data form.save() - return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' @@ -243,8 +237,7 @@ def SeasonSubmitForm(req, abbr): # Request context ctx = { 'form': form, - 'show': show, - 'showurl': get_show_url(abbr) + 'show': show } # Handle POST @@ -264,7 +257,7 @@ def SeasonSubmitForm(req, abbr): new_season.show = show new_season.save() - return HttpResponseRedirect(ctx['showurl']) + return HttpResponseRedirect(show.url()) else: ctx['error'] = 'Invalid fields!' @@ -283,8 +276,7 @@ def EpisodeSubmitForm(req, abbr, season): ctx = { 'form': form, 'season': season, - 'show': show, - 'showurl': get_show_url(abbr) + 'show': show } # Handle POST @@ -305,7 +297,7 @@ def EpisodeSubmitForm(req, abbr, season): new_episode.season = season new_episode.save() - return HttpResponseRedirect(ctx['showurl']) + return HttpResponseRedirect(show.url()) else: ctx['error'] = 'Invalid fields!' @@ -319,7 +311,6 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View): pos_bool = int(positive) == 1 user = req.user - showurl = get_show_url(abbr) # Get the submission from the database submission = get_object_or_404(Submission, id=subid) @@ -352,7 +343,7 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View): ) new_vote.save() - return HttpResponseRedirect('%s/episode/%d/%d'%(showurl, submission.episode.season.number, submission.episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), submission.episode.season.number, submission.episode.episode)) # Episode form GET and POST @permission_required_or_403('LandingPage.can_create_show_ban', (Show, 'abbr', 'abbr'), accept_global_perms=True) @@ -407,7 +398,7 @@ def BanFromShowForm(req, abbr): if 'delete' in req.POST: Submission.objects.filter(episode__show=show,user=banTarget).delete() - return HttpResponseRedirect(ctx['showurl']) + return HttpResponseRedirect(show.url()) else: ctx['error'] = 'Invalid fields!' @@ -437,7 +428,7 @@ def ReportSubmission(req, abbr, submission): 'showurl': get_show_url(abbr) } - url = '%s/episode/%d/%d?submission=%s'%(ctx['showurl'], episode.season.number, episode.episode,submission.pk) + url = '%s/episode/%d/%d?submission=%s'%(show.url(), episode.season.number, episode.episode, submission.pk) # Handle POST if req.method == 'POST': @@ -463,7 +454,7 @@ def ReportSubmission(req, abbr, submission): new_report.url = url new_report.save() - return HttpResponseRedirect('%s/episode/%d/%d'%(ctx['showurl'], episode.season.number, episode.episode)) + return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode)) else: ctx['error'] = 'Invalid fields!' diff --git a/options_example.ini b/options_example.ini index 516fc94..48c0668 100644 --- a/options_example.ini +++ b/options_example.ini @@ -9,6 +9,10 @@ secret_key=5up3r s3cr3t k3y #For configuration details database=sqlite:///database.sqlite3 +#Domain of this website to use in show path manufacturing +domain=http://{sub}localhost:8000{path} +use_subdomain_paths=false + [OAuth] #The root of the oauth endpoint you are using for oauth settings token_endpoint=https://icynet.eu/oauth/ From 8732babf50f6ef3765b619892bdf0fe3670d411b Mon Sep 17 00:00:00 2001 From: Evert Date: Fri, 2 Mar 2018 16:36:01 +0200 Subject: [PATCH 10/10] Final discussion boards fixes --- Discussions/templates/board.html | 13 +++++++++++-- Discussions/views.py | 3 +-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Discussions/templates/board.html b/Discussions/templates/board.html index bf032e5..482b61f 100644 --- a/Discussions/templates/board.html +++ b/Discussions/templates/board.html @@ -53,14 +53,15 @@
    {{reply.body|markdown|safe}}
    {% if user.is_authenticated %} - {% if "can_moderate_board" in show_perms or board.user == user %} + {% if "can_moderate_board" in show_perms %} Delete Content Ban - {% else %} + {% elif not user == reply.user %} {% endif %} {% endif %}
    + {% if not board.locked %} {% csrf_token %} + {% else %} + +  {{reply.positives}} + + +  {{reply.negatives}} + + {% endif %}
    diff --git a/Discussions/views.py b/Discussions/views.py index 32fb37f..367f9ab 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -140,8 +140,7 @@ def BoardForm(req, abbr): # Request context ctx = { 'form': form, - 'show': show, - 'showurl': get_show_url(abbr) + 'show': show } # Get bans for this user regarding this show