diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..f7d9dbc --- /dev/null +++ b/README.rst @@ -0,0 +1,3 @@ +==================== +django-import-export +==================== diff --git a/import_export/__init__.py b/import_export/__init__.py new file mode 100644 index 0000000..8309bf3 --- /dev/null +++ b/import_export/__init__.py @@ -0,0 +1,2 @@ +VERSION = (0, 0, '1dev') +__version__ = '.'.join(map(str, VERSION)) diff --git a/import_export/core.py b/import_export/core.py new file mode 100644 index 0000000..4fb3410 --- /dev/null +++ b/import_export/core.py @@ -0,0 +1,107 @@ +from collections import OrderedDict + +import tablib + +from django.utils.translation import ugettext_lazy as _ + + +class RowResult(object): + + def __init__(self): + self.errors = [] + self.orig_fields = [] + self.fields = [] + + +class Result(list): + + def __init__(self, *args, **kwargs): + super(Result, self).__init__(*args, **kwargs) + self.base_errors = [] + + def row_errors(self): + return [(i, row.errors) for i, row in enumerate(self) if row.errors] + + def has_errors(self): + return self.base_errors or self.row_errors() + + +class Importer(object): + model = None + format = None + import_code = "ID" + raise_errors = True + dry_run = True + mapping = None + + def __init__(self, f, **kwargs): + self.f = f + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def get_mapping(self): + if self.mapping: + return self.mapping + mapping = [(f.verbose_name, f.name) for f in self.model._meta.fields] + return OrderedDict(mapping) + + def load_dataset(self): + text = unicode(self.f.read(), 'cp1250').encode('utf-8') + if not self.format: + self.data = tablib.import_set(text) + else: + self.data = tablib.Dataset() + self.format.import_set(self.data, text) + + def get_instance(self, row): + return self.model.objects.get(**{ + self.get_mapping()[self.import_code]: row[self.import_code] + }) + + def init_instance(self, row): + return self.model() + + def get_or_init_instance(self, row): + try: + instance = self.get_instance(row) + except self.model.DoesNotExist: + instance = self.init_instance(row) + return instance + + def set_instance_attr(self, instance, row, field): + setattr(instance, self.get_mapping()[field], row[field]) + + def save_instance(self, instance): + if not self.dry_run: + instance.save() + + def get_representation(self, instance): + return [unicode(getattr(instance, f)) + for f in self.get_mapping().values()] + + def run(self): + result = Result() + try: + self.load_dataset() + except Exception, e: + result.base_errors.append(_('Loading error') + + u': %s' % unicode(e)) + if self.raise_errors: + raise + return result + + for row in self.data.dict: + try: + row_result = RowResult() + instance = self.get_or_init_instance(row) + row_result.orig_fields = self.get_representation(instance) + for field in self.get_mapping().keys(): + self.set_instance_attr(instance, row, field) + self.save_instance(instance) + row_result.fields = self.get_representation(instance) + except Exception, e: + row_result.errors.append(unicode(e)) + if self.raise_errors: + raise + result.append(row_result) + return result diff --git a/import_export/models.py b/import_export/models.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b0c8734 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django>=1.4 +tablib diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 0000000..6ba0f60 --- /dev/null +++ b/runtests.sh @@ -0,0 +1 @@ +PYTHONPATH=".:tests:$PYTHONPATH" django-admin.py test core --settings=settings diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..21617c8 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup, find_packages +import os + + +VERSION = __import__("send_instance").__version__ + +CLASSIFIERS = [ + 'Framework :: Django', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Operating System :: OS Independent', + 'Topic :: Software Development', +] + +install_requires = [ + 'tablib', + 'Django>=1.4.2', +] + +setup( + name="django-import-export", + description="django-import-export", + long_description=open(os.path.join(os.path.dirname(__file__), + 'README.rst')).read(), + version=VERSION, + author="Informatika Mihelac", + author_email="bmihelac@mihelac.org", + url="https://github.com/bmihelac/django-import-export", + packages=find_packages(exclude=["tests"]), +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/exports/books.csv b/tests/core/exports/books.csv new file mode 100644 index 0000000..d6b7fb2 --- /dev/null +++ b/tests/core/exports/books.csv @@ -0,0 +1,2 @@ +ID,Book name,Author email +1,Some book,test@example.com diff --git a/tests/core/models.py b/tests/core/models.py new file mode 100644 index 0000000..9d3fbe3 --- /dev/null +++ b/tests/core/models.py @@ -0,0 +1,9 @@ +from django.db import models + + +class Book(models.Model): + name = models.CharField('Book name', max_length=100) + author_email = models.EmailField('Author email', max_length=75, blank=True) + + def __unicode__(self): + return self.name diff --git a/tests/core/tests.py b/tests/core/tests.py new file mode 100644 index 0000000..c4e2d96 --- /dev/null +++ b/tests/core/tests.py @@ -0,0 +1,32 @@ +import os.path + +from django.test import TestCase + +from import_export.core import Importer + +from .models import Book + + +class BookImporter(Importer): + + model = Book + + +class ImporterTest(TestCase): + + def setUp(self): + self.filename = os.path.join(os.path.dirname(__file__), 'exports', + 'books.csv') + + def test_import_create(self): + result = BookImporter(open(self.filename), dry_run=False).run() + self.assertFalse(result.has_errors()) + self.assertEqual(Book.objects.count(), 1) + + def test_import_update(self): + Book.objects.create(id=1, name="Other book") + result = BookImporter(open(self.filename), dry_run=False).run() + self.assertFalse(result.has_errors()) + self.assertEqual(Book.objects.count(), 1) + self.assertEqual(Book.objects.all()[0].name, "Some book") + self.assertNotEqual(result[0].orig_fields[1], result[0].fields[1]) diff --git a/tests/settings.py b/tests/settings.py new file mode 100644 index 0000000..19a91ad --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,20 @@ +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': 'haystack_tests.db', + } +} + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + + 'import_export', + + 'core', +] + +SITE_ID = 1