diff --git a/django_mock_queries/query.py b/django_mock_queries/query.py index 2cf78c9..5761ac3 100644 --- a/django_mock_queries/query.py +++ b/django_mock_queries/query.py @@ -1,13 +1,13 @@ import datetime import random -from collections import OrderedDict, namedtuple +from collections import OrderedDict, defaultdict, namedtuple from unittest.mock import Mock, MagicMock, PropertyMock from .constants import * from .exceptions import * from .utils import ( matches, get_attribute, validate_mock_set, is_list_like_iter, flatten_list, truncate, - hash_dict, filter_results + hash_dict, filter_results, get_nested_attr ) @@ -267,16 +267,25 @@ def update(self, **attrs): return count def _delete_recursive(self, *items_to_remove, **attrs): + removed_items = defaultdict(int) + for item in matches(*items_to_remove, **attrs): self.items.remove(item) self.fire(item, self.EVENT_DELETED) + # Support returning detailed information about removed items + # even if items are not `MockModel` instances + item_label = get_nested_attr(item, '_meta.label', default=type(item).__name__) + removed_items[item_label] += 1 + if self.clone is not None: self.clone._delete_recursive(*items_to_remove, **attrs) + return sum(removed_items.values()), removed_items + def delete(self, **attrs): # Delete normally doesn't take **attrs - they're only needed for remove - self._delete_recursive(*self.items, **attrs) + return self._delete_recursive(*self.items, **attrs) # The following 2 methods were kept for backwards compatibility and # should be removed in the future since they are covered by filter & delete @@ -437,7 +446,8 @@ def __init__(self, *args, **kwargs): super(MockModel, self).__init__(*args, **kwargs) self.save = PropertyMock() - self.__meta = MockOptions(*self.get_fields()) + object_name = self.get('mock_name', type(self).__name__) + self.__meta = MockOptions(object_name, *self.get_fields()) def __getattr__(self, item): return self.get(item, None) @@ -471,9 +481,19 @@ def create_model(*fields): class MockOptions: - def __init__(self, *field_names): + def __init__(self, object_name, *field_names): self.load_fields(*field_names) self.get_latest_by = None + self.object_name = object_name + self.model_name = object_name.lower() + + @property + def label(self): + return self.object_name + + @property + def label_lower(self): + return self.model_name def load_fields(self, *field_names): fields = {name: MockField(name) for name in field_names} diff --git a/django_mock_queries/utils.py b/django_mock_queries/utils.py index fead151..9d68986 100644 --- a/django_mock_queries/utils.py +++ b/django_mock_queries/utils.py @@ -332,3 +332,13 @@ def _filter_single_q(source, q_obj, negated): return filter_results(source, q_obj) else: return matches(negated=negated, *source, **{q_obj[0]: q_obj[1]}) + + +def get_nested_attr(obj, attr_path, default=None): + attrs = attr_path.split('.') + try: + for attr in attrs: + obj = getattr(obj, attr) + return obj + except AttributeError: + return default diff --git a/tests/test_query.py b/tests/test_query.py index 66ad52a..416e37c 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -706,20 +706,32 @@ def test_query_delete_all_entries(self): item_2 = MockModel(foo=1, bar='b', mock_name='item_2') self.mock_set.add(item_1, item_2) - self.mock_set.delete() + deleted_count, deleted_items = self.mock_set.delete() assert len(self.mock_set) == 0, len(self.mock_set) + assert deleted_count == 2 + assert deleted_items == {'item_1': 1, 'item_2': 1} + + def test_query_delete_non_model_entries(self): + items = [1, 2, 3, 'foo', 'bar', True] + self.mock_set.add(*items) + + deleted_count, deleted_items = self.mock_set.delete() + assert deleted_count == 6 + assert deleted_items == {'int': 3, 'str': 2, 'bool': 1} def test_query_delete_entries_propagated_from_nested_qs(self): item_1 = MockModel(foo=1, bar='a', mock_name='item_1') item_2 = MockModel(foo=1, bar='b', mock_name='item_2') self.mock_set.add(item_1, item_2) - self.mock_set.filter(bar='b').delete() + deleted_count, deleted_items = self.mock_set.filter(bar='b').delete() assert len(self.mock_set) == 1, len(self.mock_set) assert item_1 in self.mock_set assert item_2 not in self.mock_set + assert deleted_count == 1 + assert deleted_items == {'item_2': 1} def test_query_gets_unique_match_by_attrs_from_set(self): item_1 = MockModel(foo=1)