Commit 0169f92f authored by Mickaël Desfrênes's avatar Mickaël Desfrênes
Browse files

add tasks admin/status pages

parent afb6f63c
Loading
Loading
Loading
Loading
+45 −0
Original line number Diff line number Diff line
{% extends 'common.html' %}
{% block title %}
    Task Result Details
{% endblock %}
{% block content %}
    <style>
        body {
            max-width: unset;
            width: 100%;
        }
        .task-result-details-wrapper {
            width: 100%;
        }
        .task-result-details-wrapper table {
            width: 100%;
            table-layout: fixed;
        }
        .task-result-details-wrapper th,
        .task-result-details-wrapper td {
            vertical-align: top;
        }
    </style>
    <div class="task-result-details-wrapper">
    <h1>DB Task Result Details</h1>
    <p>
        <a href="{% url 'task_results' %}">← Retour à la liste</a>
    </p>
    <table>
        <thead>
        <tr>
            <th>champ</th>
            <th>valeur</th>
        </tr>
        </thead>
        <tbody>
        {% for row in detail_rows %}
            <tr>
                <td><strong>{{ row.name }}</strong></td>
                <td><pre style="margin:0;white-space:pre-wrap;">{{ row.value }}</pre></td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
    </div>
{% endblock %}
+113 −0
Original line number Diff line number Diff line
{% extends 'common.html' %}
{% block title %}
    Task Results
{% endblock %}
{% block content %}
    <style>
        body {
            max-width: unset;
            width: 100%;
        }
        .task-results-wrapper {
            width: 100%;
        }
        .task-results-wrapper table {
            width: 100%;
            table-layout: fixed;
        }
        .task-results-wrapper th,
        .task-results-wrapper td {
            vertical-align: top;
        }
    </style>
    <div class="task-results-wrapper">
    <h1>DB Task Results</h1>
    <p>
        {{ page_obj.paginator.count }} résultat(s) au total.
        Affichage page {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}.
    </p>

    <form method="get">
        <input type="hidden" name="sort" value="{{ sort }}">
        <p>
            <button type="submit">Filtrer</button>
            <a href="{{ request.path }}">Réinitialiser</a>
        </p>
        <div style="overflow-x: auto;">
            <table>
                <thead>
                <tr>
                    {% for column in columns %}
                        <th>
                            {% if column.sortable %}
                                <a href="?{{ column.sort_link }}">
                                    {{ column.label }}
                                    {% if column.is_sorted %}
                                        {% if sort_desc %}▼{% else %}▲{% endif %}
                                    {% endif %}
                                </a>
                            {% else %}
                                {{ column.label }}
                            {% endif %}
                        </th>
                    {% endfor %}
                </tr>
                <tr>
                    {% for column in columns %}
                        <th>
                            {% if column.filterable %}
                                {% if column.name == "status" %}
                                    <select name="f_status">
                                        <option value="">Tous statuts</option>
                                        {% for status_value in status_filter_values %}
                                            <option value="{{ status_value }}" {% if column.filter_value == status_value %}selected{% endif %}>
                                                {{ status_value }}
                                            </option>
                                        {% endfor %}
                                    </select>
                                {% else %}
                                    <input
                                        type="text"
                                        name="f_{{ column.name }}"
                                        value="{{ column.filter_value }}"
                                        placeholder="Filtrer {{ column.name }}"
                                    />
                                {% endif %}
                            {% endif %}
                        </th>
                    {% endfor %}
                </tr>
                </thead>
                <tbody>
                {% for row in rows %}
                    <tr>
                        {% for value in row %}
                            <td>
                                {% if forloop.first %}
                                    <a href="{{ value }}"><button type="button">details</button></a>
                                {% else %}
                                    <pre style="margin:0;white-space:pre-wrap;">{{ value }}</pre>
                                {% endif %}
                            </td>
                        {% endfor %}
                    </tr>
                {% empty %}
                    <tr>
                        <td colspan="{{ columns|length }}">Aucun résultat.</td>
                    </tr>
                {% endfor %}
                </tbody>
            </table>
        </div>
    </form>

    <p>
        {% if previous_page_link %}
            <a href="?{{ previous_page_link }}">← Page précédente</a>
        {% endif %}
        {% if next_page_link %}
            <a href="?{{ next_page_link }}" style="margin-left: 1em;">Page suivante →</a>
        {% endif %}
    </p>
    </div>
{% endblock %}
+10 −0
Original line number Diff line number Diff line
@@ -14,6 +14,16 @@ urlpatterns = [
    path(settings.JAMA_URL_BASE_PATH + "rpc/", include("rpc.urls")),
    path(settings.JAMA_URL_BASE_PATH + "resources/", include("resources.urls")),
    path(settings.JAMA_URL_BASE_PATH + "status", views.status),
    path(
        settings.JAMA_URL_BASE_PATH + "task-results/",
        views.task_results,
        name="task_results",
    ),
    path(
        settings.JAMA_URL_BASE_PATH + "task-results/<uuid:task_result_id>/",
        views.task_result_details,
        name="task_result_details",
    ),
    path(settings.JAMA_URL_BASE_PATH + "iiif/", include("iiif.urls", namespace="iiif")),
    path(settings.JAMA_URL_BASE_PATH + "hls/", include("hls.urls", namespace="hls")),
]
+227 −92
Original line number Diff line number Diff line
@@ -2,6 +2,12 @@ from django.http import HttpResponse, HttpRequest
from django.shortcuts import render, get_object_or_404, redirect
from jama import settings
from django.contrib.auth.models import User
from django.contrib.auth.decorators import user_passes_test
from django.core.paginator import Paginator
from django.db.models import F, TextField
from django.db.models.functions import Cast
from django.urls import reverse
from django_tasks_db.models import DBTaskResult
from resources.models import Resource, Collection
from urllib.parse import urlparse, parse_qs, urlencode, urlunparse

