Codebase list django-ajax-selects / 03521dd
New upstream version 1.7.0 Brian May 6 years ago
21 changed file(s) with 174 addition(s) and 143 deletion(s). Raw diff Collapse all Expand all
00 # Change Log
1
2 ## [1.6.1](https://github.com/crucialfelix/django-ajax-selects/tree/1.6.1) (2017-09-09)
3 [Full Changelog](https://github.com/crucialfelix/django-ajax-selects/compare/1.6.0...1.6.1)
4
5 **Fixed bugs:**
6
7 - Fix Lookup.get\_objects for inherited models [\#212](https://github.com/crucialfelix/django-ajax-selects/pull/212) ([crucialfelix](https://github.com/crucialfelix))
8
9 **Merged pull requests:**
10
11 - Use DjangoJSONEncoder - which handles additional django types [\#206](https://github.com/crucialfelix/django-ajax-selects/pull/206) ([mzdeb](https://github.com/mzdeb))
112
213 ## [1.6.0](https://github.com/crucialfelix/django-ajax-selects/tree/1.6.0) (2017-05-17)
314 [Full Changelog](https://github.com/crucialfelix/django-ajax-selects/compare/1.5.2...1.6.0)
00 Metadata-Version: 1.1
11 Name: django-ajax-selects
2 Version: 1.6.0
2 Version: 1.7.0
33 Summary: Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete.
44 Home-page: https://github.com/crucialfelix/django-ajax-selects/
55 Author: Chris Sattinger
1616 - Integrate with other UI elements elsewhere on the page using the javascript API
1717 - Works in Admin as well as in normal views
1818
19 - Django >=1.7, <=2
19 - Django >=1.8, <=2.1
2020 - Python >=2.7, <=3.7
2121
2222 Platform: UNKNOWN
0
1 Edit `ForeignKey`, `ManyToManyField` and `CharField` in Django Admin using jQuery UI AutoComplete.
0 # Edit `ForeignKey`, `ManyToManyField` and `CharField` in Django Admin using jQuery UI AutoComplete.
21
32 [![Build Status](https://travis-ci.org/crucialfelix/django-ajax-selects.svg?branch=master)](https://travis-ci.org/crucialfelix/django-ajax-selects) [![PyPI version](https://badge.fury.io/py/django-ajax-selects.svg)](https://badge.fury.io/py/django-ajax-selects)
43
87
98 ![selected](/docs/source/_static/kiss-all.png?raw=true)
109
11
12 Documentation
13 ------------------
10 ## Documentation
1411
1512 http://django-ajax-selects.readthedocs.org/en/latest/
1613
17
18
19 Quick Usage
20 -----------
14 ## Quick Usage
2115
2216 Define a lookup channel:
2317
4236
4337 ```python
4438 # yourapp/forms.py
39 from ajax_select.fields import AutoCompleteSelectMultipleField
40
4541 class DocumentForm(ModelForm):
4642
4743 class Meta:
5046 tags = AutoCompleteSelectMultipleField('tags')
5147 ```
5248
49 ## Fully customizable
5350
51 * Customize search query
52 * Query other resources besides Django ORM
53 * Format results with HTML
54 * Customize styling
55 * Customize security policy
56 * Add additional custom UI alongside widget
57 * Integrate with other UI elements elsewhere on the page using the javascript API
58 * Works in Admin as well as in normal views
5459
55 Fully customizable
56 ------------------
60 ## Assets included by default
5761
58 - Customize search query
59 - Query other resources besides Django ORM
60 - Format results with HTML
61 - Customize styling
62 - Customize security policy
63 - Add additional custom UI alongside widget
64 - Integrate with other UI elements elsewhere on the page using the javascript API
65 - Works in Admin as well as in normal views
62 * //ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
63 * //code.jquery.com/ui/1.10.3/jquery-ui.js
64 * //code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css
6665
66 ## Compatibility
6767
68 Assets included by default
69 -------------------
68 * Django >=1.8, <=2.1
69 * Python >=2.7, 3.3+
7070
71 - //ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
72 - //code.jquery.com/ui/1.10.3/jquery-ui.js
73 - //code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css
74
75 Compatibility
76 -------------
77
78 - Django >=1.6, <=1.10
79 - Python >=2.7, 3.3-3.5
80
81
82 Contributors
83 ------------
71 ## Contributors
8472
8573 Many thanks to all contributors and pull requesters !
8674
8775 https://github.com/crucialfelix/django-ajax-selects/graphs/contributors
8876
89
90 License
91 -------
77 ## License
9278
9379 Dual licensed under the MIT and GPL licenses:
94 - http://www.opensource.org/licenses/mit-license.php
95 - http://www.gnu.org/licenses/gpl.html
80
81 * http://www.opensource.org/licenses/mit-license.php
82 * http://www.gnu.org/licenses/gpl.html
00 """JQuery-Ajax Autocomplete fields for Django Forms."""
1 __version__ = "1.6.0"
1 __version__ = "1.7.0"
22 __author__ = "crucialfelix"
33 __contact__ = "crucialfelix@gmail.com"
44 __homepage__ = "https://github.com/crucialfelix/django-ajax-selects/"
77 from ajax_select.helpers import make_ajax_form, make_ajax_field # noqa
88 from ajax_select.lookup_channel import LookupChannel # noqa
99
10 try:
11 # django 1.7+ will use the new AppConfig api
12 # It will load all your lookups.py modules
13 # and any specified in settings.AJAX_LOOKUP_CHANNELS
14 # It will do this after all apps are imported.
15 from django.apps import AppConfig # noqa
16 default_app_config = 'ajax_select.apps.AjaxSelectConfig'
17 except ImportError:
18 # Previous django versions should load now
19 # using settings.AJAX_LOOKUP_CHANNELS
20 registry.load_channels()
10 # django 1.7+ will use the new AppConfig api
11 # It will load all your lookups.py modules
12 # and any specified in settings.AJAX_LOOKUP_CHANNELS
13 # It will do this after all apps are imported.
14 from django.apps import AppConfig # noqa
15 default_app_config = 'ajax_select.apps.AjaxSelectConfig'
44
55 """
66 Django 1.7+ enables initializing installed applications
7 and autodiscovering modules
7 and autodiscovering modules.
88
99 On startup, search for and import any modules called `lookups.py` in all installed apps.
1010 Your LookupClass subclass may register itself.
00 from __future__ import unicode_literals
1
12 import json
2 from ajax_select.registry import registry
3
34 from django import forms
45 from django.conf import settings
56 from django.contrib.contenttypes.models import ContentType
6 from django.db.models.query import QuerySet
7 from django.db.models import Model
78 from django.forms.utils import flatatt
89 from django.template.defaultfilters import force_escape
910 from django.template.loader import render_to_string
1112 from django.utils.safestring import mark_safe
1213 from django.utils.six import text_type
1314 from django.utils.translation import ugettext as _
15 from django.utils.module_loading import import_string
16
17 from ajax_select.registry import registry
18
1419 try:
1520 from django.urls import reverse
1621 except ImportError:
3439 return forms.Media(css={'all': ('ajax_select/css/ajax_select.css',)}, js=js)
3540
3641
42 json_encoder = import_string(getattr(settings, 'AJAX_SELECT_JSON_ENCODER',
43 'django.core.serializers.json.DjangoJSONEncoder'))
44
45
3746 ###############################################################################
3847
3948
95104 'func_slug': self.html_id.replace("-", ""),
96105 'add_link': self.add_link,
97106 }
98 context.update(make_plugin_options(lookup, self.channel, self.plugin_options, initial))
107 context.update(
108 make_plugin_options(lookup, self.channel, self.plugin_options, initial))
99109 templates = (
100110 'ajax_select/autocompleteselect_%s.html' % self.channel,
101111 'ajax_select/autocompleteselect.html')
126136 )
127137 widget_kwargs.update(kwargs.pop('widget_options', {}))
128138 kwargs["widget"] = AutoCompleteSelectWidget(**widget_kwargs)
129 super(AutoCompleteSelectField, self).__init__(max_length=255, *args, **kwargs)
139 super(AutoCompleteSelectField, self).__init__(
140 max_length=255, *args, **kwargs)
130141
131142 def clean(self, value):
132143 if value:
137148 # or your channel is faulty
138149 # out of the scope of this field to do anything more than
139150 # tell you it doesn't exist
140 raise forms.ValidationError("%s cannot find object: %s" % (lookup, value))
151 raise forms.ValidationError(
152 "%s cannot find object: %s" % (lookup, value))
141153 return objs[0]
142154 else:
143155 if self.required:
193205
194206 lookup = registry.get(self.channel)
195207
196 if isinstance(value, QuerySet):
197 objects = value
198 else:
199 objects = lookup.get_objects(value)
208 values = list(value)
209 if all([isinstance(v, Model) for v in values]):
210 objects = values
211 else:
212 objects = lookup.get_objects(values)
200213
201214 current_ids = pack_ids([obj.pk for obj in objects])
202215
216229 'html_id': self.html_id,
217230 'current': value,
218231 'current_ids': current_ids,
219 'current_reprs': mark_safe(json.dumps(initial)),
232 'current_reprs': mark_safe(
233 json.dumps(initial, cls=json_encoder)
234 ),
220235 'help_text': help_text,
221236 'extra_attrs': mark_safe(flatatt(final_attrs)),
222237 'func_slug': self.html_id.replace("-", ""),
223238 'add_link': self.add_link,
224239 }
225 context.update(make_plugin_options(lookup, self.channel, self.plugin_options, initial))
240 context.update(
241 make_plugin_options(lookup, self.channel, self.plugin_options, initial))
226242 templates = ('ajax_select/autocompleteselectmultiple_%s.html' % self.channel,
227 'ajax_select/autocompleteselectmultiple.html')
243 'ajax_select/autocompleteselectmultiple.html')
228244 out = render_to_string(templates, context)
229245 return mark_safe(out)
230246
268284 dh = 'Hold down "Control", or "Command" on a Mac, to select more than one.'
269285 django_default_help = _(dh).translate(settings.LANGUAGE_CODE)
270286 if django_default_help in translated:
271 cleaned_help = translated.replace(django_default_help, '').strip()
287 cleaned_help = translated.replace(
288 django_default_help, '').strip()
272289 # probably will not show up in translations
273290 if cleaned_help:
274291 help_text = cleaned_help
357374 'extra_attrs': mark_safe(flatatt(final_attrs)),
358375 'func_slug': self.html_id.replace("-", ""),
359376 }
360 context.update(make_plugin_options(lookup, self.channel, self.plugin_options, initial))
377 context.update(
378 make_plugin_options(lookup, self.channel, self.plugin_options, initial))
361379 templates = ('ajax_select/autocomplete_%s.html' % self.channel,
362380 'ajax_select/autocomplete.html')
363381 return mark_safe(render_to_string(templates, context))
364382
365383
366384 class AutoCompleteField(forms.CharField):
385
367386 """
368387 A CharField that uses an AutoCompleteWidget to lookup matching
369388 and stores the result as plain text.
410429 if can_add:
411430 app_label = related_model._meta.app_label
412431 model = related_model._meta.object_name.lower()
413 self.widget.add_link = reverse('admin:%s_%s_add' % (app_label, model)) + '?_popup=1'
432 self.widget.add_link = reverse(
433 'admin:%s_%s_add' % (app_label, model)) + '?_popup=1'
414434
415435
416436 def autoselect_fields_check_can_add(form, model, user):
422442 for name, form_field in form.declared_fields.items():
423443 if isinstance(form_field, (AutoCompleteSelectMultipleField, AutoCompleteSelectField)):
424444 db_field = model._meta.get_field(name)
425 form_field.check_can_add(user, db_field.rel.to)
445 if hasattr(db_field, "remote_field"):
446 form_field.check_can_add(user, db_field.remote_field.model)
447 else:
448 form_field.check_can_add(user, db_field.rel.to)
426449
427450
428451 def make_plugin_options(lookup, channel_name, widget_plugin_options, initial):
440463 po['html'] = True
441464
442465 return {
443 'plugin_options': mark_safe(json.dumps(po)),
444 'data_plugin_options': force_escape(json.dumps(po))
466 'plugin_options': mark_safe(json.dumps(po, cls=json_encoder)),
467 'data_plugin_options': force_escape(
468 json.dumps(po, cls=json_encoder)
469 )
445470 }
446471
447472
00 from django.db.models.fields.related import ForeignKey, ManyToManyField
11 from django.forms.models import ModelForm
2 from django.utils.encoding import force_text
23 from django.utils.text import capfirst
3 from django.utils.encoding import force_text
44 from django.utils.translation import ugettext_lazy as _
55
66
8383 (AutoCompleteField, AutoCompleteSelectField, AutoCompleteSelectMultipleField): field
8484 """
8585 from ajax_select.fields import AutoCompleteField, \
86 AutoCompleteSelectMultipleField, \
87 AutoCompleteSelectField
86 AutoCompleteSelectMultipleField, \
87 AutoCompleteSelectField
8888
8989 field = related_model._meta.get_field(fieldname_on_model)
9090 if 'label' not in kwargs:
9393 Returns:
9494 list: list of Model objects
9595 """
96 if self.model._meta.pk.rel is not None:
96 # Inherited models have a OneToOneField (rather than eg AutoField)
97 if getattr(self.model._meta.pk, "remote_field", False):
98 # Use the type of the field being referenced (2.0+)
99 pk_type = self.model._meta.pk.remote_field.field.to_python
100 elif getattr(self.model._meta.pk, "rel", False):
97101 # Use the type of the field being referenced
98 pk_type = self.model._meta.pk.target_field.to_python
102 pk_type = self.model._meta.pk.rel.field.to_python
99103 else:
100104 pk_type = self.model._meta.pk.to_python
101105
+0
-5
ajax_select/models.py less more
0 """
1 Blank file so that Django recognizes the app.
2
3 This is only required for Django < 1.7
4 """
0 from django.apps import apps
1 from django.conf import settings
02 from django.core.exceptions import ImproperlyConfigured
1 from django.conf import settings
3 from django.utils.module_loading import autodiscover_modules
24
35
46 class LookupChannelRegistry(object):
1618 Called when loading the application. Cannot be called a second time,
1719 (eg. for testing) as Django will not re-import and re-register anything.
1820 """
19 self._registry = {}
20 try:
21 from django.utils.module_loading import autodiscover_modules
22 except ImportError:
23 pass
24 else:
25 autodiscover_modules('lookups')
21 autodiscover_modules('lookups')
2622
2723 if hasattr(settings, 'AJAX_LOOKUP_CHANNELS'):
2824 self.register(settings.AJAX_LOOKUP_CHANNELS)
111107
112108 def get_model(app_label, model_name):
113109 """Loads the model given an 'app_label' 'ModelName'"""
114 try:
115 # django >= 1.7
116 from django.apps import apps
117 except ImportError:
118 # django < 1.7
119 from django.db import models
120 return models.get_model(app_label, model_name)
121 else:
122 return apps.get_model(app_label, model_name)
123
124
125 def can_autodiscover():
126 try:
127 from django.apps import AppConfig # noqa
128 except ImportError:
129 return False
130 return True
110 return apps.get_model(app_label, model_name)
131111
132112
133113 def register(channel):
00 Metadata-Version: 1.1
11 Name: django-ajax-selects
2 Version: 1.6.0
2 Version: 1.7.0
33 Summary: Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete.
44 Home-page: https://github.com/crucialfelix/django-ajax-selects/
55 Author: Chris Sattinger
1616 - Integrate with other UI elements elsewhere on the page using the javascript API
1717 - Works in Admin as well as in normal views
1818
19 - Django >=1.7, <=2
19 - Django >=1.8, <=2.1
2020 - Python >=2.7, <=3.7
2121
2222 Platform: UNKNOWN
1212 ajax_select/fields.py
1313 ajax_select/helpers.py
1414 ajax_select/lookup_channel.py
15 ajax_select/models.py
1615 ajax_select/registry.py
1716 ajax_select/urls.py
1817 ajax_select/views.py
0 django>=1.7, <2
0 django>=1.8, <2.1
11 wheel==0.29.0
00 #!/usr/bin/env python
11
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
2 from setuptools import setup
83
94 setup(
105 name='django-ajax-selects',
11 version='1.6.0',
6 version='1.7.0',
127 description='Edit ForeignKey, ManyToManyField and CharField in Django Admin using jQuery UI AutoComplete.',
138 author='Chris Sattinger',
149 author_email='crucialfelix@gmail.com',
1510 url='https://github.com/crucialfelix/django-ajax-selects/',
1611 packages=['ajax_select'],
17 package_data={'ajax_select':
12 package_data={
13 'ajax_select':
1814 [
1915 '*.py',
2016 '*.txt',
5349 - Integrate with other UI elements elsewhere on the page using the javascript API
5450 - Works in Admin as well as in normal views
5551
56 - Django >=1.7, <=2
52 - Django >=1.8, <=2.1
5753 - Python >=2.7, <=3.7
5854 """
5955 )
22
33 Should not be used by other tests.
44 """
5 from django.contrib.auth.models import User
56 from django.utils.html import escape
6 from django.contrib.auth.models import User
7 from tests.models import Person, Author
7
88 import ajax_select
9
10 from tests.models import Author, Person, PersonWithTitle
911
1012
1113 @ajax_select.register('person')
2426
2527 def format_item_display(self, obj):
2628 return "%s<div><i>%s</i></div>" % (escape(obj.name), escape(obj.email))
29
30
31 @ajax_select.register('person-with-title')
32 class PersonWithTitleLookup(ajax_select.LookupChannel):
33
34 model = PersonWithTitle
35
36 def get_query(self, q, request):
37 return self.model.objects.filter(title__icontains=q)
38
39 def get_result(self, obj):
40 return "{} {}".format(obj.name, obj.title)
2741
2842
2943 @ajax_select.register('user')
55
66 name = models.CharField(max_length=50)
77 email = models.EmailField(null=True, blank=True)
8
9 class Meta:
10 app_label = 'tests'
11
12
13 class PersonWithTitle(Person):
14
15 """
16 Testing an inherited model (multi-table)
17 """
18
19 title = models.CharField(max_length=150)
820
921 class Meta:
1022 app_label = 'tests'
2234
2335 """ Book has no admin, its an inline in the Author admin"""
2436
25 author = models.ForeignKey(Author, null=True)
37 author = models.ForeignKey(Author, null=True, on_delete=models.CASCADE)
2638 name = models.CharField(max_length=50)
2739 mentions_persons = models.ManyToManyField(Person, help_text="MENTIONS PERSONS HELP TEXT")
2840
1717 "tests"
1818 ]
1919 SITE_ID = 1
20 MIDDLEWARE_CLASSES = (
20 MIDDLEWARE = MIDDLEWARE_CLASSES = (
2121 'django.contrib.sessions.middleware.SessionMiddleware',
2222 'django.contrib.auth.middleware.AuthenticationMiddleware',
2323 'django.contrib.messages.middleware.MessageMiddleware'
88 import django
99 from django.forms.models import ModelForm
1010 from django.test import TestCase, Client
11 from django.core.urlresolvers import reverse
1211 from django.contrib.auth.models import User
1312
1413 from tests.models import Book, Author, Person
1514 from ajax_select import fields
15 try:
16 from django.urls import reverse
17 except ImportError:
18 from django.core.urlresolvers import reverse
1619
1720 # Other versions will autoload
1821 if django.VERSION[1] < 7:
4750 def _make_instance(self):
4851 author = Author.objects.create(name="author")
4952 book = Book.objects.create(name="book", author=author)
50 book.mentions_persons = [Person.objects.create(name='person')]
53 book.mentions_persons.add(Person.objects.create(name='person'))
5154 return book
5255
5356 def _book_data(self, book):
00
1 from django.contrib.auth.models import User
12 from django.test import TestCase
2 from django.contrib.auth.models import User
3 from .lookups import UserLookup
3
4 from tests.lookups import PersonWithTitleLookup, UserLookup
5 from tests.models import PersonWithTitle
46
57
68 class TestLookups(TestCase):
79
810 def test_get_objects(self):
911 user1 = User.objects.create(username='user1',
10 email='user1@example.com',
11 password='password')
12 email='user1@example.com',
13 password='password')
1214 user2 = User.objects.create(username='user2',
13 email='user2@example.com',
14 password='password')
15 email='user2@example.com',
16 password='password')
1517 lookup = UserLookup()
18 # deliberately asking for them backwards:
1619 users = lookup.get_objects([user2.id, user1.id])
1720 self.assertEqual(len(users), 2)
21 # to make sure they come back in the order requested
1822 u2, u1 = users
1923 self.assertEqual(u1, user1)
2024 self.assertEqual(u2, user2)
25
26 def test_get_objects_inherited_model(self):
27 """
28 Tests that get_objects works with inherited models
29 """
30 one = PersonWithTitle.objects.create(name='one', title='The One')
31 two = PersonWithTitle.objects.create(name='two', title='The Other')
32 lookup = PersonWithTitleLookup()
33 users = lookup.get_objects([one.id, two.id])
34 self.assertEqual(len(users), 2)
35 u1, u2 = users
36 self.assertEqual(u1, one)
37 self.assertEqual(u2, two)
00
11 from django.test import TestCase
22 import ajax_select
3 from ajax_select.registry import can_autodiscover
43
54
65 class TestRegistry(TestCase):
87 def test_lookup_py_is_autoloaded(self):
98 """Django >= 1.7 autoloads tests/lookups.py"""
109 is_registered = ajax_select.registry.is_registered('person')
11 if can_autodiscover():
12 self.assertTrue(is_registered)
13 else:
14 # person is not in settings and this django will not autoload lookups.py
15 # self.assertFalse(is_registered)
16 # test_integration is more important and requires that lookup.py be loaded
17 # Will drop support for 1.6 soon anyway and we know that it does work
18 pass
10 self.assertTrue(is_registered)
1911
2012 def test_back_compatible_loads_by_settings(self):
2113 """a module and class specified in settings"""
2820 def test_unsetting_a_channel(self):
2921 """settings can unset a channel that was specified in a lookups.py"""
3022 # self.assertFalse(ajax_select.registry.is_registered('user'))
31 self.assertFalse(ajax_select.registry.is_registered('was-never-a-channel'))
23 self.assertFalse(
24 ajax_select.registry.is_registered('was-never-a-channel'))
3225
3326 # def test_reimporting_lookup(self):
3427 # """
99
1010 urlpatterns = [
1111 url(r'^ajax_lookups/', include(ajax_select_urls)),
12 url(r'^admin/', include(admin.site.urls)),
12 url(r'^admin/', admin.site.urls),
1313 ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)