summaryrefslogtreecommitdiff
path: root/archaeological_warehouse/models.py
diff options
context:
space:
mode:
Diffstat (limited to 'archaeological_warehouse/models.py')
-rw-r--r--archaeological_warehouse/models.py284
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