def create_deferring_foreign_related_manager(related, original_manager_cls):
relation_name = related.get_accessor_name()<EOL>rel_field = related.field<EOL>rel_model = related.related_model<EOL>superclass = rel_model._default_manager.__class__<EOL>class DeferringRelatedManager(superclass):<EOL><INDENT>def __init__(self, instance):<EOL><INDENT>super(DeferringRelatedManager, self).__init__()<EOL>self.model = rel_model<EOL>self.instance = instance<EOL><DEDENT>def _get_cluster_related_objects(self):<EOL><INDENT>try:<EOL><INDENT>return self.instance._cluster_related_objects<EOL><DEDENT>except AttributeError:<EOL><INDENT>cluster_related_objects = {}<EOL>self.instance._cluster_related_objects = cluster_related_objects<EOL>return cluster_related_objects<EOL><DEDENT><DEDENT>def get_live_query_set(self):<EOL><INDENT>return self.get_live_queryset()<EOL><DEDENT>def get_live_queryset(self):<EOL><INDENT>"""<STR_LIT>"""<EOL>return original_manager_cls(self.instance).get_queryset()<EOL><DEDENT>def get_queryset(self):<EOL><INDENT>"""<STR_LIT>"""<EOL>try:<EOL><INDENT>results = self.instance._cluster_related_objects[relation_name]<EOL><DEDENT>except (AttributeError, KeyError):<EOL><INDENT>return self.get_live_queryset()<EOL><DEDENT>return FakeQuerySet(related.related_model, results)<EOL><DEDENT>def _apply_rel_filters(self, queryset):<EOL><INDENT>return queryset._next_is_sticky()<EOL><DEDENT>def get_prefetch_queryset(self, instances, queryset=None):<EOL><INDENT>if queryset is None:<EOL><INDENT>db = self._db or router.db_for_read(self.model, instance=instances[<NUM_LIT:0>])<EOL>queryset = super(DeferringRelatedManager, self).get_queryset().using(db)<EOL><DEDENT>rel_obj_attr = rel_field.get_local_related_value<EOL>instance_attr = rel_field.get_foreign_related_value<EOL>instances_dict = dict((instance_attr(inst), inst) for inst in instances)<EOL>query = {'<STR_LIT>' % rel_field.name: instances}<EOL>qs = queryset.filter(**query)<EOL>for rel_obj in qs:<EOL><INDENT>instance = instances_dict[rel_obj_attr(rel_obj)]<EOL>setattr(rel_obj, rel_field.name, instance)<EOL><DEDENT>cache_name = rel_field.related_query_name()<EOL>return qs, rel_obj_attr, instance_attr, False, cache_name, False<EOL><DEDENT>def get_object_list(self):<EOL><INDENT>"""<STR_LIT>"""<EOL>cluster_related_objects = self._get_cluster_related_objects()<EOL>try:<EOL><INDENT>object_list = cluster_related_objects[relation_name]<EOL><DEDENT>except KeyError:<EOL><INDENT>object_list = list(self.get_live_queryset())<EOL>cluster_related_objects[relation_name] = object_list<EOL><DEDENT>return object_list<EOL><DEDENT>def add(self, *new_items):<EOL><INDENT>"""<STR_LIT>"""<EOL>items = self.get_object_list()<EOL>for target in new_items:<EOL><INDENT>item_matched = False<EOL>for i, item in enumerate(items):<EOL><INDENT>if item == target:<EOL><INDENT>items[i] = target<EOL>item_matched = True<EOL>break<EOL><DEDENT><DEDENT>if not item_matched:<EOL><INDENT>items.append(target)<EOL><DEDENT>setattr(target, related.field.name, self.instance)<EOL><DEDENT>if rel_model._meta.ordering and len(items) > <NUM_LIT:1>:<EOL><INDENT>sort_by_fields(items, rel_model._meta.ordering)<EOL><DEDENT><DEDENT>def remove(self, *items_to_remove):<EOL><INDENT>"""<STR_LIT>"""<EOL>items = self.get_object_list()<EOL>items[:] = [item for item in items if item not in items_to_remove]<EOL><DEDENT>def create(self, **kwargs):<EOL><INDENT>items = self.get_object_list()<EOL>new_item = related.related_model(**kwargs)<EOL>items.append(new_item)<EOL>return new_item<EOL><DEDENT>def clear(self):<EOL><INDENT>"""<STR_LIT>"""<EOL>self.set([])<EOL><DEDENT>def set(self, objs, bulk=True, clear=False):<EOL><INDENT>objs = list(objs)<EOL>cluster_related_objects = self._get_cluster_related_objects()<EOL>for obj in objs:<EOL><INDENT>setattr(obj, related.field.name, self.instance)<EOL><DEDENT>if rel_model._meta.ordering and len(objs) > <NUM_LIT:1>:<EOL><INDENT>sort_by_fields(objs, rel_model._meta.ordering)<EOL><DEDENT>cluster_related_objects[relation_name] = objs<EOL><DEDENT>def commit(self):<EOL><INDENT>"""<STR_LIT>"""<EOL>if self.instance.pk is None:<EOL><INDENT>raise IntegrityError("<STR_LIT>" % relation_name)<EOL><DEDENT>try:<EOL><INDENT>final_items = self.instance._cluster_related_objects[relation_name]<EOL><DEDENT>except (AttributeError, KeyError):<EOL><INDENT>return<EOL><DEDENT>original_manager = original_manager_cls(self.instance)<EOL>live_items = list(original_manager.get_queryset())<EOL>for item in live_items:<EOL><INDENT>if item not in final_items:<EOL><INDENT>item.delete()<EOL><DEDENT><DEDENT>for item in final_items:<EOL><INDENT>original_manager.add(item, bulk=False)<EOL><DEDENT>del self.instance._cluster_related_objects[relation_name]<EOL><DEDENT><DEDENT>return DeferringRelatedManager<EOL>
Create a DeferringRelatedManager class that wraps an ordinary RelatedManager
with 'deferring' behaviour: any updates to the object set (via e.g. add() or clear())
are written to a holding area rather than committed to the database immediately.
Writing to the database is deferred until the model is saved.