diff --git a/atdb/requirements/base.txt b/atdb/requirements/base.txt index c52d7065c7e389f671bdbd370b3da06d3a492a40..ac71b471c94cb22ea3a47e7a4ae72571378190e4 100644 --- a/atdb/requirements/base.txt +++ b/atdb/requirements/base.txt @@ -1,7 +1,7 @@ Django==3.1.4 djangorestframework==3.12.2 django-filter==2.3.0 -psycopg2==2.8.6 +psycopg2-binary==2.9.3 django-cors-headers==3.6.0 django-extensions==3.1.0 django-bootstrap-pagination==1.7.0 diff --git a/atdb/taskdatabase/services/algorithms.py b/atdb/taskdatabase/services/algorithms.py index d1f5dc250c95db46bef0c6424055c41f68f3868c..91b45e6fe0cd3d532065bdc091acc9d49c6dbc18 100644 --- a/atdb/taskdatabase/services/algorithms.py +++ b/atdb/taskdatabase/services/algorithms.py @@ -554,3 +554,8 @@ def construct_dashboard_html(request, selection): results_logs = construct_logs_per_workflow_html(request, log_records) return results_tasks, results_logs + + +def unique_values_for_aggregation_key(queryset, aggregation_key): + + return list(map(lambda x: x[aggregation_key], queryset.values(aggregation_key).distinct())) \ No newline at end of file diff --git a/atdb/taskdatabase/urls.py b/atdb/taskdatabase/urls.py index df6c25f048cace4621a808efa6e70adec0b989d5..e669d7290bd8d656d27c5489250ea1f09df315b4 100644 --- a/atdb/taskdatabase/urls.py +++ b/atdb/taskdatabase/urls.py @@ -61,6 +61,9 @@ urlpatterns = [ path('tasks/get_size/', views.GetSizeView.as_view(), name='get-size-view'), # /atdb/get_min_start_and_max_end_time?sas_id=65005 path('get_min_start_and_max_end_time/', views.GetMinMaxTimeView.as_view(), name='get-min-start-and-max-end-time-view'), + # /atdb/get_unique_values_for_key/<aggregation_key> + path('get_unique_values_for_key/<str:aggregation_key>', views.GetUniqueValuesForKey.as_view(), + name='get-unique-values-for-key-view'), # --- controller resources --- path('tasks/<int:pk>/setstatus/<new_status>/<page>', views.TaskSetStatus, name='task-setstatus-view'), diff --git a/atdb/taskdatabase/views.py b/atdb/taskdatabase/views.py index a3542fd6662d4a10495f919d8e2f5fa99c711be1..933e2c7e324aa4d910cc9be2b22f1290e5ca1fd1 100644 --- a/atdb/taskdatabase/views.py +++ b/atdb/taskdatabase/views.py @@ -1,4 +1,3 @@ - import logging import json @@ -20,6 +19,7 @@ from django_tables2 import SingleTableView from django.shortcuts import render, redirect from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from rest_framework.request import Request from django.conf import settings from .models import Task, Status, Workflow, LogEntry, Configuration, Job, PostProcessingRule, Monitor @@ -30,9 +30,9 @@ from .serializers import \ TaskWriteSerializer, \ TaskReadSerializer, \ TaskReadSerializerFast, \ - WorkflowSerializer,\ - LogEntrySerializer,\ - ConfigurationSerializer,\ + WorkflowSerializer, \ + LogEntrySerializer, \ + ConfigurationSerializer, \ JobSerializer, \ PostProcessingRuleSerializer, \ MonitorSerializer @@ -41,9 +41,9 @@ from .services import algorithms logger = logging.getLogger(__name__) + # ---------- filters (in the REST API) --------- class TaskFilter(filters.FilterSet): - class Meta: model = Task @@ -51,18 +51,19 @@ class TaskFilter(filters.FilterSet): 'task_type': ['exact', 'icontains', 'in'], 'creationTime': ['icontains'], 'filter': ['exact', 'icontains'], - 'workflow__id' : ['exact', 'icontains'], + 'workflow__id': ['exact', 'icontains'], 'project': ['exact', 'icontains'], - 'sas_id': ['exact', 'icontains','in'], + 'sas_id': ['exact', 'icontains', 'in'], 'status': ['exact', 'icontains', 'in', 'startswith'], 'purge_policy': ['exact'], - 'priority': ['exact','lte','gte'], + 'priority': ['exact', 'lte', 'gte'], 'resume': ['exact'], # http://localhost:8000/atdb/tasks/?predecessor__isnull=True 'predecessor': ['isnull'], 'predecessor__status': ['exact', 'icontains', 'in', 'startswith'], } + class TaskFilterQueryPage(filters.FilterSet): resume = django_filters.BooleanFilter(lookup_expr='exact', label='resuming') @@ -71,19 +72,18 @@ class TaskFilterQueryPage(filters.FilterSet): fields = { 'id': ['exact', 'gte', 'lte'], - #'task_type': ['exact','in'], + # 'task_type': ['exact','in'], 'workflow__id': ['exact'], 'filter': ['exact', 'icontains'], 'priority': ['exact', 'gte', 'lte'], 'status': ['icontains', 'in'], - 'project': ['exact', 'icontains','in'], + 'project': ['exact', 'icontains', 'in'], 'sas_id': ['exact', 'icontains', 'in'], - #'resume': ['exact'], + # 'resume': ['exact'], } class WorkflowFilter(filters.FilterSet): - class Meta: model = Workflow @@ -95,18 +95,17 @@ class WorkflowFilter(filters.FilterSet): class LogEntryFilter(filters.FilterSet): - class Meta: model = LogEntry fields = { 'task__id': ['exact'], 'step_name': ['exact', 'icontains', 'in', 'startswith'], - 'status': ['exact','in'], + 'status': ['exact', 'in'], } -class ConfigurationFilter(filters.FilterSet): +class ConfigurationFilter(filters.FilterSet): class Meta: model = Configuration @@ -116,8 +115,8 @@ class ConfigurationFilter(filters.FilterSet): 'value': ['exact', 'icontains'], } -class JobFilter(filters.FilterSet): +class JobFilter(filters.FilterSet): class Meta: model = Job @@ -127,8 +126,8 @@ class JobFilter(filters.FilterSet): 'job_id': ['exact'], } -class PostProcessingFilter(filters.FilterSet): +class PostProcessingFilter(filters.FilterSet): class Meta: model = PostProcessingRule @@ -139,6 +138,7 @@ class PostProcessingFilter(filters.FilterSet): 'workflow_to_apply__id': ['exact'], } + # ---------- Tables2 Views (experimental) ----------- # implementation with tables2: http://localhost:8000/atdb/tables2 class QueryView(SingleTableMixin, FilterView): @@ -192,7 +192,7 @@ class IndexView(ListView): # check if there is a 'task_filter' put on the session try: filter = self.request.session['task_filter'] - if filter!='all': + if filter != 'all': tasks = get_searched_tasks(filter, sort) except: pass @@ -200,7 +200,7 @@ class IndexView(ListView): # check if there is a 'task_onhold_filter' put on the session try: onhold = self.request.session['task_onhold_filter'] - if onhold!=None: + if onhold != None: tasks = tasks.filter(resume=not onhold) except: @@ -254,7 +254,6 @@ class TaskTables2View(SingleTableView): def TaskDetails(request, id=0, page=0): - try: task = Task.objects.get(id=id) @@ -277,9 +276,7 @@ def TaskDetails(request, id=0, page=0): log_entries = LogEntry.objects.filter(task=task).order_by('-timestamp') logentries_html = algorithms.convert_logentries_to_html(log_entries) - return render(request, "taskdatabase/tasks/task_details.html", {'task': task, 'logentries': logentries_html }) - - + return render(request, "taskdatabase/tasks/task_details.html", {'task': task, 'logentries': logentries_html}) def ShowInputs(request, id): @@ -315,7 +312,7 @@ def ShowConfig(request): def ShowDashboard(request, selection): # gather the results - results_tasks,results_logs = algorithms.construct_dashboard_html(request, selection) + results_tasks, results_logs = algorithms.construct_dashboard_html(request, selection) return render(request, "dashboard/dashboard.html", {'results_tasks': results_tasks, 'results_logs': results_logs, @@ -336,6 +333,7 @@ class DiagramView(ListView): model = Task template_name = "taskdatabase/diagram.html" + # ---------- REST API views ----------- # example: /atdb/tasks/ @@ -346,8 +344,8 @@ class TaskListViewAPI(generics.ListCreateAPIView): A pagination list of tasks, unsorted. """ model = Task - queryset = Task.objects.filter(task_type='regular').order_by('-priority','id') - #serializer_class = TaskSerializer + queryset = Task.objects.filter(task_type='regular').order_by('-priority', 'id') + # serializer_class = TaskSerializer # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html filter_backends = (filters.DjangoFilterBackend,) @@ -359,13 +357,14 @@ class TaskListViewAPI(generics.ListCreateAPIView): else: return TaskWriteSerializer + class PostProcessingTaskListViewAPI(generics.ListCreateAPIView): """ A pagination list of tasks, unsorted. """ model = Task - queryset = Task.objects.filter(task_type='postprocessing').order_by('-priority','id') - #serializer_class = TaskSerializer + queryset = Task.objects.filter(task_type='postprocessing').order_by('-priority', 'id') + # serializer_class = TaskSerializer # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html filter_backends = (filters.DjangoFilterBackend,) @@ -377,14 +376,15 @@ class PostProcessingTaskListViewAPI(generics.ListCreateAPIView): else: return TaskWriteSerializer + # all tasks class AllTaskListViewAPI(generics.ListCreateAPIView): """ A pagination list of tasks, unsorted. """ model = Task - queryset = Task.objects.all().order_by('-priority','id') - #serializer_class = TaskSerializer + queryset = Task.objects.all().order_by('-priority', 'id') + # serializer_class = TaskSerializer # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html filter_backends = (filters.DjangoFilterBackend,) @@ -402,7 +402,7 @@ class TaskListViewAPIFast(generics.ListAPIView): A pagination list of tasks, unsorted. """ model = Task - queryset = Task.objects.all().order_by('-priority','id') + queryset = Task.objects.all().order_by('-priority', 'id') serializer_class = TaskReadSerializerFast # using the Django Filter Backend - https://django-filter.readthedocs.io/en/latest/index.html @@ -418,6 +418,7 @@ class TaskDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView): """ model = Task queryset = Task.objects.all() + # serializer_class = TaskSerializer def get_serializer_class(self): @@ -445,6 +446,7 @@ class WorkflowListViewAPI(generics.ListCreateAPIView): filter_backends = (filters.DjangoFilterBackend,) filter_class = WorkflowFilter + # example: /atdb/workflows/5/ class WorkflowDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView): model = Workflow @@ -547,7 +549,7 @@ class MonitorListViewAPI(generics.ListCreateAPIView): serializer_class = MonitorSerializer filter_backends = (filters.DjangoFilterBackend,) - #filter_class = PostProcessingFilter + # filter_class = PostProcessingFilter # example: /atdb/job/5/ @@ -555,26 +557,26 @@ class MonitorDetailsViewAPI(generics.RetrieveUpdateDestroyAPIView): model = Monitor queryset = Monitor.objects.all() serializer_class = MonitorSerializer - - + + # --- controller resources, triggered by a button in the GUI or directoy with a URL --- # set task status to 'new_status' - called from the GUI @login_required -def Hold(request,pk,hold_it,page=0): +def Hold(request, pk, hold_it, page=0): model = Task task = Task.objects.get(pk=pk) task.resume = (hold_it == 'resume') task.save() - if page==0: + if page == 0: # redirect to details screen return redirect('/atdb/query') else: # redirect to tasks list - return redirect('/atdb/?page='+page) + return redirect('/atdb/?page=' + page) -def HoldQuery(request,pk,hold_it,query_params): +def HoldQuery(request, pk, hold_it, query_params): model = Task task = Task.objects.get(pk=pk) task.resume = (hold_it == 'resume') @@ -585,21 +587,22 @@ def HoldQuery(request,pk,hold_it,query_params): @login_required -def TaskSetStatus(request,pk,new_status,page=0): +def TaskSetStatus(request, pk, new_status, page=0): model = Task task = Task.objects.get(pk=pk) task.new_status = new_status task.save() - if page==0: + if page == 0: # redirect to details screen return redirect('/atdb/task_details') else: # redirect to tasks list - return redirect('/atdb/?page='+page) + return redirect('/atdb/?page=' + page) + # set a filter value in the session, used later by the 'get_searched_tasks' mechanism -def TaskSetFilter(request,filter): +def TaskSetFilter(request, filter): request.session['task_filter'] = filter # switch off the other filters @@ -608,12 +611,14 @@ def TaskSetFilter(request,filter): return redirect('/atdb/?page=1') + # set the defined list of ACTIVE_STATUSSES on the session, used later by the 'get_searched_tasks' mechanism def TaskSetActiveFilter(request): request.session['task_filter'] = settings.ACTIVE_STATUSSES request.session['task_onhold_filter'] = None return redirect('/atdb/?page=1') + def TaskSetOnHoldFilter(request, onhold): request.session['task_onhold_filter'] = onhold return redirect('/atdb/?page=1') @@ -625,21 +630,21 @@ def ChangePriority(request, pk, priority_change, page=0): task = Task.objects.get(pk=pk) priority = task.priority + int(priority_change) - if priority<0: - priority=0 + if priority < 0: + priority = 0 task.priority = priority task.save() - if page==0: + if page == 0: # redirect to details screen return redirect('/atdb/task_details') else: # redirect to tasks list - return redirect('/atdb/?page='+page) + return redirect('/atdb/?page=' + page) -def SortTasks(request,sort): +def SortTasks(request, sort): # store the sort field on the session request.session['sort'] = sort return redirect('/atdb') @@ -652,8 +657,8 @@ def convert_query_params_to_url(query_params): # because the query_params come in as a QueryDict converted to a string # it needs some converting to a json string that can be loaded into a dict - s = query_params.replace('<QueryDict: ','')[:-1] - s = s.replace('[','') + s = query_params.replace('<QueryDict: ', '')[:-1] + s = s.replace('[', '') s = s.replace(']', '') s = s.replace('\'', '"') @@ -670,18 +675,17 @@ def convert_query_params_to_url(query_params): @login_required -def TaskSetStatusTables2(request,pk,new_status,query_params): +def TaskSetStatusTables2(request, pk, new_status, query_params): model = Task task = Task.objects.get(pk=pk) task.new_status = new_status task.save() current_query_params = convert_query_params_to_url(query_params) - #current_query_params = "id=&id__gte=&id__lte=&workflow__id=&filter=%09test&filter__icontains=&priority=&priority__gte=&priority__lte=&status__icontains=&status__in=&project=&project__icontains=&sas_id=&sas_id__icontains=&resume=unknown" + # current_query_params = "id=&id__gte=&id__lte=&workflow__id=&filter=%09test&filter__icontains=&priority=&priority__gte=&priority__lte=&status__icontains=&status__in=&project=&project__icontains=&sas_id=&sas_id__icontains=&resume=unknown" return redirect('/atdb/query/?' + current_query_params) - @login_required def TaskMultiStatus(request, new_status, query_params): # get the list of id's from the session @@ -696,14 +700,14 @@ def TaskMultiStatus(request, new_status, query_params): task.save() current_query_params = request.session['current_query_params'] - return redirect('/atdb/query?'+current_query_params) + return redirect('/atdb/query?' + current_query_params) # add the current query parameters to the session so that they survive # the request/response to the confirmation page (which has other query parameters) current_query_params = convert_query_params_to_url(query_params) request.session['current_query_params'] = current_query_params - return render(request, "query/confirm_multi_change.html",{'new_value': new_status, 'count' : count}) + return render(request, "query/confirm_multi_change.html", {'new_value': new_status, 'count': count}) @login_required @@ -720,14 +724,14 @@ def TaskMultiHold(request, onhold, query_params): task.save() current_query_params = request.session['current_query_params'] - return redirect('/atdb/query?'+current_query_params) + return redirect('/atdb/query?' + current_query_params) # add the current query parameters to the session so that they survive # the request/response to the confirmation page (which has other query parameters) current_query_params = convert_query_params_to_url(query_params) request.session['current_query_params'] = current_query_params - return render(request, "query/confirm_multi_change.html",{'new_value': onhold, 'count' : count}) + return render(request, "query/confirm_multi_change.html", {'new_value': onhold, 'count': count}) # /atdb/get_size?status__in=defined,staged @@ -740,8 +744,8 @@ class GetSizeView(generics.ListAPIView): query_params = dict(self.request.query_params) try: status_in = query_params['status__in'] - status_list=status_in[0].split(',') - if status_list==['']: + status_list = status_in[0].split(',') + if status_list == ['']: status_list = settings.STATUSSES_WITH_DATA except: # if no 'status__in=' is given, then use the default list @@ -780,4 +784,30 @@ class GetMinMaxTimeView(generics.ListAPIView): except Exception as error: return Response({ 'error': str(error) - }) \ No newline at end of file + }) + +from rest_framework.serializers import ListSerializer +# /atdb/get_unique_values_for_key/{key} +class GetUniqueValuesForKey(generics.ListAPIView): + queryset = Task.objects.all() + model = Task + filter_backends = (filters.DjangoFilterBackend,) + filter_class = TaskFilter + # override list and generate a custom response + + + def list(self, request: Request, *args, **kwargs): + try: + + aggregation_key = kwargs['aggregation_key'] + queryset = self.get_queryset() + queryset = self.filter_queryset(queryset) + return Response({'aggregation_key': aggregation_key, + 'result': algorithms.unique_values_for_aggregation_key( + queryset, + aggregation_key) + }) + except Exception as error: + return Response({ + 'error': str(error) + })