Source code for django_select2_chained.fields

"""Contains all the Django fields for select2-chained."""
import copy
import logging

from django_select2.fields import NO_ERR_RESP, AutoModelSelect2Field

from .widgets import ChainedAutoSelect2Widget

__all__ = (
    'ChainedAutoModelSelect2FieldMixin',
    'ChainedAutoModelSelect2Field',
    'RequestSpecificAutoModelSelect2Field',
    'ChainedRequestSpecificAutoModelSelect2Field'
)

logger = logging.getLogger(__name__)


[docs]class RequestSpecificAutoModelSelect2Field(AutoModelSelect2Field): """ An AutoHeavy field whose queryset is determined from the current request parameter. This is done by using get_queryset method instead of the class-level queryset property. Allows using the same AutoHeavy field instance between multiple requests and having request-specific data. """
[docs] def get_queryset(self, request): """ Method that determines the queryset from the current request. Must be implemented. """ raise NotImplementedError("get_queryset() must be implemented.")
[docs] def get_results(self, request, term, page, context): """ See :py:meth:`.views.Select2View.get_results`. This implementation takes care of detecting if more results are available. """ if not hasattr(self, 'search_fields') or not self.search_fields: raise ValueError('search_fields is required.') # Add request parameter to get_queryset. qs = self.get_queryset(request) if qs is not None: qs = copy.deepcopy(qs) params = self.prepare_qs_params(request, term, self.search_fields) if self.max_results: min_ = (page - 1) * self.max_results max_ = min_ + self.max_results + 1 # Fetch one extra row to check if there are more rows. res = list(qs.filter(*params['or'], **params['and'])[min_:max_]) has_more = len(res) == (max_ - min_) if has_more: res = res[:-1] else: res = list(qs.filter(*params['or'], **params['and'])) has_more = False res = [(getattr(obj, self.to_field_name), self.label_from_instance(obj), self.extra_data_from_instance(obj)) for obj in res] else: res = [] has_more = False return NO_ERR_RESP, has_more, res
[docs]class ChainedAutoModelSelect2FieldMixin(object): """ A mixin for subclasses of AutoModelSelect2Field that adds chaining functionality. The attached field gets filtered by another field in the form, specified by the chain_field attribute. The selected option in the chain_field limits the queryset in the current field. """ def __init__(self, *args, **kwargs): """ Init method. :param chain_field: related field name :param model_field: real foreign key field name in the model :param allow_empty: if true, displays all options when related field is empty """ self.chain_field = kwargs.pop('chain_field', None) self.model_field = kwargs.pop('model_field', self.chain_field) self.allow_empty = kwargs.pop('allow_empty', None) select2_options = kwargs.pop('select2_options', None) widget = kwargs.get('widget', None) if not widget: widget = ChainedAutoSelect2Widget(chain_field=self.chain_field, model_field=self.model_field, select2_options=select2_options) kwargs.update({ 'widget': widget }) self.chain_field = self.chain_field or widget.chain_field self.model_field = self.model_field or widget.model_field if not self.chain_field or not self.model_field: raise NotImplementedError(u"Chain field and model field must be specified.") super(ChainedAutoModelSelect2FieldMixin, self).__init__(*args, **kwargs)
[docs] def prepare_qs_params(self, request, search_term, search_fields): """Prepare queryset parameters for filtering.""" params = super(ChainedAutoModelSelect2FieldMixin, self).prepare_qs_params(request, search_term, search_fields) chain_field_id = request.GET.get(self.chain_field, None) if chain_field_id: params['and'].update({self.model_field: chain_field_id}) elif not self.allow_empty: params['and'].update({'pk__isnull': True}) return params
[docs]class ChainedAutoModelSelect2Field(ChainedAutoModelSelect2FieldMixin, AutoModelSelect2Field): """An :py:class:`AutoModelSelect2Field` with chaining functionality.""" pass
[docs]class ChainedRequestSpecificAutoModelSelect2Field(ChainedAutoModelSelect2FieldMixin, RequestSpecificAutoModelSelect2Field): """A :py:class:`RequestSpecificAutoModelSelect2Field` with chaining functionality.""" pass