Advertisement
Guest User

Jquery-Django-Autocomplete

a guest
Apr 9th, 2013
177
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # -*- coding: utf-8 -*-
  2.  
  3. """
  4. Notes:
  5. - The javascript will only allow values provided by the callback. If nothing is selected, the text will revert back
  6.   to the previously selected value (or blank, if nothing was set). Also no ID will be set.
  7.  
  8. -- TODO --
  9. - use a user-specified label-method to generate the label. Currently this is done in two places, which is not that nice
  10. - Write Tests
  11. - CSRF-Exempt neccessary?
  12. -
  13.  
  14. >>> # The Models
  15. >>> class ModelB(models.Model):
  16. >>>     value = models.CharField(max_length=255)
  17. >>>    
  18. >>>     def __repr__(self):
  19. >>>         return self.value
  20. >>>    
  21. >>>    
  22. >>> class ModelA(models.Model):
  23. >>>     #... some fields ...
  24. >>>     model_b = models.ForeignKey(ModelB)
  25. >>>
  26. >>>    
  27. >>> # The Form
  28. >>> class MyForm(ModelForm):
  29. >>>     class Meta:
  30. >>>         model = ModelA
  31. >>>    
  32. >>>     # Some Fields
  33. >>>     model_b = ModelAutocompleteField(ModelB, url=reverse("myproject.myapp.views.callback"))
  34. >>>    
  35. >>>    
  36. >>> # The callback in the view
  37. >>> def callback(request):
  38. >>>     search_term = request.GET['term']
  39. >>>     matches = ModelB.objects.filter(value__contains = search_term).all()
  40. >>>     return_values = [{"id": match.pk, "label": match.value} for match in matches]
  41. >>>     return HttpResponse(simplejson.dumps(return_values))
  42. >>>    
  43. """
  44.  
  45. from django import forms
  46. from django.forms.util import ValidationError
  47. from django.utils.safestring import mark_safe
  48.  
  49.  
  50.  
  51. CLIENT_CODE = u"""
  52. <input type="text" class="ac_input" name="{name}_label" id="{id}_label" value="{label}"/>
  53. <input type="hidden" name="{name}" id="{id}" value="{value}" />
  54. <script type="text/javascript">
  55.    $(function(){{
  56.        
  57.        // Store the initial value into the hidden field
  58.        $("#{id}").data('previous_label', '{label}');
  59.        console.log($("#{id}_label"));
  60.        // attach the jquery-autocomplete to the label-input
  61.        $("#{id}_label").autocomplete({{
  62.            'source': '{url}',
  63.            'minLength': 2, // length of inputs neccessary to trigger the autocomplete
  64.            'select': function(event, ui) {{
  65.                // if a new value is selected from the autocomplete update the id-field and notify possible observer
  66.                 $("#{id}")
  67.                     .val(ui.item.id)
  68.                     .data('previous_label', ui.item.label)
  69.                     .trigger('change')
  70.                     .trigger('autocomplete:change', this, ui.item.id);
  71.                
  72.            }},
  73.            'close': function(event, ui) {{
  74.                // dont restore the old value here. It should be better to restore it in the blur-function
  75.            }}
  76.        }})
  77.        .blur(function() {{
  78.            // if we blur (loose focus) restore the old value.
  79.            // if we already selected a value, this will be the new value, otherwise it will be the old
  80.            var previous_label = $('#{id}').data('previous_label');
  81.            $("#{id}_label").val(previous_label);
  82.        }})
  83.        .data("ui-autocomplete")._renderItem = function( ul, item ) {{
  84.            // Use a custom render function, so we use item.label instead of just item
  85.            return $( "<li></li>" )
  86.                .data( "item.autocomplete", item )
  87.                .append( "<a>" + item.label + "</a>" )
  88.                .appendTo( ul );
  89.        }}
  90.    }});
  91. </script>
  92. """
  93.  
  94. class ModelAutocompleteWidget(forms.widgets.TextInput):
  95.     """
  96.    The autocomplete-widget for the autocomplete-field. This will render the above CLIENT_CODE
  97.    containing the two inputs and the neccessary javascript.
  98.    
  99.    To display the text this widget required the model, so it gets a reference. For the label-value, _repr_method is called with
  100.    an instance of the model. Currently this defaults to repr
  101.    
  102.    The widget is based on a TextInput-Field
  103.    """
  104.     def __init__(self, url, model, *args, **kw):
  105.         """
  106.        Initing the Widget
  107.        `url`  
  108.            The url used as callback. Must return a JSON-String in the format of [{"id": 1, "label": "..."}, {"id": 2, "label": "..."}, ...]
  109.        `model`
  110.            The model the Autocomplete is based on
  111.        """
  112.         super(ModelAutocompleteWidget, self).__init__(*args, **kw)
  113.         self._url = url
  114.         self._model = model
  115.         self._repr_method = repr
  116.  
  117.     def render(self, name, value, attrs=None):
  118.         """
  119.        Rendering the HTML-Code of the Widget
  120.        `name`
  121.            The name of the widget. Used to specify the id- and name-tags of the input-field
  122.        `value`
  123.            The value is the id of the entity to display (=the id). It is used to load the label from the model
  124.        """
  125.        
  126.         # If there is a value try to load the Object
  127.         if value != None:
  128.             try:
  129.                 value, label = value, self._repr_method(self._model.objects.get(pk=value))
  130.             except self.__model.DoesNotExist:
  131.                 value, label = "", ""
  132.         else:
  133.             value, label = "", ""
  134.            
  135.            
  136.         id = attrs.get('id', name)
  137.         _url = self._url
  138.         _url = _url() if callable(_url) else _url
  139.        
  140.         return mark_safe(CLIENT_CODE.format(name=name, id=id, url=_url, value=value, label=label))
  141.        
  142.     # Not neccessary?
  143.     #def value_from_datadict(self, data, files, name):
  144.         """
  145.        Given a dictionary of data and this widget's name, returns the value
  146.        of this widget. Returns None if it's not provided.
  147.        """
  148.      #   return data.get(name, None)
  149.  
  150.  
  151.  
  152.        
  153. class ModelAutocompleteField(forms.fields.CharField):
  154.     """
  155.    This field renders the associated modul-column using an hidden id-field and a visible
  156.    autocomplete textfield.
  157.    This inherits from CharField and just extends the init-function, for additional parameter
  158.    and the to_python function, to convert the integer-value (Forein key) back to an instance of the Model
  159.    """
  160.    
  161.     default_error_messages = {
  162.         'invalid_choice': mark_safe(u'Invalid Option. Please choose a valid option!'),
  163.         'no_choice' : mark_safe(u'No option selected. Please choose a valid option!'),
  164.     }
  165.  
  166.     def __init__(self, model, url, *args, **kwargs):
  167.         """
  168.        Create the autocomplete-field. Calls the parent-init-method and replaces the default-widget
  169.        for a CharField with a ModelAutocompleteWidget
  170.        `model`
  171.            Das Model auf dem die Vervollstaendigung beruht.
  172.        `url`
  173.            Die URL aus der die Callbackdatan angefragt werden
  174.        """
  175.         self._model = model
  176.         widget = ModelAutocompleteWidget(url=url, model=model)
  177.         super(ModelAutocompleteField, self).__init__(widget=widget, *args, **kwargs)
  178.        
  179.     # Not neccessary anymore?
  180.     #def update_url(self, url):
  181.         #self.widget.url = url
  182.        
  183.     def to_python(self, value):
  184.         """
  185.        Converts the given value (PK of the model) to an instance of the model itself.
  186.        If the id is not valid or empty, an appropriate ValidationError is thrown
  187.        `value`
  188.            The value from the POST
  189.        """
  190.         # if no value is given, but we arent required just return None
  191.         if value == "" and not self.required:
  192.             return None
  193.        
  194.         # If no value is given, but we need one
  195.         elif value == "" and self.required:
  196.             raise ValidationError(self.error_messages['no_choice'])
  197.        
  198.         # Else we have a value, try to load the object-instance and return it
  199.         else:
  200.             try:
  201.                 return self._model.objects.get(pk=value)                
  202.             except self._model.DoesNotExist:
  203.                 raise ValidationError(self.error_messages['invalid_choice'])
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement