diff options
Diffstat (limited to 'archaeological_warehouse/models.py')
-rw-r--r-- | archaeological_warehouse/models.py | 409 |
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) |