Issue 54. Widget performance fix.

This commit is contained in:
AppleGrew (applegrew) 2013-09-17 22:02:32 +05:30
parent 096162ac84
commit cc94db7e2b
9 changed files with 108 additions and 9 deletions

View file

@ -116,7 +116,7 @@ try:
from .views import Select2View, NO_ERR_RESP
if logger.isEnabledFor(logging.DEBUG):
logger.debug("Django found and fields and widgest loaded.")
logger.debug("Django found and fields and widgets loaded.")
except ImportError:
if logger.isEnabledFor(logging.INFO):
logger.info("Django not found.")

View file

@ -318,10 +318,30 @@ class ChoiceMixin(object):
result._choices = copy.deepcopy(self._choices, memo)
return result
class FilterableModelChoiceIterator(ModelChoiceIterator):
"""
Extends ModelChoiceIterator to add the capability to apply additional
filter on the passed queryset.
"""
def set_extra_filter(self, **filter_map):
"""
Applies additional filter on the queryset. This can be called multiple times.
:param kwargs: The ``**kwargs`` to pass to :py:meth:`django.db.models.query.QuerySet.filter`.
If this is not set then additional filter (if) applied before is removed.
"""
if not hasattr(self, '_original_queryset'):
import copy
self._original_queryset = copy.deepcopy(self.queryset)
if filter_map:
self.queryset = self._original_queryset.filter(**filter_map)
else:
self.queryset = self._original_queryset
class QuerysetChoiceMixin(ChoiceMixin):
"""
Overrides ``choices``' getter to return instance of :py:class:`.ModelChoiceIterator`
Overrides ``choices``' getter to return instance of :py:class:`.FilterableModelChoiceIterator`
instead.
"""
@ -338,12 +358,17 @@ class QuerysetChoiceMixin(ChoiceMixin):
# accessed) so that we can ensure the QuerySet has not been consumed. This
# construct might look complicated but it allows for lazy evaluation of
# the queryset.
return ModelChoiceIterator(self)
return FilterableModelChoiceIterator(self)
choices = property(_get_choices, ChoiceMixin._set_choices)
def __deepcopy__(self, memo):
result = super(QuerysetChoiceMixin, self).__deepcopy__(memo)
# Need to force a new ModelChoiceIterator to be created, bug #11183
result.queryset = result.queryset
return result
class ModelChoiceFieldMixin(object):
class ModelChoiceFieldMixin(QuerysetChoiceMixin):
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset', None)
@ -370,6 +395,8 @@ class ModelChoiceFieldMixin(object):
if hasattr(self, '_queryset'):
return self._queryset
def get_pk_field_name(self):
return self.to_field_name or 'pk'
### Slightly altered versions of the Django counterparts with the same name in forms module. ###
@ -453,6 +480,9 @@ class HeavySelect2FieldBaseMixin(object):
if hasattr(self, 'field_id'):
self.widget.field_id = self.field_id
# Widget should have been instantiated by now.
self.widget.field = self
if logger.isEnabledFor(logging.DEBUG):
t2 = util.timer_start('HeavySelect2FieldBaseMixin.__init__:choices initialization')

View file

