Compare commits

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

2 Commits

Author SHA1 Message Date
ed696a99f5
User action logging for discussions 2018-03-05 14:04:20 +02:00
5d3e81fe78
Add a moderator dashboard, add user action logging 2018-03-03 14:42:00 +02:00
7 changed files with 225 additions and 16 deletions

View File

@ -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, Report
from LandingPage.models import Show, DiscussionBoard, DiscussionReply, DiscussionVote, Ban, Report, UserAction
from . import forms
import datetime
@ -176,6 +176,9 @@ def BoardForm(req, abbr):
new_post = DiscussionReply(user=user,board=new_board,body=form_data['body'])
new_post.save()
act = UserAction(user=user,show=show,act_type=0,url='%s/discuss/board/%d-%s'%(show.url(), new_board.pk, slugify(form_data['title'])))
act.save()
return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s'%(new_board.pk, slugify(form_data['title'])))
else:
ctx['error'] = 'Invalid fields!'
@ -227,13 +230,15 @@ 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
new_reply.board = board
new_reply.save()
act = UserAction(user=user,show=show,act_type=1,url='%s/discuss/board/%d-%s?findReply=%d'%(show.url(), new_board.pk, slugify(form_data['title']), new_reply.pk))
act.save()
return HttpResponseRedirect(show.url() + '/discuss/board/%d-%s?findReply=%d'%(board.pk, slugify(board.title), new_reply.pk))
else:
ctx['error'] = 'Invalid fields!'
@ -273,6 +278,10 @@ class BoardVoteSubmit(LoginRequiredMixin, View):
if not vote.positive == pos_bool:
vote.positive = pos_bool
vote.save()
act = UserAction(user=user,show=show,act_type=6 + int(positive),url='%s/discuss/board/%d-%s?findReply=%d'%(showurl,
reply.board.pk, slugify(reply.board.title), reply.pk))
act.save()
else:
vote.delete()
else:
@ -283,6 +292,10 @@ class BoardVoteSubmit(LoginRequiredMixin, View):
)
new_vote.save()
act = UserAction(user=user,show=show,act_type=6 + int(positive),url='%s/discuss/board/%d-%s?findReply=%d'%(showurl, reply.board.pk,
slugify(reply.board.title), reply.pk))
act.save()
return HttpResponseRedirect('%s/discuss/board/%d-%s?findReply=%d'%(showurl, reply.board.pk, slugify(reply.board.title), reply.pk))
@login_required
@ -318,7 +331,7 @@ def ReportForm(req, abbr, rid):
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:
if Report.objects.filter(reporter=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)
@ -368,6 +381,9 @@ def BoardDelete(req, abbr, bid):
board = get_object_or_404(DiscussionBoard, pk=bid)
showurl = get_show_url(abbr)
act = UserAction(user=req.user,show=board.show,act_type=4,url='#%s by %s'%(board.title, board.user.display_name))
act.save()
DiscussionBoard.objects.filter(pk=board.pk).delete()
return HttpResponseRedirect('%s/discuss' % (board.show.url()))
@ -376,8 +392,12 @@ def BoardDelete(req, abbr, bid):
def BoardDeleteReply(req, abbr, rid):
reply = get_object_or_404(DiscussionReply, pk=rid)
act = UserAction(user=req.user,show=reply.board.show,act_type=4,url='%s/discuss/board/%d-%s?findReply=%d'%(reply.board.show.url(),
reply.board.pk, slugify(reply.board.title), reply.pk))
act.save()
delete = not reply.deleted
DiscussionReply.objects.filter(pk=reply.pk).update(deleted=delete)
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(reply.show.url(), reply.board.pk, slugify(reply.board.title)))
return HttpResponseRedirect('%s/discuss/board/%d-%s'%(reply.board.show.url(), reply.board.pk, slugify(reply.board.title)))

View File

