diff options
| -rw-r--r-- | archaeological_finds/models_finds.py | 8 | ||||
| -rw-r--r-- | archaeological_operations/models.py | 8 | ||||
| -rw-r--r-- | archaeological_operations/templates/ishtar/sheet_operation.html | 1 | ||||
| -rw-r--r-- | archaeological_warehouse/models.py | 103 | ||||
| -rw-r--r-- | archaeological_warehouse/templates/ishtar/sheet_warehouse.html | 44 | ||||
| -rw-r--r-- | example_project/settings.py | 2 | ||||
| -rw-r--r-- | ishtar_common/models.py | 13 | ||||
| -rw-r--r-- | ishtar_common/static/media/style.css | 6 | 
8 files changed, 168 insertions, 17 deletions
| diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 3785267b2..52601c896 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -37,7 +37,6 @@ from archaeological_operations.models import AdministrativeAct  from archaeological_context_records.models import ContextRecord, Dating  from ishtar_common.models import PRIVATE_FIELDS, SpatialReferenceSystem -from archaeological_warehouse.models import Container, Collection  class MaterialType(GeneralType): @@ -637,7 +636,8 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem):      datings = models.ManyToManyField(Dating, verbose_name=_(u"Dating"),                                       related_name='find')      container = models.ForeignKey( -        Container, verbose_name=_(u"Container"), blank=True, null=True, +        "archaeological_warehouse.Container", verbose_name=_(u"Container"), +        blank=True, null=True,          related_name='finds', on_delete=models.SET_NULL)      is_complete = models.NullBooleanField(_(u"Is complete?"), blank=True,                                            null=True) @@ -671,8 +671,8 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem):      estimated_value = models.FloatField(_(u"Estimated value"), blank=True,                                          null=True)      collection = models.ForeignKey( -        Collection, verbose_name=_(u"Collection"), blank=True, null=True, -        related_name='finds', on_delete=models.SET_NULL) +        "archaeological_warehouse.Collection", verbose_name=_(u"Collection"), +        blank=True, null=True, related_name='finds', on_delete=models.SET_NULL)      cached_label = models.TextField(_(u"Cached name"), null=True, blank=True)      history = HistoricalRecords()      BASKET_MODEL = FindBasket diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index bef149b2c..3826678c3 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -593,14 +593,6 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms,              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") diff --git a/archaeological_operations/templates/ishtar/sheet_operation.html b/archaeological_operations/templates/ishtar/sheet_operation.html index 71f41d8c4..5051c4604 100644 --- a/archaeological_operations/templates/ishtar/sheet_operation.html +++ b/archaeological_operations/templates/ishtar/sheet_operation.html @@ -142,6 +142,7 @@  {% endif %}  <h3>{% trans "Statistics" %}</h3> +<small class="centered"><em>{% trans "Theses number are updated hourly" %}</em></small>  <h4>{% trans "Administrative acts" %}</h4>  <ul class='form-flex'> diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index fe054a37b..2851e1df0 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -21,7 +21,7 @@ import datetime  from django.conf import settings  from django.contrib.gis.db import models -from django.db.models import Q +from django.db.models import Q, Count  from django.db.models.signals import post_save, post_delete  from django.template.defaultfilters import slugify  from django.utils.translation import ugettext_lazy as _, ugettext @@ -29,7 +29,9 @@ from django.utils.translation import ugettext_lazy as _, ugettext  from ishtar_common.utils import cached_label_changed  from ishtar_common.models import GeneralType, get_external_id, \ -    LightHistorizedItem, OwnPerms, Address, Person, post_save_cache, ImageModel +    LightHistorizedItem, OwnPerms, Address, Person, post_save_cache, \ +    ImageModel, DashboardFormItem +from archaeological_finds.models import Find  class WarehouseType(GeneralType): @@ -41,7 +43,7 @@ post_save.connect(post_save_cache, sender=WarehouseType)  post_delete.connect(post_save_cache, sender=WarehouseType) -class Warehouse(Address, OwnPerms): +class Warehouse(Address, DashboardFormItem, OwnPerms):      SHOW_URL = 'show-warehouse'      name = models.CharField(_(u"Name"), max_length=200)      warehouse_type = models.ForeignKey(WarehouseType, @@ -83,6 +85,98 @@ class Warehouse(Address, OwnPerms):      def get_query_owns(cls, user):          return Q(person_in_charge__ishtaruser=user.ishtaruser) +    @property +    def number_of_finds(self): +        return Find.objects.filter(container__responsible=self).count() + +    @property +    def number_of_finds_hosted(self): +        return Find.objects.filter(container__location=self).count() + +    @property +    def number_of_containers(self): +        return Container.objects.filter(location=self).count() + +    def _get_divisions(self, current_path, remaining_division, depth=0): +        if not remaining_division: +            return [current_path] +        current_division = remaining_division.pop(0) +        q = ContainerLocalisation.objects.filter( +            division=current_division, +        ) +        for div, ref in current_path: +            q = q.filter( +                container__division__division=div, +                container__division__reference=ref +            ) +        res = [] +        old_ref = None +        for ref in q.values('reference').order_by('reference').all(): +            if ref['reference'] == old_ref: +                continue +            old_ref = ref['reference'] +            cpath = current_path[:] +            cpath.append((current_division, ref['reference'])) +            for r in self._get_divisions(cpath, remaining_division[:], +                                         depth + 1): +                res.append(r) +        return res + +    @property +    def available_division_tuples(self): +        """ +        :return: ordered list of available paths. Each path is a list of +        tuple with the WarehouseDivisionLink and the reference. +        """ +        divisions = list( +            WarehouseDivisionLink.objects.filter(warehouse=self +                                                 ).order_by('order').all()) +        return self._get_divisions([], divisions) + +    def _number_of_items_by_place(self, model, division_key='division'): +        res = {} +        paths = self.available_division_tuples[:] +        for path in paths: +            q = model.objects +            cpath = [] +            for division, ref in path: +                lbl = u"{} {}".format(division.division, ref) +                cpath.append(lbl) +                attrs = { +                    division_key + "__division": division, +                    division_key + "__reference": ref +                } +                q = q.filter(**attrs) +                if tuple(cpath) not in res: +                    res[tuple(cpath)] = q.count() +        res = [(k, res[k]) for k in res] +        final_res, current_res, depth = [], [], 1 +        for path, nb in sorted(res, key=lambda x: (len(x[0]), x[0])): +            if depth != len(path): +                final_res.append(current_res[:]) +                current_res = [] +                depth = len(path) +            current_res.append((u" | ".join(path), nb)) +        final_res.append(current_res[:]) +        return final_res + +    def _number_of_finds_by_place(self): +        return self._number_of_items_by_place( +            Find, division_key='container__division') + +    @property +    def number_of_finds_by_place(self, update=False): +        return self._get_or_set_stats('_number_of_finds_by_place', update, +                                      settings.CACHE_SMALLTIMEOUT) + +    def _number_of_containers_by_place(self): +        return self._number_of_items_by_place(Container) + +    @property +    def number_of_containers_by_place(self, update=False): +        return self._get_or_set_stats('_number_of_containers_by_place', update, +                                      settings.CACHE_SMALLTIMEOUT) +      def save(self, *args, **kwargs):          super(Warehouse, self).save(*args, **kwargs)          for container in self.containers.all(): @@ -287,7 +381,8 @@ post_save.connect(cached_label_changed, sender=Container)  class ContainerLocalisation(models.Model): -    container = models.ForeignKey(Container, verbose_name=_(u"Container")) +    container = models.ForeignKey(Container, verbose_name=_(u"Container"), +                                  related_name='division')      division = models.ForeignKey(WarehouseDivisionLink,                                   verbose_name=_(u"Division"))      reference = models.CharField(_(u"Reference"), max_length=200, default='') diff --git a/archaeological_warehouse/templates/ishtar/sheet_warehouse.html b/archaeological_warehouse/templates/ishtar/sheet_warehouse.html index c31fc93b4..17a2c6c2b 100644 --- a/archaeological_warehouse/templates/ishtar/sheet_warehouse.html +++ b/archaeological_warehouse/templates/ishtar/sheet_warehouse.html @@ -26,4 +26,48 @@  {% dynamic_table_document '' 'containers' 'responsible' item.pk 'TABLE_COLS' output %}  {% endif %} +<h3>{% trans "Statistics" %}</h3> +<small class="centered"><em>{% trans "Theses number are updated hourly" %}</em></small> + +<h4>{% trans "Finds" %}</h4> +<ul class='form-flex'> +    {% field_li "Number of attached finds" item.number_of_finds %} +    {% field_li "Number of hosted finds" item.number_of_finds_hosted %} +</ul> + +{% if item.number_of_finds_by_place %} +<h4>{% trans "Finds by location in the warehouse" %}</h4> +<ul class='form-flex'> +    {% for items in item.number_of_finds_by_place %} +    <li> +        <table class='clean-table small'> +            {% for item in items %} +                <tr><th>{{item.0}}</th><td>{{item.1}}</td></tr> +            {% endfor %} +        </table> +    </li> +    {% endfor %} +</ul> +{% endif %} + +<h4>{% trans "Containers" %}</h4> +<ul class='form-flex'> +    {% field_li "Number of containers" item.number_of_containers %} +</ul> + +{% if item.number_of_containers_by_place %} +<h4>{% trans "Containers by location in the warehouse" %}</h4> +<ul class='form-flex'> +    {% for items in item.number_of_containers_by_place %} +    <li> +        <table class='clean-table small'> +            {% for item in items %} +            <tr><th>{{item.0}}</th><td>{{item.1}}</td></tr> +            {% endfor %} +        </table> +    </li> +    {% endfor %} +</ul> +{% endif %} +  {% endblock %} diff --git a/example_project/settings.py b/example_project/settings.py index efbf0297a..78d97f0ae 100644 --- a/example_project/settings.py +++ b/example_project/settings.py @@ -18,7 +18,7 @@ if "test" in sys.argv:  IMAGE_MAX_SIZE = (1024, 768)  THUMB_MAX_SIZE = (300, 300) -CACHE_SMALLTIMEOUT = 120 +CACHE_SMALLTIMEOUT = 60  CACHE_TIMEOUT = 3600  CACHE_BACKEND = 'memcached://127.0.0.1:11211/' diff --git a/ishtar_common/models.py b/ishtar_common/models.py index bf5c6056a..1632cbfb2 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -1306,6 +1306,19 @@ class UserDashboard:  class DashboardFormItem(object): +    """ +    Provide methods to manage statistics +    """ + +    def _get_or_set_stats(self, funcname, update, +                          timeout=settings.CACHE_TIMEOUT): +        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, timeout) +        return val +      @classmethod      def get_periods(cls, slice='month', fltr={}, date_source='creation'):          date_var = date_source + '_date' diff --git a/ishtar_common/static/media/style.css b/ishtar_common/static/media/style.css index fc840526e..011db3652 100644 --- a/ishtar_common/static/media/style.css +++ b/ishtar_common/static/media/style.css @@ -324,6 +324,12 @@ ul.list{      line-height:16px;  } +.centered{ +    text-align: center; +    width: 100%; +    display: inline-block; +} +  div.nav-button{      cursor:pointer;      width:15px; | 
