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):
|
||||
# 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)
|
||||
|
||||
|
@ -46,3 +46,4 @@ admin.site.register(Watch)
|
||||
admin.site.register(DiscussionBoard)
|
||||
admin.site.register(DiscussionReply)
|
||||
admin.site.register(DiscussionVote)
|
||||
admin.site.register(UserAction)
|
||||
|
@ -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)
|
||||
|
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 = [
|
||||
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),
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user