Karen Tracey
Manuel Alvarez
Ustun Ozgur
+@leo-the-manic
+Calvin Spealman
+Rebecca Lovewell
+Thomas Güttler
+Yuri Khrustalev
+@SaeX
Thanks for all of your work!
-Copyright (c) 2010-2013, Mark Lavin
+Copyright (c) 2010-2014, Mark Lavin
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
--- /dev/null
+Metadata-Version: 1.1
+Name: django-selectable
+Version: 0.9.0
+Summary: Auto-complete selection widgets using Django and jQuery UI.
+Home-page: https://github.com/mlavin/django-selectable
+Author: Mark Lavin
+Author-email: markdlavin@gmail.com
+License: BSD
+Description: django-selectable
+ ===================
+
+ Tools and widgets for using/creating auto-complete selection widgets using Django and jQuery UI.
+
+ .. image:: https://travis-ci.org/mlavin/django-selectable.svg?branch=master
+ :target: https://travis-ci.org/mlavin/django-selectable
+
+
+ Features
+ -----------------------------------
+
+ - Works with the latest jQuery UI Autocomplete library
+ - Auto-discovery/registration pattern for defining lookups
+
+
+ Installation Requirements
+ -----------------------------------
+
+ - Python 2.6-2.7, 3.2+
+ - `Django <http://www.djangoproject.com/>`_ >= 1.5
+ - `jQuery <http://jquery.com/>`_ >= 1.7
+ - `jQuery UI <http://jqueryui.com/>`_ >= 1.8
+
+ To install::
+
+ pip install django-selectable
+
+ Next add `selectable` to your `INSTALLED_APPS` to include the related css/js::
+
+ INSTALLED_APPS = (
+ 'contrib.staticfiles',
+ # Other apps here
+ 'selectable',
+ )
+
+ The jQuery and jQuery UI libraries are not included in the distribution but must be included
+ in your templates. See the example project for an example using these libraries from the
+ Google CDN.
+
+ Once installed you should add the urls to your root url patterns::
+
+ urlpatterns = patterns('',
+ # Other patterns go here
+ (r'^selectable/', include('selectable.urls')),
+ )
+
+
+ Documentation
+ -----------------------------------
+
+ Documentation for django-selectable is available on `Read The Docs <http://readthedocs.org/docs/django-selectable>`_.
+
+
+ Additional Help/Support
+ -----------------------------------
+
+ You can find additional help or support on the mailing list: http://groups.google.com/group/django-selectable
+
+
+ Contributing
+ --------------------------------------
+
+ If you think you've found a bug or are interested in contributing to this project
+ check out our `contributing guide <http://readthedocs.org/docs/django-selectable/en/latest/contribute.html>`_.
+
+ If you are interested in translating django-selectable into your native language
+ you can join the `Transifex project <https://www.transifex.com/projects/p/django-selectable/>`_.
+
+
+Platform: UNKNOWN
+Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Framework :: Django
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Operating System :: OS Independent
Tools and widgets for using/creating auto-complete selection widgets using Django and jQuery UI.
+.. image:: https://travis-ci.org/mlavin/django-selectable.svg?branch=master
+ :target: https://travis-ci.org/mlavin/django-selectable
+
+
Features
-----------------------------------
Installation Requirements
-----------------------------------
-- Python 2.6 or Python 2.7
-- `Django <http://www.djangoproject.com/>`_ >= 1.3
-- `jQuery <http://jquery.com/>`_ >= 1.4.4
+- Python 2.6-2.7, 3.2+
+- `Django <http://www.djangoproject.com/>`_ >= 1.5
+- `jQuery <http://jquery.com/>`_ >= 1.7
- `jQuery UI <http://jqueryui.com/>`_ >= 1.8
-.. note::
-
- Begining with version django-selectable version 0.6, Django 1.2 is no longer supported.
- While it may continue to work, bugs related to Django 1.2 support will not be fixed.
-
- Version 0.7 adds experimental support for Python 3.2+ when used with Django 1.5+.
-
To install::
pip install django-selectable
"Auto-complete selection widgets using Django and jQuery UI."
-__version__ = '0.7.0'
\ No newline at end of file
+__version__ = '0.9.0'
+
+default_app_config = 'selectable.apps.SelectableConfig'
--- /dev/null
+try:
+ from django.apps import AppConfig
+except ImportError:
+ AppConfig = object
+
+
+class SelectableConfig(AppConfig):
+ """App configuration for django-selectable."""
+
+ name = 'selectable'
+
+ def ready(self):
+ from . import registry
+ registry.autodiscover()
return qs
def get_queryset(self):
- qs = self.model._default_manager.get_query_set()
+ try:
+ qs = self.model._default_manager.get_queryset()
+ except AttributeError: # Django <= 1.5.
+ qs = self.model._default_manager.get_query_set()
if self.filters:
qs = qs.filter(**self.filters)
return qs
from django.utils.encoding import smart_unicode as smart_text
from django.utils.encoding import force_unicode as force_text
+try:
+ from django.forms.utils import flatatt
+except ImportError:
+ from django.forms.util import flatatt
+
PY3 = sys.version_info[0] == 3
if PY3:
string_types = str,
else:
- string_types = basestring,
\ No newline at end of file
+ string_types = basestring,
+
+try:
+ from importlib import import_module
+except ImportError:
+ from django.utils.importlib import import_module
+
+try:
+ from django.apps import AppConfig
+ LEGACY_AUTO_DISCOVER = False
+except ImportError:
+ LEGACY_AUTO_DISCOVER = True
from django import forms
from django.conf import settings
-from django.utils.importlib import import_module
-from selectable.compat import string_types
+from selectable.compat import string_types, import_module
__all__ = (
from django.core.exceptions import ValidationError
from django.core.validators import EMPTY_VALUES
from django.utils.translation import ugettext_lazy as _
+from django.db.models import Model
from selectable.forms.base import import_lookup_class
from selectable.forms.widgets import AutoCompleteSelectWidget
)
-class AutoCompleteSelectField(forms.Field):
+def model_vars(obj):
+ fields = dict(
+ (field.name, getattr(obj, field.name))
+ for field in obj._meta.fields
+ )
+ return fields
+
+
+class BaseAutoCompleteField(forms.Field):
+
+ def _has_changed(self, initial, data):
+ "Detects if the data was changed. This is added in 1.6."
+ if initial is None and data is None:
+ return False
+ if data and not hasattr(data, '__iter__'):
+ data = self.widget.decompress(data)
+ initial = self.to_python(initial)
+ data = self.to_python(data)
+ if hasattr(self, '_coerce'):
+ data = self._coerce(data)
+ if isinstance(data, Model) and isinstance(initial, Model):
+ return model_vars(data) != model_vars(initial)
+ else:
+ return data != initial
+
+class AutoCompleteSelectField(BaseAutoCompleteField):
widget = AutoCompleteSelectWidget
default_error_messages = {
super(AutoCompleteSelectField, self).__init__(*args, **kwargs)
+
def to_python(self, value):
if value in EMPTY_VALUES:
return None
return value
-class AutoCompleteSelectMultipleField(forms.Field):
+class AutoCompleteSelectMultipleField(BaseAutoCompleteField):
widget = AutoCompleteSelectMultipleWidget
default_error_messages = {
import json
-from django import forms
+from django import forms, VERSION as DJANGO_VERSION
from django.conf import settings
-from django.forms.util import flatatt
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from selectable import __version__
-from selectable.compat import force_text
+from selectable.compat import force_text, flatatt
from selectable.forms.base import import_lookup_class
__all__ = (
}
js = ('%sjs/jquery.dj.selectable.js?v=%s' % (STATIC_PREFIX, __version__),)
+
class AutoCompleteWidget(forms.TextInput, SelectableMediaMixin):
def __init__(self, lookup_class, *args, **kwargs):
def update_query_parameters(self, qs_dict):
self.widgets[0].update_query_parameters(qs_dict)
- def _has_changed(self, initial, data):
- "Decects if the widget was changed. This is removed in 1.6."
- if initial is None and data is None:
- return False
- if data and not hasattr(data, '__iter__'):
- data = self.decompress(data)
- return super(SelectableMultiWidget, self)._has_changed(initial, data)
+ if DJANGO_VERSION < (1, 6):
+ def _has_changed(self, initial, data):
+ "Detects if the widget was changed. This is removed in Django 1.6."
+ if initial is None and data is None:
+ return False
+ if data and not hasattr(data, '__iter__'):
+ data = self.decompress(data)
+ return super(SelectableMultiWidget, self)._has_changed(initial, data)
def decompress(self, value):
if value:
return [item_value, value]
return [None, None]
+ def get_compatible_postdata(self, data, name):
+ """Get postdata built for a normal <select> element.
+
+ Django MultiWidgets create post variables like ``foo_0`` and ``foo_1``,
+ and this behavior is not cleanly overridable. Non-multiwidgets, like
+ Select, get simple names like ``foo``. In order to keep this widget
+ compatible with requests designed for traditional select widgets,
+ search postdata for a name like ``foo`` and return that value.
+
+ This will return ``None`` if a ``<select>``-compatibile post variable
+ is not found.
+ """
+ return data.get(name, None)
-class AutoCompleteSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+
+class _BaseSingleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+ """
+ Common base class for AutoCompleteSelectWidget and AutoComboboxSelectWidget
+ each which use one widget (primary_widget) to select text and a single
+ hidden input to hold the selected id.
+ """
+
+ primary_widget = None
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = import_lookup_class(lookup_class)
self.limit = kwargs.pop('limit', None)
query_params = kwargs.pop('query_params', {})
widgets = [
- AutoCompleteWidget(
+ self.primary_widget(
lookup_class, allow_new=self.allow_new,
limit=self.limit, query_params=query_params,
attrs=kwargs.get('attrs'),
),
forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
]
- super(AutoCompleteSelectWidget, self).__init__(widgets, *args, **kwargs)
+ super(_BaseSingleSelectWidget, self).__init__(widgets, *args, **kwargs)
def value_from_datadict(self, data, files, name):
- value = super(AutoCompleteSelectWidget, self).value_from_datadict(data, files, name)
+ value = super(_BaseSingleSelectWidget, self).value_from_datadict(data, files, name)
+ if not value[1]:
+ compatible_postdata = self.get_compatible_postdata(data, name)
+ if compatible_postdata:
+ value[1] = compatible_postdata
if not self.allow_new:
return value[1]
return value
+class AutoCompleteSelectWidget(_BaseSingleSelectWidget):
+
+ primary_widget = AutoCompleteWidget
+
+
class AutoComboboxWidget(AutoCompleteWidget, SelectableMediaMixin):
def build_attrs(self, extra_attrs=None, **kwargs):
return attrs
-class AutoComboboxSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+class AutoComboboxSelectWidget(_BaseSingleSelectWidget):
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.allow_new = kwargs.pop('allow_new', False)
- self.limit = kwargs.pop('limit', None)
- query_params = kwargs.pop('query_params', {})
- widgets = [
- AutoComboboxWidget(
- lookup_class, allow_new=self.allow_new,
- limit=self.limit, query_params=query_params,
- attrs=kwargs.get('attrs'),
- ),
- forms.HiddenInput(attrs={'data-selectable-type': 'hidden'})
- ]
- super(AutoComboboxSelectWidget, self).__init__(widgets, *args, **kwargs)
-
- def value_from_datadict(self, data, files, name):
- value = super(AutoComboboxSelectWidget, self).value_from_datadict(data, files, name)
- if not self.allow_new:
- return value[1]
- return value
+ primary_widget = AutoComboboxWidget
class LookupMultipleHiddenInput(forms.MultipleHiddenInput):
return attrs
-class AutoCompleteSelectMultipleWidget(SelectableMultiWidget, SelectableMediaMixin):
+class _BaseMultipleSelectWidget(SelectableMultiWidget, SelectableMediaMixin):
+ """
+ Common base class for AutoCompleteSelectMultipleWidget and AutoComboboxSelectMultipleWidget
+ each which use one widget (primary_widget) to select text and a multiple
+ hidden inputs to hold the selected ids.
+ """
+
+ primary_widget = None
def __init__(self, lookup_class, *args, **kwargs):
self.lookup_class = import_lookup_class(lookup_class)
attrs.update(kwargs.get('attrs', {}))
query_params = kwargs.pop('query_params', {})
widgets = [
- AutoCompleteWidget(
+ self.primary_widget(
lookup_class, allow_new=False,
limit=self.limit, query_params=query_params, attrs=attrs
),
LookupMultipleHiddenInput(lookup_class)
]
- super(AutoCompleteSelectMultipleWidget, self).__init__(widgets, *args, **kwargs)
+ super(_BaseMultipleSelectWidget, self).__init__(widgets, *args, **kwargs)
def value_from_datadict(self, data, files, name):
- return self.widgets[1].value_from_datadict(data, files, name + '_1')
+ value = self.widgets[1].value_from_datadict(data, files, name + '_1')
+ if not value:
+ # Fall back to the compatible POST name
+ value = self.get_compatible_postdata(data, name)
+ return value
def render(self, name, value, attrs=None):
if value and not hasattr(value, '__iter__'):
value = [value]
value = ['', value]
- return super(AutoCompleteSelectMultipleWidget, self).render(name, value, attrs)
-
- def _has_changed(self, initial, data):
- """"
- Decects if the widget was changed. This is removed in 1.6.
-
- For the multi-select case we only care if the hidden inputs changed.
- """
- initial = ['', initial]
- data = ['', data]
- return super(AutoCompleteSelectMultipleWidget, self)._has_changed(initial, data)
+ return super(_BaseMultipleSelectWidget, self).render(name, value, attrs)
+ if DJANGO_VERSION < (1, 6):
+ def _has_changed(self, initial, data):
+ """"
+ Detects if the widget was changed. This is removed in Django 1.6.
+ For the multi-select case we only care if the hidden inputs changed.
+ """
+ initial = ['', initial]
+ data = ['', data]
+ return super(_BaseMultipleSelectWidget, self)._has_changed(initial, data)
-class AutoComboboxSelectMultipleWidget(SelectableMultiWidget, SelectableMediaMixin):
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.limit = kwargs.pop('limit', None)
- position = kwargs.pop('position', 'bottom')
- attrs = {
- 'data-selectable-multiple': 'true',
- 'data-selectable-position': position
- }
- attrs.update(kwargs.get('attrs', {}))
- query_params = kwargs.pop('query_params', {})
- widgets = [
- AutoComboboxWidget(
- lookup_class, allow_new=False,
- limit=self.limit, query_params=query_params, attrs=attrs
- ),
- LookupMultipleHiddenInput(lookup_class)
- ]
- super(AutoComboboxSelectMultipleWidget, self).__init__(widgets, *args, **kwargs)
+class AutoCompleteSelectMultipleWidget(_BaseMultipleSelectWidget):
- def value_from_datadict(self, data, files, name):
- return self.widgets[1].value_from_datadict(data, files, name + '_1')
+ primary_widget = AutoCompleteWidget
- def render(self, name, value, attrs=None):
- if value and not hasattr(value, '__iter__'):
- value = [value]
- value = ['', value]
- return super(AutoComboboxSelectMultipleWidget, self).render(name, value, attrs)
- def _has_changed(self, initial, data):
- """"
- Decects if the widget was changed. This is removed in 1.6.
+class AutoComboboxSelectMultipleWidget(_BaseMultipleSelectWidget):
- For the multi-select case we only care if the hidden inputs changed.
- """
- initial = ['', initial]
- data = ['', data]
- return super(AutoComboboxSelectMultipleWidget, self)._has_changed(initial, data)
+ primary_widget = AutoComboboxWidget
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-02-17 14:18-0500\n"
+"POT-Creation-Date: 2014-10-21 20:14-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
msgid "Show more results"
msgstr ""
-#: forms/fields.py:22 forms/fields.py:69
+#: forms/fields.py:48 forms/fields.py:96
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
-#
+#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-selectable\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-02-17 14:18-0500\n"
-"PO-Revision-Date: 2013-02-13 14:21+0000\n"
-"Last-Translator: escriva <escriva@gmail.com>\n"
-"Language-Team: Spanish (http://www.transifex.com/projects/p/django-"
-"selectable/language/es/)\n"
-"Language: es\n"
+"POT-Creation-Date: 2012-10-06 15:02-0400\n"
+"PO-Revision-Date: 2013-11-20 10:18+0000\n"
+"Last-Translator: Manuel Alvarez <escriva@gmail.com>\n"
+"Language-Team: Spanish (http://www.transifex.com/projects/p/django-selectable/language/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-#: base.py:117
+#: base.py:115
msgid "Show more results"
msgstr "Mostrar más resultados"
-#: forms/fields.py:22 forms/fields.py:69
-msgid "Select a valid choice. That choice is not one of the available choices."
+#: forms/fields.py:19 forms/fields.py:63
+msgid ""
+"Select a valid choice. That choice is not one of the available choices."
msgstr "Seleccione una opción válida. La opción seleccionada no está disponible."
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# Mark Lavin <markdlavin@gmail.com>, 2014
+msgid ""
+msgstr ""
+"Project-Id-Version: django-selectable\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-10-06 15:02-0400\n"
+"PO-Revision-Date: 2014-01-21 01:00+0000\n"
+"Last-Translator: Mark Lavin <markdlavin@gmail.com>\n"
+"Language-Team: French (http://www.transifex.com/projects/p/django-selectable/language/fr/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: base.py:115
+msgid "Show more results"
+msgstr "Afficher plus de résultats"
+
+#: forms/fields.py:19 forms/fields.py:63
+msgid ""
+"Select a valid choice. That choice is not one of the available choices."
+msgstr "Sélectionnez un choix valide. Ce choix ne fait pas partie de ceux disponibles."
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
-#
+#
# Translators:
-# <slafs.e@gmail.com>, 2012.
+# slafs <slafs.e@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: django-selectable\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-02-17 14:18-0500\n"
-"PO-Revision-Date: 2012-10-11 12:50+0000\n"
+"POT-Creation-Date: 2012-10-06 15:02-0400\n"
+"PO-Revision-Date: 2013-11-20 10:18+0000\n"
"Last-Translator: slafs <slafs.e@gmail.com>\n"
-"Language-Team: Polish (http://www.transifex.com/projects/p/django-selectable/"
-"language/pl/)\n"
-"Language: pl\n"
+"Language-Team: Polish (http://www.transifex.com/projects/p/django-selectable/language/pl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
-"|| n%100>=20) ? 1 : 2);\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
-#: base.py:117
+#: base.py:115
msgid "Show more results"
msgstr "Pokaż więcej wyników"
-#: forms/fields.py:22 forms/fields.py:69
-msgid "Select a valid choice. That choice is not one of the available choices."
+#: forms/fields.py:19 forms/fields.py:63
+msgid ""
+"Select a valid choice. That choice is not one of the available choices."
msgstr "Dokonaj poprawnego wyboru. Ten wybór nie jest jednym z dostępnych."
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
-#
+#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-selectable\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2013-02-17 14:18-0500\n"
-"PO-Revision-Date: 2012-10-06 19:19+0000\n"
-"Last-Translator: mlavin <markdlavin@gmail.com>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"Language: pt_BR\n"
+"POT-Creation-Date: 2012-10-06 15:02-0400\n"
+"PO-Revision-Date: 2013-11-20 10:18+0000\n"
+"Last-Translator: Mark Lavin <markdlavin@gmail.com>\n"
+"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/django-selectable/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
+"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: base.py:117
+#: base.py:115
msgid "Show more results"
msgstr "Mostrar mais resultados"
-#: forms/fields.py:22 forms/fields.py:69
-msgid "Select a valid choice. That choice is not one of the available choices."
+#: forms/fields.py:19 forms/fields.py:63
+msgid ""
+"Select a valid choice. That choice is not one of the available choices."
msgstr "Selecione uma escolha valida. Esta escolha não é uma das disponíveis."
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+#
+# Translators:
+# mozillazg <opensource.mozillazg@gmail.com>, 2013
+msgid ""
+msgstr ""
+"Project-Id-Version: django-selectable\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-10-06 15:02-0400\n"
+"PO-Revision-Date: 2013-11-21 05:08+0000\n"
+"Last-Translator: mozillazg <opensource.mozillazg@gmail.com>\n"
+"Language-Team: Chinese (China) (http://www.transifex.com/projects/p/django-selectable/language/zh_CN/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: zh_CN\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: base.py:115
+msgid "Show more results"
+msgstr "显示更多结果"
+
+#: forms/fields.py:19 forms/fields.py:63
+msgid ""
+"Select a valid choice. That choice is not one of the available choices."
+msgstr "请选择一个有效的选项。当前选项无效。"
import copy
from django.conf import settings
- from django.utils.importlib import import_module
- from django.utils.module_loading import module_has_submodule
-
- for app in settings.INSTALLED_APPS:
- mod = import_module(app)
- # Attempt to import the app's lookups module.
- try:
- before_import_registry = copy.copy(registry._registry)
- import_module('%s.lookups' % app)
- except:
- registry._registry = before_import_registry
- if module_has_submodule(mod, 'lookups'):
- raise
+
+ try:
+ from django.utils.module_loading import autodiscover_modules
+ except ImportError:
+ from django.utils.importlib import import_module
+ from django.utils.module_loading import module_has_submodule
+
+ def autodiscover_modules(submod, **kwargs):
+ for app_name in settings.INSTALLED_APPS:
+ mod = import_module(app_name)
+ try:
+ before_import_registry = copy.copy(registry._registry)
+ import_module('%s.lookups' % app_name)
+ except:
+ registry._registry = before_import_registry
+ if module_has_submodule(mod, 'lookups'):
+ raise
+
+ # Attempt to import the app's lookups module.
+ autodiscover_modules('lookups', register_to=registry)
/*
* django-selectable UI widget CSS
- * Source: https://bitbucket.org/mlavin/django-selectable
+ * Source: https://github.com/mlavin/django-selectable
* Docs: http://django-selectable.readthedocs.org/
*
- * Copyright 2010-2013, Mark Lavin
+ * Copyright 2010-2014, Mark Lavin
* BSD License
*
*/
/*jshint trailing:true, indent:4*/
/*
* django-selectable UI widget
- * Source: https://bitbucket.org/mlavin/django-selectable
+ * Source: https://github.com/mlavin/django-selectable
* Docs: http://django-selectable.readthedocs.org/
*
* Depends:
- * - jQuery 1.4.4+
+ * - jQuery 1.7+
* - jQuery UI 1.8 widget factory
*
- * Copyright 2010-2013, Mark Lavin
+ * Copyright 2010-2014, Mark Lavin
* BSD License
*
*/
},
close: function (event) {
var page = $(this.element).data('page');
- if (page != null) {
+ if (page !== null) {
return;
}
// Call super trigger
};
function djangoAdminPatches() {
- /* Monkey-patch Django's dynamic formset, if defined */
- if (typeof(django) !== "undefined" && typeof(django.jQuery) !== "undefined") {
- if (django.jQuery.fn.formset) {
- var oldformset = django.jQuery.fn.formset;
- django.jQuery.fn.formset = function (opts) {
- var options = $.extend({}, opts);
- var addedevent = function (row) {
- window.bindSelectables($(row));
- };
- var added = null;
- if (options.added) {
- // Wrap previous added function and include call to bindSelectables
- var oldadded = options.added;
- added = function (row) { oldadded(row); addedevent(row); };
- }
- options.added = added || addedevent;
- return oldformset.call(this, options);
- };
- }
- }
+ /* Listen for new rows being added to the dynamic inlines.
+ Requires Django 1.5+ */
+ $('body').on('click', '.add-row', function (e) {
+ var wrapper = $(this).parents('.inline-related'),
+ newRow = $('.form-row:not(.empty-form)', wrapper).last();
+ window.bindSelectables(newRow);
+ });
/* Monkey-patch Django's dismissAddAnotherPopup(), if defined */
if (typeof(dismissAddAnotherPopup) !== "undefined" &&
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
-from selectable.base import ModelLookup
-from selectable.registry import registry
+from ..base import ModelLookup
+from ..registry import registry
+@python_2_unicode_compatible
class Thing(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=100)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class OtherThing(models.Model):
name = models.CharField(max_length=100)
thing = models.ForeignKey(Thing)
- def __unicode__(self):
+ def __str__(self):
return self.name
+@python_2_unicode_compatible
class ManyThing(models.Model):
name = models.CharField(max_length=100)
things = models.ManyToManyField(Thing)
- def __unicode__(self):
+ def __str__(self):
return self.name
registry.register(ThingLookup)
-from selectable.tests.base import *
-from selectable.tests.decorators import *
-from selectable.tests.fields import *
-from selectable.tests.functests import *
-from selectable.tests.forms import *
-from selectable.tests.templatetags import *
-from selectable.tests.views import *
-from selectable.tests.widgets import *
+from .test_base import *
+from .test_decorators import *
+from .test_fields import *
+from .test_functional import *
+from .test_forms import *
+from .test_templatetags import *
+from .test_views import *
+from .test_widgets import *
from xml.dom.minidom import parseString
from django.conf import settings
-from django.core.urlresolvers import reverse
from django.test import TestCase
-from django.utils.html import escape
-from django.utils.safestring import SafeData, mark_safe
-from selectable.base import ModelLookup
-from selectable.tests import Thing
-
-__all__ = (
- 'ModelLookupTestCase',
- 'MultiFieldLookupTestCase',
- 'LookupEscapingTestCase',
-)
+from ..base import ModelLookup
+from . import Thing
def as_xml(html):
class SimpleModelLookup(ModelLookup):
model = Thing
- search_fields = ('name__icontains', )
-
-
-class ModelLookupTestCase(BaseSelectableTestCase):
- lookup_cls = SimpleModelLookup
-
- def get_lookup_instance(self):
- return self.__class__.lookup_cls()
-
- def test_get_name(self):
- name = self.__class__.lookup_cls.name()
- self.assertEqual(name, 'tests-simplemodellookup')
-
- def test_get_url(self):
- url = self.__class__.lookup_cls.url()
- test_url = reverse('selectable-lookup', args=['tests-simplemodellookup'])
- self.assertEqual(url, test_url)
-
- def test_format_item(self):
- lookup = self.get_lookup_instance()
- thing = Thing()
- item_info = lookup.format_item(thing)
- self.assertTrue('id' in item_info)
- self.assertTrue('value' in item_info)
- self.assertTrue('label' in item_info)
-
- def test_get_query(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- other_thing = self.create_thing(data={'name': 'Other Thing'})
- qs = lookup.get_query(request=None, term='other')
- self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
- self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
-
- def test_create_item(self):
- value = self.get_random_string()
- lookup = self.get_lookup_instance()
- thing = lookup.create_item(value)
- self.assertEqual(thing.__class__, Thing)
- self.assertEqual(thing.name, value)
- self.assertFalse(thing.pk)
-
- def test_get_item(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- item = lookup.get_item(thing.pk)
- self.assertEqual(thing, item)
-
- def test_format_item_escaping(self):
- "Id, value and label should be escaped."
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'name': 'Thing'})
- item_info = lookup.format_item(thing)
- self.assertFalse(isinstance(item_info['id'], SafeData))
- self.assertFalse(isinstance(item_info['value'], SafeData))
- self.assertTrue(isinstance(item_info['label'], SafeData))
-
-
-class MultiFieldLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', 'description__icontains', )
-
-
-class MultiFieldLookupTestCase(ModelLookupTestCase):
- lookup_cls = MultiFieldLookup
-
- def test_get_name(self):
- name = self.__class__.lookup_cls.name()
- self.assertEqual(name, 'tests-multifieldlookup')
-
- def test_get_url(self):
- url = self.__class__.lookup_cls.url()
- test_url = reverse('selectable-lookup', args=['tests-multifieldlookup'])
- self.assertEqual(url, test_url)
-
- def test_description_search(self):
- lookup = self.get_lookup_instance()
- thing = self.create_thing(data={'description': 'Thing'})
- other_thing = self.create_thing(data={'description': 'Other Thing'})
- qs = lookup.get_query(request=None, term='other')
- self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
- self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
-
-
-class HTMLLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-class SafeHTMLLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
- def get_item_label(self, item):
- "Mark label as safe."
- return mark_safe(item.name)
-
-
-class LookupEscapingTestCase(BaseSelectableTestCase):
-
- def test_escape_html(self):
- "HTML should be escaped by default."
- lookup = HTMLLookup()
- bad_name = "<script>alert('hacked');</script>"
- escaped_name = escape(bad_name)
- thing = self.create_thing(data={'name': bad_name})
- item_info = lookup.format_item(thing)
- self.assertEqual(item_info['label'], escaped_name)
-
- def test_conditional_escape(self):
- "Methods should be able to mark values as safe."
- lookup = SafeHTMLLookup()
- bad_name = "<script>alert('hacked');</script>"
- escaped_name = escape(bad_name)
- thing = self.create_thing(data={'name': bad_name})
- item_info = lookup.format_item(thing)
- self.assertEqual(item_info['label'], bad_name)
+ search_fields = ('name__icontains', )
\ No newline at end of file
--- /dev/null
+from __future__ import unicode_literals
+
+from django.core.urlresolvers import reverse
+from django.utils.html import escape
+from django.utils.safestring import SafeData, mark_safe
+
+from ..base import ModelLookup
+from . import Thing
+from .base import BaseSelectableTestCase, SimpleModelLookup
+
+__all__ = (
+ 'ModelLookupTestCase',
+ 'MultiFieldLookupTestCase',
+ 'LookupEscapingTestCase',
+)
+
+
+class ModelLookupTestCase(BaseSelectableTestCase):
+ lookup_cls = SimpleModelLookup
+
+ def get_lookup_instance(self):
+ return self.__class__.lookup_cls()
+
+ def test_get_name(self):
+ name = self.__class__.lookup_cls.name()
+ self.assertEqual(name, 'tests-simplemodellookup')
+
+ def test_get_url(self):
+ url = self.__class__.lookup_cls.url()
+ test_url = reverse('selectable-lookup', args=['tests-simplemodellookup'])
+ self.assertEqual(url, test_url)
+
+ def test_format_item(self):
+ lookup = self.get_lookup_instance()
+ thing = Thing()
+ item_info = lookup.format_item(thing)
+ self.assertTrue('id' in item_info)
+ self.assertTrue('value' in item_info)
+ self.assertTrue('label' in item_info)
+
+ def test_get_query(self):
+ lookup = self.get_lookup_instance()
+ thing = self.create_thing(data={'name': 'Thing'})
+ other_thing = self.create_thing(data={'name': 'Other Thing'})
+ qs = lookup.get_query(request=None, term='other')
+ self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
+ self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
+
+ def test_create_item(self):
+ value = self.get_random_string()
+ lookup = self.get_lookup_instance()
+ thing = lookup.create_item(value)
+ self.assertEqual(thing.__class__, Thing)
+ self.assertEqual(thing.name, value)
+ self.assertFalse(thing.pk)
+
+ def test_get_item(self):
+ lookup = self.get_lookup_instance()
+ thing = self.create_thing(data={'name': 'Thing'})
+ item = lookup.get_item(thing.pk)
+ self.assertEqual(thing, item)
+
+ def test_format_item_escaping(self):
+ "Id, value and label should be escaped."
+ lookup = self.get_lookup_instance()
+ thing = self.create_thing(data={'name': 'Thing'})
+ item_info = lookup.format_item(thing)
+ self.assertFalse(isinstance(item_info['id'], SafeData))
+ self.assertFalse(isinstance(item_info['value'], SafeData))
+ self.assertTrue(isinstance(item_info['label'], SafeData))
+
+
+class MultiFieldLookup(ModelLookup):
+ model = Thing
+ search_fields = ('name__icontains', 'description__icontains', )
+
+
+class MultiFieldLookupTestCase(ModelLookupTestCase):
+ lookup_cls = MultiFieldLookup
+
+ def test_get_name(self):
+ name = self.__class__.lookup_cls.name()
+ self.assertEqual(name, 'tests-multifieldlookup')
+
+ def test_get_url(self):
+ url = self.__class__.lookup_cls.url()
+ test_url = reverse('selectable-lookup', args=['tests-multifieldlookup'])
+ self.assertEqual(url, test_url)
+
+ def test_description_search(self):
+ lookup = self.get_lookup_instance()
+ thing = self.create_thing(data={'description': 'Thing'})
+ other_thing = self.create_thing(data={'description': 'Other Thing'})
+ qs = lookup.get_query(request=None, term='other')
+ self.assertTrue(thing.pk not in qs.values_list('id', flat=True))
+ self.assertTrue(other_thing.pk in qs.values_list('id', flat=True))
+
+
+class HTMLLookup(ModelLookup):
+ model = Thing
+ search_fields = ('name__icontains', )
+
+
+class SafeHTMLLookup(ModelLookup):
+ model = Thing
+ search_fields = ('name__icontains', )
+
+ def get_item_label(self, item):
+ "Mark label as safe."
+ return mark_safe(item.name)
+
+
+class LookupEscapingTestCase(BaseSelectableTestCase):
+
+ def test_escape_html(self):
+ "HTML should be escaped by default."
+ lookup = HTMLLookup()
+ bad_name = "<script>alert('hacked');</script>"
+ escaped_name = escape(bad_name)
+ thing = self.create_thing(data={'name': bad_name})
+ item_info = lookup.format_item(thing)
+ self.assertEqual(item_info['label'], escaped_name)
+
+ def test_conditional_escape(self):
+ "Methods should be able to mark values as safe."
+ lookup = SafeHTMLLookup()
+ bad_name = "<script>alert('hacked');</script>"
+ escaped_name = escape(bad_name)
+ thing = self.create_thing(data={'name': bad_name})
+ item_info = lookup.format_item(thing)
+ self.assertEqual(item_info['label'], bad_name)
--- /dev/null
+try:
+ from unittest.mock import Mock
+except ImportError:
+ from mock import Mock
+
+from ..decorators import ajax_required, login_required, staff_member_required
+from .base import BaseSelectableTestCase, SimpleModelLookup
+
+
+__all__ = (
+ 'AjaxRequiredLookupTestCase',
+ 'LoginRequiredLookupTestCase',
+ 'StaffRequiredLookupTestCase',
+)
+
+
+class AjaxRequiredLookupTestCase(BaseSelectableTestCase):
+
+ def setUp(self):
+ self.lookup = ajax_required(SimpleModelLookup)()
+
+ def test_ajax_call(self):
+ "Ajax call should yield a successful response."
+ request = Mock()
+ request.is_ajax = lambda: True
+ response = self.lookup.results(request)
+ self.assertTrue(response.status_code, 200)
+
+ def test_non_ajax_call(self):
+ "Non-Ajax call should yield a bad request response."
+ request = Mock()
+ request.is_ajax = lambda: False
+ response = self.lookup.results(request)
+ self.assertEqual(response.status_code, 400)
+
+
+class LoginRequiredLookupTestCase(BaseSelectableTestCase):
+
+ def setUp(self):
+ self.lookup = login_required(SimpleModelLookup)()
+
+ def test_authenicated_call(self):
+ "Authenicated call should yield a successful response."
+ request = Mock()
+ user = Mock()
+ user.is_authenticated = lambda: True
+ request.user = user
+ response = self.lookup.results(request)
+ self.assertTrue(response.status_code, 200)
+
+ def test_non_authenicated_call(self):
+ "Non-Authenicated call should yield an unauthorized response."
+ request = Mock()
+ user = Mock()
+ user.is_authenticated = lambda: False
+ request.user = user
+ response = self.lookup.results(request)
+ self.assertEqual(response.status_code, 401)
+
+
+class StaffRequiredLookupTestCase(BaseSelectableTestCase):
+
+ def setUp(self):
+ self.lookup = staff_member_required(SimpleModelLookup)()
+
+ def test_staff_member_call(self):
+ "Staff member call should yield a successful response."
+ request = Mock()
+ user = Mock()
+ user.is_authenticated = lambda: True
+ user.is_staff = True
+ request.user = user
+ response = self.lookup.results(request)
+ self.assertTrue(response.status_code, 200)
+
+ def test_authenicated_but_not_staff(self):
+ "Authenicated but non staff call should yield a forbidden response."
+ request = Mock()
+ user = Mock()
+ user.is_authenticated = lambda: True
+ user.is_staff = False
+ request.user = user
+ response = self.lookup.results(request)
+ self.assertTrue(response.status_code, 403)
+
+ def test_non_authenicated_call(self):
+ "Non-Authenicated call should yield an unauthorized response."
+ request = Mock()
+ user = Mock()
+ user.is_authenticated = lambda: False
+ user.is_staff = False
+ request.user = user
+ response = self.lookup.results(request)
+ self.assertEqual(response.status_code, 401)
--- /dev/null
+from django import forms
+
+from selectable.forms import fields, widgets
+from selectable.tests import ThingLookup
+from selectable.tests.base import BaseSelectableTestCase
+
+
+__all__ = (
+ 'AutoCompleteSelectFieldTestCase',
+ 'AutoCompleteSelectMultipleFieldTestCase',
+)
+
+class FieldTestMixin(object):
+ field_cls = None
+ lookup_cls = None
+
+ def get_field_instance(self, allow_new=False, limit=None, widget=None):
+ return self.field_cls(self.lookup_cls, allow_new=allow_new, limit=limit, widget=widget)
+
+ def test_init(self):
+ field = self.get_field_instance()
+ self.assertEqual(field.lookup_class, self.lookup_cls)
+
+ def test_init_with_limit(self):
+ field = self.get_field_instance(limit=10)
+ self.assertEqual(field.limit, 10)
+ self.assertEqual(field.widget.limit, 10)
+
+ def test_clean(self):
+ self.fail('This test has not yet been written')
+
+ def test_dotted_path(self):
+ """
+ Ensure lookup_class can be imported from a dotted path.
+ """
+ dotted_path = '.'.join([self.lookup_cls.__module__, self.lookup_cls.__name__])
+ field = self.field_cls(dotted_path)
+ self.assertEqual(field.lookup_class, self.lookup_cls)
+
+ def test_invalid_dotted_path(self):
+ """
+ An invalid lookup_class dotted path should raise an ImportError.
+ """
+ with self.assertRaises(ImportError):
+ self.field_cls('this.is.an.invalid.path')
+
+ def test_dotted_path_wrong_type(self):
+ """
+ lookup_class must be a subclass of LookupBase.
+ """
+ dotted_path = 'selectable.forms.fields.AutoCompleteSelectField'
+ with self.assertRaises(TypeError):
+ self.field_cls(dotted_path)
+
+class AutoCompleteSelectFieldTestCase(BaseSelectableTestCase, FieldTestMixin):
+ field_cls = fields.AutoCompleteSelectField
+ lookup_cls = ThingLookup
+
+ def test_clean(self):
+ thing = self.create_thing()
+ field = self.get_field_instance()
+ value = field.clean([thing.name, thing.id])
+ self.assertEqual(thing, value)
+
+ def test_new_not_allowed(self):
+ field = self.get_field_instance()
+ value = self.get_random_string()
+ self.assertRaises(forms.ValidationError, field.clean, [value, ''])
+
+ def test_new_allowed(self):
+ field = self.get_field_instance(allow_new=True)
+ value = self.get_random_string()
+ value = field.clean([value, ''])
+ self.assertTrue(isinstance(value, ThingLookup.model))
+
+ def test_default_widget(self):
+ field = self.get_field_instance()
+ self.assertTrue(isinstance(field.widget, widgets.AutoCompleteSelectWidget))
+
+ def test_alternate_widget(self):
+ widget_cls = widgets.AutoComboboxWidget
+ field = self.get_field_instance(widget=widget_cls)
+ self.assertTrue(isinstance(field.widget, widget_cls))
+
+ def test_alternate_widget_instance(self):
+ widget = widgets.AutoComboboxWidget(self.lookup_cls)
+ field = self.get_field_instance(widget=widget)
+ self.assertTrue(isinstance(field.widget, widgets.AutoComboboxWidget))
+
+ def test_invalid_pk(self):
+ field = self.get_field_instance()
+ value = self.get_random_string()
+ self.assertRaises(forms.ValidationError, field.clean, [value, 'XXX'])
+
+
+class AutoCompleteSelectMultipleFieldTestCase(BaseSelectableTestCase, FieldTestMixin):
+ field_cls = fields.AutoCompleteSelectMultipleField
+ lookup_cls = ThingLookup
+
+ def get_field_instance(self, limit=None, widget=None):
+ return self.field_cls(self.lookup_cls, limit=limit, widget=widget)
+
+ def test_clean(self):
+ thing = self.create_thing()
+ field = self.get_field_instance()
+ value = field.clean([thing.id])
+ self.assertEqual([thing], value)
+
+ def test_clean_multiple(self):
+ thing = self.create_thing()
+ other_thing = self.create_thing()
+ field = self.get_field_instance()
+ ids = [thing.id, other_thing.id]
+ value = field.clean(ids)
+ self.assertEqual([thing, other_thing], value)
+
+ def test_default_widget(self):
+ field = self.get_field_instance()
+ self.assertTrue(isinstance(field.widget, widgets.AutoCompleteSelectMultipleWidget))
+
+ def test_alternate_widget(self):
+ widget_cls = widgets.AutoComboboxSelectMultipleWidget
+ field = self.get_field_instance(widget=widget_cls)
+ self.assertTrue(isinstance(field.widget, widget_cls))
+
+ def test_alternate_widget_instance(self):
+ widget = widgets.AutoComboboxSelectMultipleWidget(self.lookup_cls)
+ field = self.get_field_instance(widget=widget)
+ self.assertTrue(isinstance(field.widget, widgets.AutoComboboxSelectMultipleWidget))
+
+ def test_invalid_pk(self):
+ field = self.get_field_instance()
+ value = self.get_random_string()
+ self.assertRaises(forms.ValidationError, field.clean, ['XXX', ])
--- /dev/null
+from django.conf import settings
+
+from ..forms import BaseLookupForm
+from .base import BaseSelectableTestCase, PatchSettingsMixin
+
+
+__all__ = (
+ 'BaseLookupFormTestCase',
+)
+
+
+class BaseLookupFormTestCase(PatchSettingsMixin, BaseSelectableTestCase):
+
+ def get_valid_data(self):
+ data = {
+ 'term': 'foo',
+ 'limit': 10,
+ }
+ return data
+
+ def test_valid_data(self):
+ data = self.get_valid_data()
+ form = BaseLookupForm(data)
+ self.assertTrue(form.is_valid(), "%s" % form.errors)
+
+ def test_invalid_limit(self):
+ """
+ Test giving the form an invalid limit.
+ """
+
+ data = self.get_valid_data()
+ data['limit'] = 'bar'
+ form = BaseLookupForm(data)
+ self.assertFalse(form.is_valid())
+
+ def test_no_limit(self):
+ """
+ If SELECTABLE_MAX_LIMIT is set and limit is not given then
+ the form will return SELECTABLE_MAX_LIMIT.
+ """
+
+ data = self.get_valid_data()
+ if 'limit' in data:
+ del data['limit']
+ form = BaseLookupForm(data)
+ self.assertTrue(form.is_valid(), "%s" % form.errors)
+ self.assertEqual(form.cleaned_data['limit'], settings.SELECTABLE_MAX_LIMIT)
+
+ def test_no_max_set(self):
+ """
+ If SELECTABLE_MAX_LIMIT is not set but given then the form
+ will return the given limit.
+ """
+
+ settings.SELECTABLE_MAX_LIMIT = None
+ data = self.get_valid_data()
+ form = BaseLookupForm(data)
+ self.assertTrue(form.is_valid(), "%s" % form.errors)
+ if 'limit' in data:
+ self.assertTrue(form.cleaned_data['limit'], data['limit'])
+
+ def test_no_max_set_not_given(self):
+ """
+ If SELECTABLE_MAX_LIMIT is not set and not given then the form
+ will return no limit.
+ """
+
+ settings.SELECTABLE_MAX_LIMIT = None
+ data = self.get_valid_data()
+ if 'limit' in data:
+ del data['limit']
+ form = BaseLookupForm(data)
+ self.assertTrue(form.is_valid(), "%s" % form.errors)
+ self.assertFalse(form.cleaned_data.get('limit'))
+
+ def test_over_limit(self):
+ """
+ If SELECTABLE_MAX_LIMIT is set and limit given is greater then
+ the form will return SELECTABLE_MAX_LIMIT.
+ """
+
+ data = self.get_valid_data()
+ data['limit'] = settings.SELECTABLE_MAX_LIMIT + 100
+ form = BaseLookupForm(data)
+ self.assertTrue(form.is_valid(), "%s" % form.errors)
+ self.assertEqual(form.cleaned_data['limit'], settings.SELECTABLE_MAX_LIMIT)
--- /dev/null
+"""
+Larger functional tests for fields and widgets.
+"""
+from __future__ import unicode_literals
+
+from django import forms
+
+from ..forms import AutoCompleteSelectField, AutoCompleteSelectMultipleField
+from ..forms import AutoCompleteSelectWidget, AutoComboboxSelectWidget
+from . import ManyThing, OtherThing, ThingLookup
+from .base import BaseSelectableTestCase, parsed_inputs
+
+
+__all__ = (
+ 'FuncAutoCompleteSelectTestCase',
+ 'FuncSelectModelChoiceTestCase',
+ 'FuncComboboxModelChoiceTestCase',
+ 'FuncManytoManyMultipleSelectTestCase',
+ 'FuncFormTestCase',
+)
+
+
+class OtherThingForm(forms.ModelForm):
+
+ thing = AutoCompleteSelectField(lookup_class=ThingLookup)
+
+ class Meta(object):
+ model = OtherThing
+
+
+class FuncAutoCompleteSelectTestCase(BaseSelectableTestCase):
+
+ def setUp(self):
+ self.test_thing = self.create_thing()
+
+ def test_valid_form(self):
+ "Valid form using an AutoCompleteSelectField."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': self.test_thing.pk, # Hidden input
+ }
+ form = OtherThingForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_invalid_form_missing_selected_pk(self):
+ "Invalid form using an AutoCompleteSelectField."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': '', # Hidden input
+ }
+ form = OtherThingForm(data=data)
+ self.assertFalse(form.is_valid(), 'Form should not be valid')
+ self.assertFalse('name' in form.errors)
+ self.assertTrue('thing' in form.errors)
+
+ def test_invalid_form_missing_name(self):
+ "Invalid form using an AutoCompleteSelectField."
+ data = {
+ 'name': '',
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': self.test_thing.pk, # Hidden input
+ }
+ form = OtherThingForm(data=data)
+ self.assertFalse(form.is_valid(), 'Form should not be valid')
+ self.assertTrue('name' in form.errors)
+ self.assertFalse('thing' in form.errors)
+
+ def test_invalid_but_still_selected(self):
+ "Invalid form should keep selected item."
+ data = {
+ 'name': '',
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': self.test_thing.pk, # Hidden input
+ }
+ form = OtherThingForm(data=data)
+ self.assertFalse(form.is_valid(), 'Form should not be valid')
+ rendered_form = form.as_p()
+ inputs = parsed_inputs(rendered_form)
+ # Selected text should be populated
+ thing_0 = inputs['thing_0'][0]
+ self.assertEqual(thing_0.attributes['value'].value, self.test_thing.name)
+ # Selected pk should be populated
+ thing_1 = inputs['thing_1'][0]
+ self.assertEqual(int(thing_1.attributes['value'].value), self.test_thing.pk)
+
+ def test_populate_from_model(self):
+ "Populate from existing model."
+ other_thing = OtherThing.objects.create(thing=self.test_thing, name='a')
+ form = OtherThingForm(instance=other_thing)
+ rendered_form = form.as_p()
+ inputs = parsed_inputs(rendered_form)
+ # Selected text should be populated
+ thing_0 = inputs['thing_0'][0]
+ self.assertEqual(thing_0.attributes['value'].value, self.test_thing.name)
+ # Selected pk should be populated
+ thing_1 = inputs['thing_1'][0]
+ self.assertEqual(int(thing_1.attributes['value'].value), self.test_thing.pk)
+
+
+class SelectWidgetForm(forms.ModelForm):
+
+ class Meta(object):
+ model = OtherThing
+ widgets = {
+ 'thing': AutoCompleteSelectWidget(lookup_class=ThingLookup)
+ }
+
+
+class FuncSelectModelChoiceTestCase(BaseSelectableTestCase):
+ """
+ Functional tests for AutoCompleteSelectWidget compatibility
+ with a ModelChoiceField.
+ """
+
+ def setUp(self):
+ self.test_thing = self.create_thing()
+
+ def test_valid_form(self):
+ "Valid form using an AutoCompleteSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': self.test_thing.pk, # Hidden input
+ }
+ form = SelectWidgetForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_missing_pk(self):
+ "Invalid form (missing required pk) using an AutoCompleteSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': '', # Hidden input missing
+ }
+ form = SelectWidgetForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertTrue('thing' in form.errors)
+
+ def test_invalid_pk(self):
+ "Invalid form (invalid pk value) using an AutoCompleteSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': 'XXX', # Hidden input doesn't match a PK
+ }
+ form = SelectWidgetForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertTrue('thing' in form.errors)
+
+ def test_post_compatibility(self):
+ """
+ If new items are not allowed then the original field
+ name can be included in the POST with the selected id.
+ """
+ data = {
+ 'name': self.get_random_string(),
+ 'thing': self.test_thing.pk,
+ }
+ form = SelectWidgetForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+
+class ComboboxSelectWidgetForm(forms.ModelForm):
+
+ class Meta(object):
+ model = OtherThing
+ widgets = {
+ 'thing': AutoComboboxSelectWidget(lookup_class=ThingLookup)
+ }
+
+
+class FuncComboboxModelChoiceTestCase(BaseSelectableTestCase):
+ """
+ Functional tests for AutoComboboxSelectWidget compatibility
+ with a ModelChoiceField.
+ """
+
+ def setUp(self):
+ self.test_thing = self.create_thing()
+
+ def test_valid_form(self):
+ "Valid form using an AutoComboboxSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': self.test_thing.pk, # Hidden input
+ }
+ form = ComboboxSelectWidgetForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_missing_pk(self):
+ "Invalid form (missing required pk) using an AutoComboboxSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': '', # Hidden input missing
+ }
+ form = ComboboxSelectWidgetForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertTrue('thing' in form.errors)
+
+ def test_invalid_pk(self):
+ "Invalid form (invalid pk value) using an AutoComboboxSelectWidget."
+ data = {
+ 'name': self.get_random_string(),
+ 'thing_0': self.test_thing.name, # Text input
+ 'thing_1': 'XXX', # Hidden input doesn't match a PK
+ }
+ form = ComboboxSelectWidgetForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertTrue('thing' in form.errors)
+
+ def test_post_compatibility(self):
+ """
+ If new items are not allowed then the original field
+ name can be included in the POST with the selected id.
+ """
+ data = {
+ 'name': self.get_random_string(),
+ 'thing': self.test_thing.pk,
+ }
+ form = ComboboxSelectWidgetForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+
+class ManyThingForm(forms.ModelForm):
+
+ things = AutoCompleteSelectMultipleField(lookup_class=ThingLookup)
+
+ class Meta(object):
+ model = ManyThing
+
+
+class FuncManytoManyMultipleSelectTestCase(BaseSelectableTestCase):
+ """
+ Functional tests for AutoCompleteSelectMultipleField compatibility
+ with a ManyToManyField.
+ """
+
+ def setUp(self):
+ self.test_thing = self.create_thing()
+
+ def test_valid_form(self):
+ "Valid form using an AutoCompleteSelectMultipleField."
+ data = {
+ 'name': self.get_random_string(),
+ 'things_0': '', # Text input
+ 'things_1': [self.test_thing.pk, ], # Hidden inputs
+ }
+ form = ManyThingForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_valid_save(self):
+ "Saving data from a valid form."
+ data = {
+ 'name': self.get_random_string(),
+ 'things_0': '', # Text input
+ 'things_1': [self.test_thing.pk, ], # Hidden inputs
+ }
+ form = ManyThingForm(data=data)
+ manything = form.save()
+ self.assertEqual(manything.name, data['name'])
+ things = manything.things.all()
+ self.assertEqual(things.count(), 1)
+ self.assertTrue(self.test_thing in things)
+
+ def test_not_required(self):
+ "Valid form where many to many is not required."
+ data = {
+ 'name': self.get_random_string(),
+ 'things_0': '', # Text input
+ 'things_1': [], # Hidden inputs
+ }
+ form = ManyThingForm(data=data)
+ form.fields['things'].required = False
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_not_required_save(self):
+ "Saving data when many to many is not required."
+ data = {
+ 'name': self.get_random_string(),
+ 'things_0': '', # Text input
+ 'things_1': [], # Hidden inputs
+ }
+ form = ManyThingForm(data=data)
+ form.fields['things'].required = False
+ manything = form.save()
+ self.assertEqual(manything.name, data['name'])
+ things = manything.things.all()
+ self.assertEqual(things.count(), 0)
+
+ def test_has_changed(self):
+ "Populate intial data from a model."
+ manything = ManyThing.objects.create(name='Foo')
+ thing_1 = self.create_thing()
+ manything.things.add(thing_1)
+ data = {
+ 'name': manything.name,
+ 'things_0': '', # Text input
+ 'things_1': [thing_1.pk], # Hidden inputs
+ }
+ form = ManyThingForm(data=data, instance=manything)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+
+ def test_post_compatibility(self):
+ """
+ If new items are not allowed then the original field
+ name can be included in the POST with the selected ids.
+ """
+ data = {
+ 'name': self.get_random_string(),
+ 'things': [self.test_thing.pk, ],
+ }
+ form = ManyThingForm(data=data)
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+
+class SimpleForm(forms.Form):
+ "Non-model form usage."
+ thing = AutoCompleteSelectField(lookup_class=ThingLookup)
+ new_thing = AutoCompleteSelectField(lookup_class=ThingLookup, allow_new=True)
+ things = AutoCompleteSelectMultipleField(lookup_class=ThingLookup)
+
+
+class FuncFormTestCase(BaseSelectableTestCase):
+ """
+ Functional tests for using AutoCompleteSelectField
+ and AutoCompleteSelectMultipleField outside the context
+ of a ModelForm.
+ """
+
+ def setUp(self):
+ self.test_thing = self.create_thing()
+
+ def test_blank_new_item(self):
+ "Regression test for #91. new_thing is required but both are blank."
+ data = {
+ 'thing_0': self.test_thing.name,
+ 'thing_1': self.test_thing.pk,
+ 'new_thing_0': '',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': [self.test_thing.pk, ]
+ }
+ form = SimpleForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertTrue('new_thing' in form.errors)
+
+ def test_has_changed_with_empty_permitted(self):
+ """
+ Regression test for #92. has_changed fails when there is no initial and
+ allow_new=False.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': self.test_thing.pk,
+ 'new_thing_0': self.test_thing.name,
+ 'new_thing_1': self.test_thing.pk,
+ 'things_0': '',
+ 'things_1': [self.test_thing.pk, ]
+ }
+ form = SimpleForm(data=data, empty_permitted=True)
+ self.assertTrue(form.has_changed())
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_not_changed(self):
+ """
+ Regression test for #92. has_changed fails when there is no initial and
+ allow_new=False.
+ """
+ data = {
+ 'thing_0': self.test_thing.name,
+ 'thing_1': self.test_thing.pk,
+ 'new_thing_0': self.test_thing.name,
+ 'new_thing_1': self.test_thing.pk,
+ 'things_0': '',
+ 'things_1': [self.test_thing.pk, ]
+ }
+ initial = {
+ 'thing': self.test_thing.pk,
+ 'new_thing': self.test_thing.pk,
+ 'things': [self.test_thing.pk, ]
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertFalse(form.has_changed())
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_not_changed_with_empty_permitted(self):
+ """
+ Regression test for #92. has_changed fails when there is no initial and
+ allow_new=False.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': '',
+ 'new_thing_0': '',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': '',
+ }
+ initial = {
+ 'thing': '',
+ 'new_thing': '',
+ 'things': '',
+ }
+ form = SimpleForm(data=data, initial=initial, empty_permitted=True)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_no_initial_with_empty_permitted(self):
+ """
+ If empty data is submitted and allowed with no initial then
+ the form should not be seen as changed.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': '',
+ 'new_thing_0': '',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': '',
+ }
+ form = SimpleForm(data=data, empty_permitted=True)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_no_data_with_empty_permitted(self):
+ """
+ If no data is submitted and allowed with no initial then
+ the form should not be seen as changed.
+ """
+ form = SimpleForm(data={}, empty_permitted=True)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+ self.assertTrue(form.is_valid(), str(form.errors))
+
+ def test_select_multiple_changed(self):
+ """
+ Detect changes for a multiple select input with and without
+ initial data.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': '',
+ 'new_thing_0': '',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': [self.test_thing.pk, ]
+ }
+ form = SimpleForm(data=data)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('things' in form.changed_data)
+
+ initial = {
+ 'thing': '',
+ 'new_thing': '',
+ 'things': [self.test_thing.pk, ],
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+
+ initial = {
+ 'thing': '',
+ 'new_thing': '',
+ 'things': [],
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('things' in form.changed_data)
+
+ def test_single_select_changed(self):
+ """
+ Detect changes for a single select input with and without
+ initial data.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': self.test_thing.pk,
+ 'new_thing_0': '',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': ''
+ }
+ form = SimpleForm(data=data)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('thing' in form.changed_data)
+
+ initial = {
+ 'thing': self.test_thing.pk,
+ 'new_thing': '',
+ 'things': '',
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+
+ initial = {
+ 'thing': '',
+ 'new_thing': '',
+ 'things': '',
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('thing' in form.changed_data)
+
+ def test_new_select_changed(self):
+ """
+ Detect changes for a single select input which allows new items
+ with and without initial data.
+ """
+ data = {
+ 'thing_0': '',
+ 'thing_1': '',
+ 'new_thing_0': 'Foo',
+ 'new_thing_1': '',
+ 'things_0': '',
+ 'things_1': ''
+ }
+ form = SimpleForm(data=data)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('new_thing' in form.changed_data)
+
+ initial = {
+ 'thing': '',
+ 'new_thing': ['Foo', None],
+ 'things': '',
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertFalse(form.has_changed(), str(form.changed_data))
+
+ initial = {
+ 'thing': '',
+ 'new_thing': '',
+ 'things': '',
+ }
+ form = SimpleForm(data=data, initial=initial)
+ self.assertTrue(form.has_changed())
+ self.assertTrue('new_thing' in form.changed_data)
--- /dev/null
+from django.template import Template, Context
+
+from .base import BaseSelectableTestCase
+
+__all__ = (
+ 'JqueryTagTestCase',
+ 'ThemeTagTestCase',
+)
+
+
+class JqueryTagTestCase(BaseSelectableTestCase):
+
+ def assertJQueryVersion(self, result, version):
+ expected = "//ajax.googleapis.com/ajax/libs/jquery/%s/jquery.min.js" % version
+ self.assertTrue(expected in result)
+
+ def assertUIVersion(self, result, version):
+ expected = "//ajax.googleapis.com/ajax/libs/jqueryui/%s/jquery-ui.js" % version
+ self.assertTrue(expected in result)
+
+ def test_render(self):
+ "Render template tag with default versions."
+ template = Template("{% load selectable_tags %}{% include_jquery_libs %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertJQueryVersion(result, '1.7.2')
+ self.assertUIVersion(result, '1.8.23')
+
+ def test_render_jquery_version(self):
+ "Render template tag with specified jQuery version."
+ template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertJQueryVersion(result, '1.4.3')
+
+ def test_render_variable_jquery_version(self):
+ "Render using jQuery version from the template context."
+ version = '1.4.3'
+ template = Template("{% load selectable_tags %}{% include_jquery_libs version %}")
+ context = Context({'version': version})
+ result = template.render(context)
+ self.assertJQueryVersion(result, '1.4.3')
+
+ def test_render_jquery_ui_version(self):
+ "Render template tag with specified jQuery UI version."
+ template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' '1.8.13' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertUIVersion(result, '1.8.13')
+
+ def test_render_variable_jquery_ui_version(self):
+ "Render using jQuery UI version from the template context."
+ version = '1.8.13'
+ template = Template("{% load selectable_tags %}{% include_jquery_libs '1.4.3' version %}")
+ context = Context({'version': version})
+ result = template.render(context)
+ self.assertUIVersion(result, '1.8.13')
+
+ def test_render_no_jquery(self):
+ "Render template tag without jQuery."
+ template = Template("{% load selectable_tags %}{% include_jquery_libs '' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertTrue('jquery.min.js' not in result)
+
+ def test_render_no_jquery_ui(self):
+ "Render template tag without jQuery UI."
+ template = Template("{% load selectable_tags %}{% include_jquery_libs '1.7.2' '' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertTrue('jquery-ui.js' not in result)
+
+
+class ThemeTagTestCase(BaseSelectableTestCase):
+
+ def assertUICSS(self, result, theme, version):
+ expected = "//ajax.googleapis.com/ajax/libs/jqueryui/%s/themes/%s/jquery-ui.css" % (version, theme)
+ self.assertTrue(expected in result)
+
+ def test_render(self):
+ "Render template tag with default settings."
+ template = Template("{% load selectable_tags %}{% include_ui_theme %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertUICSS(result, 'base', '1.8.23')
+
+ def test_render_version(self):
+ "Render template tag with alternate version."
+ template = Template("{% load selectable_tags %}{% include_ui_theme 'base' '1.8.13' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertUICSS(result, 'base', '1.8.13')
+
+ def test_variable_version(self):
+ "Render using version from content variable."
+ version = '1.8.13'
+ template = Template("{% load selectable_tags %}{% include_ui_theme 'base' version %}")
+ context = Context({'version': version})
+ result = template.render(context)
+ self.assertUICSS(result, 'base', version)
+
+ def test_render_theme(self):
+ "Render template tag with alternate theme."
+ template = Template("{% load selectable_tags %}{% include_ui_theme 'ui-lightness' %}")
+ context = Context({})
+ result = template.render(context)
+ self.assertUICSS(result, 'ui-lightness', '1.8.23')
+
+ def test_variable_theme(self):
+ "Render using theme from content variable."
+ theme = 'ui-lightness'
+ template = Template("{% load selectable_tags %}{% include_ui_theme theme %}")
+ context = Context({'theme': theme})
+ result = template.render(context)
+ self.assertUICSS(result, theme, '1.8.23')
--- /dev/null
+from __future__ import division
+
+import json
+
+from django.conf import settings
+from django.core.urlresolvers import reverse
+
+from . import ThingLookup
+from .base import BaseSelectableTestCase, PatchSettingsMixin
+
+
+__all__ = (
+ 'SelectableViewTest',
+)
+
+
+class SelectableViewTest(PatchSettingsMixin, BaseSelectableTestCase):
+
+ def setUp(self):
+ super(SelectableViewTest, self).setUp()
+ self.url = ThingLookup.url()
+ self.lookup = ThingLookup()
+ self.thing = self.create_thing()
+ self.other_thing = self.create_thing()
+
+ def test_response_type(self):
+ response = self.client.get(self.url)
+ self.assertEqual(response['Content-Type'], 'application/json')
+
+ def test_response_keys(self):
+ response = self.client.get(self.url)
+ data = json.loads(response.content.decode('utf-8'))
+ for result in data.get('data'):
+ self.assertTrue('id' in result)
+ self.assertTrue('value' in result)
+ self.assertTrue('label' in result)
+
+ def test_no_term_lookup(self):
+ data = {}
+ response = self.client.get(self.url, data)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data), 2)
+
+ def test_simple_term_lookup(self):
+ data = {'term': self.thing.name}
+ response = self.client.get(self.url, data)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data), 2)
+ self.assertEqual(len(data.get('data')), 1)
+
+ def test_unknown_lookup(self):
+ unknown_url = reverse('selectable-lookup', args=["XXXXXXX"])
+ response = self.client.get(unknown_url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_basic_limit(self):
+ for i in range(settings.SELECTABLE_MAX_LIMIT):
+ self.create_thing(data={'name': 'Thing%s' % i})
+ response = self.client.get(self.url)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
+ meta = data.get('meta')
+ self.assertTrue('next_page' in meta)
+
+ def test_get_next_page(self):
+ for i in range(settings.SELECTABLE_MAX_LIMIT * 2):
+ self.create_thing(data={'name': 'Thing%s' % i})
+ data = {'term': 'Thing', 'page': 2}
+ response = self.client.get(self.url, data)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
+ # No next page
+ meta = data.get('meta')
+ self.assertFalse('next_page' in meta)
+
+ def test_request_more_than_max(self):
+ for i in range(settings.SELECTABLE_MAX_LIMIT):
+ self.create_thing(data={'name': 'Thing%s' % i})
+ data = {'term': '', 'limit': settings.SELECTABLE_MAX_LIMIT * 2}
+ response = self.client.get(self.url)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
+
+ def test_request_less_than_max(self):
+ for i in range(settings.SELECTABLE_MAX_LIMIT):
+ self.create_thing(data={'name': 'Thing%s' % i})
+ new_limit = settings.SELECTABLE_MAX_LIMIT // 2
+ data = {'term': '', 'limit': new_limit}
+ response = self.client.get(self.url, data)
+ data = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(len(data.get('data')), new_limit)
--- /dev/null
+import json
+
+from django import forms
+from django.utils.http import urlencode
+
+from ..compat import urlparse
+from ..forms import widgets
+from . import Thing, ThingLookup
+from .base import BaseSelectableTestCase, parsed_inputs
+
+
+__all__ = (
+ 'AutoCompleteWidgetTestCase',
+ 'AutoCompleteSelectWidgetTestCase',
+ 'AutoComboboxWidgetTestCase',
+ 'AutoComboboxSelectWidgetTestCase',
+ 'AutoCompleteSelectMultipleWidgetTestCase',
+ 'AutoComboboxSelectMultipleWidgetTestCase',
+)
+
+
+class WidgetTestMixin(object):
+ widget_cls = None
+ lookup_cls = None
+
+ def get_widget_instance(self, **kwargs):
+ return self.__class__.widget_cls(self.__class__.lookup_cls, **kwargs)
+
+ def test_init(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.lookup_class, self.__class__.lookup_cls)
+
+ def test_dotted_path(self):
+ """
+ Ensure lookup_class can be imported from a dotted path.
+ """
+ dotted_path = '.'.join([self.__class__.lookup_cls.__module__, self.__class__.lookup_cls.__name__])
+ widget = self.__class__.widget_cls(dotted_path)
+ self.assertEqual(widget.lookup_class, self.__class__.lookup_cls)
+
+ def test_invalid_dotted_path(self):
+ """
+ An invalid lookup_class dotted path should raise an ImportError.
+ """
+ with self.assertRaises(ImportError):
+ self.__class__.widget_cls('this.is.an.invalid.path')
+
+ def test_dotted_path_wrong_type(self):
+ """
+ lookup_class must be a subclass of LookupBase.
+ """
+ dotted_path = 'selectable.forms.widgets.AutoCompleteWidget'
+ with self.assertRaises(TypeError):
+ self.__class__.widget_cls(dotted_path)
+
+
+class AutoCompleteWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoCompleteWidget
+ lookup_cls = ThingLookup
+
+ def test_build_attrs(self):
+ widget = self.get_widget_instance()
+ attrs = widget.build_attrs()
+ self.assertTrue('data-selectable-url' in attrs)
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertTrue('data-selectable-allow-new' in attrs)
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ attrs = widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+
+
+class AutoCompleteSelectWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoCompleteSelectWidget
+ lookup_cls = ThingLookup
+
+ def test_has_complete_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[0].__class__, widgets.AutoCompleteWidget)
+
+ def test_has_hidden_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[1].__class__, forms.HiddenInput)
+
+ def test_hidden_type(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[1]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertEqual(attrs['data-selectable-type'], 'hidden')
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+
+ def test_postdata_compatible_with_select(self):
+ "Checks postdata for values that a select widget would generate."
+ postdata = {'fruit': '1'}
+ widget = self.get_widget_instance()
+ widget_val = widget.value_from_datadict(postdata, [], 'fruit')
+ self.assertEquals(widget_val, '1')
+
+
+class AutoComboboxWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoComboboxWidget
+ lookup_cls = ThingLookup
+
+ def test_build_attrs(self):
+ widget = self.get_widget_instance()
+ attrs = widget.build_attrs()
+ self.assertTrue('data-selectable-url' in attrs)
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertTrue('data-selectable-allow-new' in attrs)
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ attrs = widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ attrs = widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+
+
+class AutoComboboxSelectWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoComboboxSelectWidget
+ lookup_cls = ThingLookup
+
+ def test_has_complete_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[0].__class__, widgets.AutoComboboxWidget)
+
+ def test_has_hidden_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[1].__class__, forms.HiddenInput)
+
+ def test_hidden_type(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[1]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertEqual(attrs['data-selectable-type'], 'hidden')
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+
+
+class AutoCompleteSelectMultipleWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoCompleteSelectMultipleWidget
+ lookup_cls = ThingLookup
+
+ def test_has_complete_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[0].__class__, widgets.AutoCompleteWidget)
+
+ def test_multiple_attr(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-multiple' in attrs)
+ self.assertEqual(attrs['data-selectable-multiple'], 'true')
+
+ def test_has_hidden_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[1].__class__, widgets.LookupMultipleHiddenInput)
+
+ def test_hidden_type(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[1]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertEqual(attrs['data-selectable-type'], 'hidden-multiple')
+
+ def test_render_single(self):
+ widget = self.get_widget_instance()
+ val = 4
+ rendered_value = widget.render('field_name', val)
+ inputs = parsed_inputs(rendered_value)
+ field = inputs['field_name_1'][0]
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ self.assertEqual(int(field.attributes['value'].value), val)
+
+ def test_render_list(self):
+ widget = self.get_widget_instance()
+ list_val = [8, 5]
+ rendered_value = widget.render('field_name', list_val)
+ inputs = parsed_inputs(rendered_value)
+ found_values = []
+ for field in inputs['field_name_1']:
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ found_values.append(int(field.attributes['value'].value))
+ self.assertListEqual(found_values, list_val)
+
+ def test_render_qs(self):
+ widget = self.get_widget_instance()
+ t1 = self.create_thing()
+ t2 = self.create_thing()
+ qs_val = Thing.objects.filter(pk__in=[t1.pk, t2.pk]).values_list('pk', flat=True)
+ rendered_value = widget.render('field_name', qs_val)
+ inputs = parsed_inputs(rendered_value)
+ found_values = []
+ for field in inputs['field_name_1']:
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ found_values.append(int(field.attributes['value'].value))
+ self.assertListEqual(found_values, [t1.pk, t2.pk])
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+
+
+class AutoComboboxSelectMultipleWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
+ widget_cls = widgets.AutoComboboxSelectMultipleWidget
+ lookup_cls = ThingLookup
+
+ def test_has_complete_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[0].__class__, widgets.AutoComboboxWidget)
+
+ def test_multiple_attr(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-multiple' in attrs)
+ self.assertEqual(attrs['data-selectable-multiple'], 'true')
+
+ def test_has_hidden_widget(self):
+ widget = self.get_widget_instance()
+ self.assertEqual(widget.widgets[1].__class__, widgets.LookupMultipleHiddenInput)
+
+ def test_hidden_type(self):
+ widget = self.get_widget_instance()
+ sub_widget = widget.widgets[1]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-type' in attrs)
+ self.assertEqual(attrs['data-selectable-type'], 'hidden-multiple')
+
+ def test_render_single(self):
+ widget = self.get_widget_instance()
+ val = 4
+ rendered_value = widget.render('field_name', val)
+ inputs = parsed_inputs(rendered_value)
+ field = inputs['field_name_1'][0]
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ self.assertEqual(field.attributes['value'].value, str(val))
+
+ def test_render_list(self):
+ widget = self.get_widget_instance()
+ list_val = [8, 5]
+ rendered_value = widget.render('field_name', list_val)
+ inputs = parsed_inputs(rendered_value)
+ found_values = []
+ for field in inputs['field_name_1']:
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ found_values.append(int(field.attributes['value'].value))
+ self.assertListEqual(found_values, list_val)
+
+ def test_render_qs(self):
+ widget = self.get_widget_instance()
+ t1 = self.create_thing()
+ t2 = self.create_thing()
+ qs_val = Thing.objects.filter(pk__in=[t1.pk, t2.pk]).values_list('pk', flat=True)
+ rendered_value = widget.render('field_name', qs_val)
+ inputs = parsed_inputs(rendered_value)
+ found_values = []
+ for field in inputs['field_name_1']:
+ self.assertEqual(field.attributes['data-selectable-type'].value, 'hidden-multiple')
+ self.assertEqual(field.attributes['type'].value, 'hidden')
+ found_values.append(int(field.attributes['value'].value))
+ self.assertListEqual(found_values, [t1.pk, t2.pk])
+
+ def test_update_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance()
+ widget.update_query_parameters(params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_limit_paramter(self):
+ widget = self.get_widget_instance(limit=10)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertTrue('limit=10' in query)
+
+ def test_initial_query_parameters(self):
+ params = {'active': 1}
+ widget = self.get_widget_instance(query_params=params)
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ url = attrs['data-selectable-url']
+ parse = urlparse(url)
+ query = parse.query
+ self.assertEqual(query, urlencode(params))
+
+ def test_build_selectable_options(self):
+ "Serialize selectable options as json in data attribute."
+ options = {'autoFocus': True}
+ widget = self.get_widget_instance(attrs={'data-selectable-options': options})
+ sub_widget = widget.widgets[0]
+ attrs = sub_widget.build_attrs()
+ self.assertTrue('data-selectable-options' in attrs)
+ self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
-try:
- from django.conf.urls import handler404, handler500, patterns, include
-except ImportError:
- # Django < 1.4
- from django.conf.urls.defaults import handler404, handler500, patterns, include
+from django.conf.urls import handler404, handler500, include, url
+
handler404 = 'selectable.tests.views.test_404'
handler500 = 'selectable.tests.views.test_500'
-urlpatterns = patterns('',
- (r'^selectable-tests/', include('selectable.urls')),
-)
+urlpatterns = [
+ url(r'^selectable-tests/', include('selectable.urls')),
+]
-from __future__ import division
-
-import json
-
-from django.conf import settings
-from django.core.urlresolvers import reverse
from django.http import HttpResponseNotFound, HttpResponseServerError
-from selectable.tests import ThingLookup
-from selectable.tests.base import BaseSelectableTestCase, PatchSettingsMixin
-
-
-__all__ = (
- 'SelectableViewTest',
-)
-
-
def test_404(request):
return HttpResponseNotFound()
def test_500(request):
return HttpResponseServerError()
-
-
-class SelectableViewTest(PatchSettingsMixin, BaseSelectableTestCase):
-
- def setUp(self):
- super(SelectableViewTest, self).setUp()
- self.url = ThingLookup.url()
- self.lookup = ThingLookup()
- self.thing = self.create_thing()
- self.other_thing = self.create_thing()
-
- def test_response_type(self):
- response = self.client.get(self.url)
- self.assertEqual(response['Content-Type'], 'application/json')
-
- def test_response_keys(self):
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- for result in data.get('data'):
- self.assertTrue('id' in result)
- self.assertTrue('value' in result)
- self.assertTrue('label' in result)
-
- def test_no_term_lookup(self):
- data = {}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data), 2)
-
- def test_simple_term_lookup(self):
- data = {'term': self.thing.name}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data), 2)
- self.assertEqual(len(data.get('data')), 1)
-
- def test_unknown_lookup(self):
- unknown_url = reverse('selectable-lookup', args=["XXXXXXX"])
- response = self.client.get(unknown_url)
- self.assertEqual(response.status_code, 404)
-
- def test_basic_limit(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
- meta = data.get('meta')
- self.assertTrue('next_page' in meta)
-
- def test_get_next_page(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT * 2):
- self.create_thing(data={'name': 'Thing%s' % i})
- data = {'term': 'Thing', 'page': 2}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
- # No next page
- meta = data.get('meta')
- self.assertFalse('next_page' in meta)
-
- def test_request_more_than_max(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- data = {'term': '', 'limit': settings.SELECTABLE_MAX_LIMIT * 2}
- response = self.client.get(self.url)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), settings.SELECTABLE_MAX_LIMIT)
-
- def test_request_less_than_max(self):
- for i in range(settings.SELECTABLE_MAX_LIMIT):
- self.create_thing(data={'name': 'Thing%s' % i})
- new_limit = settings.SELECTABLE_MAX_LIMIT // 2
- data = {'term': '', 'limit': new_limit}
- response = self.client.get(self.url, data)
- data = json.loads(response.content.decode('utf-8'))
- self.assertEqual(len(data.get('data')), new_limit)
-try:
- from django.conf.urls import handler404, handler500, patterns, url
-except ImportError:
- # Django < 1.4
- from django.conf.urls.defaults import handler404, handler500, patterns, url
+from django.conf.urls import url
-from selectable import registry
+from . import views
+from .compat import LEGACY_AUTO_DISCOVER
+if LEGACY_AUTO_DISCOVER:
+ # Auto-discovery is now handled by the app configuration
+ from . import registry
-registry.autodiscover()
+ registry.autodiscover()
-urlpatterns = patterns('selectable.views',
- url(r'^(?P<lookup_name>[-\w]+)/$', 'get_lookup', name="selectable-lookup"),
-)
+
+urlpatterns = [
+ url(r'^(?P<lookup_name>[-\w]+)/$', views.get_lookup, name="selectable-lookup"),
+]
--- /dev/null
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
author_email='markdlavin@gmail.com',
packages=find_packages(exclude=['example']),
include_package_data=True,
- url='http://bitbucket.org/mlavin/django-selectable',
+ url='https://github.com/mlavin/django-selectable',
license='BSD',
description=' '.join(__import__('selectable').__doc__.splitlines()).strip(),
classifiers=[
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
'Framework :: Django',
'Development Status :: 5 - Production/Stable',
'Operating System :: OS Independent',