diff -r aeebc5cc7a4c django/db/models/fields/related.py
a
|
b
|
|
9 | 9 | from django.db.models.deletion import CASCADE |
10 | 10 | from django.utils.encoding import smart_unicode |
11 | 11 | from django.utils.translation import ugettext_lazy as _, string_concat |
12 | | from django.utils.functional import curry |
| 12 | from django.utils.functional import curry, memoize, cached_property |
13 | 13 | from django.core import exceptions |
14 | 14 | from django import forms |
15 | 15 | |
… |
… |
|
386 | 386 | if instance is None: |
387 | 387 | return self |
388 | 388 | |
389 | | return self.create_manager(instance, |
390 | | self.related.model._default_manager.__class__) |
| 389 | manager = self.create_manager(self.related.model._default_manager.__class__) |
| 390 | return manager(instance) |
391 | 391 | |
392 | 392 | def __set__(self, instance, value): |
393 | 393 | if instance is None: |
… |
… |
|
406 | 406 | than the default manager, as returned by __get__). Used by |
407 | 407 | Model.delete(). |
408 | 408 | """ |
409 | | return self.create_manager(instance, |
410 | | self.related.model._base_manager.__class__) |
| 409 | manager = self.create_manager(self.related.model._base_manager.__class__) |
| 410 | return manager(instance) |
411 | 411 | |
412 | | def create_manager(self, instance, superclass): |
| 412 | def create_manager(self, superclass): |
413 | 413 | """ |
414 | 414 | Creates the managers used by other methods (__get__() and delete()). |
415 | 415 | """ |
| 416 | |
| 417 | # We use closures for these values so that we only need to memoize this |
| 418 | # function on the one argument of 'superclass', and the two places that |
| 419 | # call create_manager simply need to pass instance to the manager |
| 420 | # __init__ |
416 | 421 | rel_field = self.related.field |
| 422 | rel_model = self.related.model |
| 423 | attname = rel_field.rel.get_related_field().attname |
| 424 | |
417 | 425 | class RelatedManager(superclass): |
418 | | def __init__(self, model=None, core_filters=None, instance=None, |
419 | | rel_field=None): |
| 426 | def __init__(self, instance): |
420 | 427 | super(RelatedManager, self).__init__() |
421 | | self.model = model |
422 | | self.core_filters = core_filters |
423 | 428 | self.instance = instance |
424 | | self.rel_field = rel_field |
| 429 | self.core_filters = { |
| 430 | '%s__%s' % (rel_field.name, attname): getattr(instance, attname) |
| 431 | } |
| 432 | self.model = rel_model |
425 | 433 | |
426 | 434 | def get_query_set(self): |
427 | 435 | db = self._db or router.db_for_read(self.model, instance=self.instance) |
… |
… |
|
431 | 439 | for obj in objs: |
432 | 440 | if not isinstance(obj, self.model): |
433 | 441 | raise TypeError("'%s' instance expected" % self.model._meta.object_name) |
434 | | setattr(obj, self.rel_field.name, self.instance) |
| 442 | setattr(obj, rel_field.name, self.instance) |
435 | 443 | obj.save() |
436 | 444 | add.alters_data = True |
437 | 445 | |
438 | 446 | def create(self, **kwargs): |
439 | | kwargs[self.rel_field.name] = self.instance |
| 447 | kwargs[rel_field.name] = self.instance |
440 | 448 | db = router.db_for_write(self.model, instance=self.instance) |
441 | 449 | return super(RelatedManager, self.db_manager(db)).create(**kwargs) |
442 | 450 | create.alters_data = True |
… |
… |
|
444 | 452 | def get_or_create(self, **kwargs): |
445 | 453 | # Update kwargs with the related object that this |
446 | 454 | # ForeignRelatedObjectsDescriptor knows about. |
447 | | kwargs[self.rel_field.name] = self.instance |
| 455 | kwargs[rel_field.name] = self.instance |
448 | 456 | db = router.db_for_write(self.model, instance=self.instance) |
449 | 457 | return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs) |
450 | 458 | get_or_create.alters_data = True |
… |
… |
|
452 | 460 | # remove() and clear() are only provided if the ForeignKey can have a value of null. |
453 | 461 | if rel_field.null: |
454 | 462 | def remove(self, *objs): |
455 | | val = getattr(self.instance, self.rel_field.rel.get_related_field().attname) |
| 463 | val = getattr(self.instance, attname) |
456 | 464 | for obj in objs: |
457 | 465 | # Is obj actually part of this descriptor set? |
458 | | if getattr(obj, self.rel_field.attname) == val: |
459 | | setattr(obj, self.rel_field.name, None) |
| 466 | if getattr(obj, rel_field.attname) == val: |
| 467 | setattr(obj, rel_field.name, None) |
460 | 468 | obj.save() |
461 | 469 | else: |
462 | | raise self.rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance)) |
| 470 | raise rel_field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance)) |
463 | 471 | remove.alters_data = True |
464 | 472 | |
465 | 473 | def clear(self): |
466 | | self.update(**{self.rel_field.name: None}) |
| 474 | self.update(**{rel_field.name: None}) |
467 | 475 | clear.alters_data = True |
468 | 476 | |
469 | | attname = rel_field.rel.get_related_field().name |
470 | | return RelatedManager(model=self.related.model, |
471 | | core_filters = {'%s__%s' % (rel_field.name, attname): |
472 | | getattr(instance, attname)}, |
473 | | instance=instance, |
474 | | rel_field=rel_field, |
475 | | ) |
| 477 | return RelatedManager |
| 478 | create_manager = memoize(create_manager, {}, 2) |
476 | 479 | |
477 | 480 | |
478 | 481 | def create_many_related_manager(superclass, rel): |
… |
… |
|
663 | 666 | def __init__(self, related): |
664 | 667 | self.related = related # RelatedObject instance |
665 | 668 | |
| 669 | @cached_property |
| 670 | def related_manager_cls(self): |
| 671 | # Dynamically create a class that subclasses the related |
| 672 | # model's default manager. |
| 673 | return create_many_related_manager( |
| 674 | self.related.model._default_manager.__class__, |
| 675 | self.related.field.rel |
| 676 | ) |
| 677 | |
666 | 678 | def __get__(self, instance, instance_type=None): |
667 | 679 | if instance is None: |
668 | 680 | return self |
669 | 681 | |
670 | | # Dynamically create a class that subclasses the related |
671 | | # model's default manager. |
672 | 682 | rel_model = self.related.model |
673 | | superclass = rel_model._default_manager.__class__ |
674 | | RelatedManager = create_many_related_manager(superclass, self.related.field.rel) |
675 | 683 | |
676 | | manager = RelatedManager( |
| 684 | manager = self.related_manager_cls( |
677 | 685 | model=rel_model, |
678 | 686 | core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, |
679 | 687 | instance=instance, |
… |
… |
|
716 | 724 | # a property to ensure that the fully resolved value is returned. |
717 | 725 | return self.field.rel.through |
718 | 726 | |
| 727 | @cached_property |
| 728 | def related_manager_cls(self): |
| 729 | # Dynamically create a class that subclasses the related model's |
| 730 | # default manager. |
| 731 | return create_many_related_manager( |
| 732 | self.field.rel.to._default_manager.__class__, |
| 733 | self.field.rel |
| 734 | ) |
| 735 | |
719 | 736 | def __get__(self, instance, instance_type=None): |
720 | 737 | if instance is None: |
721 | 738 | return self |
722 | 739 | |
723 | | # Dynamically create a class that subclasses the related |
724 | | # model's default manager. |
725 | | rel_model=self.field.rel.to |
726 | | superclass = rel_model._default_manager.__class__ |
727 | | RelatedManager = create_many_related_manager(superclass, self.field.rel) |
728 | | |
729 | | manager = RelatedManager( |
730 | | model=rel_model, |
| 740 | manager = self.related_manager_cls( |
| 741 | model=self.field.rel.to, |
731 | 742 | core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, |
732 | 743 | instance=instance, |
733 | 744 | symmetrical=self.field.rel.symmetrical, |
diff -r aeebc5cc7a4c django/utils/functional.py
a
|
b
|
|
28 | 28 | return result |
29 | 29 | return wrapper |
30 | 30 | |
| 31 | class cached_property(object): |
| 32 | """ |
| 33 | Decorator that creates converts a method with a single |
| 34 | self argument into a property cached on the instance. |
| 35 | """ |
| 36 | def __init__(self, func): |
| 37 | self.func = func |
| 38 | |
| 39 | def __get__(self, instance, type): |
| 40 | res = instance.__dict__[self.func.__name__] = self.func(instance) |
| 41 | return res |
| 42 | |
31 | 43 | class Promise(object): |
32 | 44 | """ |
33 | 45 | This is just a base class for the proxy class created in |
… |
… |
|
288 | 300 | results = ([], []) |
289 | 301 | for item in values: |
290 | 302 | results[predicate(item)].append(item) |
291 | | return results |
292 | | No newline at end of file |
| 303 | return results |
diff -r aeebc5cc7a4c tests/modeltests/many_to_one/tests.py
a
|
b
|
|
399 | 399 | self.assertEqual(repr(a3), |
400 | 400 | repr(Article.objects.get(reporter_id=self.r2.id, |
401 | 401 | pub_date=datetime(2011, 5, 7)))) |
| 402 | |
| 403 | def test_manager_class_caching(self): |
| 404 | r1 = Reporter.objects.create(first_name='Mike') |
| 405 | r2 = Reporter.objects.create(first_name='John') |
| 406 | |
| 407 | # Same twice |
| 408 | self.assertTrue(r1.article_set.__class__ is r1.article_set.__class__) |
| 409 | |
| 410 | # Same as each other |
| 411 | self.assertTrue(r1.article_set.__class__ is r2.article_set.__class__) |
diff -r aeebc5cc7a4c tests/regressiontests/m2m_regress/tests.py
a
|
b
|
|
73 | 73 | |
74 | 74 | self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"]) |
75 | 75 | self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"]) |
| 76 | |
| 77 | def test_manager_class_caching(self): |
| 78 | e1 = Entry.objects.create() |
| 79 | e2 = Entry.objects.create() |
| 80 | t1 = Tag.objects.create() |
| 81 | t2 = Tag.objects.create() |
| 82 | |
| 83 | # Get same manager twice in a row: |
| 84 | self.assertTrue(t1.entry_set.__class__ is t1.entry_set.__class__) |
| 85 | self.assertTrue(e1.topics.__class__ is e1.topics.__class__) |
| 86 | |
| 87 | # Get same manager for different instances |
| 88 | self.assertTrue(e1.topics.__class__ is e2.topics.__class__) |
| 89 | self.assertTrue(t1.entry_set.__class__ is t2.entry_set.__class__) |