summaryrefslogtreecommitdiff
path: root/archaeological_finds/models_treatments.py
diff options
context:
space:
mode:
Diffstat (limited to 'archaeological_finds/models_treatments.py')
-rw-r--r--archaeological_finds/models_treatments.py441
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