diff options
Diffstat (limited to 'ishtar_common/models.py')
| -rw-r--r-- | ishtar_common/models.py | 1245 | 
1 files changed, 297 insertions, 948 deletions
diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 7f79df72f..a4fe5af7f 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -22,53 +22,54 @@ Models description  """  from cStringIO import StringIO  import copy -import csv  import datetime  from PIL import Image -from importlib import import_module  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 unicodecsv -import zipfile +import time  from django.conf import settings  from django.core.cache import cache -from django.core.exceptions import ObjectDoesNotExist, ValidationError, \ -    SuspiciousOperation -from django.core.files import File +from django.core.exceptions import ObjectDoesNotExist, ValidationError  from django.core.files.uploadedfile import SimpleUploadedFile  from django.core.validators import validate_slug  from django.core.urlresolvers import reverse, NoReverseMatch  from django.db.utils import DatabaseError  from django.db.models import Q, Max, Count -from django.db.models.base import ModelBase -from django.db.models.signals import post_save, pre_delete, post_delete +from django.db.models.signals import post_save, post_delete  from django.utils.functional import lazy -from django.utils.translation import ugettext_lazy as _, ugettext, \ -    pgettext_lazy +from django.utils.translation import ugettext_lazy as _  from django.utils.safestring import SafeUnicode, mark_safe  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 - +from ishtar_common.utils import get_cache, disable_for_loaddata, create_slug,\ +    get_all_field_names + +from ishtar_common.models_imports import ImporterModel, ImporterType, \ +    ImporterDefault, ImporterDefaultValues, ImporterColumn, \ +    ImporterDuplicateField, Regexp, ImportTarget, TargetKey, FormaterType, \ +    Import, TargetKeyGroup + +__all__ = [ +    'ImporterModel', 'ImporterType', 'ImporterDefault', 'ImporterDefaultValues', +    'ImporterColumn', 'ImporterDuplicateField', 'Regexp', 'ImportTarget', +    'TargetKey', 'FormaterType', 'Import', 'TargetKeyGroup' +]  logger = logging.getLogger(__name__) @@ -76,19 +77,12 @@ 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) @@ -130,15 +124,6 @@ def check_model_access_control(request, model, available_perms=None):      return allowed, own -class Imported(models.Model): -    imports = models.ManyToManyField( -        'Import', blank=True, null=True, -        related_name="imported_%(app_label)s_%(class)s") - -    class Meta: -        abstract = True - -  class ValueGetter(object):      _prefix = ""      GET_VALUES_EXTRA = [] @@ -148,7 +133,7 @@ class ValueGetter(object):          if not prefix:              prefix = self._prefix          values = {} -        for field_name in self._meta.get_all_field_names(): +        for field_name in get_all_field_names(self):              if not hasattr(self, field_name):                  continue              value = getattr(self, field_name) @@ -158,6 +143,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(): @@ -176,7 +170,7 @@ class ValueGetter(object):          if not prefix:              prefix = cls._prefix          values = {} -        for field_name in cls._meta.get_all_field_names(): +        for field_name in get_all_field_names(cls):              values[prefix + field_name] = ''          return values @@ -187,7 +181,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 +191,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 +205,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 +250,7 @@ def is_unique(cls, field):      return func -class OwnPerms: +class OwnPerms(object):      """      Manage special permissions for object's owner      """ @@ -315,13 +312,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()) @@ -410,6 +407,16 @@ def post_save_cache(sender, **kwargs):      sender.refresh_cache() +class SlugModelManager(models.Manager): +    def get_by_natural_key(self, slug): +        return self.get(slug=slug) + + +class TypeManager(models.Manager): +    def get_by_natural_key(self, txt_idx): +        return self.get(txt_idx=txt_idx) + +  class GeneralType(Cached, models.Model):      """      Abstract class for "types" @@ -417,18 +424,25 @@ class GeneralType(Cached, models.Model):      label = models.CharField(_(u"Label"), max_length=100)      txt_idx = models.CharField(          _(u"Textual ID"), validators=[validate_slug], max_length=100, -        unique=True) +        unique=True, +        help_text=_( +            u"The slug is the standardized version of the name. It contains " +            u"only lowercase letters, numbers and hyphens. Each slug must " +            u"be unique."))      comment = models.TextField(_(u"Comment"), blank=True, null=True)      available = models.BooleanField(_(u"Available"), default=True)      HELP_TEXT = u"" +    objects = TypeManager()      class Meta:          abstract = True -        unique_together = (('txt_idx', 'available'),)      def __unicode__(self):          return self.label +    def natural_key(self): +        return (self.txt_idx, ) +      @classmethod      def create_default_for_test(cls):          return [cls.objects.create(label='Test %d' % i) for i in range(5)] @@ -508,7 +522,7 @@ class GeneralType(Cached, models.Model):          return u""      @classmethod -    def _get_initial_types(cls, initial, type_pks): +    def _get_initial_types(cls, initial, type_pks, instance=False):          new_vals = []          if not initial:              return [] @@ -523,7 +537,10 @@ class GeneralType(Cached, models.Model):                  continue              try:                  extra_type = cls.objects.get(pk=pk) -                new_vals.append((extra_type.pk, unicode(extra_type))) +                if instance: +                    new_vals.append(extra_type) +                else: +                    new_vals.append((extra_type.pk, unicode(extra_type)))              except cls.DoesNotExist:                  continue          return new_vals @@ -738,55 +755,27 @@ class GeneralType(Cached, models.Model):              item.generate_key() -class Basket(models.Model): -    """ -    Abstract class for a basket -    Subclass must be defined with an "items" ManyToManyField -    """ -    IS_BASKET = True -    label = models.CharField(_(u"Label"), max_length=1000) -    comment = models.TextField(_(u"Comment"), blank=True, null=True) -    user = models.ForeignKey('IshtarUser', blank=True, null=True) -    available = models.BooleanField(_(u"Available"), default=True) - -    class Meta: -        abstract = True -        unique_together = (('label', 'user'),) - -    def __unicode__(self): -        return self.label - -    @property -    def cached_label(self): -        return unicode(self) - -    @classmethod -    def get_short_menu_class(cls, pk): -        return 'basket' - -    @property -    def associated_filename(self): -        return "{}-{}".format(datetime.date.today().strftime( -            "%Y-%m-%d"), slugify(self.label)) - -  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, +        Import, null=True, blank=True,          help_text=_(u"Specific key to an import"))      def __unicode__(self):          return self.key +def get_image_path(instance, filename): +    return instance._get_image_path(filename) + +  class ImageModel(models.Model): -    image = models.ImageField(upload_to="upload/", blank=True, null=True, +    image = models.ImageField(upload_to=get_image_path, blank=True, null=True,                                max_length=255) -    thumbnail = models.ImageField(upload_to='upload/thumbs/', blank=True, +    thumbnail = models.ImageField(upload_to=get_image_path, blank=True,                                    null=True, max_length=255)      IMAGE_MAX_SIZE = settings.IMAGE_MAX_SIZE      THUMB_MAX_SIZE = settings.THUMB_MAX_SIZE @@ -795,15 +784,11 @@ class ImageModel(models.Model):      class Meta:          abstract = True -    def __init__(self, *args, **kwargs): -        super(ImageModel, self).__init__(*args, **kwargs) -        image = self._meta.get_field_by_name("image")[0] -        IMAGE_PREFIX = self.IMAGE_PREFIX -        if not IMAGE_PREFIX.endswith('/'): -            IMAGE_PREFIX += u'/' -        image.upload_to = IMAGE_PREFIX -        thumbnail = self._meta.get_field_by_name("thumbnail")[0] -        thumbnail.upload_to = IMAGE_PREFIX + "thumbs/" +    def _get_image_path(self, filename): +        return u"{}/{}".format(self._get_base_image_path(), filename) + +    def _get_base_image_path(self): +        return u"upload"      def has_changed(self, field):          if not self.pk: @@ -850,8 +835,12 @@ class ImageModel(models.Model):                          pass                  # save the thumbnail +                splited = filename.split('.') +                thumb_filename = u"{}-thumb.{}".format( +                    u".".join(splited[:-1]), splited[-1] +                )                  self.thumbnail.save( -                    '_%s' % filename, +                    thumb_filename,                      self.create_thumb(image, self.THUMB_MAX_SIZE),                      save=False)              except IOError: @@ -869,6 +858,37 @@ 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 Imported(models.Model): +    imports = models.ManyToManyField( +        Import, blank=True, +        related_name="imported_%(app_label)s_%(class)s") + +    class Meta: +        abstract = True + +  class BaseHistorizedItem(Imported):      IS_BASKET = False      history_modifier = models.ForeignKey( @@ -916,8 +936,8 @@ class BaseHistorizedItem(Imported):              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] +        for k in get_all_field_names(model): +            field = model._meta.get_field(k)              if hasattr(field, 'rel') and field.rel:                  if not hasattr(item, k + '_id'):                      setattr(item, k, getattr(self, k)) @@ -941,14 +961,14 @@ class BaseHistorizedItem(Imported):      def last_edition_date(self):          try:              return self.history.order_by('-history_date').all()[0].history_date -        except IndexError: +        except (AttributeError, IndexError):              return      @property      def history_creation_date(self):          try:              return self.history.order_by('history_date').all()[0].history_date -        except IndexError: +        except (AttributeError, IndexError):              return      def rollback(self, date): @@ -1022,10 +1042,9 @@ class GeneralRelationType(GeneralType):      symmetrical = models.BooleanField(_(u"Symmetrical"))      tiny_label = models.CharField(_(u"Tiny label"), max_length=50,                                    blank=True, null=True) -    # # an inverse must be set -    # inverse_relation = models.ForeignKey( -    #    'RelationType', verbose_name=_(u"Inverse relation"), blank=True, -    #    null=True) +    inverse_relation = models.ForeignKey( +        'self', verbose_name=_(u"Inverse relation"), blank=True, +        null=True)      class Meta:          abstract = True @@ -1163,16 +1182,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 +1201,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,64 +1219,64 @@ 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 "                      u"data can be destructive."))      active = models.BooleanField(_(u"Current active"), default=False)      currency = models.CharField(_(u"Currency"), default=u"€", -                                choices=CURRENCY, max_length='5') +                                choices=CURRENCY, max_length=5)      class Meta:          verbose_name = _(u"Ishtar site profile") @@ -1397,7 +1416,8 @@ class DashboardFormItem(object):          q = cls.objects.filter(**{date_var + '__isnull': False})          if fltr:              q = q.filter(**fltr) -        return q.filter(**{date_var + '__year': year}).distinct('pk') +        return q.filter( +            **{date_var + '__year': year}).order_by('pk').distinct('pk')      @classmethod      def get_by_month(cls, year, month, fltr={}, date_source='creation'): @@ -1407,14 +1427,14 @@ class DashboardFormItem(object):              q = q.filter(**fltr)          q = q.filter(              **{date_var + '__year': year, date_var + '__month': month}) -        return q.distinct('pk') +        return q.order_by('pk').distinct('pk')      @classmethod      def get_total_number(cls, fltr={}):          q = cls.objects          if fltr:              q = q.filter(**fltr) -        return q.distinct('pk').count() +        return q.order_by('pk').distinct('pk').count()  class Dashboard(object): @@ -1555,10 +1575,14 @@ class DocumentTemplate(models.Model):      CLASSNAMES = (('archaeological_operations.models.AdministrativeAct',                    _(u"Administrative Act")),)      name = models.CharField(_(u"Name"), max_length=100) -    template = models.FileField(_(u"Template"), upload_to="upload/templates/") +    slug = models.SlugField(_(u"Slug"), blank=True, null=True, max_length=100, +                            unique=True) +    template = models.FileField( +        _(u"Template"), upload_to="templates/%Y/")      associated_object_name = models.CharField(          _(u"Associated object"), max_length=100, choices=CLASSNAMES)      available = models.BooleanField(_(u"Available"), default=True) +    objects = SlugModelManager()      class Meta:          verbose_name = _(u"Document template") @@ -1568,6 +1592,14 @@ class DocumentTemplate(models.Model):      def __unicode__(self):          return self.name +    def natural_key(self): +        return (self.slug, ) + +    def save(self, *args, **kwargs): +        if not self.slug: +            self.slug = create_slug(DocumentTemplate, self.name) +        return super(DocumentTemplate, self).save(*args, **kwargs) +      @classmethod      def get_tuples(cls, dct={}, empty_first=True):          dct['available'] = True @@ -1584,7 +1616,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 + "/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 +1779,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. @@ -1785,772 +1858,6 @@ post_delete.connect(post_save_cache, sender=OrganizationType)  organization_type_pk_lazy = lazy(OrganizationType.get_or_create_pk, unicode)  organization_type_pks_lazy = lazy(OrganizationType.get_or_create_pks, unicode) -IMPORTER_CLASSES = {} - -IMPORTER_CLASSES.update({ -    'sra-pdl-files': -    'archaeological_files.data_importer.FileImporterSraPdL'}) - - -def get_model_fields(model): -    """ -    Return a dict of fields from  model -    To be replace in Django 1.8 with get_fields, get_field -    """ -    fields = {} -    options = model._meta -    for field in sorted(options.fields + options.many_to_many): -        fields[field.name] = field -    if hasattr(model, 'get_extra_fields'): -        fields.update(model.get_extra_fields()) -    return fields - - -def import_class(full_path_classname): -    """ -    Return the model class from the full path -    TODO: add a white list for more security -    """ -    mods = full_path_classname.split('.') -    if len(mods) == 1: -        mods = ['ishtar_common', 'models', mods[0]] -    elif 'models' not in mods and 'models_finds' not in mods \ -            and 'models_treatments' not in mods: -        raise SuspiciousOperation( -            u"Try to import a non model from a string") -    module = import_module('.'.join(mods[:-1])) -    return getattr(module, mods[-1]) - - -class ImporterModel(models.Model): -    name = models.CharField(_(u"Name"), max_length=200) -    klass = models.CharField(_(u"Class name"), max_length=200) - -    class Meta: -        verbose_name = _(u"Importer - Model") -        verbose_name_plural = _(u"Importer - Models") -        ordering = ('name',) - -    def __unicode__(self): -        return self.name - - -class ImporterType(models.Model): -    """ -    Description of a table to be mapped with ishtar database -    """ -    name = models.CharField(_(u"Name"), blank=True, null=True, -                            max_length=100) -    slug = models.SlugField(_(u"Slug"), unique=True, blank=True, null=True, -                            max_length=100) -    description = models.CharField(_(u"Description"), blank=True, null=True, -                                   max_length=500) -    users = models.ManyToManyField('IshtarUser', verbose_name=_(u"Users"), -                                   blank=True, null=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"), -        related_name='+') -    is_template = models.BooleanField(_(u"Is template"), default=False) -    unicity_keys = models.CharField(_(u"Unicity keys (separator \";\")"), -                                    blank=True, null=True, max_length=500) - -    class Meta: -        verbose_name = _(u"Importer - Type") -        verbose_name_plural = _(u"Importer - Types") -        ordering = ('name',) - -    def __unicode__(self): -        return self.name - -    def get_importer_class(self, import_instance=None): -        if self.slug and self.slug in IMPORTER_CLASSES: -            cls = import_class(IMPORTER_CLASSES[self.slug]) -            return cls -        OBJECT_CLS = import_class(self.associated_models.klass) -        DEFAULTS = dict([(default.keys, default.values) -                         for default in self.defaults.all()]) -        LINE_FORMAT = [] -        idx = 0 -        for column in self.columns.order_by('col_number').all(): -            idx += 1 -            while column.col_number > idx: -                LINE_FORMAT.append(None) -                idx += 1 -            targets = [] -            formater_types = [] -            nb = column.targets.count() -            if not nb: -                LINE_FORMAT.append(None) -                continue -            force_news = [] -            concat_str = [] -            for target in column.targets.all(): -                ft = target.formater_type.get_formater_type( -                    target, import_instance=import_instance) -                if not ft: -                    continue -                formater_types.append(ft) -                targets.append(target.target) -                concat_str.append(target.concat_str) -                force_news.append(target.force_new) -            formater_kwargs = {} -            if column.regexp_pre_filter: -                formater_kwargs['regexp'] = re.compile( -                    column.regexp_pre_filter.regexp) -            formater_kwargs['concat_str'] = concat_str -            formater_kwargs['duplicate_fields'] = [ -                (field.field_name, field.force_new, field.concat, -                 field.concat_str) -                for field in column.duplicate_fields.all()] -            formater_kwargs['label'] = column.label -            formater_kwargs['required'] = column.required -            formater_kwargs['force_new'] = force_news -            if column.export_field_name: -                formater_kwargs['export_field_name'] = [ -                    column.export_field_name] -            formater = ImportFormater(targets, formater_types, -                                      **formater_kwargs) -            LINE_FORMAT.append(formater) -        UNICITY_KEYS = [] -        if self.unicity_keys: -            UNICITY_KEYS = [un.strip() for un in self.unicity_keys.split(';')] -        MODEL_CREATION_LIMIT = [] -        for modls in self.created_models.all(): -            MODEL_CREATION_LIMIT.append(import_class(modls.klass)) -        args = {'OBJECT_CLS': OBJECT_CLS, 'DESC': self.description, -                'DEFAULTS': DEFAULTS, 'LINE_FORMAT': LINE_FORMAT, -                'UNICITY_KEYS': UNICITY_KEYS, -                'MODEL_CREATION_LIMIT': MODEL_CREATION_LIMIT} -        name = str(''.join( -            x for x in slugify(self.name).replace('-', ' ').title() -            if not x.isspace())) -        newclass = type(name, (Importer,), args) -        return newclass - - -def get_associated_model(parent_model, keys): -    model = None -    if isinstance(parent_model, unicode) or \ -       isinstance(parent_model, str): -        OBJECT_CLS = import_class(parent_model) -    else: -        OBJECT_CLS = parent_model -    for idx, item in enumerate(keys): -        if not idx: -            field = get_model_fields(OBJECT_CLS)[item] -            if hasattr(field, 'rel') and hasattr(field.rel, 'to'): -                model = field.rel.to -            if type(field) == ModelBase: -                model = field -        else: -            return get_associated_model(model, keys[1:]) -    return model - - -class ImporterDefault(models.Model): -    """ -    Targets of default values in an import -    """ -    importer_type = models.ForeignKey(ImporterType, related_name='defaults') -    target = models.CharField(u"Target", max_length=500) - -    class Meta: -        verbose_name = _(u"Importer - Default") -        verbose_name_plural = _(u"Importer - Defaults") - -    def __unicode__(self): -        return u"{} - {}".format(self.importer_type, self.target) - -    @property -    def keys(self): -        return tuple(self.target.split('__')) - -    @property -    def associated_model(self): -        return get_associated_model(self.importer_type.associated_models.klass, -                                    self.keys) - -    @property -    def values(self): -        values = {} -        for default_value in self.default_values.all(): -            values[default_value.target] = default_value.get_value() -        return values - - -class ImporterDefaultValues(models.Model): -    """ -    Default values in an import -    """ -    default_target = models.ForeignKey(ImporterDefault, -                                       related_name='default_values') -    target = models.CharField(u"Target", max_length=500) -    value = models.CharField(u"Value", max_length=500) - -    def __unicode__(self): -        return u"{} - {}".format(self.default_target, self.target, self.value) - -    class Meta: -        verbose_name = _(u"Importer - Default value") -        verbose_name_plural = _(u"Importer - Default values") - -    def get_value(self): -        parent_model = self.default_target.associated_model -        if not parent_model: -            return self.value -        fields = get_model_fields(parent_model) -        target = self.target.strip() -        if target not in fields: -            return -        field = fields[target] -        if not hasattr(field, 'rel') or not hasattr(field.rel, 'to'): -            return -        model = field.rel.to -        # if value is an id -        try: -            return model.objects.get(pk=int(self.value)) -        except (ValueError, model.DoesNotExist): -            pass -        # try with txt_idx -        try: -            return model.objects.get(txt_idx=self.value) -        except (ValueError, model.DoesNotExist): -            pass -        return "" - - -class ImporterColumn(models.Model): -    """ -    Import file column description -    """ -    label = models.CharField(_(u"Label"), blank=True, null=True, -                             max_length=200) -    importer_type = models.ForeignKey(ImporterType, related_name='columns') -    col_number = models.IntegerField(_(u"Column number"), default=1) -    description = models.TextField(_("Description"), blank=True, null=True) -    regexp_pre_filter = models.ForeignKey("Regexp", blank=True, null=True) -    required = models.BooleanField(_(u"Required"), default=False) -    export_field_name = models.CharField( -        _(u"Export field name"), blank=True, null=True, max_length=200, -        help_text=_(u"Fill this field if the field name is ambiguous for " -                    u"export. For instance: concatenated fields.") -    ) - -    class Meta: -        verbose_name = _(u"Importer - Column") -        verbose_name_plural = _(u"Importer - Columns") -        ordering = ('importer_type', 'col_number') -        unique_together = ('importer_type', 'col_number') - -    def __unicode__(self): -        return u"{} - {}".format(self.importer_type, self.col_number) - -    def targets_lbl(self): -        return u', '.join([target.target for target in self.targets.all()]) - -    def duplicate_fields_lbl(self): -        return u', '.join([dp.field_name -                           for dp in self.duplicate_fields.all()]) - - -class ImporterDuplicateField(models.Model): -    """ -    Direct copy of result in other fields -    """ -    column = models.ForeignKey(ImporterColumn, related_name='duplicate_fields') -    field_name = models.CharField(_(u"Field name"), blank=True, null=True, -                                  max_length=200) -    force_new = models.BooleanField(_(u"Force creation of new items"), -                                    default=False) -    concat = models.BooleanField(_(u"Concatenate with existing"), -                                 default=False) -    concat_str = models.CharField(_(u"Concatenate character"), max_length=5, -                                  blank=True, null=True) - -    class Meta: -        verbose_name = _(u"Importer - Duplicate field") -        verbose_name_plural = _(u"Importer - Duplicate fields") - - -class Regexp(models.Model): -    name = models.CharField(_(u"Name"), max_length=100) -    description = models.CharField(_(u"Description"), blank=True, null=True, -                                   max_length=500) -    regexp = models.CharField(_(u"Regular expression"), max_length=500) - -    class Meta: -        verbose_name = _(u"Importer - Regular expression") -        verbose_name_plural = _(u"Importer - Regular expressions") - -    def __unicode__(self): -        return self.name - - -class ImportTarget(models.Model): -    """ -    Ishtar database target for a column -    """ -    column = models.ForeignKey(ImporterColumn, related_name='targets') -    target = models.CharField(u"Target", max_length=500) -    regexp_filter = models.ForeignKey("Regexp", blank=True, null=True) -    formater_type = models.ForeignKey("FormaterType") -    force_new = models.BooleanField(_(u"Force creation of new items"), -                                    default=False) -    concat = models.BooleanField(_(u"Concatenate with existing"), -                                 default=False) -    concat_str = models.CharField(_(u"Concatenate character"), max_length=5, -                                  blank=True, null=True) -    comment = models.TextField(_(u"Comment"), blank=True, null=True) - -    class Meta: -        verbose_name = _(u"Importer - Target") -        verbose_name_plural = _(u"Importer - Targets") - -    def __unicode__(self): -        return self.target[:50] if self.target else self.comment - -    @property -    def associated_model(self): -        try: -            return get_associated_model( -                self.column.importer_type.associated_models.klass, -                self.target.split('__')) -        except KeyError: -            return - -    def get_choices(self): -        if self.formater_type.formater_type == 'UnknowType' \ -                and self.column.importer_type.slug: -            cls = self.column.importer_type.get_importer_class() -            formt = cls().line_format[self.column.col_number - 1] -            if hasattr(formt.formater, 'choices'): -                return [('', '--' * 8)] + list(formt.formater.choices) -            return [('', '--' * 8)] -        if self.formater_type.formater_type == 'StrToBoolean': -            return [('', '--' * 8), -                    ('True', _(u"True")), -                    ('False', _(u"False"))] -        if not self.associated_model or not hasattr(self.associated_model, -                                                    'get_types'): -            return [] -        return self.associated_model.get_types() - - -class TargetKey(models.Model): -    """ -    User's link between import source and ishtar database. -    Also temporary used for GeneralType to point missing link before adding -    them in ItemKey table. -    A targetkey connection can be create to be applied to on particular -    import (associated_import), one particular user (associated_user) or to all -    imports (associated_import and associated_user are empty). -    """ -    target = models.ForeignKey(ImportTarget, related_name='keys') -    key = models.TextField(_(u"Key")) -    value = models.TextField(_(u"Value"), blank=True, null=True) -    is_set = models.BooleanField(_(u"Is set"), default=False) -    associated_import = models.ForeignKey('Import', blank=True, null=True) -    associated_user = models.ForeignKey('IshtarUser', blank=True, null=True) - -    class Meta: -        unique_together = ('target', 'key', 'associated_user', -                           'associated_import') -        verbose_name = _(u"Importer - Target key") -        verbose_name_plural = _(u"Importer - Targets keys") - -    def __unicode__(self): -        return u" - ".join([unicode(self.target), self.key[:50]]) - -    def column_nb(self): -        # for the admin -        return self.target.column.col_number - -    def importer_type(self): -        # for the admin -        return self.target.column.importer_type.name - -    def format(self): -        if not self.is_set: -            return None -        if self.target.formater_type.formater_type == 'StrToBoolean': -            if self.value in ('False', '0'): -                return False -            elif self.value: -                return True -            return -        return self.value - -    def save(self, *args, **kwargs): -        obj = super(TargetKey, self).save(*args, **kwargs) -        if not self.value: -            return obj -        associated_model = self.target.associated_model -        if associated_model and hasattr(self.target.associated_model, -                                        "add_key"): -            v = None -            # pk is given -            try: -                v = self.target.associated_model.objects.get( -                    pk=unicode(int(self.value))) -            except (ValueError, self.target.associated_model.DoesNotExist): -                # try with txt_idx -                try: -                    v = self.target.associated_model.objects.get( -                        txt_idx=unicode(self.value)) -                except self.target.associated_model.DoesNotExist: -                    pass -            if v: -                v.add_key(self.key, importer=self.associated_import) -        return obj - -TARGET_MODELS = [ -    ('OrganizationType', _(u"Organization type")), -    ('TitleType', _(u"Title")), -    ('SourceType', _(u"Source type")), -    ('AuthorType', _(u"Author type")), -    ('Format', _(u"Format")), -    ('archaeological_operations.models.OperationType', _(u"Operation type")), -    ('archaeological_operations.models.Period', _(u"Period")), -    ('archaeological_operations.models.ReportState', _(u"Report state")), -    ('archaeological_operations.models.RemainType', _(u"Remain type")), -    ('archaeological_context_records.models.Unit', _(u"Unit")), -    ('archaeological_context_records.models.ActivityType', -     _(u"Activity type")), -    ('archaeological_context_records.models.DocumentationType', -     _(u"Documentation type")), -    ('archaeological_finds.models.MaterialType', _(u"Material")), -    ('archaeological_finds.models.ConservatoryState', -     _(u"Conservatory state")), -    ('archaeological_warehouse.models.ContainerType', _(u"Container type")), -    ('archaeological_finds.models.PreservationType', _(u"Preservation type")), -    ('archaeological_finds.models.ObjectType', _(u"Object type")), -    ('archaeological_finds.models.IntegrityType', _(u"Integrity type")), -    ('archaeological_finds.models.RemarkabilityType', -     _(u"Remarkability type")), -    ('archaeological_finds.models.BatchType', _(u"Batch type")), -    ('archaeological_context_records.models.IdentificationType', -     _("Identification type")), -    ('archaeological_context_records.models.RelationType', -     _(u"Context record relation type")), -    ('SpatialReferenceSystem', _(u"Spatial reference system")), -    ('SupportType', _(u"Support type")), -    ('TitleType', _(u"Title type")), -] - -TARGET_MODELS_KEYS = [tm[0] for tm in TARGET_MODELS] - -IMPORTER_TYPES = ( -    ('IntegerFormater', _(u"Integer")), -    ('FloatFormater', _(u"Float")), -    ('UnicodeFormater', _(u"String")), -    ('DateFormater', _(u"Date")), -    ('TypeFormater', _(u"Type")), -    ('YearFormater', _(u"Year")), -    ('StrToBoolean', _(u"String to boolean")), -    ('FileFormater', pgettext_lazy("filesystem", u"File")), -    ('UnknowType', _(u"Unknow type")) -) - -IMPORTER_TYPES_DCT = { -    'IntegerFormater': IntegerFormater, -    'FloatFormater': FloatFormater, -    'UnicodeFormater': UnicodeFormater, -    'DateFormater': DateFormater, -    'TypeFormater': TypeFormater, -    'YearFormater': YearFormater, -    'StrToBoolean': StrToBoolean, -    'FileFormater': FileFormater, -    'UnknowType': None, -} - -DATE_FORMATS = ( -    ('%Y', _(u"4 digit year. e.g.: \"2015\"")), -    ('%Y/%m/%d', _(u"4 digit year/month/day. e.g.: \"2015/02/04\"")), -    ('%d/%m/%Y', _(u"Day/month/4 digit year. e.g.: \"04/02/2015\"")), -) - -IMPORTER_TYPES_CHOICES = {'TypeFormater': TARGET_MODELS, -                          'DateFormater': DATE_FORMATS} - - -class FormaterType(models.Model): -    formater_type = models.CharField(u"Formater type", max_length=20, -                                     choices=IMPORTER_TYPES) -    options = models.CharField(_(u"Options"), max_length=500, blank=True, -                               null=True) -    many_split = models.CharField(_(u"Split character(s)"), max_length=10, -                                  blank=True, null=True) - -    class Meta: -        verbose_name = _(u"Importer - Formater type") -        verbose_name_plural = _(u"Importer - Formater types") -        unique_together = ('formater_type', 'options', 'many_split') -        ordering = ('formater_type', 'options') - -    def __unicode__(self): -        return u" - ".join( -            [unicode(dict(IMPORTER_TYPES)[self.formater_type]) -             if self.formater_type in IMPORTER_TYPES_DCT else ''] + -            [getattr(self, k) for k in ('options', 'many_split') -             if getattr(self, k)]) - -    def get_choices(self): -        if self.format_type in IMPORTER_TYPES_CHOICES: -            return IMPORTER_TYPES_CHOICES[self.format_type] - -    def get_formater_type(self, target, import_instance=None): -        if self.formater_type not in IMPORTER_TYPES_DCT.keys(): -            return -        kwargs = {'db_target': target, 'import_instance': import_instance} -        if self.many_split: -            kwargs['many_split'] = self.many_split -        if self.formater_type == 'TypeFormater': -            if self.options not in TARGET_MODELS_KEYS: -                logger.warning( -                    "**WARN FormaterType.get_formater_type**: {} " -                    "is not in TARGET_MODELS_KEYS".format(self.options)) -                return -            model = None -            if self.options in dir(): -                model = dir()[self.options] -            else: -                model = import_class(self.options) -            return TypeFormater(model, **kwargs) -        elif self.formater_type == 'UnicodeFormater': -            if self.options: -                try: -                    return UnicodeFormater(int(self.options.strip()), **kwargs) -                except ValueError: -                    pass -            return UnicodeFormater(**kwargs) -        elif self.formater_type == 'DateFormater': -            date_formats = self.options -            if self.many_split: -                date_formats = self.options.split(kwargs.pop('many_split')) -            return DateFormater(date_formats, **kwargs) -        elif self.formater_type == 'StrToBoolean': -            return StrToBoolean(**kwargs) -        elif self.formater_type == 'UnknowType': -            return -        else: -            return IMPORTER_TYPES_DCT[self.formater_type](**kwargs) - -IMPORT_STATE = (("C", _(u"Created")), -                ("AP", _(u"Analyse in progress")), -                ("A", _(u"Analysed")), -                ("P", _(u"Import pending")), -                ("IP", _(u"Import in progress")), -                ("FE", _(u"Finished with errors")), -                ("F", _(u"Finished")), -                ("AC", _(u"Archived")), -                ) - -IMPORT_STATE_DCT = dict(IMPORT_STATE) -ENCODINGS = [(settings.ENCODING, settings.ENCODING), -             (settings.ALT_ENCODING, settings.ALT_ENCODING), -             ('utf-8', 'utf-8')] - - -class Import(models.Model): -    user = models.ForeignKey('IshtarUser') -    name = models.CharField(_(u"Name"), max_length=500, -                            blank=True, null=True) -    importer_type = models.ForeignKey(ImporterType) -    imported_file = models.FileField( -        _(u"Imported file"), upload_to="upload/imports/", max_length=220) -    imported_images = models.FileField( -        _(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) -    skip_lines = models.IntegerField(_(u"Skip lines"), default=1) -    error_file = models.FileField(_(u"Error file"), -                                  upload_to="upload/imports/", -                                  blank=True, null=True, max_length=255) -    result_file = models.FileField(_(u"Result file"), -                                   upload_to="upload/imports/", -                                   blank=True, null=True, max_length=255) -    match_file = models.FileField(_(u"Match file"), -                                  upload_to="upload/imports/", -                                  blank=True, null=True, max_length=255) -    state = models.CharField(_(u"State"), max_length=2, choices=IMPORT_STATE, -                             default='C') -    conservative_import = models.BooleanField( -        _(u"Conservative import"), default=False, -        help_text='If set to true, do not overload existing values') -    creation_date = models.DateTimeField( -        _(u"Creation date"), auto_now_add=True, blank=True, null=True) -    end_date = models.DateTimeField(_(u"End date"), blank=True, -                                    null=True, editable=False) -    seconds_remaining = models.IntegerField( -        _(u"Remaining seconds"), blank=True, null=True, editable=False) - -    class Meta: -        verbose_name = _(u"Import") -        verbose_name_plural = _(u"Imports") - -    def __unicode__(self): -        return u"{} | {}".format(self.name or u"-", self.importer_type) - -    def need_matching(self): -        return bool(TargetKey.objects.filter(associated_import=self, -                                             is_set=False).count()) - -    @property -    def errors(self): -        if not self.error_file: -            return [] -        errors = [] -        with open(self.error_file.path, 'rb') as csvfile: -            reader = csv.DictReader(csvfile, fieldnames=['line', 'column', -                                                         'error']) -            reader.next()  # pass the header -            for row in reader: -                errors.append(row) -        return errors - -    def get_actions(self): -        """ -        Get available action relevant with the current status -        """ -        actions = [] -        if self.state == 'C': -            actions.append(('A', _(u"Analyse"))) -        if self.state == 'A': -            actions.append(('A', _(u"Re-analyse"))) -            actions.append(('I', _(u"Launch import"))) -        if self.state in ('F', 'FE'): -            actions.append(('A', _(u"Re-analyse"))) -            actions.append(('I', _(u"Re-import"))) -            actions.append(('AC', _(u"Archive"))) -        if self.state == 'AC': -            actions.append(('A', _(u"Unarchive"))) -        actions.append(('D', _(u"Delete"))) -        return actions - -    @property -    def imported_filename(self): -        return self.imported_file.name.split(os.sep)[-1] - -    @property -    def status(self): -        if self.state not in IMPORT_STATE_DCT: -            return "" -        return IMPORT_STATE_DCT[self.state] - -    def get_importer_instance(self): -        return self.importer_type.get_importer_class(import_instance=self)( -            skip_lines=self.skip_lines, import_instance=self, -            conservative_import=self.conservative_import) - -    @property -    def data_table(self): -        imported_file = self.imported_file.path -        tmpdir = None -        if zipfile.is_zipfile(imported_file): -            z = zipfile.ZipFile(imported_file) -            filename = None -            for name in z.namelist(): -                # get first CSV file found -                if name.endswith('.csv'): -                    filename = name -                    break -            if not filename: -                return [] -            tmpdir = tempfile.mkdtemp(prefix='tmp-ishtar-') -            imported_file = z.extract(filename, tmpdir) - -        encodings = [self.encoding] -        encodings += [coding for coding, c in ENCODINGS -                      if coding != self.encoding] -        for encoding in encodings: -            try: -                with open(imported_file) as csv_file: -                    vals = [line -                            for line in unicodecsv.reader(csv_file, -                                                          encoding=encoding)] -                    if tmpdir: -                        shutil.rmtree(tmpdir) -                    return vals -            except UnicodeDecodeError: -                pass  # try the next encoding -        if tmpdir: -            shutil.rmtree(tmpdir) -        return [] - -    def initialize(self): -        self.state = 'AP' -        self.save() -        self.get_importer_instance().initialize(self.data_table, output='db') -        self.state = 'A' -        self.save() - -    def importation(self): -        self.state = 'IP' -        self.save() -        importer = self.get_importer_instance() -        importer.importation(self.data_table) -        # result file -        filename = slugify(self.importer_type.name) -        now = datetime.datetime.now().isoformat('-').replace(':', '') -        result_file = filename + "_result_%s.csv" % now -        result_file = os.sep.join([self.result_file.storage.location, -                                   result_file]) -        with open(result_file, 'w') as fle: -            fle.write(importer.get_csv_result().encode('utf-8')) -        self.result_file = File(open(fle.name)) -        if importer.errors: -            self.state = 'FE' -            error_file = filename + "_errors_%s.csv" % now -            error_file = os.sep.join([self.error_file.storage.location, -                                      error_file]) -            with open(error_file, 'w') as fle: -                fle.write(importer.get_csv_errors().encode('utf-8')) -            self.error_file = File(open(fle.name)) -        else: -            self.state = 'F' -            self.error_file = None -        if importer.match_table: -            match_file = filename + "_match_%s.csv" % now -            match_file = os.sep.join([self.match_file.storage.location, -                                      match_file]) -            with open(match_file, 'w') as fle: -                fle.write(importer.get_csv_matches().encode('utf-8')) -            self.match_file = File(open(fle.name)) -        self.save() - -    def archive(self): -        self.state = 'AC' -        self.save() - -    def get_all_imported(self): -        imported = [] -        for related, zorg in \ -                self._meta.get_all_related_m2m_objects_with_model(): -            accessor = related.get_accessor_name() -            imported += [(accessor, obj) -                         for obj in getattr(self, accessor).all()] -        return imported - - -def pre_delete_import(sender, **kwargs): -    # deleted imported items when an import is delete -    instance = kwargs.get('instance') -    if not instance: -        return -    to_delete = [] -    for accessor, imported in instance.get_all_imported(): -        to_delete.append(imported) -    for item in to_delete: -        item.delete() - - -pre_delete.connect(pre_delete_import, sender=Import) -  class Organization(Address, Merge, OwnPerms, ValueGetter):      TABLE_COLS = ('name', 'organization_type', 'town') @@ -2572,13 +1879,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): @@ -2614,7 +1919,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") @@ -2689,11 +1994,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 @@ -2781,24 +2086,24 @@ 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 \ +            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) +            cache.set(cache_key, res, settings.CACHE_TIMEOUT)          return res      def full_label(self): @@ -2862,7 +2167,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') @@ -2879,8 +2184,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) @@ -2888,6 +2195,23 @@ class IshtarUser(User):          verbose_name = _(u"Ishtar user")          verbose_name_plural = _(u"Ishtar users") +    def __unicode__(self): +        return unicode(self.person) + +    @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 @@ -2897,13 +2221,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): @@ -2914,19 +2232,48 @@ 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 Basket(models.Model): +    """ +    Abstract class for a basket +    Subclass must be defined with an "items" ManyToManyField +    """ +    IS_BASKET = True +    label = models.CharField(_(u"Label"), max_length=1000) +    comment = models.TextField(_(u"Comment"), blank=True, null=True) +    user = models.ForeignKey(IshtarUser, blank=True, null=True) +    available = models.BooleanField(_(u"Available"), default=True) + +    class Meta: +        abstract = True +        unique_together = (('label', 'user'),) + +    def __unicode__(self): +        return self.label + +    @property +    def cached_label(self): +        return unicode(self) + +    @classmethod +    def get_short_menu_class(cls, pk): +        return 'basket' + +    @property +    def associated_filename(self): +        return "{}-{}".format(datetime.date.today().strftime( +            "%Y-%m-%d"), slugify(self.label))  class AuthorType(GeneralType): @@ -2950,16 +2297,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): @@ -3017,7 +2359,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")) @@ -3038,7 +2380,6 @@ class Source(OwnPerms, ImageModel, models.Model):      duplicate = models.BooleanField(_(u"Has a duplicate"), default=False)      TABLE_COLS = ['title', 'source_type', 'authors', 'associated_url']      COL_LINK = ['associated_url'] -    IMAGE_PREFIX = 'sources'      class Meta:          abstract = True @@ -3046,6 +2387,13 @@ class Source(OwnPerms, ImageModel, models.Model):      def __unicode__(self):          return self.title +    def get_associated_operation(self): +        raise NotImplementedError() + +    def _get_base_image_path(self): +        base = self.owner._get_base_image_path() +        return u"{}/sources".format(base) +      @property      def associated_filename(self):          values = [unicode(getattr(self, attr)) @@ -3126,7 +2474,8 @@ class OperationType(GeneralType):              items = items.exclude(txt_idx__in=exclude)          current_preventive, current_lst = None, None          item_list = list(items.order_by(*cls._meta.ordering).all()) -        new_vals = cls._get_initial_types(initial, [i.pk for i in item_list]) +        new_vals = cls._get_initial_types(initial, [i.pk for i in item_list], +                                          instance=True)          item_list += new_vals          for item in item_list:              if not current_lst or item.preventive != current_preventive: @@ -3156,7 +2505,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:  | 