@ -46,3 +46,4 @@ admin.site.register(Watch)
admin.site.register(DiscussionBoard)
admin.site.register(DiscussionReply)
admin.site.register(DiscussionVote)
admin.site.register(UserAction)

View File

@ -467,3 +467,48 @@ class DiscussionVote(TimestampedModel):
)
def __str__(self):
return "%s %s reply %d"%(self.user, '\U0001f592' if self.positive else '\U0001f44e', self.reply.pk)
class UserAction(TimestampedModel):
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
help_text='The user who committed this action',
)
url = models.URLField(
max_length=100,
help_text='The URL of the content',
verbose_name = 'Content URL'
)
show = models.ForeignKey(
Show,
on_delete=models.CASCADE,
null=True,
blank=True,
help_text='The show associated with the action',
)
act_type = models.IntegerField(
help_text='Type of this action',
default=0,
verbose_name = 'Action Type'
)
def act_str(self):
action = 'created'
if self.act_type == 1:
action = 'submitted'
elif self.act_type == 2:
action = 'replied'
elif self.act_type == 3:
action = 'modified'
elif self.act_type == 4:
action = 'deleted'
elif self.act_type == 5:
action = 'banned'
elif self.act_type == 6:
action = 'voted down'
elif self.act_type == 7:
action = 'voted up'
return action
def __str__(self):
return "%s %s %s"%(self.user, self.act_str(), self.url)

View File

@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}
Moderator area for {{show.name}} - Episodes.Community
{% endblock %}
{% block content %}
{% load guardian_tags %}
{% get_obj_perms request.user for show as "show_perms" %}
{% include "partials/show_banner.html" %}
<section class="mb-5 mt-2 container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{show.url}}">{{show.name}}</a></li>
<li class="breadcrumb-item active" aria-current="page">Moderator Area</li>
</ol>
</nav>
<h1>Moderator Area</h1>
<div class="row">
<div class="col">
<h2>Recent Activity</h2>
{% for action in actions %}
<div class="action">
<div class="font-weight-bold">
<a href="/community/user/{{action.user.id}}-{{action.user.display_name|slugify}}">{{action.user.display_name}}</a>
</div>
<p class="text-muted">{{action.act_str}} <a href="{{action.url}}" target="_blank">{{action.url}}</a></p>
</div>
{% empty %}
<p>Nothing to show.</p>
{% endfor %}
</div>
<div class="col">
<h2>Unresolved Reports</h2>
{% for report in reports %}
<div class="report border-bottom">
<a class="font-weight-bold" href="{{show.url}}/moderator/report/{{report.pk}}">{{report.title}}</a>
<p class="font-weight-light text-muted">
<a href="/community/user/{{report.reporter.id}}-{{report.reporter.display_name|slugify}}">{{report.reporter.display_name}}</a> · {{report.timestamp}}
</p>
</div>
{% empty %}
<p>Nothing to show.</p>
{% endfor %}
</div>
</div>
</section>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block title %}
Moderator area for {{show.name}} - Episodes.Community
{% endblock %}
{% block content %}
{% load guardian_tags %}
{% get_obj_perms request.user for show as "show_perms" %}
{% include "partials/show_banner.html" %}
<section class="mb-5 mt-2 container">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{show.url}}">{{show.name}}</a></li>
<li class="breadcrumb-item"><a href="{{show.url}}/moderator">Moderator Area</a></li>
<li class="breadcrumb-item active" aria-current="page">Read Report</li>
</ol>
</nav>
<h1>{{report.title}}</h1>
<div class="user-content">{{report.details}}</div>
<p>URL: <a href="{{report.url}}">{{report.url}}</a></p>
<form method="POST">
{%csrf_token%}
{% if not report.resolved %}
<button name="resolve" class="btn btn-primary">Mark as resolved</button>
{% endif %}
<button name="delete" class="btn btn-danger">Delete</button>
<button name="delete_ban" class="btn btn-danger">Delete and Ban User</button>
</form>
</section>
{% endblock %}

