-
Notifications
You must be signed in to change notification settings - Fork 53
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
MockOptions, get_or_create_with defaults #36
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
|
||
from .constants import * | ||
from .exceptions import * | ||
from .utils import matches, merge, intersect, get_attribute | ||
from .utils import matches, merge, intersect, get_attribute, validate_mock_set | ||
|
||
|
||
class MockBase(MagicMock): | ||
|
@@ -30,8 +30,9 @@ def MockSet(*initial_items, **kwargs): | |
'prefetch_related', | ||
'select_for_update' | ||
]) | ||
mock_set.cls = clone.cls if clone else kwargs.get('cls', empty_func) | ||
mock_set.cls = clone.cls if clone else kwargs.get('cls', MockModel) | ||
mock_set.count = MagicMock(side_effect=lambda: len(items)) | ||
mock_set.model = clone.model if clone else kwargs.get('model', None) | ||
mock_set.__len__ = MagicMock(side_effect=lambda: len(items)) | ||
|
||
def add(*model): | ||
|
@@ -174,9 +175,11 @@ def __iter__(): | |
mock_set.__iter__ = MagicMock(side_effect=__iter__) | ||
|
||
def create(**attrs): | ||
validate_mock_set(mock_set) | ||
for k in attrs.keys(): | ||
if k not in [f.attname for f in mock_set.model._meta.concrete_fields]: | ||
raise ValueError('MockSet model has no field {}'.format(k)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we raise in this case a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we can! |
||
obj = mock_set.cls(**attrs) | ||
if not obj: | ||
raise ModelNotSpecified() | ||
obj.save(force_insert=True, using=MagicMock()) | ||
add(obj) | ||
return obj | ||
|
@@ -194,8 +197,13 @@ def get(**attrs): | |
|
||
mock_set.get = MagicMock(side_effect=get) | ||
|
||
def get_or_create(**attrs): | ||
results = filter(**attrs) | ||
def get_or_create(defaults=None, **attrs): | ||
if defaults is not None: | ||
validate_mock_set(mock_set) | ||
defaults = defaults or {} | ||
lookup = attrs.copy() | ||
attrs.update(defaults) | ||
results = filter(**lookup) | ||
if not results.exists(): | ||
return create(**attrs), True | ||
elif results.count() > 1: | ||
|
@@ -255,25 +263,46 @@ def values(*fields): | |
return mock_set | ||
|
||
|
||
def MockModel(cls=None, mock_name=None, spec_set=None, **attrs): | ||
mock_attrs = dict(spec=cls, name=mock_name, spec_set=spec_set) | ||
class MockModel(dict): | ||
def __init__(self, *args, **kwargs): | ||
self.save = PropertyMock() | ||
super(MockModel, self).__init__(*args, **kwargs) | ||
|
||
def __getattr__(self, item): | ||
return self.get(item, None) | ||
|
||
def __setattr__(self, key, value): | ||
self.__setitem__(key, value) | ||
|
||
def __hash__(self): | ||
return hash(tuple(sorted(self.items()))) | ||
|
||
_meta = type('_meta', (object,), dict( | ||
_forward_fields_map={}, fields_map={}, parents={}, | ||
concrete_fields=[type('concrete_field', (object,), dict(attname=x)) for x in attrs.keys()])) | ||
@property | ||
def _meta(self): | ||
keys_list = list(self.keys()) | ||
keys_list.remove('save') | ||
return MockOptions(*keys_list) | ||
|
||
mock_model = MagicMock(**mock_attrs) | ||
|
||
if mock_name: | ||
setattr(type(mock_model), '__repr__', MagicMock(return_value=mock_name)) | ||
def create_model(*fields): | ||
if len(fields) == 0: | ||
raise ValueError('create_model() is called without fields specified') | ||
return MockModel(**{f: None for f in fields}) | ||
|
||
for key, value in attrs.items(): | ||
setattr(type(mock_model), key, PropertyMock(return_value=value)) | ||
|
||
setattr(type(mock_model), '_meta', PropertyMock(return_value=_meta)) | ||
class MockOptions(object): | ||
def __init__(self, *fields): | ||
for key in ('_forward_fields_map', 'parents', 'fields_map'): | ||
self.__dict__[key] = {} | ||
for key in ('local_concrete_fields', 'concrete_fields', 'fields'): | ||
self.__dict__[key] = [] | ||
|
||
return mock_model | ||
for field in fields: | ||
for key in ('local_concrete_fields', 'concrete_fields', 'fields'): | ||
self.__dict__[key].append(MockField(field)) | ||
|
||
|
||
def empty_func(*args, **kwargs): | ||
pass | ||
class MockField(object): | ||
def __init__(self, field): | ||
for key in ('name', 'attname'): | ||
self.__dict__[key] = field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we remove
.cls
now that we have. model
that covers everything? It has a_meta
with all the fields required forcreate
andget_or_create
etc.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it, but had to do some tweaks with MockModel class.
Also create() now fills fields that are not specified in kwargs with None.