View difference between Paste ID: LncqfQM2 and hWBUGX1H
SHOW: | | - or go back to the newest paste.
1
# -*- coding: utf-8 -*-
2
3
"""
4-
from django.utils.encoding import force_unicode 
4+
Notes:
5-
from django.core.urlresolvers import reverse
5+
 - The javascript will only allow values provided by the callback. If nothing is selected, the text will revert back
6-
from django.forms.util import ErrorList, ValidationError
6+
   to the previously selected value (or blank, if nothing was set). Also no ID will be set.
7-
from django.utils.translation import ugettext as _
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-
<input type="text" class="ac_input" name="%(name)s_text" id="%(html_id)s_text" value="%(text)s"/>
11+
 - CSRF-Exempt neccessary?
12-
<input type="hidden" name="%(name)s" id="%(html_id)s" value="%(value)s" />
12+
 - 
13
14-
    $(function(){
14+
>>> # The Models
15-
        $("#%(html_id)s_text").autocomplete({
15+
>>> class ModelB(models.Model):
16-
            'source': '%(url)s',
16+
>>>     value = models.CharField(max_length=255)
17-
            'minLength': 2,
17+
>>>     
18-
            'select': function(event, ui) {
18+
>>>     def __repr__(self):
19-
                 $("#%(html_id)s")
19+
>>>         return self.value
20
>>>     
21-
                     .data('prev', ui.item.label)
21+
>>>     
22-
                     .trigger('change');
22+
>>> class ModelA(models.Model):
23-
                %(callback)s
23+
>>>     #... some fields ...
24-
            },
24+
>>>     model_b = models.ForeignKey(ModelB)
25-
            'close': function(event, ui) {
25+
>>>
26-
                // dont restore the old value here. It should be better to restore it in the blur-function */
26+
>>>     
27-
                /*setTimeout(function() {
27+
>>> # The Form
28-
                    var prev = $('#%(html_id)s').data('prev');
28+
>>> class MyForm(ModelForm):
29-
                    $("#%(html_id)s_text").val(prev);
29+
>>>     class Meta:
30-
                }, 100);*/
30+
>>>         model = ModelA
31-
            }
31+
>>>     
32-
        })   
32+
>>>     # Some Fields
33-
        .blur(function() {
33+
>>>     model_b = ModelAutocompleteField(ModelB, url=reverse("myproject.myapp.views.callback"))
34-
            var prev = $('#%(html_id)s').data('prev');
34+
>>>     
35-
            $("#%(html_id)s_text").val(prev);
35+
>>>     
36-
        })     
36+
>>> # The callback in the view
37-
        .data("autocomplete")._renderItem = function( ul, item ) {
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-
        };
42+
>>>     
43
"""
44-
         // Speichern des initialien Werts
44+
45-
        $("#%(html_id)s").data('prev', '%(value)s');
45+
46-
    });
46+
from django.forms.util import ValidationError
47
from django.utils.safestring import mark_safe
48
49
50-
class ModelAutoCompleteWidget(forms.widgets.TextInput):
50+
51
CLIENT_CODE = u"""
52-
    Autocomplete-Widget. Rendert die Inputs und das JAvascript um das AutoComplete-Field
52+
<input type="text" class="ac_input" name="{name}_label" id="{id}_label" value="{label}"/>
53-
    einzubinden. Kann nur in Verbindung mit dem ModelAutoCompleteField benutzt werden.
53+
<input type="hidden" name="{name}" id="{id}" value="{value}" />
54-
    (Fuer den Text-wert wird das Model benoetigt, es wird also vom Field an dieses Widget
54+
55-
    weitergegeben)
55+
    $(function(){{
56
        
57-
    def __init__(self, url, model, callback=None, *args, **kw):
57+
        // Store the initial value into the hidden field
58-
        super(forms.widgets.TextInput, self).__init__(*args, **kw)
58+
        $("#{id}").data('previous_label', '{label}');
59-
        self.url = url
59+
        console.log($("#{id}_label"));
60-
        self.model = model
60+
        // attach the jquery-autocomplete to the label-input
61-
        self.callback = callback
61+
        $("#{id}_label").autocomplete({{
62
            'source': '{url}', 
63-
       
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-
        Rendert das Widget
67+
68
                     .data('previous_label', ui.item.label)
69-
            Der Name des Widgets
69+
                     .trigger('change')
70
                     .trigger('autocomplete:change', this, ui.item.id);
71-
            Der Wert den der Hauptinput (In diesem Fall ID) haben soll
71+
                
72
            }},
73-
        # Defaults falls was nicht Ok ist:
73+
            'close': function(event, ui) {{
74-
        text = ''
74+
                // dont restore the old value here. It should be better to restore it in the blur-function
75-
        val = ''
75+
            }}
76
        }})
77-
        # Wenn Wert gegeben wurde versuchen die Daten zu holen
77+
        .blur(function() {{
78
            // if we blur (loose focus) restore the old value.
79-
            try:                
79+
            // if we already selected a value, this will be the new value, otherwise it will be the old
80-
                text = self.model.objects.get(pk=value)
80+
            var previous_label = $('#{id}').data('previous_label');
81-
                val = value
81+
            $("#{id}_label").val(previous_label);
82-
            except Exception:
82+
        }})
83-
                pass
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-
        html_id = attrs.get('id', name)
86+
87-
        url = self.url() if callable(self.url) else self.url
87+
88-
        callback = u"%s(ui.item.id);" % self.callback if self.callback else ""
88+
89
        }}
90-
        return mark_safe(CLIENT_CODE % dict(name=name, html_id=html_id, url=url, value=val, text=text, callback=callback))
90+
    }});
91
</script>
92
"""
93-
    def value_from_datadict(self, data, files, name):
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-
        return data.get(name, None)
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-
class ModelAutoCompleteField(forms.fields.CharField):
104+
    def __init__(self, url, model, *args, **kw):
