diff options
Diffstat (limited to 'ishtar/ishtar_base/models.py')
| -rw-r--r-- | ishtar/ishtar_base/models.py | 1204 | 
1 files changed, 1204 insertions, 0 deletions
diff --git a/ishtar/ishtar_base/models.py b/ishtar/ishtar_base/models.py new file mode 100644 index 000000000..7de7b239b --- /dev/null +++ b/ishtar/ishtar_base/models.py @@ -0,0 +1,1204 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (C) 2010-2011 É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. + +""" +Models description +""" +import datetime + +from django.core.exceptions import ObjectDoesNotExist, ValidationError +from django.core.validators import validate_slug +from django.utils.translation import ugettext_lazy as _, ugettext +from django.db.utils import DatabaseError +from django.utils.safestring import SafeUnicode, mark_safe +from django.db.models import Q, Max +from django.db.models.signals import m2m_changed + +from django.contrib.auth.models import User +from django.contrib.gis.db import models +from django.contrib import admin + +from simple_history.models import HistoricalRecords as BaseHistoricalRecords + +from ishtar import settings + +JOINT = u" - " + +# HistoricalRecords enhancement: don't save identical versions +class HistoricalRecords(BaseHistoricalRecords): +    def create_historical_record(self, instance, type): +        manager = getattr(instance, self.manager_name) +        attrs = {} +        for field in instance._meta.fields: +            attrs[field.attname] = getattr(instance, field.attname) +        history = instance.history.all() +        if not history: +            manager.create(history_type=type, **attrs) +            return +        old_instance = history[0] +        for field in instance._meta.fields: +            if getattr(old_instance, field.attname) != attrs[field.attname]: +                manager.create(history_type=type, **attrs) +                return + +# valid ID validator for models +def valid_id(cls): +    def func(value): +        try: +            cls.objects.get(pk=value) +        except ObjectDoesNotExist: +            raise ValidationError(_(u"Not a valid item.")) +    return func + +# unique validator for models +def is_unique(cls, field): +    def func(value): +        query = {field:value} +        try: +            assert cls.objects.filter(**query).count() == 0 +        except AssertionError: +            raise ValidationError(_(u"This item already exist.")) +    return func + +class OwnPerms: +    """ +    Manage special permissions for object's owner +    """ +    @classmethod +    def get_query_owns(cls, user): +        """ +        Query object to get own items +        """ +        return None # implement for each object + +    def is_own(self, user): +        """ +        Check if the current object is owned by the user +        """ +        query = self.get_query_owns(user) +        if not query: +            return False +        query = query & Q(pk=self.pk) +        return cls.objects.filter(query).count() + +    @classmethod +    def has_item_of(cls, user): +        """ +        Check if the user own some items +        """ +        query = cls.get_query_owns(user) +        if not query: +            return False +        return cls.objects.filter(query).count() + +    @classmethod +    def get_owns(cls, user): +        """ +        Get Own items +        """ +        if isinstance(user, User): +            user = IshtarUser.objects.get(user_ptr=user) +        if user.is_anonymous(): +            return [] +        query = cls.get_query_owns(user) +        if not query: +            return [] +        return cls.objects.filter(query).order_by(*cls._meta.ordering).all() + +class GeneralType(models.Model): +    """ +    Abstract class for "types" +    """ +    label = models.CharField(_(u"Label"), max_length=100) +    txt_idx = models.CharField(_(u"Textual ID"), +                         validators=[validate_slug], max_length=30, unique=True) +    comment = models.TextField(_(u"Comment"), blank=True, null=True) +    available = models.BooleanField(_(u"Available")) +    HELP_TEXT = u"" + +    class Meta: +        abstract = True + +    def __unicode__(self): +        return self.label + +    @classmethod +    def get_help(cls, dct={}): +        help_text = cls.HELP_TEXT +        c_rank = -1 +        help_items = u"\n" +        for item in cls.get_types(dct=dct, instances=True): +            if not item.comment: +                continue +            if c_rank > item.rank: +                help_items += u"</dl>\n" +            elif c_rank < item.rank: +                help_items += u"<dl>\n" +            c_rank = item.rank +            help_items += u"<dt>%s</dt><dd>%s</dd>" % (item.label, +                                u"<br/>".join(item.comment.split('\n'))) +        c_rank += 1 +        if c_rank: +            help_items += c_rank*u"</dl>" +        return mark_safe(help_text + help_items) + +    @classmethod +    def get_types(cls, dct={}, instances=False): +        base_dct = dct.copy() +        if hasattr(cls, 'parent'): +            return cls._get_parent_types(base_dct, instances) +        return cls._get_types(base_dct, instances) + +    @classmethod +    def _get_types(cls, dct={}, instances=False): +        dct['available'] = True +        if not instances: +            yield ('', '--') +        for item in cls.objects.filter(**dct).all(): +            if instances: +                item.rank = 0 +                yield item +            else: +                yield (item.pk, _(unicode(item))) + +    PREFIX = "› " + +    @classmethod +    def _get_childs(cls, item, dct, prefix=0, instances=False): +        prefix += 1 +        dct['parent'] = item +        childs = cls.objects.filter(**dct) +        if hasattr(cls, 'order'): +            childs = childs.order_by('order') +        for child in childs.all(): +            if instances: +                child.rank = prefix +                yield child +            else: +                yield (child.pk, SafeUnicode(prefix*cls.PREFIX + \ +                                         unicode(_(unicode(child))) )) +            for sub_child in cls._get_childs(child, dct, prefix, instances): +                yield sub_child + +    @classmethod +    def _get_parent_types(cls, dct={}, instances=False): +        dct['available'] = True +        if not instances: +            yield ('', '--') +        dct['parent'] = None +        items = cls.objects.filter(**dct) +        if hasattr(cls, 'order'): +            items = items.order_by('order') +        for item in items.all(): +            if instances: +                item.rank = 0 +                yield item +            else: +                yield (item.pk, unicode(item)) +            for child in cls._get_childs(item, dct, instances): +                yield child + +class HistoryError(Exception): +    def __init__(self, value): +        self.value = value +    def __str__(self): +        return repr(self.value) + +class BaseHistorizedItem(models.Model): +    history_modifier = models.ForeignKey(User, related_name='+', +                                      verbose_name=_(u"Last modifier")) +    class Meta: +        abstract = True + +    def save(self, *args, **kwargs): +        assert hasattr(self, 'history_modifier') == True +        super(BaseHistorizedItem, self).save(*args, **kwargs) +        return True + +    def get_previous(self, step=None, date=None, strict=True): +        """ +        Get a "step" previous state of the item +        """ +        assert step or date +        historized = self.history.all() +        item = None +        if step: +            assert len(historized) > step +            item = historized[step] +        else: +            for step, item in enumerate(historized): +                if item.history_date == date: +                    break +            # ended with no match +            if item.history_date != date: +                return +        item._step = step +        if len(historized) != (step + 1): +            item._previous = historized[step + 1].history_date +        else: +            item._previous = None +        if step > 0: +            item._next = historized[step - 1].history_date +        else: +            item._next = None +        item.history_date = historized[step].history_date +        model = self.__class__ +        for k in model._meta.get_all_field_names(): +            field = model._meta.get_field_by_name(k)[0] +            if hasattr(field, 'rel') and field.rel: +                if not hasattr(item, k+'_id'): +                    setattr(item, k, getattr(self, k)) +                    continue +                val = getattr(item, k+'_id') +                if not val: +                    setattr(item, k, None) +                    continue +                try: +                    val = field.rel.to.objects.get(pk=val) +                    setattr(item, k, val) +                except ObjectDoesNotExist: +                    if strict: +                        raise HistoryError(u"The class %s has no pk %d" % ( +                                           unicode(field.rel.to), val)) +                    setattr(item, k, None) +        item.pk = self.pk +        return item + +    def rollback(self, date): +        """ +        Rollback to a previous state +        """ +        to_del, new_item = [], None +        for item in self.history.all(): +            to_del.append(item) +            if item.history_date == date: +                new_item = item +                break +        if not new_item: +            raise HistoryError(u"The date to rollback to doesn't exist.") +        try: +            for f in self._meta.fields: +                k = f.name +                if k != 'id' and hasattr(self, k): +                    if not hasattr(new_item, k): +                        k = k + "_id" +                    setattr(self, k, getattr(new_item, k)) +            self.save() +        except: +            raise HistoryError(u"The rollback has failed.") +        # clean the obsolete history +        for historized_item in to_del: +            historized_item.delete() + +    def values(self): +        values = {} +        for f in self._meta.fields: +            k = f.name +            if k != 'id': +                values[k] = getattr(self, k) +        return values + +class LightHistorizedItem(BaseHistorizedItem): +    history_date = models.DateTimeField(default=datetime.datetime.now) +    class Meta: +        abstract = True + +class Departement(models.Model): +    label = models.CharField(_(u"Label"), max_length=30) +    number = models.CharField(_(u"Number"), unique=True, max_length=3) + +    class Meta: +        verbose_name = _(u"Departement") +        verbose_name_plural = _(u"Departements") +        ordering = ['number'] + +    def __unicode__(self): +        return unicode(self.number) + JOINT + self.label + +class Address(BaseHistorizedItem): +    address = models.TextField(_(u"Address"), null=True, blank=True) +    address_complement = models.TextField(_(u"Address complement"), null=True, +                                          blank=True) +    postal_code = models.CharField(_(u"Postal code"), max_length=10, null=True, +                                   blank=True) +    town = models.CharField(_(u"Town"), max_length=30, null=True, blank=True) +    country = models.CharField(_(u"Country"), max_length=30, null=True, +                               blank=True) +    phone = models.CharField(_(u"Phone"), max_length=18, null=True, blank=True) +    mobile_phone = models.CharField(_(u"Mobile phone"), max_length=18, +                                    null=True, blank=True) +    history = HistoricalRecords() + +    class Meta: +        abstract = True + +class OrganizationType(GeneralType): +    class Meta: +        verbose_name = _(u"Organization type") +        verbose_name_plural = _(u"Organization types") + +class Organization(Address, OwnPerms): +    name = models.CharField(_(u"Name"), max_length=100) +    organization_type = models.ForeignKey(OrganizationType, +                                          verbose_name=_(u"Type")) +    history = HistoricalRecords() +    class Meta: +        verbose_name = _(u"Organization") +        verbose_name_plural = _(u"Organizations") +        permissions = ( +          ("view_own_organization", ugettext(u"Can view own Organization")), +          ("add_own_organization", ugettext(u"Can add own Organization")), +          ("change_own_organization", ugettext(u"Can change own Organization")), +          ("delete_own_organization", ugettext(u"Can delete own Organization")), +        ) + +    def __unicode__(self): +        return self.name + +class PersonType(GeneralType): +    class Meta: +        verbose_name = _(u"Person type") +        verbose_name_plural = _(u"Person types") + +class Person(Address, OwnPerms) : +    TYPE = (('Mr', _(u'Mr')), +            ('Ms', _(u'Miss')), +            ('Md', _(u'Mrs')), +            ('Dr', _(u'Doctor')), +            ) +    title = models.CharField(_(u"Title"), max_length=2, choices=TYPE) +    surname = models.CharField(_(u"Surname"), max_length=20) +    name = models.CharField(_(u"Name"), max_length=30) +    email = models.CharField(_(u"Email"), max_length=40, blank=True, null=True) +    person_type = models.ForeignKey(PersonType, verbose_name=_(u"Type")) +    attached_to = models.ForeignKey('Organization', +                       verbose_name=_(u"Is attached to"), blank=True, null=True) +    is_author = models.NullBooleanField(_(u"Is an author?"), blank=True, +                                        null=True) +    in_charge_storage = models.NullBooleanField(_(u"In charge of a storage?"), +                                            blank=True, null=True) + +    class Meta: +        verbose_name = _(u"Person") +        verbose_name_plural = _(u"Persons") +        permissions = ( +            ("view_person", ugettext(u"Can view Person")), +            ("view_own_person", ugettext(u"Can view own Person")), +            ("add_own_person", ugettext(u"Can add own Person")), +            ("change_own_person", ugettext(u"Can change own Person")), +            ("delete_own_person", ugettext(u"Can delete own Person")), +        ) + +    def __unicode__(self): +        lbl = u"%s %s" % (self.name, self.surname) +        lbl += JOINT +        if self.attached_to: +            lbl += unicode(self.attached_to) +        elif self.email: +            lbl += self.email +        return lbl + +    def full_label(self): +        return u" ".join([unicode(getattr(self, attr)) +                         for attr in ('title', 'surname', 'name', 'attached_to') +                         if getattr(self, attr)]) + +class IshtarUser(User): +    person = models.ForeignKey(Person, verbose_name=_(u"Person"), unique=True) + +    class Meta: +        verbose_name = _(u"Ishtar user") +        verbose_name_plural = _(u"Ishtar users") + +class AuthorType(GeneralType): +    class Meta: +        verbose_name = _(u"Author type") +        verbose_name_plural = _(u"Author types") + +class Author(models.Model): +    person = models.ForeignKey(Person, verbose_name=_(u"Person")) +    author_type = models.ForeignKey(AuthorType, verbose_name=_(u"Author type")) + +    class Meta: +        verbose_name = _(u"Author") +        verbose_name_plural = _(u"Authors") + +    def __unicode__(self): +        return unicode(self.person) + JOINT + unicode(self.author_type) + +class SourceType(GeneralType): +    class Meta: +        verbose_name = _(u"Source type") +        verbose_name_plural = _(u"Source types") + +class Source(models.Model): +    title = models.CharField(_(u"Title"), max_length=200) +    source_type = models.ForeignKey(SourceType, verbose_name=_(u"Type")) +    authors = models.ManyToManyField(Author, verbose_name=_(u"Authors")) + +    class Meta: +        abstract = True + +    def __unicode__(self): +        return self.title + +class FileType(GeneralType): +    class Meta: +        verbose_name = _(u"Archaeological file type") +        verbose_name_plural = _(u"Archaeological file types") + +    @classmethod +    def is_preventive(cls, file_type_id, key=''): +        key = key or 'preventive' +        try: +            preventive = FileType.objects.get(txt_idx=key).pk +            return file_type_id == preventive +        except ObjectDoesNotExist: +            return False + +class PermitType(GeneralType): +    class Meta: +        verbose_name = _(u"Permit type") +        verbose_name_plural = _(u"Permit types") + +if settings.COUNTRY == 'fr': +    class SaisineType(GeneralType): +        delay = models.IntegerField(_(u"Delay (in days)")) +        class Meta: +            verbose_name = u"Type Saisine" +            verbose_name_plural = u"Types Saisine" + +class File(BaseHistorizedItem, OwnPerms): +    TABLE_COLS = ['numeric_reference', 'year', 'internal_reference', +                  'file_type', 'saisine_type', 'towns', ] +    year = models.IntegerField(_(u"Year"), +                               default=lambda:datetime.datetime.now().year) +    numeric_reference = models.IntegerField(_(u"Numeric reference")) +    internal_reference = models.CharField(_(u"Internal reference"), +                                          max_length=60, unique=True) +    file_type = models.ForeignKey(FileType, verbose_name=_(u"File type")) +    in_charge = models.ForeignKey(Person, related_name='+', +                                  verbose_name=_(u"Person in charge")) +    general_contractor = models.ForeignKey(Person, related_name='+', +               verbose_name=_(u"General contractor"), blank=True, null=True) +    town_planning_service = models.ForeignKey(Organization, related_name='+', +               verbose_name=_(u"Town planning service"), blank=True, null=True) +    permit_type = models.ForeignKey(PermitType, verbose_name=_(u"Permit type"), +                                                          blank=True, null=True) +    permit_reference = models.CharField(_(u"Permit reference"), +                                          max_length=60, blank=True, null=True) +    is_active = models.BooleanField(_(u"Is active?"), default=True) +    towns = models.ManyToManyField("Town", verbose_name=_(u"Towns")) +    creation_date = models.DateField(_(u"Creation date"), +                                     default=datetime.date.today) +    reception_date = models.DateField(_(u'Reception date'), blank=True, +                                      null=True) +    related_file = models.ForeignKey("File", verbose_name=_(u"Related file"), +                                     blank=True, null=True) +    if settings.COUNTRY == 'fr': +        saisine_type = models.ForeignKey(SaisineType, blank=True, null=True, +                                         verbose_name= u"Type de saisine") +        reference_number = models.IntegerField(_(u"Reference number"), +                                               blank=True, null=True) +    total_surface = models.IntegerField(_(u"Total surface (m²)"), +                                        blank=True, null=True) +    total_developed_surface = models.IntegerField( +            _(u"Total developed surface (m²)"), blank=True, null=True) +    address = models.TextField(_(u"Main address"), null=True, blank=True) +    address_complement = models.TextField(_(u"Main address - complement"), +                                          null=True, blank=True) +    postal_code = models.CharField(_(u"Main address - postal code"), +                                   max_length=10, null=True, blank=True) +    comment = models.TextField(_(u"Comment")) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Archaeological file") +        verbose_name_plural = _(u"Archaeological files") +        permissions = ( +            ("view_own_file", ugettext(u"Can view own Archaelogical file")), +            ("add_own_file", ugettext(u"Can add own Archaelogical file")), +            ("change_own_file", ugettext(u"Can change own Archaelogical file")), +            ("delete_own_file", ugettext(u"Can delete own Archaelogical file")), +        ) +        ordering = ['-year', '-numeric_reference'] + +    def __unicode__(self): +        items = [unicode(_('Intercommunal'))] +        if self.towns.count() == 1: +            items[0] = unicode(self.towns.all()[0]) +        items.append("-".join((unicode(self.year), +                               unicode(self.numeric_reference)))) +        items += [unicode(getattr(self, k))[:36] +                  for k in ['internal_reference',] if getattr(self, k)] +        return JOINT.join(items) + +    @classmethod +    def get_query_owns(cls, user): +        return Q(history_modifier=user) & Q(is_active=True) + +    def closing(self): +        if self.is_active: +            return +        for item in self.history.all(): +            if item.is_active(): +                break +            closing_item = item +        return {'date':item.history_date, 'user':item.history_modifier} + +    def total_surface_ha(self): +        if self.total_surface: +            return self.total_surface/10000.0 + +    def total_developed_surface_ha(self): +        if self.total_developed_surface: +            return self.total_developed_surface/10000.0 + +    def operation_acts(self): +        acts = [] +        for ope in self.operations.all(): +            for act in ope.administrative_act.all(): +                acts.append(act) +        return acts + +    def is_preventive(self): +        return FileType.is_preventive(self.file_type.pk) + +class OperationType(GeneralType): +    class Meta: +        verbose_name = _(u"Operation type") +        verbose_name_plural = _(u"Operation types") + +    @classmethod +    def is_preventive(cls, ope_type_id, key=''): +        key = key or 'prev_excavation' +        try: +            preventive = OperationType.objects.get(txt_idx=key).pk +            return ope_type_id == preventive +        except ObjectDoesNotExist: +            return False + +class RemainType(GeneralType): +    class Meta: +        verbose_name = _(u"Remain type") +        verbose_name_plural = _(u"Remain types") + +class Operation(BaseHistorizedItem, OwnPerms): +    TABLE_COLS = ['operation_code', 'year', 'operation_type', +                  'remains', 'towns', 'associated_file', 'start_date'] +    start_date = models.DateField(_(u"Start date"), null=True, blank=True) +    end_date = models.DateField(_(u"Closing date"), null=True, blank=True) +    in_charge = models.ForeignKey('Person', related_name='+', null=True, +                                  blank=True, verbose_name=_(u"In charge")) +    year = models.IntegerField(_(u"Year")) +    operation_code = models.IntegerField(_(u"Operation code")) +    associated_file = models.ForeignKey(File, related_name='operations', +                                 verbose_name=_(u"File"), blank=True, null=True) +    operation_type = models.ForeignKey(OperationType, related_name='+', +                                       verbose_name=_(u"Operation type")) +    surface = models.IntegerField(_(u"Surface (m²)"), blank=True, null=True) +    remains = models.ManyToManyField("RemainType", verbose_name=_(u'Remains')) +    towns = models.ManyToManyField("Town", verbose_name=_(u"Towns")) +    cost = models.IntegerField(_(u"Cost (€)"), blank=True, null=True) +    periods = models.ManyToManyField('Period', verbose_name=_(u"Periods")) +    if settings.COUNTRY == 'fr': +        code_patriarche = models.IntegerField(u"Code PATRIARCHE", null=True, +                                              blank=True) +        code_dracar = models.CharField(u"Code DRACAR", max_length=10, null=True, +                                       blank=True) +        fnap_financing = models.FloatField(u"Financement FNAP", +                                             blank=True, null=True) +        TABLE_COLS += ["code_patriarche"] +        zoning_prescription = models.NullBooleanField( +                            _(u"Prescription on zoning"), blank=True, null=True) +        large_area_prescription = models.NullBooleanField( +                        _(u"Prescription on large area"), blank=True, null=True) +        geoarchaeological_context_prescription = models.NullBooleanField( +         _(u"Prescription on geoarchaeological context"), blank=True, null=True) +    comment = models.TextField(_(u"Comment"), null=True, blank=True) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Operation") +        verbose_name_plural = _(u"Operations") +        permissions = ( +            ("view_own_operation", ugettext(u"Can view own Operation")), +            ("add_own_operation", ugettext(u"Can add own Operation")), +            ("change_own_operation", ugettext(u"Can change own Operation")), +            ("delete_own_operation", ugettext(u"Can delete own Operation")), +        ) + +    def __unicode__(self): +        items = [unicode(_('Intercommunal'))] +        if self.towns.count() == 1: +            items[0] = unicode(self.towns.all()[0]) +        items.append("-".join((unicode(self.year), +                               unicode(self.operation_code)))) +        return JOINT.join(items) + +    def is_own(self, person): +        return False + +    @classmethod +    def get_query_owns(cls, user): +        return Q(in_charge=user.person)|Q(history_modifier=user)\ +               & Q(end_date__isnull=True) + +    def is_active(self): +        return not bool(self.end_date) + +    def closing(self): +        if self.is_active(): +            return +        for item in self.history.all(): +            if not item.end_date: +                break +        return {'date':item.history_date, +                'user':IshtarUser.objects.get(pk=item.history_modifier_id)} + +class OperationSource(Source): +    class Meta: +        verbose_name = _(u"Operation documentation") +        verbose_name_plural = _(u"Operation documentations") +    operation = models.ForeignKey(Operation, verbose_name=_(u"Operation"), +                                  related_name="source") + +class Parcel(LightHistorizedItem): +    associated_file = models.ForeignKey(File, related_name='parcels', +                                blank=True, null=True, verbose_name=_(u"File")) +    operation = models.ForeignKey(Operation, related_name='parcels', blank=True, +                                  null=True, verbose_name=_(u"Operation")) +    year = models.IntegerField(_(u"Year"), +                               default=lambda:datetime.datetime.now().year) +    town = models.ForeignKey("Town", related_name='parcels', +                             verbose_name=_(u"Town")) +    section = models.CharField(_(u"Section"), max_length=4) +    parcel_number = models.CharField(_(u"Parcel number"), max_length=6) + +    class Meta: +        verbose_name = _(u"Parcel") +        verbose_name_plural = _(u"Parcels") + +    def short_label(self): +        return JOINT.join([unicode(item) for item in [self.section, +                                                   self.parcel_number] if item]) + +    def __unicode__(self): +        return self.short_label() + +    def long_label(self): +        return JOINT.join([unicode(item) for item in \ +       [self.associated_file, self.operation, self.section, self.parcel_number] +                                          if item]) + +class Period(GeneralType) : +    order = models.IntegerField(_(u"Order")) +    start_date = models.IntegerField(_(u"Start date")) +    end_date = models.IntegerField(_(u"End date")) +    parent = models.ForeignKey("Period", verbose_name=_(u"Parent period"), +                               blank=True, null=True) + +    class Meta: +        verbose_name = _(u"Type Period") +        verbose_name_plural = _(u"Types Period") + +    def __unicode__(self): +        return self.label + +class DatingType(GeneralType): +    class Meta: +        verbose_name = _(u"Dating type") +        verbose_name_plural = _(u"Dating types") + +class DatingQuality(GeneralType): +    class Meta: +        verbose_name = _(u"Dating quality") +        verbose_name_plural = _(u"Dating qualities") + +class Dating(models.Model): +    period = models.ForeignKey(Period, verbose_name=_(u"Period")) +    start_date = models.IntegerField(_(u"Start date"), blank=True, null=True) +    end_date = models.IntegerField(_(u"End date"), blank=True, null=True) +    dating_type = models.ForeignKey(DatingType, verbose_name=_(u"Dating type"), +                                    blank=True, null=True) +    quality = models.ForeignKey(DatingQuality, verbose_name=_(u"Quality"), +                                blank=True, null=True) + +    class Meta: +        verbose_name = _(u"Dating") +        verbose_name_plural = _(u"Datings") + +    def __unicode__(self): +        start_date = self.start_date and unicode(self.start_date) or u"" +        end_date = self.end_date and unicode(self.end_date) or u"" +        if not start_date and not end_date: +            return unicode(self.period) +        return u"%s (%s-%s)" % (self.period, start_date, end_date) + +class Unit(GeneralType): +    order = models.IntegerField(_(u"Order")) +    parent = models.ForeignKey("Unit", verbose_name=_(u"Parent unit"), +                               blank=True, null=True) + +    class Meta: +        verbose_name = _(u"Type Unit") +        verbose_name_plural = _(u"Types Unit") + +    def __unicode__(self): +        return self.label + +class ActivityType(GeneralType): +    order = models.IntegerField(_(u"Order")) + +    class Meta: +        verbose_name = _(u"Type Activity") +        verbose_name_plural = _(u"Types Activity") + +    def __unicode__(self): +        return self.label + +class IdentificationType(GeneralType): +    order = models.IntegerField(_(u"Order")) +    class Meta: +        verbose_name = _(u"Type Identification") +        verbose_name_plural = _(u"Types Identification") + +    def __unicode__(self): +        return self.label + +class ContextRecord(BaseHistorizedItem, OwnPerms): +    TABLE_COLS = ['parcel.town', 'parcel.operation.year', +                  'parcel.operation.operation_code', +                  'label', 'unit'] +    if settings.COUNTRY == 'fr': +        TABLE_COLS.insert(1, 'parcel.operation.code_patriarche') +    parcel = models.ForeignKey(Parcel, verbose_name=_(u"Parcel"), +                               related_name='context_record') +    operation = models.ForeignKey(Operation, verbose_name=_(u"Operation"), +                                  related_name='context_record') +    label = models.CharField(_(u"ID"), max_length=200) +    description = models.TextField(_("Description"), blank=True, null=True) +    length = models.IntegerField(_(u"Length (cm)"), blank=True, null=True) +    width = models.IntegerField(_(u"Width (cm)"), blank=True, null=True) +    thickness = models.IntegerField(_(u"Thickness (cm)"), blank=True, null=True) +    depth = models.IntegerField(_(u"Depth (cm)"), blank=True, null=True) +    location = models.CharField(_(u"Location"), blank=True, null=True, +     max_length=200, +     help_text=_(u"A short description of the location of the context record")) +    datings = models.ManyToManyField(Dating) +    unit = models.ForeignKey(Unit, verbose_name=_(u"Unit"), related_name='+', +                             blank=True, null=True) +    has_furniture = models.NullBooleanField(u"Has furniture?", blank=True, +                                            null=True) +    filling = models.TextField(_(u"Filling"), blank=True, null=True) +    interpretation = models.TextField(_(u"Interpretation"), blank=True, +                                      null=True) +    taq = models.IntegerField(_(u"TAQ"), blank=True, null=True, +     help_text=_("\"Terminus Ante Quem\" the context record can't have been " +                 "created after this date")) +    taq_estimated = models.IntegerField(_(u"Estimated TAQ"), blank=True, +     null=True, help_text=_("Estimation of a \"Terminus Ante Quem\"")) +    tpq = models.IntegerField(_(u"TPQ"), blank=True, null=True, +     help_text=_("\"Terminus Post Quem\" the context record can't have been " +                 " created before this date")) +    tpq_estimated = models.IntegerField(_(u"Estimated TPQ"), blank=True, +     null=True, help_text=_("Estimation of a \"Terminus Post Quem\"")) +    identification = models.ForeignKey(IdentificationType, blank=True, +                            null=True, verbose_name=_(u"Identification"),) +    activity = models.ForeignKey(ActivityType,blank=True, null=True, +                                 verbose_name=_(u"Activity"),) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Context Record") +        verbose_name_plural = _(u"Context Record") +        permissions = ( + ("view_own_contextrecord", ugettext(u"Can view own Context Record")), + ("add_own_contextrecord", ugettext(u"Can add own Context Record")), + ("change_own_contextrecord", ugettext(u"Can change own Context Record")), + ("delete_own_contextrecord", ugettext(u"Can delete own Context Record")), +        ) + +    def __unicode__(self): +        return JOINT.join((unicode(self.parcel), self.label)) + +    def full_label(self): +        if not self.parcel.operation: +            return unicode(self) +        return self._real_label() or self._temp_label() + +    def _real_label(self): +        if not self.parcel.operation.code_patriarche: +            return +        return JOINT.join((self.parcel.operation.code_patriarche, +                           self.label)) + +    def _temp_label(self): +        if self.parcel.operation.code_patriarche: +            return +        return JOINT.join([unicode(lbl) for lbl in [self.parcel.operation.year, +                                           self.parcel.operation.operation_code, +                                           self.label] if lbl]) +class ContextRecordSource(Source): +    class Meta: +        verbose_name = _(u"Context record documentation") +        verbose_name_plural = _(u"Context record documentations") +    context_record = models.ForeignKey(ContextRecord, +                       verbose_name=_(u"Context record"), related_name="source") + +class MaterialType(GeneralType): +    recommendation = models.TextField(_(u"Recommendation")) +    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") + +class BaseItem(BaseHistorizedItem, OwnPerms): +    label = models.CharField(_(u"ID"), max_length=60) +    description = models.TextField(_(u"Description")) +    context_record = models.ForeignKey(ContextRecord, +              related_name='base_items', verbose_name=_(u"Context Record")) +    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 item") +        verbose_name_plural = _(u"Base items") +        permissions = ( +            ("view_own_baseitem", ugettext(u"Can view own Base item")), +            ("add_own_baseitem", ugettext(u"Can add own Base item")), +            ("change_own_baseitem", ugettext(u"Can change own Base item")), +            ("delete_own_baseitem", ugettext(u"Can delete own Base item")), +        ) + +    def __unicode__(self): +        return self.label + +    def get_last_item(self): +        #TODO: manage virtuals - property(last_item) ? +        items = self.item.filter().order_by("-order").all() +        return items and items[0] + +    def full_label(self): +        return self._real_label() or self._temp_label() + +    def material_type_label(self): +        item = self.get_last_item() +        lbl = item and (unicode(item.material_type) + unicode(_(":"))) or '' +        if self.context_record.parcel.operation.code_patriarche: +            return lbl + JOINT.join([unicode(it) for it in ( +                           self.context_record.parcel.operation.code_patriarche, +                           self.context_record.label, +                           self.material_index, +                           self.label)]) +        return lbl + JOINT.join([unicode(it) for it in ( +                           self.context_record.parcel.year, +                           self.index, +                           self.context_record.label, +                           self.material_index, +                           self.label)]) + + +    def _real_label(self): +        if not self.context_record.parcel.operation.code_patriarche: +            return +        return JOINT.join([unicode(it) for it in ( +                           self.context_record.parcel.operation.code_patriarche, +                           self.context_record.label, +                           self.label)]) + +    def _temp_label(self): +        if self.context_record.parcel.operation.code_patriarche: +            return +        return JOINT.join([unicode(it) for it in ( +                           self.context_record.parcel.year, +                           self.index, +                           self.context_record.label, +                           self.label)]) +class Item(BaseHistorizedItem, OwnPerms): +    TABLE_COLS = ['base_items.context_record.parcel.town', +                  'base_items.context_record.parcel.operation.year', +                  'base_items.context_record.parcel.operation.operation_code', +                  'label', 'material_type', 'dating.period', +                  'base_items.is_isolated'] +    if settings.COUNTRY == 'fr': +        TABLE_COLS.insert(1, +                  'base_items.context_record.parcel.operation.code_patriarche') +    base_items = models.ManyToManyField(BaseItem, verbose_name=_(u"Base item"), +                                        related_name='item') +    order = models.IntegerField(_(u"Order")) +    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")) +    volume = models.FloatField(_(u"Volume (l)"), blank=True, null=True) +    weight = models.FloatField(_(u"Weight (g)"), blank=True, null=True) +    item_number = models.IntegerField(_("Item 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")) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Item") +        verbose_name_plural = _(u"Items") +        permissions = ( +            ("view_own_item", ugettext(u"Can view own Item")), +            ("add_own_item", ugettext(u"Can add own Item")), +            ("change_own_item", ugettext(u"Can change own Item")), +            ("delete_own_item", ugettext(u"Can delete own Item")), +        ) + +    def __unicode__(self): +        return self.label + +    def save(self, *args, **kwargs): +        if not self.pk: +            super(Item, self).save(*args, **kwargs) +        for base_item in self.base_items.all(): +            if not base_item.index: +                idx = BaseItem.objects.filter(context_record=\ +                      base_item.context_record).aggregate(Max('index')) +                base_item.index = idx and idx['index__max'] + 1 or 1 +            if not base_item.material_index: +                idx = BaseItem.objects.filter(context_record=\ +                        base_item.context_record, +                        item__material_type=self.material_type).aggregate( +                                                      Max('material_index')) +                base_item.material_index = idx and \ +                                        idx['material_index__max'] + 1 or 1 +            base_item.save() +        super(Item, self).save(*args, **kwargs) + +class ItemSource(Source): +    class Meta: +        verbose_name = _(u"Item documentation") +        verbose_name_plural = _(u"Item documentations") +    item = models.ForeignKey(Item, verbose_name=_(u"Item"), +                             related_name="source") + +class ParcelOwner(LightHistorizedItem): +    owner = models.ForeignKey(Person, verbose_name=_(u"Owner")) +    parcel = models.ForeignKey(Parcel, verbose_name=_(u"Parcel")) +    start_date = models.DateField(_(u"Start date")) +    end_date = models.DateField(_(u"End date")) + +    class Meta: +        verbose_name = _(u"Parcel owner") +        verbose_name_plural = _(u"Parcel owners") + +    def __unicode__(self): +        return self.owner + JOINT + self.parcel + +class WarehouseType(GeneralType): +    class Meta: +        verbose_name = _(u"Warehouse type") +        verbose_name_plural = _(u"Warehouse types") + +class Warehouse(Address, OwnPerms): +    name = models.CharField(_(u"Name"), max_length=40) +    warehouse_type = models.ForeignKey(WarehouseType, +                                       verbose_name=_(u"Warehouse type")) +    person_in_charge = models.ForeignKey(Person, +                     verbose_name=_(u"Person in charge"), null=True, blank=True) +    comment = models.TextField(_(u"Comment"), null=True, blank=True) + +    class Meta: +        verbose_name = _(u"Warehouse") +        verbose_name_plural = _(u"Warehouses") +        permissions = ( +            ("view_own_warehouse", ugettext(u"Can view own Warehouse")), +            ("add_own_warehouse", ugettext(u"Can add own Warehouse")), +            ("change_own_warehouse", ugettext(u"Can change own Warehouse")), +            ("delete_own_warehouse", ugettext(u"Can delete own Warehouse")), +        ) + +    def __unicode__(self): +        return u"%s (%s)" % (self.name, unicode(self.warehouse_type)) + +class ActType(GeneralType): +    TYPE = (('F', _(u'Archaelogical file')), +            ('O', _(u'Operation')), +            ) +    intented_to = models.CharField(_(u"Intended to"), max_length=1, +                                   choices=TYPE) +    class Meta: +        verbose_name = _(u"Act type") +        verbose_name_plural = _(u"Act types") + +class AdministrativeAct(BaseHistorizedItem, OwnPerms): +    TABLE_COLS = ['act_type', 'associated_file', 'operation', +                  'associated_file.towns', 'operation.towns'] +    TABLE_COLS_FILE = ['act_type', 'associated_file', 'associated_file.towns',] +    TABLE_COLS_OPE = ['act_type', 'operation', 'operation.towns'] +    act_type = models.ForeignKey(ActType, verbose_name=_(u"Act type")) +    in_charge = models.ForeignKey(Person, blank=True, null=True, +        related_name='+', verbose_name=_(u"Person in charge of the operation")) +    operator = models.ForeignKey(Organization, blank=True, null=True, +                    verbose_name=_(u"Archaeological preventive operator")) +    scientific = models.ForeignKey(Person, blank=True, null=True, +related_name='+', verbose_name=_(u"Person in charge of the scientific part")) +    signatory = models.ForeignKey(Person, blank=True, null=True, +                    related_name='+', verbose_name=_(u"Signatory")) +    operation = models.ForeignKey(Operation, blank=True, null=True, +                related_name='administrative_act', verbose_name=_(u"Operation")) +    associated_file = models.ForeignKey(File, blank=True, null=True, +       related_name='administrative_act', verbose_name=_(u"Archaelogical file")) +    signature_date = models.DateField(_(u"Signature date")) +    act_object = models.CharField(_(u"Object"), max_length=200) +    if settings.COUNTRY == 'fr': +        ref_sra = models.CharField(u"Référence SRA", max_length=15) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Administrative act") +        verbose_name_plural = _(u"Administrative acts") +        permissions = ( +("view_own_administrativeact", ugettext(u"Can view own Administrative act")), +("add_own_administrativeact", ugettext(u"Can add own Administrative act")), +("change_own_administrativeact", ugettext(u"Can change own Administrative act")), +("delete_own_administrativeact", ugettext(u"Can delete own Administrative act")), +        ) + +    def __unicode__(self): +        return JOINT.join([unicode(item) +          for item in [self.operation, self.associated_file, self.act_object] +          if item]) + +class ContainerType(GeneralType): +    length = models.IntegerField(_(u"Length (mm)")) +    width = models.IntegerField(_(u"Width (mm)")) +    height = models.IntegerField(_(u"Height (mm)")) +    volume = models.IntegerField(_(u"Volume (l)")) +    reference = models.CharField(_(u"Reference"), max_length=30) + +    class Meta: +        verbose_name = _(u"Container type") +        verbose_name_plural = _(u"Container types") + +class Container(LightHistorizedItem): +    location = models.ForeignKey(Warehouse, verbose_name=_(u"Location")) +    container_type = models.ForeignKey(ContainerType, +                                       verbose_name=_("Container type")) +    reference = models.CharField(_(u"Reference"), max_length=40) +    comment = models.TextField(_(u"Comment")) + +    class Meta: +        verbose_name = _(u"Container") +        verbose_name_plural = _(u"Containers") + +if settings.COUNTRY == 'fr': +    class Arrondissement(models.Model): +        name = models.CharField(u"Nom", max_length=30) +        department = models.ForeignKey(Departement, verbose_name=u"Département") + +        def __unicode__(self): +            return JOINT.join((self.name, unicode(self.department))) + +    class Canton(models.Model): +        name = models.CharField(u"Nom", max_length=30) +        arrondissement = models.ForeignKey(Arrondissement, +                                           verbose_name=u"Arrondissement") +        def __unicode__(self): +            return JOINT.join((self.name, unicode(self.arrondissement))) + +class Town(models.Model): +    name = models.CharField(_(u"Name"), max_length=100) +    surface = models.IntegerField(_(u"Surface (m²)"), blank=True, null=True) +    center = models.PointField(_(u"Localisation"), srid=settings.SRID, +                               blank=True, null=True) +    if settings.COUNTRY == 'fr': +        numero_insee = models.CharField(u"Numéro INSEE", max_length=6, +                                        unique=True) +        departement = models.ForeignKey(Departement, verbose_name=u"Département", +                                       null=True, blank=True) +        canton = models.ForeignKey(Canton, verbose_name=u"Canton", null=True, +                                   blank=True) +    objects = models.GeoManager() + +    class Meta: +        verbose_name = _(u"Town") +        verbose_name_plural = _(u"Towns") +        if settings.COUNTRY == 'fr': +            ordering = ['numero_insee'] + +    def __unicode__(self): +        if settings.COUNTRY == "fr": +            return u"%s (%s)" % (self.name, self.numero_insee) +        return self.name + +class TreatmentType(GeneralType): +    virtual = models.BooleanField(_(u"Virtual")) +    class Meta: +        verbose_name = _(u"Treatment type") +        verbose_name_plural = _(u"Treatment types") + +class Treatment(BaseHistorizedItem, OwnPerms): +    container = models.ForeignKey(Container, verbose_name=_(u"Container"), +                                  blank=True, null=True) +    description = models.TextField(_(u"Description"), blank=True, null=True) +    treatment_type = models.ForeignKey(TreatmentType, +                                       verbose_name=_(u"Treatment type")) +    location = models.ForeignKey(Warehouse, verbose_name=_(u"Location"), +                                 blank=True, null=True) +    person = models.ForeignKey(Person, verbose_name=_(u"Person"), +                               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) +    history = HistoricalRecords() + +    class Meta: +        verbose_name = _(u"Treatment") +        verbose_name_plural = _(u"Treatments") +        permissions = ( +            ("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")), +        ) + +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") + +class Property(LightHistorizedItem): +    item = models.ForeignKey(Item, verbose_name=_(u"Item")) +    administrative_act = models.ForeignKey(AdministrativeAct, +                                         verbose_name=_(u"Administrative act")) +    person = models.ForeignKey(Person, verbose_name=_(u"Person")) +    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 + JOINT + self.item +  | 
