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) | 
