#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2012-2015 Étienne Loks # 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 . # See the file COPYING for details. from django.conf import settings from django.contrib.gis.db import models from django.core.urlresolvers import reverse from django.db.models import Max from django.utils.translation import ugettext_lazy as _, ugettext from ishtar_common.models import GeneralType, ImageModel, BaseHistorizedItem, \ ShortMenuItem, LightHistorizedItem, HistoricalRecords, OwnPerms, Source, \ Person from archaeological_operations.models import AdministrativeAct from archaeological_context_records.models import ContextRecord, Dating WAREHOUSE_AVAILABLE = 'archaeological_warehouse' in settings.INSTALLED_APPS if WAREHOUSE_AVAILABLE: from archaeological_warehouse.models import Warehouse, Container FILES_AVAILABLE = 'archaeological_files' in settings.INSTALLED_APPS class MaterialType(GeneralType): code = models.CharField(_(u"Code"), max_length=10, blank=True, null=True) recommendation = models.TextField(_(u"Recommendation"), blank=True, null=True) parent = models.ForeignKey("MaterialType", blank=True, null=True, verbose_name=_(u"Parent material")) class Meta: verbose_name = _(u"Material type") verbose_name_plural = _(u"Material types") ordering = ('label',) class ConservatoryState(GeneralType): parent = models.ForeignKey("ConservatoryState", blank=True, null=True, verbose_name=_(u"Parent conservatory state")) class Meta: verbose_name = _(u"Conservatory state") verbose_name_plural = _(u"Conservatory states") ordering = ('label',) class PreservationType(GeneralType): class Meta: verbose_name = _(u"Preservation type") verbose_name_plural = _(u"Preservation types") ordering = ('label',) class BaseFind(BaseHistorizedItem, OwnPerms): label = models.CharField(_(u"ID"), max_length=60) external_id = models.CharField(_(u"External ID"), blank=True, null=True, max_length=120) description = models.TextField(_(u"Description"), blank=True, null=True) comment = models.TextField(_(u"Comment"), blank=True, null=True) topographic_localisation = models.CharField(_(u"Topographic localisation"), blank=True, null=True, max_length=120) special_interest = models.CharField(_(u"Special interest"), blank=True, null=True, max_length=120) context_record = models.ForeignKey(ContextRecord, related_name='base_finds', verbose_name=_(u"Context Record")) discovery_date = models.DateField(_(u"Discovery date"), blank=True, null=True) is_isolated = models.NullBooleanField(_(u"Is isolated?"), blank=True, null=True) index = models.IntegerField(u"Index", default=0) material_index = models.IntegerField(u"Material index", default=0) history = HistoricalRecords() class Meta: verbose_name = _(u"Base find") verbose_name_plural = _(u"Base finds") permissions = ( ("view_basefind", ugettext(u"Can view all Base find")), ("view_own_basefind", ugettext(u"Can view own Base find")), ("add_own_basefind", ugettext(u"Can add own Base find")), ("change_own_basefind", ugettext(u"Can change own Base find")), ("delete_own_basefind", ugettext(u"Can delete own Base find")), ) def __unicode__(self): return self.label def get_last_find(self): #TODO: manage virtuals - property(last_find) ? finds = self.find.filter().order_by("-order").all() return finds and finds[0] def complete_id(self): # OPE|MAT.CODE|UE|FIND_index if not self.context_record.operation: return find = self.get_last_find() ope = self.context_record.operation c_id = [unicode(ope.code_patriarche) if ope.code_patriarche else (unicode(ope.year) + "-" + unicode(ope.operation_code))] print c_id c_id.append(find and find.material_type.code or '') c_id.append(self.context_record.label) c_id.append(unicode(self.index)) return settings.JOINT.join(c_id) def short_id(self): # OPE|FIND_index if not self.context_record.operation: return find = self.get_last_find() ope = self.context_record.operation c_id = [unicode(ope.code_patriarche) or \ (unicode(ope.year) + "-" + unicode(ope.operation_code))] c_id.append(unicode(self.index)) return settings.JOINT.join(c_id) def full_label(self): return self._real_label() or self._temp_label() or u"" def material_type_label(self): find = self.get_last_find() finds = [find and find.material_type.code or ''] ope = self.context_record.operation finds += [unicode(ope.code_patriarche) or \ (unicode(ope.year) + "-" + unicode(ope.operation_code))] finds += [self.context_record.label, unicode(self.material_index)] return settings.JOINT.join(finds) def _real_label(self): if not self.context_record.parcel \ or not self.context_record.operation \ or not self.context_record.operation.code_patriarche: return find = self.get_last_find() lbl = find.label or self.label return settings.JOINT.join([unicode(it) for it in ( self.context_record.operation.code_patriarche, self.context_record.label, lbl) if it]) def _temp_label(self): if not self.context_record.parcel: return find = self.get_last_find() lbl = find.label or self.label return settings.JOINT.join([unicode(it) for it in ( self.context_record.parcel.year, self.index, self.context_record.label, lbl) if it]) @property def name(self): return self.label WEIGHT_UNIT = (('g', _(u"g")), ('kg', _(u"kg")),) class Find(BaseHistorizedItem, ImageModel, OwnPerms, ShortMenuItem): TABLE_COLS = ['label', 'material_type', 'dating.period', 'base_finds.context_record.parcel.town', 'base_finds.context_record.operation.year', 'base_finds.context_record.operation.operation_code', 'container.reference', 'container.location', 'base_finds.is_isolated'] if settings.COUNTRY == 'fr': TABLE_COLS.insert(6, 'base_finds.context_record.operation.code_patriarche') base_finds = models.ManyToManyField(BaseFind, verbose_name=_(u"Base find"), related_name='find') external_id = models.CharField(_(u"External ID"), blank=True, null=True, max_length=120) order = models.IntegerField(_(u"Order"), default=1) label = models.CharField(_(u"ID"), max_length=60) description = models.TextField(_(u"Description"), blank=True, null=True) material_type = models.ForeignKey(MaterialType, verbose_name = _(u"Material type")) conservatory_state = models.ForeignKey(ConservatoryState, verbose_name = _(u"Conservatory state"), blank=True, null=True) preservation_to_consider = models.ForeignKey(PreservationType, verbose_name = _(u"Type of preservation to consider"), blank=True, null=True) volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True) weight = models.FloatField(_(u"Weight (g)"), blank=True, null=True) weight_unit = models.CharField(_(u"Weight unit"), max_length=1, blank=True, null=True, choices=WEIGHT_UNIT) find_number = models.IntegerField(_("Find number"), blank=True, null=True) upstream_treatment = models.ForeignKey("Treatment", blank=True, null=True, related_name='downstream_treatment', verbose_name=_("Upstream treatment")) downstream_treatment = models.ForeignKey("Treatment", blank=True, null=True, related_name='upstream_treatment', verbose_name=_("Downstream treatment")) dating = models.ForeignKey(Dating, verbose_name=_(u"Dating"), blank=True, null=True) if WAREHOUSE_AVAILABLE: container = models.ForeignKey(Container, verbose_name=_(u"Container"), blank=True, null=True, related_name='finds') history = HistoricalRecords() def __init__(self, *args, **kwargs): super(Find, self).__init__(*args, **kwargs) image = self._meta.get_field_by_name("image")[0] image.upload_to = "finds/" thumbnail = self._meta.get_field_by_name("thumbnail")[0] thumbnail.upload_to = "finds/thumbs/" class Meta: verbose_name = _(u"Find") verbose_name_plural = _(u"Finds") permissions = ( ("view_find", ugettext(u"Can view all Find")), ("view_own_find", ugettext(u"Can view own Find")), ("add_own_find", ugettext(u"Can add own Find")), ("change_own_find", ugettext(u"Can change own Find")), ("delete_own_find", ugettext(u"Can delete own Find")), ) @property def short_class_name(self): return _(u"FIND") def __unicode__(self): return self.label @property def short_label(self): return self.reference @property def show_url(self): return reverse('show-find', args=[self.pk, '']) @property def name(self): return u" - ".join([base_find.name for base_find in self.base_finds.all()]) def get_first_base_find(self): q= self.base_finds if not q.count(): return return q.all()[0] @property def reference(self): bf = self.get_first_base_find() if not bf: return "00" return bf.short_id() def get_department(self): bf = self.get_first_base_find() if not bf: return "00" return bf.context_record.operation.get_department() def get_town_label(self): bf = self.get_first_base_find() if not bf: return "00" return bf.context_record.operation.get_town_label() @classmethod def get_periods(cls, slice='year', fltr={}): q = cls.objects if fltr: q = q.filter(**fltr) if slice == 'year': years = set() finds = q.filter(downstream_treatment__isnull=True) for find in finds: bi = find.base_finds.all() if not bi: continue bi = bi[0] if bi.context_record.operation.start_date: yr = bi.context_record.operation.start_date.year years.add(yr) return list(years) @classmethod def get_by_year(cls, year, fltr={}): q = cls.objects if fltr: q = q.filter(**fltr) return q.filter(downstream_treatment__isnull=True, base_finds__context_record__operation__start_date__year=year) @classmethod def get_operations(cls): operations = set() finds = cls.objects.filter(downstream_treatment__isnull=True) for find in finds: bi = find.base_finds.all() if not bi: continue bi = bi[0] pk = bi.context_record.operation.pk operations.add(pk) return list(operations) @classmethod def get_by_operation(cls, operation_id): return cls.objects.filter(downstream_treatment__isnull=True, base_finds__context_record__operation__pk=operation_id) @classmethod def get_total_number(cls, fltr={}): q = cls.objects if fltr: q = q.filter(**fltr) return q.filter(downstream_treatment__isnull=True).count() def duplicate(self, user): dct = dict([(attr, getattr(self, attr)) for attr in ('order', 'label', 'description', 'material_type', 'volume', 'weight', 'find_number', 'dating', 'conservatory_state', 'preservation_to_consider', 'weight_unit', 'find_number')]) dct['order'] += 1 dct['history_modifier'] = user new = self.__class__(**dct) new.save() for base_find in self.base_finds.all(): new.base_finds.add(base_find) return new def save(self, *args, **kwargs): super(Find, self).save(*args, **kwargs) for base_find in self.base_finds.all(): if not base_find.index: idx = BaseFind.objects.filter(context_record=\ base_find.context_record).aggregate(Max('index')) base_find.index = idx and idx['index__max'] + 1 or 1 if not base_find.material_index: idx = BaseFind.objects.filter(context_record=\ base_find.context_record, find__material_type=self.material_type).aggregate( Max('material_index')) base_find.material_index = idx and \ idx['material_index__max'] + 1 or 1 base_find.save() class FindSource(Source): class Meta: verbose_name = _(u"Find documentation") verbose_name_plural = _(u"Find documentations") find = models.ForeignKey(Find, verbose_name=_(u"Find"), related_name="source") @property def owner(self): return self.find class TreatmentType(GeneralType): virtual = models.BooleanField(_(u"Virtual")) class Meta: verbose_name = _(u"Treatment type") verbose_name_plural = _(u"Treatment types") ordering = ('label',) class Treatment(BaseHistorizedItem, OwnPerms): external_id = models.CharField(_(u"External ID"), blank=True, null=True, max_length=120) if WAREHOUSE_AVAILABLE: container = models.ForeignKey(Container, verbose_name=_(u"Container"), blank=True, null=True) description = models.TextField(_(u"Description"), blank=True, null=True) comment = models.TextField(_(u"Comment"), blank=True, null=True) treatment_type = models.ForeignKey(TreatmentType, verbose_name=_(u"Treatment type")) if WAREHOUSE_AVAILABLE: location = models.ForeignKey(Warehouse, verbose_name=_(u"Location"), blank=True, null=True) other_location = models.CharField(_(u"Other location"), max_length=200, blank=True, null=True) person = models.ForeignKey(Person, verbose_name=_(u"Person"), blank=True, null=True, on_delete=models.SET_NULL, related_name='treatments') start_date = models.DateField(_(u"Start date"), blank=True, null=True) end_date = models.DateField(_(u"End date"), blank=True, null=True) history = HistoricalRecords() class Meta: verbose_name = _(u"Treatment") verbose_name_plural = _(u"Treatments") permissions = ( ("view_treatment", ugettext(u"Can view all Treatment")), ("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_type) if self.person: lbl += u" %s %s" % (_(u"by"), unicode(self.person)) return lbl 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 class Property(LightHistorizedItem): find = models.ForeignKey(Find, verbose_name=_(u"Find")) if FILES_AVAILABLE: administrative_act = models.ForeignKey(AdministrativeAct, verbose_name=_(u"Administrative act")) person = models.ForeignKey(Person, verbose_name=_(u"Person"), related_name='properties') start_date = models.DateField(_(u"Start date")) end_date = models.DateField(_(u"End date")) class Meta: verbose_name = _(u"Property") verbose_name_plural = _(u"Properties") def __unicode__(self): return self.person + settings.JOINT + self.find