From d3d84ed7cc5ff3cb9c164838c78cfcee73fbc44e Mon Sep 17 00:00:00 2001
From: Étienne Loks
Date: Mon, 5 Sep 2016 17:05:03 +0200
Subject: Operation: display statistics on the sheet (refs #2989)
---
archaeological_files/models.py | 1 +
archaeological_operations/models.py | 198 +++++++++++++++++-
.../templates/ishtar/sheet_operation.html | 230 ++++++++++++---------
ishtar_common/models.py | 1 +
ishtar_common/static/media/style.css | 4 +
ishtar_common/templatetags/link_to_window.py | 2 +
ishtar_common/utils.py | 5 +
version.py | 2 +-
8 files changed, 348 insertions(+), 95 deletions(-)
diff --git a/archaeological_files/models.py b/archaeological_files/models.py
index 98223d7ff..eaa9d832e 100644
--- a/archaeological_files/models.py
+++ b/archaeological_files/models.py
@@ -73,6 +73,7 @@ class File(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter,
ShortMenuItem, DashboardFormItem):
TABLE_COLS = ['numeric_reference', 'year', 'internal_reference',
'file_type', 'saisine_type', 'towns', ]
+ SHOW_URL = 'show-file'
year = models.IntegerField(_(u"Year"),
default=lambda: datetime.datetime.now().year)
numeric_reference = models.IntegerField(
diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py
index 2db101104..ccafe5202 100644
--- a/archaeological_operations/models.py
+++ b/archaeological_operations/models.py
@@ -22,17 +22,18 @@ from itertools import groupby
from django.conf import settings
from django.contrib.gis.db import models
+from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.db.models import Q, Count, Sum, Max, Avg
from django.db.models.signals import post_save, m2m_changed, post_delete
from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _, ugettext
-from ishtar_common.utils import cached_label_changed
+from ishtar_common.utils import cached_label_changed, get_cache, mode
from ishtar_common.models import GeneralType, BaseHistorizedItem, \
HistoricalRecords, LightHistorizedItem, OwnPerms, Department, Source,\
- Person, Organization, Town, Dashboard, IshtarUser, ValueGetter, \
+ SourceType, Person, Organization, Town, Dashboard, IshtarUser, ValueGetter,\
DocumentTemplate, ShortMenuItem, DashboardFormItem, GeneralRelationType,\
GeneralRecordRelations, post_delete_record_relation, OperationType, \
get_external_id
@@ -459,6 +460,199 @@ class Operation(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter,
self.operation_code = self.get_available_operation_code(self.year)
return super(Operation, self).save(*args, **kwargs)
+ @property
+ def nb_parcels(self):
+ nb = 0
+ if self.associated_file:
+ nb = self.associated_file.parcels.count()
+ if not nb:
+ nb = self.parcels.count()
+ return nb
+
+ def _get_or_set_stats(self, funcname, update):
+ key, val = get_cache(self.__class__, [funcname, self.pk])
+ if not update and val is not None:
+ return val
+ val = getattr(self, funcname)()
+ cache.set(key, val, settings.CACHE_TIMEOUT)
+ return val
+
+ @property
+ def nb_acts(self, update=False):
+ _(u"Number of administrative acts")
+ return self._get_or_set_stats('_nb_acts', update)
+
+ def _nb_acts(self):
+ return self.administrative_act.count()
+
+ @property
+ def nb_indexed_acts(self, update=False):
+ _(u"Number of indexed administrative acts")
+ return self._get_or_set_stats('_nb_indexed_acts', update)
+
+ def _nb_indexed_acts(self):
+ return self.administrative_act.filter(act_type__indexed=True).count()
+
+ @property
+ def nb_context_records(self, update=False):
+ _(u"Number of context records")
+ return self._get_or_set_stats('_nb_context_records', update)
+
+ def _nb_context_records(self):
+ return self.context_record.count()
+
+ @property
+ def nb_context_records_by_type(self, update=False):
+ return self._get_or_set_stats('_nb_context_records_by_type', update)
+
+ def _nb_context_records_by_type(self):
+ nbs = []
+ q = self.context_record.values(
+ 'unit', 'unit__label').distinct().order_by('label')
+ for res in q.all():
+ nbs.append((unicode(res['unit__label']),
+ self.context_record.filter(unit=res['unit']).count()))
+ return nbs
+
+ @property
+ def nb_context_records_by_periods(self, update=False):
+ return self._get_or_set_stats('_nb_context_records_by_periods', update)
+
+ def _nb_context_records_by_periods(self):
+ nbs = []
+ q = self.context_record.values(
+ 'datings__period', 'datings__period__label').distinct().order_by(
+ 'datings__period__order')
+ for res in q.all():
+ nbs.append((unicode(res['datings__period__label']),
+ self.context_record.filter(
+ datings__period=res['datings__period']).count()))
+ return nbs
+
+ @property
+ def nb_finds(self, update=False):
+ _(u"Number of finds")
+ return self._get_or_set_stats('_nb_finds', update)
+
+ def _nb_finds(self):
+ from archaeological_finds.models import Find
+ q = Find.objects.filter(
+ base_finds__context_record__operation=self)
+ return q.count()
+
+ @property
+ def nb_finds_by_material_type(self, update=False):
+ return self._get_or_set_stats('_nb_finds_by_material_type', update)
+
+ def _nb_finds_by_material_type(self):
+ from archaeological_finds.models import Find
+ nbs = []
+ q = Find.objects.filter(
+ base_finds__context_record__operation=self).values(
+ 'material_types__pk', 'material_types__label').distinct().order_by(
+ 'material_types__label')
+ for res in q.all():
+ nbs.append(
+ (unicode(res['material_types__label']),
+ Find.objects.filter(
+ material_types__pk=res['material_types__pk']).count()))
+ return nbs
+
+ @property
+ def nb_finds_by_types(self, update=False):
+ return self._get_or_set_stats('_nb_finds_by_types', update)
+
+ def _nb_finds_by_types(self):
+ from archaeological_finds.models import Find
+ nbs = []
+ q = Find.objects.filter(
+ base_finds__context_record__operation=self).values(
+ 'object_types', 'object_types__label').distinct().order_by(
+ 'object_types__label')
+ for res in q.all():
+ label = unicode(res['object_types__label'])
+ if label == 'None':
+ label = _(u"No type")
+ nbs.append(
+ (label,
+ Find.objects.filter(
+ object_types=res['object_types']).count()))
+ return nbs
+
+ @property
+ def nb_finds_by_periods(self, update=False):
+ return self._get_or_set_stats('_nb_finds_by_periods', update)
+
+ def _nb_finds_by_periods(self):
+ from archaeological_finds.models import Find
+ nbs = []
+ q = Find.objects.filter(
+ base_finds__context_record__operation=self).values(
+ 'datings__period', 'datings__period__label').distinct().order_by(
+ 'datings__period__order')
+ for res in q.all():
+ nbs.append(
+ (unicode(res['datings__period__label']),
+ Find.objects.filter(
+ datings__period=res['datings__period']).count()))
+ return nbs
+
+ @property
+ def nb_documents(self, update=False):
+ _(u"Number of sources")
+ return self._get_or_set_stats('_nb_documents', update)
+
+ def _nb_documents(self):
+ from archaeological_context_records.models import ContextRecordSource
+ from archaeological_finds.models import FindSource
+ nbs = self.source.count() + \
+ ContextRecordSource.objects.filter(
+ context_record__operation=self).count() + \
+ FindSource.objects.filter(
+ find__base_finds__context_record__operation=self).count()
+ return nbs
+
+ @property
+ def nb_documents_by_types(self, update=False):
+ return self._get_or_set_stats('_nb_documents_by_types', update)
+
+ def _nb_documents_by_types(self):
+ from archaeological_context_records.models import ContextRecordSource
+ from archaeological_finds.models import FindSource
+ docs = {}
+
+ qs = [
+ self.source,
+ ContextRecordSource.objects.filter(context_record__operation=self),
+ FindSource.objects.filter(
+ find__base_finds__context_record__operation=self)]
+ for q in qs:
+ for res in q.values('source_type').distinct():
+ st = res['source_type']
+ if st not in docs:
+ docs[st] = 0
+ docs[st] += q.filter(source_type=st).count()
+ docs = [(unicode(SourceType.objects.get(pk=k)), docs[k]) for k in docs]
+ return sorted(docs, key=lambda x: x[0])
+
+ @property
+ def nb_stats_finds_by_ue(self, update=False):
+ return self._get_or_set_stats('_nb_stats_finds_by_ue', update)
+
+ def _nb_stats_finds_by_ue(self):
+ _(u"Mean")
+ res, finds = {}, []
+ for cr in self.context_record.all():
+ finds.append(cr.base_finds.count())
+ if not finds:
+ return res
+ res['mean'] = float(sum(finds)) / max(len(finds), 1)
+ res['min'] = min(finds)
+ res['max'] = max(finds)
+ res['mode'] = u" ; ".join([str(m) for m in mode(finds)])
+ return res
+
+
m2m_changed.connect(cached_label_changed, sender=Operation.towns.through)
diff --git a/archaeological_operations/templates/ishtar/sheet_operation.html b/archaeological_operations/templates/ishtar/sheet_operation.html
index f0846f11f..f8421c830 100644
--- a/archaeological_operations/templates/ishtar/sheet_operation.html
+++ b/archaeological_operations/templates/ishtar/sheet_operation.html
@@ -6,110 +6,69 @@
{% block content %}
{% window_nav item window_id 'show-operation' 'operation_modify' 'show-historized-operation' 'revert-operation' previous next %}
-{% if previous or next %}
-
-{% endif %}
-
-
-
-
-
{% if item.virtual_operation %}
{% trans "This operation is virtual." %}
{% endif %}
-
-{% trans "General"%}
-{% if item.common_name %} {{ item.common_name }}
{% endif %}
-{% if item.year %} {{ item.year }}
{% endif %}
-{% if item.operation_code %} {{ item.operation_code }}
{% endif %}
-
-{% if item.code_patriarche %} OA{{ item.code_patriarche }}
{%else%}
- {%trans "Patriarche OA code not yet recorded!"%}
{%endif%}
-{% field "Old code" item.old_code %}
-
- {% if item.history_date %}{{ item.history_date }}{% else %}{{ item.history.all.0.history_date }}{% endif %}
- {{ item.history_creator.ishtaruser.full_label }}
-
-{% if item.start_date %} {{ item.start_date }}
- {{ item.excavation_end_date|default:"-" }}
-{%endif%}
-{% if item.scientist %} {{ item.scientist.full_label }}
{%endif%}
-{% if item.in_charge %} {{ item.in_charge.full_label }}
{%endif%}
-{% if item.operator %} {{ item.operator }}
{% endif %}
- {% if item.is_active %}{%trans "Active file"%}
-{% else %}{%trans "Closed operation"%}
-{% if item.closing.date %} {{ item.closing.date }} {%trans "by" %} {{ item.closing.user }}
{% endif %}
-{% endif %}
-{% field "Report delivery date" item.report_delivery_date %}
-{% field "Report processing" item.report_processing %}
- {{ item.operation_type }}
-{% if item.surface %} {{ item.surface }} m2 ({{ item.surface_ha }} ha)
{% endif %}
-{% if item.cost %} {{ item.cost }} €{% if item.cost_by_m2 %}, ({{ item.cost_by_m2 }} €/m2){%endif%}
{%endif%}
-{% if item.duration %} {{ item.duration }} {%trans "Day"%}s
{%endif%}
-
-{% field_multiple "Remains" item.remains %}
-{% field_multiple "Periods" item.periods %}
-
-{% if item.QUALITY_DICT %}
-{% field "Record quality" item.record_quality|from_dict:item.QUALITY_DICT %}
+{% if not item.code_patriarche %}
+ {%trans "Patriarche OA code not yet recorded!"%}
{% endif %}
-{% if item.history_object and item.history_object.QUALITY_DICT %}
-{% field "Record quality" item.record_quality|from_dict:item.history_object.QUALITY_DICT %}
-{% endif %}
-
-{% field "Abstract" item.abstract %}
-{% if item.associated_file %}
- {{ item.associated_file }}
-{% if item.associated_file.is_preventive %}
-{#{% if item.operator_reference_code %} {{ item.operator_reference_code }}
{% endif %}#}
-
-{% field "Responsible for town planning service" item.associated_file.responsible_town_planning_service.full_address %}
+{% trans "General"%}
+" "
" %}
+{% field_li "Comment" item.comment "" "
" %}
+
{% if item.towns.count %}
{% trans "Localisation"%}
- {{ item.towns.all|join:", " }}
+
{% endif %}
-{% if item.associated_file.address %} {{ item.associated_file.address }}
-{% if item.associated_file.address_complement %} {{ item.associated_file.address_complement }}
{%endif%}
-{% if item.associated_file.postal_code %} {{ item.associated_file.postal_code }}
{%endif%}
-{%endif%}
-{% comment %}
- {{ item.lambert_x }}
- {{ item.lambert_y }}
- {{ item.altitude }}
-{% endcomment %}
-
{% if item.right_relations.count %}
{% trans "Relations"%}
{% for rel in item.right_relations.all %}
@@ -130,8 +89,8 @@
{% include "ishtar/blocks/window_tables/parcels.html" %}
{% if item.administrative_act %}
-{% trans "Administrative acts" as administrativeacts_label %}
-{% table_administrativact administrativeacts_label item.administrative_act.all %}
+{% trans "Administrative acts" %}
+{% table_administrativact "" item.administrative_act.all %}
{% endif %}
{% trans "Document from this operation" as operation_docs %}
@@ -139,9 +98,9 @@
{% dynamic_table_document operation_docs 'operation_docs' 'operation' item.pk '' output %}
{% endif %}
-{% trans "Context records" as context_records %}
{% if item.context_record.count %}
-{% dynamic_table_document context_records 'context_records_for_ope' 'operation' item.pk 'TABLE_COLS_FOR_OPE' output %}
+{% trans "Context records" %}
+{% dynamic_table_document '' 'context_records_for_ope' 'operation' item.pk 'TABLE_COLS_FOR_OPE' output %}
{% endif %}
{% trans "Documents from associated context records" as cr_docs %}
@@ -159,4 +118,91 @@
{% dynamic_table_document finds_docs 'finds_docs' 'find__base_finds__context_record__operation' item.pk '' output %}
{% endif %}
+{% trans "Statistics" %}
+
+{% trans "Administrative acts" %}
+
+
+{% trans "Context records" %}
+
+
+
+{% trans "Finds" %}
+
+
+
+{% trans "Sources" %}
+
+
+
+{% if item.nb_stats_finds_by_ue %}
+{% trans "Finds by context records" %}
+
+{% endif %}
+
{% endblock %}
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index c536b64ae..186d88f1a 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -2200,6 +2200,7 @@ pre_delete.connect(pre_delete_import, sender=Import)
class Organization(Address, Merge, OwnPerms, ValueGetter):
TABLE_COLS = ('name', 'organization_type',)
+ SHOW_URL = 'show-organization'
name = models.CharField(_(u"Name"), max_length=500)
organization_type = models.ForeignKey(OrganizationType,
verbose_name=_(u"Type"))
diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css
index 02f4d930b..7dd424974 100644
--- a/ishtar_common/static/media/style.css
+++ b/ishtar_common/static/media/style.css
@@ -1276,6 +1276,10 @@ table.table-form td input{
border-color:#922;
}
+.clean-table.small {
+ width: auto;
+}
+
.clean-table {
margin: 10px 0 10px 0;
width: 100%;
diff --git a/ishtar_common/templatetags/link_to_window.py b/ishtar_common/templatetags/link_to_window.py
index 288bfcd8a..93924b77a 100644
--- a/ishtar_common/templatetags/link_to_window.py
+++ b/ishtar_common/templatetags/link_to_window.py
@@ -10,6 +10,8 @@ register = Library()
@register.filter
def link_to_window(item):
+ if not item:
+ return ""
return mark_safe(
u' '
diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py
index 9fe7a3a00..d4973012e 100644
--- a/ishtar_common/utils.py
+++ b/ishtar_common/utils.py
@@ -56,3 +56,8 @@ def shortify(lbl, number=20):
if len(lbl) <= number:
return lbl
return lbl[:number - len(SHORTIFY_STR)] + SHORTIFY_STR
+
+
+def mode(array):
+ most = max(list(map(array.count, array)))
+ return list(set(filter(lambda x: array.count(x) == most, array)))
diff --git a/version.py b/version.py
index 0f4077755..424b4478e 100644
--- a/version.py
+++ b/version.py
@@ -1,4 +1,4 @@
-VERSION = (0, 97, 4)
+VERSION = (0, 97, 4, 1)
def get_version():
--
cgit v1.2.3