diff options
Diffstat (limited to 'ishtar_common')
| -rw-r--r-- | ishtar_common/data_importer.py | 35 | ||||
| -rw-r--r-- | ishtar_common/forms_common.py | 13 | ||||
| -rw-r--r-- | ishtar_common/ishtar_menu.py | 17 | ||||
| -rw-r--r-- | ishtar_common/migrations/0027_auto__chg_field_targetkey_target.py | 313 | ||||
| -rw-r--r-- | ishtar_common/models.py | 96 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/form.html | 13 | ||||
| -rw-r--r-- | ishtar_common/templates/ishtar/import_list.html | 47 | ||||
| -rw-r--r-- | ishtar_common/urls.py | 5 | ||||
| -rw-r--r-- | ishtar_common/views.py | 69 | 
9 files changed, 556 insertions, 52 deletions
| diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py index 194a9a5fa..cef98789e 100644 --- a/ishtar_common/data_importer.py +++ b/ishtar_common/data_importer.py @@ -21,7 +21,7 @@ import copy, csv, datetime, logging, re, sys  from tempfile import NamedTemporaryFile  from django.contrib.auth.models import User -from django.db import DatabaseError, IntegrityError +from django.db import DatabaseError, IntegrityError, transaction  from django.template.defaultfilters import slugify  from django.utils.translation import ugettext_lazy as _ @@ -275,12 +275,16 @@ class StrChoiceFormater(Formater):              else:                  self.equiv_dict[value] = None          if output == 'db' and self.db_target: -            for missing in missings: -                try: -                    q = {'target':self.db_target, 'value':missing} -                    models.TargetKey.objects.create(**q) -                except IntegrityError: -                    pass +            from ishtar_common.models import TargetKey +            for missing in self.missings: +                q = {'target':self.db_target, 'value':missing} +                if TargetKey.objects.filter(**q).count(): +                    continue +                with transaction.commit_on_success(): +                    try: +                        TargetKey.objects.create(**q) +                    except IntegrityError: +                        pass      def new(self, value):          return @@ -402,7 +406,8 @@ class StrToBoolean(Formater):              else:                  self.dct[value] = None          if output == 'db' and self.db_target: -            for missing in missings: +            from ishtar_common.models import TargetKey +            for missing in self.missings:                  try:                      q = {'target':self.db_target, 'value':missing}                      models.TargetKey.objects.create(**q) @@ -439,7 +444,7 @@ class Importer(object):      def __init__(self, skip_lines=0, reference_header=None,               check_col_num=False, test=False, check_validity=True,               history_modifier=None, output='silent', -             importer_instance=None): +             import_instance=None):          """           * skip_line must be set if the data provided has got headers lines.           * a reference_header can be provided to perform a data compliance @@ -458,14 +463,13 @@ class Importer(object):          self.check_col_num = check_col_num          self.check_validity = check_validity          self.line_format = copy.copy(self.LINE_FORMAT) -        self.importer_instance = importer_instance -        self._initialized = False +        self.import_instance = import_instance          self._defaults = self.DEFAULTS.copy()          self.history_modifier = history_modifier          self.output = output          if not self.history_modifier: -            if self.importer_instance: -                self.history_modifier = self.importer_instance.user +            if self.import_instance: +                self.history_modifier = self.import_instance.user              else:                  # import made by the CLI: get the first admin                  self.history_modifier = User.objects.filter( @@ -494,11 +498,10 @@ class Importer(object):          for idx, formater in enumerate(self.line_format):              if formater:                  formater.init(vals[idx], output) -        self._initialized = True -    def importation(self, table): +    def importation(self, table, initialize=True):          self.validity_file = None -        if not self._initialized: +        if initialize:              self.initialize(table, self.output)          if self.check_validity:              with NamedTemporaryFile(delete=False) as validity_file: diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py index a229fe319..b6971c4f4 100644 --- a/ishtar_common/forms_common.py +++ b/ishtar_common/forms_common.py @@ -1,6 +1,6 @@  #!/usr/bin/env python  # -*- coding: utf-8 -*- -# Copyright (C) 2010-2013  Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2010-2015  Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet>  # This program is free software: you can redistribute it and/or modify  # it under the terms of the GNU Affero General Public License as @@ -27,8 +27,8 @@ from django.conf import settings  from django.contrib.auth.models import User  from django.contrib.sites.models import Site  from django.core import validators +from django.core.exceptions import ObjectDoesNotExist, ValidationError  from django.core.mail import send_mail -from django.core.exceptions import ObjectDoesNotExist  from django.forms.formsets import formset_factory, DELETION_FIELD_NAME  from django.forms.models import BaseModelFormSet  from django.template import Context, RequestContext, loader @@ -89,6 +89,15 @@ class NewItemForm(forms.Form):                  if len(new_choices) == 1:                      self.fields[key].initial = [new_choices[0][0]] +class NewImportForm(forms.ModelForm): +    class Meta: +        model = models.Import +        fields = ('importer_type', 'imported_file', 'skip_lines') + +    def save(self, user, commit=True): +        self.instance.user = user +        return super(NewImportForm, self).save(commit) +  class OrganizationForm(NewItemForm):      form_label = _(u"Organization")      associated_models = {'organization_type':models.OrganizationType} diff --git a/ishtar_common/ishtar_menu.py b/ishtar_common/ishtar_menu.py index 1dd22bd8a..fdfe60448 100644 --- a/ishtar_common/ishtar_menu.py +++ b/ishtar_common/ishtar_menu.py @@ -1,6 +1,6 @@  #!/usr/bin/env python  # -*- coding: utf-8 -*- -# Copyright (C) 2010-2013 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet> +# Copyright (C) 2010-2015 Étienne Loks  <etienne.loks_AT_peacefrogsDOTnet>  # This program is free software: you can redistribute it and/or modify  # it under the terms of the GNU Affero General Public License as @@ -71,6 +71,19 @@ MENU_SECTIONS = [                                           'change_own_organization']),                      ]),              ]) -    ) +    ), +    (15, SectionItem('imports', _(u"Imports"), +            childs=[ +                MenuItem('import', _(u"New import"), +                    model=models.Import, +                    access_controls=['change_import']), +                MenuItem('import-list', _(u"Current imports"), +                    model=models.Import, +                    access_controls=['change_import']), +                MenuItem('import-list-old', _(u"Old imports"), +                    model=models.Import, +                    access_controls=['change_import']), +            ]) +    ),  ] diff --git a/ishtar_common/migrations/0027_auto__chg_field_targetkey_target.py b/ishtar_common/migrations/0027_auto__chg_field_targetkey_target.py new file mode 100644 index 000000000..d6ea7e10a --- /dev/null +++ b/ishtar_common/migrations/0027_auto__chg_field_targetkey_target.py @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + +    def forwards(self, orm): + +        # Changing field 'TargetKey.target' +        db.alter_column('ishtar_common_targetkey', 'target_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.ImportTarget'])) + +    def backwards(self, orm): + +        # Changing field 'TargetKey.target' +        db.alter_column('ishtar_common_targetkey', 'target_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['ishtar_common.ImporterColumn'])) + +    models = { +        'auth.group': { +            'Meta': {'object_name': 'Group'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), +            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) +        }, +        'auth.permission': { +            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, +            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) +        }, +        'auth.user': { +            'Meta': {'object_name': 'User'}, +            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), +            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), +            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), +            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), +            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), +            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), +            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'contenttypes.contenttype': { +            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, +            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) +        }, +        'ishtar_common.arrondissement': { +            'Meta': {'object_name': 'Arrondissement'}, +            'department': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Department']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) +        }, +        'ishtar_common.author': { +            'Meta': {'object_name': 'Author'}, +            'author_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.AuthorType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'author'", 'to': "orm['ishtar_common.Person']"}) +        }, +        'ishtar_common.authortype': { +            'Meta': {'object_name': 'AuthorType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.canton': { +            'Meta': {'object_name': 'Canton'}, +            'arrondissement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Arrondissement']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '30'}) +        }, +        'ishtar_common.department': { +            'Meta': {'ordering': "['number']", 'object_name': 'Department'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '30'}), +            'number': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '3'}) +        }, +        'ishtar_common.documenttemplate': { +            'Meta': {'ordering': "['associated_object_name', 'name']", 'object_name': 'DocumentTemplate'}, +            'associated_object_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'template': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}) +        }, +        'ishtar_common.format': { +            'Meta': {'object_name': 'Format'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.formatertype': { +            'Meta': {'unique_together': "(('formater_type', 'options', 'many_split'),)", 'object_name': 'FormaterType'}, +            'formater_type': ('django.db.models.fields.CharField', [], {'max_length': '20'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'many_split': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'options': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.globalvar': { +            'Meta': {'ordering': "['slug']", 'object_name': 'GlobalVar'}, +            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), +            'value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.historicalorganization': { +            'Meta': {'ordering': "('-history_date', '-history_id')", 'object_name': 'HistoricalOrganization'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'history_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), +            'history_id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'history_modifier_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'history_type': ('django.db.models.fields.CharField', [], {'max_length': '1'}), +            'history_user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), +            'id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'blank': 'True'}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '300'}), +            'organization_type_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.import': { +            'Meta': {'object_name': 'Import'}, +            'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), +            'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), +            'error_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'imported_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.ImporterType']"}), +            'result_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'seconds_remaining': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), +            'skip_lines': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'state': ('django.db.models.fields.CharField', [], {'default': "'C'", 'max_length': '2'}), +            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.IshtarUser']"}) +        }, +        'ishtar_common.importercolumn': { +            'Meta': {'object_name': 'ImporterColumn'}, +            'col_number': ('django.db.models.fields.IntegerField', [], {'default': '1'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'columns'", 'to': "orm['ishtar_common.ImporterType']"}), +            'regexp_pre_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Regexp']", 'null': 'True', 'blank': 'True'}), +            'required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) +        }, +        'ishtar_common.importerdefault': { +            'Meta': {'object_name': 'ImporterDefault'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'defaults'", 'to': "orm['ishtar_common.ImporterType']"}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.importerdefaultvalues': { +            'Meta': {'object_name': 'ImporterDefaultValues'}, +            'default_target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'default_values'", 'to': "orm['ishtar_common.ImporterDefault']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}), +            'value': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.importerduplicatefield': { +            'Meta': {'object_name': 'ImporterDuplicateField'}, +            'column': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'duplicate_fields'", 'to': "orm['ishtar_common.ImporterColumn']"}), +            'field_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) +        }, +        'ishtar_common.importertype': { +            'Meta': {'object_name': 'ImporterType'}, +            'associated_models': ('django.db.models.fields.CharField', [], {'max_length': '200'}), +            'description': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_template': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), +            'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['ishtar_common.IshtarUser']", 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.importtarget': { +            'Meta': {'object_name': 'ImportTarget'}, +            'column': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'targets'", 'to': "orm['ishtar_common.ImporterColumn']"}), +            'formater_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.FormaterType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'regexp_filter': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Regexp']", 'null': 'True', 'blank': 'True'}), +            'target': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.ishtaruser': { +            'Meta': {'object_name': 'IshtarUser', '_ormbases': ['auth.User']}, +            'person': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ishtaruser'", 'unique': 'True', 'to': "orm['ishtar_common.Person']"}), +            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}) +        }, +        'ishtar_common.itemkey': { +            'Meta': {'object_name': 'ItemKey'}, +            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'importer': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Import']", 'null': 'True', 'blank': 'True'}), +            'key': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}) +        }, +        'ishtar_common.organization': { +            'Meta': {'object_name': 'Organization'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), +            'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Organization']"}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '300'}), +            'organization_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.OrganizationType']"}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.organizationtype': { +            'Meta': {'ordering': "('label',)", 'object_name': 'OrganizationType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.person': { +            'Meta': {'object_name': 'Person'}, +            'address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'address_complement': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'attached_to': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'members'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['ishtar_common.Organization']"}), +            'country': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}), +            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), +            'history_creator': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'history_modifier': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['auth.User']"}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'merge_candidate': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_candidate_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), +            'merge_exclusion': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'merge_exclusion_rel_+'", 'null': 'True', 'to': "orm['ishtar_common.Person']"}), +            'merge_key': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'mobile_phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), +            'person_types': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['ishtar_common.PersonType']", 'symmetrical': 'False'}), +            'phone': ('django.db.models.fields.CharField', [], {'max_length': '18', 'null': 'True', 'blank': 'True'}), +            'postal_code': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), +            'raw_name': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True', 'blank': 'True'}), +            'surname': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), +            'title': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}), +            'town': ('django.db.models.fields.CharField', [], {'max_length': '70', 'null': 'True', 'blank': 'True'}) +        }, +        'ishtar_common.persontype': { +            'Meta': {'ordering': "('label',)", 'object_name': 'PersonType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.regexp': { +            'Meta': {'object_name': 'Regexp'}, +            'description': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'regexp': ('django.db.models.fields.CharField', [], {'max_length': '500'}) +        }, +        'ishtar_common.sourcetype': { +            'Meta': {'object_name': 'SourceType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.supporttype': { +            'Meta': {'object_name': 'SupportType'}, +            'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), +            'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'txt_idx': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) +        }, +        'ishtar_common.targetkey': { +            'Meta': {'unique_together': "(('target', 'value'),)", 'object_name': 'TargetKey'}, +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'is_set': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), +            'key': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), +            'target': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'keys'", 'to': "orm['ishtar_common.ImportTarget']"}), +            'value': ('django.db.models.fields.TextField', [], {}) +        }, +        'ishtar_common.town': { +            'Meta': {'ordering': "['numero_insee']", 'object_name': 'Town'}, +            'canton': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Canton']", 'null': 'True', 'blank': 'True'}), +            'center': ('django.contrib.gis.db.models.fields.PointField', [], {'srid': '27572', 'null': 'True', 'blank': 'True'}), +            'departement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['ishtar_common.Department']", 'null': 'True', 'blank': 'True'}), +            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), +            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), +            'numero_insee': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '6'}), +            'surface': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) +        } +    } + +    complete_apps = ['ishtar_common']
\ No newline at end of file diff --git a/ishtar_common/models.py b/ishtar_common/models.py index 1f059f2f5..b43d3a50c 100644 --- a/ishtar_common/models.py +++ b/ishtar_common/models.py @@ -973,11 +973,15 @@ def get_model_fields(model):      """      fields = {}      options = model._meta -    for field in sorted(options.concrete_fields + options.many_to_many + -                        options.virtual_fields): +    for field in sorted(options.fields):          fields[field.name] = field      return fields +def import_class(full_path_classname): +    mods = full_path_classname.split('.') +    module = import_module('.'.join(mods[:-1])) +    return getattr(module, mods[-1]) +  class ImporterType(models.Model):      """      Description of a table to be mapped with ishtar database @@ -998,22 +1002,19 @@ class ImporterType(models.Model):      def __unicode__(self):          return self.name -    @property -    def importer_class(self): -        name = ''.join(x for x in slugify(self.name).replace('-', ' ').title() -                       if not x.isspace()) -        OBJECT_CLS = import_module(self.associated_models) -        DEFAULTS = dict((default.keys, default.values) -                                        for default in self.defaults.all()) +    def get_importer_class(self): +        OBJECT_CLS = import_class(self.associated_models) +        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.order > idx: +            while column.col_number > idx:                  LINE_FORMAT.append(None)                  idx += 1 -            targets = None -            formater_types = None +            targets = [] +            formater_types = []              nb = column.targets.count()              if not nb:                  LINE_FORMAT.append(None) @@ -1035,6 +1036,8 @@ class ImporterType(models.Model):              LINE_FORMAT.append(formater)          args = {'OBJECT_CLS':OBJECT_CLS, 'DESC':self.description,                  'DEFAULTS':DEFAULTS, 'LINE_FORMAT':LINE_FORMAT} +        name = str(''.join(x for x in slugify(self.name).replace('-', ' ').title() +                       if not x.isspace()))          newclass = type(name, (Importer,), args)          return newclass @@ -1050,12 +1053,12 @@ class ImporterDefault(models.Model):      @property      def keys(self): -        return default.target.split('__') +        return tuple(self.target.split('__'))      @property      def associated_model(self):          field = None -        OBJECT_CLS = import_module(self.importer_type.associated_models) +        OBJECT_CLS = import_class(self.importer_type.associated_models)          for idx, item in enumerate(self.keys):              if not idx:                  field = get_model_fields(OBJECT_CLS)[item] @@ -1084,9 +1087,17 @@ class ImporterDefaultValues(models.Model):          verbose_name_plural = _(u"Importer - Default values")      def get_value(self): -        model = self.default_target.associated_model -        if not model: +        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)) @@ -1154,7 +1165,7 @@ class TargetKey(models.Model):      Also temporary used for GeneralType to point missing link before adding      them in ItemKey table      """ -    target = models.ForeignKey(ImporterColumn, related_name='keys') +    target = models.ForeignKey(ImportTarget, related_name='keys')      key = models.TextField(_(u"Key"), blank=True, null=True)      value = models.TextField(_(u"Value"))      is_set = models.BooleanField(_(u"Is set"), default=False) @@ -1174,7 +1185,7 @@ TARGET_MODELS = [      ('archaeological_operations.models.Period', _(u"Period")),      ] -TARGET_MODELS_KEYS = (tm[0] for tm in TARGET_MODELS) +TARGET_MODELS_KEYS = [tm[0] for tm in TARGET_MODELS]  IMPORTER_TYPES = (      ('IntegerFormater', _(u"Integer")), @@ -1226,7 +1237,7 @@ class FormaterType(models.Model):      def get_formater_type(self, target):          if self.formater_type not in IMPORTER_TYPES_DCT.keys():              return -        kwargs = {'target':target} +        kwargs = {'db_target':target}          if self.many_split:              kwargs['many_split'] = self.many_split          if self.formater_type == 'TypeFormater': @@ -1236,18 +1247,18 @@ class FormaterType(models.Model):              if self.options in dir():                  model = dir()[self.options]              else: -                model = import_module(self.options) +                model = import_class(self.options)              return TypeFormater(model, **kwargs)          elif self.formater_type == 'IntegerFormater':              return IntegerFormater(**kwargs)          elif self.formater_type == 'FloatFormater':              return FloatFormater(**kwargs) -        elif self.format_type == 'UnicodeFormater': +        elif self.formater_type == 'UnicodeFormater':              try:                  return UnicodeFormater(int(self.options.strip()), **kwargs)              except ValueError:                  return -        elif self.format_type == 'DateFormater': +        elif self.formater_type == 'DateFormater':              return DateFormater(self.options, **kwargs)  IMPORT_STATE = (("C", _(u"Created")), @@ -1257,6 +1268,8 @@ IMPORT_STATE = (("C", _(u"Created")),                  ("IP", _(u"Import in progress")),                  ("F", _(u"Finished"))) +IMPORT_STATE_DCT = dict(IMPORT_STATE) +  class Import(models.Model):      user = models.ForeignKey('IshtarUser')      importer_type = models.ForeignKey(ImporterType) @@ -1285,15 +1298,40 @@ class Import(models.Model):          return u"%s - %s" % (unicode(self.importer_type),                               unicode(self.user)) +    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': +            if ImporterType.objects.filter(pk=self.importer_type.pk, +                               columns__targets__keys__is_set=False).count(): +                actions.append(('L', _(u"Link unmatched items"))) +            actions.append(('A', _(u"Re-analyse"))) +            actions.append(('I', _(u"Launch import"))) +        actions.append(('D', _(u"Delete"))) +        return actions +      @property -    def importer_instance(self): -        return self.importer_type.importer_class(skip_lines=self.skip_lines, -                                                 import_instance=self) +    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()( +                        skip_lines=self.skip_lines, import_instance=self)      @property      def data_table(self):          encodings = [settings.ENCODING, settings.ALT_ENCODING, 'utf-8'] -        with open(self.imported_file.filename) as csv_file: +        with open(self.imported_file.path) as csv_file:              for encoding in encodings:                  try:                      return [line for line in unicodecsv.reader(csv_file, @@ -1305,7 +1343,11 @@ class Import(models.Model):          return []      def initialize(self): -        self.importer_instance.initialize(self.data_table, output='db') +        self.state = 'AP' +        self.save() +        self.get_importer_instance().initialize(self.data_table, output='db') +        self.state = 'A' +        self.save()  class Organization(Address, Merge, OwnPerms, ValueGetter):      TABLE_COLS = ('name', 'organization_type',) diff --git a/ishtar_common/templates/ishtar/form.html b/ishtar_common/templates/ishtar/form.html new file mode 100644 index 000000000..1e795c540 --- /dev/null +++ b/ishtar_common/templates/ishtar/form.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} +{% load i18n inline_formset %} +{% block content %} +<h2>{{page_name}}</h2> +<div class='form'> +<form enctype="multipart/form-data" action="." method="post">{% csrf_token %} +<table> +{{form}} +</table> +<input type="submit" value="{% trans "Validate" %}"/> +</form> +</div> +{% endblock %} diff --git a/ishtar_common/templates/ishtar/import_list.html b/ishtar_common/templates/ishtar/import_list.html new file mode 100644 index 000000000..868ca9c9e --- /dev/null +++ b/ishtar_common/templates/ishtar/import_list.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} +{% load i18n inline_formset %} +{% block content %} +<h2>{{page_name}}</h2> +<div class='form'> +{% if not object_list %} +<p>{% trans "No pending imports." %}</p> +{% else %} +<form action="." method="post">{% csrf_token %} +<table> +<tr> +  <th>{% trans "Type" %}</th> +  <th>{% trans "Filename" %}</th> +  <th>{% trans "Creation" %}</th> +  <th>{% trans "Status" %}</th> +</tr> +{% for import in object_list %} +<tr> +  <td> +    {{import.importer_type}} +  </td> +  <td> +    {{import.imported_filename}} +  </td> +  <td> +    {{import.creation_date}} +  </td> +  <td> +    {{import.status}} +  </td> +  <td> +    <select name='import-action-{{import.pk}}'> +        <option value=''>--------</option> +        {% for action, lbl in import.get_actions %} +        <option value='{{action}}'>{{lbl}}</option> +        {% endfor%} +    </select> +  </td> +</tr> +{% endfor %} +</table> +    <input type="submit" value="{% trans "Validate" %}"/> +</form> +{% endif %} +</div> +{% endblock %} + diff --git a/ishtar_common/urls.py b/ishtar_common/urls.py index d1e88f68f..81b96967a 100644 --- a/ishtar_common/urls.py +++ b/ishtar_common/urls.py @@ -48,6 +48,11 @@ urlpatterns = patterns('',             views.organization_deletion_wizard, name='organization_deletion'),         url(r'account_management/(?P<step>.+)?$',             views.account_management_wizard, name='account_management'), +       url(r'import/$', views.NewImportView.as_view(), name='new_import'), +       url(r'import-list/$', views.ImportListView.as_view(), +           name='current_imports'), +       url(r'import-list-old/$', views.ImportOldListView.as_view(), +           name='old_imports')         )  for section in menu.childs:      for menu_item in section.childs: diff --git a/ishtar_common/views.py b/ishtar_common/views.py index 6f18f8daa..3c2aa8b3c 100644 --- a/ishtar_common/views.py +++ b/ishtar_common/views.py @@ -38,11 +38,14 @@ from django.core import serializers  from django.core.exceptions import ObjectDoesNotExist  from django.core.urlresolvers import reverse, NoReverseMatch  from django.db.models import Q, F, ImageField -from django.http import HttpResponse, Http404 +from django.forms.models import model_to_dict, modelformset_factory +from django.http import HttpResponse, Http404, HttpResponseRedirect  from django.shortcuts import render_to_response, redirect  from django.template import RequestContext, loader  from django.utils.decorators import method_decorator  from django.utils.translation import ugettext, ugettext_lazy as _ +from django.views.generic import ListView +from django.views.generic.edit import CreateView  from xhtml2odt import xhtml2odt @@ -787,10 +790,6 @@ def dashboard_main_detail(request, item_name):      return render_to_response('ishtar/dashboards/dashboard_main_detail.html',                              dct, context_instance=RequestContext(request)) -from django.forms.models import model_to_dict - -from django.forms.models import modelformset_factory -  ITEM_PER_PAGE = 20  def merge_action(model, form, key):      def merge(request, page=1): @@ -832,6 +831,13 @@ person_merge = merge_action(models.Person, forms.MergePersonForm, 'person')  organization_merge = merge_action(models.Organization, forms.MergeOrganizationForm,                              'organization') +class IshtarMixin(object): +    page_name = u"" +    def get_context_data(self, **kwargs): +        context = super(IshtarMixin, self).get_context_data(**kwargs) +        context['page_name'] = self.page_name +        return context +  class LoginRequiredMixin(object):      @method_decorator(login_required)      def dispatch(self, request, *args, **kwargs): @@ -856,3 +862,56 @@ class GlobalVarEdit(AdminLoginRequiredMixin, ModelFormSetView):      extra = 1      can_delete = True      fields = ['slug', 'value', 'description'] + +class NewImportView(IshtarMixin, LoginRequiredMixin, CreateView): +    template_name = 'ishtar/form.html' +    model = models.Import +    form_class = forms.NewImportForm +    page_name = _(u"New import") + +    def get_success_url(self): +        return reverse('current_imports') + +    def form_valid(self, form): +        user = models.IshtarUser.objects.get(pk=self.request.user.pk) +        self.object = form.save(user=user) +        return HttpResponseRedirect(self.get_success_url()) + +class ImportListView(IshtarMixin, LoginRequiredMixin, ListView): +    template_name = 'ishtar/import_list.html' +    model = models.Import +    page_name = _(u"Current imports") +    current_url = 'current_imports' + +    def get_queryset(self): +        user = models.IshtarUser.objects.get(pk=self.request.user.pk) +        return self.model.objects.filter(user=user).exclude(state='F' +                                        ).order_by('-creation_date') + +    def post(self, request, *args, **kwargs): +        for field in request.POST: +            if not field.startswith('import-action-') or \ +               not request.POST[field]: +                continue +            # prevent forged forms +            try: +                imprt = models.Import.objects.get(pk=int(field.split('-')[-1])) +            except (models.Import.DoesNotExist, ValueError): +                continue +            # user can only edit his own imports +            user = models.IshtarUser.objects.get(pk=self.request.user.pk) +            if imprt.user != user: +                continue +            action = request.POST[field] +            if action == 'D': +                imprt.delete() +            elif action == 'A': +                imprt.initialize() +        return HttpResponseRedirect(reverse(self.current_url)) + +class ImportOldListView(ImportListView): +    current_url = 'old_imports' +    def get_queryset(self): +        user = models.IshtarUser.objects.get(pk=self.request.user.pk) +        return self.model.objects.filter(user=user, state='F' +                                        ).order_by('-creation_date') | 
