diff options
Diffstat (limited to 'ishtar_common/models.py')
-rw-r--r-- | ishtar_common/models.py | 1235 |
1 files changed, 290 insertions, 945 deletions
diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 7f79df72f..0c06f0b4d 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 + +__all__ = [ + 'ImporterModel', 'ImporterType', 'ImporterDefault', 'ImporterDefaultValues', + 'ImporterColumn', 'ImporterDuplicateField', 'Regexp', 'ImportTarget', + 'TargetKey', 'FormaterType', 'Import' +] 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)] @@ -738,55 +752,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 +781,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 +832,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 +855,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 +933,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 +958,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 +1039,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 +1179,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 +1198,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 +1216,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 +1413,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 +1424,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 +1572,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 +1589,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 +1613,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 +1776,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 +1855,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 +1876,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 +1916,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 +1991,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 +2083,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 +2164,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 +2181,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 +2192,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 +2218,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 +2229,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 +2294,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 +2356,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 +2377,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 +2384,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)) @@ -3156,7 +2501,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: |