From c78d92c0c39e3a3df02f9c23d0a572b04203bd45 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Sat, 2 Aug 2014 14:23:21 +0200 Subject: [PATCH 01/15] fix #79 - missing pk for the addKiller function for autocompleteselect --- ajax_select/static/ajax_select/js/ajax_select.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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'); From 6f38fecaaa9fffbd79f5d95e8d312e40ae791fbf Mon Sep 17 00:00:00 2001 From: Ony A Date: Mon, 22 Sep 2014 12:52:47 +0100 Subject: [PATCH 02/15] Added option for fields in TheForm superclass --- ajax_select/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ajax_select/__init__.py b/ajax_select/__init__.py index 511feaf963..af7c571157 100644 --- a/ajax_select/__init__.py +++ b/ajax_select/__init__.py @@ -92,7 +92,7 @@ class YourModelAdmin(Admin): class TheForm(superclass): class Meta: - pass + fields = '__all__' setattr(Meta, 'model', model) if hasattr(superclass, 'Meta'): if hasattr(superclass.Meta, 'fields'): From e221f094bb89efb03f38bcd61a2dd6f9d30ed241 Mon Sep 17 00:00:00 2001 From: David Cormier Date: Thu, 15 Jan 2015 17:16:01 -0500 Subject: [PATCH 03/15] Update README.md Change `SongAdmin` to `LabelAdmin` to make it reflect the example in https://github.com/crucialfelix/django-ajax-selects/blob/60fd4cd023059fcb234a34968b2aaaf97df020a4/example/example/admin.py#L16 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4db23d0edd..cff0249cf2 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ In your admin.py: pass 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'}) From f7a390d0bc9020e008689d62a28a4afb8051fe09 Mon Sep 17 00:00:00 2001 From: Skrzypek Date: Thu, 19 Mar 2015 22:59:49 +0100 Subject: [PATCH 04/15] Change order for running script by .sh #112 (NOTICE) Fixed `ImportError: No module named ajax_select` #112 --- example/install.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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:' From 29672378e11dcd7c2d04fa04ff224daf7fcc3577 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 16:47:25 +0200 Subject: [PATCH 05/15] protect Form meta classes from being broken by Django 1.8 --- README.md | 1 + ajax_select/__init__.py | 3 ++- example/example/forms.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cff0249cf2..2d7351e9a9 100644 --- a/README.md +++ b/README.md @@ -405,6 +405,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) diff --git a/ajax_select/__init__.py b/ajax_select/__init__.py index af7c571157..005b574c12 100644 --- a/ajax_select/__init__.py +++ b/ajax_select/__init__.py @@ -92,7 +92,8 @@ class YourModelAdmin(Admin): class TheForm(superclass): class Meta: - fields = '__all__' + exclude = [] + setattr(Meta, 'model', model) if hasattr(superclass, 'Meta'): if hasattr(superclass.Meta, 'fields'): diff --git a/example/example/forms.py b/example/example/forms.py index 8a4db7e945..4e833c7760 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) From 2e68068a7111c69df70815e059dd9045aa4a0f7b Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 16:51:42 +0200 Subject: [PATCH 06/15] update example settings for Django 1.8 --- example/example/settings.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 From e874405f25e8b5dff963a0d61172373b56e20283 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:08:01 +0200 Subject: [PATCH 07/15] remove example and doc for django 1.3 --- README.md | 1 - example/manage-1.3.x.py | 11 ----------- 2 files changed, 12 deletions(-) delete mode 100644 example/manage-1.3.x.py diff --git a/README.md b/README.md index 2d7351e9a9..8ae10617bc 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ 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: 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) From 791b3b6311138015ad5c0e344ea9e2360ce4b3a6 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:09:31 +0200 Subject: [PATCH 08/15] add flake8 setup, fix imports and white space errors --- README.md | 76 +++++++++++++++++++------------------- ajax_select/__init__.py | 8 ++-- ajax_select/fields.py | 40 +++++++++++++------- ajax_select/urls.py | 4 +- example/example/admin.py | 2 +- example/example/forms.py | 2 +- example/example/lookups.py | 16 +++++++- example/example/urls.py | 8 ++-- setup.cfg | 5 +++ setup.py | 3 +- 10 files changed, 98 insertions(+), 66 deletions(-) create mode 100644 setup.cfg diff --git a/README.md b/README.md index 8ae10617bc..c56ee30986 100644 --- a/README.md +++ b/README.md @@ -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 @@ -84,17 +84,17 @@ 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 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: @@ -104,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') @@ -158,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' ) @@ -166,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 @@ -202,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 @@ -228,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 @@ -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/ajax_select/__init__.py b/ajax_select/__init__.py index 005b574c12..fb83aa2df2 100644 --- a/ajax_select/__init__.py +++ b/ajax_select/__init__.py @@ -134,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 @@ -161,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..10c72c3cef 100644 --- a/ajax_select/fields.py +++ b/ajax_select/fields.py @@ -98,8 +98,8 @@ 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): @@ -219,8 +219,10 @@ 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|'] @@ -248,7 +250,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 +259,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 +358,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 +384,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 +397,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 +438,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/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 4e833c7760..cc2d911e2c 100644 --- a/example/example/forms.py +++ b/example/example/forms.py @@ -17,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/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/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..23ca2e696e 100644 --- a/setup.py +++ b/setup.py @@ -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 """ From 16a90676873085f6f2ac9452f3eefbb5dc09afc5 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:19:37 +0200 Subject: [PATCH 09/15] protect against import error warning with django.forms.utils --- ajax_select/fields.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ajax_select/fields.py b/ajax_select/fields.py index 10c72c3cef..7a2be9e441 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 From d9317a183e9610650fdb3c55ab320b6dbc190713 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:20:02 +0200 Subject: [PATCH 10/15] docs: Django versions supported are 1.6+ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c56ee30986..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 From a01c0cdd134038dfdb534d2d042caa973946cf02 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:38:06 +0200 Subject: [PATCH 11/15] better fix for UUID issue reported by @dbinetti in https://github.com/crucialfelix/django-ajax-selects/pull/104 --- ajax_select/fields.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/ajax_select/fields.py b/ajax_select/fields.py index 7a2be9e441..5e18e50bc5 100644 --- a/ajax_select/fields.py +++ b/ajax_select/fields.py @@ -24,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): @@ -105,12 +108,7 @@ def render(self, name, value, attrs=None): 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_ @@ -229,7 +227,7 @@ def render(self, name, value, attrs=None): 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_ From 09a5fc7b215efee5bb0f8972ceb4cf518b94d2db Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:38:24 +0200 Subject: [PATCH 12/15] fix flake8 line too long --- example/example/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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): From e66bb0a8b591961e1e6544fa04843fad61c8a4a7 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:39:21 +0200 Subject: [PATCH 13/15] version bump --- ajax_select/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ajax_select/__init__.py b/ajax_select/__init__.py index fb83aa2df2..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/" diff --git a/setup.py b/setup.py index 23ca2e696e..15a5fcd547 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', From 964306a197508018b999dd20336120114bf768c0 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:39:30 +0200 Subject: [PATCH 14/15] add release notes --- Release-notes.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Release-notes.md 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 From b12a3fed1227fcdfdb2fafc6d23c58785d0e1665 Mon Sep 17 00:00:00 2001 From: Chris Sattinger Date: Mon, 6 Apr 2015 17:41:05 +0200 Subject: [PATCH 15/15] note Django 1.6+ support in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 15a5fcd547..db063c2ac7 100644 --- a/setup.py +++ b/setup.py @@ -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