def render(self, name, value, attrs=None, choices=()): self._element_id = attrs['id'] # choices links # if there is only one choice, then nothing will be rendered lookups = utils.getLookups(self.lookups) lookups_output = self.filter_widget.renderFilter("getManyToManyJSON", self._element_id, self.model, lookups, self.select_related, self.default_index) # normal widget output from the anchestor self.choices = self._getAllChoices(value) parent_output = super(FilteredSelectMultiple, self).render(name, value, attrs, choices) # create the output including the django admin's Javascript code that # mutates the select widget into a selectfilter one # this assumes that /admin/jsi18n/, core.js, SelectBox.js and # SelectFilter2.js are loaded from the page verbose_name = self.model._meta.verbose_name_plural.replace('"', '\\"') output = u""" %s <script type="text/javascript"> (function($) { $(document).ready(function(){ SelectFilter.init("id_%s", "%s", 0, "%s"); }); })(django.jQuery); </script> """ % (self.filter_widget.composeField(lookups_output, parent_output), name, verbose_name, settings.ADMIN_MEDIA_PREFIX) return mark_safe(output)
def clean(self, value): if self.required and not value: raise ValidationError(self.error_messages["required"]) elif not self.required and not value: return [] if not isinstance(value, (list, tuple)): raise ValidationError(self.error_messages["list"]) final_values = [] # if there is only one lookup used to limit choices, then a real # validation over that limited choices is performed lookups_list = utils.getLookups(self.lookups) limit_choices_to = {} if len(lookups_list) != 1 else lookups_list[0][1] for val in value: try: obj = self.model.objects.get(pk=val, **limit_choices_to) except self.model.DoesNotExist: raise ValidationError(self.error_messages["invalid_choice"] % val) else: final_values.append(obj) return final_values
def __init__( self, model, lookups, default_index=0, select_related=None, widget=FilteredSelectMultiple, filter_widget=SelectBoxFilter, *args, **kwargs ): """ model: the related model lookups: a sequence of (label, lookup_dict) that tells how to filter the objects e.g. ( ('active', {'is_active': True}), ('inactive', {'is_active': False}), ) you may specify what you want in lookup_dict, give multiple filter lookups for the same choice and also set a choice that gets all unfiltered objects e.g. ( ('some stuff', { 'field1__startswith': 'a', 'field2': 'value' }), ('all stuff', {}), ) default_index: the index of the lookup sequence that will be the default choice when the field is initially displayed. set to None if you want the widget to start empty select_related: if not None the resulting querydict is performed using select_related(select_related), allowing foreign keys to be retrieved (e.g. useful when the unicode representation of the model objects contains references to foreign keys) It is possible to pass all the other args and kwargs accepted by the django field class. """ # get the default index and queryset # queryset is empty if default index is None if default_index is None: queryset = model.objects.none() else: lookups_list = utils.getLookups(lookups) if default_index >= len(lookups_list): default_index = 0 lookup_dict = lookups_list[default_index][1] # get the queryset queryset = utils.getObjects(model, lookup_dict, select_related) # call the parent constructor super(AjaxManyToManyField, self).__init__(queryset, widget=widget, *args, **kwargs) # populate widget with some data self.widget.lookups = self.lookups = lookups self.widget.model = self.model = model self.widget.select_related = select_related self.widget.filter_widget = filter_widget() self.widget.default_index = default_index