#28345: limit_choices_to callable is no longer applied during ModelForm
instantiation which blocks some use cases
---------------------------------+------------------------------------
Reporter: drunkard | Owner: nobody
Type: Bug | Status: new
Component: Forms | Version: 1.11
Severity: Release blocker | Resolution:
Keywords: ModelForm | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
---------------------------------+------------------------------------
Changes (by Tim Graham):
* cc: Jon Dufresne (added)
* severity: Normal => Release blocker
* stage: Unreviewed => Accepted
Old description:
> This change breaks inheriting from django.forms.models.ModelForm
> directly.
>
> I'm upgrading to python3.6 + django-1.11.2, and gets wrong, the choice
> field is always empty, caused by this commit
> 6abd6c598ea23e0a962c87b0075aa2f79f9ead36, and it's ticket is
> https://code.djangoproject.com/ticket/27563 . I'm using dynamic
> limit_choices_to function to get user specific filter expr.
>
> It just called once during develop server init, and won't be call while
> open a USER EDIT FORM, which is inherited from ModelForm, but it work in
> django admin edit page, by reading the code, django admin build form
> using modelform_factory(), with this commit reverted, my customized form
> works just fine. So, I think it's a regression.
>
> Or, the usage of ModelForm changed? but newest doc not mentioned.
>
> Below is key part of test project saying the problem:
>
> proj/middleware.py
>
> {{{
> from threading import current_thread
>
> class CUserMiddleware(object):
> """Get current operating request and user"""
> _user = {}
>
> def process_request(self, request):
> self._user[current_thread()] = request.user
>
> def process_response(self, request, response):
> # TODO cleanup by alive session, if user session expired, remove
> all
> # items related to the user (dict value).
> # self._user.pop(current_thread(), None) # clean to avoid it
> grows
> if len(self._user) > 1000:
> self._user = {}
> return response
>
> @classmethod
> def get_user(cls, default=None):
> # debug('当前用户: {}'.format(cls._user.get(current_thread(),
> default)))
> return cls._user.get(current_thread(), default)
>
> def current_user():
> return CUserMiddleware.get_user()
> }}}
>
> testapp/models.py
>
> {{{
> from django.db import models
> from django.db.models import Q
>
> from proj.middleware import current_user
>
> def limit_export_branch():
> u = current_user()
> if u:
> print('\tlimit_export_branch worked the right way')
> return Q(name__endswith='something')
> print('\tlimit_export_branch works in wrong way')
> import traceback
> traceback.print_stack()
> return Q(pk=-1) # deny to avoid info leak
>
> class Branch(models.Model):
> name = models.CharField(max_length=16)
>
> def __str__(self):
> return self.name
>
> class Export(models.Model):
> name = models.CharField(max_length=16)
> branch = models.ForeignKey(Branch,
> limit_choices_to=limit_export_branch)
>
> def __str__(self):
> return self.name
> }}}
>
> testapp/forms.py
>
> {{{
> from django import forms
> from .models import Export
>
> class ExportEditForm(forms.ModelForm):
> class Meta:
> model = Export
> fields = ['name', 'branch']
> }}}
>
> testapp/views.py
>
> {{{
> from django.views.generic import UpdateView
>
> from .forms import ExportEditForm
> from .models import Export
>
> class ExportEditView(UpdateView):
> form_class = ExportEditForm
> model = Export
> template_name = 'edit.html'
> pk_url_kwarg = 'export_pk'
> }}}
>
> and the ugly template: testapp/templates/edit.html
>
> {{{
> {{ form.as_table }}
> }}}
New description:
I'm upgrading to Django 1.11.2 and found that `limit_choices_to` is
applied to forms too early. It's caused by commit
6abd6c598ea23e0a962c87b0075aa2f79f9ead36 (ticket #27563).
I'm using dynamic limit_choices_to function to get user specific filter
expr. It just called once during develop server init, and won't be call
while open a USER EDIT FORM, which is inherited from ModelForm, but it
work in django admin edit page, by reading the code, django admin build
form using modelform_factory(), with this commit reverted, my customized
form works just fine. So, I think it's a regression.
Below is key part of test project showing the problem:
proj/middleware.py
{{{
from threading import current_thread
class CUserMiddleware(object):
"""Get current operating request and user"""
_user = {}
def process_request(self, request):
self._user[current_thread()] = request.user
def process_response(self, request, response):
# TODO cleanup by alive session, if user session expired, remove
all
# items related to the user (dict value).
# self._user.pop(current_thread(), None) # clean to avoid it
grows
if len(self._user) > 1000:
self._user = {}
return response
@classmethod
def get_user(cls, default=None):
# debug('当前用户: {}'.format(cls._user.get(current_thread(),
default)))
return cls._user.get(current_thread(), default)
def current_user():
return CUserMiddleware.get_user()
}}}
testapp/models.py
{{{
from django.db import models
from django.db.models import Q
from proj.middleware import current_user
def limit_export_branch():
u = current_user()
if u:
print('\tlimit_export_branch worked the right way')
return Q(name__endswith='something')
print('\tlimit_export_branch works in wrong way')
import traceback
traceback.print_stack()
return Q(pk=-1) # deny to avoid info leak
class Branch(models.Model):
name = models.CharField(max_length=16)
def __str__(self):
return self.name
class Export(models.Model):
name = models.CharField(max_length=16)
branch = models.ForeignKey(Branch,
limit_choices_to=limit_export_branch)
def __str__(self):
return self.name
}}}
testapp/forms.py
{{{
from django import forms
from .models import Export
class ExportEditForm(forms.ModelForm):
class Meta:
model = Export
fields = ['name', 'branch']
}}}
testapp/views.py
{{{
from django.views.generic import UpdateView
from .forms import ExportEditForm
from .models import Export
class ExportEditView(UpdateView):
form_class = ExportEditForm
model = Export
template_name = 'edit.html'
pk_url_kwarg = 'export_pk'
}}}
and the ugly template: testapp/templates/edit.html
{{{
{{ form.as_table }}
}}}
--
Comment:
Without much investigation, I'm not sure if we should revert the original
change or if some other workaround might restore allowing this use case.
--
Ticket URL: <https://code.djangoproject.com/ticket/28345#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/066.6f543fa7ef65420774f5078ba6b1fc86%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.