diff options
Diffstat (limited to 'archaeological_finds/models_treatments.py')
-rw-r--r-- | archaeological_finds/models_treatments.py | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/archaeological_finds/models_treatments.py b/archaeological_finds/models_treatments.py new file mode 100644 index 000000000..e6237efef --- /dev/null +++ b/archaeological_finds/models_treatments.py @@ -0,0 +1,441 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2016 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet> + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# See the file COPYING for details. + +import datetime + +from django.conf import settings +from django.contrib.gis.db import models +from django.db.models import Max +from django.db.models.signals import post_save, post_delete, pre_delete +from django.template.defaultfilters import slugify +from django.utils.translation import ugettext_lazy as _, ugettext + + +from ishtar_common.utils import cached_label_changed +from ishtar_common.models import GeneralType, ImageModel, BaseHistorizedItem, \ + OwnPerms, HistoricalRecords, Person, Organization, Source, \ + ValueGetter, post_save_cache +from archaeological_warehouse.models import Warehouse, Container +from archaeological_finds.models_finds import Find, FindBasket +from archaeological_operations.models import ClosedItem + + +class TreatmentType(GeneralType): + virtual = models.BooleanField(_(u"Virtual")) + upstream_is_many = models.BooleanField( + _(u"Upstream is many"), default=False, + help_text=_( + u"Check this if for this treatment from many finds you'll get " + u"one.")) + downstream_is_many = models.BooleanField( + _(u"Downstream is many"), default=False, + help_text=_( + u"Check this if for this treatment from one find you'll get " + u"many.")) + + class Meta: + verbose_name = _(u"Treatment type") + verbose_name_plural = _(u"Treatment types") + ordering = ('label',) +post_save.connect(post_save_cache, sender=TreatmentType) +post_delete.connect(post_save_cache, sender=TreatmentType) + + +class Treatment(BaseHistorizedItem, ImageModel, OwnPerms): + SHOW_URL = 'show-treatment' + TABLE_COLS = ('year', 'index', 'treatment_types_lbl', 'label', 'person', + 'start_date', 'downstream_cached_label', + 'upstream_cached_label') + REVERSED_BOOL_FIELDS = ['image__isnull'] + EXTRA_REQUEST_KEYS = { + "label": 'label__icontains', + "other_reference": 'other_reference__icontains', + "treatment_types": "treatment_types__pk", + "downstream_cached_label": "downstream__cached_label", + "upstream_cached_label": "upstream__cached_label", + 'image': 'image__isnull', + } + TABLE_COLS_LBL = { + "downstream_cached_label": _(u"Downstream find"), + "upstream_cached_label": _(u"Upstream find"), + } + IMAGE_PREFIX = 'treatment' + label = models.CharField(_(u"Label"), blank=True, null=True, + max_length=200) + other_reference = models.CharField(_(u"Other ref."), blank=True, null=True, + max_length=200) + year = models.IntegerField(_(u"Year"), + default=lambda: datetime.datetime.now().year) + index = models.IntegerField(_(u"Index"), default=1) + file = models.ForeignKey('TreatmentFile', related_name='treatments', + blank=True, null=True) + treatment_types = models.ManyToManyField( + TreatmentType, verbose_name=_(u"Treatment type")) + location = models.ForeignKey( + Warehouse, verbose_name=_(u"Location"), blank=True, null=True, + help_text=_( + u"Location where the treatment is done. Target warehouse for " + u"a move.")) + person = models.ForeignKey( + Person, verbose_name=_(u"Responsible"), blank=True, null=True, + on_delete=models.SET_NULL, related_name='treatments') + organization = models.ForeignKey( + Organization, verbose_name=_(u"Organization"), blank=True, null=True, + on_delete=models.SET_NULL, related_name='treatments') + external_id = models.CharField(_(u"External ID"), blank=True, null=True, + max_length=200) + comment = models.TextField(_(u"Comment"), blank=True, null=True) + description = models.TextField(_(u"Description"), blank=True, null=True) + goal = models.TextField(_(u"Goal"), blank=True, null=True) + start_date = models.DateField(_(u"Start date"), blank=True, null=True) + end_date = models.DateField(_(u"End date"), blank=True, null=True) + container = models.ForeignKey(Container, verbose_name=_(u"Container"), + blank=True, null=True) + target_is_basket = models.BooleanField(_(u"Target a basket"), default=False) + history = HistoricalRecords() + + class Meta: + verbose_name = _(u"Treatment") + verbose_name_plural = _(u"Treatments") + unique_together = ('year', 'index') + permissions = ( + ("view_treatment", ugettext(u"Can view all Treatments")), + ("view_own_treatment", ugettext(u"Can view own Treatment")), + ("add_own_treatment", ugettext(u"Can add own Treatment")), + ("change_own_treatment", ugettext(u"Can change own Treatment")), + ("delete_own_treatment", ugettext(u"Can delete own Treatment")), + ) + + def __unicode__(self): + lbl = unicode(self.treatment_types_lbl()) + if self.person: + lbl += u" %s %s" % (_(u"by"), unicode(self.person)) + return lbl + + def treatment_types_lbl(self): + """ + Treatment types label + :return: string + """ + return u" ; ".join([unicode(t) for t in self.treatment_types.all()]) + + def pre_save(self): + # is not new + if self.pk is not None: + return + self.index = 1 + q = Treatment.objects.filter(year=self.year) + if q.count(): + self.index = q.all().aggregate(Max('index'))['index__max'] + 1 + + def save(self, *args, **kwargs): + items, user, extra_args_for_new = [], None, [] + if "items" in kwargs: + items = kwargs.pop('items') + if "user" in kwargs: + user = kwargs.pop('user') + if "extra_args_for_new" in kwargs: + extra_args_for_new = kwargs.pop('extra_args_for_new') + self.pre_save() + super(Treatment, self).save(*args, **kwargs) + updated = [] + if hasattr(items, "items"): + items = items.items.all() + for item in items: + new = item.duplicate(user) + item.downstream_treatment = self + item.save() + new.upstream_treatment = self + for k in extra_args_for_new: + setattr(new, k, extra_args_for_new[k]) + new.save() + updated.append(new.pk) + # update baskets + for basket in \ + FindBasket.objects.filter(items__pk=item.pk).all(): + basket.items.remove(item) + basket.items.add(new) + # manage containers + for find in Find.objects.filter(upstream_treatment=self).all(): + if find.container != self.container: + find.container = self.container + if find.pk in updated: + # don't record twice history + find.skip_history_when_saving = True + find.save() + + @property + def associated_filename(self): + return "-".join([str(slugify(getattr(self, attr))) + for attr in ('year', 'index', 'label')]) + + +def pre_delete_treatment(sender, **kwargs): + treatment = kwargs.get('instance') + for find in Find.objects.filter(upstream_treatment=treatment).all(): + if find.downstream_treatment: + # a new treatment have be done since the deleted treatment + raise NotImplemented() + find.delete() + for find in Find.objects.filter(downstream_treatment=treatment).all(): + find.downstream_treatment = None + find.save() + +pre_delete.connect(pre_delete_treatment, sender=Treatment) + + +class AbsFindTreatments(models.Model): + find = models.ForeignKey(Find, verbose_name=_(u"Find"), + related_name='%(class)s_related') + treatment = models.ForeignKey(Treatment, verbose_name=_(u"Treatment"), + primary_key=True) + # primary_key is set to prevent django to ask for an id column + # treatment is not a primary key + treatment_nb = models.IntegerField(_(u"Order")) + TABLE_COLS = ['treatment__treatment_type', + 'treatment__start_date', 'treatment__end_date', + 'treatment__location', 'treatment__container', + 'treatment__person', 'treatment_nb'] + EXTRA_FULL_FIELDS_LABELS = { + 'treatment__treatment_type': _(u"Treatment type"), + 'treatment__start_date': _(u"Start date"), + 'treatment__end_date': _(u"End date"), + 'treatment__location': _(u"Location"), + 'treatment__container': _(u"Container"), + 'treatment__person': _(u"Doer"), + 'treatment__upstream': _(u"Related finds"), + 'treatment__downstream': _(u"Related finds"), + } + + class Meta: + abstract = True + + def __unicode__(self): + return u"{} - {} [{}]".format( + self.find, self.treatment, self.treatment_nb) + + +class FindUpstreamTreatments(AbsFindTreatments): + """ + CREATE VIEW find_uptreatments_tree AS + WITH RECURSIVE rel_tree AS ( + SELECT id AS find_id, upstream_treatment_id, downstream_treatment_id, + 1 AS level, + array[upstream_treatment_id] AS path_info + FROM archaeological_finds_find + WHERE upstream_treatment_id is null + UNION ALL + SELECT c.id AS find_id, c.upstream_treatment_id, + c.downstream_treatment_id, + p.level + 1, p.path_info||c.upstream_treatment_id + FROM archaeological_finds_find c + JOIN rel_tree p + ON c.upstream_treatment_id = p.downstream_treatment_id + ) + SELECT DISTINCT find_id, path_info, level + FROM rel_tree ORDER BY find_id; + + CREATE VIEW find_uptreatments AS + SELECT DISTINCT find_id, + path_info[nb] AS treatment_id, level - nb + 1 AS treatment_nb + FROM (SELECT *, generate_subscripts(path_info, 1) AS nb + FROM find_uptreatments_tree) y + WHERE path_info[nb] is not NULL + ORDER BY find_id, treatment_id; + + -- deactivate deletion + CREATE RULE find_uptreatments_del AS ON DELETE TO find_uptreatments + DO INSTEAD DELETE FROM archaeological_finds_find where id=NULL; + """ + TABLE_COLS = ['treatment__treatment_type', + 'treatment__upstream', + 'treatment__start_date', 'treatment__end_date', + 'treatment__location', 'treatment__container', + 'treatment__person', 'treatment_nb'] + + # search parameters + EXTRA_REQUEST_KEYS = {'find_id': 'find_id'} + + class Meta: + managed = False + db_table = 'find_uptreatments' + unique_together = ('find', 'treatment') + ordering = ('find', '-treatment_nb') + + +class FindDownstreamTreatments(AbsFindTreatments): + """ + CREATE VIEW find_downtreatments_tree AS + WITH RECURSIVE rel_tree AS ( + SELECT id AS find_id, downstream_treatment_id, upstream_treatment_id, + 1 AS level, + array[downstream_treatment_id] AS path_info + FROM archaeological_finds_find + WHERE downstream_treatment_id is null + UNION ALL + SELECT c.id AS find_id, c.downstream_treatment_id, + c.upstream_treatment_id, + p.level + 1, p.path_info||c.downstream_treatment_id + FROM archaeological_finds_find c + JOIN rel_tree p + ON c.downstream_treatment_id = p.upstream_treatment_id + ) + SELECT DISTINCT find_id, path_info, level + FROM rel_tree ORDER BY find_id; + + CREATE VIEW find_downtreatments AS + SELECT DISTINCT find_id, + path_info[nb] AS treatment_id, level - nb + 1 AS treatment_nb + FROM (SELECT *, generate_subscripts(path_info, 1) AS nb + FROM find_downtreatments_tree) y + WHERE path_info[nb] is not NULL + ORDER BY find_id, treatment_id; + + -- deactivate deletion + CREATE RULE find_downtreatments_del AS ON DELETE TO find_downtreatments + DO INSTEAD DELETE FROM archaeological_finds_find where id=NULL; + """ + TABLE_COLS = ['treatment__treatment_type', + 'treatment__downstream', + 'treatment__start_date', 'treatment__end_date', + 'treatment__location', 'treatment__container', + 'treatment__person', 'treatment_nb'] + + # search parameters + EXTRA_REQUEST_KEYS = {'find_id': 'find_id'} + + class Meta: + managed = False + db_table = 'find_downtreatments' + unique_together = ('find', 'treatment') + ordering = ('find', '-treatment_nb') + + +class FindTreatments(AbsFindTreatments): + """ + CREATE VIEW find_treatments AS + SELECT find_id, treatment_id, treatment_nb, TRUE as upstream + FROM find_uptreatments + UNION + SELECT find_id, treatment_id, treatment_nb, FALSE as upstream + FROM find_downtreatments + ORDER BY find_id, treatment_id, upstream; + + -- deactivate deletion + CREATE RULE find_treatments_del AS ON DELETE TO find_treatments + DO INSTEAD DELETE FROM archaeological_finds_find where id=NULL; + """ + upstream = models.BooleanField(_(u"Is upstream")) + + class Meta: + managed = False + db_table = 'find_treatments' + unique_together = ('find', 'treatment') + ordering = ('find', 'upstream', '-treatment_nb') + + +class TreatmentFileType(GeneralType): + class Meta: + verbose_name = _(u"Treatment file type") + verbose_name_plural = _(u"Treatment file types") + ordering = ('label',) + + +class TreatmentFile(ClosedItem, BaseHistorizedItem, OwnPerms, ValueGetter): + SLUG = 'treatmentfile' + SHOW_URL = 'show-treatmentfile' + TABLE_COLS = ['type', 'year', 'index', 'internal_reference', 'name'] + + # fields + year = models.IntegerField(_(u"Year"), + default=lambda: datetime.datetime.now().year) + index = models.IntegerField(_(u"Index"), default=1) + internal_reference = models.CharField(_(u"Internal reference"), blank=True, + null=True, max_length=200) + external_id = models.CharField(_(u"External ID"), blank=True, null=True, + max_length=200) + name = models.TextField(_(u"Name"), blank=True, null=True) + type = models.ForeignKey(TreatmentFileType, verbose_name=_(u"Treatment " + u"file type")) + in_charge = models.ForeignKey( + Person, related_name='treatmentfile_responsability', + verbose_name=_(u"Person in charge"), on_delete=models.SET_NULL, + blank=True, null=True) + end_date = models.DateField(_(u"Closing date"), null=True, blank=True) + creation_date = models.DateField( + _(u"Creation date"), default=datetime.date.today, blank=True, + null=True) + reception_date = models.DateField(_(u'Reception date'), blank=True, + null=True) + comment = models.TextField(_(u"Comment"), null=True, blank=True) + cached_label = models.TextField(_(u"Cached name"), null=True, blank=True) + history = HistoricalRecords() + + class Meta: + verbose_name = _(u"Treatment file") + verbose_name_plural = _(u"Treatment files") + unique_together = ('year', 'index') + permissions = ( + ("view_filetreatement", ugettext(u"Can view all Treatment files")), + ("view_own_filetreatement", + ugettext(u"Can view own Treatment file")), + ("add_own_filetreatement", + ugettext(u"Can add own Treatment file")), + ("change_own_filetreatement", + ugettext(u"Can change own Treatment file")), + ("delete_own_filetreatement", + ugettext(u"Can delete own Treatment file")), + ) + ordering = ('cached_label',) + + def __unicode__(self): + return self.cached_label + + def _generate_cached_label(self): + items = [unicode(getattr(self, k)) + for k in ['year', 'index', 'internal_reference', 'name'] if + getattr(self, k)] + return settings.JOINT.join(items) + + def pre_save(self): + # is not new + if self.pk is not None: + return + self.index = 1 + q = TreatmentFile.objects.filter(year=self.year) + if q.count(): + self.index = q.all().aggregate(Max('index'))['index__max'] + 1 + + def save(self, *args, **kwargs): + self.pre_save() + super(TreatmentFile, self).save(*args, **kwargs) + +post_save.connect(cached_label_changed, sender=TreatmentFile) + + +class TreatmentSource(Source): + class Meta: + verbose_name = _(u"Treatment documentation") + verbose_name_plural = _(u"Treament documentations") + treatment = models.ForeignKey( + Treatment, verbose_name=_(u"Treatment"), related_name="source") + + @property + def owner(self): + return self.treatment |