From 920d6b15131521200ace711cbb78878eb378c744 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Sun, 19 Mar 2017 18:28:08 +0100 Subject: Containers: add an image (refs #3393) --- archaeological_warehouse/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'archaeological_warehouse/models.py') diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index 91184c907..d3671adf2 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2012 Étienne Loks +# Copyright (C) 2012-2017 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -28,7 +28,7 @@ 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 + LightHistorizedItem, OwnPerms, Address, Person, post_save_cache, ImageModel class WarehouseType(GeneralType): @@ -149,9 +149,10 @@ post_save.connect(post_save_cache, sender=ContainerType) post_delete.connect(post_save_cache, sender=ContainerType) -class Container(LightHistorizedItem): +class Container(LightHistorizedItem, ImageModel): TABLE_COLS = ['reference', 'container_type__label', 'cached_location', 'divisions_lbl'] + IMAGE_PREFIX = 'containers/' # search parameters EXTRA_REQUEST_KEYS = { -- cgit v1.2.3 From f7a4bd783df87aea052aba07b2a48001b6389772 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Tue, 21 Mar 2017 19:18:15 +0100 Subject: Finds search: conditionnal search (warehouse module available) for warehouses and containers (refs #3416) --- archaeological_finds/forms.py | 60 ++++++++++++++++------ archaeological_finds/models_finds.py | 2 + archaeological_finds/views.py | 42 +++++++++++++-- archaeological_finds/wizards.py | 6 ++- archaeological_warehouse/models.py | 2 +- .../templates/ishtar/sheet_container.html | 3 +- ishtar_common/wizards.py | 26 ++++++---- 7 files changed, 108 insertions(+), 33 deletions(-) (limited to 'archaeological_warehouse/models.py') diff --git a/archaeological_finds/forms.py b/archaeological_finds/forms.py index 1e45ca4b3..09d23a3bc 100644 --- a/archaeological_finds/forms.py +++ b/archaeological_finds/forms.py @@ -77,7 +77,8 @@ __all__ = [ 'SourceTreatmentFileFormSelection', 'TreatmentSourceFormSelection', 'TreatmentFileSourceFormSelection', 'RecordFormSelection', 'FindForm', 'DateForm', 'DatingFormSet', - 'FindSelect', 'FindFormSelection', 'MultipleFindFormSelection', + 'FindSelect', 'FindFormSelection', 'FindFormSelectionWarehouseModule', + 'MultipleFindFormSelection', 'MultipleFindFormSelectionWarehouseModule', 'FindMultipleFormSelection', 'check_form', 'check_exist', 'check_not_exist', 'check_value', 'check_type_field', 'check_type_not_field', 'check_treatment', 'ResultFindForm', 'ResultFindFormSet', @@ -350,19 +351,6 @@ class FindSelect(TableSelect): label=_(u"Search within related operations"), choices=[], widget=widgets.CheckboxSelectMultiple) datings__period = forms.ChoiceField(label=_(u"Period"), choices=[]) - # TODO search by warehouse - container__location = forms.IntegerField( - label=_(u"Warehouse (location)"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-warehouse'), - associated_model=Warehouse), - validators=[valid_id(Warehouse)]) - container__responsible = forms.IntegerField( - label=_(u"Warehouse (responsible)"), - widget=widgets.JQueryAutoComplete( - reverse_lazy('autocomplete-warehouse'), - associated_model=Warehouse), - validators=[valid_id(Warehouse)]) material_types = forms.ChoiceField(label=_(u"Material type"), choices=[]) object_types = forms.ChoiceField(label=_(u"Object type"), choices=[]) preservation_to_considers = forms.ChoiceField( @@ -424,6 +412,23 @@ class FindSelect(TableSelect): return ids +class FindSelectWarehouseModule(FindSelect): + container__location = forms.IntegerField( + label=_(u"Warehouse (location)"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-warehouse'), + associated_model=Warehouse), + validators=[valid_id(Warehouse)]) + container__responsible = forms.IntegerField( + label=_(u"Warehouse (responsible)"), + widget=widgets.JQueryAutoComplete( + reverse_lazy('autocomplete-warehouse'), + associated_model=Warehouse), + validators=[valid_id(Warehouse)]) + container__index = forms.IntegerField(label=_(u"Container ID")) + container__reference = forms.IntegerField(label=_(u"Container ref.")) + + class FindFormSelection(forms.Form): form_label = _("Find search") associated_models = {'pk': models.Find} @@ -437,6 +442,16 @@ class FindFormSelection(forms.Form): validators=[valid_id(models.Find)]) +class FindFormSelectionWarehouseModule(FindFormSelection): + pk = forms.IntegerField( + label="", required=False, + widget=widgets.JQueryJqGrid( + reverse_lazy('get-find'), + FindSelectWarehouseModule, models.Find, + source_full=reverse_lazy('get-find-full')), + validators=[valid_id(models.Find)]) + + class MultipleFindFormSelection(forms.Form): form_label = _("Find search") associated_models = {'pk': models.Find} @@ -451,15 +466,28 @@ class MultipleFindFormSelection(forms.Form): validators=[valid_id(models.Find)]) +class MultipleFindFormSelectionWarehouseModule(MultipleFindFormSelection): + pk = forms.IntegerField( + label="", required=False, + widget=widgets.JQueryJqGrid( + reverse_lazy('get-find'), + FindSelectWarehouseModule, models.Find, + multiple_select=True, + source_full=reverse_lazy('get-find-full')), + validators=[valid_id(models.Find)]) + + class FindMultipleFormSelection(forms.Form): form_label = _(u"Upstream finds") associated_models = {'finds': models.Find} associated_labels = {'finds': _(u"Finds")} + # using FindSelectWarehouseModule because this form is only used with + # the warehouse module activated finds = forms.CharField( label="", required=False, widget=widgets.JQueryJqGrid( - reverse_lazy('get-find'), FindSelect, models.Find, multiple=True, - multiple_cols=[2, 3, 4]), + reverse_lazy('get-find'), FindSelectWarehouseModule, models.Find, + multiple=True, multiple_cols=[2, 3, 4]), validators=[valid_ids(models.Find)]) def clean(self): diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 1d4d6e369..c54fd1ed9 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -573,6 +573,8 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): 'image__isnull': 'image__isnull', 'container__location': 'container__location__pk', 'container__responsible': 'container__responsible__pk', + 'container__index': 'container__index', + 'container__reference': 'container__reference', } EXTRA_REQUEST_KEYS.update( dict( diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py index 0b9cb8952..03094cbb0 100644 --- a/archaeological_finds/views.py +++ b/archaeological_finds/views.py @@ -28,7 +28,7 @@ from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView from django.views.generic.edit import CreateView, FormView -from ishtar_common.models import IshtarUser +from ishtar_common.models import IshtarUser, get_current_profile from archaeological_operations.models import AdministrativeAct from ishtar_common.forms import FinalForm @@ -117,6 +117,15 @@ show_findbasket = show_item(models.FindBasket, 'findbasket') display_findbasket = display_item(models.FindBasket, show_url='show-find/basket-') + +def check_warehouse_module(self): + return get_current_profile().warehouse + + +def check_not_warehouse_module(self): + return not check_warehouse_module(self) + + find_creation_steps = [ ('selecrecord-find_creation', RecordFormSelectionTable), ('find-find_creation', FindForm), @@ -129,17 +138,32 @@ find_creation_wizard = FindWizard.as_view( label=_(u"New find"), url_name='find_creation',) +find_search_condition_dict = { + 'general-find_search': check_not_warehouse_module, + 'generalwarehouse-find_search': check_warehouse_module, +} + find_search_wizard = SearchWizard.as_view([ - ('general-find_search', FindFormSelection)], + ('general-find_search', FindFormSelection), + ('generalwarehouse-find_search', FindFormSelectionWarehouseModule)], label=_(u"Find search"), - url_name='find_search',) + url_name='find_search', + condition_dict=find_search_condition_dict +) + +find_modification_condition_dict = { + 'selec-find_modification': check_not_warehouse_module, + 'selecw-find_modification': check_warehouse_module, +} find_modification_wizard = FindModificationWizard.as_view([ ('selec-find_modification', FindFormSelection), + ('selecw-find_modification', FindFormSelectionWarehouseModule), ('selecrecord-find_modification', RecordFormSelection), ('find-find_modification', FindForm), ('dating-find_modification', DatingFormSet), ('final-find_modification', FinalForm)], + condition_dict=find_modification_condition_dict, label=_(u"Find modification"), url_name='find_modification',) @@ -152,9 +176,16 @@ def find_modify(request, pk): reverse('find_modification', kwargs={'step': 'selecrecord-find_modification'})) +find_deletion_condition_dict = { + 'selec-find_deletion': check_not_warehouse_module, + 'selecw-find_deletion': check_warehouse_module, +} + find_deletion_wizard = FindDeletionWizard.as_view([ ('selec-find_deletion', FindFormSelection), + ('selecw-find_deletion', FindFormSelectionWarehouseModule), ('final-find_deletion', FindDeletionForm)], + condition_dict=find_deletion_condition_dict, label=_(u"Find deletion"), url_name='find_deletion',) @@ -256,7 +287,10 @@ class SelectItemsInBasket(IshtarMixin, LoginRequiredMixin, TemplateView): except models.FindBasket.DoesNotExist: raise PermissionDenied context['basket'] = self.basket - context['form'] = MultipleFindFormSelection() + if get_current_profile().warehouse: + context['form'] = MultipleFindFormSelectionWarehouseModule() + else: + context['form'] = MultipleFindFormSelection() context['add_url'] = reverse('add_iteminbasket') context['list_url'] = reverse('list_iteminbasket', kwargs={'pk': self.basket.pk}) diff --git a/archaeological_finds/wizards.py b/archaeological_finds/wizards.py index 96a83e582..43fe6262f 100644 --- a/archaeological_finds/wizards.py +++ b/archaeological_finds/wizards.py @@ -74,7 +74,11 @@ class FindWizard(Wizard): class FindModificationWizard(FindWizard): modification = True - filter_owns = {'selec-find_modification': ['pk']} + main_item_select_keys = ('selec-', 'selecw-') + filter_owns = { + 'selec-find_modification': ['pk'], + 'selecw-find_modification': ['pk'], + } class FindDeletionWizard(DeletionWizard): diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index d3671adf2..d1918f46a 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -183,7 +183,7 @@ class Container(LightHistorizedItem, ImageModel): null=True, blank=True) cached_location = models.CharField(_(u"Cached location"), max_length=500, null=True, blank=True) - index = models.IntegerField(u"Index", default=0) + index = models.IntegerField(u"ID", default=0) external_id = models.TextField(_(u"External ID"), blank=True, null=True) auto_external_id = models.BooleanField( _(u"External ID is set automatically"), default=False) diff --git a/archaeological_warehouse/templates/ishtar/sheet_container.html b/archaeological_warehouse/templates/ishtar/sheet_container.html index 38bb79b4f..7845da2c1 100644 --- a/archaeological_warehouse/templates/ishtar/sheet_container.html +++ b/archaeological_warehouse/templates/ishtar/sheet_container.html @@ -12,12 +12,13 @@

{{ item.reference|default:"" }}

{{ item.container_type|default:"" }}

+

{{ item.responsible.name }} - {{ item.index }}

{% include "ishtar/blocks/sheet_external_id.html" %}
    {% field_li_detail "Responsible warehouse" item.responsible %} - {% include "ishtar/blocks/sheet_creation_section.html" %} {% field_li_detail "Location (warehouse)" item.location %} + {% include "ishtar/blocks/sheet_creation_section.html" %}
{% field "Location" item.precise_location %} {% field "Comment" item.comment "
" "
" %} diff --git a/ishtar_common/wizards.py b/ishtar_common/wizards.py index 874b68eae..7950bcc7e 100644 --- a/ishtar_common/wizards.py +++ b/ishtar_common/wizards.py @@ -132,6 +132,7 @@ class Wizard(NamedUrlWizardView): current_object_key = 'pk' ignore_init_steps = [] file_storage = default_storage + main_item_select_keys = ('selec-',) saved_args = {} # argument to pass on object save @@ -217,8 +218,9 @@ class Wizard(NamedUrlWizardView): dct = {'current_step_label': self.form_list[current_step].form_label, 'wizard_label': self.label, 'current_object': self.get_current_object(), - 'is_search': current_step.startswith('selec-') - if current_step else False + 'is_search': bool( + [k for k in self.main_item_select_keys + if current_step.startswith(k)]) if current_step else False } context.update(dct) if step == current_step: @@ -1004,20 +1006,24 @@ class Wizard(NamedUrlWizardView): def get_current_object(self): """Get the current object for an instancied wizard""" current_obj = None - main_form_key = 'selec-' + self.url_name - try: - idx = self.session_get_value(main_form_key, self.current_object_key) - idx = int(idx) - current_obj = self.model.objects.get(pk=idx) - except(TypeError, ValueError, ObjectDoesNotExist): - pass + for key in self.main_item_select_keys: + main_form_key = key + self.url_name + try: + idx = int(self.session_get_value(main_form_key, + self.current_object_key)) + current_obj = self.model.objects.get(pk=idx) + break + except(TypeError, ValueError, ObjectDoesNotExist): + pass return current_obj def get_form_initial(self, step, data=None): current_obj = self.get_current_object() current_step = self.steps.current request = self.request - if step.startswith('selec-') and step in self.form_list \ + step_is_main_select = bool([k for k in self.main_item_select_keys + if step.startswith(k)]) + if step_is_main_select and step in self.form_list \ and 'pk' in self.form_list[step].associated_models: model_name = self.form_list[step]\ .associated_models['pk'].__name__.lower() -- cgit v1.2.3 From 02136acb286d0f6b97dcbc715138f085d59767ea Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Tue, 28 Mar 2017 13:17:29 +0200 Subject: Access control: fix get owns query for UEs, finds, warehouses and containers --- archaeological_context_records/models.py | 7 ++++--- archaeological_files/models.py | 2 +- archaeological_finds/models_finds.py | 11 ++++++----- archaeological_warehouse/models.py | 11 +++++++++++ 4 files changed, 22 insertions(+), 9 deletions(-) (limited to 'archaeological_warehouse/models.py') diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py index 4df56c49f..a16b4cae7 100644 --- a/archaeological_context_records/models.py +++ b/archaeological_context_records/models.py @@ -365,9 +365,10 @@ class ContextRecord(BaseHistorizedItem, ImageModel, OwnPerms, @classmethod def get_query_owns(cls, user): - return Q(operation__scientist=user.ishtaruser.person) |\ - Q(operation__in_charge=user.ishtaruser.person) |\ - Q(history_creator=user) + return (Q(operation__scientist=user.ishtaruser.person) | + Q(operation__in_charge=user.ishtaruser.person) | + Q(history_creator=user)) \ + & Q(operation__end_date__isnull=True) @classmethod def get_owns(cls, user, menu_filtr=None, limit=None, diff --git a/archaeological_files/models.py b/archaeological_files/models.py index 7f37a298f..52f628817 100644 --- a/archaeological_files/models.py +++ b/archaeological_files/models.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2012-2016 Étienne Loks +# Copyright (C) 2012-2017 Étienne Loks # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index c54fd1ed9..cbd13e925 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -868,11 +868,12 @@ class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): @classmethod def get_query_owns(cls, user): - return Q(base_finds__context_record__operation__scientist=user. - ishtaruser.person) | \ - Q(base_finds__context_record__operation__in_charge=user. - ishtaruser.person) | \ - Q(history_creator=user) + return (Q(base_finds__context_record__operation__scientist=user. + ishtaruser.person) | + Q(base_finds__context_record__operation__in_charge=user. + ishtaruser.person) | + Q(history_creator=user)) \ + & Q(base_finds__context_record__operation__end_date__isnull=True) @classmethod def get_owns(cls, user, menu_filtr=None, limit=None, diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index d1918f46a..fe054a37b 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -21,6 +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.signals import post_save, post_delete from django.template.defaultfilters import slugify from django.utils.translation import ugettext_lazy as _, ugettext @@ -78,6 +79,10 @@ class Warehouse(Address, OwnPerms): return datetime.date.today().strftime('%Y-%m-%d') + '-' + \ slugify(unicode(self)) + @classmethod + def get_query_owns(cls, user): + return Q(person_in_charge__ishtaruser=user.ishtaruser) + def save(self, *args, **kwargs): super(Warehouse, self).save(*args, **kwargs) for container in self.containers.all(): @@ -208,6 +213,12 @@ class Container(LightHistorizedItem, ImageModel): cached_label = u" - ".join(items) return cached_label + @classmethod + def get_query_owns(cls, user): + return Q(history_creator=user) | \ + Q(location__person_in_charge__ishtaruser=user.ishtaruser) | \ + Q(responsible__person_in_charge__ishtaruser=user.ishtaruser) + @property def associated_filename(self): filename = datetime.date.today().strftime('%Y-%m-%d') -- 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_warehouse/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 From d9b7d7940a65b6c1b9ef790a119e153c3b63c091 Mon Sep 17 00:00:00 2001 From: Étienne Loks Date: Wed, 5 Apr 2017 14:11:16 +0200 Subject: Prevent circular import on some instances --- archaeological_warehouse/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'archaeological_warehouse/models.py') diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index 2851e1df0..96814339c 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -31,7 +31,6 @@ 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, DashboardFormItem -from archaeological_finds.models import Find class WarehouseType(GeneralType): @@ -87,10 +86,12 @@ class Warehouse(Address, DashboardFormItem, OwnPerms): @property def number_of_finds(self): + from archaeological_finds.models import Find return Find.objects.filter(container__responsible=self).count() @property def number_of_finds_hosted(self): + from archaeological_finds.models import Find return Find.objects.filter(container__location=self).count() @property @@ -161,6 +162,7 @@ class Warehouse(Address, DashboardFormItem, OwnPerms): return final_res def _number_of_finds_by_place(self): + from archaeological_finds.models import Find return self._number_of_items_by_place( Find, division_key='container__division') -- cgit v1.2.3