Codebase list django-ajax-selects / 2009239
Imported Upstream version 1.2.5 SVN-Git Migration 8 years ago
19 changed file(s) with 275 addition(s) and 229 deletion(s). Raw diff Collapse all Expand all
1414 [Note: screen shots are from the older version. Styling has changed slightly]
1515
1616 1. The user types a search term into the text field
17 2. An ajax request is sent to the server.
18 3. The dropdown menu is populated with results.
17 2. An ajax request is sent to the server.
18 3. The dropdown menu is populated with results.
1919 4. User selects by clicking or using arrow keys
2020 5. Selected result displays in the "deck" area directly below the input field.
2121 6. User can click trashcan icon to remove a selected item
4646 `easy_install django-ajax-selects`
4747 or
4848 download or checkout the distribution
49 or
49 or
5050 install using buildout by adding `django-ajax-selects` to your `eggs`
5151
5252 on fedora:
156156
157157 + channel_name : {'model': 'app.modelname', 'search_field': 'name_of_field_to_search' }
158158 > This will create a channel automatically
159
159
160160 chanel_name : ( 'app.lookups', 'YourLookup' )
161161 This points to a custom Lookup channel name YourLookup in app/lookups.py
162162
163163 AJAX_LOOKUP_CHANNELS = {
164164 # channel : dict with settings to create a channel
165165 'person' : {'model':'example.person', 'search_field':'name'},
166
166
167167 # channel: ( module.where_lookup_is, ClassNameOfLookup )
168168 'song' : ('example.lookups', 'SongLookup'),
169169 }
170
170
171171 #### AJAX_SELECT_BOOTSTRAP
172172
173173 Sets if it should automatically include jQuery/jQueryUI/theme. On large formsets this will cause it to check each time but it will only jQuery the first time.
185185
186186 This controls if and how these:
187187
188 ajax_select/static/js/ajax_select.js
189 ajax_select/static/css/ajax_select.css
188 ajax_select/static/js/ajax_select.js
189 ajax_select/static/css/ajax_select.css
190190
191191 are included inline in the html with each form field.
192192
270270
271271 The model class this channel searches
272272
273 ###### plugin_options [property, default={}]
274
275 Set any options for the jQuery plugin. This includes:
276
277 + minLength
278 + autoFocus
279 + disabled
280 + position
281 + source - setting this would overide the normal ajax URL. could be used to add URL query params
282
283 See http://docs.jquery.com/UI/Autocomplete#options
284
285 The field or widget may also specify plugin_options that will overwrite those specified by the channel.
286
273287 ###### min_length [property, default=1]
288
289 This is a jQuery plugin option. It is preferred to set this in the plugin_options dict, but this older style attribute will still be honored.
274290
275291 Minimum query length to return a result. Large datasets can choke if they search too often with small queries.
276292 Better to demand at least 2 or 3 characters.
280296
281297 Name of the field for the query to search with icontains. This is used only in the default get_query implementation.
282298 Usually better to just implement your own get_query
283
299
284300 ###### get_query(self,q,request)
285301
286302 return a query set searching for the query string q, ordering as appropriate.
342358 + *fieldlist*: a dict of {fieldname : channel_name, ... }
343359 + *superclass*: [default ModelForm] Substitute a different superclass for the constructed Form class.
344360 + *show_help_text*: [default False]
345 Leave blank [False] if using this form in a standard Admin.
361 Leave blank [False] if using this form in a standard Admin.
346362 Set it True for InlineAdmin classes or if making a form for use outside of the Admin.
347363
348364 ######Example
350366 from ajax_select import make_ajax_form
351367 from ajax_select.admin import AjaxSelectAdmin
352368 from yourapp.models import YourModel
353
369
354370 class YourModelAdmin(AjaxSelectAdmin):
355371 # create an ajax form class using the factory function
356372 # model,fieldlist, [form superclass]
357373 form = make_ajax_form(Label,{'owner':'person'})
358
374
359375 admin.site.register(YourModel,YourModelAdmin)
360376
361377 You may use AjaxSelectAdmin as a mixin class and multiple inherit if you have another Admin class that you would like to use. You may also just add the hook into your own Admin class:
371387 forms.py
372388 --------
373389
374 subclass ModelForm just as usual. You may add ajax fields using the helper or directly.
390 subclass ModelForm just as usual. You may add ajax fields using the helper or directly.
375391
376392 #### make_ajax_field(model,model_fieldname,channel,show_help_text = False,**kwargs)
377393
389405 # do not show any help at all
390406 help_text=None
391407
408 plugin_options - directly specify jQuery plugin options. see Lookup plugin_options above
409
410
392411 #####Example
393412
394413 from ajax_select import make_ajax_field
404423
405424
406425 from ajax_select.fields import AutoCompleteSelectField
407
426
408427 class ReleaseForm(ModelForm):
409
428
410429 group = AutoCompleteSelectField('group', required=False, help_text=None)
411430
431 #### Setting plugin options
432
433 from ajax_select.fields import AutoCompleteSelectField
434
435 class ReleaseForm(ModelForm):
436
437 group = AutoCompleteSelectField('group', required=False, help_text=None,plugin_options = {'autoFocus':True,'minLength':4})
412438
413439 #### Using ajax selects in a `FormSet`
414440
438464 templates/
439465 ----------
440466
441 Each form field widget is rendered using a template. You may write a custom template per channel and extend the base template in order to implement these blocks:
467 Each form field widget is rendered using a template. You may write a custom template per channel and extend the base template in order to implement these blocks:
442468
443469 {% block extra_script %}{% endblock %}
444470 {% block help %}{% endblock %}
522548 });
523549 {% endblock %}
524550
525 There is no remove as there is no kill/delete button in a simple auto-complete.
551 There is no remove as there is no kill/delete button in a simple auto-complete.
526552 The user may clear the text themselves but there is no javascript involved. Its just a text field.
527553
528554
00 """JQuery-Ajax Autocomplete fields for Django Forms"""
1 __version__ = "1.2.4"
1 __version__ = "1.2.5"
22 __author__ = "crucialfelix"
33 __contact__ = "crucialfelix@gmail.com"
4 __homepage__ = "http://code.google.com/p/django-ajax-selects/"
4 __homepage__ = "https://github.com/crucialfelix/django-ajax-selects/"
55
66 from django.conf import settings
77 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
1515 class LookupChannel(object):
1616
1717 """Subclass this, setting model and overiding the methods below to taste"""
18
18
1919 model = None
20 plugin_options = {}
2021 min_length = 1
21
22
2223 def get_query(self,q,request):
23 """ return a query set searching for the query string q
24 """ return a query set searching for the query string q
2425 either implement this method yourself or set the search_field
2526 in the LookupChannel class definition
2627 """
5051 return [things[aid] for aid in ids if things.has_key(aid)]
5152
5253 def can_add(self,user,argmodel):
53 """ Check if the user has permission to add
54 """ Check if the user has permission to add
5455 one of these models. This enables the green popup +
5556 Default is the standard django permission check
5657 """
7071
7172 def make_ajax_form(model,fieldlist,superclass=ModelForm,show_help_text=False,**kwargs):
7273 """ Creates a ModelForm subclass with autocomplete fields
73
74
7475 usage:
7576 class YourModelAdmin(Admin):
7677 ...
7778 form = make_ajax_form(YourModel,{'contacts':'contact','author':'contact'})
7879
79 where
80 where
8081 'contacts' is a ManyToManyField specifying to use the lookup channel 'contact'
8182 and
8283 'author' is a ForeignKeyField specifying here to also use the lookup channel 'contact'
8485 # will support previous arg name for several versions before deprecating
8586 if 'show_m2m_help' in kwargs:
8687 show_help_text = kwargs.pop('show_m2m_help')
87
88
8889 class TheForm(superclass):
89
90
9091 class Meta:
9192 pass
9293 setattr(Meta, 'model', model)
105106 """ Makes a single autocomplete field for use in a Form
106107
107108 optional args:
108 help_text - default is the model db field's help_text.
109 help_text - default is the model db field's help_text.
109110 None will disable all help text
110111 label - default is the model db field's verbose name
111112 required - default is the model db field's (not) blank
112
113 show_help_text -
113
114 show_help_text -
114115 Django will show help text below the widget, but not for ManyToMany inside of admin inlines
115116 This setting will show the help text inside the widget itself.
116117 """
123124 AutoCompleteSelectField
124125
125126 field = model._meta.get_field(model_fieldname)
126 if kwargs.has_key('label'):
127 label = kwargs.pop('label')
128 else:
129 label = _(capfirst(unicode(field.verbose_name)))
130
131 if kwargs.has_key('help_text'):
132 help_text = kwargs.pop('help_text')
133 else:
134 if isinstance(field.help_text,basestring) and field.help_text:
135 help_text = _(field.help_text)
136 else:
137 help_text = field.help_text
138 if kwargs.has_key('required'):
139 required = kwargs.pop('required')
140 else:
141 required = not field.blank
127 if not kwargs.has_key('label'):
128 kwargs['label'] = _(capfirst(unicode(field.verbose_name)))
129
130 if not kwargs.has_key('help_text') and field.help_text:
131 kwargs['help_text'] = field.help_text
132 if not kwargs.has_key('required'):
133 kwargs['required'] = not field.blank
142134
143135 kwargs['show_help_text'] = show_help_text
144136 if isinstance(field,ManyToManyField):
145137 f = AutoCompleteSelectMultipleField(
146138 channel,
147 required=required,
148 help_text=help_text,
149 label=label,
150139 **kwargs
151140 )
152141 elif isinstance(field,ForeignKey):
153142 f = AutoCompleteSelectField(
154143 channel,
155 required=required,
156 help_text=help_text,
157 label=label,
158144 **kwargs
159145 )
160146 else:
161147 f = AutoCompleteField(
162148 channel,
163 required=required,
164 help_text=help_text,
165 label=label,
166149 **kwargs
167150 )
168151 return f
196179 lambda self,obj: unicode(obj)))
197180 if not hasattr(lookup_class,'format_item_display'):
198181 setattr(lookup_class, 'format_item_display',
199 getattr(lookup_class,'format_item',
182 getattr(lookup_class,'format_item',
200183 lambda self,obj: unicode(obj)))
201184 if not hasattr(lookup_class,'get_result'):
202185 setattr(lookup_class, 'get_result',
203 getattr(lookup_class,'format_result',
186 getattr(lookup_class,'format_result',
204187 lambda self,obj: unicode(obj)))
205188
206189 return lookup_class()
209192 def make_channel(app_model,arg_search_field):
210193 """ used in get_lookup
211194 app_model : app_name.model_name
212 search_field : the field to search against and to display in search results
195 search_field : the field to search against and to display in search results
213196 """
214197 from django.db import models
215198 app_label, model_name = app_model.split(".")
216199 themodel = models.get_model(app_label, model_name)
217
200
218201 class MadeLookupChannel(LookupChannel):
219
202
220203 model = themodel
221204 search_field = arg_search_field
222
205
223206 return MadeLookupChannel()
224207
225208
99
1010 def get_form(self, request, obj=None, **kwargs):
1111 form = super(AjaxSelectAdmin,self).get_form(request,obj,**kwargs)
12
12
1313 autoselect_fields_check_can_add(form,self.model,request.user)
1414 return form
1515
88 from django.template.loader import render_to_string
99 from django.utils.safestring import mark_safe
1010 from django.utils.translation import ugettext as _
11 from django.conf import settings
12 from django.utils import simplejson
1113 import os
1214
1315
16 as_default_help = u'Enter text to search.'
1417
1518 ####################################################################################
1619
2225
2326 def __init__(self,
2427 channel,
25 help_text='',
26 show_help_text=False,
27 *args, **kw):
28 super(forms.widgets.TextInput, self).__init__(*args, **kw)
28 help_text = u'',
29 show_help_text = True,
30 plugin_options = {},
31 *args, **kwargs):
32 self.plugin_options = plugin_options
33 super(forms.widgets.TextInput, self).__init__(*args, **kwargs)
2934 self.channel = channel
3035 self.help_text = help_text
3136 self.show_help_text = show_help_text
3641 final_attrs = self.build_attrs(attrs)
3742 self.html_id = final_attrs.pop('id', name)
3843
44 current_repr = ''
45 initial = None
3946 lookup = get_lookup(self.channel)
4047 if value:
4148 objs = lookup.get_objects([value])
4350 obj = objs[0]
4451 except IndexError:
4552 raise Exception("%s cannot find object:%s" % (lookup, value))
46 display = lookup.format_item_display(obj)
47 current_repr = mark_safe( """new Array("%s",%s)""" % (escapejs(display),obj.pk) )
48 else:
49 current_repr = 'null'
53 current_repr = lookup.format_item_display(obj)
54 initial = [current_repr,obj.pk]
5055
5156 if self.show_help_text:
5257 help_text = self.help_text
5358 else:
54 help_text = ''
59 help_text = u''
5560
5661 context = {
57 'name': name,
58 'html_id' : self.html_id,
59 'min_length': getattr(lookup, 'min_length', 1),
60 'lookup_url': reverse('ajax_lookup',kwargs={'channel':self.channel}),
61 'current_id': value,
62 'current_repr': current_repr,
63 'help_text': help_text,
64 'extra_attrs': mark_safe(flatatt(final_attrs)),
65 'func_slug': self.html_id.replace("-",""),
66 'add_link' : self.add_link,
67 }
62 'name': name,
63 'html_id': self.html_id,
64 'current_id': value,
65 'current_repr': current_repr,
66 'help_text': help_text,
67 'extra_attrs': mark_safe(flatatt(final_attrs)),
68 'func_slug': self.html_id.replace("-",""),
69 'add_link': self.add_link,
70 }
71 context.update(plugin_options(lookup,self.channel,self.plugin_options,initial))
6872 context.update(bootstrap())
69
73
7074 return mark_safe(render_to_string(('autocompleteselect_%s.html' % self.channel, 'autocompleteselect.html'),context))
7175
7276 def value_from_datadict(self, data, files, name):
9195 def __init__(self, channel, *args, **kwargs):
9296 self.channel = channel
9397 widget = kwargs.get("widget", False)
94
98
9599 if not widget or not isinstance(widget, AutoCompleteSelectWidget):
96 help_text = kwargs.get('help_text',_('Enter text to search.'))
97 show_help_text = kwargs.pop('show_help_text',False)
98 kwargs["widget"] = AutoCompleteSelectWidget(channel=channel,help_text=help_text,show_help_text=show_help_text)
100 widget_kwargs = dict(
101 channel = channel,
102 help_text = kwargs.get('help_text',_(as_default_help)),
103 show_help_text = kwargs.pop('show_help_text',True),
104 plugin_options = kwargs.pop('plugin_options',{})
105 )
106 kwargs["widget"] = AutoCompleteSelectWidget(**widget_kwargs)
99107 super(AutoCompleteSelectField, self).__init__(max_length=255,*args, **kwargs)
100108
101109 def clean(self, value):
129137 def __init__(self,
130138 channel,
131139 help_text='',
132 show_help_text=False,
140 show_help_text=True,
141 plugin_options = {},
133142 *args, **kwargs):
134143 super(AutoCompleteSelectMultipleWidget, self).__init__(*args, **kwargs)
135144 self.channel = channel
136
137 self.help_text = help_text or _('Enter text to search.')
145
146 self.help_text = help_text
138147 self.show_help_text = show_help_text
148 self.plugin_options = plugin_options
139149
140150 def render(self, name, value, attrs=None):
141151
156166 objects = lookup.get_objects(value)
157167
158168 # text repr of currently selected items
159 current_repr_json = []
169 initial = []
160170 for obj in objects:
161171 display = lookup.format_item_display(obj)
162 current_repr_json.append( """new Array("%s",%s)""" % (escapejs(display),obj.pk) )
163 current_reprs = mark_safe("new Array(%s)" % ",".join(current_repr_json))
164
172 initial.append([display,obj.pk])
173
165174 if self.show_help_text:
166175 help_text = self.help_text
167176 else:
168 help_text = ''
169
177 help_text = u''
178
170179 context = {
171180 'name':name,
172181 'html_id':self.html_id,
173 'min_length': getattr(lookup, 'min_length', 1),
174 'lookup_url':reverse('ajax_lookup',kwargs={'channel':self.channel}),
175182 'current':value,
176183 'current_ids':current_ids,
177 'current_reprs': current_reprs,
184 'current_reprs':mark_safe(simplejson.dumps(initial)),
178185 'help_text':help_text,
179186 'extra_attrs': mark_safe(flatatt(final_attrs)),
180187 'func_slug': self.html_id.replace("-",""),
181188 'add_link' : self.add_link,
182189 }
190 context.update(plugin_options(lookup,self.channel,self.plugin_options,initial))
183191 context.update(bootstrap())
184192
185193 return mark_safe(render_to_string(('autocompleteselectmultiple_%s.html' % self.channel, 'autocompleteselectmultiple.html'),context))
202210 def __init__(self, channel, *args, **kwargs):
203211 self.channel = channel
204212
205 as_default_help = u'Enter text to search.'
206213 help_text = kwargs.get('help_text')
214 show_help_text = kwargs.pop('show_help_text',False)
215
207216 if not (help_text is None):
208 try:
209 en_help = help_text.translate('en')
210 except AttributeError:
211 pass
212 else:
213 # monkey patch the django default help text to the ajax selects default help text
214 django_default_help = u'Hold down "Control", or "Command" on a Mac, to select more than one.'
215 if django_default_help in en_help:
216 en_help = en_help.replace(django_default_help,'').strip()
217 # '' will cause translation to fail
218 # should be u''
219 if type(help_text) == str:
220 help_text = unicode(help_text)
221 # django admin appends "Hold down "Control",..." to the help text
222 # regardless of which widget is used. so even when you specify an explicit help text it appends this other default text onto the end.
223 # This monkey patches the help text to remove that
224 if help_text != u'':
225 if type(help_text) != unicode:
226 # ideally this could check request.LANGUAGE_CODE
227 translated = help_text.translate(settings.LANGUAGE_CODE)
228 else:
229 translated = help_text
230 django_default_help = _(u'Hold down "Control", or "Command" on a Mac, to select more than one.').translate(settings.LANGUAGE_CODE)
231 if django_default_help in translated:
232 cleaned_help = translated.replace(django_default_help,'').strip()
217233 # probably will not show up in translations
218 if en_help:
219 help_text = _(en_help)
234 if cleaned_help:
235 help_text = cleaned_help
220236 else:
221 help_text = _(as_default_help)
237 help_text = u""
238 show_help_text = False
222239 else:
223240 help_text = _(as_default_help)
224241
225 # admin will also show help text, so by default do not show it in widget
226 # if using in a normal form then set to True so the widget shows help
227 show_help_text = kwargs.pop('show_help_text',False)
228
229 kwargs['widget'] = AutoCompleteSelectMultipleWidget(channel=channel,help_text=help_text,show_help_text=show_help_text)
242 # django admin will also show help text outside of the display
243 # area of the widget. this results in duplicated help.
244 # it should just let the widget do the rendering
245 # so by default do not show it in widget
246 # if using in a normal form then set to True when creating the field
247 widget_kwargs = {
248 'channel': channel,
249 'help_text': help_text,
250 'show_help_text': show_help_text,
251 'plugin_options': kwargs.pop('plugin_options',{})
252 }
253 kwargs['widget'] = AutoCompleteSelectMultipleWidget(**widget_kwargs)
230254 kwargs['help_text'] = help_text
231
255
232256 super(AutoCompleteSelectMultipleField, self).__init__(*args, **kwargs)
233257
234258 def clean(self, value):
255279 def __init__(self, channel, *args, **kwargs):
256280 self.channel = channel
257281 self.help_text = kwargs.pop('help_text', '')
258 self.show_help_text = kwargs.pop('show_help_text',False)
259
282 self.show_help_text = kwargs.pop('show_help_text',True)
283 self.plugin_options = kwargs.pop('plugin_options',{})
284
260285 super(AutoCompleteWidget, self).__init__(*args, **kwargs)
261286
262287 def render(self, name, value, attrs=None):
263288
264 value = value or ''
265
289 initial = value or ''
290
266291 final_attrs = self.build_attrs(attrs)
267292 self.html_id = final_attrs.pop('id', name)
268293
270295 if self.show_help_text:
271296 help_text = self.help_text
272297 else:
273 help_text = ''
298 help_text = u''
299
274300 context = {
275 'current_repr': value,
276 'current_id': value,
301 'current_repr': initial,
302 'current_id': initial,
277303 'help_text': help_text,
278304 'html_id': self.html_id,
279 'min_length': getattr(lookup, 'min_length', 1),
280 'lookup_url': reverse('ajax_lookup', args=[self.channel]),
281305 'name': name,
282 'extra_attrs':mark_safe(flatatt(final_attrs)),
306 'extra_attrs': mark_safe(flatatt(final_attrs)),
283307 'func_slug': self.html_id.replace("-",""),
284308 }
309 context.update(plugin_options(lookup,self.channel,self.plugin_options,initial))
285310 context.update(bootstrap())
286311
287312 templates = ('autocomplete_%s.html' % self.channel,
299324 def __init__(self, channel, *args, **kwargs):
300325 self.channel = channel
301326
302 widget_kwargs = dict(help_text=kwargs.get('help_text', _('Enter text to search.')))
303 widget_kwargs['show_help_text'] = kwargs.pop('show_help_text',False)
327 widget_kwargs = dict(
328 help_text = kwargs.get('help_text', _(as_default_help)),
329 show_help_text = kwargs.pop('show_help_text',True),
330 plugin_options = kwargs.pop('plugin_options',{})
331 )
304332 if 'attrs' in kwargs:
305333 widget_kwargs['attrs'] = kwargs.pop('attrs')
306334 widget = AutoCompleteWidget(channel,**widget_kwargs)
314342 ####################################################################################
315343
316344 def _check_can_add(self,user,model):
317 """ check if the user can add the model, deferring first to
345 """ check if the user can add the model, deferring first to
318346 the channel if it implements can_add()
319347 else using django's default perm check.
320 if it can add, then enable the widget to show the + link
348 if it can add, then enable the widget to show the + link
321349 """
322350 lookup = get_lookup(self.channel)
323351 if hasattr(lookup,'can_add'):
337365 db_field = model._meta.get_field_by_name(name)[0]
338366 form_field.check_can_add(user,db_field.rel.to)
339367
368 def plugin_options(channel,channel_name,widget_plugin_options,initial):
369 """ Make a JSON dumped dict of all options for the jquery ui plugin itself """
370 po = {}
371 if initial:
372 po['initial'] = initial
373 po.update(getattr(channel,'plugin_options',{}))
374 po.update(widget_plugin_options)
375 if not po.get('min_length'):
376 # backward compatibility: honor the channel's min_length attribute
377 # will deprecate that some day and prefer to use plugin_options
378 po['min_length'] = getattr(channel, 'min_length', 1)
379 if not po.get('source'):
380 po['source'] = reverse('ajax_lookup',kwargs={'channel':channel_name})
381 return {
382 'plugin_options': mark_safe(simplejson.dumps(po)),
383 # continue to support any custom templates that still expect these
384 'lookup_url': po['source'],
385 'min_length': po['min_length']
386 }
387
340388
341389 def bootstrap():
342390 b = {}
0
10 .results_on_deck .ui-icon-trash {
21 float: left;
32 cursor: pointer;
3130 */
3231 margin: 0;
3332 padding: 0;
33 position: absolute;
3434 }
3535 ul.ui-autocomplete li {
3636 list-style-type: none;
2121 return this.each(function() {
2222 var id = this.id;
2323 var $this = $(this);
24
24
2525 var $text = $("#"+id+"_text");
2626 var $deck = $("#"+id+"_on_deck");
2727
5656 $this.val('');
5757 $deck.children().fadeOut(1.0).remove();
5858 }
59
59
6060 options.select = receiveResult;
6161 $text.autocomplete(options);
6262 $text.autocompletehtml();
63
63
6464 if (options.initial) {
6565 its = options.initial;
6666 addKiller(its[0], its[1]);
8484 function receiveResult(event, ui) {
8585 pk = ui.item.pk;
8686 prev = $this.val();
87
87
8888 if (prev.indexOf("|"+pk+"|") == -1) {
8989 $this.val((prev ? prev : "|") + pk + "|");
9090 addKiller(ui.item.repr, pk);
114114 options.select = receiveResult;
115115 $text.autocomplete(options);
116116 $text.autocompletehtml();
117
117
118118 if (options.initial) {
119119 $.each(options.initial, function(i, its) {
120120 addKiller(its[0], its[1]);
127127 });
128128 });
129129 };
130 })(jQuery);
131130
132 function addAutoComplete(prefix_id, callback/*(html_id)*/) {
133 /* detects inline forms and converts the html_id if needed */
134 var prefix = 0;
135 var html_id = prefix_id;
136 if(html_id.indexOf("__prefix__") != -1) {
137 // Some dirty loop to find the appropriate element to apply the callback to
138 while (jQuery('#'+html_id).length) {
139 html_id = prefix_id.replace(/__prefix__/, prefix++);
140 }
141 html_id = prefix_id.replace(/__prefix__/, prefix-2);
142 // Ignore the first call to this function, the one that is triggered when
143 // page is loaded just because the "empty" form is there.
144 if (jQuery("#"+html_id+", #"+html_id+"_text").hasClass("ui-autocomplete-input"))
145 return;
131 window.addAutoComplete = function (prefix_id, callback ) { /*(html_id)*/
132 /* detects inline forms and converts the html_id if needed */
133 var prefix = 0;
134 var html_id = prefix_id;
135 if(html_id.indexOf("__prefix__") != -1) {
136 // Some dirty loop to find the appropriate element to apply the callback to
137 while ($('#'+html_id).length) {
138 html_id = prefix_id.replace(/__prefix__/, prefix++);
146139 }
147 callback(html_id);
140 html_id = prefix_id.replace(/__prefix__/, prefix-2);
141 // Ignore the first call to this function, the one that is triggered when
142 // page is loaded just because the "empty" form is there.
143 if ($("#"+html_id+", #"+html_id+"_text").hasClass("ui-autocomplete-input"))
144 return;
148145 }
146 callback(html_id);
147 }
149148 /* the popup handler
150149 requires RelatedObjects.js which is part of the django admin js
151150 so if using outside of the admin then you would need to include that manually */
152 function didAddPopup(win,newId,newRepr) {
153 var name = windowname_to_id(win.name);
154 jQuery("#"+name).trigger('didAddPopup',[html_unescape(newId),html_unescape(newRepr)]);
155 win.close();
156 }
157 }
151 window.didAddPopup = function (win,newId,newRepr) {
152 var name = windowname_to_id(win.name);
153 $("#"+name).trigger('didAddPopup',[html_unescape(newId),html_unescape(newRepr)]);
154 win.close();
155 }
156
157 })(jQuery);
158
159 }
77 }
88 if(typeof jQuery == 'undefined' || (typeof jQuery.ui == 'undefined')) {
99 document.write('<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"><\/script>');
10 document.write('<link type="text/css" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/smoothness/jquery-ui.css" />');
10 document.write('<link type="text/css" rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/smoothness/jquery-ui.css" />');
1111 }
1212 //]]>
1313 </script>
33 jQuery(document).ready(function($){
44 {% block script %}
55 addAutoComplete("{{html_id}}", function(html_id) {
6 $("#"+html_id).autocomplete({
7 minLength: {{min_length}},
8 source: '{{lookup_url}}',
9 initial: '{{current_repr|escapejs}}',
10 select: function(event, ui) {
11 $("#"+html_id).val(ui.item.value);
12 $("#"+html_id).trigger("added");
6 var opts = {{ plugin_options }};
7 opts['select'] =
8 function(event, ui) {
9 $("#"+html_id).val(ui.item.value).trigger("added");
1310 return false;
1411 }
15 }).autocompletehtml();
12 $("#"+html_id).autocomplete(opts).autocompletehtml();
1613 });
1714 {% block extra_script %}{% endblock %}
1815 {% endblock %}
2017 //]]>
2118 </script>
2219 {% block help %}{% if help_text %}<p class="help">{{ help_text }}</p>{% endif %}{% endblock %}
23 {{ inline }}
20 {{ inline }}
44 <a href="{{ add_link }}" class="add-another addlink" id="add_{{ html_id }}" onclick="return showAddAnotherPopup(this);"> add</a>
55 {% endif %}
66 <input type="hidden" name="{{name}}" id="{{html_id}}" value="{{current_id}}" />
7 <div id="{{html_id}}_on_deck" class="results_on_deck"><div>{{current_result|safe}}</div></div>
7 <div id="{{html_id}}_on_deck" class="results_on_deck"><div>{{current_repr|safe}}</div></div>
88 <script type="text/javascript">//<![CDATA[
99 jQuery(document).ready(function($){
1010 addAutoComplete("{{html_id}}", function(html_id) {
11 $("#"+html_id).autocompleteselect({
12 minLength: {{min_length}},
13 source: '{{lookup_url}}',
14 initial: {{current_repr}}
15 });
11 $("#"+html_id).autocompleteselect({{ plugin_options }});
1612 });
1713 {% block extra_script %}{% endblock %}
1814 });//]]>
1915 </script>
2016 {% block help %}{% if help_text %}<p class="help">{{help_text}}</p>{% endif %}{% endblock %}
2117 </span>
22 {{ inline }}
18 {{ inline }}
77 <script type="text/javascript">//<![CDATA[
88 jQuery(document).ready(function($){
99 addAutoComplete("{{html_id}}", function(html_id) {
10 $("#"+html_id).autocompleteselectmultiple({
11 minLength: {{min_length}},
12 source: '{{lookup_url}}',
13 initial: {{current_reprs}}
14 });
10 $("#"+html_id).autocompleteselectmultiple({{plugin_options}});
1511 });
1612 {% block extra_script %}{% endblock %}
1713 });
1915 </script>
2016 {# django admin adds the help text. this is for use outside of the admin #}
2117 {% block help %}{% if help_text %}<p class="help">{{help_text}}</p>{% endif %}{% endblock %}
22 {{ inline }}
18 {{ inline }}
4848 make sure that you have added ajax_select.urls to your urls.py:
4949 (r'^ajax_select/', include('ajax_select.urls')),
5050 this URL is expected in the code below, so it won't work under a different path
51
51
5252 this view then hijacks the result that the django admin returns
53 and instead of calling django's dismissAddAnontherPopup(win,newId,newRepr)
53 and instead of calling django's dismissAddAnontherPopup(win,newId,newRepr)
5454 it calls didAddPopup(win,newId,newRepr) which was added inline with bootstrap.html
5555 """
5656 themodel = models.get_model(app_label, model)
5757 admin = site._registry[themodel]
5858
5959 # TODO : should detect where we really are
60 admin.admin_site.root_path = "/ajax_select/"
60 admin.admin_site.root_path = "/ajax_select/"
6161
6262 response = admin.add_view(request,request.path)
6363 if request.method == 'POST':
99 class PersonAdmin(admin.ModelAdmin):
1010
1111 pass
12
12
1313 admin.site.register(Person,PersonAdmin)
1414
1515
1616
1717 class LabelAdmin(AjaxSelectAdmin):
18 """ to get + popup buttons, subclass AjaxSelectAdmin
19
18 """ to get + popup buttons, subclass AjaxSelectAdmin
19
2020 multi-inheritance is also possible if you have an Admin class you want to inherit from:
21
21
2222 class PersonAdmin(YourAdminSuperclass,AjaxSelectAdmin):
23
23
2424 this acts as a MixIn to add the relevant methods
2525 """
2626 # this shows a ForeignKey field
2828 # create an ajax form class using the factory function
2929 # model,fieldlist, [form superclass]
3030 form = make_ajax_form(Label,{'owner':'person'})
31
31
3232 admin.site.register(Label,LabelAdmin)
3333
3434
6565 model = Book
6666 form = make_ajax_form(Book,{'about_group':'group','mentions_persons':'person'},show_help_text=True)
6767 extra = 2
68
68
6969 # + check add still not working
7070 # no + appearing
7171 # def get_formset(self, request, obj=None, **kwargs):
7878 inlines = [
7979 BookInline,
8080 ]
81
81
8282 admin.site.register(Author, AuthorAdmin)
8383
8484
1111 model = Release
1212
1313 # args: this model, fieldname on this model, lookup_channel_name
14 group = make_ajax_field(Release,'group','group')
15
16 # no help text at all
14 group = make_ajax_field(Release,'group','group',show_help_text=True)
15
1716 label = make_ajax_field(Release,'label','label',help_text="Search for label by name")
18
17
1918 # any extra kwargs are passed onto the field, so you may pass a custom help_text here
20 songs = make_ajax_field(Release,'songs','song',help_text=u"Search for song by title")
19 #songs = make_ajax_field(Release,'songs','song',help_text=u"Search for song by title")
20
21 # testing bug with no help text supplied
22 songs = make_ajax_field(Release,'songs','song',help_text="",show_help_text=True)
2123
2224 # these are from a fixed array defined in lookups.py
2325 title = make_ajax_field(Release,'title','cliche',help_text=u"Autocomplete will suggest clichés about cats.")
11 # creates a virtualenv and installs a django here
22 virtualenv AJAXSELECTS
33 source AJAXSELECTS/bin/activate
4 easy_install django
4 pip install django
55
66 # put ajax selects in the path
77 ln -s ../ajax_select/ ./ajax_select
0
10
21 from django.db.models import Q
32 from django.utils.html import escape
3534
3635 def get_result(self,obj):
3736 return unicode(obj)
38
37
3938 def format_match(self,obj):
4039 return self.format_item_display(obj)
4140
5958
6059 def get_result(self,obj):
6160 return unicode(obj.title)
62
61
6362 def format_match(self,obj):
6463 return self.format_item_display(obj)
6564
0 # -*- coding: utf8 -*-
01
12 from django.db import models
23
5354 title = models.CharField(max_length=100)
5455 catalog = models.CharField(blank=True, max_length=100)
5556
56 group = models.ForeignKey(Group,blank=True,null=True)
57 group = models.ForeignKey(Group,blank=True,null=True,verbose_name=u"Русский текст")
5758 label = models.ForeignKey(Label,blank=False,null=False)
5859 songs = models.ManyToManyField(Song,blank=True)
5960
7071 title = models.CharField(max_length=100)
7172 about_group = models.ForeignKey(Group)
7273 mentions_persons = models.ManyToManyField(Person)
73
74
88 'django.contrib.sites',
99 'django.contrib.admin',
1010 'example',
11
11
1212 ####################################
1313 'ajax_select', # <- add the app
1414 ####################################
2222 AJAX_LOOKUP_CHANNELS = {
2323 # simplest way, automatically construct a search channel by passing a dictionary
2424 'label' : {'model':'example.label', 'search_field':'name'},
25
25
2626 # Custom channels are specified with a tuple
2727 # channel: ( module.where_lookup_is, ClassNameOfLookup )
2828 'person' : ('example.lookups', 'PersonLookup'),
9999 # http://www.i18nguy.com/unicode/language-identifiers.html
100100 LANGUAGE_CODE = 'en-us'
101101
102 # for testing translations
103 # LANGUAGE_CODE = 'de-at'
104
102105 SITE_ID = 1
103106
104107 # If you set this to False, Django will make some optimizations so as not
105108 # to load the internationalization machinery.
106 USE_I18N = False
109 USE_I18N = True
110
107111
108112 # Absolute path to the directory that holds media.
109113 # Example: "/home/media/media.lawrence.com/"
117121 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
118122 # trailing slash.
119123 # Examples: "http://foo.com/media/", "/media/".
120 ADMIN_MEDIA_PREFIX = '/media/'
124 STATIC_URL = '/media/'
121125
122126 # Make this unique, and don't share it with nobody.
123127 SECRET_KEY = '=9fhrrwrazha6r_m)r#+in*@n@i322ubzy4r+zz%wz$+y(=qpb'
124128
125 # List of callables that know how to import templates from various sources.
126 TEMPLATE_LOADERS = (
127 'django.template.loaders.filesystem.load_template_source',
128 'django.template.loaders.app_directories.load_template_source',
129 # 'django.template.loaders.eggs.load_template_source',
130 )
131
132 MIDDLEWARE_CLASSES = (
133 'django.middleware.common.CommonMiddleware',
134 'django.contrib.sessions.middleware.SessionMiddleware',
135 'django.contrib.auth.middleware.AuthenticationMiddleware',
136 )
137129
138130 ROOT_URLCONF = 'example.urls'
139131
1414 label="Favorite Cliché",
1515 attrs={'size': 100}
1616 )
17
17
1818 def search_form(request):
19
19
2020 dd = {}
2121 if 'q' in request.GET:
2222 dd['entered'] = request.GET.get('q')
2424 form = SearchForm(initial=initial)
2525 dd['form'] = form
2626 return render_to_response('search_form.html',dd,context_instance=RequestContext(request))
27
00 #!/usr/bin/env python
11
2 from distutils.core import setup
2 try:
3 from setuptools import setup
4 except ImportError:
5 from ez_setup import use_setuptools
6 use_setuptools()
7 from setuptools import setup
38
49 setup(name='django-ajax-selects',
5 version='1.2.4',
10 version='1.2.5',
611 description='jQuery-powered auto-complete fields for editing ForeignKey, ManyToManyField and CharField',
712 author='crucialfelix',
813 author_email='crucialfelix@gmail.com',
9 url='http://code.google.com/p/django-ajax-selects/',
14 url='https://github.com/crucialfelix/django-ajax-selects/',
1015 packages=['ajax_select', ],
1116 package_data={'ajax_select': ['*.py','*.txt','static/css/*','static/images/*','static/js/*','templates/*.html', 'templates/ajax_select/*.html']},
1217 classifiers = [
2530 Enables editing of `ForeignKey`, `ManyToManyField` and `CharField` using jQuery UI AutoComplete.
2631
2732 1. The user types a search term into the text field
28 2. An ajax request is sent to the server.
29 3. The dropdown menu is populated with results.
33 2. An ajax request is sent to the server.
34 3. The dropdown menu is populated with results.
3035 4. User selects by clicking or using arrow keys
3136 5. Selected result displays in the "deck" area directly below the input field.
3237 6. User can click trashcan icon to remove a selected item