Skip to content

Commit

Permalink
adds meta attribute flag 'skip_diff'
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewhegarty committed Dec 1, 2019
1 parent 3cf5e3f commit 68cd4dd
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 7 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,4 @@ The following is a list of much appreciated contributors:
* aniav (Ania Warzecha)
* ababic (Andy Babic)
* BramManuel (Bram Janssen)
* Matthew Hegarty
31 changes: 24 additions & 7 deletions import_export/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ class ResourceOptions:
The default value is False.
"""

skip_diff = False
"""
Controls whether or not an instance should be diffed following import.
By default, an instance is copied prior to insert, update or delete.
After each row is processed, the instance's copy is diffed against the original, and the value
stored in each ``RowResult``.
If diffing is not required, then disabling the diff operation by setting this value to ``True``
improves performance, because the copy and comparison operations are skipped for each row.
The default value is False.
"""


class DeclarativeMetaclass(type):

Expand Down Expand Up @@ -491,6 +502,7 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
:param dry_run: If ``dry_run`` is set, or error occurs, transaction
will be rolled back.
"""
skip_diff = self._meta.skip_diff
row_result = self.get_row_result_class()()
try:
self.before_import_row(row, **kwargs)
Expand All @@ -501,16 +513,19 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
else:
row_result.import_type = RowResult.IMPORT_TYPE_UPDATE
row_result.new_record = new
original = deepcopy(instance)
diff = self.get_diff_class()(self, original, new)
if not skip_diff:
original = deepcopy(instance)
diff = self.get_diff_class()(self, original, new)
if self.for_delete(row, instance):
if new:
row_result.import_type = RowResult.IMPORT_TYPE_SKIP
diff.compare_with(self, None, dry_run)
if not skip_diff:
diff.compare_with(self, None, dry_run)
else:
row_result.import_type = RowResult.IMPORT_TYPE_DELETE
self.delete_instance(instance, using_transactions, dry_run)
diff.compare_with(self, None, dry_run)
if not skip_diff:
diff.compare_with(self, None, dry_run)
else:
import_validation_errors = {}
try:
Expand All @@ -520,7 +535,7 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
# validate_instance(), where they can be combined with model
# instance validation errors if necessary
import_validation_errors = e.update_error_dict(import_validation_errors)
if self.skip_row(instance, original):
if not skip_diff and self.skip_row(instance, original):
row_result.import_type = RowResult.IMPORT_TYPE_SKIP
else:
self.validate_instance(instance, import_validation_errors)
Expand All @@ -529,9 +544,11 @@ def import_row(self, row, instance_loader, using_transactions=True, dry_run=Fals
# Add object info to RowResult for LogEntry
row_result.object_id = instance.pk
row_result.object_repr = force_text(instance)
diff.compare_with(self, instance, dry_run)
if not skip_diff:
diff.compare_with(self, instance, dry_run)

row_result.diff = diff.as_html()
if not skip_diff:
row_result.diff = diff.as_html()
self.after_import_row(row, row_result, **kwargs)

except ValidationError as e:
Expand Down
59 changes: 59 additions & 0 deletions tests/core/tests/test_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1188,3 +1188,62 @@ def check_value(self, result, export_headers, expected_value):
diff = result.rows[0].diff
self.assertEqual(diff[export_headers.index("categories")],
expected_value)


@mock.patch("copy.deepcopy")
@mock.patch("import_export.resources.Diff", spec=True)
class SkipDiffTest(TestCase):
"""
Tests that the meta attribute 'skip_diff' means that no diff operations are called.
"""
def setUp(self):
class _BookResource(resources.ModelResource):
class Meta:
model = Book
skip_diff = True
self.resource = _BookResource()
self.dataset = tablib.Dataset(headers=['id', 'name', 'birthday'])
self.dataset.append(['', 'A.A.Milne', '1882test-01-18'])

def test_skip_diff(self, mock_diff, mock_deep_copy):
result = self.resource.import_data(self.dataset, raise_errors=False)
mock_diff.return_value.compare_with.assert_not_called()
mock_diff.return_value.as_html.assert_not_called()
mock_deep_copy.assert_not_called()

def test_skip_diff_for_delete_new_resource(self, mock_diff, mock_deep_copy):
class BookResource(resources.ModelResource):

def for_delete(self, row, instance):
return True

class Meta:
model = Book
skip_diff = True

resource = BookResource()
result = resource.import_data(self.dataset, raise_errors=False)
mock_diff.return_value.compare_with.assert_not_called()
mock_diff.return_value.as_html.assert_not_called()
mock_deep_copy.assert_not_called()

def test_skip_diff_for_delete_existing_resource(self, mock_diff, mock_deep_copy):
book = Book.objects.create()
class BookResource(resources.ModelResource):

def get_or_init_instance(self, instance_loader, row):
return book, False

def for_delete(self, row, instance):
return True

class Meta:
model = Book
skip_diff = True

resource = BookResource()

result = resource.import_data(self.dataset, raise_errors=False, dry_run=True)
mock_diff.return_value.compare_with.assert_not_called()
mock_diff.return_value.as_html.assert_not_called()
mock_deep_copy.assert_not_called()

0 comments on commit 68cd4dd

Please sign in to comment.