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  | 