View File

@ -36,6 +36,8 @@ from . import views
urlpatterns = [
url(r'^$', views.IndexView.as_view()),
url(r'^moderator/report/(?P<repid>\d{1,4})$', views.ModReport),
url(r'^moderator/$', views.ModDashboard),
url(r'^create_ban$', views.BanFromShowForm),
url(r'^season/new$', views.SeasonSubmitForm),
url(r'^season/(?P<season>\d{1,4})/append$', views.EpisodeSubmitForm),

View File

@ -20,13 +20,14 @@ 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.utils.text import slugify
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 guardian.decorators import permission_required_or_403
from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban, Report
from LandingPage.models import User, Show, Season, Episode, Submission, SubmissionVote, Ban, Report, UserAction
from . import forms
@ -180,6 +181,9 @@ def SubmissionForm(req, abbr, season, episode):
new_submission.episode = episode
new_submission.save()
act = UserAction(user=user,show=show,act_type=1,url='%s/episode/%d/%d?submission=%d'%(show.url(), episode.season.number, episode.episode, new_submission.pk))
act.save()
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
else:
ctx['error'] = 'Invalid fields!'
@ -206,10 +210,14 @@ def SubmissionModForm(req, abbr, submission):
# Handle POST
if req.method == 'POST':
if 'delete' in req.POST:
act = UserAction(user=user,show=show,act_type=4,url='%s/episode/%d/%d?submission=%d'%(show.url(), episode.season.number, episode.episode, submission.pk))
act.save()
submission.delete()
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
if 'delete_ban' in req.POST:
act = UserAction(user=user,show=show,act_type=4,url='%s/episode/%d/%d?submission=%d'%(show.url(), episode.season.number, episode.episode, submission.pk))
act.save()
submission.delete()
return HttpResponseRedirect('%s/create_ban?user=%s'%(show.url(),submission.user.username))
@ -220,6 +228,9 @@ def SubmissionModForm(req, abbr, submission):
form_data = form.cleaned_data
form.save()
act = UserAction(user=user,show=show,act_type=3,url='%s/episode/%d/%d?submission=%d'%(show.url(), episode.season.number, episode.episode, submission.pk))
act.save()
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
else:
ctx['error'] = 'Invalid fields!'
@ -257,6 +268,9 @@ def SeasonSubmitForm(req, abbr):
new_season.show = show
new_season.save()
act = UserAction(user=user,show=show,act_type=0,url='%s#season-%d'%(show.url(), new_season.number))
act.save()
return HttpResponseRedirect(show.url())
else:
ctx['error'] = 'Invalid fields!'
@ -297,6 +311,9 @@ def EpisodeSubmitForm(req, abbr, season):
new_episode.season = season
new_episode.save()
act = UserAction(user=user,show=show,act_type=0,url='%s/episode/%d/%d'%(show.url(), season.number, new_episode.episode))
act.save()
return HttpResponseRedirect(show.url())
else:
ctx['error'] = 'Invalid fields!'
@ -333,6 +350,10 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View):
if not vote.positive == pos_bool:
vote.positive = pos_bool
vote.save()
act = UserAction(user=user,show=show,act_type=6 + int(positive),url='%s/episode/%d/%d?submission=%d'%(show.url(),
submission.episode.season.number, submission.episode.episode, submission.pk))
act.save()
else:
vote.delete()
else:
@ -342,6 +363,9 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View):
positive=pos_bool
)
new_vote.save()
act = UserAction(user=user,show=show,act_type=6 + int(positive),url='%s/episode/%d/%d?submission=%d'%(show.url(),
submission.episode.season.number, submission.episode.episode, submission.pk))
act.save()
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), submission.episode.season.number, submission.episode.episode))
@ -351,15 +375,15 @@ def BanFromShowForm(req, abbr):
show = get_object_or_404(Show, abbr=abbr)
user = req.user
banTarget = get_object_or_404(User, username=req.GET.get('user', None))
ban_target = get_object_or_404(User, username=req.GET.get('user', None))
if banTarget == user:
if ban_target == user:
return HttpResponseForbidden('You cannot ban yourself!')
if banTarget.is_staff:
if ban_target.is_staff:
return HttpResponseForbidden('You cannot ban a staff member!')
if banTarget.has_perm('LandingPage.can_moderate_show', show):
if ban_target.has_perm('LandingPage.can_moderate_show', show):
return HttpResponseForbidden('You cannot ban another moderator!')
form = forms.BanForm()
@ -368,8 +392,7 @@ def BanFromShowForm(req, abbr):
ctx = {
'form': form,
'show': show,
'target': banTarget,
'showurl': get_show_url(abbr)
'target': ban_target
}
# Handle POST
@ -387,16 +410,19 @@ def BanFromShowForm(req, abbr):
new_ban.expiration = datetime.datetime.now()
new_ban.site_wide = False
new_ban.user = banTarget
new_ban.user = ban_target
new_ban.admin = user
new_ban.save()
# Add show to scope
new_ban.scope.add(show)
act = UserAction(user=user,show=show,act_type=5,url='/community/user/%d-%s'%(ban_target.pk, slugify(ban_target.display_name)))
act.save()
# Delete all of the user's submissions for this show
if 'delete' in req.POST:
Submission.objects.filter(episode__show=show,user=banTarget).delete()
Submission.objects.filter(episode__show=show,user=ban_target).delete()
return HttpResponseRedirect(show.url())
else:
@ -424,8 +450,7 @@ def ReportSubmission(req, abbr, submission):
'form': form,
'show': show,
'episode': episode,
'submission': submission,
'showurl': get_show_url(abbr)
'submission': submission
}
url = '%s/episode/%d/%d?submission=%s'%(show.url(), episode.season.number, episode.episode, submission.pk)
@ -440,7 +465,7 @@ def ReportSubmission(req, abbr, submission):
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:
if Report.objects.filter(reporter=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)
@ -460,3 +485,42 @@ def ReportSubmission(req, abbr, submission):
ctx['error'] = 'Invalid fields!'
return render(req, "report.html", ctx)
@permission_required_or_403('LandingPage.can_create_show_ban', (Show, 'abbr', 'abbr'), accept_global_perms=True)
def ModDashboard(req, abbr):
show = get_object_or_404(Show, abbr=abbr)
activity = UserAction.objects.filter(show=show).order_by("-timestamp")[:20]
reports = Report.objects.filter(show=show,resolved=False).order_by("-timestamp")
ctx = {}
ctx['actions'] = activity
ctx['reports'] = reports
ctx['show'] = show
return render(req, "mod_dash.html", ctx)
@permission_required_or_403('LandingPage.can_create_show_ban', (Show, 'abbr', 'abbr'), accept_global_perms=True)
def ModReport(req, abbr, repid):
show = get_object_or_404(Show, abbr=abbr)
report = get_object_or_404(Report, pk=repid)
user = req.user
ctx = {}
ctx['report'] = report
ctx['show'] = show
if req.method == 'POST':
if 'delete' in req.POST:
Report.objects.filter(pk=report.pk).delete()
return HttpResponseRedirect('%s/moderator'%(show.url()))
elif 'delete_ban' in req.POST:
Report.objects.filter(pk=report.pk).delete()
return HttpResponseRedirect('%s/create_ban?user=%s'%(show.url(),report.reporter.username))
else:
Report.objects.filter(pk=report.pk).update(read_at=datetime.datetime.now(),read_by=user,resolved=True)
return HttpResponseRedirect('%s/moderator'%(show.url()))
if not report.resolved:
Report.objects.filter(pk=report.pk).update(read_at=datetime.datetime.now(),read_by=user)
return render(req, "mod_report.html", ctx)