diff options
Diffstat (limited to 'archaeological_warehouse/models.py')
-rw-r--r-- | archaeological_warehouse/models.py | 284 |
1 files changed, 220 insertions, 64 deletions
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 |