Skip to content

Commit

Permalink
Merge a5c0884 into d49db73
Browse files Browse the repository at this point in the history
  • Loading branch information
K0Te committed Oct 9, 2018
2 parents d49db73 + a5c0884 commit 26ac783
Showing 1 changed file with 64 additions and 51 deletions.
115 changes: 64 additions & 51 deletions django_object_manager/object_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def __init__(self):
"""Initialize object creator."""
self._instances = defaultdict(dict)
self._converters = {ForeignKey: self._create_foreing,
ManyToManyRel: self._create_m2m,
ManyToManyField: self._create_m2m,
ManyToManyRel: self._create_m2m_rel,
ManyToManyField: self._create_m2m_field,
DateTimeField: self._parse_datetime,
OneToOneRel: self._make1to1}

Expand Down Expand Up @@ -69,7 +69,7 @@ def with_context(self, context):
return ContextCallable(self, context)

def __call__(self, *args, **kwargs):
"""Create object(s)."""
"""Implemented to provide better error message."""
raise RuntimeError(
'object_manager() instead of object_manager.get_`model_name`()')

Expand Down Expand Up @@ -97,46 +97,21 @@ def _get(self, _name, _key):
return self._instances[_name][_key]
return None

# TODO: Decrease complexity of the function
def _create_dependencies(self, model, params): # noqa
post_add = []
def _create_dependencies(self, model, params):
cbs = []
for field in model._meta.get_fields():
if field.name in params:
if params[field.name] is None:
continue
for (converter_type, converter) in self._converters.items():
if isinstance(field, converter_type):
params[field.name] = converter(field,
# TODO use namedtuple here ?
params[field.name], cbs, pop_params = converter(field,
params[field.name])
if isinstance(field, ManyToManyRel):
def cb(field, field_val, instance):
args = \
{instance._meta.model.__name__.lower(): instance,
field.related_model.__name__.lower(): field_val}
# Create dependency after main object, using
# M2M "through" model
field.through(**args).save()
for related_val in params.pop(field.name):
post_add.append(partial(cb, field, related_val))
elif isinstance(field, OneToOneRel):
def cb(field_val, instance):
setattr(field_val,
instance._meta.model.__name__.lower(),
instance)
# Delay 1-to-1 dependency object creation
field_val.save()
val = params.pop(field.name)
post_add.append(partial(cb, val))
elif isinstance(field, ManyToManyField):
def cb(field, field_val, instance):
field_val.save()
# Delay forward M2M dependency,
# use RelatedManager helper
# TODO no related manager if `through` model has extra attributes ?
getattr(instance, field.name).add(field_val)
for related_val in params.pop(field.name):
post_add.append(partial(cb, field, related_val))
return post_add
if pop_params:
params.pop(field.name)

return cbs

def _get_or_create(self, _name, _key, _create_in_db=True, _custom=False,
**kwargs):
Expand All @@ -160,39 +135,77 @@ def _create_foreing(self, field, value):
foreing_model = field.remote_field.model
name = foreing_model.__name__.lower()
if isinstance(value, foreing_model):
return value
return value, [], False
else:
assert isinstance(value, str), \
'Related values must be either instances or str ids'
# TODO Use type to select related model, name can be misleading !
return self._get_or_create(name,
value,
**self._data[name][value])

def _create_m2m(self, field, values):
**self._data[name][value]), [], False

# TODO M2M are similar, probably they can be combined
def _create_m2m_rel(self, field, values):
def cb(field, field_val, instance):
args = \
{instance._meta.model.__name__.lower(): instance,
field.related_model.__name__.lower(): field_val}
# Create dependency after main object, using
# M2M "through" model
field.through(**args).save()
foreing_model = field.related_model
name = foreing_model.__name__.lower()
def gen():
for value in values:
if isinstance(value, foreing_model):
yield value
else:
assert isinstance(value, str), \
'Related values must be either instances or str ids'
yield self._get_or_create(name,
value,
**self._data[name][value])
return gen(), [partial(cb, field, related_val) for related_val in values], True

def _create_m2m_field(self, field, values):
def cb(field, field_val, instance):
field_val.save()
# Delay forward M2M dependency,
# use RelatedManager helper
# TODO no related manager if `through` model has extra attributes ???
getattr(instance, field.name).add(field_val)
foreing_model = field.related_model
name = foreing_model.__name__.lower()
for value in values:
if isinstance(value, foreing_model):
yield value
else:
assert isinstance(value, str), \
'Related values must be either instances or str ids'
yield self._get_or_create(name,
value,
**self._data[name][value])
def gen():
for value in values:
if isinstance(value, foreing_model):
yield value
else:
assert isinstance(value, str), \
'Related values must be either instances or str ids'
yield self._get_or_create(name,
value,
**self._data[name][value])
res = list(gen())
return res, [partial(cb, field, related_val) for related_val in res], True

def _make1to1(self, field, value):
foreing_model = field.related_model
name = foreing_model.__name__.lower()
assert isinstance(value, str)
assert value not in self._instances[name]
# DB record will be created during "main" model creation
def cb(field_val, instance):
setattr(field_val,
instance._meta.model.__name__.lower(),
instance)
# Delay 1-to-1 dependency object creation
field_val.save()
return self._get_or_create(name, value, _create_in_db=False,
**self._data[name][value])
**self._data[name][value]), [partial(cb, value)], [], False

def _parse_datetime(self, _field, value):
return datetime.strptime(value, '%b %d %Y').replace(tzinfo=utc)
return datetime.strptime(value, '%b %d %Y').replace(tzinfo=utc), [], False


class ObjManagerMixin:
Expand Down

0 comments on commit 26ac783

Please sign in to comment.