Source code for autocomplete_light.fields

from __future__ import unicode_literals

import six
from django import forms
from django.db import models
from django.db.models.query import QuerySet

from .registry import registry as default_registry
from .widgets import ChoiceWidget, MultipleChoiceWidget
from .exceptions import AutocompleteChoicesMustBeQuerySet

__all__ = ['FieldBase', 'ChoiceField', 'MultipleChoiceField',
    'ModelChoiceField', 'ModelMultipleChoiceField', 'GenericModelChoiceField',
    'GenericModelMultipleChoiceField']


[docs]class FieldBase(object): default_error_messages = { 'invalid': '%(autocomplete)s cannot validate %(value)s', } def __init__(self, autocomplete=None, registry=None, widget=None, widget_js_attributes=None, autocomplete_js_attributes=None, extra_context=None, *args, **kwargs): self.autocomplete = self.get_autocomplete(autocomplete, registry, widget) widget = widget or self.widget if isinstance(widget, type): widget = widget(autocomplete, widget_js_attributes, autocomplete_js_attributes, extra_context) kwargs['widget'] = widget # Does the subclass have ModelChoiceField or ModelMultipleChoiceField # as a base class? parents = type(self).__mro__ if (forms.ModelChoiceField in parents or forms.ModelMultipleChoiceField in parents): if not isinstance(self.autocomplete.choices, QuerySet): raise AutocompleteChoicesMustBeQuerySet(self.autocomplete) kwargs['queryset'] = self.autocomplete.choices super(FieldBase, self).__init__(*args, **kwargs) def get_autocomplete(self, autocomplete, registry, widget): if widget: # BC: maybe defining the autocomplete as a widget argument ? autocomplete = getattr(widget, 'autocomplete', None) registry = registry or default_registry return registry.get_autocomplete_from_arg(autocomplete) def validate(self, value): """ Wrap around forms.Field and Autocomplete.validate_values(). Field.validate_values() handles the required option. """ forms.Field.validate(self, value) # FIXME: we might actually want to change the Autocomplete API to # support python values instead of raw values, that would probably be # more performant. values = self.prepare_value(value) if value and not self.autocomplete(values=values).validate_values(): error_params = { 'autocomplete': self.autocomplete.__name__, 'value': value } raise forms.ValidationError(self.error_messages['invalid'], code='invalid', params=error_params)
[docs]class ChoiceField(FieldBase, forms.ChoiceField): widget = ChoiceWidget def __init__(self, autocomplete=None, registry=None, widget=None, widget_js_attributes=None, autocomplete_js_attributes=None, extra_context=None, *args, **kwargs): kwargs.update({'choices': self.get_choices(autocomplete, registry, widget)}) super(ChoiceField, self).__init__(autocomplete, registry, widget, widget_js_attributes, autocomplete_js_attributes, extra_context, *args, **kwargs) def get_choices(self, autocomplete, registry, widget): a = self.get_autocomplete(autocomplete, registry, widget)() return ((a.choice_value(c), a.choice_label(c)) for c in a.choices)
[docs]class MultipleChoiceField(ChoiceField, forms.MultipleChoiceField): widget = MultipleChoiceWidget
class ModelChoiceFieldBase(FieldBase): def _get_queryset(self): return self._queryset def _set_queryset(self, queryset): self._queryset = queryset self.widget.choices = self.choices # Also update autocomplete choices self.autocomplete.choices = queryset queryset = property(_get_queryset, _set_queryset)
[docs]class ModelChoiceField(ModelChoiceFieldBase, forms.ModelChoiceField): widget = ChoiceWidget
[docs]class ModelMultipleChoiceField(ModelChoiceFieldBase, forms.ModelMultipleChoiceField): widget = MultipleChoiceWidget
[docs]class GenericModelChoiceField(ModelChoiceFieldBase, forms.Field): """ Simple form field that converts strings to models. """ widget = ChoiceWidget
[docs] def prepare_value(self, value): """ Given a model instance as value, with content type id of 3 and pk of 5, return such a string '3-5'. """ from django.contrib.contenttypes.models import ContentType if isinstance(value, six.string_types): # Apparently there's a bug in django, that causes a python value to # be passed here. This ONLY happens when in an inline .... return value elif isinstance(value, models.Model): content_type = ContentType.objects.get_for_model( value, for_concrete_model=False) return '%s-%s' % (content_type.pk, value.pk)
[docs] def to_python(self, value): """ Given a string like '3-5', return the model of content type id 3 and pk 5. """ from django.contrib.contenttypes.models import ContentType if not value: return value content_type_id, object_id = value.split('-', 1) try: content_type = ContentType.objects.get_for_id(content_type_id) except ContentType.DoesNotExist: raise forms.ValidationError('Wrong content type') else: model = content_type.model_class() try: return model.objects.get(pk=object_id) except model.DoesNotExist: raise forms.ValidationError('Wrong object id')
[docs]class GenericModelMultipleChoiceField(GenericModelChoiceField): """ Simple form field that converts strings to models. """ widget = MultipleChoiceWidget def prepare_value(self, value): return [super(GenericModelMultipleChoiceField, self ).prepare_value(v) for v in value] def to_python(self, value): return [super(GenericModelMultipleChoiceField, self).to_python(v) for v in value]