105
        """ 
106-
    Autocomplete-Feld das die gelesenen Werte direkt zurueck in Django-Model-Objekte
106+
        Initing the Widget
107-
    konvertiert. Als Widget wird das AutoCOmpleteWidget von oben benutzt, was die
107+
        `url`  
108-
    JavaScript-Logik und Felder korrekt fuellt.
108+
            The url used as callback. Must return a JSON-String in the format of [{"id": 1, "label": "..."}, {"id": 2, "label": "..."}, ...]
109-
    Momentan muss ein gueltiger WErt selektiert werden.
109+
110
            The model the Autocomplete is based on
111
        """
112
        super(ModelAutocompleteWidget, self).__init__(*args, **kw)
113-
        'invalid_choice': mark_safe(u'Keine g&uuml;ltige Auswahl. Bitte eine g&uuml;ltige Option w&auml;hlen.'),
113+
        self._url = url
114-
        'no_choice' : mark_safe(u'Kein Eintrag ausgew&auml;hlt. Bitte w&auml;hlen sie einen Eintrag.'),
114+
        self._model = model
115
        self._repr_method = repr
116
117
    def render(self, name, value, attrs=None):
118-
    def __init__(self, model, url, callback=None,size=None, *args, **kwargs):
118+
119
        Rendering the HTML-Code of the Widget
120-
        Neues AutoCOmpleteField anlegen
120+
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-
        self.model = model
126+
        # If there is a value try to load the Object
127
        if value != None:
128-
        # build attrs for widget
128+
            try:
129
                value, label = value, self._repr_method(self._model.objects.get(pk=value))
130-
        attrs = {'size': size} if size else {}
130+
            except self.__model.DoesNotExist:
131
                value, label = "", ""
132-
        super(ModelAutoCompleteField, self).__init__(
132+
        else:
133-
            widget = ModelAutoCompleteWidget(url=url, model=model, callback=callback, attrs=attrs),            
133+
            value, label = "", ""
134-
            *args,
134+
135-
            **kwargs)
135+
136
        id = attrs.get('id', name)
137-
    def update_url(self, url):
137+
        _url = self._url
138-
        self.widget.url = url
138+
        _url = _url() if callable(_url) else _url
139
        
140-
    def clean(self, value):
140+
        return mark_safe(CLIENT_CODE.format(name=name, id=id, url=_url, value=value, label=label))
141
        
142-
        Die Methode konvertiert die ID des HiddenFields zurueck in ein Django-MOdel
142+
    # Not neccessary?
143
    #def value_from_datadict(self, data, files, name):
144
        """
145-
        # Kein WErt gegeben, es wird aber auch keiner gebraucht
145+
146-
        if value=='' and not self.required:
146+
147
        """
148
     #   return data.get(name, None)
149-
        # Wenn kein Wert gegeben ist kann auch kein Model gelesen werden
149+
150-
        if value=='':
150+
151
152
        
153-
        # Versuchen das Objekt zu lesen
153+
class ModelAutocompleteField(forms.fields.CharField):
154-
        try: 
154+
155-
            obj = self.model.objects.get(pk=value)
155+
    This field renders the associated modul-column using an hidden id-field and a visible 
156-
        except self.model.DoesNotExist:
156+
    autocomplete textfield.
157-
            raise ValidationError(self.error_messages['invalid_choice'])
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-
        return obj
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'])