From: Maciej Tronowski Date: Tue, 24 Feb 2015 16:51:30 +0000 (+0100) Subject: reworked filters in jobs list view X-Git-Tag: v1.0~159 X-Git-Url: http://mmka.chem.univ.gda.pl/gitweb/?a=commitdiff_plain;ds=sidebyside;h=cb292970db9b3ea3054212858abb5eebe73f0fd9;p=qcg-portal.git reworked filters in jobs list view --- diff --git a/plgng/settings.py b/plgng/settings.py index 0567769..24f298b 100644 --- a/plgng/settings.py +++ b/plgng/settings.py @@ -90,7 +90,7 @@ USE_L10N = True USE_TZ = True -CUSTOM_DATETIME_FORMAT = 'j b Y, G:i' +CUSTOM_DATETIME_FORMAT = 'j b Y, H:i' # Static files (CSS, JavaScript, Images) @@ -127,3 +127,12 @@ OPENID_STRICT_USERNAMES = True SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' + + +# 3-rd party settings + +BOOTSTRAP3 = { + 'horizontal_label_class': 'col-md-4', + 'horizontal_field_class': 'col-md-6', + 'set_placeholder': False, +} diff --git a/qcg/forms.py b/qcg/forms.py index 3d90145..d9d7a92 100644 --- a/qcg/forms.py +++ b/qcg/forms.py @@ -26,6 +26,8 @@ class FiltersForm(forms.Form): STATUS_MAP = { ACTIVE: ( + Task.STATUS_CHOICES_REVERSED[TaskStatus.UNSUBMITTED], + Task.STATUS_CHOICES_REVERSED[TaskStatus.UNCOMMITTED], Task.STATUS_CHOICES_REVERSED[TaskStatus.QUEUED], Task.STATUS_CHOICES_REVERSED[TaskStatus.PREPROCESSING], Task.STATUS_CHOICES_REVERSED[TaskStatus.PENDING], @@ -35,14 +37,13 @@ class FiltersForm(forms.Form): ), FINISHED: ( Task.STATUS_CHOICES_REVERSED[TaskStatus.FINISHED], - Task.STATUS_CHOICES_REVERSED[TaskStatus.FAILED], - Task.STATUS_CHOICES_REVERSED[TaskStatus.CANCELED], ), FAILED: ( Task.STATUS_CHOICES_REVERSED[TaskStatus.FAILED], Task.STATUS_CHOICES_REVERSED[TaskStatus.CANCELED], ), } + STATUS_CHOICES_DICT = dict(STATUS_CHOICES) status = forms.MultipleChoiceField(choices=STATUS_CHOICES, label=u"Status", required=False, widget=forms.CheckboxSelectMultiple) @@ -51,6 +52,5 @@ class FiltersForm(forms.Form): # advanced keywords = forms.CharField(max_length=100, label=u"Wyszukaj frazę", required=False) - status_exact = forms.ChoiceField(choices=[(None, u"----------")] + Task.STATUS_CHOICES, label=u"Status", required=False) submission = forms.CharField(label=u"Data zlecenia", validators=[date_range_validator], required=False) finish = forms.CharField(label=u"Data zakończenia", validators=[date_range_validator], required=False) diff --git a/qcg/templates/qcg/jobs.html b/qcg/templates/qcg/jobs.html index 58b4998..f2b9efe 100644 --- a/qcg/templates/qcg/jobs.html +++ b/qcg/templates/qcg/jobs.html @@ -1,5 +1,5 @@ {% extends 'qcg/base.html' %} -{% load staticfiles bootstrap3 %} +{% load staticfiles bootstrap3 query_string qcg_utils %} {% block extra_css %} @@ -54,94 +54,67 @@ firstDay: 1 } }); - - $('#toggle-advanced').click(function() { - if($('#advanced-filters').attr('aria-expanded') == "false") - $(this).text('« mniej'); - else - $(this).text('więcej »'); - }); }); {% endblock %} {% block container %} + +

{% block title %}Lista zadań{% endblock %}

