From 8ff14b5402eb5af8685d2c3783b3bd5bdb2ec9a2 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Wed, 29 Mar 2017 19:14:27 +0200 Subject: Operations: add collaborators field (refs #3368) --- archaeological_operations/models.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'archaeological_operations/models.py') diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index e741f5644..782f9cf35 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -269,6 +269,8 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms, verbose_name=_(u"In charge"), on_delete=models.SET_NULL, related_name='operation_responsability') + collaborators = models.ManyToManyField( + Person, blank=True, null=True, verbose_name=_(u"Collaborators")) year = models.IntegerField(_(u"Year"), null=True, blank=True) operation_code = models.IntegerField(_(u"Numeric reference"), null=True, blank=True) -- cgit v1.2.3 From 88359ac26f56f3c6dae232bb9af529b9a35c758e Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Wed, 29 Mar 2017 19:42:02 +0200 Subject: Access control: collaborators are included in get_own_query for operations, context records and finds (refs #3196) --- archaeological_context_records/models.py | 1 + archaeological_finds/models_finds.py | 2 ++ archaeological_operations/models.py | 6 ++++-- 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'archaeological_operations/models.py') diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index a16b4cae7..bba9c643b 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -367,6 +367,7 @@ class ContextRecord(BaseHistorizedItem, ImageModel, OwnPerms, def get_query_owns(cls, user): return (Q(operation__scientist=user.ishtaruser.person) | Q(operation__in_charge=user.ishtaruser.person) | + Q(operation__collaborators__pk=user.ishtaruser.person.pk) | Q(history_creator=user)) \ & Q(operation__end_date__isnull=True) diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index cbd13e925..735bc01a8 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -872,6 +872,8 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): ishtaruser.person) | Q(base_finds__context_record__operation__in_charge=user. ishtaruser.person) | + Q(base_finds__context_record__operation__collaborators__pk=user. + ishtaruser.person.pk) | Q(history_creator=user)) \ & Q(base_finds__context_record__operation__end_date__isnull=True) diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 782f9cf35..13997a632 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -572,8 +572,10 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms, @classmethod def get_query_owns(cls, user): - return (Q(in_charge=user.ishtaruser.person) |\ - Q(scientist=user.ishtaruser.person) |\ + return ( + Q(in_charge=user.ishtaruser.person) | + Q(scientist=user.ishtaruser.person) | + Q(collaborators__pk=user.ishtaruser.person.pk) | Q(history_creator=user)) & Q(end_date__isnull=True) def is_active(self): -- cgit v1.2.3 From c6835861d3b3575b25161eb13b8284b0da663ab8 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Thu, 30 Mar 2017 12:31:55 +0200 Subject: Sources: fix query owns requests (refs #3196) --- archaeological_context_records/models.py | 9 +++++++++ archaeological_finds/models_finds.py | 12 ++++++++++++ archaeological_operations/models.py | 7 +++++++ ishtar_common/models.py | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) (limited to 'archaeological_operations/models.py') diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index bba9c643b..5e6b4622b 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -624,3 +624,12 @@ class ContextRecordSource(Source): @property def owner(self): return self.context_record + + @classmethod + def get_query_owns(cls, user): + return ( + Q(context_record__operation__scientist=user.ishtaruser.person) | + Q(context_record__operation__in_charge=user.ishtaruser.person) | + Q(context_record__operation__collaborators__pk= + user.ishtaruser.person.pk)) \ + & Q(context_record__operation__end_date__isnull=True) diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 735bc01a8..f6be69f93 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -1078,6 +1078,18 @@ class FindSource(Source): def owner(self): return self.find + @classmethod + def get_query_owns(cls, user): + return (Q(find__base_finds__context_record__operation__scientist=user. + ishtaruser.person) | + Q(find__base_finds__context_record__operation__in_charge=user. + ishtaruser.person) | + Q( + find__base_finds__context_record__operation__collaborators__pk=user. + ishtaruser.person.pk)) \ + & Q( + find__base_finds__context_record__operation__end_date__isnull=True) + class Property(LightHistorizedItem): find = models.ForeignKey(Find, verbose_name=_(u"Find")) diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index 13997a632..d18181722 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -915,6 +915,13 @@ class OperationSource(Source): return u"{}-{:04d}".format(self.operation.code_patriarche or '', self.index) + @classmethod + def get_query_owns(cls, user): + return (Q(operation__in_charge=user.ishtaruser.person) | + Q(operation__scientist=user.ishtaruser.person) | + Q(operation__collaborators__pk=user.ishtaruser.person.pk)) \ + & Q(operation__end_date__isnull=True) + class ActType(GeneralType): TYPE = (('F', _(u'Archaeological file')), diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 35608abdf..6a9f86569 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -2903,7 +2903,7 @@ post_save.connect(post_save_cache, sender=Format) post_delete.connect(post_save_cache, sender=Format) -class Source(ImageModel, models.Model): +class Source(OwnPerms, ImageModel, models.Model): title = models.CharField(_(u"Title"), max_length=300) external_id = models.CharField(_(u"External ID"), max_length=12, null=True, blank=True) -- cgit v1.2.3 From b1b03d624a8c501bc15a21e90e7abde2fd28698f Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Thu, 30 Mar 2017 19:27:00 +0200 Subject: Access control: define get_query_own for persons --- archaeological_operations/models.py | 4 +++- ishtar_common/models.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'archaeological_operations/models.py') diff --git a/archaeological_operations/models.py b/archaeological_operations/models.py index d18181722..bef149b2c 100644 --- a/archaeological_operations/models.py +++ b/archaeological_operations/models.py @@ -270,7 +270,9 @@ class Operation(ClosedItem, BaseHistorizedItem, ImageModel, OwnPerms, on_delete=models.SET_NULL, related_name='operation_responsability') collaborators = models.ManyToManyField( - Person, blank=True, null=True, verbose_name=_(u"Collaborators")) + Person, blank=True, null=True, verbose_name=_(u"Collaborators"), + related_name='operation_collaborator' + ) year = models.IntegerField(_(u"Year"), null=True, blank=True) operation_code = models.IntegerField(_(u"Numeric reference"), null=True, blank=True) diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 048af1294..66433747c 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -2770,6 +2770,18 @@ class Person(Address, Merge, OwnPerms, ValueGetter): for fle in self.general_contractor.all(): fle.save() # force update of raw_general_contractor + @classmethod + def get_query_owns(cls, user): + return \ + Q(operation_scientist_responsability__collaborators__ishtaruser + =user.ishtaruser) | \ + Q(operation_scientist_responsability__scientist__ishtaruser + =user.ishtaruser) | \ + Q(operation_collaborator__collaborators__ishtaruser + =user.ishtaruser) | \ + Q(operation_collaborator__scientist__ishtaruser + =user.ishtaruser) + class IshtarUser(User): TABLE_COLS = ('username', 'person__name', 'person__surname', -- cgit v1.2.3 From 2e512bc9dffca5624342aa75ca01b3ff7f390141 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Wed, 5 Apr 2017 11:33:54 +0200 Subject: Warehouse sheet: provide statistics (refs #3398) - Number of finds (total and by places) - Number of container (total and by places) --- archaeological_finds/models_finds.py | 8 +- archaeological_operations/models.py | 8 -- .../templates/ishtar/sheet_operation.html | 1 + archaeological_warehouse/models.py | 103 ++++++++++++++++++++- .../templates/ishtar/sheet_warehouse.html | 44 +++++++++ example_project/settings.py | 2 +- ishtar_common/models.py | 13 +++ ishtar_common/static/media/style.css | 6 ++ 8 files changed, 168 insertions(+), 17 deletions(-) (limited to 'archaeological_operations/models.py') 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 %}

{% trans "Statistics" %}

+{% trans "Theses number are updated hourly" %}

{% trans "Administrative acts" %}

    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 %} +

    {% trans "Statistics" %}

    +{% trans "Theses number are updated hourly" %} + +

    {% trans "Finds" %}

    +
      + {% field_li "Number of attached finds" item.number_of_finds %} + {% field_li "Number of hosted finds" item.number_of_finds_hosted %} +
    + +{% if item.number_of_finds_by_place %} +

    {% trans "Finds by location in the warehouse" %}

    +
      + {% for items in item.number_of_finds_by_place %} +
    • + + {% for item in items %} + + {% endfor %} +
      {{item.0}}{{item.1}}
      +
    • + {% endfor %} +
    +{% endif %} + +

    {% trans "Containers" %}

    +
      + {% field_li "Number of containers" item.number_of_containers %} +
    + +{% if item.number_of_containers_by_place %} +

    {% trans "Containers by location in the warehouse" %}

    +
      + {% for items in item.number_of_containers_by_place %} +
    • + + {% for item in items %} + + {% endfor %} +
      {{item.0}}{{item.1}}
      +
    • + {% endfor %} +
    +{% 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; -- cgit v1.2.3