diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2017-04-07 12:16:37 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2017-04-07 12:16:37 +0200 |
commit | 6b87dae76d931ead9838db1f66a6ff1fe1cc1dd1 (patch) | |
tree | 0a2273d9db0499214e18a041987339eac9d20ac9 /archaeological_warehouse | |
parent | 75730c0ac55c935c03e58977405b4b8a2233595d (diff) | |
parent | 3d8b8d86f01ecb9b37e24e25fd15500b8f4fb2a0 (diff) | |
download | Ishtar-6b87dae76d931ead9838db1f66a6ff1fe1cc1dd1.tar.bz2 Ishtar-6b87dae76d931ead9838db1f66a6ff1fe1cc1dd1.zip |
Merge branch 'master' into v0.9
Diffstat (limited to 'archaeological_warehouse')
7 files changed, 254 insertions, 47 deletions
diff --git a/archaeological_warehouse/forms.py b/archaeological_warehouse/forms.py index 3dd38d747..94e31b759 100644 --- a/archaeological_warehouse/forms.py +++ b/archaeological_warehouse/forms.py @@ -100,7 +100,7 @@ class WarehouseForm(ManageOldType, forms.Form): associated_models = {'warehouse_type': models.WarehouseType, 'person_in_charge': Person} - name = forms.CharField(label=_(u"Name"), max_length=40, + name = forms.CharField(label=_(u"Name"), max_length=200, validators=[name_validator]) warehouse_type = forms.ChoiceField(label=_(u"Warehouse type"), choices=[]) diff --git a/archaeological_warehouse/locale/django.pot b/archaeological_warehouse/locale/django.pot index d5d6e9b63..eb88b13af 100644 --- a/archaeological_warehouse/locale/django.pot +++ b/archaeological_warehouse/locale/django.pot @@ -4,19 +4,20 @@ # Étienne Loks <etienne.loks at peacefrogs net>, 2010-2011. # Valérie-Emma Leroux <emma@iggdrasil.net>, 2016. #zanata # Valérie-Emma Leroux <emma@iggdrasil.net>, 2017. #zanata +# Étienne Loks <etienne.loks@iggdrasil.net>, 2017. #zanata msgid "" msgstr "" -#: forms.py:36 forms.py:99 ishtar_menu.py:40 models.py:63 models.py:103 +#: forms.py:36 forms.py:99 ishtar_menu.py:40 models.py:65 models.py:204 #: templates/ishtar/sheet_warehouse.html:4 msgid "Warehouse" msgstr "" -#: forms.py:45 forms.py:50 models.py:281 +#: forms.py:45 forms.py:50 models.py:389 msgid "Division" msgstr "" -#: forms.py:52 models.py:127 +#: forms.py:52 models.py:228 msgid "Order" msgstr "" @@ -24,15 +25,15 @@ msgstr "" msgid "There are identical divisions." msgstr "" -#: forms.py:70 models.py:53 +#: forms.py:70 models.py:55 msgid "Divisions" msgstr "" -#: forms.py:74 forms.py:103 models.py:45 models.py:100 +#: forms.py:74 forms.py:103 models.py:47 models.py:201 msgid "Name" msgstr "" -#: forms.py:75 forms.py:105 models.py:36 models.py:47 +#: forms.py:75 forms.py:105 models.py:38 models.py:49 msgid "Warehouse type" msgstr "" @@ -44,11 +45,11 @@ msgstr "" msgid "Warehouse search" msgstr "" -#: forms.py:108 models.py:50 +#: forms.py:108 models.py:52 msgid "Person in charge" msgstr "" -#: forms.py:114 forms.py:184 models.py:51 models.py:181 +#: forms.py:114 forms.py:184 models.py:53 models.py:282 msgid "Comment" msgstr "" @@ -80,16 +81,16 @@ msgstr "" msgid "Would you like to delete this warehouse?" msgstr "" -#: forms.py:158 models.py:192 models.py:279 +#: forms.py:158 models.py:293 models.py:386 #: templates/ishtar/sheet_container.html:4 msgid "Container" msgstr "" -#: forms.py:163 forms.py:237 models.py:142 +#: forms.py:163 forms.py:237 models.py:243 msgid "Ref." msgstr "" -#: forms.py:164 forms.py:236 models.py:145 models.py:179 +#: forms.py:164 forms.py:236 models.py:246 models.py:280 msgid "Container type" msgstr "" @@ -97,7 +98,7 @@ msgstr "" msgid "Current location (warehouse)" msgstr "" -#: forms.py:172 models.py:176 +#: forms.py:172 models.py:277 msgid "Responsible warehouse" msgstr "" @@ -148,7 +149,7 @@ msgstr "" msgid "Packaged finds" msgstr "" -#: forms.py:280 models.py:182 +#: forms.py:280 models.py:283 msgid "Localisation" msgstr "" @@ -176,115 +177,116 @@ msgstr "" msgid "Deletion" msgstr "" -#: ishtar_menu.py:57 models.py:193 templates/ishtar/sheet_warehouse.html:20 +#: ishtar_menu.py:57 models.py:294 templates/ishtar/sheet_warehouse.html:20 +#: templates/ishtar/sheet_warehouse.html:53 msgid "Containers" msgstr "" -#: models.py:37 +#: models.py:39 msgid "Warehouse types" msgstr "" -#: models.py:56 models.py:187 +#: models.py:58 models.py:288 msgid "External ID" msgstr "" -#: models.py:58 models.py:189 +#: models.py:60 models.py:290 msgid "External ID is set automatically" msgstr "" -#: models.py:64 +#: models.py:66 msgid "Warehouses" msgstr "" -#: models.py:66 +#: models.py:68 msgid "Can view all Warehouses" msgstr "" -#: models.py:67 +#: models.py:69 msgid "Can view own Warehouse" msgstr "" -#: models.py:68 +#: models.py:70 msgid "Can add own Warehouse" msgstr "" -#: models.py:69 +#: models.py:71 msgid "Can change own Warehouse" msgstr "" -#: models.py:70 +#: models.py:72 msgid "Can delete own Warehouse" msgstr "" -#: models.py:102 +#: models.py:203 msgid "Description" msgstr "" -#: models.py:107 models.py:108 +#: models.py:208 models.py:209 msgid "Collection" msgstr "" -#: models.py:117 +#: models.py:218 msgid "Warehouse division type" msgstr "" -#: models.py:118 +#: models.py:219 msgid "Warehouse division types" msgstr "" -#: models.py:138 +#: models.py:239 msgid "Length (mm)" msgstr "" -#: models.py:139 +#: models.py:240 msgid "Width (mm)" msgstr "" -#: models.py:140 +#: models.py:241 msgid "Height (mm)" msgstr "" -#: models.py:141 +#: models.py:242 msgid "Volume (l)" msgstr "" -#: models.py:146 +#: models.py:247 msgid "Container types" msgstr "" -#: models.py:165 +#: models.py:266 msgid "Location - index" msgstr "" -#: models.py:166 +#: models.py:267 msgid "Precise localisation" msgstr "" -#: models.py:167 +#: models.py:268 msgid "Type" msgstr "" -#: models.py:173 +#: models.py:274 msgid "Location (warehouse)" msgstr "" -#: models.py:180 +#: models.py:281 msgid "Container ref." msgstr "" -#: models.py:184 +#: models.py:285 msgid "Cached location" msgstr "" -#: models.py:282 +#: models.py:390 msgid "Reference" msgstr "" -#: models.py:285 +#: models.py:393 msgid "Container localisation" msgstr "" -#: models.py:286 +#: models.py:394 msgid "Container localisations" msgstr "" @@ -320,8 +322,34 @@ msgstr "" msgid "Attached containers" msgstr "" -#: templates/ishtar/wizard/wizard_containerlocalisation.html:6 +#: templates/ishtar/sheet_warehouse.html:29 +msgid "Statistics" +msgstr "" + +#: templates/ishtar/sheet_warehouse.html:30 +msgid "Theses number are updated hourly" +msgstr "" + +#: templates/ishtar/sheet_warehouse.html:32 +msgid "Finds" +msgstr "" + +#: templates/ishtar/sheet_warehouse.html:39 +msgid "Finds by location in the warehouse" +msgstr "" + +#: templates/ishtar/sheet_warehouse.html:59 +msgid "Containers by location in the warehouse" +msgstr "" + +#: templates/ishtar/wizard/wizard_containerlocalisation.html:8 msgid "" "No division set for this warehouse. Define at least one division to localise " "containers in this warehouse." msgstr "" + +#: templates/ishtar/wizard/wizard_warehouse_divisions.html:8 +msgid "" +"Containers with localisation are associated to this warehouse. You cannot " +"change divisions." +msgstr "" diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index d1918f46a..96814339c 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, 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 @@ -28,7 +29,8 @@ 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 class WarehouseType(GeneralType): @@ -40,7 +42,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, @@ -78,6 +80,105 @@ 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) + + @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 + 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): + from archaeological_finds.models import Find + 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(): @@ -208,6 +309,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') @@ -276,7 +383,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..eb31392e4 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 "These numbers 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/archaeological_warehouse/templates/ishtar/wizard/wizard_containerlocalisation.html b/archaeological_warehouse/templates/ishtar/wizard/wizard_containerlocalisation.html index 2e817ff41..41be02748 100644 --- a/archaeological_warehouse/templates/ishtar/wizard/wizard_containerlocalisation.html +++ b/archaeological_warehouse/templates/ishtar/wizard/wizard_containerlocalisation.html @@ -3,7 +3,9 @@ {% load url from future %} {% block form_head %} {% if not wizard.form.fields %} -<p class='warning'>{% trans "No division set for this warehouse. Define at least one division to localise containers in this warehouse." %}<br/> +<p class="alert"> + <i class="fa fa-exclamation-triangle"></i> + {% trans "No division set for this warehouse. Define at least one division to localise containers in this warehouse." %}<br/> {{wizard.form.warehouse}} <a href="{% url 'warehouse_modify' wizard.form.warehouse.pk %}"> <span class="fa-stack fa-lg"> diff --git a/archaeological_warehouse/templates/ishtar/wizard/wizard_warehouse_divisions.html b/archaeological_warehouse/templates/ishtar/wizard/wizard_warehouse_divisions.html new file mode 100644 index 000000000..83dbfc0fe --- /dev/null +++ b/archaeological_warehouse/templates/ishtar/wizard/wizard_warehouse_divisions.html @@ -0,0 +1,11 @@ +{% extends "ishtar/wizard/default_wizard.html" %} +{% load i18n %} +{% load url from future %} +{% block form_head %} +{% if wizard.form.readonly %} +<p class="alert"> + <i class="fa fa-exclamation-triangle"></i> + {% trans "Containers with localisation are associated to this warehouse. You cannot change divisions." %}<br/> +</p> +{% endif %} +{% endblock %} diff --git a/archaeological_warehouse/wizards.py b/archaeological_warehouse/wizards.py index 9ecc16b3f..571e56b1b 100644 --- a/archaeological_warehouse/wizards.py +++ b/archaeological_warehouse/wizards.py @@ -64,6 +64,20 @@ class WarehouseModificationWizard(Wizard): model = models.Warehouse modification = True wizard_done_window = reverse_lazy('show-warehouse') + wizard_templates = { + 'divisions-warehouse_modification': + 'ishtar/wizard/wizard_warehouse_divisions.html', + } + + def get_form_kwargs(self, step=None): + kwargs = super(WarehouseModificationWizard, self).get_form_kwargs(step) + if step == "divisions-warehouse_modification": + current_warehouse = self.get_current_object() + q = models.ContainerLocalisation.objects.filter( + division__warehouse=current_warehouse) + if q.count(): + kwargs['readonly'] = True + return kwargs class WarehouseDeletionWizard(DeletionWizard): |