Admin improvements (#11)

* Add the group column to OrmQ admin page

* Add group field to tasks list and search_field

* Add search by name to Schedule admin page

* Limit succesful task history per schedule with SAVE_LIMIT_GROUP config

* Improve ScheduleAdmin list view performance

Co-authored-by: Marc Sabatier <marc@sabatier.online>
This commit is contained in:
Stan Triepels
2022-10-14 17:13:48 +02:00
committed by GitHub
co-authored by Marc Sabatier
parent 4aa6325ae8
commit e3f959c258
4 changed files with 61 additions and 17 deletions
+34 -8
View File
@@ -1,16 +1,19 @@
"""Admin module for Django.""" """Admin module for Django."""
from django.urls import reverse
from django.utils.html import format_html
from django.contrib import admin from django.contrib import admin
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.db.models.expressions import OuterRef, Subquery
from django_q.conf import Conf, croniter from django_q.conf import Conf, croniter
from django_q.models import Failure, OrmQ, Schedule, Success from django_q.models import Failure, OrmQ, Schedule, Success, Task
from django_q.tasks import async_task from django_q.tasks import async_task
class TaskAdmin(admin.ModelAdmin): class TaskAdmin(admin.ModelAdmin):
"""model admin for success tasks.""" """model admin for success tasks."""
list_display = ("name", "func", "started", "stopped", "time_taken", "group") list_display = ("name", "group", "func", "started", "stopped", "time_taken")
def has_add_permission(self, request): def has_add_permission(self, request):
"""Don't allow adds.""" """Don't allow adds."""
@@ -43,14 +46,14 @@ retry_failed.short_description = _("Resubmit selected tasks to queue")
class FailAdmin(admin.ModelAdmin): class FailAdmin(admin.ModelAdmin):
"""model admin for failed tasks.""" """model admin for failed tasks."""
list_display = ("name", "func", "started", "stopped", "short_result") list_display = ("name", "group", "func", "started", "stopped", "short_result")
def has_add_permission(self, request): def has_add_permission(self, request):
"""Don't allow adds.""" """Don't allow adds."""
return False return False
actions = [retry_failed] actions = [retry_failed]
search_fields = ("name", "func") search_fields = ("name", "func", "group")
list_filter = ("group",) list_filter = ("group",)
readonly_fields = [] readonly_fields = []
@@ -70,8 +73,8 @@ class ScheduleAdmin(admin.ModelAdmin):
"repeats", "repeats",
"cluster", "cluster",
"next_run", "next_run",
"last_run", "get_last_run",
"success", "get_success",
) )
# optional cron strings # optional cron strings
@@ -79,14 +82,37 @@ class ScheduleAdmin(admin.ModelAdmin):
readonly_fields = ("cron",) readonly_fields = ("cron",)
list_filter = ("next_run", "schedule_type", "cluster") list_filter = ("next_run", "schedule_type", "cluster")
search_fields = ("func",) search_fields = ("name", "func",)
list_display_links = ("id", "name") list_display_links = ("id", "name")
def get_queryset(self, request):
qs = super().get_queryset(request)
task_query = Task.objects.filter(id=OuterRef('task')).values('id', 'name', 'success')
qs = qs.annotate(task_id=Subquery(task_query.values('id')), task_name=Subquery(task_query.values('name')),
task_success=Subquery(task_query.values('success')))
return qs
def get_success(self, obj):
return obj.task_success
get_success.boolean = True
get_success.short_description = _("success")
def get_last_run(self, obj):
if obj.task_name is not None:
if obj.task_success:
url = reverse("admin:django_q_success_change", args=(obj.task_id,))
else:
url = reverse("admin:django_q_failure_change", args=(obj.task_id,))
return format_html(f'<a href="{url}">[{obj.task_name}]</a>')
return None
get_last_run.allow_tags = True
get_last_run.short_description = _("last_run")
class QueueAdmin(admin.ModelAdmin): class QueueAdmin(admin.ModelAdmin):
"""queue admin for ORM broker""" """queue admin for ORM broker"""
list_display = ("id", "key", "task_id", "name", "func", "lock") list_display = ("id", "key", "name", "group", "func", "lock", "task_id")
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
obj.save(using=Conf.ORM) obj.save(using=Conf.ORM)
+15 -7
View File
@@ -479,12 +479,21 @@ def save_task(task, broker: Broker):
# SAVE LIMIT > 0: Prune database, SAVE_LIMIT 0: No pruning # SAVE LIMIT > 0: Prune database, SAVE_LIMIT 0: No pruning
close_old_django_connections() close_old_django_connections()
try: try:
with db.transaction.atomic(): if task["success"]:
last = Success.objects.select_for_update().last() # first apply per group success history limit
if task["success"] and 0 < Conf.SAVE_LIMIT <= Success.objects.count(): if "group" in task:
last.delete() with db.transaction.atomic():
qs = Success.objects.filter(group=task["group"])
last = qs.select_for_update().last()
if Conf.SAVE_LIMIT_PER_GROUP <= qs.count():
last.delete()
# then apply global success history limit
with db.transaction.atomic():
last = Success.objects.select_for_update().last()
if Conf.SAVE_LIMIT <= Success.objects.count():
last.delete()
# check if this task has previous results # check if this task has previous results
if Task.objects.filter(id=task["id"], name=task["name"]).exists(): try:
existing_task = Task.objects.get(id=task["id"], name=task["name"]) existing_task = Task.objects.get(id=task["id"], name=task["name"])
# only update the result if it hasn't succeeded yet # only update the result if it hasn't succeeded yet
if not existing_task.success: if not existing_task.success:
@@ -499,8 +508,7 @@ def save_task(task, broker: Broker):
and existing_task.attempt_count >= Conf.MAX_ATTEMPTS and existing_task.attempt_count >= Conf.MAX_ATTEMPTS
): ):
broker.acknowledge(task["ack_id"]) broker.acknowledge(task["ack_id"])
except Task.DoesNotExist:
else:
func = task["func"] func = task["func"]
# convert func to string # convert func to string
if inspect.isfunction(func): if inspect.isfunction(func):
+4
View File
@@ -86,6 +86,10 @@ class Conf:
# Failures are always saved # Failures are always saved
SAVE_LIMIT = conf.get("save_limit", 250) SAVE_LIMIT = conf.get("save_limit", 250)
# Maximum number of successful tasks of the same group kept in the database. 0 saves everything. -1 saves none
# Failures are always saved
SAVE_LIMIT_PER_GROUP = conf.get("save_limit_per_group", 5)
# Guard loop sleep in seconds. Should be between 0 and 60 seconds. # Guard loop sleep in seconds. Should be between 0 and 60 seconds.
GUARD_CYCLE = conf.get("guard_cycle", 0.5) GUARD_CYCLE = conf.get("guard_cycle", 0.5)
+6
View File
@@ -220,7 +220,10 @@ class Schedule(models.Model):
return self.func return self.func
success.boolean = True success.boolean = True
success.short_description = _("success")
last_run.allow_tags = True last_run.allow_tags = True
last_run.short_description = _("last_run")
class Meta: class Meta:
app_label = "django_q" app_label = "django_q"
@@ -246,6 +249,9 @@ class OrmQ(models.Model):
def name(self): def name(self):
return self.task()["name"] return self.task()["name"]
def group(self):
return self.task().get("group")
class Meta: class Meta:
app_label = "django_q" app_label = "django_q"
verbose_name = _("Queued task") verbose_name = _("Queued task")