diff --git a/README.md b/README.md index 4db23d0edd..51267e62d1 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Features + Customize HTML, CSS and JS + JQuery triggers allow you to customize interface behavior to respond when items are added or removed + Default (but customizable) security prevents griefers from pilfering your data via JSON requests - ++ Django 1.6+ Quick Installation @@ -65,7 +65,7 @@ In settings.py : In your urls.py: - from django.conf.urls import * + from django.conf.urls import patterns, include from django.contrib import admin from ajax_select import urls as ajax_select_urls @@ -78,24 +78,23 @@ In your urls.py: (r'^admin/', include(admin.site.urls)), ) -For Django 1.3 or earlier replace the first line by `from django.conf.urls.defaults import *`. In your admin.py: from django.contrib import admin from ajax_select import make_ajax_form from ajax_select.admin import AjaxSelectAdmin - from example.models import * + from example.models import Person, Label class PersonAdmin(admin.ModelAdmin): pass - admin.site.register(Person,PersonAdmin) + admin.site.register(Person, PersonAdmin) - class SongAdmin(AjaxSelectAdmin): + class LabelAdmin(AjaxSelectAdmin): # create an ajax form class using the factory function # model,fieldlist, [form superclass] - form = make_ajax_form(Label,{'owner':'person'}) - admin.site.register(Label,LabelAdmin) + form = make_ajax_form(Label, {'owner': 'person'}) + admin.site.register(Label, LabelAdmin) example/lookups.py: @@ -105,7 +104,7 @@ example/lookups.py: model = Song - def get_query(self,q,request): + def get_query(self, q, request): return Song.objects.filter(title__icontains=q).order_by('title') @@ -159,7 +158,7 @@ settings.py Defines the available lookup channels. -+ channel_name : {'model': 'app.modelname', 'search_field': 'name_of_field_to_search' } ++ channel_name : {'model': 'app.modelname', 'search_field': 'name_of_field_to_search'} > This will create a channel automatically channel_name : ( 'app.lookups', 'YourLookup' ) @@ -167,10 +166,10 @@ Defines the available lookup channels. AJAX_LOOKUP_CHANNELS = { # channel : dict with settings to create a channel - 'person' : {'model':'example.person', 'search_field':'name'}, + 'person': {'model': 'example.person', 'search_field': 'name'}, # channel: ( module.where_lookup_is, ClassNameOfLookup ) - 'song' : ('example.lookups', 'SongLookup'), + 'song': ('example.lookups', 'SongLookup'), } #### AJAX_SELECT_BOOTSTRAP @@ -203,7 +202,7 @@ urls.py Simply include the ajax_select urls in your site's urlpatterns: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, include from django.contrib import admin from ajax_select import urls as ajax_select_urls @@ -229,26 +228,26 @@ Those old lookup channels will still work and the previous methods will be used. from ajax_select import LookupChannel from django.utils.html import escape from django.db.models import Q - from example.models import * + from example.models import Person class PersonLookup(LookupChannel): model = Person - def get_query(self,q,request): + def get_query(self, q, request): return Person.objects.filter(Q(name__icontains=q) | Q(email__istartswith=q)).order_by('name') - def get_result(self,obj): + def get_result(self, obj): u""" result is the simple text that is the completion of what the person typed """ return obj.name - def format_match(self,obj): + def format_match(self, obj): """ (HTML) formatted item for display in the dropdown """ return self.format_item_display(obj) - def format_item_display(self,obj): + def format_item_display(self, obj): """ (HTML) formatted item for displaying item in the selected deck area """ - return u"%s
%s
" % (escape(obj.name),escape(obj.email)) + return u"%s
%s
" % (escape(obj.name), escape(obj.email)) Note that raw strings should always be escaped with the escape() function @@ -405,6 +404,7 @@ A factory function to makes an ajax field + widget. The helper ensures things a class Meta: model = Release + exclude = [] group = make_ajax_field(Release, 'group', 'group', help_text=None) @@ -435,7 +435,7 @@ There is possibly a better way to do this, but here is an initial example: from django.forms.models import BaseModelFormSet from ajax_select.fields import AutoCompleteSelectMultipleField, AutoCompleteSelectField - from models import * + from models import Task # create a superclass class BaseTaskFormSet(BaseModelFormSet): @@ -446,7 +446,7 @@ There is possibly a better way to do this, but here is an initial example: form.fields["project"] = AutoCompleteSelectField('project', required=False) # pass in the base formset class to the factory - TaskFormSet = modelformset_factory(Task, fields=('name', 'project', 'area'),extra=0, formset=BaseTaskFormSet) + TaskFormSet = modelformset_factory(Task, fields=('name', 'project', 'area'), extra=0, formset=BaseTaskFormSet) @@ -459,9 +459,9 @@ Each form field widget is rendered using a template. You may write a custom tem {% block help %}{% endblock %} - - - + + +
form Fieldtries this firstdefault template
AutoCompleteFieldtemplates/autocomplete_{{CHANNELNAME}}.htmltemplates/autocomplete.html
AutoCompleteSelectFieldtemplates/autocompleteselect_{{CHANNELNAME}}.htmltemplates/autocompleteselect.html
AutoCompleteSelectMultipleFieldtemplates/autocompleteselectmultiple_{{CHANNELNAME}}.htmltemplates/autocompleteselectmultiple.html
form Fieldtries this firstdefault template
AutoCompleteFieldtemplates/autocomplete_{{CHANNELNAME}}.htmltemplates/autocomplete.html
AutoCompleteSelectFieldtemplates/autocompleteselect_{{CHANNELNAME}}.htmltemplates/autocompleteselect.html
AutoCompleteSelectMultipleFieldtemplates/autocompleteselectmultiple_{{CHANNELNAME}}.htmltemplates/autocompleteselectmultiple.html
See ajax_select/static/js/ajax_select.js below for the use of jQuery trigger events @@ -501,35 +501,35 @@ Extend the template, implement the extra_script block and bind functions that wi ##### multi select: {% block extra_script %} - $("#{{html_id}}_on_deck").bind('added',function() { - id = $("#{{html_id}}").val(); - alert('added id:' + id ); + $("#{{html_id}}_on_deck").bind('added', function() { + id = $("#{{html_id}}").val(); + alert('added id:' + id ); }); - $("#{{html_id}}_on_deck").bind('killed',function() { - current = $("#{{html_id}}").val() - alert('removed, current is:' + current); + $("#{{html_id}}_on_deck").bind('killed', function() { + current = $("#{{html_id}}").val() + alert('removed, current is:' + current); }); {% endblock %} ##### select: {% block extra_script %} - $("#{{html_id}}_on_deck").bind('added',function() { - id = $("#{{html_id}}").val(); - alert('added id:' + id ); - }); - $("#{{html_id}}_on_deck").bind('killed',function() { - alert('removed'); - }); + $("#{{html_id}}_on_deck").bind('added', function() { + id = $("#{{html_id}}").val(); + alert('added id:' + id ); + }); + $("#{{html_id}}_on_deck").bind('killed', function() { + alert('removed'); + }); {% endblock %} ##### auto-complete text field: {% block extra_script %} - $('#{{ html_id }}').bind('added',function() { - entered = $('#{{ html_id }}').val(); - alert( entered ); - }); + $('#{{ html_id }}').bind('added', function() { + entered = $('#{{ html_id }}').val(); + alert(entered); + }); {% endblock %} There is no remove as there is no kill/delete button in a simple auto-complete. diff --git a/Release-notes.md b/Release-notes.md new file mode 100644 index 0000000000..121c65ab03 --- /dev/null +++ b/Release-notes.md @@ -0,0 +1,54 @@ + +Version 1.3.6 +============= + +Support for Django 1.8 + +Version 1.3.5 +============= + +Support for Django 1.7 +Support for non-integer primary keys + +Version 1.3.4 +============= + +Fix protocols removing http/https in bootstrap + +Version 1.3.2 +============= + +Fixed issues with bootstrap.js correctly detecting the presence of jQuery.ui.autocomplete + + +Version 1.3 +=========== + ++ Support for Django 1.5 and 1.6 ++ Assets moved so that staticfiles works correctly ++ Media classes for widgets added so that Form and Admin media work correctly ++ Spinner image is now served locally, no from github (since staticfiles works now) ++ Improved bootstrap.js that locates or CDN loads a jQuery and a jQuery-ui as needed ++ XSS vulnerability patched in the default lookup + ++ Inline scripts moved out of html. + Form fields are activated after the body is loaded with plugin settings stored in the field's data-plugin-options. + This means that javascript including jquery can be at the bottom of the page. + Also less html, more resuable javascript. + ++ max-width hack removed + This set the max-width of the dropdown menu for autocomplete fields (simple text) to the size of the text field. + Dropdowns are now the min-width of the text field and max width of 60% of parent div. + This works well in Django admin, in pop ups and in narrow pages. + + +Breaking changes +---------------- + +The widget templates no longer have any javascript in them. If you have custom templates you can simplify them now. + +The extra_script block is retained in case you are extending a template (to add custom triggers) but it is no longer inside a `jQuery.ready(function() { })` block, so if you are using it then you will need to wrap your code in one. + +The bootstrap script uses Django staticfiles + +AJAX_SELECT_BOOTSTRAP defaults to True now diff --git a/ajax_select/__init__.py b/ajax_select/__init__.py index 511feaf963..0a3026fe9d 100644 --- a/ajax_select/__init__.py +++ b/ajax_select/__init__.py @@ -1,5 +1,5 @@ """JQuery-Ajax Autocomplete fields for Django Forms""" -__version__ = "1.3.5" +__version__ = "1.3.6" __author__ = "crucialfelix" __contact__ = "crucialfelix@gmail.com" __homepage__ = "https://github.com/crucialfelix/django-ajax-selects/" @@ -92,7 +92,8 @@ class YourModelAdmin(Admin): class TheForm(superclass): class Meta: - pass + exclude = [] + setattr(Meta, 'model', model) if hasattr(superclass, 'Meta'): if hasattr(superclass.Meta, 'fields'): @@ -133,12 +134,12 @@ def make_ajax_field(model, model_fieldname, channel, show_help_text=False, **kwa AutoCompleteSelectField field = model._meta.get_field(model_fieldname) - if not 'label' in kwargs: + if 'label' not in kwargs: kwargs['label'] = _(capfirst(force_text(field.verbose_name))) - if not 'help_text' in kwargs and field.help_text: + if ('help_text' not in kwargs) and field.help_text: kwargs['help_text'] = field.help_text - if not 'required' in kwargs: + if 'required' not in kwargs: kwargs['required'] = not field.blank kwargs['show_help_text'] = show_help_text @@ -160,7 +161,7 @@ def make_ajax_field(model, model_fieldname, channel, show_help_text=False, **kwa return f -#################### private ################################################## +# ----------------------- private --------------------------------------------- # def get_lookup(channel): """ find the lookup class for the named channel. this is used internally """ diff --git a/ajax_select/fields.py b/ajax_select/fields.py index bbc4fd0a21..5e18e50bc5 100644 --- a/ajax_select/fields.py +++ b/ajax_select/fields.py @@ -5,7 +5,10 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.core.urlresolvers import reverse -from django.forms.util import flatatt +try: + from django.forms.utils import flatatt +except ImportError: + from django.forms.util import flatatt from django.template.loader import render_to_string from django.template.defaultfilters import force_escape from django.utils.encoding import force_text @@ -21,10 +24,13 @@ IS_PYTHON2 = sys.version_info[0] == 2 -def _to_number(got): - if IS_PYTHON2: - return long(got) - return int(got) +def _as_pk(got): + # a unicode method that checks for integers + if got.isnumeric(): + if IS_PYTHON2: + return long(got) + return int(got) + return got def _media(self): @@ -98,16 +104,11 @@ def render(self, name, value, attrs=None): 'add_link': self.add_link, } context.update(plugin_options(lookup, self.channel, self.plugin_options, initial)) - - return mark_safe(render_to_string(('autocompleteselect_%s.html' % self.channel, 'autocompleteselect.html'), context)) + out = render_to_string(('autocompleteselect_%s.html' % self.channel, 'autocompleteselect.html'), context) + return mark_safe(out) def value_from_datadict(self, data, files, name): - - got = data.get(name, None) - if got: - return _to_number(got) - else: - return None + return _as_pk(data.get(name, None)) def id_for_label(self, id_): return '%s_text' % id_ @@ -219,12 +220,14 @@ def render(self, name, value, attrs=None): 'add_link': self.add_link, } context.update(plugin_options(lookup, self.channel, self.plugin_options, initial)) - - return mark_safe(render_to_string(('autocompleteselectmultiple_%s.html' % self.channel, 'autocompleteselectmultiple.html'), context)) + out = render_to_string( + ('autocompleteselectmultiple_%s.html' % self.channel, 'autocompleteselectmultiple.html'), + context) + return mark_safe(out) def value_from_datadict(self, data, files, name): # eg. 'members': ['|229|4688|190|'] - return [_to_number(val) for val in data.get(name, '').split('|') if val] + return [_as_pk(val) for val in data.get(name, '').split('|') if val] def id_for_label(self, id_): return '%s_text' % id_ @@ -248,7 +251,8 @@ def __init__(self, channel, *args, **kwargs): if type(help_text) == str: help_text = force_text(help_text) # django admin appends "Hold down "Control",..." to the help text - # regardless of which widget is used. so even when you specify an explicit help text it appends this other default text onto the end. + # regardless of which widget is used. so even when you specify an explicit + # help text it appends this other default text onto the end. # This monkey patches the help text to remove that if help_text != '': if not self._is_string(help_text): @@ -256,7 +260,8 @@ def __init__(self, channel, *args, **kwargs): translated = help_text.translate(settings.LANGUAGE_CODE) else: translated = help_text - django_default_help = _('Hold down "Control", or "Command" on a Mac, to select more than one.').translate(settings.LANGUAGE_CODE) + dh = 'Hold down "Control", or "Command" on a Mac, to select more than one.' + django_default_help = _(dh).translate(settings.LANGUAGE_CODE) if django_default_help in translated: cleaned_help = translated.replace(django_default_help, '').strip() # probably will not show up in translations @@ -354,7 +359,8 @@ def render(self, name, value, attrs=None): class AutoCompleteField(forms.CharField): """ - Field uses an AutoCompleteWidget to lookup possible completions using a channel and stores raw text (not a foreign key) + Field uses an AutoCompleteWidget to lookup possible completions + using a channel and stores raw text (not a foreign key) """ channel = None @@ -379,10 +385,11 @@ def __init__(self, channel, *args, **kwargs): #################################################################################### def _check_can_add(self, user, model): - """ check if the user can add the model, deferring first to - the channel if it implements can_add() - else using django's default perm check. - if it can add, then enable the widget to show the + link + """ + check if the user can add the model, deferring first to + the channel if it implements can_add() + else using django's default perm check. + if it can add, then enable the widget to show the + link """ lookup = get_lookup(self.channel) if hasattr(lookup, 'can_add'): @@ -391,11 +398,17 @@ def _check_can_add(self, user, model): ctype = ContentType.objects.get_for_model(model) can_add = user.has_perm("%s.add_%s" % (ctype.app_label, ctype.model)) if can_add: - self.widget.add_link = reverse('add_popup', kwargs={'app_label': model._meta.app_label, 'model': model._meta.object_name.lower()}) + self.widget.add_link = reverse('add_popup', kwargs={ + 'app_label': model._meta.app_label, + 'model': model._meta.object_name.lower() + }) def autoselect_fields_check_can_add(form, model, user): - """ check the form's fields for any autoselect fields and enable their widgets with + sign add links if permissions allow""" + """ + check the form's fields for any autoselect fields and enable their + widgets with + sign add links if permissions allow + """ for name, form_field in form.declared_fields.items(): if isinstance(form_field, (AutoCompleteSelectMultipleField, AutoCompleteSelectField)): db_field = model._meta.get_field_by_name(name)[0] @@ -426,4 +439,4 @@ def plugin_options(channel, channel_name, widget_plugin_options, initial): # continue to support any custom templates that still expect these 'lookup_url': po['source'], 'min_length': po['min_length'] - } + } diff --git a/ajax_select/static/ajax_select/js/ajax_select.js b/ajax_select/static/ajax_select/js/ajax_select.js index c2de21a466..9661949195 100644 --- a/ajax_select/static/ajax_select/js/ajax_select.js +++ b/ajax_select/static/ajax_select/js/ajax_select.js @@ -15,7 +15,7 @@ } $this.val(ui.item.pk); $text.val(''); - addKiller(ui.item.repr); + addKiller(ui.item.repr, ui.item.pk); $deck.trigger('added', [ui.item.pk, ui.item]); $this.trigger('change'); diff --git a/ajax_select/urls.py b/ajax_select/urls.py index 35ba1e8e2f..445a1354f2 100644 --- a/ajax_select/urls.py +++ b/ajax_select/urls.py @@ -1,7 +1,7 @@ try: - from django.conf.urls import * + from django.conf.urls import patterns, url except: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, url urlpatterns = patterns('', diff --git a/example/example/admin.py b/example/example/admin.py index 2c8f30da5a..91d35fa3cb 100644 --- a/example/example/admin.py +++ b/example/example/admin.py @@ -3,7 +3,7 @@ from ajax_select import make_ajax_form from ajax_select.admin import AjaxSelectAdmin, AjaxSelectAdminTabularInline from example.forms import ReleaseForm -from example.models import * +from example.models import Person, Label, Group, Song, Release, Book, Author class PersonAdmin(AjaxSelectAdmin): diff --git a/example/example/forms.py b/example/example/forms.py index 8a4db7e945..cc2d911e2c 100644 --- a/example/example/forms.py +++ b/example/example/forms.py @@ -9,6 +9,7 @@ class ReleaseForm(ModelForm): class Meta: model = Release + exclude = [] # args: this model, fieldname on this model, lookup_channel_name group = make_ajax_field(Release, 'group', 'group', show_help_text=True) @@ -16,7 +17,7 @@ class Meta: label = make_ajax_field(Release, 'label', 'label', help_text="Search for label by name") # any extra kwargs are passed onto the field, so you may pass a custom help_text here - #songs = make_ajax_field(Release,'songs','song', help_text=u"Search for song by title") + # songs = make_ajax_field(Release,'songs','song', help_text=u"Search for song by title") # testing bug with no help text supplied songs = make_ajax_field(Release, 'songs', 'song', help_text="", show_help_text=True) diff --git a/example/example/lookups.py b/example/example/lookups.py index 31ddcc2742..5e51e714db 100644 --- a/example/example/lookups.py +++ b/example/example/lookups.py @@ -1,7 +1,7 @@ from django.db.models import Q from django.utils.html import escape -from example.models import * +from example.models import Person, Group, Song from ajax_select import LookupChannel @@ -81,7 +81,19 @@ class ClicheLookup(LookupChannel): u"let the cat out of the bag", u"fat cat", u"the early bird catches the worm", - u"catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as catch as catch can as can", + u"catch as catch can as catch as catch can as catch as catch can as catch as catch " + "can as catch as catch can as catch as catch can as catch as catch can as catch as " + "catch can as catch as catch can as catch as catch can as catch as catch can as catch " + "as catch can as catch as catch can as catch as catch can as catch as catch can as " + "catch as catch can as catch as catch can as catch as catch can as catch as catch " + "can as catch as catch can as catch as catch can as catch as catch can as catch " + "as catch can as catch as catch can as catch as catch can as catch as catch can " + "as catch as catch can as catch as catch can as catch as catch can as catch as " + "catch can as catch as catch can as catch as catch can as catch as catch can as " + "catch as catch can as catch as catch can as catch as catch can as catch as catch " + "can as catch as catch can as catch as catch can as catch as catch can as catch " + "as catch can as catch as catch can as catch as catch can as catch as catch can " + "as catch as catch can as catch as catch can as can", u"you can catch more flies with honey than with vinegar", u"catbird seat", u"cat's paw", diff --git a/example/example/models.py b/example/example/models.py index c635752420..6a523c74ac 100644 --- a/example/example/models.py +++ b/example/example/models.py @@ -18,7 +18,9 @@ class Group(models.Model): """ a music group """ name = models.CharField(max_length=200, unique=True, help_text="Name of the group") - members = models.ManyToManyField(Person, blank=True, help_text="Enter text to search for and add each member of the group.") + members = models.ManyToManyField(Person, + blank=True, + help_text="Enter text to search for and add each member of the group.") url = models.URLField(blank=True) def __unicode__(self): diff --git a/example/example/settings.py b/example/example/settings.py index b824a7586e..3a51a14caf 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -5,8 +5,8 @@ INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', + 'django.contrib.messages', 'django.contrib.sessions', - 'django.contrib.sites', 'django.contrib.admin', 'django.contrib.staticfiles', 'example', @@ -16,6 +16,11 @@ #################################### ) +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware' +) ########################################################################### @@ -86,8 +91,6 @@ # for testing translations # LANGUAGE_CODE = 'de-at' -SITE_ID = 1 - # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = True diff --git a/example/example/urls.py b/example/example/urls.py index 62796851c8..980fbfad9d 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,7 +1,7 @@ try: - from django.conf.urls import * + from django.conf.urls import patterns, url, include except: - from django.conf.urls.defaults import * + from django.conf.urls.defaults import patterns, url, include from django.conf.urls.static import static from django.contrib import admin from django.conf import settings @@ -11,7 +11,9 @@ admin.autodiscover() urlpatterns = patterns('', - url(r'^search_form', view='example.views.search_form', name='search_form'), + url(r'^search_form', + view='example.views.search_form', + name='search_form'), (r'^admin/lookups/', include(ajax_select_urls)), (r'^admin/', include(admin.site.urls)), ) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/example/install.sh b/example/install.sh index 7fce4d2d9f..552830d5d4 100755 --- a/example/install.sh +++ b/example/install.sh @@ -16,14 +16,14 @@ else pip install django fi -echo "Creating a sqllite database:" -./manage.py syncdb - if [ ! -d ./ajax_select ]; then echo "\nSymlinking ajax_select into this app directory:" ln -s ../ajax_select/ ./ajax_select fi +echo "Creating a sqllite database:" +./manage.py syncdb + echo "\nto activate the virtualenv:\nsource AJAXSELECTS/bin/activate" echo '\nto create an admin account:' diff --git a/example/manage-1.3.x.py b/example/manage-1.3.x.py deleted file mode 100644 index 5e78ea979e..0000000000 --- a/example/manage-1.3.x.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..1ecac25e96 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +ignore = E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E133 +exclude = ./example/AJAXSELECTS/lib/* +max-line-length = 120 +max-complexity = 10 diff --git a/setup.py b/setup.py index f029363e88..db063c2ac7 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ setup( name='django-ajax-selects', - version='1.3.5', + version='1.3.6', description='jQuery-UI powered auto-complete fields for editing ForeignKey, ManyToManyField and CharField', author='crucialfelix', author_email='crucialfelix@gmail.com', @@ -51,7 +51,7 @@ 5. Selected result displays in the "deck" area directly below the input field. 6. User can click trashcan icon to remove a selected item -+ Django 1.4+ ++ Django 1.6+ + Optional boostrap mode allows easy installation by automatic inclusion of jQueryUI from the googleapis CDN + Compatible with staticfiles, appmedia, django-compressor etc + Popup to add a new item is supported @@ -59,7 +59,8 @@ + Ajax Selects works in the admin and also in public facing forms. + Rich formatting can be easily defined for the dropdown display and the selected "deck" display. + Templates and CSS are fully customizable -+ JQuery triggers enable you to add javascript to respond when items are added or removed, so other interface elements on the page can react ++ JQuery triggers enable you to add javascript to respond when items are added or removed, + so other interface elements on the page can react + Default (but customizable) security prevents griefers from pilfering your data via JSON requests """