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.py409
1 files changed, 238 insertions, 171 deletions
diff --git a/archaeological_warehouse/models.py b/archaeological_warehouse/models.py
index a8a38c34d..ffc491ddc 100644
--- a/archaeological_warehouse/models.py
+++ b/archaeological_warehouse/models.py
@@ -45,6 +45,201 @@ from ishtar_common.utils import cached_label_changed, \
cached_label_and_geo_changed
+class DivisionContainer(DashboardFormItem):
+ DIVISION_TEMPLATE = """<a class="display_details"
+ href="#" onclick="load_window('/show-container/{id}/')">
+ <i class="fa fa-info-circle" aria-hidden="true"></i>
+ </a> {container} {ref}"""
+ BASE_QUERY_LOCATION = "location"
+
+ @property
+ def pk(self):
+ # of course implemented by models.Model
+ raise NotImplemented
+
+ def get_max_division_number(self):
+ raise NotImplemented
+
+ @property
+ def start_division_number(self):
+ raise NotImplemented
+
+ @property
+ def division_labels(self):
+ if not self.get_max_division_number() + 1:
+ return []
+ start = self.start_division_number
+ return [
+ "{} {}".format(_("Division"), idx + start + 1)
+ for idx in range(self.get_max_division_number() + 1)
+ ]
+
+ @property
+ def number_divisions(self):
+ q = {
+ self.BASE_QUERY_LOCATION + "__id": self.pk,
+ "container_type__stationary": True
+ }
+ return Container.objects.filter(**q).count()
+
+ @property
+ def number_containers(self):
+ q = {
+ self.BASE_QUERY_LOCATION + "__id": self.pk,
+ "container_type__stationary": False
+ }
+ return Container.objects.filter(**q).count()
+
+ @property
+ def number_of_finds_hosted(self):
+ Find = apps.get_model("archaeological_finds", "Find")
+ q = {
+ "container__{}__id".format(self.BASE_QUERY_LOCATION): self.pk,
+ }
+ return Find.objects.filter(**q).count()
+
+ @property
+ def number_of_finds(self):
+ Find = apps.get_model("archaeological_finds", "Find")
+ q = {
+ "container_ref__{}__id".format(self.BASE_QUERY_LOCATION): self.pk,
+ }
+ return Find.objects.filter(**q).count()
+
+ @property
+ def number_of_containers(self):
+ return Container.objects.filter(
+ **{self.BASE_QUERY_LOCATION: self}).count()
+
+ def _number_of_finds_by_place(self):
+ Find = apps.get_model("archaeological_finds", "Find")
+ return self._number_of_items_by_place(
+ Find, division_key='inside_container__container__')
+
+ @property
+ def number_of_finds_by_place(self, update=False):
+ return self._get_or_set_stats('_number_of_finds_by_place', update)
+
+ def _number_of_containers_by_place(self):
+ return self._number_of_items_by_place(
+ ContainerTree, 'container_parent__', 'container__children')
+
+ @property
+ def number_of_containers_by_place(self, update=False):
+ return self._get_or_set_stats('_number_of_containers_by_place', update)
+
+ def _get_divisions(self, current_path, remaining_division, depth=0):
+ if not remaining_division:
+ return [current_path]
+ remaining_division.pop(0)
+
+ query_location = self.BASE_QUERY_LOCATION
+ for __ in range(depth):
+ query_location = "parent__" + query_location
+ base_q = Container.objects.filter(**{query_location: self})
+
+ q = base_q
+ if self.BASE_QUERY_LOCATION == 'location':
+ exclude = "parent_"
+ for idx in range(depth):
+ exclude += "_parent_"
+ q = base_q.filter(**{exclude + "id": None})
+ elif not depth and not current_path:
+ q = base_q.filter(parent_id=self.pk)
+
+ for idx, p in enumerate(reversed(current_path)):
+ parent_id, __ = p
+ key = "parent__" * (idx + 1) + "id"
+ q = q.filter(**{key: parent_id})
+ res = []
+ old_ref, ct = None, None
+ if not q.count():
+ return [current_path]
+ q = q.values(
+ 'id', 'reference', 'container_type__label', 'container_type_id'
+ ).order_by('container_type__label', 'reference')
+
+ for ref in q.all():
+ if ref['reference'] == old_ref and \
+ ref["container_type__label"] == ct:
+ continue
+ old_ref = ref['reference']
+ ct = ref["container_type__label"]
+ cpath = current_path[:]
+ lbl = self.DIVISION_TEMPLATE.format(
+ id=ref["id"], container=ref["container_type__label"],
+ ref=ref['reference'])
+ cpath.append((ref["id"], lbl))
+ query = {
+ "containers__parent__reference": ref['reference'],
+ "containers__parent__container_type_id": ref[
+ "container_type_id"],
+ "containers__" + self.BASE_QUERY_LOCATION: self
+ }
+ remaining_division = list(
+ ContainerType.objects.filter(
+ **query).distinct())
+ 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 container type and the full reference.
+ """
+ q = {
+ "containers__" + self.BASE_QUERY_LOCATION: self
+ }
+ if self.BASE_QUERY_LOCATION == 'location':
+ q["containers__parent"] = None
+ top_divisions = list(
+ ContainerType.objects.filter(**q).distinct())
+ divisions = self._get_divisions([], top_divisions)
+ return divisions
+
+ def _number_of_items_by_place(self, model, division_key, count_filter=None):
+ res = {}
+ paths = self.available_division_tuples[:]
+ for path in paths:
+ cpath = []
+ for container_id, lbl in path:
+ cpath.append((container_id, lbl))
+ if tuple(cpath) in res:
+ continue
+ q = model.objects
+ for idx, p in enumerate(reversed(cpath)):
+ container_id, __ = p
+ div_key = division_key + "parent__" * idx
+ attrs = {
+ div_key + "id": container_id
+ }
+ q = q.filter(**attrs)
+ if count_filter:
+ q = q.filter(**{count_filter: None})
+ res[tuple(cpath)] = q.distinct().count()
+ res = [(k, res[k]) for k in res]
+ final_res, current_res, depth = [], [], 1
+
+ len_divisions = self.get_max_division_number() + 1
+ for path, nb in sorted(res, key=lambda x: (len(x[0]), x[0])):
+ if len(path) > len_divisions:
+ continue
+ if depth != len(path):
+ final_res.append(current_res[:])
+ current_res = []
+ depth = len(path)
+ if path[-1] == '-':
+ continue
+ path = [k[1] for k in path]
+ path = path + ['' for __ in range(len_divisions - len(path))]
+ current_res.append((path, nb))
+ final_res.append(current_res[:])
+ return final_res
+
+
class WarehouseType(GeneralType):
class Meta:
verbose_name = _("Warehouse type")
@@ -56,8 +251,8 @@ post_save.connect(post_save_cache, sender=WarehouseType)
post_delete.connect(post_save_cache, sender=WarehouseType)
-class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
- OwnPerms, MainItem):
+class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem,
+ OwnPerms, MainItem, DivisionContainer):
SLUG = 'warehouse'
APP = "archaeological-warehouse"
MODEL = "warehouse"
@@ -156,28 +351,6 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
return self.name
@property
- def number_divisions(self):
- return Container.objects.filter(
- location=self.pk, container_type__stationary=True).count()
-
- @property
- def number_containers(self):
- return Container.objects.filter(
- location=self.pk, container_type__stationary=False).count()
-
- @property
- def number_finds(self):
- Find = apps.get_model("archaeological_finds", "Find")
- return Find.objects.filter(
- container__location_id=self.pk).count()
-
- @property
- def number_owned_finds(self):
- Find = apps.get_model("archaeological_finds", "Find")
- return Find.objects.filter(
- container_ref__location_id=self.pk).count()
-
- @property
def short_label(self):
return self.name
@@ -215,14 +388,12 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
setattr(self, k, None)
self.save()
+ def get_max_division_number(self):
+ return self.max_division_number
+
@property
- def division_labels(self):
- if not self.max_division_number:
- return []
- return [
- "{} {}".format(_("Division"), idx + 1)
- for idx in range(self.max_division_number)
- ]
+ def start_division_number(self):
+ return 0
@property
def default_location_types(self):
@@ -246,145 +417,6 @@ class Warehouse(Address, DocumentItem, GeoItem, QRCodeItem, DashboardFormItem,
def _get_query_owns_dicts(cls, ishtaruser):
return [{'person_in_charge__ishtaruser': ishtaruser}]
- @property
- def number_of_finds(self):
- from archaeological_finds.models import Find
- return Find.objects.filter(container_ref__location=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]
- remaining_division.pop(0)
-
- query_location = "location"
- for __ in range(depth):
- query_location = "parent__" + query_location
- base_q = Container.objects.filter(**{query_location: self})
-
- q = base_q.annotate(nb_children=Count("children__id")).exclude(
- nb_children=0)
-
- if not current_path:
- q = q.annotate(nb_parent=Count("parent__id")).filter(
- nb_parent=0)
-
- for idx, p in enumerate(reversed(current_path)):
- parent_id, __ = p
- key = "parent__" * (idx + 1) + "id"
- q = q.filter(**{key: parent_id})
- res = []
- old_ref, ct = None, None
- if not q.count():
- return [current_path]
- q = q.values(
- 'id', 'reference', 'container_type__label', 'container_type_id'
- ).order_by('container_type__label', 'reference')
-
- DIVISION_TEMPLATE = """<a class="display_details"
- href="#" onclick="load_window('/show-container/{id}/')">
- <i class="fa fa-info-circle" aria-hidden="true"></i></a>
- {container} {ref}"""
- for ref in q.all():
- if ref['reference'] == old_ref and \
- ref["container_type__label"] == ct:
- continue
- old_ref = ref['reference']
- ct = ref["container_type__label"]
- cpath = current_path[:]
- lbl = DIVISION_TEMPLATE.format(
- id=ref["id"], container=ref["container_type__label"],
- ref=ref['reference'])
- cpath.append((ref["id"], lbl))
- remaining_division = list(
- ContainerType.objects.filter(
- containers__parent__reference=ref['reference'],
- containers__parent__container_type_id=ref[
- "container_type_id"],
- containers__location=self).distinct())
- 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 container type and the full reference.
- """
- top_divisions = list(
- ContainerType.objects.filter(
- containers__parent=None,
- containers__location=self,
- stationary=True).distinct())
- divisions = self._get_divisions([], top_divisions)
- return divisions
-
- def _number_of_items_by_place(self, model, division_key, count_filter=None):
- res = {}
- paths = self.available_division_tuples[:]
- for path in paths:
- cpath = []
- for container_id, lbl in path:
- cpath.append((container_id, lbl))
- if tuple(cpath) in res:
- continue
- q = model.objects
- for idx, p in enumerate(reversed(cpath)):
- container_id, __ = p
- div_key = division_key + "parent__" * idx
- attrs = {
- div_key + "id": container_id
- }
- q = q.filter(**attrs)
- if count_filter:
- q = q.filter(**{count_filter: None})
- res[tuple(cpath)] = q.distinct().count()
- res = [(k, res[k]) for k in res]
- final_res, current_res, depth = [], [], 1
-
- len_divisions = self.max_division_number
- for path, nb in sorted(res, key=lambda x: (len(x[0]), x[0])):
- if len(path) > len_divisions:
- continue
- if depth != len(path):
- final_res.append(current_res[:])
- current_res = []
- depth = len(path)
- if path[-1] == '-':
- continue
- path = [k[1] for k in path]
- path = path + ['' for __ in range(len_divisions - len(path))]
- current_res.append((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='inside_container__container__')
-
- @property
- def number_of_finds_by_place(self, update=False):
- return self._get_or_set_stats('_number_of_finds_by_place', update)
-
- def _number_of_containers_by_place(self):
- return self._number_of_items_by_place(
- ContainerTree, 'container_parent__', 'container__children')
-
- @property
- def number_of_containers_by_place(self, update=False):
- return self._get_or_set_stats('_number_of_containers_by_place', update)
-
def merge(self, item, keep_old=False):
# do not recreate missing divisions
available_divisions = [
@@ -569,7 +601,7 @@ class ContainerTree(models.Model):
class Container(DocumentItem, Merge, LightHistorizedItem, QRCodeItem, GeoItem,
- OwnPerms, MainItem):
+ OwnPerms, MainItem, DivisionContainer):
SLUG = 'container'
APP = "archaeological-warehouse"
MODEL = "container"
@@ -617,6 +649,10 @@ class Container(DocumentItem, Merge, LightHistorizedItem, QRCodeItem, GeoItem,
'finds__base_finds__context_record',
'finds': 'finds',
'container_type__label': 'container_type__label',
+
+ # dynamic tables
+ 'container_tree_child__container_parent__id':
+ 'container_tree_child__container_parent__id'
}
COL_LABELS = {
'cached_location': _("Location - index"),
@@ -788,6 +824,8 @@ class Container(DocumentItem, Merge, LightHistorizedItem, QRCodeItem, GeoItem,
)
QUICK_ACTIONS = [QA_EDIT, QA_LOCK]
+ BASE_QUERY_LOCATION = "container_tree_child__container_parent"
+
objects = UUIDModelManager()
# fields
@@ -853,6 +891,35 @@ class Container(DocumentItem, Merge, LightHistorizedItem, QRCodeItem, GeoItem,
return self.cached_label or ""
@property
+ def start_division_number(self):
+ depth = 1
+ parent = self.parent
+ parents = []
+ while parent and parent.pk not in parents:
+ parents.append(parent.pk)
+ depth += 1
+ parent = parent.parent
+ return depth
+
+ def get_max_division_number(self):
+ return self.location.get_max_division_number() \
+ - self.start_division_number
+
+ @property
+ def number_of_finds_hosted(self):
+ count = super(Container, self).number_of_finds_hosted
+ Find = apps.get_model("archaeological_finds", "Find")
+ count += Find.objects.filter(container_id=self.pk).count()
+ return count
+
+ @property
+ def number_of_finds(self):
+ count = super(Container, self).number_of_finds
+ Find = apps.get_model("archaeological_finds", "Find")
+ count += Find.objects.filter(container_ref_id=self.pk).count()
+ return count
+
+ @property
def name(self):
return "{} - {}".format(self.container_type.name, self.reference)