From 5d3e81fe784c7424057bb9a2a5964300e49ef11c Mon Sep 17 00:00:00 2001 From: Evert Date: Sat, 3 Mar 2018 14:42:00 +0200 Subject: [PATCH] Add a moderator dashboard, add user action logging --- Discussions/views.py | 2 +- LandingPage/admin.py | 1 + LandingPage/models.py | 45 +++++++++++++++++ Show/templates/mod_dash.html | 47 ++++++++++++++++++ Show/templates/mod_report.html | 30 ++++++++++++ Show/urls.py | 2 + Show/views.py | 88 +++++++++++++++++++++++++++++----- 7 files changed, 202 insertions(+), 13 deletions(-) create mode 100644 Show/templates/mod_dash.html create mode 100644 Show/templates/mod_report.html diff --git a/Discussions/views.py b/Discussions/views.py index ecef0f9..46d53a6 100644 --- a/Discussions/views.py +++ b/Discussions/views.py @@ -318,7 +318,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) diff --git a/LandingPage/admin.py b/LandingPage/admin.py index 62c1348..9fa4b56 100644 --- a/LandingPage/admin.py +++ b/LandingPage/admin.py @@ -46,3 +46,4 @@ admin.site.register(Watch) admin.site.register(DiscussionBoard) admin.site.register(DiscussionReply) admin.site.register(DiscussionVote) +admin.site.register(UserAction) diff --git a/LandingPage/models.py b/LandingPage/models.py index 517fd2d..5cdd734 100644 --- a/LandingPage/models.py +++ b/LandingPage/models.py @@ -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) diff --git a/Show/templates/mod_dash.html b/Show/templates/mod_dash.html new file mode 100644 index 0000000..8dc7ab1 --- /dev/null +++ b/Show/templates/mod_dash.html @@ -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" %} +
+ +

Moderator Area

+
+
+

Recent Activity

+ {% for action in actions %} +
+ +

{{action.act_str}} {{action.url}}

+
+ {% empty %} +

Nothing to show.

+ {% endfor %} +
+
+

Unresolved Reports

+ {% for report in reports %} +
+ {{report.title}} +

+ {{report.reporter.display_name}} ยท {{report.timestamp}} +

+
+ {% empty %} +

Nothing to show.

+ {% endfor %} +
+
+
+{% endblock %} diff --git a/Show/templates/mod_report.html b/Show/templates/mod_report.html new file mode 100644 index 0000000..1f017f9 --- /dev/null +++ b/Show/templates/mod_report.html @@ -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" %} +
+ +

{{report.title}}

+
{{report.details}}
+

URL: {{report.url}}

+
+ {%csrf_token%} + {% if not report.resolved %} + + {% endif %} + + +
+
+{% endblock %} diff --git a/Show/urls.py b/Show/urls.py index 62b6e69..ddfd794 100644 --- a/Show/urls.py +++ b/Show/urls.py @@ -36,6 +36,8 @@ from . import views urlpatterns = [ url(r'^$', views.IndexView.as_view()), + url(r'^moderator/report/(?P\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\d{1,4})/append$', views.EpisodeSubmitForm), diff --git a/Show/views.py b/Show/views.py index f71ef5d..f59e399 100644 --- a/Show/views.py +++ b/Show/views.py @@ -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)