Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proper return value to MockSet delete #184

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions django_mock_queries/query.py
Original file line number Diff line number Diff line change
@@ -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
)


Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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}
Expand Down
10 changes: 10 additions & 0 deletions django_mock_queries/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 14 additions & 2 deletions tests/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down