Skip to content

Commit

Permalink
Python 3.3 support
Browse files Browse the repository at this point in the history
  • Loading branch information
bmihelac committed Jan 30, 2014
1 parent b307a8b commit 77c0ead
Show file tree
Hide file tree
Showing 28 changed files with 155 additions and 56 deletions.
9 changes: 6 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
env:
- DJANGO=1.4
- DJANGO=1.4.10
- DJANGO=1.5.5
- DJANGO=1.6.1
install:
- pip install -q Django==$DJANGO --use-mirrors
- pip install -e git+https://github.com/kennethreitz/tablib.git#egg=tablib
- pip install -r requirements/base.txt --use-mirrors
script:
- python tests/manage.py test core --settings=settings
- if [[ $TRAVIS_PYTHON_VERSION != '3.3' && $DJANGO != "1.4.10" ]]; then python tests/manage.py test core --settings=settings; fi
7 changes: 7 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ Username and password for admin are 'admin', 'password'.


.. _`tablib`: https://github.com/kennethreitz/tablib

Requirements
============

* Python 2.7+ or Python 3.3+
* Django 1.4.2+
* tablib (dev or 0.9.11)
4 changes: 2 additions & 2 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
Changelog for django-import-export
==================================

0.1.7 (unreleased)
0.2.0 (unreleased)
------------------

- Nothing changed yet.
- Python 3 support


0.1.6 (2014-01-21)
Expand Down
2 changes: 1 addition & 1 deletion import_export/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.1.7.dev0"
__version__ = "0.2.1.dev0"
9 changes: 7 additions & 2 deletions import_export/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
)
from .formats import base_formats

try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text


#: import / export formats
DEFAULT_FORMATS = (
Expand Down Expand Up @@ -100,7 +105,7 @@ def process_import(self, request, *args, **kwargs):
input_format.get_read_mode())
data = import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = unicode(data, self.from_encoding).encode('utf-8')
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)

resource.import_data(dataset, dry_run=False,
Expand Down Expand Up @@ -148,7 +153,7 @@ def import_action(self, request, *args, **kwargs):
# warning, big files may exceed memory
data = uploaded_import_file.read()
if not input_format.is_binary() and self.from_encoding:
data = unicode(data, self.from_encoding).encode('utf-8')
data = force_text(data, self.from_encoding)
dataset = input_format.create_dataset(data)
result = resource.import_data(dataset, dry_run=True,
raise_errors=False)
Expand Down
3 changes: 3 additions & 0 deletions import_export/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import unicode_literals


class ImportExportError(Exception):
"""A generic exception for all others to extend."""
pass
Expand Down
3 changes: 3 additions & 0 deletions import_export/fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import unicode_literals

from . import widgets

from django.core.exceptions import ObjectDoesNotExist


class Field(object):
"""
Field represent mapping between `object` field and representation of
Expand Down
1 change: 1 addition & 0 deletions import_export/formats/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import unicode_literals
14 changes: 13 additions & 1 deletion import_export/formats/base_formats.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

import warnings
import tablib

Expand All @@ -15,6 +17,7 @@
XLS_IMPORT = False

from django.utils.importlib import import_module
from django.utils import six


class Format(object):
Expand Down Expand Up @@ -103,9 +106,18 @@ def is_binary(self):
return False


class CSV(TextFormat):
class CSV(TablibFormat):
"""
CSV is treated as binary in Python 2.
"""
TABLIB_MODULE = 'tablib.formats._csv'

def get_read_mode(self):
return 'rU' if six.PY3 else 'rb'

def is_binary(self):
return False if six.PY3 else True


class JSON(TextFormat):
TABLIB_MODULE = 'tablib.formats._json'
Expand Down
2 changes: 2 additions & 0 deletions import_export/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

from django import forms
from django.utils.translation import ugettext_lazy as _

Expand Down
3 changes: 3 additions & 0 deletions import_export/instance_loaders.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import unicode_literals


class BaseInstanceLoader(object):
"""
Base abstract implementation of instance loader.
Expand Down
1 change: 1 addition & 0 deletions import_export/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import unicode_literals
43 changes: 25 additions & 18 deletions import_export/resources.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

import functools
from copy import deepcopy
import sys
Expand All @@ -8,6 +10,7 @@

from django.utils.safestring import mark_safe
from django.utils.datastructures import SortedDict
from django.utils import six
from django.db import transaction
from django.db.models.related import RelatedObject
from django.conf import settings
Expand All @@ -16,8 +19,14 @@
from .fields import Field
from import_export import widgets
from .instance_loaders import (
ModelInstanceLoader,
)
ModelInstanceLoader,
)


try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text


USE_TRANSACTIONS = getattr(settings, 'IMPORT_EXPORT_USE_TRANSACTIONS', False)
Expand Down Expand Up @@ -50,7 +59,7 @@ class ResourceOptions(object):
* ``use_transactions`` - Controls if import should use database
transactions. Default value is ``None`` meaning
``settings.IMPORT_EXPORT_USE_TRANSACTIONS`` will be evaluated.
* ``skip_unchanged`` - Controls if the import should skip unchanged records.
Default value is False
Expand All @@ -77,15 +86,15 @@ def __new__(cls, meta=None):
if not override_name.startswith('_'):
overrides[override_name] = getattr(meta, override_name)

return object.__new__(type('ResourceOptions', (cls,), overrides))
return object.__new__(type(str('ResourceOptions'), (cls,), overrides))


