diff options
author | Étienne Loks <etienne.loks@iggdrasil.net> | 2020-04-06 12:31:38 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@iggdrasil.net> | 2021-02-28 12:15:20 +0100 |
commit | 33bcdb096df9e56238f6e21ad0c1da7bcd8e87c9 (patch) | |
tree | 1ac684557b5b6d2b7457b80040f8955e0db19d07 | |
parent | 2d7a40f22bc9da10515330becff08c589012c0ba (diff) | |
download | Ishtar-33bcdb096df9e56238f6e21ad0c1da7bcd8e87c9.tar.bz2 Ishtar-33bcdb096df9e56238f6e21ad0c1da7bcd8e87c9.zip |
New container localisation - adapt sheets, imports
19 files changed, 1033 insertions, 134 deletions
diff --git a/archaeological_finds/migrations/0104_findinsidecontainer.py b/archaeological_finds/migrations/0104_findinsidecontainer.py new file mode 100644 index 000000000..20a91c9cf --- /dev/null +++ b/archaeological_finds/migrations/0104_findinsidecontainer.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-04-03 16:33 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_finds', '0103_auto_20200129_1944'), + ] + + operations = [ + migrations.CreateModel( + name='FindInsideContainer', + fields=[ + ('find', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='inside_container', serialize=False, to='archaeological_finds.Find', verbose_name='Find')), + ], + options={ + 'db_table': 'find_inside_container', + 'managed': False, + }, + ), + ] diff --git a/archaeological_finds/models.py b/archaeological_finds/models.py index ad349e315..53fa35c40 100644 --- a/archaeological_finds/models.py +++ b/archaeological_finds/models.py @@ -3,7 +3,7 @@ from archaeological_finds.models_finds import MaterialType, ConservatoryState, \ FindBasket, Find, Property, BatchType, BFBulkView, FBulkView, \ FirstBaseFindView, AlterationType, AlterationCauseType, \ TreatmentEmergencyType, TreatmentType, CommunicabilityType, \ - MaterialTypeQualityType, ObjectTypeQualityType + MaterialTypeQualityType, ObjectTypeQualityType, FindInsideContainer from archaeological_finds.models_treatments import Treatment, \ AbsFindTreatments, FindUpstreamTreatments, FindDownstreamTreatments, \ FindTreatments, TreatmentFile, TreatmentFileType, \ @@ -18,4 +18,4 @@ __all__ = ['MaterialType', 'ConservatoryState', 'IntegrityType', 'CheckedType', 'FindNonModifTreatments', 'FindDownstreamTreatments', 'FindTreatments', 'TreatmentFile', 'TreatmentFileType', 'CommunicabilityType', 'MaterialTypeQualityType', - 'ObjectTypeQualityType'] + 'ObjectTypeQualityType', 'FindInsideContainer'] diff --git a/archaeological_finds/models_finds.py b/archaeological_finds/models_finds.py index 0260200e9..119a2022a 100644 --- a/archaeological_finds/models_finds.py +++ b/archaeological_finds/models_finds.py @@ -785,8 +785,7 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, 'base_finds__context_record__label', 'cached_materials', 'cached_object_types', 'cached_periods', - 'container__cached_label', - 'container_ref__cached_label'] + 'container__cached_label'] if settings.COUNTRY == 'fr': TABLE_COLS.insert( 3, 'base_finds__context_record__operation__code_patriarche') @@ -2294,7 +2293,8 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, def localisation_9(self): return self.get_localisation(8) - def set_localisation(self, place, context, value, is_ref=False): + def set_localisation(self, place, context, value, is_ref=False, + static=False): """ Get localisation reference in the warehouse @@ -2302,6 +2302,7 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, :param context: context of the request - not used :param value: localisation value :param is_ref: if true - reference container else current container + :param static: if true: do not create new container :return: None """ if is_ref: @@ -2314,19 +2315,17 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, return if is_ref: raise ImporterError( - _(u"No reference container have been set - the " - u"localisation cannot be set.")) + _("No reference container have been set - the " + "localisation cannot be set.")) else: raise ImporterError( - _(u"No container have been set - the localisation cannot " - u"be set.")) + _("No container have been set - the localisation cannot " + "be set.")) - localisation = container.set_localisation(place, value) - if value and value != '-' and not localisation: - raise ImporterError( - str(_(u"The division number {} have not been set " - u"for the warehouse {}.")).format( - place + 1, container.location)) + localisation, error = container.set_localisation( + place, value, static=static, return_errors=True) + if error: + raise ImporterError(error) @post_importer_action def set_reference_localisation_1(self, context, value): @@ -2374,6 +2373,60 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, set_reference_localisation_9.post_save = True @post_importer_action + def set_reference_static_localisation_1(self, context, value): + return self.set_localisation(0, context, value, is_ref=True, + static=True) + set_reference_static_localisation_1.post_save = True + + @post_importer_action + def set_reference_static_localisation_2(self, context, value): + return self.set_localisation(1, context, value, is_ref=True, + static=True) + set_reference_static_localisation_2.post_save = True + + @post_importer_action + def set_reference_static_localisation_3(self, context, value): + return self.set_localisation(2, context, value, is_ref=True, + static=True) + set_reference_static_localisation_3.post_save = True + + @post_importer_action + def set_reference_static_localisation_4(self, context, value): + return self.set_localisation(3, context, value, is_ref=True, + static=True) + set_reference_static_localisation_4.post_save = True + + @post_importer_action + def set_reference_static_localisation_5(self, context, value): + return self.set_localisation(4, context, value, is_ref=True, + static=True) + set_reference_static_localisation_5.post_save = True + + @post_importer_action + def set_reference_static_localisation_6(self, context, value): + return self.set_localisation(5, context, value, is_ref=True, + static=True) + set_reference_static_localisation_6.post_save = True + + @post_importer_action + def set_reference_static_localisation_7(self, context, value): + return self.set_localisation(6, context, value, is_ref=True, + static=True) + set_reference_static_localisation_7.post_save = True + + @post_importer_action + def set_reference_static_localisation_8(self, context, value): + return self.set_localisation(7, context, value, is_ref=True, + static=True) + set_reference_static_localisation_8.post_save = True + + @post_importer_action + def set_reference_static_localisation_9(self, context, value): + return self.set_localisation(8, context, value, is_ref=True, + static=True) + set_reference_static_localisation_9.post_save = True + + @post_importer_action def set_localisation_1(self, context, value): return self.set_localisation(0, context, value) set_localisation_1.post_save = True @@ -2418,6 +2471,51 @@ class Find(BulkUpdatedItem, ValueGetter, DocumentItem, BaseHistorizedItem, return self.set_localisation(8, context, value) set_localisation_9.post_save = True + @post_importer_action + def set_static_localisation_1(self, context, value): + return self.set_localisation(0, context, value, static=True) + set_static_localisation_1.post_save = True + + @post_importer_action + def set_static_localisation_2(self, context, value): + return self.set_localisation(1, context, value, static=True) + set_static_localisation_2.post_save = True + + @post_importer_action + def set_static_localisation_3(self, context, value): + return self.set_localisation(2, context, value, static=True) + set_static_localisation_3.post_save = True + + @post_importer_action + def set_static_localisation_4(self, context, value): + return self.set_localisation(3, context, value, static=True) + set_static_localisation_4.post_save = True + + @post_importer_action + def set_static_localisation_5(self, context, value): + return self.set_localisation(4, context, value, static=True) + set_static_localisation_5.post_save = True + + @post_importer_action + def set_static_localisation_6(self, context, value): + return self.set_localisation(5, context, value, static=True) + set_static_localisation_6.post_save = True + + @post_importer_action + def set_static_localisation_7(self, context, value): + return self.set_localisation(6, context, value, static=True) + set_static_localisation_7.post_save = True + + @post_importer_action + def set_static_localisation_8(self, context, value): + return self.set_localisation(7, context, value, static=True) + set_static_localisation_8.post_save = True + + @post_importer_action + def set_static_localisation_9(self, context, value): + return self.set_localisation(8, context, value, static=True) + set_static_localisation_9.post_save = True + def generate_index(self): """ Generate index based on operation or context record (based on @@ -2555,6 +2653,50 @@ m2m_changed.connect(base_find_find_changed, sender=Find.base_finds.through) m2m_changed.connect(document_attached_changed, sender=Find.documents.through) + +class FindInsideContainer(models.Model): + CREATE_SQL = """ + CREATE VIEW find_inside_container AS + SELECT fb.id AS find_id, fb.container_id AS container_id + FROM archaeological_finds_find fb + WHERE fb.downstream_treatment_id IS NULL AND fb.container_id IS NOT NULL + UNION + SELECT f.id AS find_id, r.container_parent_id AS container_id + FROM archaeological_finds_find f + INNER JOIN container_tree r + ON r.container_id = f.container_id + WHERE f.downstream_treatment_id IS NULL; + + -- deactivate deletion + CREATE RULE find_inside_container_del AS + ON DELETE TO find_inside_container + DO INSTEAD DELETE FROM archaeological_finds_find where id=NULL; + """ + DELETE_SQL = """ + DROP VIEW IF EXISTS find_inside_container; + """ + TABLE_COLS = ["find__" + t for t in Find.TABLE_COLS] + COL_LABELS = { + "find__" + k: Find.COL_LABELS[k] for k in Find.COL_LABELS.keys() + } + EXTRA_REQUEST_KEYS = { + "find__" + k: + "find__" + Find.EXTRA_REQUEST_KEYS[k] + for k in Find.EXTRA_REQUEST_KEYS.keys() + } + + find = models.OneToOneField( + Find, verbose_name=_("Find"), related_name="inside_container", + primary_key=True) + container = models.ForeignKey("archaeological_warehouse.Container", + verbose_name=_("Container"), + related_name="container_content") + + class Meta: + managed = False + db_table = 'find_inside_container' + + for attr in Find.HISTORICAL_M2M: m2m_changed.connect(m2m_historization_changed, sender=getattr(Find, attr).through) diff --git a/archaeological_finds/templates/ishtar/sheet_find.html b/archaeological_finds/templates/ishtar/sheet_find.html index a696bcf9f..2b2e868be 100644 --- a/archaeological_finds/templates/ishtar/sheet_find.html +++ b/archaeological_finds/templates/ishtar/sheet_find.html @@ -253,20 +253,10 @@ {% if display_warehouse_treatments %} <div class="tab-pane fade" id="{{window_id}}-warehouse" role="tabpanel" aria-labelledby="{{window_id}}-warehouse-tab"> - {% if item.container_ref %} - <h3>{% trans "Warehouse - reference container"%}</h3> + {% if item.container %} + <h3>{% trans "Warehouse - container" %}</h3> <div class='row'> - {% field_flex_detail "Container" item.container_ref %} - {% field_flex "Container ID" item.container_ref.cached_location %} - {% field_flex_detail "Responsible warehouse" item.container_ref.responsible %} - {% field_flex_detail "Location (warehouse)" item.container_ref.location %} - {% field_flex "Precise localisation" item.container_ref.cached_division %} - </div> - {% endif %} - {% if item.container and item.container_ref.pk != item.container.pk %} - <h3>{% trans "Warehouse - current container"%}</h3> - <div class='row'> - {% field_flex_detail "Container" item.container %} + {% field_flex_detail "Container" item.container "large" %} {% field_flex "Container ID" item.container.cached_location %} {% field_flex_detail "Responsible warehouse" item.container.responsible %} {% field_flex_detail "Location (warehouse)" item.container.location %} diff --git a/archaeological_finds/tests.py b/archaeological_finds/tests.py index 55006c4ca..80202f442 100644 --- a/archaeological_finds/tests.py +++ b/archaeological_finds/tests.py @@ -18,8 +18,11 @@ # See the file COPYING for details. from copy import deepcopy +import csv import datetime import json +import os +import tempfile from rest_framework.test import APITestCase from rest_framework.authtoken.models import Token @@ -35,6 +38,7 @@ from django.test import tag from django.test.client import Client from ishtar_common.models import ImporterType, IshtarUser, ImporterColumn,\ FormaterType, ImportTarget, IshtarSiteProfile, ProfileType +from django.utils.text import slugify from django.utils.translation import pgettext_lazy, gettext_lazy as _ from ishtar_common.models import Person, get_current_profile, UserProfile, \ @@ -598,15 +602,22 @@ class TreatmentWizardCreationTest(WizardTest, FindInit, TestCase): # treat) -class ImportFindTest(ImportTest, TestCase): - fixtures = FIND_TOWNS_FIXTURES +class ImportFindTest(ImportTest, FindInit, TestCase): + fixtures = FIND_TOWNS_FIXTURES + [ + settings.ROOT_PATH + + '../archaeological_finds/tests/import_loca_test.json', + ] + + def setUp(self): + super(ImportFindTest, self).setUp() + self.tmpdir = tempfile.TemporaryDirectory() def test_mcc_import_finds(self): self.init_context_record() old_nb = models.BaseFind.objects.count() old_nb_find = models.Find.objects.count() - MCC = ImporterType.objects.get(name=u"MCC - Mobilier") + MCC = ImporterType.objects.get(name="MCC - Mobilier") col = ImporterColumn.objects.create(col_number=25, importer_type_id=MCC.pk) @@ -671,6 +682,153 @@ class ImportFindTest(ImportTest, TestCase): f.index, expected_index )) + def test_import_locations(self): + self.create_finds() + self.create_finds() + self.create_finds() + self.create_finds() + external_ids = [] + for idx, f in enumerate(self.finds): + f.label = "Find {}".format(idx) + f.save() + external_ids.append(f.external_id) + + wt, __ = WarehouseType.objects.get_or_create(label="WT", txt_idx="WT") + + warehouse, __ = Warehouse.objects.get_or_create( + external_id="warehouse-test", + defaults={"name": "Warehouse test", + "warehouse_type": wt} + ) + + container_types = [] + levels = ["Building", "Area", "Shelf", "Box"] + for level in levels: + container_type, __ = ContainerType.objects.get_or_create( + label=level, txt_idx=slugify(level) + ) + container_types.append(container_type) + + for idx in range(len(levels[:-1])): + WarehouseDivisionLink.objects.get_or_create( + warehouse=warehouse, + container_type=container_types[idx], + order=(idx + 1) * 10 + ) + + old_nb = models.BaseFind.objects.count() + old_nb_find = models.Find.objects.count() + old_nb_container = Container.objects.count() + + importer_type = ImporterType.objects.get(slug="importeur-test") + + imp_filename = os.path.join(self.tmpdir.name, "imp_locations.csv") + + with open(imp_filename, "w") as impfile: + w = csv.writer(impfile) + w.writerow(['External ID', "Warehouse", "Ref.", + "Container type", "Localisation 1", "Localisation 2", + "Localisation 3"]) + for idx, ext_id in enumerate(external_ids): + if idx < 2: + w.writerow([ext_id, "warehouse-test", + "Réf. {}".format((idx + 1) * 10), + container_types[-1].name, + "A", "42", idx + 1]) + else: + w.writerow([ext_id, "warehouse-test", + "Réf. {}".format((idx + 1) * 10), + container_types[-1].name, + "A", 43]) + + imp_file = open(imp_filename, "rb") + file_dict = {'imported_file': SimpleUploadedFile(imp_file.name, + imp_file.read())} + post_dict = {'importer_type': importer_type.pk, 'skip_lines': 1, + "encoding": 'utf-8', "name": 'init_find_import', + "csv_sep": ","} + form = forms_common.NewImportForm(data=post_dict, files=file_dict, + user=self.user) + form.is_valid() + self.assertTrue(form.is_valid()) + impt = form.save(self.ishtar_user) + impt.initialize() + + impt.importation() + # no new finds has now been imported + current_nb = models.BaseFind.objects.count() + self.assertEqual(current_nb, old_nb) + current_nb = models.Find.objects.count() + self.assertEqual(current_nb, old_nb_find) + + current_nb = Container.objects.count() + self.assertEqual(current_nb, old_nb_container + 4 + 2 + 2 + 1) + containers = list(Container.objects.all()) + for container in containers: + self.assertEqual(container.location, warehouse) + q = Container.objects.filter(container_type=container_types[0]) + self.assertEqual(q.count(), 1) + building = q.all()[0] + self.assertIsNone(building.parent) + + q = Container.objects.filter(container_type=container_types[1]) + self.assertEqual(q.count(), 2) + areas = list(q.all()) + area = q.all()[0] + self.assertEqual(area.parent, building) + + q = Container.objects.filter(container_type=container_types[2]) + self.assertEqual(q.count(), 2) + shelves = list(q.all()) + for shelf in shelves: + self.assertEqual(shelf.parent, area) + + q = Container.objects.filter(container_type=container_types[3]) + self.assertEqual(q.count(), 4) + boxes = list(q.all().order_by("id")) + previous_shelf = None + for box in boxes[:2]: + if not previous_shelf: + previous_shelf = box.parent + else: + # on a different shelf + self.assertNotEqual(previous_shelf, box.parent) + self.assertIn(box.parent_id, [s.pk for s in shelves]) + previous_area = None + for box in boxes[2:]: + if not previous_area: + previous_area = box.parent + else: + self.assertEqual(previous_area, box.parent) # on the same area + self.assertIn(box.parent_id, [s.pk for s in areas]) + + importer_type = ImporterType.objects.get(slug="importeur-test") + cols = list(ImporterColumn.objects.filter( + importer_type=importer_type, + col_number__gte=5).values_list("id", flat=True)) + for target in ImportTarget.objects.filter(column_id__in=cols): + target.target = target.target.replace("set_localisation", + "set_static_localisation") + target.save() + + # delete area 43 and all boxes + Container.objects.filter(reference="43").delete() + Container.objects.filter(container_type=container_types[-1]).delete() + + # reimport + impt.initialize() + impt.importation() + # check errors + self.assertEqual(len(impt.errors), 2) + for error in impt.errors: + self.assertEqual( + error['error'], + "The division Area 43 do not exist for the location Warehouse " + "test.") + + def tearDown(self): + self.tmpdir.cleanup() + class FindTest(FindInit, TestCase): fixtures = FIND_FIXTURES diff --git a/archaeological_finds/tests/import_loca_test.json b/archaeological_finds/tests/import_loca_test.json new file mode 100644 index 000000000..1f0a9054a --- /dev/null +++ b/archaeological_finds/tests/import_loca_test.json @@ -0,0 +1,305 @@ +[ +{ + "model": "ishtar_common.importertype", + "fields": { + "name": "Importeur test", + "slug": "importeur-test", + "description": null, + "associated_models": [ + "archaeological_finds.models_finds.Find" + ], + "is_template": false, + "unicity_keys": "external_id", + "available": true, + "users": [], + "created_models": [ + [ + "archaeological_warehouse.models.Container" + ], + [ + "archaeological_finds.models_finds.Find" + ], + [ + "archaeological_finds.models.BaseFind" + ] + ] + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "ID externe", + "importer_type": [ + "importeur-test" + ], + "col_number": 1, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": true, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "Localisation 1", + "importer_type": [ + "importeur-test" + ], + "col_number": 5, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "Localisation 2", + "importer_type": [ + "importeur-test" + ], + "col_number": 6, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "Localisation 3", + "importer_type": [ + "importeur-test" + ], + "col_number": 7, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "D\u00e9p\u00f4t", + "importer_type": [ + "importeur-test" + ], + "col_number": 2, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "Ref. container", + "importer_type": [ + "importeur-test" + ], + "col_number": 3, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importercolumn", + "fields": { + "label": "Type de contenant", + "importer_type": [ + "importeur-test" + ], + "col_number": 4, + "description": "", + "regexp_pre_filter": null, + "value_format": null, + "required": false, + "export_field_name": null + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 1 + ], + "target": "external_id", + "formater_type": [ + "UnicodeFormater", + "100", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 5 + ], + "target": "set_localisation_1", + "formater_type": [ + "UnicodeFormater", + "10", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 6 + ], + "target": "set_localisation_2", + "formater_type": [ + "UnicodeFormater", + "10", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 7 + ], + "target": "set_localisation_3", + "formater_type": [ + "UnicodeFormater", + "10", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 2 + ], + "target": "container__location__external_id", + "formater_type": [ + "UnicodeFormater", + "100", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 3 + ], + "target": "container__reference", + "formater_type": [ + "UnicodeFormater", + "30", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importtarget", + "fields": { + "column": [ + "importeur-test", + 4 + ], + "target": "container__container_type", + "formater_type": [ + "TypeFormater", + "archaeological_warehouse.models.ContainerType", + "" + ], + "force_new": false, + "concat": false, + "concat_str": null, + "comment": "" + } +}, +{ + "model": "ishtar_common.importerduplicatefield", + "fields": { + "column": [ + "importeur-test", + 2 + ], + "field_name": "container__responsible__external_id", + "force_new": false, + "concat": false, + "concat_str": null + } +}, +{ + "model": "ishtar_common.importerduplicatefield", + "fields": { + "column": [ + "importeur-test", + 3 + ], + "field_name": "container__external_id", + "force_new": false, + "concat": false, + "concat_str": "-" + } +}, +{ + "model": "ishtar_common.importerduplicatefield", + "fields": { + "column": [ + "importeur-test", + 2 + ], + "field_name": "container__external_id", + "force_new": false, + "concat": false, + "concat_str": "-" + } +} +] diff --git a/archaeological_finds/urls.py b/archaeological_finds/urls.py index 588777552..bab3fc6b4 100644 --- a/archaeological_finds/urls.py +++ b/archaeological_finds/urls.py @@ -270,6 +270,11 @@ urlpatterns = [ name='get-own-find-for-treatment', kwargs={'force_own': True}), url(r'get-find-for-treatment/(?P<type>.+)?$', views.get_find_for_treatment, name='get-find-for-treatment'), + url(r'get-find-inside-container/own/(?P<type>.+)?$', + views.get_find_inside_container, + name='get-find-inside-container', kwargs={'force_own': True}), + url(r'get-find-inside-container/(?P<type>.+)?$', + views.get_find_inside_container, name='get-find-inside-container'), url(r'get-find-full/own/(?P<type>.+)?$', views.get_find, name='get-own-find-full', kwargs={'full': True, 'force_own': True}), url(r'get-find-full/(?P<type>.+)?$', views.get_find, diff --git a/archaeological_finds/views.py b/archaeological_finds/views.py index 1fc2b3722..cf82cceb8 100644 --- a/archaeological_finds/views.py +++ b/archaeological_finds/views.py @@ -66,6 +66,11 @@ def get_table_cols_for_ope(): return settings.TABLE_COLS[tb_key] +def get_table_cols_for_container(): + table_col = get_table_cols_for_ope() + return ["find__" + tc for tc in table_col] + + get_find_for_ope = get_item(models.Find, 'get_find', 'find', own_table_cols=get_table_cols_for_ope()) @@ -73,6 +78,12 @@ get_find_for_treatment = get_item( models.Find, 'get_find', 'find', own_table_cols=get_table_cols_for_ope(), base_request={}) +get_find_inside_container = get_item( + models.FindInsideContainer, 'get_find_inside_container', + 'find', + extra_request_keys=models.FindInsideContainer.EXTRA_REQUEST_KEYS, + own_table_cols=get_table_cols_for_container()) + autocomplete_find = get_autocomplete_item(model=models.Find) diff --git a/archaeological_warehouse/forms.py b/archaeological_warehouse/forms.py index e03918965..a7390a890 100644 --- a/archaeological_warehouse/forms.py +++ b/archaeological_warehouse/forms.py @@ -372,15 +372,14 @@ class ContainerSelect(DocumentItemSelect): label=_(u"Full text search"), widget=widgets.SearchWidget( 'archaeological-warehouse', 'container' )) - location_name = get_warehouse_field( - label=_(u"Current location (warehouse)")) - responsible_name = get_warehouse_field(label=_(u"Responsible warehouse")) - container_type = forms.ChoiceField(label=_(u"Container type"), choices=[]) - reference = forms.CharField(label=_(u"Ref.")) - old_reference = forms.CharField(label=_(u"Old reference")) + location_name = get_warehouse_field(label=_("Warehouse")) + container_type = forms.ChoiceField(label=_("Container type"), choices=[]) + reference = forms.CharField(label=_("Ref.")) + old_reference = forms.CharField(label=_("Old reference")) comment = forms.CharField(label=_(u"Comment")) - no_finds = forms.NullBooleanField(label=_(u"No associated finds")) - empty = forms.NullBooleanField(label=_(u"Currently empty")) + contain_containers = forms.NullBooleanField(label=_("Contain containers")) + empty = forms.NullBooleanField(label=_("Currently empty")) + is_stationary = forms.NullBooleanField(label=_("Is stationary")) archaeological_sites = forms.IntegerField( label=_("Archaeological site (attached to the operation)"), diff --git a/archaeological_warehouse/management/commands/migrate_to_new_container_management.py b/archaeological_warehouse/management/commands/migrate_to_new_container_management.py index b5885cbf0..d8f701793 100644 --- a/archaeological_warehouse/management/commands/migrate_to_new_container_management.py +++ b/archaeological_warehouse/management/commands/migrate_to_new_container_management.py @@ -30,18 +30,23 @@ class Command(BaseCommand): def handle(self, *args, **options): to_update = models.Container.objects.filter( - division__pk__isnull=False) + division__pk__isnull=False, parent__isnull=True) container_types = {} created_nb = 0 for div_type in models.WarehouseDivision.objects.all(): container_type, c = models.ContainerType.objects.get_or_create( txt_idx=slugify(div_type.label), - defaults={"label": div_type.label}) + defaults={"label": div_type.label, + "stationary": True}) if c: created_nb += 1 sys.stdout.write("-> {} created\n".format( div_type.label)) container_types[div_type.pk] = container_type + for wdl in models.WarehouseDivisionLink.objects.all(): + wdl.container_type = container_types[wdl.division_id] + wdl.save() + if created_nb: sys.stdout.write("* {} container types created\n".format( created_nb)) @@ -50,10 +55,10 @@ class Command(BaseCommand): potential_duplicate = {} data = [("id", "warehouse", "reference", "old cached division", "new cached division")] - for idx, container in enumerate(models.Container.objects.filter( - division__pk__isnull=False).all()): + for idx, container in enumerate(to_update.values("id").all()): sys.stdout.write("* Updating: {}/{}\r".format(idx + 1, to_be_done)) sys.stdout.flush() + container = models.Container.objects.get(pk=container["id"]) if container.responsible_id not in potential_duplicate: potential_duplicate[container.responsible_id] = {} parent = None diff --git a/archaeological_warehouse/migrations/0103_auto_20200403_1638.py b/archaeological_warehouse/migrations/0103_auto_20200403_1638.py new file mode 100644 index 000000000..d7bd5320b --- /dev/null +++ b/archaeological_warehouse/migrations/0103_auto_20200403_1638.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-04-03 16:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_warehouse', '0102_auto_20200324_1142'), + ] + + operations = [ + migrations.AddField( + model_name='containertype', + name='stationary', + field=models.BooleanField(default=False, help_text='Container that usually will not be moved. Ex: building, room.', verbose_name='Stationary'), + ), + migrations.AddField( + model_name='warehousedivisionlink', + name='container_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='archaeological_warehouse.ContainerType'), + ), + migrations.AlterField( + model_name='container', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='archaeological_warehouse.Container', verbose_name='Parent container'), + ), + migrations.AlterField( + model_name='container', + name='responsible', + field=models.ForeignKey(blank=True, help_text='Deprecated - do not use', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_containers', to='archaeological_warehouse.Warehouse', verbose_name='Responsible warehouse'), + ), + migrations.AlterField( + model_name='warehousedivisionlink', + name='division', + field=models.ForeignKey(blank=True, help_text='Deprecated - do not use', null=True, on_delete=django.db.models.deletion.CASCADE, to='archaeological_warehouse.WarehouseDivision'), + ), + ] diff --git a/archaeological_warehouse/migrations/0104_auto_container_views.py b/archaeological_warehouse/migrations/0104_auto_container_views.py new file mode 100644 index 000000000..4f707c8d7 --- /dev/null +++ b/archaeological_warehouse/migrations/0104_auto_container_views.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-04-03 16:47 +from __future__ import unicode_literals + +from django.db import migrations + +import archaeological_warehouse.models +import archaeological_finds.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('archaeological_warehouse', '0103_auto_20200403_1638'), + ] + + operations = [ + migrations.RunSQL( + archaeological_warehouse.models.ContainerTree.DELETE_SQL), + migrations.RunSQL( + archaeological_warehouse.models.ContainerTree.CREATE_SQL), + migrations.RunSQL( + archaeological_finds.models.FindInsideContainer.DELETE_SQL), + migrations.RunSQL( + archaeological_finds.models.FindInsideContainer.CREATE_SQL), + ] diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py index 839ca1a6e..c4b26e3d1 100644 --- a/archaeological_warehouse/models.py +++ b/archaeological_warehouse/models.py @@ -393,12 +393,37 @@ class WarehouseDivisionLinkManager(models.Manager): division__txt_idx=division) +class ContainerType(GeneralType): + stationary = models.BooleanField( + _("Stationary"), default=False, + help_text=_("Container that usually will not be moved. Ex: building, " + "room.")) + length = models.IntegerField(_(u"Length (mm)"), blank=True, null=True) + width = models.IntegerField(_(u"Width (mm)"), blank=True, null=True) + height = models.IntegerField(_(u"Height (mm)"), blank=True, null=True) + volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True) + reference = models.CharField(_(u"Ref."), max_length=300, blank=True, + null=True) + + class Meta: + verbose_name = _(u"Container type") + verbose_name_plural = _(u"Container types") + ordering = ('label',) + + +post_save.connect(post_save_cache, sender=ContainerType) +post_delete.connect(post_save_cache, sender=ContainerType) + + class WarehouseDivisionLink(models.Model): RELATED_SET_NAME = "divisions" RELATED_ATTRS = ["order"] RELATIVE_MODELS = {Warehouse: 'warehouse'} warehouse = models.ForeignKey(Warehouse, related_name='divisions') - division = models.ForeignKey(WarehouseDivision) + container_type = models.ForeignKey(ContainerType, blank=True, null=True) + division = models.ForeignKey( + WarehouseDivision, help_text=_("Deprecated - do not use"), + blank=True, null=True) order = models.IntegerField(_("Order"), default=10) objects = WarehouseDivisionLinkManager() @@ -413,22 +438,43 @@ class WarehouseDivisionLink(models.Model): return self.warehouse.uuid, self.division.txt_idx -class ContainerType(GeneralType): - length = models.IntegerField(_(u"Length (mm)"), blank=True, null=True) - width = models.IntegerField(_(u"Width (mm)"), blank=True, null=True) - height = models.IntegerField(_(u"Height (mm)"), blank=True, null=True) - volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True) - reference = models.CharField(_(u"Ref."), max_length=300, blank=True, - null=True) - - class Meta: - verbose_name = _(u"Container type") - verbose_name_plural = _(u"Container types") - ordering = ('label',) - - -post_save.connect(post_save_cache, sender=ContainerType) -post_delete.connect(post_save_cache, sender=ContainerType) +class ContainerTree: + CREATE_SQL = """ + CREATE VIEW containers_tree AS + WITH RECURSIVE rel_tree AS ( + SELECT c.id AS container_id, c.parent_id AS container_parent_id, + 1 AS level + FROM archaeological_warehouse_container c + WHERE c.parent_id is NOT NULL + UNION ALL + SELECT p.container_id AS container_id, + c.parent_id as container_parent_id, + p.level + 1 + FROM archaeological_warehouse_container c, rel_tree p + WHERE c.id = p.container_parent_id + AND c.parent_id is NOT NULL + AND p.level < 10 -- prevent recursive... + ) + SELECT DISTINCT container_id, container_parent_id, level + FROM rel_tree; + + CREATE VIEW container_tree AS + SELECT DISTINCT y.container_id, y.container_parent_id + FROM (SELECT * FROM containers_tree) y + ORDER BY y.container_id, y.container_parent_id; + + -- deactivate deletion + CREATE RULE container_tree_del AS + ON DELETE TO container_tree + DO INSTEAD DELETE FROM archaeological_warehouse_container where id=NULL; + CREATE RULE containers_tree_del AS + ON DELETE TO container_tree + DO INSTEAD DELETE FROM archaeological_warehouse_container where id=NULL; + """ + DELETE_SQL = """ + DROP VIEW IF EXISTS container_tree; + DROP VIEW IF EXISTS containers_tree; + """ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, @@ -439,7 +485,8 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, SHOW_URL = 'show-container' DELETE_URL = 'delete-container' NEW_QUERY_ENGINE = True - TABLE_COLS = ['reference', 'container_type__label', 'cached_location', + TABLE_COLS = ['container_type__label', 'reference', + 'location__name', 'cached_division', 'old_reference'] IMAGE_PREFIX = 'containers/' BASE_SEARCH_VECTORS = [ @@ -457,6 +504,7 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, # search parameters EXTRA_REQUEST_KEYS = { 'location': 'location__pk', + 'location__name': "location__name", 'location_id': 'location__pk', 'responsible_id': 'responsible__pk', 'container_type': 'container_type__pk', @@ -470,9 +518,10 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, 'container_type__label': 'container_type__label', } COL_LABELS = { - 'cached_location': _(u"Location - index"), - 'cached_division': _(u"Precise localisation"), - 'container_type__label': _(u"Type") + 'cached_location': _("Location - index"), + 'cached_division': _("Precise localisation"), + 'container_type__label': _("Type"), + "location__name": _("Warehouse") } GEO_LABEL = "cached_label" CACHED_LABELS = ['cached_division', 'cached_label', 'cached_location', ] @@ -483,10 +532,6 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, pgettext_lazy("key for text search", "location"), 'location__name__iexact' ), - 'responsible_name': SearchAltName( - pgettext_lazy("key for text search", "responsible-warehouse"), - 'responsible__name__iexact' - ), 'container_type': SearchAltName( pgettext_lazy("key for text search", "type"), 'container_type__label__iexact' @@ -595,20 +640,25 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, pgettext_lazy("key for text search", "find-description"), 'finds__description__iexact'), 'empty': SearchAltName( - pgettext_lazy("key for text search", u"empty"), + pgettext_lazy("key for text search", "empty"), 'finds' ), - 'no_finds': SearchAltName( - pgettext_lazy("key for text search", u"no-associated-finds"), - 'finds_ref' + 'contain_containers': SearchAltName( + pgettext_lazy("key for text search", "contain-containers"), + 'children__isnull' ), - + 'is_stationary': SearchAltName( + pgettext_lazy("key for text search", "is-stationary"), + 'container_type__stationary' + ) } REVERSED_BOOL_FIELDS = [ + 'children__isnull', 'documents__image__isnull', 'documents__associated_file__isnull', 'documents__associated_url__isnull', ] + BOOL_FIELDS = ['container_type__stationary'] REVERSED_MANY_COUNTED_FIELDS = ['finds', 'finds_ref'] ALT_NAMES.update(LightHistorizedItem.ALT_NAMES) @@ -638,11 +688,13 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, # fields uuid = models.UUIDField(default=uuid.uuid4) location = models.ForeignKey( - Warehouse, verbose_name=_(u"Location (warehouse)"), + Warehouse, verbose_name=_("Location (warehouse)"), related_name='containers') responsible = models.ForeignKey( - Warehouse, verbose_name=_(u"Responsible warehouse"), - related_name='owned_containers') + Warehouse, verbose_name=_("Responsible warehouse"), + related_name='owned_containers', blank=True, null=True, + help_text=_("Deprecated - do not use") + ) container_type = models.ForeignKey(ContainerType, verbose_name=_("Container type")) reference = models.TextField(_(u"Container ref.")) @@ -655,19 +707,21 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, null=True, blank=True, db_index=True) parent = models.ForeignKey("Container", verbose_name=_("Parent container"), on_delete=models.SET_NULL, - blank=True, null=True) - index = models.IntegerField(u"Container ID", default=0) - old_reference = models.TextField(_(u"Old reference"), blank=True, null=True) - external_id = models.TextField(_(u"External ID"), blank=True, null=True) + related_name="children", blank=True, null=True) + index = models.IntegerField(_("Container ID"), default=0) + old_reference = models.TextField(_("Old reference"), blank=True, null=True) + external_id = models.TextField(_("External ID"), blank=True, null=True) auto_external_id = models.BooleanField( - _(u"External ID is set automatically"), default=False) + _("External ID is set automatically"), default=False) documents = models.ManyToManyField( - Document, related_name='containers', verbose_name=_(u"Documents"), + Document, related_name='containers', verbose_name=_("Documents"), blank=True) main_image = models.ForeignKey( Document, related_name='main_image_containers', on_delete=models.SET_NULL, - verbose_name=_(u"Main image"), blank=True, null=True) + verbose_name=_("Main image"), blank=True, null=True) + + DISABLE_POLYGONS = False class Meta: verbose_name = _(u"Container") @@ -688,13 +742,15 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, def __str__(self): return self.cached_label or "" + @property + def short_label(self): + return "{} {}".format(self.container_type.label, self.reference) + def natural_key(self): return (self.uuid, ) def _generate_cached_label(self): - items = [self.reference, self.precise_location] - cached_label = u" | ".join(items) - return cached_label + return self.precise_location def _generate_cached_location(self): items = [self.location.name, str(self.index)] @@ -715,10 +771,11 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, "{} {}".format(loca.container_type.name, loca.reference) for loca in reversed(parents) ] + locas.append("{} {}".format(self.container_type.name, self.reference)) return " | ".join(locas) def _get_base_image_path(self): - return self.responsible._get_base_image_path() + u"/" + self.external_id + return self.location._get_base_image_path() + "/" + self.external_id def merge(self, item, keep_old=False): locas = [ @@ -754,21 +811,21 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, @property def associated_filename(self): filename = datetime.date.today().strftime('%Y-%m-%d') - filename += u'-' + self.reference - filename += u"-" + self.location.name - filename += u"-" + str(self.index) + filename += "-" + self.reference + filename += "-" + self.location.name + filename += "-" + str(self.index) if self.cached_division is None: self.skip_history_when_saving = True self.save() if self.cached_division: - filename += u"-" + self.cached_division + filename += "-" + self.cached_division return slugify(filename) @property def precise_location(self): location = self.location.name if self.cached_division: - location += u" " + self.cached_division + location += " " + self.cached_division return location def get_localisations(self): @@ -777,11 +834,12 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, :return: tuple of strings with localisations """ - return tuple(( - loca.reference - for loca in ContainerLocalisation.objects.filter( - container=self).order_by('division__order') - )) + localisations = [] + parent = self.parent + while parent: + localisations.append(parent) + parent = parent.parent + return reversed(localisations) def get_localisation(self, place): locas = self.get_localisations() @@ -825,30 +883,83 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, def localisation_9(self): return self.get_localisation(8) - def set_localisation(self, place, value): + def set_localisation(self, place, value, static=False, return_errors=False): """ Set the reference for the localisation number "place" (starting from 0) :param place: the number of the localisation :param value: the reference to be set + :param static: do not create new containers + :param return_errors: return error message :return: the container location object or None if the place does not - exist + exist - return also error message if return_errors set to True """ + value = value.strip() + if not value or value == "-": + if return_errors: + return None, _("No value") + return q = WarehouseDivisionLink.objects.filter( warehouse=self.location).order_by('order') + + current_container_type = None + error_msg = str( + _("The division number {} have not been set for the warehouse {}.") + ).format(place + 1, self.location) + previous_container_types = [] for idx, division_link in enumerate(q.all()): if idx == place: + current_container_type = division_link.container_type break + previous_container_types.append(division_link.container_type_id) else: + if return_errors: + return None, error_msg return - dct = {'container': self, 'division': division_link} - if not value: - if ContainerLocalisation.objects.filter(**dct).count(): - c = ContainerLocalisation.objects.filter(**dct).all()[0] - c.delete() + if not current_container_type: + # no division link set at this place + if return_errors: + return None, error_msg return - dct['defaults'] = {'reference': value} - obj, created = ContainerLocalisation.objects.update_or_create(**dct) - return obj + + # modify existing + current_localisations = self.get_localisations() + current_localisation, current_parent = None, None + for loca in current_localisations: + if loca.container_type == current_container_type: + if loca.reference == value: + current_localisation = loca + break + elif loca.container_type_id in previous_container_types: + current_parent = loca + + if not current_localisation: + dct = { + "reference": value, + "container_type": current_container_type, + "parent": current_parent, + "location": self.location + } + q = Container.objects.filter(**dct) + if q.count(): + current_localisation = q.all()[0] + else: + if static: + if return_errors: + error_msg = str( + _("The division {} {} do not exist for the " + "location {}.") + ).format(current_container_type, value, self.location) + return None, error_msg + return + current_localisation = Container.objects.create(**dct) + self.parent = current_localisation + self.save() + if return_errors: + return current_localisation, None + return current_localisation + + def set_static_localisation(self, place, value): + return self.set_localisation(place, value, static=True) @post_importer_action def set_localisation_1(self, context, value): @@ -895,6 +1006,51 @@ class Container(DocumentItem, LightHistorizedItem, QRCodeItem, GeoItem, return self.set_localisation(8, value) set_localisation_9.post_save = True + @post_importer_action + def set_static_localisation_1(self, context, value): + return self.set_static_localisation(0, value) + set_static_localisation_1.post_save = True + + @post_importer_action + def set_static_localisation_2(self, context, value): + return self.set_static_localisation(1, value) + set_static_localisation_2.post_save = True + + @post_importer_action + def set_static_localisation_3(self, context, value): + return self.set_static_localisation(2, value) + set_static_localisation_3.post_save = True + + @post_importer_action + def set_static_localisation_4(self, context, value): + return self.set_static_localisation(3, value) + set_static_localisation_4.post_save = True + + @post_importer_action + def set_static_localisation_5(self, context, value): + return self.set_static_localisation(4, value) + set_static_localisation_5.post_save = True + + @post_importer_action + def set_static_localisation_6(self, context, value): + return self.set_static_localisation(5, value) + set_static_localisation_6.post_save = True + + @post_importer_action + def set_static_localisation_7(self, context, value): + return self.set_static_localisation(6, value) + set_static_localisation_7.post_save = True + + @post_importer_action + def set_static_localisation_8(self, context, value): + return self.set_static_localisation(7, value) + set_static_localisation_8.post_save = True + + @post_importer_action + def set_static_localisation_9(self, context, value): + return self.set_static_localisation(8, value) + set_static_localisation_9.post_save = True + def get_extra_actions(self, request): """ extra actions for the sheet template diff --git a/archaeological_warehouse/templates/ishtar/sheet_container.html b/archaeological_warehouse/templates/ishtar/sheet_container.html index ffc533513..1bb004071 100644 --- a/archaeological_warehouse/templates/ishtar/sheet_container.html +++ b/archaeological_warehouse/templates/ishtar/sheet_container.html @@ -1,7 +1,10 @@ {% extends "ishtar/sheet.html" %} -{% load i18n window_header window_field window_tables %} +{% load i18n window_header window_field window_tables link_to_window %} -{% block head_title %}<strong>{% trans "Container" %}</strong> - {{ item.reference|default:"" }} ({{ item.container_type|default:"" }}){% endblock %} +{% block head_title %}<strong>{% trans "Container" %}</strong> - +{{ item.container_type|default:"" }} {{ item.reference|default:"" }} - +{{ item.location.name|default:"" }} +{% endblock %} {% block toolbar %} {% window_nav item window_id 'show-container' 'container_modify' '' '' previous next 1 %} @@ -19,9 +22,8 @@ {% else %} <div class="float-left col-6 col-md-3 text-center"> {% endif %} - <p class="window-refs">{{ item.reference|default:"" }}</p> - <p class="window-refs">{{ item.container_type|default:"" }}</p> - <p class="window-refs">{{ item.responsible.name }} - {{ item.index }}</p> + <p class="window-refs">{{ item.container_type|default:"" }} {{ item.reference|default:"" }}</p> + <p class="window-refs">{{ item.location.name }} - {{ item.index }}</p> <p class="window-refs">{{ item.old_reference|default:"" }}</p> {% include "ishtar/blocks/sheet_external_id.html" %} </div> @@ -30,36 +32,57 @@ {% else %} <div class="float-left row col-6 col-md-8"> {% endif %} - {% field_flex_detail "Responsible warehouse" item.responsible %} - {% field_flex_detail "Location (warehouse)" item.location %} + {% field_flex_detail "Warehouse" item.location %} + {% if item.parent %} + <dl class="col-12 col-md-6 flex-wrap"> + <dt>{% trans "Location" %}</dt> + <dd> + <nav aria-label="breadcrumb"> + <ol class="breadcrumb"> + {% for loca in item.get_localisations %} + <li class="breadcrumb-item"> + {{loca.short_label}} {{loca|simple_link_to_window}} + </li> + {% endfor %} + </ol> + </nav> + </dd> + </dl> + {% endif %} {% include "ishtar/blocks/sheet_creation_section.html" %} - {% field_flex "Location" item.precise_location %} {% field_flex_full "Comment" item.comment "<pre>" "</pre>" %} {% include "ishtar/blocks/sheet_json.html" %} </div> </div> +{% if item.container_content.count or item.children.count %} +<h4>{% trans "Content" %}</h4> + +{% if item.children.count %} +{% trans "Containers" as container_lbl %} +{% dynamic_table_document container_lbl 'containers' 'parent' item.pk 'TABLE_COLS' output 'large' %} +{% endif %} + +{% if item.container_content.count %} +{% trans "Finds" as finds_lbl %} +{% dynamic_table_document finds_lbl 'finds_inside_container' 'container' item.pk 'TABLE_COLS' output 'large' %} +{% endif %} + +{% endif %} + +{% if PROFILE.locate_warehouses %} {% if item.point_2d or item.multi_polygon %} <h3>{% trans "Localisation"%}</h3> <div class='row'> + {{item.point_2d}} {% with geo_item=item %} - {% if PROFILE.locate_warehouses %}{% include "ishtar/blocks/sheet_simple_map.html"%}{% endif %} + {% include "ishtar/blocks/sheet_simple_map.html" %} <div class="col-12 col-lg-6 flex-wrap"> - {% if PROFILE.locate_warehouses %}{% include "ishtar/blocks/sheet_coordinates.html"%}{% endif %} + {% include "ishtar/blocks/sheet_coordinates.html" %} </div> {% endwith %} </div> {% endif %} - - -{% if item.finds.count %} -<h4>{% trans "Content" %}</h4> -{% dynamic_table_document finds 'finds' 'container' item.pk 'TABLE_COLS' output 'large' %} -{% endif %} - -{% if item.finds_ref.count %} -<h4>{% trans "Reference content" %}</h4> -{% dynamic_table_document finds 'finds' 'container_ref' item.pk 'TABLE_COLS' output 'large' %} {% endif %} {% endblock %} diff --git a/ishtar_common/fixtures/initial_importtypes-fr.json b/ishtar_common/fixtures/initial_importtypes-fr.json index 5ae394040..374b5ccbc 100644 --- a/ishtar_common/fixtures/initial_importtypes-fr.json +++ b/ishtar_common/fixtures/initial_importtypes-fr.json @@ -133,6 +133,13 @@ } }, { + "model": "ishtar_common.importermodel", + "fields": { + "name": "Contenant", + "klass": "archaeological_warehouse.models.Container" + } +}, +{ "model": "ishtar_common.documenttemplate", "fields": { "name": "Document de r\u00e9f\u00e9rence", diff --git a/ishtar_common/templatetags/link_to_window.py b/ishtar_common/templatetags/link_to_window.py index 6f0db9dc1..77d743ea0 100644 --- a/ishtar_common/templatetags/link_to_window.py +++ b/ishtar_common/templatetags/link_to_window.py @@ -33,9 +33,9 @@ def link_to_window(item, context): elif "request" in context: # RequestContext request = context['request'] else: - return u"" + return "" if not item.can_view(request): - return u"" + return "" return simple_link_to_window(item) diff --git a/ishtar_common/templatetags/window_field.py b/ishtar_common/templatetags/window_field.py index 7aaf62397..cd4122b58 100644 --- a/ishtar_common/templatetags/window_field.py +++ b/ishtar_common/templatetags/window_field.py @@ -161,9 +161,10 @@ def field_li_detail(context, caption, item): @register.inclusion_tag('ishtar/blocks/window_field_flex_detail.html', takes_context=True) -def field_flex_detail(context, caption, item, small=False): - size = None - if small: +def field_flex_detail(context, caption, item, size=None): + if size == "large": + size = "full" + elif size: size = 2 return field_detail(context, caption, item, size=size) diff --git a/ishtar_common/templatetags/window_tables.py b/ishtar_common/templatetags/window_tables.py index ab60f7eeb..078afca62 100644 --- a/ishtar_common/templatetags/window_tables.py +++ b/ishtar_common/templatetags/window_tables.py @@ -19,7 +19,8 @@ from archaeological_operations.models import Operation, ArchaeologicalSite, \ from archaeological_context_records.models import ContextRecord, \ RecordRelationView, RecordRelations as CRRecordRelations from archaeological_finds.models import Find, FindUpstreamTreatments, \ - FindDownstreamTreatments, FindTreatments, TreatmentFile, Treatment + FindDownstreamTreatments, FindTreatments, TreatmentFile, Treatment, \ + FindInsideContainer from archaeological_warehouse.models import Container, Warehouse register = template.Library() @@ -55,6 +56,8 @@ ASSOCIATED_MODELS['finds_upstreamtreatments'] = ( FindUpstreamTreatments, 'get-upstreamtreatment', '') ASSOCIATED_MODELS['finds_downstreamtreatments'] = ( FindDownstreamTreatments, 'get-downstreamtreatment', '') +ASSOCIATED_MODELS['finds_inside_container'] = ( + FindInsideContainer, 'get-find-inside-container', '') ASSOCIATED_MODELS['treatments'] = ( FindTreatments, 'get-treatment', '') ASSOCIATED_MODELS['base_treatments'] = ( diff --git a/ishtar_common/utils.py b/ishtar_common/utils.py index 2eacce013..f18544ea9 100644 --- a/ishtar_common/utils.py +++ b/ishtar_common/utils.py @@ -740,7 +740,8 @@ def _post_save_geo(sender, **kwargs): current_source = str(instance.__class__._meta.verbose_name) modified = False - if hasattr(instance, 'multi_polygon'): + if hasattr(instance, 'multi_polygon') and not getattr( + instance, "DISABLE_POLYGONS", False): if instance.multi_polygon_source_item and \ instance.multi_polygon_source_item != current_source: # refetch instance.multi_polygon = None |