Ticket #14891: patch.diff

File patch.diff, 6.7 KB (added by palkeo, 6 years ago)

New version of the patch adding custom managers for ForeignKey fields

  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    index 1e7e73d..c385446 100644
    a b class ReverseSingleRelatedObjectDescriptor(six.with_metaclass(RenameRelatedObjec 
    251251    # a single "remote" value, on the class that defines the related field.
    252252    # In the example "choice.poll", the poll attribute is a
    253253    # ReverseSingleRelatedObjectDescriptor instance.
    254     def __init__(self, field_with_rel):
     254    def __init__(self, field_with_rel, manager_class=None):
    255255        self.field = field_with_rel
    256256        self.cache_name = self.field.get_cache_name()
    257257
     258        if manager_class is None:
     259            self.manager = None
     260        else:
     261            self.manager = manager_class()
     262            self.manager.model = self.field.rel.to
     263
     264
    258265    def is_cached(self, instance):
    259266        return hasattr(instance, self.cache_name)
    260267
    261268    def get_queryset(self, **db_hints):
    262269        db = router.db_for_read(self.field.rel.to, **db_hints)
    263         rel_mgr = self.field.rel.to._default_manager
     270
     271        if self.manager is not None:
     272            return self.manager.using(db)
     273
    264274        # If the related manager indicates that it should be used for
    265275        # related fields, respect that.
     276        rel_mgr = self.field.rel.to._default_manager
    266277        if getattr(rel_mgr, 'use_for_related_fields', False):
    267278            return rel_mgr.using(db)
    268279        else:
    class ForeignRelatedObjectsDescriptor(object): 
    368379    # multiple "remote" values and have a ForeignKey pointed at them by
    369380    # some other model. In the example "poll.choice_set", the choice_set
    370381    # attribute is a ForeignRelatedObjectsDescriptor instance.
    371     def __init__(self, related):
     382    def __init__(self, related, manager_class=None):
    372383        self.related = related   # RelatedObject instance
     384        self.manager_class = manager_class
    373385
    374386    def __get__(self, instance, instance_type=None):
    375387        if instance is None:
    class ForeignRelatedObjectsDescriptor(object): 
    389401    def related_manager_cls(self):
    390402        # Dynamically create a class that subclasses the related model's default
    391403        # manager.
    392         superclass = self.related.model._default_manager.__class__
     404        superclass = self.manager_class or self.related.model._default_manager.__class__
    393405        rel_field = self.related.field
    394406        rel_model = self.related.model
    395407
    class ForeignObject(RelatedField): 
    939951                parent_link=kwargs.pop('parent_link', False),
    940952                on_delete=kwargs.pop('on_delete', CASCADE),
    941953            )
     954
    942955        kwargs['verbose_name'] = kwargs.get('verbose_name', None)
    943956
     957        self.manager_class = kwargs.pop('manager_class', None)
     958        self.reverse_manager_class = kwargs.pop('reverse_manager_class', None)
     959
    944960        super(ForeignObject, self).__init__(**kwargs)
    945961
    946962    def resolve_related_fields(self):
    class ForeignObject(RelatedField): 
    11041120
    11051121    def contribute_to_class(self, cls, name, virtual_only=False):
    11061122        super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
    1107         setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
     1123
     1124        setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self, self.manager_class))
    11081125
    11091126    def contribute_to_related_class(self, cls, related):
    11101127        # Internal FK's - i.e., those with a related name ending with '+' -
    11111128        # and swapped models don't get a related descriptor.
    11121129        if not self.rel.is_hidden() and not related.model._meta.swapped:
    1113             setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related))
     1130
     1131            setattr(cls, related.get_accessor_name(), ForeignRelatedObjectsDescriptor(related, self.reverse_manager_class))
    11141132            if self.rel.limit_choices_to:
    11151133                cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to)
    11161134
  • new file tests/custom_field_managers/models.py

    diff --git a/tests/custom_field_managers/__init__.py b/tests/custom_field_managers/__init__.py
    new file mode 100644
    index 0000000..e69de29
    diff --git a/tests/custom_field_managers/models.py b/tests/custom_field_managers/models.py
    new file mode 100644
    index 0000000..30dbb94
    - +  
     1"""
     2Custom manager for relationship fields.
     3
     4Using the ``manager`` and ``reverse_manager`` when creating a ForeignKey, you can
     5choose custom managers used for direct and reverse relationship.
     6
     7Note: You will rarely need to define a custom ``manager`` for a ForeignKey,
     8      execept when you want to filter out the queryset that will be
     9      used to retrieve the related object.
     10
     11"""
     12
     13from __future__ import unicode_literals
     14
     15from django.db import models
     16from django.utils.encoding import python_2_unicode_compatible
     17
     18
     19class ArticlesManager(models.Manager):
     20    def get_articles_starting_with_a(self):
     21        return self.filter(name__startswith='a')
     22
     23class DeletedManager(models.Manager):
     24    def get_queryset(self):
     25        return super(DeletedManager, self).get_queryset().filter(deleted=False)
     26
     27
     28@python_2_unicode_compatible
     29class Author(models.Model):
     30    name = models.CharField(max_length=30)
     31    deleted = models.BooleanField(default=False)
     32
     33
     34@python_2_unicode_compatible
     35class Article(models.Model):
     36    name = models.CharField(max_length=30)
     37    author = models.ForeignKey(Author, manager_class=DeletedManager, reverse_manager_class=ArticlesManager, related_name='articles')
     38
     39    def __str__(self):
     40        return self.name
  • new file tests/custom_field_managers/tests.py

    diff --git a/tests/custom_field_managers/tests.py b/tests/custom_field_managers/tests.py
    new file mode 100644
    index 0000000..b2cc65f
    - +  
     1from __future__ import absolute_import
     2
     3from django.test import TestCase
     4from django.utils import six
     5
     6from .models import Author, Article, ArticlesManager
     7
     8
     9class CustomFieldsManagerTests(TestCase):
     10    def test_manager(self):
     11        author = Author.objects.create(name='The author')
     12        article = Article.objects.create(name='The article', author=author)
     13
     14        # Let's see if we have our custom ArticlesManager, for the reverse relationship.
     15        self.assertIsInstance(author.articles, ArticlesManager)
     16        self.assertQuerysetEqual(author.articles.get_articles_starting_with_a(), [])
     17
     18        self.assertEqual(article.author, author)
     19
     20        author.deleted = True
     21        author.save()
     22
     23        article = Article.objects.all()[0] # If we reuse the same article, the author is cached.
     24
     25        # Because our forward manager will mask a deleted author, this will raise an exception.
     26        self.assertRaises(Author.DoesNotExist, lambda: article.author)
Back to Top