Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # -*- coding: utf-8 -*-
- """
- Notes:
- - The javascript will only allow values provided by the callback. If nothing is selected, the text will revert back
- to the previously selected value (or blank, if nothing was set). Also no ID will be set.
- -- TODO --
- - use a user-specified label-method to generate the label. Currently this is done in two places, which is not that nice
- - Write Tests
- - CSRF-Exempt neccessary?
- -
- >>> # The Models
- >>> class ModelB(models.Model):
- >>> value = models.CharField(max_length=255)
- >>>
- >>> def __repr__(self):
- >>> return self.value
- >>>
- >>>
- >>> class ModelA(models.Model):
- >>> #... some fields ...
- >>> model_b = models.ForeignKey(ModelB)
- >>>
- >>>
- >>> # The Form
- >>> class MyForm(ModelForm):
- >>> class Meta:
- >>> model = ModelA
- >>>
- >>> # Some Fields
- >>> model_b = ModelAutocompleteField(ModelB, url=reverse("myproject.myapp.views.callback"))
- >>>
- >>>
- >>> # The callback in the view
- >>> def callback(request):
- >>> search_term = request.GET['term']
- >>> matches = ModelB.objects.filter(value__contains = search_term).all()
- >>> return_values = [{"id": match.pk, "label": match.value} for match in matches]
- >>> return HttpResponse(simplejson.dumps(return_values))
- >>>
- """
- from django import forms
- from django.forms.util import ValidationError
- from django.utils.safestring import mark_safe
- CLIENT_CODE = u"""
- <input type="text" class="ac_input" name="{name}_label" id="{id}_label" value="{label}"/>
- <input type="hidden" name="{name}" id="{id}" value="{value}" />
- <script type="text/javascript">
- $(function(){{
- // Store the initial value into the hidden field
- $("#{id}").data('previous_label', '{label}');
- console.log($("#{id}_label"));
- // attach the jquery-autocomplete to the label-input
- $("#{id}_label").autocomplete({{
- 'source': '{url}',
- 'minLength': 2, // length of inputs neccessary to trigger the autocomplete
- 'select': function(event, ui) {{
- // if a new value is selected from the autocomplete update the id-field and notify possible observer
- $("#{id}")
- .val(ui.item.id)
- .data('previous_label', ui.item.label)
- .trigger('change')
- .trigger('autocomplete:change', this, ui.item.id);
- }},
- 'close': function(event, ui) {{
- // dont restore the old value here. It should be better to restore it in the blur-function
- }}
- }})
- .blur(function() {{
- // if we blur (loose focus) restore the old value.
- // if we already selected a value, this will be the new value, otherwise it will be the old
- var previous_label = $('#{id}').data('previous_label');
- $("#{id}_label").val(previous_label);
- }})
- .data("ui-autocomplete")._renderItem = function( ul, item ) {{
- // Use a custom render function, so we use item.label instead of just item
- return $( "<li></li>" )
- .data( "item.autocomplete", item )
- .append( "<a>" + item.label + "</a>" )
- .appendTo( ul );
- }}
- }});
- </script>
- """
- class ModelAutocompleteWidget(forms.widgets.TextInput):
- """
- The autocomplete-widget for the autocomplete-field. This will render the above CLIENT_CODE
- containing the two inputs and the neccessary javascript.
- To display the text this widget required the model, so it gets a reference. For the label-value, _repr_method is called with
- an instance of the model. Currently this defaults to repr
- The widget is based on a TextInput-Field
- """
- def __init__(self, url, model, *args, **kw):
- """
- Initing the Widget
- `url`
- The url used as callback. Must return a JSON-String in the format of [{"id": 1, "label": "..."}, {"id": 2, "label": "..."}, ...]
- `model`
- The model the Autocomplete is based on
- """
- super(ModelAutocompleteWidget, self).__init__(*args, **kw)
- self._url = url
- self._model = model
- self._repr_method = repr
- def render(self, name, value, attrs=None):
- """
- Rendering the HTML-Code of the Widget
- `name`
- The name of the widget. Used to specify the id- and name-tags of the input-field
- `value`
- The value is the id of the entity to display (=the id). It is used to load the label from the model
- """
- # If there is a value try to load the Object
- if value != None:
- try:
- value, label = value, self._repr_method(self._model.objects.get(pk=value))
- except self.__model.DoesNotExist:
- value, label = "", ""
- else:
- value, label = "", ""
- id = attrs.get('id', name)
- _url = self._url
- _url = _url() if callable(_url) else _url
- return mark_safe(CLIENT_CODE.format(name=name, id=id, url=_url, value=value, label=label))
- # Not neccessary?
- #def value_from_datadict(self, data, files, name):
- """
- Given a dictionary of data and this widget's name, returns the value
- of this widget. Returns None if it's not provided.
- """
- # return data.get(name, None)
- class ModelAutocompleteField(forms.fields.CharField):
- """
- This field renders the associated modul-column using an hidden id-field and a visible
- autocomplete textfield.
- This inherits from CharField and just extends the init-function, for additional parameter
- and the to_python function, to convert the integer-value (Forein key) back to an instance of the Model
- """
- default_error_messages = {
- 'invalid_choice': mark_safe(u'Invalid Option. Please choose a valid option!'),
- 'no_choice' : mark_safe(u'No option selected. Please choose a valid option!'),
- }
- def __init__(self, model, url, *args, **kwargs):
- """
- Create the autocomplete-field. Calls the parent-init-method and replaces the default-widget
- for a CharField with a ModelAutocompleteWidget
- `model`
- Das Model auf dem die Vervollstaendigung beruht.
- `url`
- Die URL aus der die Callbackdatan angefragt werden
- """
- self._model = model
- widget = ModelAutocompleteWidget(url=url, model=model)
- super(ModelAutocompleteField, self).__init__(widget=widget, *args, **kwargs)
- # Not neccessary anymore?
- #def update_url(self, url):
- #self.widget.url = url
- def to_python(self, value):
- """
- Converts the given value (PK of the model) to an instance of the model itself.
- If the id is not valid or empty, an appropriate ValidationError is thrown
- `value`
- The value from the POST
- """
- # if no value is given, but we arent required just return None
- if value == "" and not self.required:
- return None
- # If no value is given, but we need one
- elif value == "" and self.required:
- raise ValidationError(self.error_messages['no_choice'])
- # Else we have a value, try to load the object-instance and return it
- else:
- try:
- return self._model.objects.get(pk=value)
- except self._model.DoesNotExist:
- raise ValidationError(self.error_messages['invalid_choice'])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement