diff options
Diffstat (limited to 'ishtar_common/models.py')
| -rw-r--r-- | ishtar_common/models.py | 260 | 
1 files changed, 164 insertions, 96 deletions
| diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 639e44af3..2a7e386b2 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -30,9 +30,11 @@ import logging  import os  from os.path import isfile, join  import re +from secretary import Renderer as SecretaryRenderer  import shutil  from subprocess import Popen, PIPE  import tempfile +import time  import unicodecsv  import zipfile @@ -57,38 +59,29 @@ from django.template.defaultfilters import slugify  from django.contrib.auth.models import User, Group  from django.contrib.contenttypes.models import ContentType -from django.contrib.contenttypes import generic +from django.contrib.contenttypes.fields import GenericForeignKey  from django.contrib.gis.db import models  from simple_history.models import HistoricalRecords as BaseHistoricalRecords -from ishtar_common.ooo_replace import ooo_replace  from ishtar_common.model_merging import merge_model_objects  from ishtar_common.utils import get_cache, disable_for_loaddata  from ishtar_common.data_importer import Importer, ImportFormater, \      IntegerFormater, FloatFormater, UnicodeFormater, DateFormater, \      TypeFormater, YearFormater, StrToBoolean, FileFormater -  logger = logging.getLogger(__name__)  def post_save_user(sender, **kwargs):      user = kwargs['instance'] -    try: -        q = IshtarUser.objects.filter(username=user.username) -        if not q.count(): -            ishtaruser = IshtarUser.create_from_user(user) -        else: -            ishtaruser = q.all()[0] -        administrator, created = PersonType.objects.get_or_create( -            txt_idx='administrator') -        if ishtaruser.is_superuser \ -           and not ishtaruser.has_right('administrator'): -            ishtaruser.person.person_types.add(administrator) -    except DatabaseError:  # manage when db is not synced -        pass +    if kwargs["created"]: +        try: +            IshtarUser.create_from_user(user) +        except DatabaseError:  # manage when db is not synced +            pass +    IshtarUser.set_superuser(user)  post_save.connect(post_save_user, sender=User) @@ -132,7 +125,7 @@ def check_model_access_control(request, model, available_perms=None):  class Imported(models.Model):      imports = models.ManyToManyField( -        'Import', blank=True, null=True, +        'Import', blank=True,          related_name="imported_%(app_label)s_%(class)s")      class Meta: @@ -158,6 +151,15 @@ class ValueGetter(object):                  values[prefix + field_name] = value          for extra_field in self.GET_VALUES_EXTRA:              values[prefix + extra_field] = getattr(self, extra_field) or '' +        for key in values.keys(): +            val = values[key] +            if val is None: +                val = '' +            else: +                val = unicode(val) +            if val.endswith('.None'): +                val = '' +            values[key] = val          values['KEYS'] = u'\n'.join(values.keys())          value_list = []          for key in values.keys(): @@ -187,7 +189,7 @@ class HistoricalRecords(BaseHistoricalRecords):              history_modifier = getattr(instance, 'history_modifier', None)              assert history_modifier          except (User.DoesNotExist, AssertionError): -            # on batch removing of users, user could have disapeared +            # on batch removing of users, user could have disappeared              return          manager = getattr(instance, self.manager_name)          attrs = {} @@ -197,7 +199,8 @@ class HistoricalRecords(BaseHistoricalRecords):              .filter(history_modifier_id=history_modifier.pk)\              .order_by('-history_date', '-history_id')          if not q_history.count(): -            manager.create(history_type=type, **attrs) +            manager.create(history_type=type, +                           history_date=datetime.datetime.now(), **attrs)              return          old_instance = q_history.all()[0]          # multiple saving by the same user in a very short time are generaly @@ -210,6 +213,8 @@ class HistoricalRecords(BaseHistoricalRecords):          if q.count():              return +        if 'history_date' not in attrs or not attrs['history_date']: +            attrs['history_date'] = datetime.datetime.now()          # record a new version only if data have been changed          for field in instance._meta.fields:              if getattr(old_instance, field.attname) != attrs[field.attname]: @@ -253,7 +258,7 @@ def is_unique(cls, field):      return func -class OwnPerms: +class OwnPerms(object):      """      Manage special permissions for object's owner      """ @@ -315,13 +320,13 @@ class OwnPerms:          """          Get Own items          """ -        if isinstance(user, User): -            user = IshtarUser.objects.get(user_ptr=user) -        if user.is_anonymous(): +        if hasattr(user, 'is_authenticated') and not user.is_authenticated():              returned = cls.objects.filter(pk__isnull=True)              if values:                  returned = []              return returned +        if isinstance(user, User): +            user = IshtarUser.objects.get(user_ptr=user)          items = []          if hasattr(cls, 'BASKET_MODEL'):              items = list(cls.BASKET_MODEL.objects.filter(user=user).all()) @@ -774,7 +779,7 @@ class ItemKey(models.Model):      key = models.CharField(_(u"Key"), max_length=100)      content_type = models.ForeignKey(ContentType)      object_id = models.PositiveIntegerField() -    content_object = generic.GenericForeignKey('content_type', 'object_id') +    content_object = GenericForeignKey('content_type', 'object_id')      importer = models.ForeignKey(          'Import', null=True, blank=True,          help_text=_(u"Specific key to an import")) @@ -869,6 +874,28 @@ class HistoryError(Exception):  PRIVATE_FIELDS = ('id', 'history_modifier', 'order') +class BulkUpdatedItem(object): +    @classmethod +    def bulk_recursion(cls, transaction_id, extra_args): +        """ +        Prevent infinite recursion. Should not happen but wrong manipulation +        in the database or messy imports can generate circular relations + +        :param transaction_id: current transaction ID (unix time) - if null +        a transaction ID is generated +        :param extra_args: arguments dealing with +        :return: (transaction ID, is a recursion) +        """ +        if not transaction_id: +            transaction_id = unicode(time.time()) +        args = ['cached_label_bulk_update', transaction_id] + extra_args +        key, val = get_cache(cls, args) +        if val: +            return transaction_id, True +        cache.set(key, 1, settings.CACHE_SMALLTIMEOUT) +        return transaction_id, False + +  class BaseHistorizedItem(Imported):      IS_BASKET = False      history_modifier = models.ForeignKey( @@ -1163,16 +1190,16 @@ class IshtarSiteProfile(models.Model, Cached):      description = models.TextField(_(u"Description"), null=True, blank=True)      base_color = models.CharField(          _(u"CSS color code for base module"), -        default='rgba(0, 0, 0, 0)', max_length=200) +        default=u'rgba(0, 0, 0, 0)', max_length=200)      files = models.BooleanField(_(u"Files module"), default=False)      files_color = models.CharField(          _(u"CSS color code for files module"), -        default='rgba(0, 32, 210, 0.1)', max_length=200) +        default=u'rgba(0, 32, 210, 0.1)', max_length=200)      context_record = models.BooleanField(_(u"Context records module"),                                           default=False)      context_record_color = models.CharField(          _(u"CSS color code for context record module"), -        default='rgba(210,200,0,0.2)', max_length=200) +        default=u'rgba(210,200,0,0.2)', max_length=200)      find = models.BooleanField(_(u"Finds module"), default=False,                                 help_text=_(u"Need context records module"))      find_index = models.CharField( @@ -1182,16 +1209,16 @@ class IshtarSiteProfile(models.Model, Cached):                      u"only if there is no find in the database"))      find_color = models.CharField(          _(u"CSS color code for find module"), -        default='rgba(210,0,0,0.15)', max_length=200) +        default=u'rgba(210,0,0,0.15)', max_length=200)      warehouse = models.BooleanField(          _(u"Warehouses module"), default=False,          help_text=_(u"Need finds module"))      warehouse_color = models.CharField( -        _(u"CSS code for warehouse module"), default='rgba(10,20,200,0.15)', +        _(u"CSS code for warehouse module"), default=u'rgba(10,20,200,0.15)',          max_length=200)      mapping = models.BooleanField(_(u"Mapping module"), default=False)      mapping_color = models.CharField( -        _(u"CSS code for mapping module"), default='rgba(72, 236, 0, 0.15)', +        _(u"CSS code for mapping module"), default=u'rgba(72, 236, 0, 0.15)',          max_length=200)      homepage = models.TextField(          _(u"Home page"), null=True, blank=True, @@ -1200,57 +1227,57 @@ class IshtarSiteProfile(models.Model, Cached):                      u"can be used to display a random image."))      file_external_id = models.TextField(          _(u"File external id"), -        default="{year}-{numeric_reference}", +        default=u"{year}-{numeric_reference}",          help_text=_(u"Formula to manage file external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      parcel_external_id = models.TextField(          _(u"Parcel external id"), -        default="{associated_file__external_id}{operation__code_patriarche}-" -                "{town__numero_insee}-{section}{parcel_number}", +        default=u"{associated_file__external_id}{operation__code_patriarche}-" +                u"{town__numero_insee}-{section}{parcel_number}",          help_text=_(u"Formula to manage parcel external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      context_record_external_id = models.TextField(          _(u"Context record external id"), -        default="{parcel__external_id}-{label}", +        default=u"{parcel__external_id}-{label}",          help_text=_(u"Formula to manage context record external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      base_find_external_id = models.TextField(          _(u"Base find external id"), -        default="{context_record__external_id}-{label}", +        default=u"{context_record__external_id}-{label}",          help_text=_(u"Formula to manage base find external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      find_external_id = models.TextField(          _(u"Find external id"), -        default="{get_first_base_find__context_record__external_id}-{label}", +        default=u"{get_first_base_find__context_record__external_id}-{label}",          help_text=_(u"Formula to manage find external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      container_external_id = models.TextField(          _(u"Container external id"), -        default="{responsible__external_id}-{index}", +        default=u"{responsible__external_id}-{index}",          help_text=_(u"Formula to manage container external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      warehouse_external_id = models.TextField(          _(u"Warehouse external id"), -        default="{name|slug}", +        default=u"{name|slug}",          help_text=_(u"Formula to manage warehouse external ID. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external "                      u"data can be destructive."))      person_raw_name = models.TextField(          _(u"Raw name for person"), -        default="{name|upper} {surname}", +        default=u"{name|upper} {surname}",          help_text=_(u"Formula to manage person raw_name. "                      u"Change this with care. With incorrect formula, the "                      u"application might be unusable and import of external " @@ -1584,7 +1611,50 @@ class DocumentTemplate(models.Model):              datetime.date.today().strftime('%Y-%m-%d') +\              u"." + self.template.name.split('.')[-1]          values = c_object.get_values() +        engine = SecretaryRenderer() +        result = engine.render(self.template, **values) +        output = open(output_name, 'wb') +        output.write(result) +        return output_name + +    def convert_from_v1(self): +        """ +        Convert the current template from v1 to v2. +        """ +        from old.ooo_replace import ooo_replace +        from archaeological_operations.models import AdministrativeAct + +        old_dir = settings.MEDIA_ROOT + "/upload/templates/v1/" +        if not os.path.exists(old_dir): +            os.makedirs(old_dir) +        shutil.copy(settings.MEDIA_ROOT + self.template.name, old_dir) + +        tempdir = tempfile.mkdtemp("-ishtardocs") +        output_name = tempdir + os.path.sep + self.template.name.split( +            os.sep)[-1] + +        objects = [] +        filters = [ +            {'operation__isnull': False}, +            {'associated_file__isnull': False}, +            {'treatment_file__isnull': False}, +            {'treatment__isnull': False}, +        ] +        for filtr in filters: +            q = AdministrativeAct.objects.filter(**filtr) +            if q.count(): +                objects.append(q.all()[0]) + +        if not objects: +            return +        values = {} +        for obj in objects: +            values.update(obj.get_values()) +        for key in values: +            values[key] = "{{ " + key + " }}" +          ooo_replace(self.template, output_name, values) +        shutil.move(output_name, settings.MEDIA_ROOT + self.template.name)          return output_name @@ -1704,10 +1774,8 @@ class Address(BaseHistorizedItem):  class Merge(models.Model):      merge_key = models.TextField(_("Merge key"), blank=True, null=True) -    merge_candidate = models.ManyToManyField("self", -                                             blank=True, null=True) -    merge_exclusion = models.ManyToManyField("self", -                                             blank=True, null=True) +    merge_candidate = models.ManyToManyField("self", blank=True) +    merge_exclusion = models.ManyToManyField("self", blank=True)      archived = models.NullBooleanField(default=False,                                         blank=True, null=True)      # 1 for one word similarity, 2 for two word similarity, etc. @@ -1846,13 +1914,13 @@ class ImporterType(models.Model):      description = models.CharField(_(u"Description"), blank=True, null=True,                                     max_length=500)      users = models.ManyToManyField('IshtarUser', verbose_name=_(u"Users"), -                                   blank=True, null=True) +                                   blank=True)      associated_models = models.ForeignKey(          ImporterModel, verbose_name=_(u"Associated model"),          related_name='+', blank=True, null=True)      created_models = models.ManyToManyField(          ImporterModel, verbose_name=_(u"Models that can accept new items"), -        blank=True, null=True, help_text=_(u"Leave blank for no restrictions"), +        blank=True, help_text=_(u"Leave blank for no restrictions"),          related_name='+')      is_template = models.BooleanField(_(u"Is template"), default=False)      unicity_keys = models.CharField(_(u"Unicity keys (separator \";\")"), @@ -2364,7 +2432,7 @@ class Import(models.Model):          _(u"Associated images (zip file)"), upload_to="upload/imports/",          blank=True, null=True, max_length=220)      encoding = models.CharField(_(u"Encoding"), choices=ENCODINGS, -                                default='utf-8', max_length=15) +                                default=u'utf-8', max_length=15)      skip_lines = models.IntegerField(_(u"Skip lines"), default=1)      error_file = models.FileField(_(u"Error file"),                                    upload_to="upload/imports/", @@ -2376,7 +2444,7 @@ class Import(models.Model):                                    upload_to="upload/imports/",                                    blank=True, null=True, max_length=255)      state = models.CharField(_(u"State"), max_length=2, choices=IMPORT_STATE, -                             default='C') +                             default=u'C')      conservative_import = models.BooleanField(          _(u"Conservative import"), default=False,          help_text='If set to true, do not overload existing values') @@ -2570,13 +2638,11 @@ class Organization(Address, Merge, OwnPerms, ValueGetter):          verbose_name = _(u"Organization")          verbose_name_plural = _(u"Organizations")          permissions = ( -            ("view_organization", ugettext(u"Can view all Organizations")), -            ("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")), +            ("view_organization", u"Can view all Organizations"), +            ("view_own_organization", u"Can view own Organization"), +            ("add_own_organization", u"Can add own Organization"), +            ("change_own_organization", u"Can change own Organization"), +            ("delete_own_organization", u"Can delete own Organization"),          )      def simple_lbl(self): @@ -2612,7 +2678,7 @@ class Organization(Address, Merge, OwnPerms, ValueGetter):  class PersonType(GeneralType):      # rights = models.ManyToManyField(WizardStep, verbose_name=_(u"Rights"))      groups = models.ManyToManyField(Group, verbose_name=_(u"Groups"), -                                    blank=True, null=True) +                                    blank=True)      class Meta:          verbose_name = _(u"Person type") @@ -2687,11 +2753,11 @@ class Person(Address, Merge, OwnPerms, ValueGetter):          verbose_name = _(u"Person")          verbose_name_plural = _(u"Persons")          permissions = ( -            ("view_person", ugettext(u"Can view all Persons")), -            ("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")), +            ("view_person", u"Can view all Persons"), +            ("view_own_person", u"Can view own Person"), +            ("add_own_person", u"Can add own Person"), +            ("change_own_person", u"Can change own Person"), +            ("delete_own_person", u"Can delete own Person"),          )      @property @@ -2779,21 +2845,21 @@ class Person(Address, Merge, OwnPerms, ValueGetter):                  txt_idx__in=right_name).count()) or \                  bool(self.person_types.filter(                       groups__permissions__codename__in=right_name).count()) or\ -                bool(self.ishtaruser.filter( -                     groups__permissions__codename__in=right_name +                bool(self.ishtaruser.user_ptr.groups.filter( +                     permissions__codename__in=right_name                       ).count()) or\ -                bool(self.ishtaruser.filter( -                     user_permissions__codename__in=right_name).count()) +                bool(self.ishtaruser.user_ptr.user_permissions.filter( +                     codename__in=right_name).count())          # or self.person_types.filter(wizard__url_name__in=right_name).count())          else:              res = bool(self.person_types.filter(txt_idx=right_name).count()) or \                  bool(self.person_types.filter(                       groups__permissions__codename=right_name).count()) or \ -                bool(self.ishtaruser.filter( -                     groups__permissions__codename__in=[right_name] -                     ).count()) or\ -                bool(self.ishtaruser.filter( -                     user_permissions__codename__in=[right_name]).count()) +                bool(self.ishtaruser.user_ptr.groups.filter( +                     permissions__codename__in=[right_name] +                     ).count()) or \ +                bool(self.ishtaruser.user_ptr.user_permissions.filter( +                     codename__in=[right_name]).count())          # or self.person_types.filter(wizard__url_name=right_name).count())          if session:              cache.set(cache_key, res, settings.CACHE_SMALLTIMEOUT) @@ -2860,7 +2926,7 @@ class Person(Address, Merge, OwnPerms, ValueGetter):                =user.ishtaruser) -class IshtarUser(User): +class IshtarUser(models.Model):      TABLE_COLS = ('username', 'person__name', 'person__surname',                    'person__email', 'person__person_types_list',                    'person__attached_to') @@ -2877,8 +2943,10 @@ class IshtarUser(User):      }      # fields -    person = models.ForeignKey(Person, verbose_name=_(u"Person"), unique=True, -                               related_name='ishtaruser') +    user_ptr = models.OneToOneField(User, primary_key=True, +                                    related_name='ishtaruser') +    person = models.OneToOneField(Person, verbose_name=_(u"Person"), +                                  related_name='ishtaruser')      advanced_shortcut_menu = models.BooleanField(          _(u"Advanced shortcut menu"), default=False) @@ -2887,6 +2955,20 @@ class IshtarUser(User):          verbose_name_plural = _(u"Ishtar users")      @classmethod +    def set_superuser(cls, user): +        q = cls.objects.filter(user_ptr=user) +        if not q.count(): +            return +        ishtaruser = q.all()[0] +        admin, created = PersonType.objects.get_or_create( +            txt_idx='administrator') +        person = ishtaruser.person +        if user.is_superuser: +            person.person_types.add(admin) +        elif admin in person.person_types.all(): +            person.person_types.remove(admin) + +    @classmethod      def create_from_user(cls, user):          default = user.username          surname = user.first_name or default @@ -2895,13 +2977,7 @@ class IshtarUser(User):          person = Person.objects.create(surname=surname,                                         name=name, email=email,                                         history_modifier=user) -        if user.is_superuser: -            person_type, created = PersonType.objects.get_or_create( -                txt_idx='administrator') -            person.person_types.add(person_type) -        password = user.password -        isht_user = IshtarUser.objects.create( -            user_ptr=user, username=default, person=person, password=password) +        isht_user = cls.objects.create(user_ptr=user, person=person)          return isht_user      def has_right(self, right_name, session=None): @@ -2912,20 +2988,17 @@ class IshtarUser(User):      def has_perm(self, perm, model=None, session=None, obj=None):          if not session: -            return super(IshtarUser, self).has_perm(perm, model) +            return self.user_ptr.has_perm(perm, model)          cache_key = 'usersession-{}-{}-{}-{}'.format(              session.session_key, perm, model.__name__ if model else 'no',              obj.pk if obj else 'no')          res = cache.get(cache_key)          if res in (True, False):              return res -        res = super(IshtarUser, self).has_perm(perm, model) +        res = self.user_ptr.has_perm(perm, model)          cache.set(cache_key, res, settings.CACHE_SMALLTIMEOUT)          return res -IshtarUser._meta.get_field('password').help_text = _( -    u"To modify the password use the form in Auth > User") -  class AuthorType(GeneralType):      order = models.IntegerField(_(u"Order"), default=1) @@ -2948,16 +3021,11 @@ class Author(models.Model):          verbose_name_plural = _(u"Authors")          ordering = ('author_type__order', 'person__name')          permissions = ( -            ("view_author", -             ugettext(u"Can view all Authors")), -            ("view_own_author", -             ugettext(u"Can view own Author")), -            ("add_own_author", -             ugettext(u"Can add own Author")), -            ("change_own_author", -             ugettext(u"Can change own Author")), -            ("delete_own_author", -             ugettext(u"Can delete own Author")), +            ("view_author", u"Can view all Authors"), +            ("view_own_author", u"Can view own Author"), +            ("add_own_author", u"Can add own Author"), +            ("change_own_author", u"Can change own Author"), +            ("delete_own_author", u"Can delete own Author"),          )      def __unicode__(self): @@ -3015,7 +3083,7 @@ class Source(OwnPerms, ImageModel, models.Model):      authors = models.ManyToManyField(Author, verbose_name=_(u"Authors"),                                       related_name="%(class)s_related")      associated_url = models.URLField( -        verify_exists=False, blank=True, null=True, +        blank=True, null=True,          verbose_name=_(u"Numerical ressource (web address)"))      receipt_date = models.DateField(blank=True, null=True,                                      verbose_name=_(u"Receipt date")) @@ -3154,7 +3222,7 @@ post_delete.connect(post_save_cache, sender=OperationType)  class SpatialReferenceSystem(GeneralType):      order = models.IntegerField(_(u"Order"), default=10)      auth_name = models.CharField( -        _(u"Authority name"), default='EPSG', max_length=256) +        _(u"Authority name"), default=u'EPSG', max_length=256)      srid = models.IntegerField(_(u"Authority SRID"))      class Meta: | 
