Repository: carltongibson/django-filter

Issue:
Support callables as limit_choices_to
According to https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to
 it could be callable

```
 Either a dictionary, a Q object, or a callable returning a dictionary or Q object can be used.
```



Discussion:

Resolving patch:
diff --git a/django_filters/compat.py b/django_filters/compat.py
--- a/django_filters/compat.py
+++ b/django_filters/compat.py
@@ -15,3 +15,10 @@ def remote_model(field):
     if django.VERSION >= (1, 9):
         return remote_field(field).model
     return remote_field(field).to
+
+
+def remote_queryset(field):
+    model = remote_model(field)
+    limit_choices_to = field.get_limit_choices_to()
+
+    return model._default_manager.complex_filter(limit_choices_to)
diff --git a/django_filters/filterset.py b/django_filters/filterset.py
--- a/django_filters/filterset.py
+++ b/django_filters/filterset.py
@@ -15,7 +15,7 @@
 from django.utils.text import capfirst
 from django.utils.translation import ugettext as _
 
-from .compat import remote_field, remote_model
+from .compat import remote_field, remote_model, remote_queryset
 from .filters import (Filter, CharFilter, BooleanFilter, BaseInFilter, BaseRangeFilter,
                       ChoiceFilter, DateFilter, DateTimeFilter, TimeFilter, ModelChoiceFilter,
                       ModelMultipleChoiceFilter, NumberFilter, UUIDFilter,
@@ -203,24 +203,21 @@ def __new__(cls, name, bases, attrs):
     models.OneToOneField: {
         'filter_class': ModelChoiceFilter,
         'extra': lambda f: {
-            'queryset': remote_model(f)._default_manager.complex_filter(
-                remote_field(f).limit_choices_to),
+            'queryset': remote_queryset(f),
             'to_field_name': remote_field(f).field_name,
         }
     },
     models.ForeignKey: {
         'filter_class': ModelChoiceFilter,
         'extra': lambda f: {
-            'queryset': remote_model(f)._default_manager.complex_filter(
-                remote_field(f).limit_choices_to),
-            'to_field_name': remote_field(f).field_name
+            'queryset': remote_queryset(f),
+            'to_field_name': remote_field(f).field_name,
         }
     },
     models.ManyToManyField: {
         'filter_class': ModelMultipleChoiceFilter,
         'extra': lambda f: {
-            'queryset': remote_model(f)._default_manager.complex_filter(
-                remote_field(f).limit_choices_to),
+            'queryset': remote_queryset(f),
         }
     },
     models.DecimalField: {


Test patch:
diff --git a/tests/models.py b/tests/models.py
--- a/tests/models.py
+++ b/tests/models.py
@@ -6,10 +6,14 @@
 from django.utils.encoding import python_2_unicode_compatible
 
 
+REGULAR = 0
+MANAGER = 1
+ADMIN = 2
+
 STATUS_CHOICES = (
-    (0, 'Regular'),
-    (1, 'Manager'),
-    (2, 'Admin'),
+    (REGULAR, 'Regular'),
+    (MANAGER, 'Manager'),
+    (ADMIN, 'Admin'),
 )
 
 
@@ -54,6 +58,32 @@ class User(models.Model):
     def __str__(self):
         return self.username
 
+@python_2_unicode_compatible
+class UsersOfManager(models.Model):
+    users = models.ManyToManyField(User,
+                                   limit_choices_to={'is_active': True},
+                                   related_name='users_of_manager')
+    manager = models.ForeignKey(User,
+                                limit_choices_to=lambda: {'status': 1},
+                                related_name='his_users')
+
+    def __str__(self):
+        return self.manager.name + '_group'
+
+
+@python_2_unicode_compatible
+class ManagerGroup(models.Model):
+    users = models.ManyToManyField(User,
+                                   limit_choices_to={'is_active': True},
+                                   related_name='member_of')
+    manager = models.ForeignKey(User,
+                                limit_choices_to=lambda: {'status': MANAGER},
+                                related_name='manager_of',
+                                on_delete=models.CASCADE)
+
+    def __str__(self):
+        return self.manager.name + ' group'
+
 
 @python_2_unicode_compatible
 class AdminUser(User):
diff --git a/tests/test_forms.py b/tests/test_forms.py
--- a/tests/test_forms.py
+++ b/tests/test_forms.py
@@ -8,9 +8,9 @@
 from django_filters.filters import CharFilter
 from django_filters.filters import ChoiceFilter
 
-from .models import User
+from .models import User, ManagerGroup
 from .models import Book
-from .models import STATUS_CHOICES
+from .models import STATUS_CHOICES, REGULAR, MANAGER
 
 
 class FilterSetFormTests(TestCase):
@@ -229,7 +229,7 @@ class Meta:
         self.assertNotIn('o', f.fields)
         self.assertIn('order', f.fields)
         self.assertEqual(f.fields['order'].choices, [('status', 'Status')])
-    
+
     def test_ordering_with_overridden_field_name_and_descending(self):
         """
         Set the `order_by_field` on the queryset and ensure that the
@@ -272,3 +272,20 @@ class Meta:
         f = F().form
         self.assertEqual(
             f.fields['o'].choices, [('status', 'Current status')])
+
+    def test_limit_choices_to(self):
+        User.objects.create(username='inactive', is_active=False, status=REGULAR)
+        User.objects.create(username='active', is_active=True, status=REGULAR)
+        User.objects.create(username='manager', is_active=False, status=MANAGER)
+
+        class F(FilterSet):
+            class Meta:
+                model = ManagerGroup
+                fields = ['users', 'manager']
+        f = F().form
+        self.assertEquals(
+            list(f.fields['users'].choices), [(2, 'active')]
+        )
+        self.assertEquals(
+            list(f.fields['manager'].choices), [('', '---------'), (3, 'manager')]
+        )