diff --git a/README.md b/README.md index 5c04278..e1956cc 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,7 @@ pre-commit install - refactor: removed non-execution related data from the database (clarifying the fact tha the source of truth is the registry) - refactor: better support for concurrent workers - refactor: better names for models and decorators + - refactor: optimise task exec admin listing when `results`, `stdout` or `stderr` holds large data - infra: included a demo project - infra: improved testing, including for concurrency behaviour - infra: updated compatibility to Django 3.2/4.1/4.2 and Python 3.8-3.11 diff --git a/django_toosimple_q/admin.py b/django_toosimple_q/admin.py index 435e3bd..be20749 100644 --- a/django_toosimple_q/admin.py +++ b/django_toosimple_q/admin.py @@ -61,7 +61,7 @@ class TaskExecAdmin(ReadOnlyAdmin): "started_", "finished_", "replaced_by_", - "result_", + "result_preview", "task_", ] list_display_links = ["task_name"] @@ -96,6 +96,12 @@ class TaskExecAdmin(ReadOnlyAdmin): ), ] + def get_queryset(self, request): + # defer stdout, stderr and results which may host large values + qs = super().get_queryset(request) + qs = qs.defer("stdout", "stderr", "result") + return qs + def arguments_(self, obj): return format_html( "{}
{}", @@ -103,9 +109,6 @@ def arguments_(self, obj): truncatechars(str(obj.kwargs), 32), ) - def result_(self, obj): - return truncatechars(str(obj.result), 32) - @admin.display(ordering="due") def due_(self, obj): return short_naturaltime(obj.due) diff --git a/django_toosimple_q/migrations/0015_taskexec_result_preview.py b/django_toosimple_q/migrations/0015_taskexec_result_preview.py new file mode 100644 index 0000000..25800aa --- /dev/null +++ b/django_toosimple_q/migrations/0015_taskexec_result_preview.py @@ -0,0 +1,29 @@ +# Generated by Django 4.1 on 2024-03-04 15:28 + +from django.db import migrations, models +from django.template.defaultfilters import truncatechars + + +def populate_result_preview(apps, schema_editor): + # Populate the new result preview field + TaskExec = apps.get_model("toosimpleq", "TaskExec") + for task_exec in TaskExec.objects.all(): + task_exec.result_preview = truncatechars(str(task_exec.result), 255) + task_exec.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("toosimpleq", "0014_alter_workerstatus_exit_code"), + ] + + operations = [ + migrations.AddField( + model_name="taskexec", + name="result_preview", + field=models.CharField( + blank=True, editable=False, max_length=255, null=True + ), + ), + migrations.RunPython(populate_result_preview), + ] diff --git a/django_toosimple_q/models.py b/django_toosimple_q/models.py index 293a7e2..b68d77b 100644 --- a/django_toosimple_q/models.py +++ b/django_toosimple_q/models.py @@ -6,6 +6,7 @@ from croniter import croniter, croniter_range from django.db import models +from django.template.defaultfilters import truncatechars from django.utils import timezone from django.utils.functional import cached_property from django.utils.timezone import now @@ -86,6 +87,9 @@ def done(cls) -> List[str]: max_length=32, choices=States.choices, default=States.QUEUED ) result = PickledObjectField(blank=True, null=True) + result_preview = models.CharField( + max_length=255, blank=True, null=True, editable=False + ) error = models.TextField(blank=True, null=True) replaced_by = models.ForeignKey( "self", null=True, blank=True, on_delete=models.SET_NULL @@ -122,6 +126,7 @@ def execute(self): stdout, stderr = io.StringIO(), io.StringIO() with redirect_stderr(stderr), redirect_stdout(stdout): self.result = task.callable(*self.args, **self.kwargs) + self.result_preview = truncatechars(str(self.result), 255) logger.info(f"{self} succeeded") self.state = TaskExec.States.SUCCEEDED except Exception: