Add a moderator dashboard, add user action logging
This commit is contained in:
parent
8a33f6da59
commit
5d3e81fe78
@ -318,7 +318,7 @@ def ReportForm(req, abbr, rid):
|
|||||||
|
|
||||||
if not user.has_perm('LandingPage.can_moderate_board', show):
|
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
|
# 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!'
|
ctx['error'] = 'You\'ve created too many reports recently!'
|
||||||
return render(req, "report_reply.html", ctx)
|
return render(req, "report_reply.html", ctx)
|
||||||
|
|
||||||
|
@ -46,3 +46,4 @@ admin.site.register(Watch)
|
|||||||
admin.site.register(DiscussionBoard)
|
admin.site.register(DiscussionBoard)
|
||||||
admin.site.register(DiscussionReply)
|
admin.site.register(DiscussionReply)
|
||||||
admin.site.register(DiscussionVote)
|
admin.site.register(DiscussionVote)
|
||||||
|
admin.site.register(UserAction)
|
||||||
|
@ -467,3 +467,48 @@ class DiscussionVote(TimestampedModel):
|
|||||||
)
|
)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s %s reply %d"%(self.user, '\U0001f592' if self.positive else '\U0001f44e', self.reply.pk)
|
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)
|
||||||
|
47
Show/templates/mod_dash.html
Normal file
47
Show/templates/mod_dash.html
Normal 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 %}
|
30
Show/templates/mod_report.html
Normal file
30
Show/templates/mod_report.html
Normal 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 %}
|
@ -36,6 +36,8 @@ from . import views
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', views.IndexView.as_view()),
|
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'^create_ban$', views.BanFromShowForm),
|
||||||
url(r'^season/new$', views.SeasonSubmitForm),
|
url(r'^season/new$', views.SeasonSubmitForm),
|
||||||
url(r'^season/(?P<season>\d{1,4})/append$', views.EpisodeSubmitForm),
|
url(r'^season/(?P<season>\d{1,4})/append$', views.EpisodeSubmitForm),
|
||||||
|
@ -20,13 +20,14 @@ from django.views import View
|
|||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.text import slugify
|
||||||
from django.http import Http404, HttpResponseForbidden, HttpResponse, HttpResponseRedirect
|
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
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
|
||||||
from guardian.decorators import permission_required_or_403
|
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
|
from . import forms
|
||||||
|
|
||||||
@ -180,6 +181,9 @@ def SubmissionForm(req, abbr, season, episode):
|
|||||||
new_submission.episode = episode
|
new_submission.episode = episode
|
||||||
new_submission.save()
|
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))
|
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
|
||||||
else:
|
else:
|
||||||
ctx['error'] = 'Invalid fields!'
|
ctx['error'] = 'Invalid fields!'
|
||||||
@ -206,10 +210,14 @@ def SubmissionModForm(req, abbr, submission):
|
|||||||
# Handle POST
|
# Handle POST
|
||||||
if req.method == 'POST':
|
if req.method == 'POST':
|
||||||
if 'delete' in req.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()
|
submission.delete()
|
||||||
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
|
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
|
||||||
|
|
||||||
if 'delete_ban' in req.POST:
|
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()
|
submission.delete()
|
||||||
return HttpResponseRedirect('%s/create_ban?user=%s'%(show.url(),submission.user.username))
|
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_data = form.cleaned_data
|
||||||
form.save()
|
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))
|
return HttpResponseRedirect('%s/episode/%d/%d'%(show.url(), episode.season.number, episode.episode))
|
||||||
else:
|
else:
|
||||||
ctx['error'] = 'Invalid fields!'
|
ctx['error'] = 'Invalid fields!'
|
||||||
@ -257,6 +268,9 @@ def SeasonSubmitForm(req, abbr):
|
|||||||
new_season.show = show
|
new_season.show = show
|
||||||
new_season.save()
|
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())
|
return HttpResponseRedirect(show.url())
|
||||||
else:
|
else:
|
||||||
ctx['error'] = 'Invalid fields!'
|
ctx['error'] = 'Invalid fields!'
|
||||||
@ -297,6 +311,9 @@ def EpisodeSubmitForm(req, abbr, season):
|
|||||||
new_episode.season = season
|
new_episode.season = season
|
||||||
new_episode.save()
|
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())
|
return HttpResponseRedirect(show.url())
|
||||||
else:
|
else:
|
||||||
ctx['error'] = 'Invalid fields!'
|
ctx['error'] = 'Invalid fields!'
|
||||||
@ -333,6 +350,10 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View):
|
|||||||
if not vote.positive == pos_bool:
|
if not vote.positive == pos_bool:
|
||||||
vote.positive = pos_bool
|
vote.positive = pos_bool
|
||||||
vote.save()
|
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:
|
else:
|
||||||
vote.delete()
|
vote.delete()
|
||||||
else:
|
else:
|
||||||
@ -342,6 +363,9 @@ class SubmissionVoteSubmit(LoginRequiredMixin, View):
|
|||||||
positive=pos_bool
|
positive=pos_bool
|
||||||
)
|
)
|
||||||
new_vote.save()
|
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))
|
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)
|
show = get_object_or_404(Show, abbr=abbr)
|
||||||
user = req.user
|
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!')
|
return HttpResponseForbidden('You cannot ban yourself!')
|
||||||
|
|
||||||
if banTarget.is_staff:
|
if ban_target.is_staff:
|
||||||
return HttpResponseForbidden('You cannot ban a staff member!')
|
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!')
|
return HttpResponseForbidden('You cannot ban another moderator!')
|
||||||
|
|
||||||
form = forms.BanForm()
|
form = forms.BanForm()
|
||||||
@ -368,8 +392,7 @@ def BanFromShowForm(req, abbr):
|
|||||||
ctx = {
|
ctx = {
|
||||||
'form': form,
|
'form': form,
|
||||||
'show': show,
|
'show': show,
|
||||||
'target': banTarget,
|
'target': ban_target
|
||||||
'showurl': get_show_url(abbr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Handle POST
|
# Handle POST
|
||||||
@ -387,16 +410,19 @@ def BanFromShowForm(req, abbr):
|
|||||||
new_ban.expiration = datetime.datetime.now()
|
new_ban.expiration = datetime.datetime.now()
|
||||||
|
|
||||||
new_ban.site_wide = False
|
new_ban.site_wide = False
|
||||||
new_ban.user = banTarget
|
new_ban.user = ban_target
|
||||||
new_ban.admin = user
|
new_ban.admin = user
|
||||||
new_ban.save()
|
new_ban.save()
|
||||||
|
|
||||||
# Add show to scope
|
# Add show to scope
|
||||||
new_ban.scope.add(show)
|
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
|
# Delete all of the user's submissions for this show
|
||||||
if 'delete' in req.POST:
|
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())
|
return HttpResponseRedirect(show.url())
|
||||||
else:
|
else:
|
||||||
@ -424,8 +450,7 @@ def ReportSubmission(req, abbr, submission):
|
|||||||
'form': form,
|
'form': form,
|
||||||
'show': show,
|
'show': show,
|
||||||
'episode': episode,
|
'episode': episode,
|
||||||
'submission': submission,
|
'submission': submission
|
||||||
'showurl': get_show_url(abbr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
url = '%s/episode/%d/%d?submission=%s'%(show.url(), episode.season.number, episode.episode, submission.pk)
|
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):
|
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
|
# 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!'
|
ctx['error'] = 'You\'ve created too many reports recently!'
|
||||||
return render(req, "report_reply.html", ctx)
|
return render(req, "report_reply.html", ctx)
|
||||||
|
|
||||||
@ -460,3 +485,42 @@ def ReportSubmission(req, abbr, submission):
|
|||||||
ctx['error'] = 'Invalid fields!'
|
ctx['error'] = 'Invalid fields!'
|
||||||
|
|
||||||
return render(req, "report.html", ctx)
|
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)
|
||||||
|
Reference in New Issue
Block a user