summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ishtar/ishtar_base/menus.py3
-rw-r--r--ishtar/ishtar_base/models.py165
-rw-r--r--ishtar/ishtar_base/views.py13
-rw-r--r--ishtar/templates/dashboard_main.html47
-rw-r--r--static/media/style.css52
5 files changed, 274 insertions, 6 deletions
diff --git a/ishtar/ishtar_base/menus.py b/ishtar/ishtar_base/menus.py
index db80efec0..8061495f1 100644
--- a/ishtar/ishtar_base/menus.py
+++ b/ishtar/ishtar_base/menus.py
@@ -283,6 +283,9 @@ class Menu:
# ]),
SectionItem('dashboard', _(u"Dashboard"),
childs=[
+ MenuItem('dashboard_main', _(u"General informations"),
+ model=models.File,
+ access_controls=['change_file',]),
MenuItem('dashboard_file', _(u"Files"),
model=models.File,
access_controls=['change_file',]),
diff --git a/ishtar/ishtar_base/models.py b/ishtar/ishtar_base/models.py
index 2a8248804..cbf362b83 100644
--- a/ishtar/ishtar_base/models.py
+++ b/ishtar/ishtar_base/models.py
@@ -27,7 +27,7 @@ from django.core.validators import validate_slug
from django.utils.translation import ugettext_lazy as _, ugettext
from django.db.utils import DatabaseError
from django.utils.safestring import SafeUnicode, mark_safe
-from django.db.models import Q, Max
+from django.db.models import Q, Max, Count
from django.db.models.signals import m2m_changed
from django.contrib.auth.models import User
@@ -348,6 +348,77 @@ class LightHistorizedItem(BaseHistorizedItem):
super(LightHistorizedItem, self).save(*args, **kwargs)
return True
+class Dashboard:
+ def __init__(self, model):
+ self.model = model
+ self.total_number = model.get_total_number()
+ # years
+ self.years = model.get_years()
+ self.years.sort()
+ if not self.total_number or not self.years:
+ return
+ self.values = [('year', _(u"Year"), reversed(self.years))]
+ # numbers
+ self.numbers = [model.get_by_year(year).count() for year in self.years]
+ self.values += [('number', _(u"Number"), reversed(self.numbers))]
+ # calculate
+ self.average = self.get_average()
+ self.variance = self.get_variance()
+ self.standard_deviation = self.get_standard_deviation()
+ self.median = self.get_median()
+ self.mode = self.get_mode()
+ # by operation
+ if not hasattr(model, 'get_by_operation'):
+ return
+ operations = model.get_operations()
+ operation_numbers = [model.get_by_operation(op).count()
+ for op in operations]
+ # calculate
+ self.operation_average = self.get_average(operation_numbers)
+ self.operation_variance = self.get_variance(operation_numbers)
+ self.operation_standard_deviation = self.get_standard_deviation(
+ operation_numbers)
+ self.operation_median = self.get_median(operation_numbers)
+ operation_mode_pk = self.get_mode(dict(zip(operations,
+ operation_numbers)))
+ if operation_mode_pk:
+ self.operation_mode = unicode(Operation.objects.get(
+ pk=operation_mode_pk))
+
+ def get_average(self, vals=[]):
+ if not vals:
+ vals = self.numbers
+ return sum(vals)/len(vals)
+
+ def get_variance(self, vals=[]):
+ if not vals:
+ vals = self.numbers
+ avrg = self.get_average(vals)
+ return self.get_average([(x-avrg)**2 for x in vals])
+
+ def get_standard_deviation(self, vals=[]):
+ if not vals:
+ vals = self.numbers
+ return round(self.get_variance(vals)**0.5, 3)
+
+ def get_median(self, vals=[]):
+ if not vals:
+ vals = self.numbers
+ len_vals = len(vals)
+ vals.sort()
+ if (len_vals % 2) == 1:
+ return vals[len_vals/2]
+ else:
+ return (vals[len_vals/2-1] + vals[len_vals/2])/2.0
+
+ def get_mode(self, vals={}):
+ if not vals:
+ vals = dict(zip(self.years, self.numbers))
+ mx = max(vals.values())
+ for v in vals:
+ if vals[v] == mx:
+ return v
+
class Departement(models.Model):
label = models.CharField(_(u"Label"), max_length=30)
number = models.CharField(_(u"Number"), unique=True, max_length=3)
@@ -571,6 +642,19 @@ class File(BaseHistorizedItem, OwnPerms):
)
ordering = ['-year', '-numeric_reference']
+ @classmethod
+ def get_years(cls):
+ return [res['year'] for res in list(cls.objects.values('year').annotate(
+ Count("id")).order_by())]
+
+ @classmethod
+ def get_by_year(cls, year):
+ return cls.objects.filter(year=year)
+
+ @classmethod
+ def get_total_number(cls):
+ return cls.objects.count()
+
def __unicode__(self):
items = [unicode(_('Intercommunal'))]
if self.towns.count() == 1:
@@ -686,6 +770,19 @@ class Operation(BaseHistorizedItem, OwnPerms):
unicode(self.operation_code))))
return JOINT.join(items)
+ @classmethod
+ def get_years(cls):
+ return [res['year'] for res in list(cls.objects.values('year').annotate(
+ Count("id")).order_by())]
+
+ @classmethod
+ def get_by_year(cls, year):
+ return cls.objects.filter(year=year)
+
+ @classmethod
+ def get_total_number(cls):
+ return cls.objects.count()
+
year_index_lbl = _(u"Operation code")
@property
def year_index(self):
@@ -930,6 +1027,32 @@ class ContextRecord(BaseHistorizedItem, OwnPerms):
return JOINT.join([unicode(lbl) for lbl in [self.parcel.operation.year,
self.parcel.operation.operation_code,
self.label] if lbl])
+
+ @classmethod
+ def get_years(cls):
+ years = set()
+ for res in list(cls.objects.values('operation__start_date')):
+ yr = res['operation__start_date'].year
+ years.add(yr)
+ return list(years)
+
+ @classmethod
+ def get_by_year(cls, year):
+ return cls.objects.filter(operation__start_date__year=year)
+
+ @classmethod
+ def get_operations(cls):
+ return [dct['operation__pk']
+ for dct in cls.objects.values('operation__pk').distinct()]
+
+ @classmethod
+ def get_by_operation(cls, operation_id):
+ return cls.objects.filter(operation__pk=operation_id)
+
+ @classmethod
+ def get_total_number(cls):
+ return cls.objects.filter(operation__start_date__isnull=False).count()
+
class ContextRecordSource(Source):
class Meta:
verbose_name = _(u"Context record documentation")
@@ -1036,6 +1159,46 @@ class Item(BaseHistorizedItem, OwnPerms):
blank=True, null=True, related_name='items')
history = HistoricalRecords()
+ @classmethod
+ def get_years(cls):
+ years = set()
+ items = cls.objects.filter(downstream_treatment__isnull=True)
+ for item in items:
+ bi = item.base_items.all()
+ if not bi:
+ continue
+ bi = bi[0]
+ yr = bi.context_record.operation.start_date.year
+ years.add(yr)
+ return list(years)
+
+ @classmethod
+ def get_by_year(cls, year):
+ return cls.objects.filter(downstream_treatment__isnull=True,
+ base_items__context_record__operation__start_date__year=year)
+
+ @classmethod
+ def get_operations(cls):
+ operations = set()
+ items = cls.objects.filter(downstream_treatment__isnull=True)
+ for item in items:
+ bi = item.base_items.all()
+ if not bi:
+ continue
+ bi = bi[0]
+ pk = bi.context_record.operation.pk
+ operations.add(pk)
+ return list(operations)
+
+ @classmethod
+ def get_by_operation(cls, operation_id):
+ return cls.objects.filter(downstream_treatment__isnull=True,
+ base_items__context_record__operation__pk=operation_id)
+
+ @classmethod
+ def get_total_number(cls):
+ return cls.objects.filter(downstream_treatment__isnull=True).count()
+
def duplicate(self, user):
dct = dict([(attr, getattr(self, attr)) for attr in ('order', 'label',
'description', 'material_type', 'volume', 'weight',
diff --git a/ishtar/ishtar_base/views.py b/ishtar/ishtar_base/views.py
index 631e71ed7..ff55169c5 100644
--- a/ishtar/ishtar_base/views.py
+++ b/ishtar/ishtar_base/views.py
@@ -649,3 +649,16 @@ def action(request, action_slug, obj_id=None, *args, **kwargs):
return render_to_response('index.html', dct,
context_instance=RequestContext(request))
+def dashboard_main(request, dct, obj_id=None, *args, **kwargs):
+ """
+ Main dashboard
+ """
+ dct = {'items':[
+ (_(u"Archaeological files"), models.Dashboard(models.File)),
+ (_(u"Operations"), models.Dashboard(models.Operation)),
+ (_(u"Context records"), models.Dashboard(models.ContextRecord)),
+ (_(u"Archaeological items"), models.Dashboard(models.Item)),
+ ]}
+ return render_to_response('dashboard_main.html', dct,
+ context_instance=RequestContext(request))
+
diff --git a/ishtar/templates/dashboard_main.html b/ishtar/templates/dashboard_main.html
new file mode 100644
index 000000000..ae243f7c2
--- /dev/null
+++ b/ishtar/templates/dashboard_main.html
@@ -0,0 +1,47 @@
+{% extends "base.html" %}
+{% load i18n %}
+{% load range %}
+{% block extra_head %}
+{{form.media}}
+{% endblock %}
+{% block content %}
+<div class='dashboard'>
+{% for lbl, dashboard in items %}
+ <div>
+ <h3>{{lbl}}</h3>
+ <h4>{% trans "Numbers" %}</h4>
+ <p><strong>{% trans "Total:" %}</strong> {{dashboard.total_number}}</p>
+ <div class='table'>
+ <table>
+ {% for idx, lbl, values in dashboard.values %}
+ <tr class='idx {% if forloop.counter0|divisibleby:"2" %}even{%else%}odd{%endif%}'>
+ <th>{{lbl}}</th>
+ {% for value in values %}<td>{{value}}</td>{% endfor%}
+ </tr>
+ {% endfor%}
+ </table>
+ </div>
+ {% if dashboard.years %}
+ <h4>{% trans "By years" %}</h4>
+ <ul>
+ <li><strong>{% trans "Average:" %}</strong> {{dashboard.average}}</li>
+ <li><strong>{% trans "Variance:" %}</strong> {{dashboard.variance}}</li>
+ <li><strong>{% trans "Standard deviation:" %}</strong> {{dashboard.standard_deviation}}</li>
+ <li><strong>{% trans "Median:" %}</strong> {{dashboard.median}}</li>
+ <li><strong>{% trans "Mode:" %}</strong> {{dashboard.mode}}</li>
+ </ul>
+ {% endif %}
+ {% if dashboard.operation_average %}
+ <h4>{% trans "By operations" %}</h4>
+ <ul>
+ <li><strong>{% trans "Average:" %}</strong> {{dashboard.operation_average}}</li>
+ <li><strong>{% trans "Variance:" %}</strong> {{dashboard.operation_variance}}</li>
+ <li><strong>{% trans "Standard deviation:" %}</strong> {{dashboard.operation_standard_deviation}}</li>
+ <li><strong>{% trans "Median:" %}</strong> {{dashboard.operation_median}}</li>
+ <li><strong>{% trans "Mode:" %}</strong> {{dashboard.operation_mode}}</li>
+ </ul>
+ {% endif %}
+ </div>
+{% endfor%}
+</div>
+{% endblock %}
diff --git a/static/media/style.css b/static/media/style.css
index 2d8cb7ff7..a6cd7487e 100644
--- a/static/media/style.css
+++ b/static/media/style.css
@@ -321,30 +321,64 @@ table.confirm tr.spacer td:last-child{
border-radius:8px;
}
-#window .table{
+.dashboard > div{
+ width:760px;
+ background: #FFF;
+ -moz-border-radius:8px;
+ -webkit-border-radius:8px;
+ border-radius:8px;
+ -webkit-box-shadow: 0px 0px 20px #444;
+ -moz-box-shadow: 0px 0px 20px #444;
+ margin:20px;
+ text-align:left;
+ padding-bottom:10px;
+}
+
+.dashboard h3{
+ background-color:#922;
+ color:#FFF;
+ -webkit-border-top-left-radius: 8px;
+ -webkit-border-top-right-radius: 8px;
+ -moz-border-radius-topleft: 8px;
+ -moz-border-radius-topright: 8px;
+ border-top-left-radius: 8px;
+ border-top-right-radius: 8px;
+}
+
+.dashboard h4{
+ font-weight:normal;
+ color:#D14;
+}
+.dashboard h4, .dashboard p{
+ margin:0;
+ padding:0 10px;
+}
+
+#window .table, .dashboard .table{
+ padding:10px;
width:730px;
overflow:auto;
}
-#window table{
+#window table, .dashboard table{
font-size:0.9em;
margin:10px 0;
border-collapse:collapse;
width:100%;
}
-#window caption{
+#window caption, .dashboard caption{
font-size:1.2em;
}
-#window table th{
+#window table th, .dashboard table th{
text-align:center;
background-color:#922;
border:1px solid #EEE;
color:#FFF;
}
-#window table td{
+#window table td, .dashboard table td{
text-align:right;
padding:0 1em;
border:1px solid #EEE;
@@ -453,3 +487,11 @@ a.remove{
margin:0 6px;
}
+.dashboard table{
+ width:100%;
+ border-collapse:yes;
+}
+.dashboard table th, .dashboard table td{
+ border:1px solid;
+}
+