Skip to content

Commit 0e03828

Browse files
committed
Merge branch 'release/1.6.0'
2 parents 3493acd + 69a8cbb commit 0e03828

File tree

18 files changed

+166
-97
lines changed

18 files changed

+166
-97
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ example/ajax_select
99
example/ajax_selects_example_db
1010
dist
1111
MANIFEST
12+
build

.travis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@ sudo: false
33
env:
44
- TOX_ENV=py27-flake8
55
- TOX_ENV=py34-flake8
6-
- TOX_ENV=py27-dj16
7-
- TOX_ENV=py33-dj16
86
- TOX_ENV=py27-dj17
97
- TOX_ENV=py27-dj18
108
- TOX_ENV=py27-dj19
119
- TOX_ENV=py27-dj110
10+
- TOX_ENV=py27-dj111
1211
- TOX_ENV=py34-dj17
1312
- TOX_ENV=py34-dj18
1413
- TOX_ENV=py34-dj19
1514
- TOX_ENV=py34-dj110
16-
# - TOX_ENV=py35-dj18
17-
# - TOX_ENV=py35-dj19
15+
- TOX_ENV=py34-dj111
1816
install:
1917
- pip install -r requirements-test.txt
2018
script:

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Change Log
22

3+
## [1.6.0](https://github.com/crucialfelix/django-ajax-selects/tree/1.6.0) (2017-05-17)
4+
[Full Changelog](https://github.com/crucialfelix/django-ajax-selects/compare/1.5.2...1.6.0)
5+
6+
Add support for Django 1.11
7+
Drop support for Django 1.6
8+
9+
**Closed issues:**
10+
11+
- LookupChannel.get\_objects fails for inherited models [\#153](https://github.com/crucialfelix/django-ajax-selects/issues/153)
12+
13+
**Merged pull requests:**
14+
15+
- Changed the build\_attrs to work with Django==1.11. [\#202](https://github.com/crucialfelix/django-ajax-selects/pull/202) ([xbello](https://github.com/xbello))
16+
317
## [1.5.2](https://github.com/crucialfelix/django-ajax-selects/tree/1.5.2) (2016-10-19)
418
[Full Changelog](https://github.com/crucialfelix/django-ajax-selects/compare/1.5.1...1.5.2)
519

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ clean-pyc:
2121
find . -name '*~' -exec rm -f {} +
2222

2323
lint:
24-
flake8 .
24+
flake8 ajax_select tests example
2525

2626
test:
2727
tox

ajax_select/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""JQuery-Ajax Autocomplete fields for Django Forms."""
2-
__version__ = "1.5.2"
2+
__version__ = "1.6.0"
33
__author__ = "crucialfelix"
44
__contact__ = "[email protected]"
55
__homepage__ = "https://github.com/crucialfelix/django-ajax-selects/"

ajax_select/fields.py

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
from django import forms
55
from django.conf import settings
66
from django.contrib.contenttypes.models import ContentType
7-
from django.core.urlresolvers import reverse
87
from django.db.models.query import QuerySet
9-
try:
10-
from django.forms.utils import flatatt
11-
except ImportError:
12-
# < django 1.7
13-
from django.forms.util import flatatt
14-
from django.template.loader import render_to_string
8+
from django.forms.utils import flatatt
159
from django.template.defaultfilters import force_escape
10+
from django.template.loader import render_to_string
1611
from django.utils.encoding import force_text
1712
from django.utils.safestring import mark_safe
1813
from django.utils.six import text_type
1914
from django.utils.translation import ugettext as _
15+
try:
16+
from django.urls import reverse
17+
except ImportError:
18+
# < django 1.10
19+
from django.core.urlresolvers import reverse
2020

2121

2222
as_default_help = 'Enter text to search.'
@@ -35,12 +35,14 @@ def _media(self):
3535
return forms.Media(css={'all': ('ajax_select/css/ajax_select.css',)}, js=js)
3636

3737

38-
####################################################################################
38+
###############################################################################
3939

4040

4141
class AutoCompleteSelectWidget(forms.widgets.TextInput):
4242

43-
"""Widget to search for a model and return it as text for use in a CharField."""
43+
"""
44+
Widget to search for a model and return it as text for use in a CharField.
45+
"""
4446

4547
media = property(_media)
4648

@@ -61,7 +63,10 @@ def __init__(self,
6163

6264
def render(self, name, value, attrs=None):
6365
value = value or ''
64-
final_attrs = self.build_attrs(attrs)
66+
67+
final_attrs = self.build_attrs(self.attrs)
68+
final_attrs.update(attrs or {})
69+
final_attrs.pop('required', None)
6570
self.html_id = final_attrs.pop('id', name)
6671

6772
current_repr = ''
@@ -131,7 +136,8 @@ def clean(self, value):
131136
if len(objs) != 1:
132137
# someone else might have deleted it while you were editing
133138
# or your channel is faulty
134-
# out of the scope of this field to do anything more than tell you it doesn't exist
139+
# out of the scope of this field to do anything more than
140+
# tell you it doesn't exist
135141
raise forms.ValidationError("%s cannot find object: %s" % (lookup, value))
136142
return objs[0]
137143
else:
@@ -149,12 +155,14 @@ def has_changed(self, initial, data):
149155
return text_type(initial_value) != text_type(data_value)
150156

151157

152-
####################################################################################
158+
###############################################################################
153159

154160

155161
class AutoCompleteSelectMultipleWidget(forms.widgets.SelectMultiple):
156162

157-
"""Widget to select multiple models for a ManyToMany db field."""
163+
"""
164+
Widget to select multiple models for a ManyToMany db field.
165+
"""
158166

159167
media = property(_media)
160168

@@ -179,7 +187,9 @@ def render(self, name, value, attrs=None):
179187
if value is None:
180188
value = []
181189

182-
final_attrs = self.build_attrs(attrs)
190+
final_attrs = self.build_attrs(self.attrs)
191+
final_attrs.update(attrs or {})
192+
final_attrs.pop('required', None)
183193
self.html_id = final_attrs.pop('id', name)
184194

185195
lookup = registry.get(self.channel)
@@ -229,7 +239,9 @@ def id_for_label(self, id_):
229239

230240
class AutoCompleteSelectMultipleField(forms.fields.CharField):
231241

232-
""" form field to select multiple models for a ManyToMany db field """
242+
"""
243+
Form field to select multiple models for a ManyToMany db field.
244+
"""
233245

234246
channel = None
235247

@@ -245,8 +257,8 @@ def __init__(self, channel, *args, **kwargs):
245257
if isinstance(help_text, str):
246258
help_text = force_text(help_text)
247259
# django admin appends "Hold down "Control",..." to the help text
248-
# regardless of which widget is used. so even when you specify an explicit
249-
# help text it appends this other default text onto the end.
260+
# regardless of which widget is used. so even when you specify an
261+
# explicit help text it appends this other default text onto the end.
250262
# This monkey patches the help text to remove that
251263
if help_text != '':
252264
if not isinstance(help_text, text_type):
@@ -298,14 +310,15 @@ def has_changed(self, initial_value, data_value):
298310
dvs = [text_type(v) for v in (data_value or [])]
299311
return ivs != dvs
300312

301-
####################################################################################
313+
###############################################################################
302314

303315

304316
class AutoCompleteWidget(forms.TextInput):
305317

306318
"""
307-
Widget to select a search result and enter the result as raw text in the text input field.
308-
the user may also simply enter text and ignore any auto complete suggestions.
319+
Widget to select a search result and enter the result as raw text in the
320+
text input field. The user may also simply enter text and ignore any
321+
auto complete suggestions.
309322
"""
310323

311324
media = property(_media)
@@ -325,9 +338,10 @@ def __init__(self, channel, *args, **kwargs):
325338
def render(self, name, value, attrs=None):
326339

327340
initial = value or ''
328-
329-
final_attrs = self.build_attrs(attrs)
341+
final_attrs = self.build_attrs(self.attrs)
342+
final_attrs.update(attrs or {})
330343
self.html_id = final_attrs.pop('id', name)
344+
final_attrs.pop('required', None)
331345

332346
lookup = registry.get(self.channel)
333347
if self.show_help_text:
@@ -352,7 +366,8 @@ def render(self, name, value, attrs=None):
352366

353367
class AutoCompleteField(forms.CharField):
354368
"""
355-
A CharField that uses an AutoCompleteWidget to lookup matching and stores the result as plain text.
369+
A CharField that uses an AutoCompleteWidget to lookup matching
370+
and stores the result as plain text.
356371
"""
357372
channel = None
358373

@@ -375,7 +390,7 @@ def __init__(self, channel, *args, **kwargs):
375390
super(AutoCompleteField, self).__init__(*args, **defaults)
376391

377392

378-
####################################################################################
393+
###############################################################################
379394

380395
def _check_can_add(self, user, related_model):
381396
"""
@@ -402,7 +417,8 @@ def _check_can_add(self, user, related_model):
402417
def autoselect_fields_check_can_add(form, model, user):
403418
"""
404419
Check the form's fields for any autoselect fields and enable their
405-
widgets with green + button if permissions allow then to create the related_model.
420+
widgets with green + button if permissions allow then to create the
421+
related_model.
406422
"""
407423
for name, form_field in form.declared_fields.items():
408424
if isinstance(form_field, (AutoCompleteSelectMultipleField, AutoCompleteSelectField)):

ajax_select/lookup_channel.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class LookupChannel(object):
88
"""
99
Subclass this, setting the model and implementing methods to taste.
1010
11-
Attributes:
11+
Attributes::
12+
1213
model (Model): The Django Model that this lookup channel will search for.
1314
plugin_options (dict): Options passed to jQuery UI plugin that are specific to this channel.
1415
min_length (int): Minimum number of characters user types before a search is initiated.
@@ -29,12 +30,13 @@ def get_query(self, q, request):
2930
"""
3031
Return a QuerySet searching for the query string `q`.
3132
32-
Note that you may return any iterable so you can return a list or even use yield and turn this
33-
method into a generator.
33+
Note that you may return any iterable so you can return a list or even
34+
use yield and turn this method into a generator.
3435
3536
Args:
3637
q (str, unicode): The query string to search for.
37-
request (Request): This can be used to customize the search by User or to use additional GET variables.
38+
request (Request): This can be used to customize the search by User
39+
or to use additional GET variables.
3840
3941
Returns:
4042
(QuerySet, list, generator): iterable of related_models
@@ -43,12 +45,16 @@ def get_query(self, q, request):
4345
return self.model.objects.filter(**kwargs).order_by(self.search_field)
4446

4547
def get_result(self, obj):
46-
"""The text result of autocompleting the entered query.
48+
"""
49+
The text result of autocompleting the entered query.
4750
48-
For a partial string that the user typed in, each matched result is here converted to the fully completed text.
51+
For a partial string that the user typed in, each matched result is
52+
here converted to the fully completed text.
4953
50-
This is currently displayed only for a moment in the text field after the user has selected the item.
51-
Then the item is displayed in the item_display deck and the text field is cleared.
54+
This is currently displayed only for a moment in the text field after
55+
the user has selected the item.
56+
Then the item is displayed in the item_display deck and the text field
57+
is cleared.
5258
5359
Args:
5460
obj (Model):
@@ -58,7 +64,8 @@ def get_result(self, obj):
5864
return escape(force_text(obj))
5965

6066
def format_match(self, obj):
61-
"""(HTML) Format item for displaying in the dropdown.
67+
"""
68+
(HTML) Format item for displaying in the dropdown.
6269
6370
Args:
6471
obj (Model):
@@ -68,7 +75,8 @@ def format_match(self, obj):
6875
return escape(force_text(obj))
6976

7077
def format_item_display(self, obj):
71-
""" (HTML) format item for displaying item in the selected deck area.
78+
"""
79+
(HTML) format item for displaying item in the selected deck area.
7280
7381
Args:
7482
obj (Model):
@@ -78,29 +86,28 @@ def format_item_display(self, obj):
7886
return escape(force_text(obj))
7987

8088
def get_objects(self, ids):
81-
"""This is used to retrieve the currently selected objects for either ManyToMany or ForeignKey.
82-
83-
Note that the order of the ids supplied for ManyToMany fields is dependent on how the
84-
objects manager fetches it.
85-
ie. what is returned by `YourModel.{fieldname}_set.all()`
86-
87-
In most situations (especially postgres) this order is indeterminate -- not the order that you originally
88-
added them in the interface.
89-
See :doc:`/Ordered-ManyToMany` for a solution to this.
89+
"""
90+
This is used to retrieve the currently selected objects for either ManyToMany or ForeignKey.
9091
9192
Args:
9293
ids (list): list of primary keys
9394
Returns:
9495
list: list of Model objects
9596
"""
96-
# return objects in the same order as passed in here
97-
pk_type = self.model._meta.pk.to_python
97+
if self.model._meta.pk.rel is not None:
98+
# Use the type of the field being referenced
99+
pk_type = self.model._meta.pk.target_field.to_python
100+
else:
101+
pk_type = self.model._meta.pk.to_python
102+
103+
# Return objects in the same order as passed in here
98104
ids = [pk_type(pk) for pk in ids]
99105
things = self.model.objects.in_bulk(ids)
100106
return [things[aid] for aid in ids if aid in things]
101107

102108
def can_add(self, user, other_model):
103-
"""Check if the user has permission to add a ForeignKey or M2M model.
109+
"""
110+
Check if the user has permission to add a ForeignKey or M2M model.
104111
105112
This enables the green popup + on the widget.
106113
Default implentation is the standard django permission check.
@@ -116,14 +123,15 @@ def can_add(self, user, other_model):
116123
return user.has_perm("%s.add_%s" % (ctype.app_label, ctype.model))
117124

118125
def check_auth(self, request):
119-
"""By default only request.user.is_staff have access.
126+
"""
127+
By default only request.user.is_staff have access.
120128
121129
This ensures that nobody can get your data by simply knowing the lookup URL.
122130
123131
This is called from the ajax_lookup view.
124132
125-
Public facing forms (outside of the Admin) should implement this to allow
126-
non-staff to use this LookupChannel.
133+
Public facing forms (outside of the Admin) should implement this to
134+
allow non-staff to use this LookupChannel.
127135
128136
Args:
129137
request (Request)

docs/source/Release-notes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ Release Notes
33

44
See also CHANGELOG.md for github issues and bugfixes
55

6+
1.6.0
7+
=====
8+
9+
- Added Support for Django 1.11
10+
- Dropped Django 1.6
11+
612
1.5.0
713
=====
814

example/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ cleandb:
1919
help:
2020
@echo make install
2121
@echo or:
22-
@echo make clean install DJANGO=1.4.2
22+
@echo make clean install DJANGO=1.11
2323

2424

2525
.PHONY: install clean cleandb help

0 commit comments

Comments
 (0)