From f87fd09770ab0738855a5efbaff905a7c9f346ac Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Mon, 20 Oct 2014 20:40:42 +0200 Subject: Dashboard: many improvments on graph (refs #2075) * by default graph is set by month * data are reapeated in a table * ability to save the graph in a file * detail by department with a line by department + total * add a legend * ability to zoom on the graph --- archaeological_files/forms.py | 8 +- archaeological_files/models.py | 5 +- ishtar_common/models.py | 45 ++++++++--- ishtar_common/static/media/style.css | 15 +++- .../ishtar/dashboards/dashboard_main.html | 3 + .../ishtar/dashboards/dashboard_main_detail.html | 88 +++++++++++++++++----- ishtar_common/templatetags/date_formating.py | 19 +++++ ishtar_common/views.py | 11 ++- 8 files changed, 155 insertions(+), 39 deletions(-) create mode 100644 ishtar_common/templatetags/date_formating.py diff --git a/archaeological_files/forms.py b/archaeological_files/forms.py index dbbbafbd9..68a004e4f 100644 --- a/archaeological_files/forms.py +++ b/archaeological_files/forms.py @@ -122,13 +122,15 @@ class FileFormSelection(forms.Form): raise forms.ValidationError(_(u"You should select a file.")) return cleaned_data -SLICING = (('year',_(u"years")), ("month",_(u"months"))) +SLICING = (("month",_(u"months")), ('year',_(u"years")),) DATE_SOURCE = (('creation',_(u"Creation date")), ("reception",_(u"Reception date"))) class DashboardForm(forms.Form): slicing = forms.ChoiceField(label=_("Slicing"), choices=SLICING, required=False) + department_detail = forms.BooleanField(label=_("Department detail"), + required=False) date_source = forms.ChoiceField(label=_("Date get from"), choices=DATE_SOURCE, required=False) file_type = forms.ChoiceField(label=_("File type"), choices=[], @@ -145,6 +147,10 @@ class DashboardForm(forms.Form): self.fields['saisine_type'].choices = models.SaisineType.get_types() self.fields['file_type'].choices = models.FileType.get_types() + def get_show_detail(self): + return hasattr(self, 'cleaned_data') and \ + self.cleaned_data.get('department_detail') + def get_date_source(self): date_source = 'creation' if hasattr(self, 'cleaned_data') and \ diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 70570e145..a649ceac1 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -230,7 +230,7 @@ class File(BaseHistorizedItem, OwnPerms, ValueGetter, ShortMenuItem): return sorted(owns.all(), key=lambda x:x.cached_label) @classmethod - def get_periods(cls, slice='year', fltr={}, date_source='creation'): + def get_periods(cls, slice='month', fltr={}, date_source='creation'): date_var = date_source + '_date' q = cls.objects.filter(**{date_var+'__isnull':False}) if fltr: @@ -258,7 +258,8 @@ class File(BaseHistorizedItem, OwnPerms, ValueGetter, ShortMenuItem): q = cls.objects.filter(**{date_var+'__isnull':False}) if fltr: q = q.filter(**fltr) - return q.filter(**{date_var+'__year':year, date_var+'__month':month}) + q = q.filter(**{date_var+'__year':year, date_var+'__month':month}) + return q @classmethod def get_total_number(cls, fltr={}): diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 98f4addb7..c59f38674 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -561,10 +561,12 @@ class UserDashboard: .order_by('person__person_types') class Dashboard: - def __init__(self, model, slice='year', date_source=None, fltr={}): + def __init__(self, model, slice='year', date_source=None, show_detail=None, + fltr={}): # don't provide date_source if it is not relevant self.model = model self.total_number = model.get_total_number(fltr) + self.show_detail = show_detail history_model = self.model.history.model # last edited - created self.recents, self.lasts = [], [] @@ -597,21 +599,40 @@ class Dashboard: self.periods.sort() if not self.total_number or not self.periods: return + kwargs_num = base_kwargs.copy() + self.serie_labels = [_(u"Total")] # numbers if slice == 'year': - self.values = [('year', _(u"Year"), reversed(self.periods))] - self.numbers = [model.get_by_year(year, **base_kwargs).count() + self.values = [('year', "", + list(reversed(self.periods)))] + self.numbers = [model.get_by_year(year, **kwargs_num).count() for year in self.periods] - self.values += [('number', _(u"Number"), reversed(self.numbers))] + self.values += [('number', _(u"Number"), + list(reversed(self.numbers)))] if slice == 'month': - self.numbers = [model.get_by_month(*period, **base_kwargs).count() - for period in self.periods] - periods = reversed(self.periods) - self.periods = ["%d-%s-01" % (period[0], ('0'+str(period[1])) - if len(str(period[1])) == 1 else period[1]) - for period in periods] - self.values = [('month', _(u"Month"), self.periods)] - self.values += [('number', _(u"Number"), reversed(self.numbers))] + periods = list(reversed(self.periods)) + self.periods = ["%d-%s-01" % (p[0], ('0'+str(p[1])) + if len(str(p[1])) == 1 else p[1]) + for p in periods] + self.values = [('month', "", self.periods)] + if show_detail: + for dpt in settings.ISHTAR_DPTS: + self.serie_labels.append(unicode(dpt)) + idx = 'number_' + unicode(dpt) + kwargs_num['fltr'] = {"towns__numero_insee__startswith":\ + unicode(dpt)} + numbers = [model.get_by_month(*p.split('-')[:2], + **kwargs_num).count() + for p in self.periods] + self.values += [(idx, dpt, list(numbers))] + # put "Total" at the end + self.serie_labels.append(self.serie_labels.pop(0)) + kwargs_num['fltr'] = {} + self.numbers = [model.get_by_month(*p.split('-')[:2], + **kwargs_num).count() + for p in self.periods] + self.values += [('number', _(u"Total"), + list(self.numbers))] # calculate self.average = self.get_average() self.variance = self.get_variance() diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css index 341308926..54a97ca77 100644 --- a/ishtar_common/static/media/style.css +++ b/ishtar_common/static/media/style.css @@ -669,6 +669,13 @@ table.confirm tr.spacer td:last-child{ padding:0.5em 1em; } +.chart-img{ + display:none; +} + +.chart-img-form{ + margin-top:1em; +} #window table th, .dashboard table.resume th{ background-color:#922; @@ -783,8 +790,7 @@ table.confirm tr.spacer td:last-child{ margin:0.3em; } -p.alert{ - color:#D14; +p.info-box, p.alert{ display:block; font-style:italic; width:670px; @@ -794,6 +800,11 @@ p.alert{ -moz-border-radius:8px; -webkit-border-radius:8px; border-radius:8px; + font-size: 0.9em; +} + +p.alert{ + color:#D14; background-image:url(images/red_flag.png); background-repeat:no-repeat; background-position:left center; diff --git a/ishtar_common/templates/ishtar/dashboards/dashboard_main.html b/ishtar_common/templates/ishtar/dashboards/dashboard_main.html index 8822875b5..868f8a5c3 100644 --- a/ishtar_common/templates/ishtar/dashboards/dashboard_main.html +++ b/ishtar_common/templates/ishtar/dashboards/dashboard_main.html @@ -6,9 +6,12 @@ + + + {% endblock %} {% block content %} diff --git a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html index 610457ae3..820c50136 100644 --- a/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html +++ b/ishtar_common/templates/ishtar/dashboards/dashboard_main_detail.html @@ -1,11 +1,8 @@ -{% load i18n %} +{% load i18n date_formating %} {% load url from future %}

{% trans "Numbers" %}

-

{% trans "Total:" %} {{dashboard.total_number}}

-
-
{% if form %}
@@ -16,15 +13,27 @@
{% endif %} - {% comment %} - +

{% trans "Total:" %} {{dashboard.total_number}}

+
+
+

{% trans 'Draw rectangle on the graph to zoom. Double-click to reinitialize.' %}

+
+ +
+
+

{% trans 'Right-click on this image to save it.' %}

+
+
+
+
+
{% for idx, lbl, values in dashboard.values %} - {% for value in values %}{% endfor%} + {% for value in values reversed %}{% if forloop.parentloop.counter0 %}{%endif%}{% endfor%} {% endfor%} -
{{lbl}}{{value}}{% else %}{%endif%}{{value|date_formating }}{% if forloop.parentloop.counter0 %}{% else %}
{% endcomment %} +
{% if dashboard.periods %}

{% trans "By years" %}

@@ -71,23 +80,41 @@