Boards, Replies and Votes
This commit is contained in:
parent
22ca7de96b
commit
8230e83528
37
Discussions/forms.py
Normal file
37
Discussions/forms.py
Normal file
@ -0,0 +1,37 @@
|
||||
# Episodes.Community - Community-Driven TV Show Episode Link Sharing Site
|
||||
# Copyright (C) 2017 Evert "Diamond" Prants <evert@lunasqu.ee>, Taizo "Tsa6" Simpson <taizo@tsa6.net>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from django 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'
|
||||
}
|
113
Discussions/templates/board.html
Normal file
113
Discussions/templates/board.html
Normal file
@ -0,0 +1,113 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
{{board.title}} - {{show.name}} Discussions - Episodes.Community
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container mb-5 mt-5">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}">{{show.name}}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}/discuss">Discussions</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{board.title}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<h1 class="col">{{board.title}}</h1>
|
||||
<div class="col-2">
|
||||
<div class="d-flex flex-row-reverse mt-2">
|
||||
{% if board.locked %}
|
||||
<p>This board is locked</p>
|
||||
{% else %}
|
||||
{% if user.is_authenticated %}
|
||||
<a href="/show/{{show.abbr}}/discuss/board/reply/{{board.pk}}" class="btn btn-primary"><i class="fa fa-fw fa-pencil"></i> Reply</a>
|
||||
{% else %}
|
||||
<p><a href="/login">Log in</a> to reply</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="timestamp text-muted font-weight-light">Created {{board.timestamp}} by {{board.user.display_name}}</p>
|
||||
{% for reply in replies %}
|
||||
<div class="reply border-bottom mb-4" id="reply-{{forloop.counter}}">
|
||||
<div class="row">
|
||||
<div class="avatar">
|
||||
<img src="https://icynet.eu/api/avatar/{{reply.user.icy_id}}" class="m-auto d-block">
|
||||
<p class="text-center font-weight-bold">{{reply.user.display_name}}</p>
|
||||
</div>
|
||||
<div class="col border-left d-flex flex-column">
|
||||
<p class="timestamp text-muted font-weight-light">Submitted {{board.timestamp}}</p>
|
||||
<div class="user-content mb-auto">
|
||||
{{reply.body}}
|
||||
</div>
|
||||
<div class="actions d-flex flex-row-reverse">
|
||||
<div class="vote-btns">
|
||||
<form method="POST" class="d-inline" action="/show/{{show.abbr}}/discuss/vote/{{reply.id}}/1">
|
||||
{% csrf_token %}
|
||||
<button href="#" class="btn btn-link text-success">
|
||||
<i class="fa fa-fw fa-thumbs-up"></i> {{reply.positives}}
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" class="d-inline" action="/show/{{show.abbr}}/discuss/vote/{{reply.id}}/0">
|
||||
{% csrf_token %}
|
||||
<button href="#" class="btn btn-link text-danger">
|
||||
<i class="fa fa-fw fa-thumbs-down"></i> {{reply.negatives}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<!--<a href="#" class="btn btn-secondary mr-1">Quote</a>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<h3>Nobody has replied to this board!</h3>
|
||||
{% endfor %}
|
||||
{% if replies.has_other_pages %}
|
||||
<nav aria-label="Boards navigation">
|
||||
<ul class="pagination">
|
||||
{% if replies.has_previous %}
|
||||
<li class="page-item">
|
||||
<a href="?page={{ replies.previous_page_number }}" class="page-link">Previous</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% for i in replies.paginator.page_range %}
|
||||
{% if replies.number == i %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ i }} <span class="sr-only">(current)</span></span>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if replies.has_next %}
|
||||
<li class="page-item">
|
||||
<a href="?page={{ replies.next_page_number }}" class="page-link">Next</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link" href="#" tabindex="-1">Next</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated and not board.locked %}
|
||||
<h2>Quick Reply</h2>
|
||||
<div class="reply-box">
|
||||
<form class="form-horizontal" role="form" action="/show/{{show.abbr}}/discuss/board/reply/{{board.pk}}" method="post">
|
||||
{% include "form.html" %}
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Reply</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
26
Discussions/templates/board_reply.html
Normal file
26
Discussions/templates/board_reply.html
Normal file
@ -0,0 +1,26 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
Reply - {{show.name}} Discussions - Episodes.Community
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section class="container mt-5 mb-5">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}">{{show.name}}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}/discuss">Discussions</a></li>
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}/discuss/board/{{board.pk}}-{{board.title|slugify}}">{{board.title}}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Reply</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1>New Reply</h1>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{error}}</div>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" role="form" action="" method="post">
|
||||
{% include "form.html" %}
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
@ -4,12 +4,19 @@
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class="container mb-5 mt-5">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}">{{show.name}}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Discussions</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1>{{show.name}} Discussion Boards</h1>
|
||||
<p>Discuss {{show.name}} with your fellow community members!</p>
|
||||
<div class="d-flex flex-row-reverse mb-4">
|
||||
{% if user.is_authenticated %}
|
||||
<a href="/show/{{show.abbr}}/discuss/board/new" class="btn btn-primary"><i class="fa fa-fw fa-pencil"></i> Create New Board</a>
|
||||
{% else %}
|
||||
<a href="/login">Log in</a> to create boards
|
||||
<p><a href="/login">Log in</a> to create boards</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="bg-light rounded p-2 row">
|
||||
@ -29,7 +36,17 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
{% if board.num_replies > 0 %}
|
||||
<div class="reply mt-2">
|
||||
<div class="user">
|
||||
<small class="text-muted font-weight-light">by </small>
|
||||
{{board.latest_reply.user.display_name}}
|
||||
</div>
|
||||
<small class="timestamp text-muted font-weight-light">{{board.latest_reply.timestamp}}</small>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted font-weight-light">No replies</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,11 +73,11 @@
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<span class="page-link" href="?page={{ i }}">{{ i }}</span>
|
||||
<a class="page-link" href="?page={{ i }}">{{ i }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if users.has_next %}
|
||||
{% if boards.has_next %}
|
||||
<li class="page-item">
|
||||
<a href="?page={{ boards.next_page_number }}" class="page-link">Next</a>
|
||||
</li>
|
||||
|
25
Discussions/templates/boards_new.html
Normal file
25
Discussions/templates/boards_new.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
Create a Board - {{show.name}} Discussions - Episodes.Community
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<section class="container mt-5 mb-5">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}">{{show.name}}</a></li>
|
||||
<li class="breadcrumb-item"><a href="/show/{{show.abbr}}/discuss">Discussions</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">New Board</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h1>Create a Board</h1>
|
||||
{% if error %}
|
||||
<div class="alert alert-danger">{{error}}</div>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" role="form" action="" method="post">
|
||||
{% include "form.html" %}
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
{% endblock %}
|
@ -20,5 +20,9 @@ from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.Boards.as_view()),
|
||||
url(r'^vote/(?P<replyid>\d+)/(?P<positive>[0-1])/?$', views.BoardVoteSubmit.as_view()),
|
||||
url(r'^board/new$', views.BoardForm),
|
||||
url(r'^board/reply/(?P<bid>\d{1,4})/?$', views.BoardReplyForm),
|
||||
url(r'^board/(?P<bid>\d{1,4})(-[\w-]+)?/?$', views.Board.as_view()),
|
||||
]
|
||||
|
||||
|
@ -14,20 +14,25 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from django.template import RequestContext
|
||||
from django.shortcuts import render, 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.<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))
|
||||
|
@ -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
|
||||
|
@ -36,7 +36,7 @@ from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^show/(?P<abbr>\w{1,16})/discuss', include('Discussions.urls')),
|
||||
url(r'^show/(?P<abbr>\w{1,16})/discuss/', include('Discussions.urls')),
|
||||
url(r'^show/(?P<abbr>\w{1,16})/', include('Show.urls')),
|
||||
url(r'^', include('LandingPage.urls'))
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
@ -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'
|
||||
|
@ -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;
|
||||
|
Reference in New Issue
Block a user