From e3c5f4c0f4375a91c7cf0ddc771dd5e41ea40dbd Mon Sep 17 00:00:00 2001 From: Oleg Date: Tue, 9 Oct 2018 20:02:27 +0300 Subject: [PATCH] Simple dependencies creation. (#10) --- django_object_manager/object_manager.py | 115 +++++++++++++----------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/django_object_manager/object_manager.py b/django_object_manager/object_manager.py index 9cecabe..fac6c5f 100644 --- a/django_object_manager/object_manager.py +++ b/django_object_manager/object_manager.py @@ -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} @@ -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`()') @@ -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): @@ -160,27 +135,59 @@ 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 @@ -188,11 +195,17 @@ def _make1to1(self, field, value): 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: