Add `LoggedCommand` to JobLogs in django admin interface

This commit is contained in:
Daniel Egger 2023-08-25 17:32:17 +02:00
parent 6badbc480c
commit 5790fac78f
7 changed files with 171 additions and 4 deletions

View File

@ -2,9 +2,29 @@ from django.contrib import admin
from django.contrib.auth import admin as auth_admin, get_user_model
from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.models import JobLog
from vbv_lernwelt.core.utils import pretty_print_json
User = get_user_model()
class LogAdmin(admin.ModelAdmin):
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None):
return False
def get_readonly_fields(self, request, obj=None):
# set all fields read only
return [field.name for field in self.opts.local_fields] + [
field.name for field in self.opts.local_many_to_many
]
def pretty_print_json(self, json_string):
return pretty_print_json(json_string)
@admin.register(User)
class UserAdmin(auth_admin.UserAdmin):
fieldsets = (
@ -34,3 +54,27 @@ class UserAdmin(auth_admin.UserAdmin):
"sso_id",
]
search_fields = ["first_name", "last_name", "email", "username", "sso_id"]
@admin.register(JobLog)
class JobLogAdmin(LogAdmin):
date_hierarchy = "started"
list_display = (
"job_name",
"started",
"ended",
"runtime",
"success",
)
list_filter = ["job_name", "success"]
def json_data_pretty(self, instance):
return self.pretty_print_json(instance.json_data)
def get_readonly_fields(self, request, obj=None):
return super().get_readonly_fields(request, obj) + ["json_data_pretty"]
def runtime(self, obj):
if obj.ended:
return (obj.ended - obj.started).seconds // 60
return None

View File

@ -0,0 +1,38 @@
import sys
import traceback
import structlog
from django.core.management.base import BaseCommand
from django.utils import timezone
from vbv_lernwelt.core.models import JobLog
logger = structlog.get_logger(__name__)
# pylint: disable=abstract-method
class LoggedCommand(BaseCommand):
def execute(self, *args, **options):
name = getattr(self, "name", "")
if not name:
name = sys.argv[1]
logger.info("start LoggedCommand", job_name=name, label="job_log")
self.job_log = JobLog.objects.create(job_name=name)
try:
super(LoggedCommand, self).execute(*args, **options)
self.job_log.refresh_from_db()
self.job_log.ended = timezone.now()
self.job_log.success = True
self.job_log.save()
logger.info(
"LoggedCommand successfully ended", job_name=name, label="job_log"
)
except Exception as e:
self.job_log.refresh_from_db()
self.job_log.error_message = str(e)
self.job_log.stack_trace = traceback.format_exc()
self.job_log.save()
logger.error("LoggedCommand failed", job_name=name, label="job_log")
raise e

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.20 on 2023-08-25 15:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="JobLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("started", models.DateTimeField(auto_now_add=True)),
("ended", models.DateTimeField(blank=True, null=True)),
("job_name", models.CharField(max_length=255)),
("success", models.BooleanField(default=False)),
("error_message", models.TextField(blank=True, default="")),
("stack_trace", models.TextField(blank=True, default="")),
("json_data", models.JSONField(default=dict)),
],
),
]

View File

@ -41,3 +41,16 @@ class SecurityRequestResponseLog(models.Model):
response_status_code = models.CharField(max_length=255, blank=True, default="")
additional_json_data = JSONField(default=dict, blank=True)
class JobLog(models.Model):
started = models.DateTimeField(auto_now_add=True)
ended = models.DateTimeField(blank=True, null=True)
job_name = models.CharField(max_length=255)
success = models.BooleanField(default=False)
error_message = models.TextField(blank=True, default="")
stack_trace = models.TextField(blank=True, default="")
json_data = models.JSONField(default=dict)
def __str__(self):
return "{job_name} {started:%H:%M %d.%m.%Y}".format(**self.__dict__)

View File

@ -38,3 +38,16 @@ def replace_whitespace(text, replacement=" "):
def get_django_content_type(obj):
return obj._meta.app_label + "." + type(obj).__name__
def pretty_print_json(json_string):
try:
parsed = json_string
if isinstance(json_string, str):
parsed = json.loads(json_string)
return mark_safe(
"<pre>{}</pre>".format(json.dumps(parsed, indent=4, sort_keys=True))
)
# pylint: disable=broad-except
except Exception:
return json_string

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.20 on 2023-08-25 15:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("course_session", "0004_coursesessionedoniqtest"),
]
operations = [
migrations.AlterModelOptions(
name="coursesessionassignment",
options={"ordering": ["course_session", "submission_deadline__start"]},
),
migrations.AlterModelOptions(
name="coursesessionattendancecourse",
options={"ordering": ["course_session", "due_date__start"]},
),
migrations.AlterModelOptions(
name="coursesessionedoniqtest",
options={"ordering": ["course_session", "deadline__start"]},
),
]

View File

@ -1,10 +1,10 @@
from datetime import timedelta
import djclick as click
import structlog
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from vbv_lernwelt.core.base import LoggedCommand
from vbv_lernwelt.core.models import User
from vbv_lernwelt.course.models import CourseSessionUser
from vbv_lernwelt.course_session.models import CourseSessionAttendanceCourse
@ -55,6 +55,8 @@ def attendance_course_reminder_notification_job():
logger.info("No attendance courses found")
@click.command()
def command():
attendance_course_reminder_notification_job()
class Command(LoggedCommand):
help = "Sends attendance course reminder notifications to participants"
def handle(self, *args, **options):
attendance_course_reminder_notification_job()