diff --git a/django_select2/__init__.py b/django_select2/__init__.py index 5a60172..e0660fe 100644 --- a/django_select2/__init__.py +++ b/django_select2/__init__.py @@ -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.") diff --git a/django_select2/fields.py b/django_select2/fields.py index 30e6dff..955cd5d 100644 --- a/django_select2/fields.py +++ b/django_select2/fields.py @@ -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') diff --git a/django_select2/widgets.py b/django_select2/widgets.py index 220d99b..50c0994 100644 --- a/django_select2/widgets.py +++ b/django_select2/widgets.py @@ -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]) diff --git a/testapp/test.db b/testapp/test.db index ca95695..2f81331 100644 Binary files a/testapp/test.db and b/testapp/test.db differ diff --git a/testapp/testapp/templates/index.html b/testapp/testapp/templates/index.html index 2e199ac..79f6c63 100644 --- a/testapp/testapp/templates/index.html +++ b/testapp/testapp/templates/index.html @@ -11,5 +11,7 @@