-
+ {% if selected_filters %}
-
- -
-
- {% for choice in filters.status %} - - {% endfor %} -
-
- {% for choice in filters.host %} - - {% endfor %} -
-
-
-
-{#
#} -{#
#} -{# {% bootstrap_field filters.keywords layout='horizontal' horizontal_label_class='col-md-5' horizontal_field_class='col-md-4' bound_css_class=' ' %}#} -{#
#} -{#
#} -{# {% bootstrap_field filters.status_exact layout='horizontal' horizontal_label_class='col-md-5' horizontal_field_class='col-md-4' bound_css_class=' ' %}#} -{#
#} -{#
#} -{# {% bootstrap_field filters.submission layout='horizontal' horizontal_label_class='col-md-5' horizontal_field_class='col-md-4' bound_css_class=' ' %}#} -{#
#} -{#
#} -{# {% bootstrap_field filters.finish layout='horizontal' horizontal_label_class='col-md-5' horizontal_field_class='col-md-4' bound_css_class=' ' %}#} -{#
#} -{#
#} -
-
-
- {% bootstrap_field filters.keywords layout='horizontal' form_group_class='form-group row col-lg-5 col-md-6' horizontal_label_class='col-md-4' horizontal_field_class='col-md-8' bound_css_class=' ' %} - {% bootstrap_field filters.status_exact layout='horizontal' form_group_class='form-group row col-lg-5 col-md-6' horizontal_label_class='col-md-4' horizontal_field_class='col-md-8' bound_css_class=' ' %} -
-
-
- {% bootstrap_field filters.submission layout='horizontal' form_group_class='form-group row col-lg-5 col-md-6' horizontal_label_class='col-md-4' horizontal_field_class='col-md-8' bound_css_class=' ' %} - {% bootstrap_field filters.finish layout='horizontal' form_group_class='form-group row col-lg-5 col-md-6' horizontal_label_class='col-md-4' horizontal_field_class='col-md-8' bound_css_class=' ' %} +
+ Wyczyść wszystkie filtry + Wybrane filtry: + {% for label, param, val in selected_filters %} + {{ label }}  + {% endfor %}
- - -
+
+ {% endif %} @@ -175,6 +148,7 @@ + {# #} @@ -197,6 +171,7 @@ + {% endwith %} {% else %} @@ -213,6 +188,7 @@ + {% for task in job.list %} @@ -229,6 +205,7 @@ + {% endfor %} {% endifequal %} @@ -237,21 +214,24 @@
Koniec Status HostUwagi
{{ task.finish_time|date:"CUSTOM_DATETIME_FORMAT" }} {{ task.get_status_display }} {{ task.short_host_names|join:', ' }}szczegóły ›
{{ job.grouper.finish_time|date:"CUSTOM_DATETIME_FORMAT" }} {{ job.grouper.get_status_display }} -szczegóły ›
{{ task.finish_time|date:"CUSTOM_DATETIME_FORMAT" }} {{ task.get_status_display }} {{ task.short_host_names|join:', ' }}szczegóły ›
{% if not page %} -
Brak elementów
+
+ + Brak zadań spełniających wybrane kryteria +
{% endif %} + + + {% endblock container %} diff --git a/qcg/templatetags/qcg_utils.py b/qcg/templatetags/qcg_utils.py index 33dd748..2dfe96e 100644 --- a/qcg/templatetags/qcg_utils.py +++ b/qcg/templatetags/qcg_utils.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from django import template -from django.utils.html import format_html +from django.utils.html import format_html, format_html_join from django.utils.timesince import timesince from django.utils.timezone import now @@ -27,3 +27,9 @@ def display_attribute(label, value): u'
{}
' u'
{}
' u'
', label, value) + + +@register.simple_tag(takes_context=True) +def get_params_as_hidden(context, *params): + return format_html_join('\n', u'', + ((param, v) for param in params for v in context['request'].GET.getlist(param, []))) diff --git a/qcg/templatetags/query_string.py b/qcg/templatetags/query_string.py new file mode 100644 index 0000000..235f3e2 --- /dev/null +++ b/qcg/templatetags/query_string.py @@ -0,0 +1,82 @@ +import re + +from django import template +from django.utils.html import escape + + +register = template.Library() + + +class QueryStringNode(template.Node): + REPLACE = '=' + APPEND = '+=' + REMOVE = '-=' + PURGE = '!' + + OPERATORS = [REPLACE, APPEND, REMOVE, PURGE] + + op_regex = re.compile('|'.join(map(re.escape, OPERATORS))) + + def __init__(self, tag_name, parsed_args): + self.tag_name = tag_name + self.parsed_args = parsed_args + + def render(self, context): + # django.core.context_processors.request should be enabled in + # settings.TEMPLATE_CONTEXT_PROCESSORS. + # Or else, directly pass the HttpRequest object as 'request' in context. + + try: + query_dict = context['request'].GET.copy() + except KeyError: + return '' + + for key, op, value in self.parsed_args: + key = key.resolve(context) + + if op == self.APPEND: + # item+="foo": Append to current query arguments. + # e.g. item=1 -> item=1&item=foo + query_dict.appendlist(key, value.resolve(context)) + elif op == self.REMOVE: + # item-="bar": Remove from current query arguments. + # e.g. item=1&item=bar -> item=1 + try: + query_dict.getlist(key).remove(value.resolve(context)) + except KeyError: + pass + elif op == self.PURGE: + # item!: Completely remove from current query arguments. + # e.g. item=1&item=2 -> '' + try: + del query_dict[key] + except KeyError: + pass + else: + # item=1: Replace current query arguments, e.g. item=2 -> item=1 + query_dict[key] = value.resolve(context) + + qs = query_dict.urlencode() + return '?' + escape(qs) if qs else '' + + +@register.tag +def query_string(parser, token): + # {% query_string page=1 size! item+="foo" item-="bar" %} + args = token.split_contents() + + tag_name = args[0] + raw_pairs = args[1:] + + parsed_args = [] + for pair in raw_pairs: + try: + key, value = QueryStringNode.op_regex.split(pair, 1) + except: + raise template.TemplateSyntaxError("{} tag's argument should be in format foo({})bar".format( + tag_name, '|'.join(QueryStringNode.OPERATORS))) + + parsed_args.append( + (parser.compile_filter(key), QueryStringNode.op_regex.search(pair).group(), parser.compile_filter(value))) + + return QueryStringNode(tag_name, parsed_args) diff --git a/qcg/views.py b/qcg/views.py index f3fd389..e996da0 100644 --- a/qcg/views.py +++ b/qcg/views.py @@ -1,3 +1,4 @@ +# coding=utf-8 from datetime import datetime, timedelta from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME @@ -60,24 +61,14 @@ def jobs_list(request): .select_related('job').prefetch_related('allocations__nodes') filters = FiltersForm(request.GET) - advanced = False + selected_filters = [] if filters.is_valid(): keywords = filters.cleaned_data['keywords'] status = filters.cleaned_data['status'] host = filters.cleaned_data['host'] - status_exact = filters.cleaned_data['status_exact'] submission = filters.cleaned_data['submission'] finish = filters.cleaned_data['finish'] - if status: - statuses = [] - for s in status: - statuses.extend(FiltersForm.STATUS_MAP[int(s)]) - - tasks = tasks.filter(status__in=statuses) - if host: - tasks = tasks.filter(allocations__host_name__in=host) - if keywords: and_query = Q() @@ -88,22 +79,43 @@ def jobs_list(request): and_query &= or_query tasks = tasks.filter(and_query) - if status_exact: - tasks = tasks.filter(status=status_exact) + selected_filters.append((keywords, 'keywords', keywords)) + + if status: + statuses = [] + for s in status: + si = int(s) + statuses.extend(FiltersForm.STATUS_MAP[si]) + selected_filters.append((FiltersForm.STATUS_CHOICES_DICT[si], 'status', s)) + + tasks = tasks.filter(status__in=statuses) + + if host: + tasks = tasks.filter(allocations__host_name__in=host) + + host_dict = dict(filters.fields['host'].choices) + for h in host: + selected_filters.append((host_dict[h], 'host', h)) + if submission: start, end = submission.split('-') tasks = tasks.filter(submission_time__gte=parse_date(start), submission_time__lte=parse_date(end) + timedelta(days=1)) + selected_filters.append((u'Data zlecenia: ' + submission, 'submission', submission)) + if finish: start, end = finish.split('-') tasks = tasks.filter(finish_time__gte=parse_date(start), finish_time__lte=parse_date(end) + timedelta(days=1)) + selected_filters.append((u'Data zakończenia: ' + finish, 'finish', finish)) + + tasks = tasks.distinct() - advanced = bool(keywords or status_exact or submission or finish) + checked_status = {i: widget.is_checked() for i, widget in enumerate(filters['status'])} - context = {'filters': filters, 'advanced': advanced} + context = {'filters': filters, 'checked_status': checked_status, 'selected_filters': selected_filters} context.update(paginator_context(request, tasks)) return render(request, 'qcg/jobs.html', context)