mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-05-19 16:58:31 +00:00
Merge pull request #1974 from bookwyrm-social/celerybeat
Schedules automod tasks
This commit is contained in:
commit
f4e828e2fb
|
@ -5,6 +5,7 @@ from django import forms
|
|||
from django.forms import widgets
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_celery_beat.models import IntervalSchedule
|
||||
|
||||
from bookwyrm import models
|
||||
from .custom_form import CustomForm
|
||||
|
@ -127,3 +128,14 @@ class AutoModRuleForm(CustomForm):
|
|||
class Meta:
|
||||
model = models.AutoMod
|
||||
fields = ["string_match", "flag_users", "flag_statuses", "created_by"]
|
||||
|
||||
|
||||
class IntervalScheduleForm(CustomForm):
|
||||
class Meta:
|
||||
model = IntervalSchedule
|
||||
fields = ["every", "period"]
|
||||
|
||||
widgets = {
|
||||
"every": forms.NumberInput(attrs={"aria-describedby": "desc_every"}),
|
||||
"period": forms.Select(attrs={"aria-describedby": "desc_period"}),
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ INSTALLED_APPS = [
|
|||
"sass_processor",
|
||||
"bookwyrm",
|
||||
"celery",
|
||||
"django_celery_beat",
|
||||
"imagekit",
|
||||
"storages",
|
||||
]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends 'settings/layout.html' %}
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block title %}
|
||||
|
@ -16,12 +17,81 @@
|
|||
<p>
|
||||
{% trans "Auto-moderation rules will create reports for any local user or status with fields matching the provided string." %}
|
||||
{% trans "Users or statuses that have already been reported (regardless of whether the report was resolved) will not be flagged." %}
|
||||
{% trans "At this time, reports are <em>not</em> being generated automatically, and you must manually trigger a scan." %}
|
||||
</p>
|
||||
<form name="run-scan" method="POST" action="{% url 'settings-automod-run' %}">
|
||||
</div>
|
||||
<div class="box block">
|
||||
{% if task %}
|
||||
<dl class="block">
|
||||
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||
{% trans "Schedule:" %}
|
||||
</dt>
|
||||
<dd>
|
||||
{{ task.schedule }}
|
||||
</dd>
|
||||
|
||||
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||
{% trans "Last run:" %}
|
||||
</dt>
|
||||
<dd>
|
||||
{{ task.last_run_at|naturaltime }}
|
||||
</dd>
|
||||
|
||||
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||
{% trans "Total run count:" %}
|
||||
</dt>
|
||||
<dd>
|
||||
{{ task.total_run_count }}
|
||||
</dd>
|
||||
|
||||
<dt class="is-pulled-left mr-5 has-text-weight-bold">
|
||||
{% trans "Enabled:" %}
|
||||
</dt>
|
||||
<dd>
|
||||
<span class="tag {% if task.enabled %}is-success{% else %}is-danger{% endif %}">
|
||||
{{ task.enabled|yesno }}
|
||||
</span>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<div class="is-flex is-justify-content-space-between block">
|
||||
<form name="unschedule-scan" method="POST" action="{% url 'settings-automod-unschedule' task.id %}">
|
||||
{% csrf_token %}
|
||||
<button class="button is-danger">{% trans "Delete schedule" %}</button>
|
||||
</form>
|
||||
<form name="run-scan" method="POST" action="{% url 'settings-automod-run' %}">
|
||||
{% csrf_token %}
|
||||
<button class="button">{% trans "Run now" %}</button>
|
||||
<p class="help">{% trans "Last run date will not be updated" %}</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<h2 class="title is-4">{% trans "Schedule scan" %}</h2>
|
||||
<form name="schedule-scan" method="POST" action="{% url 'settings-automod-schedule' %}">
|
||||
{% csrf_token %}
|
||||
<button class="button is-warning">{% trans "Run scan" %}</button>
|
||||
<div class="field">
|
||||
<label class="label" for="id_every">
|
||||
{{ task_form.every.label }}
|
||||
</label>
|
||||
{{ task_form.every }}
|
||||
<p class="help" id="desc_every">
|
||||
{{ task_form.every.help_text }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="id_period">
|
||||
{{ task_form.period.label }}
|
||||
</label>
|
||||
<div class="select">
|
||||
{{ task_form.period }}
|
||||
</div>
|
||||
<p class="help" id="desc_period">
|
||||
{{ task_form.period.help_text }}
|
||||
</p>
|
||||
</div>
|
||||
<button class="button is-warning">{% trans "Schedule scan" %}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if success %}
|
||||
|
|
|
@ -233,11 +233,23 @@ urlpatterns = [
|
|||
# auto-moderation rules
|
||||
re_path(r"^settings/automod/?$", views.AutoMod.as_view(), name="settings-automod"),
|
||||
re_path(
|
||||
r"^settings/automod/(?P<rule_id>\d+)/delete?$",
|
||||
r"^settings/automod/(?P<rule_id>\d+)/delete/?$",
|
||||
views.automod_delete,
|
||||
name="settings-automod-delete",
|
||||
),
|
||||
re_path(r"^settings/automod/run?$", views.run_automod, name="settings-automod-run"),
|
||||
re_path(
|
||||
r"^settings/automod/schedule/?$",
|
||||
views.schedule_automod_task,
|
||||
name="settings-automod-schedule",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/automod/unschedule/(?P<task_id>\d+)/?$",
|
||||
views.unschedule_automod_task,
|
||||
name="settings-automod-unschedule",
|
||||
),
|
||||
re_path(
|
||||
r"^settings/automod/run/?$", views.run_automod, name="settings-automod-run"
|
||||
),
|
||||
# moderation
|
||||
re_path(
|
||||
r"^settings/reports/?$", views.ReportsAdmin.as_view(), name="settings-reports"
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
from .admin.announcements import Announcements, Announcement
|
||||
from .admin.announcements import EditAnnouncement, delete_announcement
|
||||
from .admin.automod import AutoMod, automod_delete, run_automod
|
||||
from .admin.automod import schedule_automod_task, unschedule_automod_task
|
||||
from .admin.dashboard import Dashboard
|
||||
from .admin.federation import Federation, FederatedServer
|
||||
from .admin.federation import AddFederatedServer, ImportServerBlocklist
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
""" moderation via flagged posts and users """
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.http import require_POST
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
|
||||
from bookwyrm import forms, models
|
||||
|
||||
|
@ -24,8 +26,9 @@ class AutoMod(View):
|
|||
|
||||
def get(self, request):
|
||||
"""view rules"""
|
||||
data = {"rules": models.AutoMod.objects.all(), "form": forms.AutoModRuleForm()}
|
||||
return TemplateResponse(request, "settings/automod/rules.html", data)
|
||||
return TemplateResponse(
|
||||
request, "settings/automod/rules.html", automod_view_data()
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
"""add rule"""
|
||||
|
@ -35,22 +38,49 @@ class AutoMod(View):
|
|||
form.save()
|
||||
form = forms.AutoModRuleForm()
|
||||
|
||||
data = {
|
||||
"rules": models.AutoMod.objects.all(),
|
||||
"form": form,
|
||||
"success": success,
|
||||
}
|
||||
data = automod_view_data()
|
||||
data["form"] = form
|
||||
return TemplateResponse(request, "settings/automod/rules.html", data)
|
||||
|
||||
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.moderate_user", raise_exception=True)
|
||||
@permission_required("bookwyrm.moderate_post", raise_exception=True)
|
||||
def schedule_automod_task(request):
|
||||
"""scheduler"""
|
||||
form = forms.IntervalScheduleForm(request.POST)
|
||||
if not form.is_valid():
|
||||
data = automod_view_data()
|
||||
data["task_form"] = form
|
||||
return TemplateResponse(request, "settings/automod/rules.html", data)
|
||||
|
||||
with transaction.atomic():
|
||||
schedule = form.save()
|
||||
PeriodicTask.objects.get_or_create(
|
||||
interval=schedule,
|
||||
name="automod-task",
|
||||
task="bookwyrm.models.antispam.automod_task",
|
||||
)
|
||||
return redirect("settings-automod")
|
||||
|
||||
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.moderate_user", raise_exception=True)
|
||||
@permission_required("bookwyrm.moderate_post", raise_exception=True)
|
||||
# pylint: disable=unused-argument
|
||||
def unschedule_automod_task(request, task_id):
|
||||
"""unscheduler"""
|
||||
get_object_or_404(PeriodicTask, id=task_id).delete()
|
||||
return redirect("settings-automod")
|
||||
|
||||
|
||||
@require_POST
|
||||
@permission_required("bookwyrm.moderate_user", raise_exception=True)
|
||||
@permission_required("bookwyrm.moderate_post", raise_exception=True)
|
||||
# pylint: disable=unused-argument
|
||||
def automod_delete(request, rule_id):
|
||||
"""Remove a rule"""
|
||||
rule = get_object_or_404(models.AutoMod, id=rule_id)
|
||||
rule.delete()
|
||||
get_object_or_404(models.AutoMod, id=rule_id).delete()
|
||||
return redirect("settings-automod")
|
||||
|
||||
|
||||
|
@ -62,3 +92,18 @@ def run_automod(request):
|
|||
"""run scan"""
|
||||
models.automod_task.delay()
|
||||
return redirect("settings-automod")
|
||||
|
||||
|
||||
def automod_view_data():
|
||||
"""helper to get data used in the template"""
|
||||
try:
|
||||
task = PeriodicTask.objects.get(name="automod-task")
|
||||
except PeriodicTask.DoesNotExist:
|
||||
task = None
|
||||
|
||||
return {
|
||||
"task": task,
|
||||
"task_form": forms.IntervalScheduleForm(),
|
||||
"rules": models.AutoMod.objects.all(),
|
||||
"form": forms.AutoModRuleForm(),
|
||||
}
|
||||
|
|
1
bw-dev
1
bw-dev
|
@ -215,6 +215,7 @@ case "$CMD" in
|
|||
;;
|
||||
setup)
|
||||
migrate
|
||||
migrate django_celery_beat
|
||||
initdb
|
||||
runweb python manage.py collectstatic --no-input
|
||||
admin_code
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
# pylint: disable=unused-wildcard-import
|
||||
from bookwyrm.settings import *
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
REDIS_BROKER_PASSWORD = requests.utils.quote(env("REDIS_BROKER_PASSWORD", None))
|
||||
REDIS_BROKER_HOST = env("REDIS_BROKER_HOST", "redis_broker")
|
||||
REDIS_BROKER_PORT = env("REDIS_BROKER_PORT", 6379)
|
||||
|
@ -16,6 +17,10 @@ CELERY_DEFAULT_QUEUE = "low_priority"
|
|||
CELERY_ACCEPT_CONTENT = ["json"]
|
||||
CELERY_TASK_SERIALIZER = "json"
|
||||
CELERY_RESULT_SERIALIZER = "json"
|
||||
|
||||
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
||||
CELERY_TIMEZONE = env("TIME_ZONE", "UTC")
|
||||
|
||||
FLOWER_PORT = env("FLOWER_PORT")
|
||||
|
||||
INSTALLED_APPS = INSTALLED_APPS + [
|
||||
|
|
|
@ -70,6 +70,19 @@ services:
|
|||
- db
|
||||
- redis_broker
|
||||
restart: on-failure
|
||||
celery_beat:
|
||||
env_file: .env
|
||||
build: .
|
||||
networks:
|
||||
- main
|
||||
command: celery -A celerywyrm beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
|
||||
volumes:
|
||||
- .:/app
|
||||
- static_volume:/app/static
|
||||
- media_volume:/app/images
|
||||
depends_on:
|
||||
- celery_worker
|
||||
restart: on-failure
|
||||
flower:
|
||||
build: .
|
||||
command: celery -A celerywyrm flower --basic_auth=${FLOWER_USER}:${FLOWER_PASSWORD}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
celery==5.2.2
|
||||
colorthief==0.2.1
|
||||
Django==3.2.12
|
||||
django-celery-beat==2.2.1
|
||||
django-compressor==2.4.1
|
||||
django-imagekit==4.1.0
|
||||
django-model-utils==4.0.0
|
||||
|
|
Loading…
Reference in a new issue