diff options
author | Étienne Loks <etienne.loks@proxience.com> | 2015-05-03 23:21:46 +0200 |
---|---|---|
committer | Étienne Loks <etienne.loks@proxience.com> | 2015-05-03 23:21:46 +0200 |
commit | 274b8d44ccf1f099f2e22b5a6a70f9743b746c1d (patch) | |
tree | 9d23175660bb6b6d1adcbcec722bf4ea88f0b818 /ishtar_common | |
parent | 40ffb48d6075a14a9591d6a7c83f40ef5afe97e5 (diff) | |
download | Ishtar-274b8d44ccf1f099f2e22b5a6a70f9743b746c1d.tar.bz2 Ishtar-274b8d44ccf1f099f2e22b5a6a70f9743b746c1d.zip |
Interface: create new import, management interface
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') |