class DeclarativeMetaclass(type):

def __new__(cls, name, bases, attrs):
declared_fields = []

for field_name, obj in attrs.items():
for field_name, obj in attrs.copy().items():
if isinstance(obj, Field):
field = attrs.pop(field_name)
if not field.column_name:
Expand All @@ -101,12 +110,11 @@ def __new__(cls, name, bases, attrs):
return new_class


class Resource(object):
class Resource(six.with_metaclass(DeclarativeMetaclass)):
"""
Resource defines how objects are mapped to their import and export
representations and handle importing and exporting data.
"""
__metaclass__ = DeclarativeMetaclass

def get_use_transactions(self):
if self._meta.use_transactions is None:
Expand Down Expand Up @@ -247,7 +255,7 @@ def get_diff(self, original, current, dry_run=False):
for field in self.get_fields():
v1 = self.export_field(field, original) if original else ""
v2 = self.export_field(field, current) if current else ""
diff = dmp.diff_main(unicode(v1), unicode(v2))
diff = dmp.diff_main(force_text(v1), force_text(v2))
dmp.diff_cleanupSemantic(diff)
html = dmp.diff_prettyHtml(diff)
html = mark_safe(html)
Expand Down Expand Up @@ -294,7 +302,7 @@ def import_data(self, dataset, dry_run=False, raise_errors=False,

try:
self.before_import(dataset, real_dry_run)
except Exception, e:
except Exception as e:
tb_info = traceback.format_exc(sys.exc_info()[2])
result.base_errors.append(Error(repr(e), tb_info))
if raise_errors:
Expand Down Expand Up @@ -332,16 +340,16 @@ def import_data(self, dataset, dry_run=False, raise_errors=False,
self.save_m2m(instance, row, real_dry_run)
row_result.diff = self.get_diff(original, instance,
real_dry_run)
except Exception, e:
tb_info = traceback.format_exc(sys.exc_info()[2])
except Exception as e:
tb_info = traceback.format_exc(2)
row_result.errors.append(Error(repr(e), tb_info))
if raise_errors:
if use_transactions:
transaction.rollback()
transaction.leave_transaction_management()
raise
if (row_result.import_type != RowResult.IMPORT_TYPE_SKIP or
self._meta.report_skipped):
six.reraise(*sys.exc_info())
if (row_result.import_type != RowResult.IMPORT_TYPE_SKIP or
self._meta.report_skipped):
result.rows.append(row_result)

if use_transactions:
Expand Down Expand Up @@ -446,11 +454,10 @@ def __new__(cls, name, bases, attrs):
return new_class


class ModelResource(Resource):
class ModelResource(six.with_metaclass(ModelDeclarativeMetaclass, Resource)):
"""
ModelResource is Resource subclass for handling Django models.
"""
__metaclass__ = ModelDeclarativeMetaclass

@classmethod
def widget_from_django_field(cls, f, default=widgets.Widget):
Expand Down Expand Up @@ -503,9 +510,9 @@ def modelresource_factory(model, resource_class=ModelResource):
Factory for creating ``ModelResource`` class for given Django model.
"""
attrs = {'model': model}
Meta = type('Meta', (object,), attrs)
Meta = type(str('Meta'), (object,), attrs)

class_name = model.__name__ + 'Resource'
class_name = model.__name__ + str('Resource')

class_attrs = {
'Meta': Meta,
Expand Down
3 changes: 3 additions & 0 deletions import_export/results.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from __future__ import unicode_literals


class Error(object):

def __init__(self, error, traceback=None):
Expand Down
11 changes: 9 additions & 2 deletions import_export/widgets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
from __future__ import unicode_literals

from decimal import Decimal
from datetime import datetime

try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text


class Widget(object):
"""
Expand All @@ -23,7 +30,7 @@ def render(self, value):
"""
Returns export representation of python value.
"""
return unicode(value)
return force_text(value)


class IntegerWidget(Widget):
Expand Down Expand Up @@ -54,7 +61,7 @@ class CharWidget(Widget):
"""

def render(self, value):
return unicode(value)
return force_text(value)


class BooleanWidget(Widget):
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Topic :: Software Development',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
]

install_requires = [
Expand Down
2 changes: 2 additions & 0 deletions tests/core/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import unicode_literals

from django.contrib import admin

from import_export.admin import ImportExportMixin
Expand Down
12 changes: 9 additions & 3 deletions tests/core/models.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
from __future__ import unicode_literals

from django.db import models
from django.utils.encoding import python_2_unicode_compatible


@python_2_unicode_compatible
class Author(models.Model):
name = models.CharField(max_length=100)
birthday = models.DateTimeField(auto_now_add=True)

def __unicode__(self):
def __str__(self):
return self.name


@python_2_unicode_compatible
class Category(models.Model):
name = models.CharField(max_length=100)

def __unicode__(self):
def __str__(self):
return self.name


@python_2_unicode_compatible
class Book(models.Model):
name = models.CharField('Book name', max_length=100)
author = models.ForeignKey(Author, blank=True, null=True)
Expand All @@ -26,7 +32,7 @@ class Book(models.Model):
blank=True)
categories = models.ManyToManyField(Category, blank=True)

def __unicode__(self):
def __str__(self):
return self.name


Expand Down
Loading

0 comments on commit 77c0ead

Please sign in to comment.