Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
int-ua committed Dec 28, 2017
2 parents c939cf9 + d0157e2 commit 0d08f30
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 37 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ matrix:
env: DJANGO="https://github.com/django/django/archive/master.tar.gz"
- python: "3.3"
env: DJANGO="Django>=2.0,<2.1"
- python: "3.4"
env: DJANGO="https://github.com/django/django/archive/master.tar.gz"
- python: "3.5"
env: DJANGO="Django>=1.6,<1.7"
- python: "3.5"
Expand All @@ -55,5 +57,4 @@ matrix:
- python: "3.6"
env: DJANGO="Django>=1.10,<1.11"
allow_failures:
- env: DJANGO="Django>=2.0,<2.1"
- env: DJANGO="https://github.com/django/django/archive/master.tar.gz"
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,6 @@ The following is a list of much appreciated contributors:
* gatsinski (Hristo Gatsinski)
* raghavsethi (Raghav Sethi)
* jdufresne (Jon Dufresne)
* trik (Marco Marche)
* krishraghuram (Raghuram Krishnaswami)
* int_ua (Serhiy Zahoriya)
4 changes: 3 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ Changelog
0.6.2 (unreleased)
------------------

- Nothing changed yet.
- discourage installation as a zipped egg (#548)

- Fixed middleware settings in test app for Django 2.x (#696)


0.6.1 (2017-12-04)
Expand Down
12 changes: 6 additions & 6 deletions docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,20 @@ Declaring fields
It is possible to override a resource field to change some of its
options::

from import_export import fields
from import_export.fields import Field

class BookResource(resources.ModelResource):
published = fields.Field(column_name='published_date')
published = Field(column_name='published_date')

class Meta:
model = Book

Other fields that don't exist in the target model may be added::

from import_export import fields
from import_export.fields import Field

class BookResource(resources.ModelResource):
myfield = fields.Field(column_name='myfield')
myfield = Field(column_name='myfield')

class Meta:
model = Book
Expand All @@ -176,10 +176,10 @@ Not all data can be easily extracted from an object/model attribute.
In order to turn complicated data model into a (generally simpler) processed
data structure, ``dehydrate_<fieldname>`` method should be defined::

from import_export import fields
from import_export.fields import Field

class BookResource(resources.ModelResource):
full_title = fields.Field()
full_title = Field()

class Meta:
model = Book
Expand Down
10 changes: 8 additions & 2 deletions import_export/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.manager import Manager
from django.db.models.fields import NOT_PROVIDED
from django import VERSION


class Field(object):
Expand Down Expand Up @@ -102,7 +103,7 @@ def get_value(self, obj):
value = value()
return value

def save(self, obj, data):
def save(self, obj, data, is_m2m=False):
"""
If this field is not declared readonly, the object's attribute will
be set to the value returned by :meth:`~import_export.fields.Field.clean`.
Expand All @@ -113,7 +114,12 @@ def save(self, obj, data):
obj = getattr(obj, attr, None)
cleaned = self.clean(data)
if cleaned is not None or self.saves_null_values:
setattr(obj, attrs[-1], cleaned)
if VERSION < (1, 9, 0):
setattr(obj, attrs[-1], cleaned)
elif not is_m2m:
setattr(obj, attrs[-1], cleaned)
else:
getattr(obj, attrs[-1]).set(cleaned)

def export(self, obj):
"""
Expand Down
43 changes: 20 additions & 23 deletions import_export/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import no_style
from django.db import connections, transaction, DEFAULT_DB_ALIAS
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models.fields import FieldDoesNotExist
from django.db.models.query import QuerySet
from django.db.transaction import TransactionManagementError
Expand All @@ -23,6 +23,7 @@
from .fields import Field
from .instance_loaders import ModelInstanceLoader
from .results import Error, Result, RowResult
from .utils import atomic_if_using_transaction

try:
from django.db.transaction import atomic, savepoint, savepoint_rollback, savepoint_commit # noqa
Expand Down Expand Up @@ -316,13 +317,13 @@ def after_delete_instance(self, instance, dry_run):
"""
pass

def import_field(self, field, obj, data):
def import_field(self, field, obj, data, is_m2m=False):
"""
Calls :meth:`import_export.fields.Field.save` if ``Field.attribute``
and ``Field.column_name`` are found in ``data``.
"""
if field.attribute and field.column_name in data:
field.save(obj, data)
field.save(obj, data, is_m2m)

def get_import_fields(self):
return self.get_fields()
Expand Down Expand Up @@ -351,7 +352,7 @@ def save_m2m(self, obj, data, using_transactions, dry_run):
for field in self.get_import_fields():
if not isinstance(field.widget, widgets.ManyToManyWidget):
continue
self.import_field(field, obj, data)
self.import_field(field, obj, data, True)

def for_delete(self, row, instance):
"""
Expand Down Expand Up @@ -467,8 +468,7 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
if self.skip_row(instance, original):
row_result.import_type = RowResult.IMPORT_TYPE_SKIP
else:
with transaction.atomic():
self.save_instance(instance, using_transactions, dry_run)
self.save_instance(instance, using_transactions, dry_run)
self.save_m2m(instance, row, using_transactions, dry_run)
diff.compare_with(self, instance, dry_run)
row_result.diff = diff.as_html()
Expand Down Expand Up @@ -519,10 +519,8 @@ def import_data(self, dataset, dry_run=False, raise_errors=False,

using_transactions = (use_transactions or dry_run) and supports_transactions

if using_transactions:
with transaction.atomic():
return self.import_data_inner(dataset, dry_run, raise_errors, using_transactions, collect_failed_rows, **kwargs)
return self.import_data_inner(dataset, dry_run, raise_errors, using_transactions, collect_failed_rows, **kwargs)
with atomic_if_using_transaction(using_transactions):
return self.import_data_inner(dataset, dry_run, raise_errors, using_transactions, collect_failed_rows, **kwargs)

def import_data_inner(self, dataset, dry_run, raise_errors, using_transactions, collect_failed_rows, **kwargs):
result = self.get_result_class()()
Expand All @@ -535,15 +533,12 @@ def import_data_inner(self, dataset, dry_run, raise_errors, using_transactions,
sp1 = savepoint()

try:
self.before_import(dataset, using_transactions, dry_run, **kwargs)
with atomic_if_using_transaction(using_transactions):
self.before_import(dataset, using_transactions, dry_run, **kwargs)
except Exception as e:
logging.exception(e)
tb_info = traceback.format_exc()
result.append_base_error(self.get_error_result_class()(e, tb_info))
if raise_errors:
if using_transactions:
savepoint_rollback(sp1)
raise

instance_loader = self._meta.instance_loader_class(self, dataset)

Expand All @@ -554,30 +549,32 @@ def import_data_inner(self, dataset, dry_run, raise_errors, using_transactions,
result.add_dataset_headers(dataset.headers)

for row in dataset.dict:
row_result = self.import_row(row, instance_loader,
using_transactions=using_transactions, dry_run=dry_run,
**kwargs)
with atomic_if_using_transaction(using_transactions):
row_result = self.import_row(
row,
instance_loader,
using_transactions=using_transactions,
dry_run=dry_run,
**kwargs
)
result.increment_row_result_total(row_result)
if row_result.errors:
if collect_failed_rows:
result.append_failed_row(row, row_result.errors[0])
if raise_errors:
if using_transactions:
savepoint_rollback(sp1)
raise row_result.errors[-1].error
if (row_result.import_type != RowResult.IMPORT_TYPE_SKIP or
self._meta.report_skipped):
result.append_row_result(row_result)

try:
self.after_import(dataset, result, using_transactions, dry_run, **kwargs)
with atomic_if_using_transaction(using_transactions):
self.after_import(dataset, result, using_transactions, dry_run, **kwargs)
except Exception as e:
logging.exception(e)
tb_info = traceback.format_exc()
result.append_base_error(self.get_error_result_class()(e, tb_info))
if raise_errors:
if using_transactions:
savepoint_rollback(sp1)
raise

if using_transactions:
Expand Down
27 changes: 27 additions & 0 deletions import_export/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import unicode_literals

from django.db import transaction


class atomic_if_using_transaction(object):
"""Context manager wraps `atomic` if `using_transactions`.
Replaces code::
if using_transactions:
with transaction.atomic():
return somethng()
return something()
"""
def __init__(self, using_transactions):
self.using_transactions = using_transactions
if using_transactions:
self.context_manager = transaction.atomic()

def __enter__(self):
if self.using_transactions:
self.context_manager.__enter__()

def __exit__(self, *args):
if self.using_transactions:
self.context_manager.__exit__(*args)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@
include_package_data=True,
install_requires=install_requires,
classifiers=CLASSIFIERS,
zip_safe=False,
)
24 changes: 24 additions & 0 deletions tests/core/fixtures/book.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[
{
"model": "core.author",
"pk": 11,
"fields": {
"name": "George R. R. Martin",
"birthday": "1948-09-20"
}
},
{
"model": "core.book",
"pk": 11,
"fields": {
"name": "A Game of Thrones",
"author": 11,
"author_email": "martin@got.com",
"imported": false,
"published": "1996-08-01",
"published_time": "21:00",
"price": 25.0,
"categories": [1]
}
}
]
20 changes: 20 additions & 0 deletions tests/core/migrations/0006_auto_20171130_0147.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-11-30 01:47
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0005_addparentchild'),
]

operations = [
migrations.AlterField(
model_name='category',
name='name',
field=models.CharField(max_length=100, unique=True),
),
]
5 changes: 4 additions & 1 deletion tests/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ def __str__(self):

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

def __str__(self):
return self.name
Expand Down

0 comments on commit 0d08f30

Please sign in to comment.