Ticket #2445: choices_for_foo.diff

File choices_for_foo.diff, 5.8 KB (added by stj, 4 years ago)

patch against r15803

  • docs/ref/models/fields.txt

     
    999999      DELETE`` constraint to the database field (perhaps using
    10001000      :ref:`initial sql<initial-sql>`).
    10011001
     1002.. versionadded:: 1.4
     1003
     1004.. attribute:: Model.choices_for_FOO()
     1005
     1006    It is possible to limit choices with respect to ``self``. Creat a method called
     1007    ``choices_for_FOO``, where ``FOO`` is the name of the field. The method needs
     1008    to return a QuerySet::
     1009
     1010        class MyModel(models.Model):
     1011            user = models.ForeignKey(User)
     1012           
     1013            def choices_for_user(self):
     1014                # Make sure only active and currently associated users are in choices.
     1015                return self.__class__.objects.exclude(is_active=False)|
     1016                       self.__class__.objects.filter(id=self.user)
     1017
    10021018.. _ref-manytomany:
    10031019
    10041020``ManyToManyField``
  • django/db/models/base.py

     
    353353            else:
    354354                setattr(self, field.attname, val)
    355355
     356        for field in self._meta.fields:
     357            # Set the model field choices function on the field
     358            # Refs #2445
     359            if field.rel is None:
     360                continue
     361            choice_func = getattr(self, 'choices_for_%s' % field.name, None)
     362            if callable(choice_func):
     363                field.queryset = choice_func
     364
    356365        if kwargs:
    357366            for prop in kwargs.keys():
    358367                try:
  • django/db/models/fields/__init__.py

     
    362362        first_choice = include_blank and blank_choice or []
    363363        if self.choices:
    364364            return first_choice + list(self.choices)
    365         rel_model = self.rel.to
    366365        if hasattr(self.rel, 'get_related_field'):
    367             lst = [(getattr(x, self.rel.get_related_field().attname), smart_unicode(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
     366            lst = [(getattr(x, self.rel.get_related_field().attname), smart_unicode(x)) for x in self.queryset]
    368367        else:
    369             lst = [(x._get_pk_val(), smart_unicode(x)) for x in rel_model._default_manager.complex_filter(self.rel.limit_choices_to)]
     368            lst = [(x._get_pk_val(), smart_unicode(x)) for x in self.queryset]
    370369        return first_choice + lst
    371370
    372371    def get_choices_default(self):
     
    447446        "Returns the value of this field in the given model instance."
    448447        return getattr(obj, self.attname)
    449448
     449    def _get_queryset(self):
     450        if hasattr(self, '_queryset'):
     451            return self._queryset()
     452        return self.rel.to._default_manager.complex_filter(self.rel.limit_choices_to)
     453
     454    def _set_queryset(self, qs):
     455        self._queryset = qs
     456
     457    queryset = property(_get_queryset, _set_queryset)
     458
    450459class AutoField(Field):
    451460    description = _("Integer")
    452461
  • django/db/models/fields/related.py

     
    847847            return
    848848
    849849        using = router.db_for_read(model_instance.__class__, instance=model_instance)
    850         qs = self.rel.to._default_manager.using(using).filter(
    851                 **{self.rel.field_name: value}
    852              )
    853         qs = qs.complex_filter(self.rel.limit_choices_to)
     850        qs = self.queryset.using(using).filter(**{self.rel.field_name: value})
    854851        if not qs.exists():
    855852            raise exceptions.ValidationError(self.error_messages['invalid'] % {
    856853                'model': self.rel.to._meta.verbose_name, 'pk': value})
     
    910907        db = kwargs.pop('using', None)
    911908        defaults = {
    912909            'form_class': forms.ModelChoiceField,
    913             'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
     910            'queryset': self.queryset.using(db),
    914911            'to_field_name': self.rel.field_name,
    915912        }
    916913        defaults.update(kwargs)
  • tests/modeltests/choices/tests.py

     
     1from django.db.models.query import QuerySet
    12from django.test import TestCase
    23
    34from models import Person
     
    2122        a.gender = 'U'
    2223        self.assertEqual(a.get_gender_display(), 'U')
    2324
     25    def test_callable(self):
     26        for name, gender, parent in (('Adrian', 'M', None), ('Simon', 'M', 1), ('Karen', 'F', 1), ('Brian', 'F', 2)):
     27            p = Person.objects.create(name=name, gender=gender, parent_id=parent)
     28       
     29        parent = p._meta.get_field('parent')
     30        self.assertIsInstance(parent.queryset, QuerySet)
     31        self.assertNotIn(p, parent.queryset)
  • tests/modeltests/choices/models.py

     
    1919class Person(models.Model):
    2020    name = models.CharField(max_length=20)
    2121    gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
     22    parent = models.ForeignKey('self', blank=True, null=True, related_name='child')
    2223
    2324    def __unicode__(self):
    2425        return self.name
     26
     27    def choices_for_parent(self):
     28        return self.__class__.objects.exclude(id=self.id)
Back to Top