Loading src/jama/templates/task_results.html +61 −2 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ } .task-results-wrapper table { width: 100%; table-layout: fixed; table-layout: auto; } .task-results-wrapper th, .task-results-wrapper td { Loading @@ -29,7 +29,7 @@ <form method="get"> <input type="hidden" name="sort" value="{{ sort }}"> <p> <p id="filter-actions"> <button type="submit">Filtrer</button> <a href="{{ request.path }}">Réinitialiser</a> </p> Loading Loading @@ -65,6 +65,24 @@ </option> {% endfor %} </select> {% elif column.name == "task_path" %} <select name="f_task_path"> <option value="">Tous task_path</option> {% for task_path_value in task_path_filter_values %} <option value="{{ task_path_value }}" {% if column.filter_value == task_path_value %}selected{% endif %}> {{ task_path_value }} </option> {% endfor %} </select> {% elif column.name == "enqueued_at" %} <select name="f_enqueued_at_date"> <option value="">Toutes dates</option> {% for enqueued_date in enqueued_at_date_filter_values %} <option value="{{ enqueued_date }}" {% if column.filter_value == enqueued_date %}selected{% endif %}> {{ enqueued_date }} </option> {% endfor %} </select> {% else %} <input type="text" Loading Loading @@ -109,5 +127,46 @@ <a href="?{{ next_page_link }}" style="margin-left: 1em;">Page suivante →</a> {% endif %} </p> <script> document.addEventListener("DOMContentLoaded", function () { const form = document.querySelector("form[method='get']"); if (!form) { return; } function submitFiltersForm() { if (form.requestSubmit) { form.requestSubmit(); } else { form.submit(); } } const actions = document.getElementById("filter-actions"); if (actions) { actions.style.display = "none"; } const filterFields = form.querySelectorAll( "input[name^='f_'], select[name^='f_']" ); const lastSubmittedFilterValues = {}; function maybeSubmitForField(field) { const currentValue = field.value; if (lastSubmittedFilterValues[field.name] === currentValue) { return; } lastSubmittedFilterValues[field.name] = currentValue; submitFiltersForm(); } for (const field of filterFields) { lastSubmittedFilterValues[field.name] = field.value; field.addEventListener("change", function () { maybeSubmitForField(field); }); } }); </script> </div> {% endblock %} src/jama/views.py +40 −3 Original line number Diff line number Diff line Loading @@ -5,8 +5,9 @@ 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.db.models.functions import Cast, TruncDate from django.urls import reverse from django.utils.dateparse import parse_date from django_tasks_db.models import DBTaskResult from resources.models import Resource, Collection from urllib.parse import urlparse, parse_qs, urlencode, urlunparse Loading Loading @@ -103,10 +104,24 @@ 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"] task_path_filter_values = list( DBTaskResult.objects.order_by("task_path") .values_list("task_path", flat=True) .distinct() ) valid_task_paths = set(task_path_filter_values) enqueued_at_date_filter_values = [ d.isoformat() for d in DBTaskResult.objects.annotate(enqueued_date=TruncDate("enqueued_at")) .values_list("enqueued_date", flat=True) .order_by("-enqueued_date") .distinct() if d is not None ] valid_enqueued_at_dates = set(enqueued_at_date_filter_values) displayed_field_names = ["task_path", "enqueued_at", "status"] displayed_field_labels = { "task_path": "task_path", "args_kwargs": "args/kwargs", "enqueued_at": "enqueued_at", "status": "status", } Loading @@ -114,16 +129,36 @@ def task_results(request: HttpRequest) -> HttpResponse: field_name: request.GET.get(f"f_{field_name}", "").strip() for field_name in field_names } if "f_status" not in request.GET: filter_values["status"] = "FAILED" enqueued_at_date_value = request.GET.get("f_enqueued_at_date", "").strip() # keep selected value aligned with displayed column filter state filter_values["enqueued_at"] = enqueued_at_date_value task_results_queryset = DBTaskResult.objects.all() if enqueued_at_date_value in valid_enqueued_at_dates: selected_date = parse_date(enqueued_at_date_value) if selected_date: task_results_queryset = task_results_queryset.filter( enqueued_at__date=selected_date ) for idx, (field_name, filter_value) in enumerate(filter_values.items()): if not filter_value: continue if field_name == "enqueued_at": # this field is filtered with f_enqueued_at_from / f_enqueued_at_to 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 if field_name == "task_path": if filter_value not in valid_task_paths: continue task_results_queryset = task_results_queryset.filter(task_path=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( Loading Loading @@ -205,6 +240,8 @@ def task_results(request: HttpRequest) -> HttpResponse: "sort": sort, "sort_desc": sort_desc, "status_filter_values": status_filter_values, "task_path_filter_values": task_path_filter_values, "enqueued_at_date_filter_values": enqueued_at_date_filter_values, "page_obj": page_obj, "previous_page_link": previous_page_link, "next_page_link": next_page_link, Loading Loading
src/jama/templates/task_results.html +61 −2 Original line number Diff line number Diff line Loading @@ -13,7 +13,7 @@ } .task-results-wrapper table { width: 100%; table-layout: fixed; table-layout: auto; } .task-results-wrapper th, .task-results-wrapper td { Loading @@ -29,7 +29,7 @@ <form method="get"> <input type="hidden" name="sort" value="{{ sort }}"> <p> <p id="filter-actions"> <button type="submit">Filtrer</button> <a href="{{ request.path }}">Réinitialiser</a> </p> Loading Loading @@ -65,6 +65,24 @@ </option> {% endfor %} </select> {% elif column.name == "task_path" %} <select name="f_task_path"> <option value="">Tous task_path</option> {% for task_path_value in task_path_filter_values %} <option value="{{ task_path_value }}" {% if column.filter_value == task_path_value %}selected{% endif %}> {{ task_path_value }} </option> {% endfor %} </select> {% elif column.name == "enqueued_at" %} <select name="f_enqueued_at_date"> <option value="">Toutes dates</option> {% for enqueued_date in enqueued_at_date_filter_values %} <option value="{{ enqueued_date }}" {% if column.filter_value == enqueued_date %}selected{% endif %}> {{ enqueued_date }} </option> {% endfor %} </select> {% else %} <input type="text" Loading Loading @@ -109,5 +127,46 @@ <a href="?{{ next_page_link }}" style="margin-left: 1em;">Page suivante →</a> {% endif %} </p> <script> document.addEventListener("DOMContentLoaded", function () { const form = document.querySelector("form[method='get']"); if (!form) { return; } function submitFiltersForm() { if (form.requestSubmit) { form.requestSubmit(); } else { form.submit(); } } const actions = document.getElementById("filter-actions"); if (actions) { actions.style.display = "none"; } const filterFields = form.querySelectorAll( "input[name^='f_'], select[name^='f_']" ); const lastSubmittedFilterValues = {}; function maybeSubmitForField(field) { const currentValue = field.value; if (lastSubmittedFilterValues[field.name] === currentValue) { return; } lastSubmittedFilterValues[field.name] = currentValue; submitFiltersForm(); } for (const field of filterFields) { lastSubmittedFilterValues[field.name] = field.value; field.addEventListener("change", function () { maybeSubmitForField(field); }); } }); </script> </div> {% endblock %}
src/jama/views.py +40 −3 Original line number Diff line number Diff line Loading @@ -5,8 +5,9 @@ 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.db.models.functions import Cast, TruncDate from django.urls import reverse from django.utils.dateparse import parse_date from django_tasks_db.models import DBTaskResult from resources.models import Resource, Collection from urllib.parse import urlparse, parse_qs, urlencode, urlunparse Loading Loading @@ -103,10 +104,24 @@ 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"] task_path_filter_values = list( DBTaskResult.objects.order_by("task_path") .values_list("task_path", flat=True) .distinct() ) valid_task_paths = set(task_path_filter_values) enqueued_at_date_filter_values = [ d.isoformat() for d in DBTaskResult.objects.annotate(enqueued_date=TruncDate("enqueued_at")) .values_list("enqueued_date", flat=True) .order_by("-enqueued_date") .distinct() if d is not None ] valid_enqueued_at_dates = set(enqueued_at_date_filter_values) displayed_field_names = ["task_path", "enqueued_at", "status"] displayed_field_labels = { "task_path": "task_path", "args_kwargs": "args/kwargs", "enqueued_at": "enqueued_at", "status": "status", } Loading @@ -114,16 +129,36 @@ def task_results(request: HttpRequest) -> HttpResponse: field_name: request.GET.get(f"f_{field_name}", "").strip() for field_name in field_names } if "f_status" not in request.GET: filter_values["status"] = "FAILED" enqueued_at_date_value = request.GET.get("f_enqueued_at_date", "").strip() # keep selected value aligned with displayed column filter state filter_values["enqueued_at"] = enqueued_at_date_value task_results_queryset = DBTaskResult.objects.all() if enqueued_at_date_value in valid_enqueued_at_dates: selected_date = parse_date(enqueued_at_date_value) if selected_date: task_results_queryset = task_results_queryset.filter( enqueued_at__date=selected_date ) for idx, (field_name, filter_value) in enumerate(filter_values.items()): if not filter_value: continue if field_name == "enqueued_at": # this field is filtered with f_enqueued_at_from / f_enqueued_at_to 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 if field_name == "task_path": if filter_value not in valid_task_paths: continue task_results_queryset = task_results_queryset.filter(task_path=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( Loading Loading @@ -205,6 +240,8 @@ def task_results(request: HttpRequest) -> HttpResponse: "sort": sort, "sort_desc": sort_desc, "status_filter_values": status_filter_values, "task_path_filter_values": task_path_filter_values, "enqueued_at_date_filter_values": enqueued_at_date_filter_values, "page_obj": page_obj, "previous_page_link": previous_page_link, "next_page_link": next_page_link, Loading