@@ -90,3 +96,132 @@ def ark_collection(request: HttpRequest, collection_id: int) -> HttpResponse:
            location = merge_query_string_to_url(location, request.META["QUERY_STRING"])
        return redirect(location)
    return HttpResponse("Jama Collection ARK location placeholder")


@user_passes_test(lambda user: user.is_superuser)
def task_results(request: HttpRequest) -> HttpResponse:
    fields = list(DBTaskResult._meta.fields)
    field_names = [field.name for field in fields]
    status_filter_values = ["SUCCESSFUL", "FAILED", "RUNNING", "READY"]
    displayed_field_names = ["task_path", "args_kwargs", "enqueued_at", "status"]
    displayed_field_labels = {
        "task_path": "task_path",
        "args_kwargs": "args/kwargs",
        "enqueued_at": "enqueued_at",
        "status": "status",
    }
    filter_values = {
        field_name: request.GET.get(f"f_{field_name}", "").strip()
        for field_name in field_names
    }

    task_results_queryset = DBTaskResult.objects.all()
    for idx, (field_name, filter_value) in enumerate(filter_values.items()):
        if not filter_value:
            continue
        if field_name == "status":
            if filter_value not in status_filter_values:
                continue
            task_results_queryset = task_results_queryset.filter(status=filter_value)
            continue
        # cast any field type to text so filtering can be done uniformly
        alias = f"_as_text_{idx}"
        task_results_queryset = task_results_queryset.annotate(
            **{alias: Cast(F(field_name), output_field=TextField())}
        ).filter(**{f"{alias}__icontains": filter_value})

    sort = request.GET.get("sort", "-enqueued_at")
    sort_field = sort[1:] if sort.startswith("-") else sort
    if sort_field not in field_names:
        sort = "-enqueued_at" if "enqueued_at" in field_names else field_names[0]
        sort_field = sort[1:] if sort.startswith("-") else sort

    task_results_queryset = task_results_queryset.order_by(sort)
    sort_desc = sort.startswith("-")

    def build_query_string(**updates) -> str:
        query = request.GET.copy()
        for key, value in updates.items():
            if value is None:
                query.pop(key, None)
            else:
                query[key] = str(value)
        return query.urlencode()

    columns = [
        {
            "name": "details",
            "label": "details",
            "sort_link": None,
            "filter_value": "",
            "is_sorted": False,
            "sortable": False,
            "filterable": False,
        }
    ]
    for field_name in displayed_field_names:
        next_sort = (
            f"-{field_name}"
            if sort_field == field_name and not sort_desc
            else field_name
        )
        columns.append(
            {
                "name": field_name,
                "label": displayed_field_labels[field_name],
                "sort_link": build_query_string(sort=next_sort, page=1),
                "filter_value": filter_values[field_name],
                "is_sorted": sort_field == field_name,
                "sortable": True,
                "filterable": True,
            }
        )

    paginator = Paginator(task_results_queryset, 100)
    page_obj = paginator.get_page(request.GET.get("page"))
    rows = []
    for item in page_obj.object_list:
        row = [
            reverse("task_result_details", kwargs={"task_result_id": item.id}),
        ]
        row.extend(
            [getattr(item, field_name, None) for field_name in displayed_field_names]
        )
        rows.append(row)

    previous_page_link = None
    next_page_link = None
    if page_obj.has_previous():
        previous_page_link = build_query_string(page=page_obj.previous_page_number())
    if page_obj.has_next():
        next_page_link = build_query_string(page=page_obj.next_page_number())

    return render(
        request,
        "task_results.html",
        {
            "columns": columns,
            "rows": rows,
            "sort": sort,
            "sort_desc": sort_desc,
            "status_filter_values": status_filter_values,
            "page_obj": page_obj,
            "previous_page_link": previous_page_link,
            "next_page_link": next_page_link,
        },
    )


@user_passes_test(lambda user: user.is_superuser)
def task_result_details(request: HttpRequest, task_result_id) -> HttpResponse:
    task_result = get_object_or_404(DBTaskResult, pk=task_result_id)
    detail_rows = []
    for field in DBTaskResult._meta.fields:
        detail_rows.append(
            {"name": field.name, "value": getattr(task_result, field.name, None)}
        )
    return render(
        request,
        "task_result_details.html",
        {"task_result": task_result, "detail_rows": detail_rows},
    )