@ -4,6 +4,7 @@ Contains all the Django widgets for Select2.
import logging
from itertools import chain
import util
from django import forms
from django.utils.encoding import force_unicode
@ -202,6 +203,9 @@ class Select2Mixin(object):
:return: The rendered markup.
:rtype: :py:obj:`unicode`
"""
if logger.isEnabledFor(logging.DEBUG):
t1 = util.timer_start('Select2Mixin.render')
args = [name, value, attrs]
if choices:
args.append(choices)
@ -214,6 +218,7 @@ class Select2Mixin(object):
s += self.render_js_code(id_, name, value, attrs, choices)
if logger.isEnabledFor(logging.DEBUG):
util.timer_end(t1)
logger.debug("Generated widget code:-\n%s", s)
return mark_safe(s)
@ -411,9 +416,16 @@ class HeavySelect2Mixin(Select2Mixin):
txts = []
all_choices = choices if choices else []
choices_dict = dict()
for val, txt in chain(self.choices, all_choices):
self_choices = self.choices
import fields
if isinstance(self_choices, fields.FilterableModelChoiceIterator):
self_choices.set_extra_filter(**{'%s__in' % self.field.get_pk_field_name(): selected_choices})
for val, txt in chain(self_choices, all_choices):
val = force_unicode(val)
choices_dict[val] = txt
for val in selected_choices:
try:
txts.append(choices_dict[val])

Binary file not shown.

View file

@ -11,5 +11,7 @@
<li><a href="{% url 'test_mixed_form' %}">Test mixed form. All fields' search must return their own results, not other fields'.</a></li>
<li><a href="{% url 'test_init_values' %}">Test that initial values are honored in unbound form</a></li>
<li><a href="{% url 'test_list_questions' %}">Test tagging support</a></li>
<li><a href="{% url 'test_auto_multivalue_field' %}">Test multi value auto model field.</a></li>
<li><a href="{% url 'test_auto_heavy_perf' %}">Test performance. Issue#54.</a></li>
</ul>
</body>

View file

@ -2,7 +2,7 @@ from django import forms
from django_select2 import *
from .models import Employee, Dept, ClassRoom, Lab, Word, School, Tag, Question
from .models import Employee, Dept, ClassRoom, Lab, Word, School, Tag, Question, WordList
from django.core.exceptions import ValidationError
@ -27,6 +27,10 @@ class WordChoices(AutoModelSelect2Field):
queryset = Word.objects
search_fields = ['word__icontains', ]
class MultiWordChoices(AutoModelSelect2MultipleField):
queryset = Word.objects
search_fields = ['word__icontains', ]
class TagField(AutoModelSelect2TagField):
queryset = Tag.objects
search_fields = ['tag__icontains', ]
@ -87,7 +91,6 @@ class SelfMultiChoices(AutoSelect2MultipleField):
########### Forms ##############]
class SchoolForm(forms.ModelForm):
classes = ClassRoomChoices()
class Meta:
@ -161,3 +164,10 @@ class QuestionForm(forms.ModelForm):
class Meta:
model = Question
class WordsForm(forms.ModelForm):
word = WordChoices()
words = MultiWordChoices()
class Meta:
model = WordList
exclude = ['kind']

View file

@ -52,3 +52,15 @@ class Question(models.Model):
def __unicode__(self):
return unicode(self.question)
class KeyValueMap(models.Model):
key = models.CharField(max_length=200)
value = models.CharField(max_length=300)
def __unicode__(self):
return u'%s=>%s' % (self.key, self.value)
class WordList(models.Model):
kind = models.CharField(max_length=100)
word = models.ForeignKey(Word, null=True, blank=True, related_name='wordlist_word')
words = models.ManyToManyField(Word, null=True, blank=True, related_name='wordlist_words')

View file

@ -14,4 +14,8 @@ urlpatterns = patterns('testapp.testmain.views',
url(r'question/$', 'test_list_questions', name='test_list_questions'),
url(r'question/form/([0-9]+)/$', 'test_tagging', name='test_tagging'),
url(r'question/form/$', 'test_tagging_new', name='test_tagging_new'),
url(r'auto_model/form/$', 'test_auto_multivalue_field', name='test_auto_multivalue_field'),
url(r'auto_heavy/perf_test/$', 'test_auto_heavy_perf', name='test_auto_heavy_perf'),
)

View file

@ -2,8 +2,8 @@ from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from .forms import EmployeeForm, DeptForm, MixedForm, InitialValueForm, QuestionForm
from .models import Employee, Dept, Question
from .forms import EmployeeForm, DeptForm, MixedForm, InitialValueForm, QuestionForm, WordsForm, SchoolForm
from .models import Employee, Dept, Question, WordList, School
def test_single_value_model_field(request):
return render(request, 'list.html', {
@ -80,3 +80,32 @@ def test_tagging(request, id):
form = QuestionForm(instance=question)
return render(request, 'form.html', {'form': form})
def test_auto_multivalue_field(request):
try:
s = School.objects.get(id=1)
except School.DoesNotExist:
s = School(id=1)
if request.POST:
form = SchoolForm(data=request.POST, instance=s)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('home'))
else:
form = SchoolForm(instance=s)
return render(request, 'form.html', {'form': form})
def test_auto_heavy_perf(request):
try:
word = WordList.objects.get(kind='Word_Of_Day')
except WordList.DoesNotExist:
word = WordList(kind='Word_Of_Day')
if request.POST:
form = WordsForm(data=request.POST, instance=word)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('home'))
else:
form = WordsForm(instance=word)
return render(request, 'form.html', {'form': form})