+++ /dev/null
-[run]
-source = selectable
-omit = */tests*,*/urls.py
+++ /dev/null
-[main]
-host = https://www.transifex.com
-
-[django-selectable.txo]
-file_filter = selectable/locale/<lang>/LC_MESSAGES/django.po
-source_file = selectable/locale/en/LC_MESSAGES/django.po
-source_lang = en
-type = PO
+++ /dev/null
-Primary author:
-
-Mark Lavin
-
-The following people who have contributed to django-selectable:
-
-Michael Manfre
-Luke Plant
-Augusto Men
-@dc
-Colin Copeland
-Sławomir Ehlert
-Dan Poirier
-Felipe Prenholato
-David Ray
-Rick Testore
-Karen Tracey
-Manuel Alvarez
-Ustun Ozgur
-@leo-the-manic
-Calvin Spealman
-Rebecca Lovewell
-Thomas Güttler
-Yuri Khrustalev
-@SaeX
-Tam Huynh
-Raphael Merx
-Josh Addington
-Tobias Zanke
-Petr Dlouhy
-
-Thanks for all of your work!
+++ /dev/null
-Copyright (c) 2010-2018, Mark Lavin
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice,
-this list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-this list of conditions and the following disclaimer in the documentation
-and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+++ /dev/null
-include AUTHORS.txt
-include README.rst
-include LICENSE.txt
-recursive-include selectable/locale *
-recursive-include selectable/static *
-recursive-include selectable/templates *
+++ /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
+++ /dev/null
-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
-
-.. image:: https://codecov.io/github/mlavin/django-selectable/coverage.svg?branch=master
- :target: https://codecov.io/github/mlavin/django-selectable?branch=master
-
-
-.. note::
-
- This project is looking for additional maintainers to help with Django/jQuery compatibility
- issues as well as addressing support issues/questions. If you are looking to help out
- on this project and take a look at the open
- `help-wanted <https://github.com/mlavin/django-selectable/issues?q=is%3Aissue+is%3Aopen+label%3Ahelp-wanted>`_
- or `question <https://github.com/mlavin/django-selectable/issues?q=is%3Aissue+is%3Aopen+label%3Aquestion>`_
- and see if you can contribute a fix. Be bold! If you want to take a larger role on
- the project, please reach out on the
- `mailing list <http://groups.google.com/group/django-selectable>`_. I'm happy to work
- with you to get you going on an issue.
-
-
-Features
------------------------------------
-
-- Works with the latest jQuery UI Autocomplete library
-- Auto-discovery/registration pattern for defining lookups
-
-
-Installation Requirements
------------------------------------
-
-- Python 2.7, 3.3+
-- `Django <http://www.djangoproject.com/>`_ >= 1.7, <= 1.11
-- `jQuery <http://jquery.com/>`_ >= 1.9, < 3.0
-- `jQuery UI <http://jqueryui.com/>`_ >= 1.10, < 1.12
-
-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 = [
- # Other patterns go here
- url(r'^selectable/', include('selectable.urls')),
- ]
-
-
-Documentation
------------------------------------
-
-Documentation for django-selectable is available on `Read The Docs <http://django-selectable.readthedocs.io/en/latest/>`_.
-
-
-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/>`_.
+++ /dev/null
-# Makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-PAPER =
-BUILDDIR = _build
-
-# Internal variables.
-PAPEROPT_a4 = -D latex_paper_size=a4
-PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
-
-.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
-
-help:
- @echo "Please use \`make <target>' where <target> is one of"
- @echo " html to make standalone HTML files"
- @echo " dirhtml to make HTML files named index.html in directories"
- @echo " singlehtml to make a single large HTML file"
- @echo " pickle to make pickle files"
- @echo " json to make JSON files"
- @echo " htmlhelp to make HTML files and a HTML help project"
- @echo " qthelp to make HTML files and a qthelp project"
- @echo " devhelp to make HTML files and a Devhelp project"
- @echo " epub to make an epub"
- @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
- @echo " latexpdf to make LaTeX files and run them through pdflatex"
- @echo " text to make text files"
- @echo " man to make manual pages"
- @echo " changes to make an overview of all changed/added/deprecated items"
- @echo " linkcheck to check all external links for integrity"
- @echo " doctest to run all doctests embedded in the documentation (if enabled)"
-
-clean:
- -rm -rf $(BUILDDIR)/*
-
-html:
- $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
-
-dirhtml:
- $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
- @echo
- @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
-
-singlehtml:
- $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
- @echo
- @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
-
-pickle:
- $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
- @echo
- @echo "Build finished; now you can process the pickle files."
-
-json:
- $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
- @echo
- @echo "Build finished; now you can process the JSON files."
-
-htmlhelp:
- $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
- @echo
- @echo "Build finished; now you can run HTML Help Workshop with the" \
- ".hhp project file in $(BUILDDIR)/htmlhelp."
-
-qthelp:
- $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
- @echo
- @echo "Build finished; now you can run "qcollectiongenerator" with the" \
- ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
- @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Django-Selectable.qhcp"
- @echo "To view the help file:"
- @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Django-Selectable.qhc"
-
-devhelp:
- $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
- @echo
- @echo "Build finished."
- @echo "To view the help file:"
- @echo "# mkdir -p $$HOME/.local/share/devhelp/Django-Selectable"
- @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Django-Selectable"
- @echo "# devhelp"
-
-epub:
- $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
- @echo
- @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
-
-latex:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo
- @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
- @echo "Run \`make' in that directory to run these through (pdf)latex" \
- "(use \`make latexpdf' here to do that automatically)."
-
-latexpdf:
- $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
- @echo "Running LaTeX files through pdflatex..."
- make -C $(BUILDDIR)/latex all-pdf
- @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
-
-text:
- $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
- @echo
- @echo "Build finished. The text files are in $(BUILDDIR)/text."
-
-man:
- $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
- @echo
- @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
-
-changes:
- $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
- @echo
- @echo "The overview file is in $(BUILDDIR)/changes."
-
-linkcheck:
- $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
- @echo
- @echo "Link check complete; look for any errors in the above output " \
- "or in $(BUILDDIR)/linkcheck/output.txt."
-
-doctest:
- $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
- @echo "Testing of doctests in the sources finished, look at the " \
- "results in $(BUILDDIR)/doctest/output.txt."
+++ /dev/null
-Admin Integration
-====================
-
-Overview
---------------------------------------
-
-Django-Selectables will work in the admin. To get started on integrated the
-fields and widgets in the admin make sure you are familiar with the Django
-documentation on the `ModelAdmin.form <http://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form>`_
-and `ModelForms <http://docs.djangoproject.com/en/stable/topics/forms/modelforms/>`_ particularly
-on `overriding the default widgets <http://docs.djangoproject.com/en/stable/topics/forms/modelforms/#overriding-the-default-field-types-or-widgets>`_.
-As you will see integrating django-selectable in the adminis the same as working with regular forms.
-
-
-.. _admin-jquery-include:
-
-Including jQuery & jQuery UI
---------------------------------------
-
-As noted :ref:`in the quick start guide <start-include-jquery>`, the jQuery and jQuery UI libraries
-are not included in the distribution but must be included in your templates. For the
-Django admin that means overriding
-`admin/base_site.html <https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/templates/admin/base_site.html>`_.
-You can include this media in the block name `extrahead` which is defined in
-`admin/base.html <https://code.djangoproject.com/browser/django/trunk/django/contrib/admin/templates/admin/base.html>`_.
-
- .. code-block:: html
-
- {% block extrahead %}
- {% load selectable_tags %}
- {% include_ui_theme %}
- {% include_jquery_libs %}
- {{ block.super }}
- {% endblock %}
-
-See the Django documentation on
-`overriding admin templates <https://docs.djangoproject.com/en/stable/ref/contrib/admin/#overriding-admin-templates>`_.
-See the example project for the full template example.
-
-
-.. _admin-grappelli:
-
-Using Grappelli
---------------------------------------
-
-`Grappelli <https://django-grappelli.readthedocs.org>`_ is a popular customization of the Django
-admin interface. It includes a number of interface improvements which are also built on top of
-jQuery UI. When using Grappelli you do not need to make any changes to the ``admin/base_site.html``
-template. django-selectable will detect jQuery and jQuery UI versions included by Grappelli
-and make use of them.
-
-
-.. _admin-basic-example:
-
-Basic Example
---------------------------------------
-
-For example, we may have a ``Farm`` model with a foreign key to ``auth.User`` and
-a many to many relation to our ``Fruit`` model.
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
-
- @python_2_unicode_compatible
- class Fruit(models.Model):
- name = models.CharField(max_length=200)
-
- def __str__(self):
- return self.name
-
-
- @python_2_unicode_compatible
- class Farm(models.Model):
- name = models.CharField(max_length=200)
- owner = models.ForeignKey('auth.User', related_name='farms')
- fruit = models.ManyToManyField(Fruit)
-
- def __str__(self):
- return "%s's Farm: %s" % (self.owner.username, self.name)
-
-In `admin.py` we will define the form and associate it with the `FarmAdmin`.
-
- .. code-block:: python
-
- from django.contrib import admin
- from django.contrib.auth.admin import UserAdmin
- from django.contrib.auth.models import User
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField, AutoCompleteSelectMultipleWidget
-
- from .models import Fruit, Farm
- from .lookups import FruitLookup, OwnerLookup
-
-
- class FarmAdminForm(forms.ModelForm):
- owner = AutoCompleteSelectField(lookup_class=OwnerLookup, allow_new=True)
-
- class Meta(object):
- model = Farm
- widgets = {
- 'fruit': AutoCompleteSelectMultipleWidget(lookup_class=FruitLookup),
- }
- exclude = ('owner', )
-
- def __init__(self, *args, **kwargs):
- super(FarmAdminForm, self).__init__(*args, **kwargs)
- if self.instance and self.instance.pk and self.instance.owner:
- self.initial['owner'] = self.instance.owner.pk
-
- def save(self, *args, **kwargs):
- owner = self.cleaned_data['owner']
- if owner and not owner.pk:
- owner = User.objects.create_user(username=owner.username, email='')
- self.instance.owner = owner
- return super(FarmAdminForm, self).save(*args, **kwargs)
-
-
- class FarmAdmin(admin.ModelAdmin):
- form = FarmAdminForm
-
-
- admin.site.register(Farm, FarmAdmin)
-
-
-You'll note this form also allows new users to be created and associated with the
-farm, if no user is found matching the given name. To make use of this feature we
-need to add ``owner`` to the exclude so that it will pass model validation. Unfortunately
-that means we must set the owner manual in the save and in the initial data because
-the ``ModelForm`` will no longer do this for you. Since ``fruit`` does not allow new
-items you'll see these steps are not necessary.
-
-The django-selectable widgets are compatitible with the add another popup in the
-admin. It's that little green plus sign that appears next to ``ForeignKey`` or
-``ManyToManyField`` items. This makes django-selectable a user friendly replacement
-for the `ModelAdmin.raw_id_fields <https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields>`_
-when the default select box grows too long.
-
-
-.. _admin-inline-example:
-
-Inline Example
---------------------------------------
-
-With our ``Farm`` model we can also associate the ``UserAdmin`` with a ``Farm``
-by making use of the `InlineModelAdmin
-<http://docs.djangoproject.com/en/stable/ref/contrib/admin/#inlinemodeladmin-objects>`_.
-We can even make use of the same ``FarmAdminForm``.
-
- .. code-block:: python
-
- # continued from above
-
- class FarmInline(admin.TabularInline):
- model = Farm
- form = FarmAdminForm
-
-
- class NewUserAdmin(UserAdmin):
- inlines = [
- FarmInline,
- ]
-
-
- admin.site.unregister(User)
- admin.site.register(User, NewUserAdmin)
-
-The auto-complete functions will be bound as new forms are added dynamically.
-
+++ /dev/null
-Advanced Usage
-==========================
-
-We've gone through the most command and simple use cases for django-selectable. Now
-we'll take a look at some of the more advanced features of this project. This assumes
-that you are comfortable reading and writing a little bit of Javascript making
-use of jQuery.
-
-
-.. _additional-parameters:
-
-Additional Parameters
---------------------------------------
-
-The basic lookup is based on handling a search based on a single term string.
-If additional filtering is needed it can be inside the lookup ``get_query`` but
-you would need to define this when the lookup is defined. While this fits a fair
-number of use cases there are times when you need to define additional query
-parameters that won't be known until either the form is bound or until selections
-are made on the client side. This section will detail how to handle both of these
-cases.
-
-
-How Parameters are Passed
-_______________________________________
-
-As with the search term, the additional parameters you define will be passed in
-``request.GET``. Since ``get_query`` gets the current request, you will have access to
-them. Since they can be manipulated on the client side, these parameters should be
-treated like all user input. It should be properly validated and sanitized.
-
-
-Limiting the Result Set
-_______________________________________
-
-The number of results are globally limited/paginated by the :ref:`SELECTABLE_MAX_LIMIT`
-but you can also lower this limit on the field or widget level. Each field and widget
-takes a ``limit`` argument in the ``__init__`` that will be passed back to the lookup
-through the ``limit`` query parameter. The result set will be automatically paginated
-for you if you use either this parameter or the global setting.
-
-
-.. _server-side-parameters:
-
-Adding Parameters on the Server Side
-_______________________________________
-
-Each of the widgets define ``update_query_parameters`` which takes a dictionary. The
-most common way to use this would be in the form ``__init__``.
-
- .. code-block:: python
-
- class FruitForm(forms.Form):
- autocomplete = forms.CharField(
- label='Type the name of a fruit (AutoCompleteWidget)',
- widget=selectable.AutoCompleteWidget(FruitLookup),
- required=False,
- )
-
- def __init__(self, *args, **kwargs):
- super(FruitForm, self).__init__(*args, **kwargs)
- self.fields['autocomplete'].widget.update_query_parameters({'foo': 'bar'})
-
-You can also pass the query parameters into the widget using the ``query_params``
-keyword argument. It depends on your use case as to whether the parameters are
-known when the form is defined or when an instance of the form is created.
-
-
-.. _client-side-parameters:
-
-Adding Parameters on the Client Side
-_______________________________________
-
-There are times where you want to filter the result set based other selections
-by the user such as a filtering cities by a previously selected state. In this
-case you will need to bind a ``prepareQuery`` to the field. This function should accept the query dictionary.
-You are free to make adjustments to the query dictionary as needed.
-
- .. code-block:: html
-
- <script type="text/javascript">
- function newParameters(query) {
- query.foo = 'bar';
- }
-
- $(document).ready(function() {
- $('#id_autocomplete').djselectable('option', 'prepareQuery', newParameters);
- });
- </script>
-
-.. note::
-
- In v0.7 the scope of ``prepareQuery`` was updated so that ``this`` refers to the
- current ``djselectable`` plugin instance. Previously ``this`` refered to the
- plugin ``options`` instance.
-
-
-.. _chain-select-example:
-
-Chained Selection
---------------------------------------
-
-It's a fairly common pattern to have two or more inputs depend one another such City/State/Zip.
-In fact there are other Django apps dedicated to this purpose such as
-`django-smart-selects <https://github.com/digi604/django-smart-selects>`_ or
-`django-ajax-filtered-fields <http://code.google.com/p/django-ajax-filtered-fields/>`_.
-It's possible to handle this kind of selection with django-selectable if you are willing
-to write a little javascript.
-
-Suppose we have city model
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
- from localflavor.us.models import USStateField
-
-
- @python_2_unicode_compatible
- class City(models.Model):
- name = models.CharField(max_length=200)
- state = USStateField()
-
- def __str__(self):
- return self.name
-
-Then in our lookup we will grab the state value and filter our results on it:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import City
-
-
- class CityLookup(ModelLookup):
- model = City
- search_fields = ('name__icontains', )
-
- def get_query(self, request, term):
- results = super(CityLookup, self).get_query(request, term)
- state = request.GET.get('state', '')
- if state:
- results = results.filter(state=state)
- return results
-
- def get_item_label(self, item):
- return "%s, %s" % (item.name, item.state)
-
-
- registry.register(CityLookup)
-
-and a simple form
-
- .. code-block:: python
-
- from django import forms
-
- from localflavor.us.forms import USStateField, USStateSelect
-
- from selectable.forms import AutoCompleteSelectField, AutoComboboxSelectWidget
-
- from .lookups import CityLookup
-
-
- class ChainedForm(forms.Form):
- city = AutoCompleteSelectField(
- lookup_class=CityLookup,
- label='City',
- required=False,
- widget=AutoComboboxSelectWidget
- )
- state = USStateField(widget=USStateSelect, required=False)
-
-
-We want our users to select a city and if they choose a state then we will only
-show them cities in that state. To do this we will pass back chosen state as
-addition parameter with the following javascript:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- function newParameters(query) {
- query.state = $('#id_state').val();
- }
- $('#id_city_0').djselectable('option', 'prepareQuery', newParameters);
- });
- </script>
-
-And that's it! We now have a working chained selection example. The full source
-is included in the example project.
-
-.. _client-side-changes:
-
-Detecting Client Side Changes
-____________________________________________
-
-The previous example detected selection changes on the client side to allow passing
-parameters to the lookup. Since django-selectable is built on top of the jQuery UI
-`Autocomplete plug-in <http://jqueryui.com/demos/autocomplete/>`_, the widgets
-expose the events defined by the plugin.
-
- - djselectablecreate
- - djselectablesearch
- - djselectableopen
- - djselectablefocus
- - djselectableselect
- - djselectableclose
- - djselectablechange
-
-For the most part these event names should be self-explanatory. If you need additional
-detail you should refer to the `jQuery UI docs on these events <http://jqueryui.com/demos/autocomplete/#events>`_.
-
-The multiple select widgets include additional events which indicate when a new item is added
-or removed from the current list. These events are ``djselectableadd`` and ``djselectableremove``.
-These events pass a dictionary of data with the following keys
-
- - element: The original text input
- - input: The hidden input to be added for the new item
- - wrapper: The ``<li>`` element to be added to the deck
- - deck: The outer ``<ul>`` deck element
-
-You can use these events to prevent items from being added or removed from the deck by
-returning ``false`` in the handling function. A simple example is given below:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $(':input[name=my_field_0]').bind('djselectableadd', function(event, item) {
- // Don't allow foo to be added
- if ($(item.input).val() === 'foo') {
- return false;
- }
- });
- });
- </script>
-
-
-Submit On Selection
---------------------------------------
-
-You might want to help your users by submitting the form once they have selected a valid
-item. To do this you simply need to listen for the ``djselectableselect`` event. This
-event is fired by the text input which has an index of 0. If your field is named ``my_field``
-then input to watch would be ``my_field_0`` such as:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $(':input[name=my_field_0]').bind('djselectableselect', function(event, ui) {
- $(this).parents("form").submit();
- });
- });
- </script>
-
-
-Dynamically Added Forms
---------------------------------------
-
-django-selectable can work with dynamically added forms such as inlines in the admin.
-To make django-selectable work in the admin there is nothing more to do than include
-the necessary static media as described in the
-:ref:`Admin Integration <admin-jquery-include>` section.
-
-If you are making use of the popular `django-dynamic-formset <http://code.google.com/p/django-dynamic-formset/>`_
-then you can make django-selectable work by passing ``bindSelectables`` to the
-`added <http://code.google.com/p/django-dynamic-formset/source/browse/trunk/docs/usage.txt#259>`_ option:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- $('#my-formset').formset({
- added: bindSelectables
- });
- });
- </script>
-
-Currently you must include the django-selectable javascript below this formset initialization
-code for this to work. See django-selectable `issue #31 <https://github.com/mlavin/django-selectable/issues/31>`_
-for some additional detail on this problem.
-
-
-.. _advanced-label-formats:
-
-Label Formats on the Client Side
---------------------------------------
-
-The lookup label is the text which is shown in the list before it is selected.
-You can use the :ref:`get_item_label <lookup-get-item-label>` method in your lookup
-to do this on the server side. This works for most applications. However if you don't
-want to write your HTML in Python or need to adapt the format on the client side you
-can use the :ref:`formatLabel <javascript-formatLabel>` option.
-
-``formatLabel`` takes two paramaters the current label and the current selected item.
-The item is a dictionary object matching what is returned by the lookup's
-:ref:`format_item <lookup-format-item>`. ``formatLabel`` should return the string
-which should be used for the label.
-
-Going back to the ``CityLookup`` we can adjust the label to wrap the city and state
-portions with their own classes for additional styling:
-
- .. code-block:: html
-
- <script type="text/javascript">
- $(document).ready(function() {
- function formatLabel(label, item) {
- var data = label.split(',');
- return '<span class="city">' + data[0] + '</span>, <span class="state">' + data[1] + '</span>';
- }
- $('#id_city_0').djselectable('option', 'formatLabel', formatLabel);
- });
- </script>
-
-This is a rather simple example but you could also pass additional information in ``format_item``
-such as a flag of whether the city is the capital and render the state captials differently.
-
-.. _advanced-bootstrap:
-
-Using with Twitter Bootstrap
---------------------------------------
-
-django-selectable can work along side with Twitter Bootstrap but there are a few things to
-take into consideration. Both jQuery UI and Bootstrap define a ``$.button`` plugin. This
-plugin is used by default by django-selectable and expects the UI version. If the jQuery UI
-JS is included after the Bootstrap JS then this will work just fine but the Bootstrap
-button JS will not be available. This is the strategy taken by the `jQuery UI Bootstrap
-<http://addyosmani.github.com/jquery-ui-bootstrap/>`_ theme.
-
-Another option is to rename the Bootstrap plugin using the ``noConflict`` option.
-
- .. code-block:: html
-
- <!-- Include Bootstrap JS -->
- <script>$.fn.bootstrapBtn = $.fn.button.noConflict();</script>
- <!-- Include jQuery UI JS -->
-
-Even with this some might complain that it's too resource heavy to include all of
-jQuery UI when you just want the autocomplete to work with django-selectable. For
-this you can use the `Download Builder <http://jqueryui.com/download/>`_ to build
-a minimal set of jQuery UI widgets. django-selectable requires the UI core, autocomplete,
-menu and button widgets. None of the effects or interactions are needed. Minified
-this totals around 100 kb of JS, CSS and images (based on jQuery UI 1.10).
-
-.. note::
-
- For a comparison this is smaller than the minified Bootstrap 2.3.0 CSS
- which is 105 kb not including the responsive CSS or the icon graphics.
-
-It is possible to remove the dependency on the UI button plugin and instead
-use the Bootstrap button styles. This is done by overriding
-the ``_comboButtonTemplate`` and ``_removeButtonTemplate`` functions used to
-create the buttons. An example is given below.
-
- .. code-block:: html
-
- <script>
- $.ui.djselectable.prototype._comboButtonTemplate = function (input) {
- var icon = $("<i>").addClass("icon-chevron-down");
- // Remove current classes on the text input
- $(input).attr("class", "");
- // Wrap with input-append
- $(input).wrap('<div class="input-append" />');
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-small");
- };
- $.ui.djselectable.prototype._removeButtonTemplate = function (item) {
- var icon = $("<i>").addClass("icon-remove-sign");
- // Return button link with the chosen icon
- return $("<a>").append(icon).addClass("btn btn-small pull-right");
- };
- </script>
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# Django-Selectable documentation build configuration file, created by
-# sphinx-quickstart on Sat Mar 12 14:14:16 2011.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-
-import datetime
-import sys, os
-import selectable
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
-
-# -- General configuration -----------------------------------------------------
-
-# If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
-
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = []
-
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
-
-# The suffix of source filenames.
-source_suffix = '.rst'
-
-# The encoding of source files.
-#source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = 'index'
-
-# General information about the project.
-project = u'Django-Selectable'
-copyright = u'2011-%s, Mark Lavin' % datetime.date.today().year
-
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-version = '.'.join(selectable.__version__.split('.')[0:2])
-# The full version, including alpha/beta/rc tags.
-release = selectable.__version__
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-#language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-#today = ''
-# Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
-exclude_patterns = ['_build']
-
-# The reST default role (used for this markup: `text`) to use for all documents.
-#default_role = None
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-#add_module_names = True
-
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-#show_authors = False
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
-
-# A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages. See the documentation for
-# a list of builtin themes.
-html_theme = 'default'
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further. For a list of options available for each theme, see the
-# documentation.
-#html_theme_options = {}
-
-# Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
-
-# The name for this set of Sphinx documents. If None, it defaults to
-# "<project> v<release> documentation".
-#html_title = None
-
-# A shorter title for the navigation bar. Default is the same as html_title.
-#html_short_title = None
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-#html_logo = None
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
-#html_favicon = None
-
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-#html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-#html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-#html_additional_pages = {}
-
-# If false, no module index is generated.
-#html_domain_indices = True
-
-# If false, no index is generated.
-#html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-#html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
-
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
-
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
-
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it. The value of this option must be the
-# base URL from which the finished HTML is served.
-#html_use_opensearch = ''
-
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = 'Django-Selectabledoc'
-
-
-# -- Options for LaTeX output --------------------------------------------------
-
-# The paper size ('letter' or 'a4').
-#latex_paper_size = 'letter'
-
-# The font size ('10pt', '11pt' or '12pt').
-#latex_font_size = '10pt'
-
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
-latex_documents = [
- ('index', 'Django-Selectable.tex', u'Django-Selectable Documentation',
- u'Mark Lavin', 'manual'),
-]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-#latex_logo = None
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-#latex_use_parts = False
-
-# If true, show page references after internal links.
-#latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-#latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-#latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-#latex_appendices = []
-
-# If false, no module index is generated.
-#latex_domain_indices = True
-
-
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [
- ('index', 'django-selectable', u'Django-Selectable Documentation',
- [u'Mark Lavin'], 1)
-]
+++ /dev/null
-.. _contributing-guide:
-
-Contributing
-==================
-
-There are plenty of ways to contribute to this project. If you think you've found
-a bug please submit an issue. If there is a feature you'd like to see then please
-open an ticket proposal for it. If you've come up with some helpful examples then
-you can add to our example project.
-
-
-Getting the Source
---------------------------------------
-
-The source code is hosted on `Github <https://github.com/mlavin/django-selectable>`_.
-You can download the full source by cloning the git repo::
-
- git clone git://github.com/mlavin/django-selectable.git
-
-Feel free to fork the project and make your own changes. If you think that it would
-be helpful for other then please submit a pull request to have it merged in.
-
-
-Submit an Issue
---------------------------------------
-
-The issues are also managed on `Github issue page <https://github.com/mlavin/django-selectable/issues>`_.
-If you think you've found a bug it's helpful if you indicate the version of django-selectable
-you are using the ticket version flag. If you think your bug is javascript related it is
-also helpful to know the version of jQuery, jQuery UI, and the browser you are using.
-
-Issues are also used to track new features. If you have a feature you would like to see
-you can submit a proposal ticket. You can also see features which are planned here.
-
-
-Submit a Translation
---------------------------------------
-
-We are working towards translating django-selectable into different languages. There
-are not many strings to be translated so it is a reasonably easy task and a great way
-to be involved with the project. The translations are managed through
-`Transifex <https://www.transifex.com/projects/p/django-selectable/>`_.
-
-Running the Test Suite
---------------------------------------
-
-There are a number of tests in place to test the server side code for this
-project. To run the tests you need Django and `mock <http://www.voidspace.org.uk/python/mock/>`_
-installed and run::
-
- python runtests.py
-
-`tox <http://tox.readthedocs.org/en/latest/index.html>`_ is used to test django-selectable
-against multiple versions of Django/Python. With tox installed you can run::
-
- tox
-
-to run all the version combinations. You can also run tox against a subset of supported
-environments::
-
- tox -e py27-django15
-
-For more information on running/installing tox please see the
-tox documentation: http://tox.readthedocs.org/en/latest/index.html
-
-Client side tests are written using `QUnit <http://docs.jquery.com/QUnit>`_. They
-can be found in ``selectable/tests/qunit/index.html``. The test suite also uses
-`PhantomJS <http://phantomjs.org/>`_ to
-run the tests. You can install PhantomJS from NPM::
-
- # Install requirements
- npm install -g phantomjs jshint
- make test-js
-
-
-Building the Documentation
---------------------------------------
-
-The documentation is built using `Sphinx <http://sphinx.pocoo.org/>`_
-and available on `Read the Docs <http://django-selectable.readthedocs.io/>`_. With
-Sphinx installed you can build the documentation by running::
-
- make html
-
-inside the docs directory. Documentation fixes and improvements are always welcome.
-
+++ /dev/null
-Fields
-==========
-
-Django-Selectable defines a number of fields for selecting either single or multiple
-lookup items. Item in this context corresponds to the object return by the underlying
-lookup ``get_item``. The single select select field :ref:`AutoCompleteSelectField`
-allows for the creation of new items. To use this feature the field's
-lookup class must define ``create_item``. In the case of lookups extending from
-:ref:`ModelLookup` newly created items have not yet been saved into the database and saving
-should be handled by the form. All fields take the lookup class as the first required
-argument.
-
-
-.. _AutoCompleteSelectField:
-
-AutoCompleteSelectField
---------------------------------------
-
-Field tied to :ref:`AutoCompleteSelectWidget` to bind the selection to the form and
-create new items, if allowed. The ``allow_new`` keyword argument (default: ``False``)
-which determines if the field allows new items. This field cleans to a single item.
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField
-
- from .lookups import FruitLookup
-
-
- class FruitSelectionForm(forms.Form):
- fruit = AutoCompleteSelectField(lookup_class=FruitLookup, label='Select a fruit')
-
-`lookup_class`` may also be a dotted path.
-
-
-.. _AutoCompleteSelectMultipleField:
-
-AutoCompleteSelectMultipleField
---------------------------------------
-
-Field tied to :ref:`AutoCompleteSelectMultipleWidget` to bind the selection to the form.
-This field cleans to a list of items. :ref:`AutoCompleteSelectMultipleField` does not
-allow for the creation of new items.
-
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectMultipleField
-
- from .lookups import FruitLookup
-
-
- class FruitsSelectionForm(forms.Form):
- fruits = AutoCompleteSelectMultipleField(lookup_class=FruitLookup,
- label='Select your favorite fruits')
+++ /dev/null
-.. include:: ../README.rst
-
-Contents:
-
-.. toctree::
- :maxdepth: 2
-
- overview
- quick-start
- lookups
- advanced
- admin
- testing
- fields
- widgets
- settings
- contribute
- releases
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
+++ /dev/null
-Defining Lookups
-==================
-
-What are Lookups?
---------------------------------------
-
-Lookups define the corresponding ajax views used by the auto-completion
-fields and widgets. They take in the current request and return the JSON
-needed by the jQuery auto-complete plugin.
-
-
-Defining a Lookup
---------------------------------------
-
-django-selectable uses a registration pattern similar to the Django admin.
-Lookups should be defined in a `lookups.py` in your application's module. Once defined
-you must register in with django-selectable. All lookups must extend from
-``selectable.base.LookupBase`` which defines the API for every lookup.
-
- .. code-block:: python
-
- from selectable.base import LookupBase
- from selectable.registry import registry
-
- class MyLookup(LookupBase):
- def get_query(self, request, term):
- data = ['Foo', 'Bar']
- return [x for x in data if x.startswith(term)]
-
- registry.register(MyLookup)
-
-
-Lookup API
---------------------------------------
-
-.. py:method:: LookupBase.get_query(request, term)
-
- This is the main method which takes the current request
- from the user and returns the data which matches their search.
-
- :param request: The current request object.
- :param term: The search term from the widget input.
- :return: An iterable set of data of items matching the search term.
-
-.. _lookup-get-item-label:
-
-.. py:method:: LookupBase.get_item_label(item)
-
- This is first of three formatting methods. The label is shown in the
- drop down menu of search results. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be shown in the search results.
- The label can include HTML. For changing the label format on the client side
- see :ref:`Advanced Label Formats <advanced-label-formats>`.
-
-
-.. py:method:: LookupBase.get_item_id(item)
-
- This is second of three formatting methods. The id is the value that will eventually
- be returned by the field/widget. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be returned by the field/widget.
-
-.. py:method:: LookupBase.get_item_value(item)
-
- This is last of three formatting methods. The value is shown in the
- input once the item has been selected. This defaults to ``item.__unicode__``.
-
- :param item: An item from the search results.
- :return: A string representation of the item to be shown in the input.
-
-.. py:method:: LookupBase.get_item(value)
-
- ``get_item`` is the reverse of ``get_item_id``. This should take the value
- from the form initial values and return the current item. This defaults
- to simply return the value.
-
- :param value: Value from the form inital value.
- :return: The item corresponding to the initial value.
-
-.. py:method:: LookupBase.create_item(value)
-
- If you plan to use a lookup with a field or widget which allows the user
- to input new values then you must define what it means to create a new item
- for your lookup. By default this raises a ``NotImplemented`` error.
-
- :param value: The user given value.
- :return: The new item created from the item.
-
-.. _lookup-format-item:
-
-.. py:method:: LookupBase.format_item(item)
-
- By default ``format_item`` creates a dictionary with the three keys used by
- the UI plugin: id, value, label. These are generated from the calls to
- ``get_item_id``, ``get_item_value`` and ``get_item_label``. If you want to
- add additional keys you should add them here.
-
- The results of ``get_item_label`` is conditionally escaped to prevent
- Cross Site Scripting (XSS) similar to the templating language.
- If you know that the content is safe and you want to use these methods
- to include HTML should mark the content as safe with ``django.utils.safestring.mark_safe``
- inside the ``get_item_label`` method.
-
- ``get_item_id`` and ``get_item_value`` are not escapted by default. These are
- not a XSS vector with the built-in JS. If you are doing additional formating using
- these values you should be conscience of this fake and be sure to escape these
- values.
-
- :param item: An item from the search results.
- :return: A dictionary of information for this item to be sent back to the client.
-
-There are also some additional methods that you could want to use/override. These
-are for more advanced use cases such as using the lookups with JS libraries other
-than jQuery UI. Most users will not need to override these methods.
-
-.. _lookup-format-results:
-
-.. py:method:: LookupBase.format_results(self, raw_data, options)
-
- Returns a python structure that later gets serialized. This makes a call to
- :ref:`paginate_results<lookup-paginate-results>` prior to calling
- :ref:`format_item<lookup-format-item>` on each item in the current page.
-
- :param raw_data: The set of all matched results.
- :param options: Dictionary of ``cleaned_data`` from the lookup form class.
- :return: A dictionary with two keys ``meta`` and ``data``.
- The value of ``data`` is an iterable extracted from page_data.
- The value of ``meta`` is a dictionary. This is a copy of options with one additional element
- ``more`` which is a translatable "Show more" string
- (useful for indicating more results on the javascript side).
-
-.. _lookup-paginate-results:
-
-.. py:method:: LookupBase.paginate_results(results, options)
-
- If :ref:`SELECTABLE_MAX_LIMIT` is defined or ``limit`` is passed in request.GET
- then ``paginate_results`` will return the current page using Django's
- built in pagination. See the Django docs on
- `pagination <https://docs.djangoproject.com/en/stable/topics/pagination/>`_
- for more info.
-
- :param results: The set of all matched results.
- :param options: Dictionary of ``cleaned_data`` from the lookup form class.
- :return: The current `Page object <https://docs.djangoproject.com/en/stable/topics/pagination/#page-objects>`_
- of results.
-
-
-.. _ModelLookup:
-
-Lookups Based on Models
---------------------------------------
-
-Perhaps the most common use case is to define a lookup based on a given Django model.
-For this you can extend ``selectable.base.ModelLookup``. To extend ``ModelLookup`` you
-should set two class attributes: ``model`` and ``search_fields``.
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Fruit
-
-
- class FruitLookup(ModelLookup):
- model = Fruit
- search_fields = ('name__icontains', )
-
- registry.register(FruitLookup)
-
-The syntax for ``search_fields`` is the same as the Django
-`field lookup syntax <http://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups>`_.
-Each of these lookups are combined as OR so any one of them matching will return a
-result. You may optionally define a third class attribute ``filters`` which is a dictionary of
-filters to be applied to the model queryset. The keys should be a string defining a field lookup
-and the value should be the value for the field lookup. Filters on the other hand are
-combined with AND.
-
-
-User Lookup Example
---------------------------------------
-
-Below is a larger model lookup example using multiple search fields, filters
-and display options for the `auth.User <https://docs.djangoproject.com/en/stable/topics/auth/#users>`_
-model.
-
- .. code-block:: python
-
- from django.contrib.auth.models import User
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
-
- class UserLookup(ModelLookup):
- model = User
- search_fields = (
- 'username__icontains',
- 'first_name__icontains',
- 'last_name__icontains',
- )
- filters = {'is_active': True, }
-
- def get_item_value(self, item):
- # Display for currently selected item
- return item.username
-
- def get_item_label(self, item):
- # Display for choice listings
- return u"%s (%s)" % (item.username, item.get_full_name())
-
- registry.register(UserLookup)
-
-
-.. _lookup-decorators:
-
-Lookup Decorators
---------------------------------------
-
-Registering lookups with django-selectable creates a small API for searching the
-lookup data. While the amount of visible data is small there are times when you want
-to restrict the set of requests which can view the data. For this purpose there are
-lookup decorators. To use them you simply decorate your lookup class.
-
- .. code-block:: python
-
- from django.contrib.auth.models import User
- from selectable.base import ModelLookup
- from selectable.decorators import login_required
- from selectable.registry import registry
-
-
- @login_required
- class UserLookup(ModelLookup):
- model = User
- search_fields = ('username__icontains', )
- filters = {'is_active': True, }
-
- registry.register(UserLookup)
-
-.. note::
-
- The class decorator syntax was introduced in Python 2.6. If you are using
- django-selectable with Python 2.5 you can still make use of these decorators
- by applying the without the decorator syntax.
-
- .. code-block:: python
-
- class UserLookup(ModelLookup):
- model = User
- search_fields = ('username__icontains', )
- filters = {'is_active': True, }
-
- UserLookup = login_required(UserLookup)
-
- registry.register(UserLookup)
-
-Below are the descriptions of the available lookup decorators.
-
-
-ajax_required
-______________________________________
-
-The django-selectable javascript will always request the lookup data via
-XMLHttpRequest (AJAX) request. This decorator enforces that the lookup can only
-be accessed in this way. If the request is not an AJAX request then it will return
-a 400 Bad Request response.
-
-
-login_required
-______________________________________
-
-This decorator requires the user to be authenticated via ``request.user.is_authenticated``.
-If the user is not authenticated this will return a 401 Unauthorized response.
-``request.user`` is set by the ``django.contrib.auth.middleware.AuthenticationMiddleware``
-which is required for this decorator to work. This middleware is enabled by default.
-
-staff_member_required
-______________________________________
-
-This decorator builds from ``login_required`` and in addition requires that
-``request.user.is_staff`` is ``True``. If the user is not authenticatated this will
-continue to return at 401 response. If the user is authenticated but not a staff member
-then this will return a 403 Forbidden response.
+++ /dev/null
-@ECHO OFF
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set BUILDDIR=_build
-set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
-if NOT "%PAPER%" == "" (
- set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
-)
-
-if "%1" == "" goto help
-
-if "%1" == "help" (
- :help
- echo.Please use `make ^<target^>` where ^<target^> is one of
- echo. html to make standalone HTML files
- echo. dirhtml to make HTML files named index.html in directories
- echo. singlehtml to make a single large HTML file
- echo. pickle to make pickle files
- echo. json to make JSON files
- echo. htmlhelp to make HTML files and a HTML help project
- echo. qthelp to make HTML files and a qthelp project
- echo. devhelp to make HTML files and a Devhelp project
- echo. epub to make an epub
- echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
- echo. text to make text files
- echo. man to make manual pages
- echo. changes to make an overview over all changed/added/deprecated items
- echo. linkcheck to check all external links for integrity
- echo. doctest to run all doctests embedded in the documentation if enabled
- goto end
-)
-
-if "%1" == "clean" (
- for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
- del /q /s %BUILDDIR%\*
- goto end
-)
-
-if "%1" == "html" (
- %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/html.
- goto end
-)
-
-if "%1" == "dirhtml" (
- %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
- goto end
-)
-
-if "%1" == "singlehtml" (
- %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
- goto end
-)
-
-if "%1" == "pickle" (
- %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the pickle files.
- goto end
-)
-
-if "%1" == "json" (
- %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can process the JSON files.
- goto end
-)
-
-if "%1" == "htmlhelp" (
- %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run HTML Help Workshop with the ^
-.hhp project file in %BUILDDIR%/htmlhelp.
- goto end
-)
-
-if "%1" == "qthelp" (
- %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; now you can run "qcollectiongenerator" with the ^
-.qhcp project file in %BUILDDIR%/qthelp, like this:
- echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Django-Selectable.qhcp
- echo.To view the help file:
- echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Django-Selectable.ghc
- goto end
-)
-
-if "%1" == "devhelp" (
- %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished.
- goto end
-)
-
-if "%1" == "epub" (
- %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The epub file is in %BUILDDIR%/epub.
- goto end
-)
-
-if "%1" == "latex" (
- %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
- goto end
-)
-
-if "%1" == "text" (
- %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The text files are in %BUILDDIR%/text.
- goto end
-)
-
-if "%1" == "man" (
- %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
- if errorlevel 1 exit /b 1
- echo.
- echo.Build finished. The manual pages are in %BUILDDIR%/man.
- goto end
-)
-
-if "%1" == "changes" (
- %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
- if errorlevel 1 exit /b 1
- echo.
- echo.The overview file is in %BUILDDIR%/changes.
- goto end
-)
-
-if "%1" == "linkcheck" (
- %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
- if errorlevel 1 exit /b 1
- echo.
- echo.Link check complete; look for any errors in the above output ^
-or in %BUILDDIR%/linkcheck/output.txt.
- goto end
-)
-
-if "%1" == "doctest" (
- %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
- if errorlevel 1 exit /b 1
- echo.
- echo.Testing of doctests in the sources finished, look at the ^
-results in %BUILDDIR%/doctest/output.txt.
- goto end
-)
-
-:end
+++ /dev/null
-Overview
-==================
-
-Motivation
---------------------------------------
-
-There are many Django apps related to auto-completion why create another? One problem
-was varying support for the `jQuery UI auto-complete plugin <http://jqueryui.com/demos/autocomplete/>`_
-versus the now deprecated `bassistance version <http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/>`_.
-Another was support for combo-boxes and multiple selects. And lastly was a simple syntax for
-defining the related backend views for the auto-completion.
-
-This library aims to meet all of these goals:
- - Built on jQuery UI auto-complete
- - Fields and widgets for a variety of use-cases:
- - Text inputs and combo-boxes
- - Text selection
- - Value/ID/Foreign key selection
- - Multiple object selection
- - Allowing new values
- - Simple and extendable syntax for defining backend views
-
-
-Related Projects
---------------------------------------
-
-Much of the work here was inspired by things that I like (and things I don't like) about
-`django-ajax-selects <http://code.google.com/p/django-ajax-selects/>`_. To see some of the
-other Django apps for handling auto-completion see `Django-Packages <http://djangopackages.com/grids/g/auto-complete/>`_.
+++ /dev/null
-Getting Started
-==================
-
-The workflow for using `django-selectable` involves two main parts:
- - Defining your lookups
- - Defining your forms
-
-This guide assumes that you have a basic knowledge of creating Django models and
-forms. If not you should first read through the documentation on
-`defining models <http://docs.djangoproject.com/en/stable/topics/db/models/>`_
-and `using forms <http://docs.djangoproject.com/en/stable/topics/forms/>`_.
-
-.. _start-include-jquery:
-
-Including jQuery & jQuery UI
---------------------------------------
-
-The widgets in django-selectable define the media they need as described in the
-Django documentation on `Form Media <https://docs.djangoproject.com/en/stable/topics/forms/media/>`_.
-That means to include the javascript and css you need to make the widgets work you
-can include ``{{ form.media.css }}`` and ``{{ form.media.js }}`` in your template. This is
-assuming your form is called `form` in the template context. For more information
-please check out the `Django documentation <https://docs.djangoproject.com/en/stable/topics/forms/media/>`_.
-
-The jQuery and jQuery UI libraries are not included in the distribution but must be included
-in your templates. However there is a template tag to easily add these libraries from
-the from the `Google CDN <http://code.google.com/apis/libraries/devguide.html#jquery>`_.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_jquery_libs %}
-
-By default these will use jQuery v1.11.2 and jQuery UI v1.11.3. You can customize the versions
-used by pass them to the tag. The first version is the jQuery version and the second is the
-jQuery UI version.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_jquery_libs '1.11.2' '1.11.3' %}
-
-Django-Selectable should work with `jQuery <http://jquery.com/>`_ >= 1.9 and
-`jQuery UI <http://jqueryui.com/>`_ >= 1.10.
-
-You must also include a `jQuery UI theme <http://jqueryui.com/themeroller/>`_ stylesheet. There
-is also a template tag to easily add this style sheet from the Google CDN.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme %}
-
-By default this will use the `base <http://jqueryui.com/themeroller/>`_ theme for jQuery UI v1.11.4.
-You can configure the theme and version by passing them in the tag.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme 'ui-lightness' '1.11.4' %}
-
-Or only change the theme.
-
- .. code-block:: html
-
- {% load selectable_tags %}
- {% include_ui_theme 'ui-lightness' %}
-
-See the the jQuery UI documentation for a full list of available stable themes: http://jqueryui.com/download#stable-themes
-
-Of course you can choose to include these rescources manually::
-
- .. code-block:: html
-
- <link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/base/jquery-ui.css" type="text/css">
- <link href="{% static 'selectable/css/dj.selectable.css' %}" type="text/css" media="all" rel="stylesheet">
- <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
- <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.js"></script>
- <script type="text/javascript" src="{% static 'selectable/js/jquery.dj.selectable.js' %}"></script>
-
-.. note::
-
- jQuery UI shares a few plugin names with the popular Twitter Bootstrap framework. There
- are notes on using Bootstrap along with django-selectable in the :ref:`advanced usage
- section <advanced-bootstrap>`.
-
-
-Defining a Lookup
---------------------------------
-
-The lookup classes define the backend views. The most common case is defining a
-lookup which searchs models based on a particular field. Let's define a simple model:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from django.db import models
- from django.utils.encoding import python_2_unicode_compatible
-
-
- @python_2_unicode_compatible
- class Fruit(models.Model):
- name = models.CharField(max_length=200)
-
- def __str__(self):
- return self.name
-
-In a `lookups.py` we will define our lookup:
-
- .. code-block:: python
-
- from __future__ import unicode_literals
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Fruit
-
-
- class FruitLookup(ModelLookup):
- model = Fruit
- search_fields = ('name__icontains', )
-
-
-This lookups extends ``selectable.base.ModelLookup`` and defines two things: one is
-the model on which we will be searching and the other is the field which we are searching.
-This syntax should look familiar as it is the same as the `field lookup syntax <http://docs.djangoproject.com/en/stable/ref/models/querysets/#field-lookups>`_
-for making queries in Django.
-
-Below this definition we will register our lookup class.
-
- .. code-block:: python
-
- registry.register(FruitLookup)
-
-.. note::
-
- You should only register your lookup once. Attempting to register the same lookup class
- more than once will lead to ``LookupAlreadyRegistered`` errors. A common problem related to the
- ``LookupAlreadyRegistered`` error is related to inconsistant import paths in your project.
- Prior to Django 1.4 the default ``manage.py`` allows for importing both with and without
- the project name (i.e. ``from myproject.myapp import lookups`` or ``from myapp import lookups``).
- This leads to the ``lookup.py`` file being imported twice and the registration code
- executing twice. Thankfully this is no longer the default in Django 1.4. Keeping
- your import consistant to include the project name (when your app is included inside the
- project directory) will avoid these errors.
-
-
-Defining Forms
---------------------------------
-
-Now that we have a working lookup we will define a form which uses it:
-
- .. code-block:: python
-
- from django import forms
-
- from selectable.forms import AutoCompleteWidget
-
- from .lookups import FruitLookup
-
-
- class FruitForm(forms.Form):
- autocomplete = forms.CharField(
- label='Type the name of a fruit (AutoCompleteWidget)',
- widget=AutoCompleteWidget(FruitLookup),
- required=False,
- )
-
-
-This replaces the default widget for the ``CharField`` with the ``AutoCompleteWidget``.
-This will allow the user to fill this field with values taken from the names of
-existing ``Fruit`` models.
-
-And that's pretty much it. Keep on reading if you want to learn about the other
-types of fields and widgets that are available as well as defining more complicated
-lookups.
+++ /dev/null
-Release Notes
-==================
-
-
-v1.1.0 (Released 2018-01-12)
---------------------------------------
-
-- Updated admin docs.
-- Added support for Django 1.11
-
-Special thanks to Luke Plant for contributing the fixes to support Django 1.11.
-
-
-v1.0.0 (Released 2017-04-14)
---------------------------------------
-
-This project has been stable for quite some time and finally declaring a 1.0 release. With
-that comes new policies on official supported versions for Django, Python, jQuery, and jQuery UI.
-
-- New translations for German and Czech.
-- Various bug and compatibility fixes.
-- Updated example project.
-
-Special thanks to Raphael Merx for helping track down issues related to this release
-and an updating the example project to work on Django 1.10.
-
-Backwards Incompatible Changes
-________________________________
-
-- Dropped support Python 2.6 and 3.2
-- Dropped support for Django < 1.7. Django 1.11 is not yet supported.
-- ``LookupBase.serialize_results`` had been removed. This is now handled by the built-in ``JsonResponse`` in Django.
-- jQuery and jQuery UI versions for the ``include_jquery_libs`` and ``include_ui_theme`` template tags have been increased to 1.12.4 and 1.11.4 respectively.
-- Dropped testing support for jQuery < 1.9 and jQuery UI < 1.10. Earlier versions may continue to work but it is recommended to upgrade.
-
-
-v0.9.0 (Released 2014-10-21)
---------------------------------------
-
-This release primarily addresses incompatibility with Django 1.7. The app-loading refactor both
-broke the previous registration and at the same time provided better utilities in Django core to
-make it more robust.
-
-- Compatibility with Django 1.7. Thanks to Calvin Spealman for the fixes.
-- Fixes for Python 3 support.
-
-Backwards Incompatible Changes
-________________________________
-
-- Dropped support for jQuery < 1.7
-
-
-v0.8.0 (Released 2014-01-20)
---------------------------------------
-
-- Widget media references now include a version string for cache-busting when upgrading django-selectable. Thanks to Ustun Ozgur.
-- Added compatibility code for \*SelectWidgets to handle POST data for the default SelectWidget. Thanks to leo-the-manic.
-- Development moved from Bitbucket to Github.
-- Update test suite compatibility with new test runner in Django 1.6. Thanks to Dan Poirier for the report and fix.
-- Tests now run on Travis CI.
-- Added French and Chinese translations.
-
-Backwards Incompatible Changes
-________________________________
-
-- Support for Django < 1.5 has been dropped. Most pieces should continue to work but there was an ugly JS hack to make django-selectable work nicely in the admin which too flakey to continue to maintain. If you aren't using the selectable widgets in inline-forms in the admin you can most likely continue to use Django 1.4 without issue.
-
-
-v0.7.0 (Released 2013-03-01)
---------------------------------------
-
-This release features a large refactor of the JS plugin used by the widgets. While this
-over makes the plugin more maintainable and allowed for some of the new features in this
-release, it does introduce a few incompatible changes. For the most part places where you
-might have previously used the ``autocomplete`` namespace/plugin, those references should
-be updated to reference the ``djselectable`` plugin.
-
-This release also adds experimental support for Python 3.2+ to go along with Django's support in 1.5.
-To use Python 3 with django-selectable you will need to use Django 1.5+.
-
-- Experimental Python 3.2+ support
-- Improved the scope of ``prepareQuery`` and ``formatLabel`` options. Not fully backwards compatible. Thanks to Augusto Men.
-- Allow passing the Python path string in place of the lookup class to the fields and widgets. Thanks to Michael Manfre.
-- Allow passing JS plugin options through the widget ``attrs`` option. Thanks to Felipe Prenholato.
-- Tests for compatibility with jQuery 1.6 through 1.9 and jQuery UI 1.8 through 1.10.
-- Added notes on Bootstrap compatibility.
-- Added compatibility with Grappelli in the admin.
-- Added Spanish translation thanks to Manuel Alvarez.
-- Added documentation notes on testing.
-
-Bug Fixes
-_________________
-
-- Fixed bug with matching hidden input when the name contains '_1'. Thanks to Augusto Men for the report and fix.
-- Fixed bug where the enter button would open the combobox options rather than submit the form. Thanks to Felipe Prenholato for the report.
-- Fixed bug with using ``allow_new=True`` creating items when no data was submitted. See #91.
-- Fixed bug with widget ``has_changed`` when there is no initial data. See #92.
-
-
-Backwards Incompatible Changes
-________________________________
-
-- The JS event namespace has changed from ``autocomplete`` to ``djselectable``.
-- ``data('autocomplete')`` is no longer available on the widgets on the client-side. Use ``data('djselectable')`` instead.
-- Combobox button was changed from a ``<button>`` to ``<a>``. Any customized styles you may have should be updated.
-- Combobox no longer changes the ``minLength`` or ``delay`` options.
-
-
-v0.6.2 (Released 2012-11-07)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed bug with special characters when highlighting matches. Thanks to Chad Files for the report.
-- Fixed javascript bug with spaces in ``item.id``. Thanks to @dc for the report and fix.
-
-
-v0.6.1 (Released 2012-10-13)
---------------------------------------
-
-Features
-_________________
-
-- Added Polish translation. Thanks to Sławomir Ehlert.
-
-Bug Fixes
-_________________
-
-- Fixed incompatibility with jQuery UI 1.9.
-
-
-v0.6.0 (Released 2012-10-09)
---------------------------------------
-
-This release continues to clean up the API and JS. This was primarily motivated by
-Sławomir Ehlert (@slafs) who is working on an alternate implementation which
-uses Select2 rather than jQuery UI. This opens the door for additional apps
-which use the same lookup declaration API with a different JS library on the front
-end.
-
-Python 2.5 support has been dropped to work towards Python 3 support.
-This also drops Django 1.2 support which is no longer receiving security fixes.
-
-Features
-_________________
-
-- Initial translations (pt_BR). Thanks to Felipe Prenholato for the patch.
-- Upgraded default jQuery UI version included by the template tags from 1.8.18 to 1.8.23
-- Added ``djselectableadd`` and ``djselectableremove`` events fired when items are added or removed from a mutliple select
-
-Bug Fixes
-_________________
-
-- Cleaned up JS scoping problems when multiple jQuery versions are used on the page. Thanks Antti Kaihola for the report.
-- Fixed minor JS bug where text input was not cleared when selected via the combobox in the multiselect. Thanks Antti Kaihola for the report and Lukas Pirl for a hotfix.
-
-Backwards Incompatible Changes
-________________________________
-
-- ``get_item_value`` and ``get_item_id`` are no longer marked as safe by default.
-- Removed AutoComboboxSelectField and AutoComboboxSelectMultipleField. These were deprecated in 0.5.
-- Dropping official Python 2.5 support.
-- Dropping official Django 1.2 support.
-- ``paginate_results`` signature changed as part of the lookup refactor.
-- ``SELECTABLE_MAX_LIMIT`` can no longer be ``None``.
-
-
-v0.5.2 (Released 2012-06-27)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed XSS flaw with lookup ``get_item_*`` methods. Thanks slafs for the report.
-- Fixed bug when passing widget instance rather than widget class to ``AutoCompleteSelectField`` or ``AutoCompleteSelectMultipleField``.
-
-
-v0.5.1 (Released 2012-06-08)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fix for double ``autocompleteselect`` event firing.
-- Fix for broken pagination in search results. Thanks David Ray for report and fix.
-
-
-v0.4.2 (Released 2012-06-08)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Backported fix for double ``autocompleteselect`` event firing.
-- Backported fix for broken pagination in search results.
-
-
-v0.5.0 (Released 2012-06-02)
---------------------------------------
-
-Features
-_________________
-
-- Template tag to add necessary jQuery and jQuery UI libraries. Thanks to Rick Testore for the initial implementation
-- :ref:`Lookup decorators <lookup-decorators>` for requiring user authentication or staff access to use the lookup
-- Additional documentation
-- Minor updates to the example project
-
-Backwards Incompatible Changes
-________________________________
-
-- Previously the minimal version of jQuery was listed as 1.4.3 when it fact there was a bug a that made django-selectable require 1.4.4. Not a new incompatibility but the docs have now been updated and 1.4.3 compatibility will not be added. Thanks to Rick Testore for the report and the fix
-- Started deprecation path for AutoComboboxSelectField and AutoComboboxSelectMultipleField
-
-
-v0.4.1 (Released 2012-03-11)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Cleaned up whitespace in css/js. Thanks Dan Poirier for the report and fix.
-- Fixed issue with saving M2M field data with AutoCompleteSelectMultipleField. Thanks Raoul Thill for the report.
-
-
-v0.4.0 (Released 2012-02-25)
---------------------------------------
-
-Features
-_________________
-
-- Better compatibility with :ref:`AutoCompleteSelectWidget`/:ref:`AutoComboboxSelectWidget` and Django's ModelChoiceField
-- Better compatibility with the Django admin :ref:`add another popup <admin-basic-example>`
-- Easier passing of query parameters. See the :ref:`Additional Parameters <additional-parameters>` section
-- Additional documentation
-- QUnit tests for JS functionality
-
-
-Backwards Incompatible Changes
-________________________________
-
-- Support for ``ModelLookup.search_field`` string has been removed. You should use the ``ModelLookup.search_fields`` tuple instead.
-
-
-v0.3.1 (Released 2012-02-23)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed issue with media urls when not using staticfiles.
-
-
-v0.3.0 (Released 2012-02-15)
---------------------------------------
-
-Features
-_________________
-
-- Multiple search fields for :ref:`model based lookups <ModelLookup>`
-- Support for :ref:`highlighting term matches <javascript-highlightMatch>`
-- Support for HTML in :ref:`result labels <lookup-get-item-label>`
-- Support for :ref:`client side formatting <advanced-label-formats>`
-- Additional documentation
-- Expanded examples in example project
-
-
-Bug Fixes
-_________________
-
-- Fixed issue with Enter key removing items from select multiple widgets `#24 <https://github.com/mlavin/django-selectable/issues/24>`_
-
-
-Backwards Incompatible Changes
-________________________________
-
-- The fix for #24 changed the remove items from a button to an anchor tag. If you were previously using the button tag for additional styling then you will need to adjust your styles.
-- The static resources were moved into a `selectable` sub-directory. This makes the media more in line with the template directory conventions. If you are using the widgets in the admin there is nothing to change. If you are using ``{{ form.media }}`` then there is also nothing to change. However if you were including static media manually then you will need to adjust them to include the selectable prefix.
-
-
-v0.2.0 (Released 2011-08-13)
---------------------------------------
-
-Features
-_________________
-
-- Additional documentation
-- :ref:`Positional configuration <AutoCompleteSelectMultipleWidget>` for multiple select fields/widgets
-- :ref:`Settings/configuration <SELECTABLE_MAX_LIMIT>` for limiting/paginating result sets
-- Compatibility and examples for :ref:`Admin inlines <admin-inline-example>`
-- JS updated for jQuery 1.6 compatibility
-- :ref:`JS hooks <client-side-parameters>` for updating query parameters
-- :ref:`Chained selection example <chain-select-example>`
-
-
-v0.1.2 (Released 2011-05-25)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed issue `#17 <https://github.com/mlavin/django-selectable/issues/17>`_
-
-
-v0.1.1 (Release 2011-03-21)
---------------------------------------
-
-Bug Fixes
-_________________
-
-- Fixed/cleaned up multiple select fields and widgets
-- Added media definitions to widgets
-
-
-Features
-_________________
-
-- Additional documentation
-- Added `update_query_parameters` to widgets
-- Refactored JS for easier configuration
-
-
-v0.1 (Released 2011-03-13)
---------------------------------------
-
-Initial public release
+++ /dev/null
-Settings
-==================
-
-
-.. _SELECTABLE_MAX_LIMIT:
-
-SELECTABLE_MAX_LIMIT
---------------------------------------
-
-This setting is used to limit the number of results returned by the auto-complete fields.
-Each field/widget can individually lower this maximum. The result sets will be
-paginated allowing the client to ask for more results. The limit is passed as a
-query parameter and validated against this value to ensure the client cannot manipulate
-the query string to retrive more values.
-
-Default: ``25``
-
-
-.. _SELECTABLE_ESCAPED_KEYS:
-
-SELECTABLE_ESCAPED_KEYS
---------------------------------------
-
-The ``LookupBase.format_item`` will conditionally escape result keys based on this
-setting. The label is escaped by default to prevent a XSS flaw when using the
-jQuery UI autocomplete. If you are using the lookup responses for a different
-autocomplete plugin then you may need to esacpe more keys by default.
-
-Default: ``('label', )``
-
-.. note::
- You probably don't want to include ``id`` in this setting.
-
-
-.. _javascript-options:
-
-Javascript Plugin Options
---------------------------------------
-
-Below the options for configuring the Javascript behavior of the django-selectable
-widgets.
-
-
-.. _javascript-removeIcon:
-
-removeIcon
-______________________________________
-
-
-This is the class name used for the remove buttons for the multiple select widgets.
-The set of icon classes built into the jQuery UI framework can be found here:
-http://jqueryui.com/themeroller/
-
-Default: ``ui-icon-close``
-
-
-.. _javascript-comboboxIcon:
-
-comboboxIcon
-______________________________________
-
-
-This is the class name used for the combobox dropdown icon. The set of icon classes built
-into the jQuery UI framework can be found here: http://jqueryui.com/themeroller/
-
-Default: ``ui-icon-triangle-1-s``
-
-
-.. _javascript-prepareQuery:
-
-prepareQuery
-______________________________________
-
-
-``prepareQuery`` is a function that is run prior to sending the search request to
-the server. It is an oppotunity to add additional parameters to the search query.
-It takes one argument which is the current search parameters as a dictionary. For
-more information on its usage see :ref:`Adding Parameters on the Client Side <client-side-parameters>`.
-
-Default: ``null``
-
-
-.. _javascript-highlightMatch:
-
-highlightMatch
-______________________________________
-
-
-If true the portions of the label which match the current search term will be wrapped
-in a span with the class ``highlight``.
-
-Default: ``true``
-
-
-.. _javascript-formatLabel:
-
-formatLabel
-______________________________________
-
-
-``formatLabel`` is a function that is run prior to rendering the search results in
-the dropdown menu. It takes two arguments: the current item label and the item data
-dictionary. It should return the label which should be used. For more information
-on its usage see :ref:`Label Formats on the Client Side <advanced-label-formats>`.
-
-Default: ``null``
-
+++ /dev/null
-Testing Forms and Lookups
-====================================
-
-django-selectable has its own test suite for testing the rendering, validation
-and server-side logic it provides. However, depending on the additional customizations
-you add to your forms and lookups you most likely will want to include tests of your
-own. This section contains some tips or techniques for testing your lookups.
-
-This guide assumes that you are reasonable familiar with the concepts of unit testing
-including Python's `unittest <http://docs.python.org/2/library/unittest.html>`_ module and
-Django's `testing guide <https://docs.djangoproject.com/en/stable/topics/testing/>`_.
-
-
-Testing Forms with django-selectable
---------------------------------------------------
-
-For the most part testing forms which use django-selectable's custom fields
-and widgets is the same as testing any Django form. One point that is slightly
-different is that the select and multi-select widgets are
-`MultiWidgets <https://docs.djangoproject.com/en/stable/ref/forms/widgets/#django.forms.MultiWidget>`_.
-The effect of this is that there are two names in the post rather than one. Take the below
-form for example.
-
- .. code-block:: python
-
- # models.py
-
- from django.db import models
-
- class Thing(models.Model):
- name = models.CharField(max_length=100)
- description = models.CharField(max_length=100)
-
- def __unicode__(self):
- return self.name
-
- .. code-block:: python
-
- # lookups.py
-
- from selectable.base import ModelLookup
- from selectable.registry import registry
-
- from .models import Thing
-
- class ThingLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
- registry.register(ThingLookup)
-
- .. code-block:: python
-
- # forms.py
-
- from django import forms
-
- from selectable.forms import AutoCompleteSelectField
-
- from .lookups import ThingLookup
-
- class SimpleForm(forms.Form):
- "Basic form for testing."
- thing = AutoCompleteSelectField(lookup_class=ThingLookup)
-
-This form has a single field to select a ``Thing``. It does not allow
-new items. Let's write some simple tests for this form.
-
- .. code-block:: python
-
- # tests.py
-
- from django.test import TestCase
-
- from .forms import SimpleForm
- from .models import Thing
-
- class SimpleFormTestCase(TestCase):
-
- def test_valid_form(self):
- "Submit valid data."
- thing = Thing.objects.create(name='Foo', description='Bar')
- data = {
- 'thing_0': thing.name,
- 'thing_1': thing.pk,
- }
- form = SimpleForm(data=data)
- self.assertTrue(form.is_valid())
-
- def test_invalid_form(self):
- "Thing is required but missing."
- data = {
- 'thing_0': 'Foo',
- 'thing_1': '',
- }
- form = SimpleForm(data=data)
- self.assertFalse(form.is_valid())
-
-Here you will note that while there is only one field ``thing`` it requires
-two items in the POST the first is for the text input and the second is for
-the hidden input. This is again due to the use of MultiWidget for the selection.
-
-There is compatibility code in the widgets to lookup the original name
-from the POST. This makes it easier to transition to the the selectable widgets without
-breaking existing tests.
-
-
-Testing Lookup Results
---------------------------------------------------
-
-Testing the lookups used by django-selectable is similar to testing your Django views.
-While it might be tempting to use the Django
-`test client <https://docs.djangoproject.com/en/stable/topics/testing/#module-django.test.client>`_,
-it is slightly easier to use the
-`request factory <https://docs.djangoproject.com/en/stable/topics/testing/#the-request-factory>`_.
-A simple example is given below.
-
- .. code-block:: python
-
- # tests.py
-
- import json
-
- from django.test import TestCase
- from django.test.client import RequestFactory
-
- from .lookups import ThingLookup
- from .models import Thing
-
- class ThingLookupTestCase(TestCase):
-
- def setUp(self):
- self.factory = RequestFactory()
- self.lookup = ThingLookup()
- self.test_thing = Thing.objects.create(name='Foo', description='Bar')
-
- def test_results(self):
- "Test full response."
- request = self.factory.get("/", {'term': 'Fo'})
- response = self.lookup.results(request)
- data = json.loads(response.content)['data']
- self.assertEqual(1, len(data))
- self.assertEqual(self.test_thing.pk, data[1]['id'])
-
- def test_label(self):
- "Test item label."
- label = self.lookup.get_item_label(self.test_thing)
- self.assertEqual(self.test_thing.name, label)
-
-As shown in the ``test_label`` example it is not required to test the full
-request/response. You can test each of the methods in the lookup API individually.
-When testing your lookups you should focus on testing the portions which have been
-customized by your application.
\ No newline at end of file
+++ /dev/null
-Widgets
-==========
-
-Below are the custom widgets defined by Django-Selectable. All widgets take the
-lookup class as the first required argument.
-
-These widgets all support a ``query_params`` keyword argument which is used to pass
-additional query parameters to the lookup search. See the section on
-:ref:`Adding Parameters on the Server Side <server-side-parameters>` for more
-information.
-
-You can configure the plugin options by passing the configuration dictionary in the ``data-selectable-options``
-attribute. The set of options availble include those define by the base
-`autocomplete plugin <http://api.jqueryui.com/1.9/autocomplete/>`_ as well as the
-:ref:`javascript-removeIcon`, :ref:`javascript-comboboxIcon`, and :ref:`javascript-highlightMatch` options
-which are unique to django-selectable.
-
- .. code-block:: python
-
- attrs = {'data-selectable-options': {'highlightMatch': True, 'minLength': 5}}
- selectable.AutoCompleteSelectWidget(lookup_class=FruitLookup, attrs=attrs)
-
-
-.. _AutoCompleteWidget:
-
-AutoCompleteWidget
---------------------------------------
-
-Basic widget for auto-completing text. The widget returns the item value as defined
-by the lookup ``get_item_value``. If the ``allow_new`` keyword argument is passed as
-true it will allow the user to type any text they wish.
-
-.. _AutoComboboxWidget:
-
-AutoComboboxWidget
---------------------------------------
-
-Similar to :ref:`AutoCompleteWidget` but has a button to reveal all options.
-
-
-.. _AutoCompleteSelectWidget:
-
-AutoCompleteSelectWidget
---------------------------------------
-
-Widget for selecting a value/id based on input text. Optionally allows selecting new items to be created.
-This widget should be used in conjunction with the :ref:`AutoCompleteSelectField` as it will
-return both the text entered by the user and the id (if an item was selected/matched).
-
-:ref:`AutoCompleteSelectWidget` works directly with Django's
-`ModelChoiceField <https://docs.djangoproject.com/en/stable/ref/forms/fields/#modelchoicefield>`_.
-You can simply replace the widget without replacing the entire field.
-
- .. code-block:: python
-
- class FarmAdminForm(forms.ModelForm):
-
- class Meta(object):
- model = Farm
- widgets = {
- 'owner': selectable.AutoCompleteSelectWidget(lookup_class=FruitLookup),
- }
-
-The one catch is that you must use ``allow_new=False`` which is the default.
-
-``lookup_class`` may also be a dotted path.
-
- .. code-block:: python
-
- widget = selectable.AutoCompleteWidget(lookup_class='core.lookups.FruitLookup')
-
-
-.. _AutoComboboxSelectWidget:
-
-AutoComboboxSelectWidget
---------------------------------------
-
-Similar to :ref:`AutoCompleteSelectWidget` but has a button to reveal all options.
-
-:ref:`AutoComboboxSelectWidget` works directly with Django's
-`ModelChoiceField <https://docs.djangoproject.com/en/stable/ref/forms/fields/#modelchoicefield>`_.
-You can simply replace the widget without replacing the entire field.
-
- .. code-block:: python
-
- class FarmAdminForm(forms.ModelForm):
-
- class Meta(object):
- model = Farm
- widgets = {
- 'owner': selectable.AutoComboboxSelectWidget(lookup_class=FruitLookup),
- }
-
-The one catch is that you must use ``allow_new=False`` which is the default.
-
-
-.. _AutoCompleteSelectMultipleWidget:
-
-AutoCompleteSelectMultipleWidget
---------------------------------------
-
-Builds a list of selected items from auto-completion. This widget will return a list
-of item ids as defined by the lookup ``get_item_id``. Using this widget with the
-:ref:`AutoCompleteSelectMultipleField` will clean the items to the item objects. This does
-not allow for creating new items. There is another optional keyword argument ``postion``
-which can take four possible values: `bottom`, `bottom-inline`, `top` or `top-inline`.
-This determine the position of the deck list of currently selected items as well as
-whether this list is stacked or inline. The default is `bottom`.
-
-
-.. _AutoComboboxSelectMultipleWidget:
-
-AutoComboboxSelectMultipleWidget
---------------------------------------
-
-Same as :ref:`AutoCompleteSelectMultipleWidget` but with a combobox.
+++ /dev/null
-/*global module:false*/
-module.exports = function(grunt) {
-
- // Project configuration.
- grunt.initConfig({
- qunit: {
- urls: [
- 'http://localhost:<%= server.port %>/selectable/tests/qunit/index.html?jquery=1.10.1&ui=1.10.3',
- 'http://localhost:<%= server.port %>/selectable/tests/qunit/index.html?jquery=1.9.1&ui=1.10.3',
- 'http://localhost:<%= server.port %>/selectable/tests/qunit/index.html?jquery=1.8.3&ui=1.9.2',
- 'http://localhost:<%= server.port %>/selectable/tests/qunit/index.html?jquery=1.7.2&ui=1.8.24',
- 'http://localhost:<%= server.port %>/selectable/tests/qunit/index.html?jquery=1.6.4&ui=1.8.24'
- ]
- },
- lint: {
- files: ['selectable/static/selectable/js/*.js']
- },
- watch: {
- files: '<config:lint.files>',
- tasks: 'lint qunit'
- },
- jshint: {
- options: {
- curly: true,
- eqeqeq: true,
- immed: true,
- latedef: true,
- newcap: true,
- noarg: true,
- sub: true,
- undef: true,
- boss: true,
- eqnull: true,
- browser: true,
- undef: true,
- trailing: true,
- indent: 4
- },
- globals: {
- jQuery: true,
- // Django admin globals
- django: true,
- dismissAddAnotherPopup: true,
- windowname_to_id: true,
- html_unescape: true,
- // Optional globals
- djselectableAdminPatch: true,
- djselectableAutoLoad: true,
- // Grappelli namespace
- grp: true
- }
- },
- server: {
- port: 8085
- },
- });
-
- // Default task.
- grunt.registerTask('default', 'server lint qunit');
-
-};
+++ /dev/null
-#!/usr/bin/env python
-import os
-import sys
-
-from django.conf import settings
-
-
-if not settings.configured:
- settings.configure(
- DATABASES={
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': ':memory:',
- }
- },
- MIDDLEWARE_CLASSES=(),
- INSTALLED_APPS=(
- 'selectable',
- ),
- SITE_ID=1,
- SECRET_KEY='super-secret',
- ROOT_URLCONF='selectable.tests.urls',
- TEMPLATES=[{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [os.path.join(os.path.normpath(os.path.join(
- os.path.dirname(__file__), 'selectable')), 'templates')]}])
-
-
-from django import setup
-from django.test.utils import get_runner
-
-
-def runtests():
- setup()
- TestRunner = get_runner(settings)
- test_runner = TestRunner(verbosity=1, interactive=True, failfast=False)
- args = sys.argv[1:] or ['selectable', ]
- failures = test_runner.run_tests(args)
- sys.exit(failures)
-
-
-if __name__ == '__main__':
- runtests()
+++ /dev/null
-"Auto-complete selection widgets using Django and jQuery UI."
-
-
-__version__ = '1.1.0'
-
-default_app_config = 'selectable.apps.SelectableConfig'
+++ /dev/null
-from django.apps import AppConfig
-
-
-class SelectableConfig(AppConfig):
- """App configuration for django-selectable."""
-
- name = 'selectable'
-
- def ready(self):
- from . import registry
- registry.autodiscover()
+++ /dev/null
-"Base classes for lookup creation."
-from __future__ import unicode_literals
-
-import operator
-import re
-from functools import reduce
-
-from django.conf import settings
-from django.core.paginator import Paginator, InvalidPage, EmptyPage
-from django.core.urlresolvers import reverse
-from django.http import JsonResponse
-from django.db.models import Q, Model
-from django.utils.encoding import smart_text
-from django.utils.html import conditional_escape
-from django.utils.translation import ugettext as _
-
-from selectable.forms import BaseLookupForm
-
-
-__all__ = (
- 'LookupBase',
- 'ModelLookup',
-)
-
-
-class LookupBase(object):
- "Base class for all django-selectable lookups."
-
- form = BaseLookupForm
- response = JsonResponse
-
- def _name(cls):
- app_name = cls.__module__.split('.')[-2].lower()
- class_name = cls.__name__.lower()
- name = '%s-%s' % (app_name, class_name)
- return name
- name = classmethod(_name)
-
- def _url(cls):
- return reverse('selectable-lookup', args=[cls.name()])
- url = classmethod(_url)
-
- def get_query(self, request, term):
- return []
-
- def get_item_label(self, item):
- return smart_text(item)
-
- def get_item_id(self, item):
- return smart_text(item)
-
- def get_item_value(self, item):
- return smart_text(item)
-
- def get_item(self, value):
- return value
-
- def create_item(self, value):
- raise NotImplemented()
-
- def format_item(self, item):
- "Construct result dictionary for the match item."
- result = {
- 'id': self.get_item_id(item),
- 'value': self.get_item_value(item),
- 'label': self.get_item_label(item),
- }
- for key in settings.SELECTABLE_ESCAPED_KEYS:
- if key in result:
- result[key] = conditional_escape(result[key])
- return result
-
- def paginate_results(self, results, options):
- "Return a django.core.paginator.Page of results."
- limit = options.get('limit', settings.SELECTABLE_MAX_LIMIT)
- paginator = Paginator(results, limit)
- page = options.get('page', 1)
- try:
- results = paginator.page(page)
- except (EmptyPage, InvalidPage):
- results = paginator.page(paginator.num_pages)
- return results
-
- def results(self, request):
- "Match results to given term and return the serialized HttpResponse."
- results = {}
- form = self.form(request.GET)
- if form.is_valid():
- options = form.cleaned_data
- term = options.get('term', '')
- raw_data = self.get_query(request, term)
- results = self.format_results(raw_data, options)
- return self.response(results)
-
- def format_results(self, raw_data, options):
- '''
- Returns a python structure that later gets serialized.
- raw_data
- full list of objects matching the search term
- options
- a dictionary of the given options
- '''
- page_data = self.paginate_results(raw_data, options)
- results = {}
- meta = options.copy()
- meta['more'] = _('Show more results')
- if page_data and page_data.has_next():
- meta['next_page'] = page_data.next_page_number()
- if page_data and page_data.has_previous():
- meta['prev_page'] = page_data.previous_page_number()
- results['data'] = [self.format_item(item) for item in page_data.object_list]
- results['meta'] = meta
- return results
-
-
-class ModelLookup(LookupBase):
- "Lookup class for easily defining lookups based on Django models."
-
- model = None
- filters = {}
- search_fields = ()
-
- def get_query(self, request, term):
- qs = self.get_queryset()
- if term:
- search_filters = []
- if self.search_fields:
- for field in self.search_fields:
- search_filters.append(Q(**{field: term}))
- qs = qs.filter(reduce(operator.or_, search_filters))
- return qs
-
- def get_queryset(self):
- qs = self.model._default_manager.get_queryset()
- if self.filters:
- qs = qs.filter(**self.filters)
- return qs
-
- def get_item_id(self, item):
- return item.pk
-
- def get_item(self, value):
- item = None
- if value:
- value = value.pk if isinstance(value, Model) else value
- try:
- item = self.get_queryset().get(pk=value)
- except (ValueError, self.model.DoesNotExist):
- item = None
- return item
-
- def create_item(self, value):
- data = {}
- if self.search_fields:
- field_name = re.sub(r'__\w+$', '', self.search_fields[0])
- if field_name:
- data = {field_name: value}
- return self.model(**data)
+++ /dev/null
-"Compatibility utilites for Python/Django versions."
-
-try:
- from urllib.parse import urlparse
-except ImportError:
- from urlparse import urlparse
+++ /dev/null
-"Decorators for additional lookup functionality."
-from __future__ import unicode_literals
-
-from functools import wraps
-
-from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
-
-
-__all__ = (
- 'ajax_required',
- 'login_required',
- 'staff_member_required',
-)
-
-
-def results_decorator(func):
- """
- Helper for constructing simple decorators around Lookup.results.
-
- func is a function which takes a request as the first parameter. If func
- returns an HttpReponse it is returned otherwise the original Lookup.results
- is returned.
- """
- # Wrap function to maintian the original doc string, etc
- @wraps(func)
- def decorator(lookup_cls):
- # Construct a class decorator from the original function
- original = lookup_cls.results
- def inner(self, request):
- # Wrap lookup_cls.results by first calling func and checking the result
- result = func(request)
- if isinstance(result, HttpResponse):
- return result
- return original(self, request)
- # Replace original lookup_cls.results with wrapped version
- lookup_cls.results = inner
- return lookup_cls
- # Return the constructed decorator
- return decorator
-
-
-@results_decorator
-def ajax_required(request):
- "Lookup decorator to require AJAX calls to the lookup view."
- if not request.is_ajax():
- return HttpResponseBadRequest()
-
-
-@results_decorator
-def login_required(request):
- "Lookup decorator to require the user to be authenticated."
- user = getattr(request, 'user', None)
- if user is None or not user.is_authenticated():
- return HttpResponse(status=401) # Unauthorized
-
-
-@results_decorator
-def staff_member_required(request):
- "Lookup decorator to require the user is a staff member."
- user = getattr(request, 'user', None)
- if user is None or not user.is_authenticated():
- return HttpResponse(status=401) # Unauthorized
- elif not user.is_staff:
- return HttpResponseForbidden()
+++ /dev/null
-class LookupAlreadyRegistered(Exception):
- "Exception when trying to register a lookup which is already registered."
-
-
-class LookupNotRegistered(Exception):
- "Exception when trying use a lookup which is not registered."
-
-
-class LookupInvalid(Exception):
- "Exception when register an invalid lookup class."
+++ /dev/null
-from selectable.forms.base import *
-from selectable.forms.fields import *
-from selectable.forms.widgets import *
+++ /dev/null
-from __future__ import unicode_literals
-
-from importlib import import_module
-
-from django import forms
-from django.conf import settings
-from django.utils.six import string_types
-
-
-__all__ = (
- 'BaseLookupForm',
- 'import_lookup_class',
-)
-
-
-class BaseLookupForm(forms.Form):
- term = forms.CharField(required=False)
- limit = forms.IntegerField(required=False, min_value=1)
- page = forms.IntegerField(required=False, min_value=1)
-
- def clean_limit(self):
- "Ensure given limit is less than default if defined"
- limit = self.cleaned_data.get('limit', None)
- if (settings.SELECTABLE_MAX_LIMIT is not None and
- (not limit or limit > settings.SELECTABLE_MAX_LIMIT)):
- limit = settings.SELECTABLE_MAX_LIMIT
- return limit
-
- def clean_page(self):
- "Return the first page if no page or invalid number is given."
- return self.cleaned_data.get('page', 1) or 1
-
-
-def import_lookup_class(lookup_class):
- """
- Import lookup_class as a dotted base and ensure it extends LookupBase
- """
- from selectable.base import LookupBase
- if isinstance(lookup_class, string_types):
- mod_str, cls_str = lookup_class.rsplit('.', 1)
- mod = import_module(mod_str)
- lookup_class = getattr(mod, cls_str)
- if not issubclass(lookup_class, LookupBase):
- raise TypeError('lookup_class must extend from selectable.base.LookupBase')
- return lookup_class
+++ /dev/null
-from __future__ import unicode_literals
-
-from django import forms, VERSION as DJANGO_VERSION
-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
-from selectable.forms.widgets import AutoCompleteSelectMultipleWidget
-
-__all__ = (
- 'AutoCompleteSelectField',
- 'AutoCompleteSelectMultipleField',
-)
-
-
-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
-
- if DJANGO_VERSION < (1, 8):
- def _has_changed(self, initial, data):
- return self.has_changed(initial, data)
-
-
-class AutoCompleteSelectField(BaseAutoCompleteField):
- widget = AutoCompleteSelectWidget
-
- default_error_messages = {
- 'invalid_choice': _('Select a valid choice. That choice is not one of the available choices.'),
- }
-
- 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)
- widget = kwargs.get('widget', self.widget) or self.widget
- if isinstance(widget, type):
- kwargs['widget'] = widget(lookup_class, allow_new=self.allow_new, limit=self.limit)
- super(AutoCompleteSelectField, self).__init__(*args, **kwargs)
-
-
-
- def to_python(self, value):
- if value in EMPTY_VALUES:
- return None
- lookup = self.lookup_class()
- if isinstance(value, list):
- # Input comes from an AutoComplete widget. It's two
- # components: text and id
- if len(value) != 2:
- raise ValidationError(self.error_messages['invalid_choice'])
- label, pk = value
- if pk in EMPTY_VALUES:
- if not self.allow_new:
- if label:
- raise ValidationError(self.error_messages['invalid_choice'])
- else:
- return None
- if label in EMPTY_VALUES:
- return None
- value = lookup.create_item(label)
- else:
- value = lookup.get_item(pk)
- if value is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- else:
- value = lookup.get_item(value)
- if value is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- return value
-
-
-class AutoCompleteSelectMultipleField(BaseAutoCompleteField):
- widget = AutoCompleteSelectMultipleWidget
-
- default_error_messages = {
- 'invalid_choice': _('Select a valid choice. That choice is not one of the available choices.'),
- }
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.limit = kwargs.pop('limit', None)
- widget = kwargs.get('widget', self.widget) or self.widget
- if isinstance(widget, type):
- kwargs['widget'] = widget(lookup_class, limit=self.limit)
- super(AutoCompleteSelectMultipleField, self).__init__(*args, **kwargs)
-
- def to_python(self, value):
- if value in EMPTY_VALUES:
- return []
- lookup = self.lookup_class()
- items = []
- for v in value:
- if v not in EMPTY_VALUES:
- item = lookup.get_item(v)
- if item is None:
- raise ValidationError(self.error_messages['invalid_choice'])
- items.append(item)
- return items
+++ /dev/null
-from __future__ import unicode_literals
-
-import inspect
-import json
-
-from django import forms
-from django.conf import settings
-from django.forms.utils import flatatt
-from django.utils.encoding import force_text
-from django.utils.http import urlencode
-from django.utils.safestring import mark_safe
-
-from selectable import __version__
-from selectable.forms.base import import_lookup_class
-
-__all__ = (
- 'AutoCompleteWidget',
- 'AutoCompleteSelectWidget',
- 'AutoComboboxWidget',
- 'AutoComboboxSelectWidget',
- 'AutoCompleteSelectMultipleWidget',
- 'AutoComboboxSelectMultipleWidget',
-)
-
-
-STATIC_PREFIX = '%sselectable/' % settings.STATIC_URL
-
-
-class SelectableMediaMixin(object):
-
- class Media(object):
- css = {
- 'all': ('%scss/dj.selectable.css?v=%s' % (STATIC_PREFIX, __version__),)
- }
- js = ('%sjs/jquery.dj.selectable.js?v=%s' % (STATIC_PREFIX, __version__),)
-
-
-new_style_build_attrs = (
- 'base_attrs' in
- inspect.getargs(forms.widgets.Widget.build_attrs.__code__).args)
-
-
-class BuildAttrsCompat(object):
- """
- Mixin to provide compatibility between old and new function
- signatures for Widget.build_attrs, and a hook for adding our
- own attributes.
- """
- # These are build_attrs definitions that make it easier for
- # us to override, without having to worry about the signature,
- # by adding a standard hook, `build_attrs_extra`.
- # It has a different signature when we are running different Django
- # versions.
- if new_style_build_attrs:
- def build_attrs(self, base_attrs, extra_attrs=None):
- attrs = super(BuildAttrsCompat, self).build_attrs(
- base_attrs, extra_attrs=extra_attrs)
- return self.build_attrs_extra(attrs)
- else:
- def build_attrs(self, extra_attrs=None, **kwargs):
- attrs = super(BuildAttrsCompat, self).build_attrs(
- extra_attrs=extra_attrs, **kwargs)
- return self.build_attrs_extra(attrs)
-
- def build_attrs_extra(self, attrs):
- # Default implementation, does nothing
- return attrs
-
- # These provide a standard interface for when we want to call build_attrs
- # in our own `render` methods. In both cases it is the same as the Django
- # 1.11 signature, but has a different implementation for different Django
- # versions.
- if new_style_build_attrs:
- def build_attrs_compat(self, base_attrs, extra_attrs=None):
- return self.build_attrs(base_attrs, extra_attrs=extra_attrs)
-
- else:
- def build_attrs_compat(self, base_attrs, extra_attrs=None):
- # Implementation copied from Django 1.11, plus include our
- # hook `build_attrs_extra`
- attrs = base_attrs.copy()
- if extra_attrs is not None:
- attrs.update(extra_attrs)
- return self.build_attrs_extra(attrs)
-
-
-CompatMixin = BuildAttrsCompat
-
-
-class AutoCompleteWidget(CompatMixin, forms.TextInput, SelectableMediaMixin):
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- self.allow_new = kwargs.pop('allow_new', False)
- self.qs = kwargs.pop('query_params', {})
- self.limit = kwargs.pop('limit', None)
- super(AutoCompleteWidget, self).__init__(*args, **kwargs)
-
- def update_query_parameters(self, qs_dict):
- self.qs.update(qs_dict)
-
- def build_attrs_extra(self, attrs):
- attrs = super(AutoCompleteWidget, self).build_attrs_extra(attrs)
- url = self.lookup_class.url()
- if self.limit and 'limit' not in self.qs:
- self.qs['limit'] = self.limit
- if self.qs:
- url = '%s?%s' % (url, urlencode(self.qs))
- if 'data-selectable-options' in attrs:
- attrs['data-selectable-options'] = json.dumps(attrs['data-selectable-options'])
- attrs['data-selectable-url'] = url
- attrs['data-selectable-type'] = 'text'
- attrs['data-selectable-allow-new'] = str(self.allow_new).lower()
- return attrs
-
-
-class SelectableMultiWidget(CompatMixin, forms.MultiWidget):
-
- def update_query_parameters(self, qs_dict):
- self.widgets[0].update_query_parameters(qs_dict)
-
- def decompress(self, value):
- if value:
- lookup = self.lookup_class()
- model = getattr(self.lookup_class, 'model', None)
- if model and isinstance(value, model):
- item = value
- value = lookup.get_item_id(item)
- else:
- item = lookup.get_item(value)
- item_value = lookup.get_item_value(item)
- 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 _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.allow_new = kwargs.pop('allow_new', False)
- self.limit = kwargs.pop('limit', None)
- query_params = kwargs.pop('query_params', {})
- widgets = [
- 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(_BaseSingleSelectWidget, self).__init__(widgets, *args, **kwargs)
-
- def value_from_datadict(self, 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_extra(self, attrs):
- attrs = super(AutoComboboxWidget, self).build_attrs_extra(attrs)
- attrs['data-selectable-type'] = 'combobox'
- return attrs
-
-
-class AutoComboboxSelectWidget(_BaseSingleSelectWidget):
-
- primary_widget = AutoComboboxWidget
-
-
-class LookupMultipleHiddenInput(CompatMixin, forms.MultipleHiddenInput):
-
- def __init__(self, lookup_class, *args, **kwargs):
- self.lookup_class = import_lookup_class(lookup_class)
- super(LookupMultipleHiddenInput, self).__init__(*args, **kwargs)
-
- # This supports Django 1.11 and later
- def get_context(self, name, value, attrs):
- lookup = self.lookup_class()
- values = self._normalize_value(value)
- values = list(values) # force evaluation
-
- context = super(LookupMultipleHiddenInput, self).get_context(name, values, attrs)
-
- # Now override/add to what super() did:
- subwidgets = context['widget']['subwidgets']
- for widget_ctx, item in zip(subwidgets, values):
- input_value, title = self._lookup_value_and_title(lookup, item)
- widget_ctx['value'] = input_value # override what super() did
- if title:
- widget_ctx['attrs']['title'] = title
- return context
-
- # This supports Django 1.10 and earlier
- def render(self, name, value, attrs=None, choices=()):
- lookup = self.lookup_class()
- value = self._normalize_value(value)
-
- base_attrs = dict(self.attrs, type=self.input_type, name=name)
- combined_attrs = self.build_attrs_compat(base_attrs, attrs)
- id_ = combined_attrs.get('id', None)
- inputs = []
- for i, v in enumerate(value):
- input_attrs = combined_attrs.copy()
- v_, title = self._lookup_value_and_title(lookup, v)
- input_attrs.update(
- value=v_,
- title=title,
- )
- if id_:
- # An ID attribute was given. Add a numeric index as a suffix
- # so that the inputs don't all have the same ID attribute.
- input_attrs['id'] = '%s_%s' % (id_, i)
- inputs.append('<input%s />' % flatatt(input_attrs))
- return mark_safe('\n'.join(inputs))
-
- # These are used by both paths
- def build_attrs_extra(self, attrs):
- attrs = super(LookupMultipleHiddenInput, self).build_attrs_extra(attrs)
- attrs['data-selectable-type'] = 'hidden-multiple'
- return attrs
-
- def _normalize_value(self, value):
- if value is None:
- value = []
- return value
-
- def _lookup_value_and_title(self, lookup, v):
- model = getattr(self.lookup_class, 'model', None)
- item = None
- if model and isinstance(v, model):
- item = v
- v = lookup.get_item_id(item)
- title = None
- if v:
- item = item or lookup.get_item(v)
- title = lookup.get_item_value(item)
- return force_text(v), title
-
-
-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)
- 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 = [
- self.primary_widget(
- lookup_class, allow_new=False,
- limit=self.limit, query_params=query_params, attrs=attrs
- ),
- LookupMultipleHiddenInput(lookup_class)
- ]
- super(_BaseMultipleSelectWidget, self).__init__(widgets, *args, **kwargs)
-
- def value_from_datadict(self, data, files, name):
- 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 build_attrs_extra(self, attrs):
- attrs = super(_BaseMultipleSelectWidget, self).build_attrs_extra(attrs)
- if 'required' in attrs:
- attrs.pop('required')
- return attrs
-
- def render(self, name, value, attrs=None):
- if value and not hasattr(value, '__iter__'):
- value = [value]
- value = ['', value]
- return super(_BaseMultipleSelectWidget, self).render(name, value, attrs)
-
-
-class AutoCompleteSelectMultipleWidget(_BaseMultipleSelectWidget):
-
- primary_widget = AutoCompleteWidget
-
-
-class AutoComboboxSelectMultipleWidget(_BaseMultipleSelectWidget):
-
- primary_widget = AutoComboboxWidget
+++ /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.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \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"
-"Language: \n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: base.py:117
-msgid "Show more results"
-msgstr ""
-
-#: forms/fields.py:48 forms/fields.py:96
-msgid "Select a valid choice. That choice is not one of the available choices."
-msgstr ""
+++ /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:
-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-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:115
-msgid "Show more results"
-msgstr "Mostrar más resultados"
-
-#: 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."
+++ /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:
-# slafs <slafs.e@gmail.com>, 2012
-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-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"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\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:115
-msgid "Show more results"
-msgstr "Pokaż więcej wyników"
-
-#: 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."
+++ /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:
-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-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:115
-msgid "Show more results"
-msgstr "Mostrar mais resultados"
-
-#: 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 "请选择一个有效的选项。当前选项无效。"
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.conf import settings
-
-# Set default settings
-if not hasattr(settings, 'SELECTABLE_MAX_LIMIT'):
- settings.SELECTABLE_MAX_LIMIT = 25
-
-if not hasattr(settings, 'SELECTABLE_ESCAPED_KEYS'):
- settings.SELECTABLE_ESCAPED_KEYS = ('label', )
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.utils.encoding import force_text
-from django.utils.module_loading import autodiscover_modules
-
-from selectable.base import LookupBase
-from selectable.exceptions import (LookupAlreadyRegistered, LookupNotRegistered,
- LookupInvalid)
-
-
-class LookupRegistry(object):
-
- def __init__(self):
- self._registry = {}
-
- def validate(self, lookup):
- if not issubclass(lookup, LookupBase):
- raise LookupInvalid('Registered lookups must inherit from the LookupBase class')
-
- def register(self, lookup):
- self.validate(lookup)
- name = force_text(lookup.name())
- if name in self._registry:
- raise LookupAlreadyRegistered('The name %s is already registered' % name)
- self._registry[name] = lookup
-
- def unregister(self, lookup):
- self.validate(lookup)
- name = force_text(lookup.name())
- if name not in self._registry:
- raise LookupNotRegistered('The name %s is not registered' % name)
- del self._registry[name]
-
- def get(self, key):
- return self._registry.get(key, None)
-
-
-registry = LookupRegistry()
-
-
-def autodiscover():
- # Attempt to import the app's lookups module.
- autodiscover_modules('lookups', register_to=registry)
+++ /dev/null
-/*
- * django-selectable UI widget CSS
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-ul.selectable-deck, ul.ui-autocomplete {
- list-style: none outside none;
-}
-ul.selectable-deck li.selectable-deck-item,
-ul.ui-autocomplete li.ui-menu-item {
- margin: 0;
- list-style-type: none;
-}
-ul.selectable-deck li.selectable-deck-item .selectable-deck-remove {
- float: right;
-}
-ul.selectable-deck-bottom-inline,
-ul.selectable-deck-top-inline {
- padding: 0;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item,
-ul.selectable-deck-top-inline li.selectable-deck-item {
- display: inline;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item .selectable-deck-remove,
-ul.selectable-deck-top-inline li.selectable-deck-item .selectable-deck-remove {
- margin-left: 0.4em;
- display: inline;
- float: none;
-}
-ul.ui-autocomplete li.ui-menu-item span.highlight {
- font-weight: bold;
-}
-input.ui-combo-input {
- margin-right: 0;
- line-height: 1.3;
-}
-a.ui-combo-button {
- margin-left: -1px;
-}
-a.ui-combo-button .ui-button-text {
- padding: 0;
-}
+++ /dev/null
-/*jshint trailing:true, indent:4*/
-/*
- * django-selectable UI widget
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Depends:
- * - jQuery 1.7+
- * - jQuery UI 1.8 widget factory
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-(function ($) {
-
- $.widget("ui.djselectable", $.ui.autocomplete, {
-
- options: {
- removeIcon: "ui-icon-close",
- comboboxIcon: "ui-icon-triangle-1-s",
- defaultClasses: {
- "text": "ui-widget ui-widget-content ui-corner-all",
- "combobox": "ui-widget ui-widget-content ui-corner-left ui-combo-input"
- },
- prepareQuery: null,
- highlightMatch: true,
- formatLabel: null
- },
-
- _initDeck: function () {
- /* Create list display for currently selected items for multi-select */
- var self = this;
- var data = $(this.element).data();
- var style = data.selectablePosition || data['selectable-position'] || 'bottom';
- this.deck = $('<ul>').addClass('ui-widget selectable-deck selectable-deck-' + style);
- if (style === 'bottom' || style === 'bottom-inline') {
- $(this.element).after(this.deck);
- } else {
- $(this.element).before(this.deck);
- }
- $(self.hiddenMultipleSelector).each(function (i, input) {
- self._addDeckItem(input);
- });
- },
-
- _addDeckItem: function (input) {
- /* Add new deck list item from a given hidden input */
- var self = this,
- li = $('<li>').addClass('selectable-deck-item'),
- item = {element: self.element, input: input, wrapper: li, deck: self.deck},
- button;
- li.html($(input).attr('title'));
- if (self._trigger("add", null, item) === false) {
- input.remove();
- } else {
- button = this._removeButtonTemplate(item);
- button.click(function (e) {
- e.preventDefault();
- if (self._trigger("remove", e, item) !== false) {
- $(input).remove();
- li.remove();
- }
- });
- li.append(button).appendTo(this.deck);
- }
- },
-
- _removeButtonTemplate: function (item) {
- var options = {
- icons: {
- primary: this.options.removeIcon
- },
- text: false,
- disabled: this.disabled
- },
- button = $('<a>')
- .attr('href', '#')
- .addClass('selectable-deck-remove')
- .button(options);
- return button;
- },
-
- select: function (item, event) {
- /* Trigger selection of a given item.
- Item should contain two properties: id and value
- Event is the original select event if there is one.
- Event should not be passed if triggered manually.
- */
- var $input = $(this.element);
- $input.removeClass('ui-state-error');
- this._setHidden(item);
- if (item) {
- if (this.allowMultiple) {
- $input.val("");
- this.term = "";
- if ($(this.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) {
- var newInput = $('<input />', {
- 'type': 'hidden',
- 'name': this.hiddenName,
- 'value': item.id,
- 'title': item.value,
- 'data-selectable-type': 'hidden-multiple'
- });
- $input.after(newInput);
- this._addDeckItem(newInput);
- }
- return false;
- } else {
- $input.val(item.value);
- var ui = {item: item};
- if (typeof(event) === 'undefined' || event.type !== "djselectableselect") {
- this.element.trigger("djselectableselect", [ui ]);
- }
- }
- }
- },
-
- _setHidden: function (item) {
- /* Set or clear single hidden input */
- var $elem = $(this.hiddenSelector);
- if (item && item.id) {
- $elem.val(item.id);
- } else {
- $elem.val("");
- }
- },
-
- _comboButtonTemplate: function (input) {
- // Add show all items button
- var options = {
- icons: {
- primary: this.options.comboboxIcon
- },
- text: false,
- disabled: this.disabled
- },
- button = $("<a>")
- .html(" ")
- .attr("tabIndex", -1)
- .attr("title", "Show All Items")
- .button(options)
- .removeClass("ui-corner-all")
- .addClass("ui-corner-right ui-button-icon ui-combo-button");
- return button;
- },
-
- _create: function () {
- /* Initialize a new selectable widget */
- var self = this,
- $input = $(this.element),
- data = $input.data(),
- options, button;
- this.url = data.selectableUrl || data['selectable-url'];
- this.allowNew = data.selectableAllowNew || data['selectable-allow-new'];
- this.allowMultiple = data.selectableMultiple || data['selectable-multiple'];
- this.textName = $input.attr('name');
- this.hiddenName = this.textName.replace(new RegExp('_0$'), '_1');
- this.hiddenSelector = ':input[data-selectable-type=hidden][name=' + this.hiddenName + ']';
- this.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + this.hiddenName + ']';
- this.selectableType = data.selectableType || data['selectable-type'];
- this.disabled = $input.prop('disabled');
- if (this.allowMultiple) {
- this.allowNew = false;
- $input.val("");
- this._initDeck();
- }
- options = data.selectableOptions || data['selectable-options'];
- if (options) {
- this._setOptions(options);
- }
- // Call super-create
- // This could be replaced by this._super() with jQuery UI 1.9
- $.ui.autocomplete.prototype._create.call(this);
- $input.addClass(this.options.defaultClasses[this.selectableType]);
- // Additional work for combobox widgets
- if (this.selectableType === 'combobox') {
- // Add show all items button
- button = this._comboButtonTemplate($input);
- button.insertAfter($input).click(function (e) {
- e.preventDefault();
- // close if already visible
- if (self.widget().is(":visible")) {
- self.close();
- }
- // pass empty string as value to search for, displaying all results
- self._search("");
- $input.focus();
- });
- }
- },
-
- // Override the default source creation
- _initSource: function () {
- var self = this,
- $input = $(this.element);
- this.source = function dataSource(request, response) {
- /* Custom data source to uses the lookup url with pagination
- Adds hook for adjusting query parameters.
- Includes timestamp to prevent browser caching the lookup. */
- var now = new Date().getTime(),
- query = {term: request.term, timestamp: now},
- page = $input.data("page");
- if (self.options.prepareQuery) {
- self.options.prepareQuery.apply(self, [query]);
- }
- if (page) {
- query.page = page;
- }
- function unwrapResponse(data) {
- var results = data.data,
- meta = data.meta;
- if (meta.next_page && meta.more) {
- results.push({
- id: '',
- value: '',
- label: meta.more,
- page: meta.next_page,
- term: request.term
- });
- }
- return response(results);
- }
- $.getJSON(self.url, query, unwrapResponse);
- };
- },
- // Override the default auto-complete render.
- _renderItem: function (ul, item) {
- /* Adds hook for additional formatting, allows HTML in the label,
- highlights term matches and handles pagination. */
- var label = item.label,
- self = this,
- $input = $(this.element),
- re, html, li;
- if (this.options.formatLabel && !item.page) {
- label = this.options.formatLabel.apply(this, [label, item]);
- }
- if (this.options.highlightMatch && this.term && !item.page) {
- re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" +
- $.ui.autocomplete.escapeRegex(this.term) +
- ")(?![^<>]*>)(?![^&;]+;)", "gi");
- if (label.html) {
- html = label.html();
- html = html.replace(re, "<span class='highlight'>$1</span>");
- label.html(html);
- } else {
- label = label.replace(re, "<span class='highlight'>$1</span>");
- }
- }
- li = $("<li></li>")
- .data("item.autocomplete", item)
- .append($("<a></a>").append(label))
- .appendTo(ul);
- if (item.page) {
- li.addClass('selectable-paginator');
- }
- return li;
- },
- // Override the default auto-complete suggest.
- _suggest: function (items) {
- /* Needed for handling pagination links */
- var $input = $(this.element),
- page = $input.data('page'),
- ul = this.menu.element;
- if (page) {
- $('.selectable-paginator', ul).remove();
- } else {
- ul.empty();
- }
- $input.data('page', null);
- ul.zIndex($input.zIndex() + 1);
- this._renderMenu(ul, items);
- // jQuery UI menu does not define deactivate
- if (this.menu.deactivate) {
- this.menu.deactivate();
- }
- this.menu.refresh();
- // size and position menu
- ul.show();
- this._resizeMenu();
- ul.position($.extend({of: this.element}, this.options.position));
- if (this.options.autoFocus) {
- this.menu.next(new $.Event("mouseover"));
- } else if (page) {
- $input.focus();
- }
- },
- // Override default trigger for additional change/select logic
- _trigger: function (type, event, data) {
- var $input = $(this.element),
- self = this;
- if (type === "select") {
- $input.removeClass('ui-state-error');
- if (data.item.page) {
- $input.data("page", data.item.page);
- this._search(data.item.term);
- return false;
- }
- return this.select(data.item, event);
- } else if (type === "change") {
- $input.removeClass('ui-state-error');
- this._setHidden(data.item);
- if ($input.val() && !data.item) {
- if (!this.allowNew) {
- $input.addClass('ui-state-error');
- }
- }
- if (this.allowMultiple && !$input.hasClass('ui-state-error')) {
- $input.val("");
- this.term = "";
- }
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype._trigger.apply(this, arguments);
- },
- close: function (event) {
- var page = $(this.element).data('page');
- if (page !== null) {
- return;
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype.close.apply(this, arguments);
- }
- });
-
- window.bindSelectables = function (context) {
- /* Bind all selectable widgets in a given context.
- Automatically called on document.ready.
- Additional calls can be made for dynamically added widgets.
- */
- $(":input[data-selectable-type=text]", context).djselectable();
- $(":input[data-selectable-type=combobox]", context).djselectable();
- };
-
- function djangoAdminPatches() {
- /* 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" &&
- typeof(windowname_to_id) !== "undefined" &&
- typeof(html_unescape) !== "undefined") {
- var django_dismissAddAnotherPopup = dismissAddAnotherPopup;
- dismissAddAnotherPopup = function (win, newId, newRepr) {
- /* See if the popup came from a selectable field.
- If not, pass control to Django's code.
- If so, handle it. */
- var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */
- var field = $('#' + fieldName);
- var multiField = $('#' + fieldName + '_0');
- /* Check for bound selectable */
- var singleWidget = field.data('djselectable');
- var multiWidget = multiField.data('djselectable');
- if (singleWidget || multiWidget) {
- // newId and newRepr are expected to have previously been escaped by
- // django.utils.html.escape.
- var item = {
- id: html_unescape(newId),
- value: html_unescape(newRepr)
- };
- if (singleWidget) {
- field.djselectable('select', item);
- }
- if (multiWidget) {
- multiField.djselectable('select', item);
- }
- win.close();
- } else {
- /* Not ours, pass on to original function. */
- return django_dismissAddAnotherPopup(win, newId, newRepr);
- }
- };
- }
- }
-
- $(document).ready(function () {
- // Patch the django admin JS
- if (typeof(djselectableAdminPatch) === "undefined" || djselectableAdminPatch) {
- djangoAdminPatches();
- }
- // Bind existing widgets on document ready
- if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) {
- window.bindSelectables('body');
- }
- });
-})(jQuery || grp.jQuery);
+++ /dev/null
-<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/jqueryui/{{ version }}/themes/{{ theme }}/jquery-ui.css" type="text/css">
+++ /dev/null
-{% if version %}<script src="//ajax.googleapis.com/ajax/libs/jquery/{{ version }}/jquery.min.js"></script>{% endif %}
-{% if ui %}<script src="//ajax.googleapis.com/ajax/libs/jqueryui/{{ ui }}/jquery-ui.js"></script>{% endif %}
+++ /dev/null
-from __future__ import unicode_literals
-
-from django import template
-
-register = template.Library()
-
-
-@register.inclusion_tag('selectable/jquery-js.html')
-def include_jquery_libs(version='1.12.4', ui='1.11.4'):
- return {'version': version, 'ui': ui}
-
-
-@register.inclusion_tag('selectable/jquery-css.html')
-def include_ui_theme(theme='smoothness', version='1.11.4'):
- return {'theme': theme, 'version': version}
+++ /dev/null
-from django.db import models
-from django.utils.encoding import python_2_unicode_compatible
-
-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 __str__(self):
- return self.name
-
- class Meta:
- ordering = ['id']
-
-
-@python_2_unicode_compatible
-class OtherThing(models.Model):
- name = models.CharField(max_length=100)
- thing = models.ForeignKey(Thing)
-
- 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 __str__(self):
- return self.name
-
-
-class ThingLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-registry.register(ThingLookup)
+++ /dev/null
-from __future__ import unicode_literals
-
-import random
-import string
-from collections import defaultdict
-from xml.dom.minidom import parseString
-
-
-from django.test import TestCase, override_settings
-
-from . import Thing
-from ..base import ModelLookup
-
-
-def as_xml(html):
- "Convert HTML portion to minidom node."
- return parseString('<root>%s</root>' % html)
-
-
-def parsed_inputs(html):
- "Returns a dictionary mapping name --> node of inputs found in the HTML."
- node = as_xml(html)
- inputs = {}
- for field in node.getElementsByTagName('input'):
- name = dict(field.attributes.items())['name']
- current = inputs.get(name, [])
- current.append(field)
- inputs[name] = current
- return inputs
-
-
-@override_settings(ROOT_URLCONF='selectable.tests.urls')
-class BaseSelectableTestCase(TestCase):
-
- def get_random_string(self, length=10):
- return ''.join(random.choice(string.ascii_letters) for x in range(length))
-
- def create_thing(self, data=None):
- data = data or {}
- defaults = {
- 'name': self.get_random_string(),
- 'description': self.get_random_string(),
- }
- defaults.update(data)
- return Thing.objects.create(**defaults)
-
-
-class SimpleModelLookup(ModelLookup):
- model = Thing
- search_fields = ('name__icontains', )
-
-
-def parsed_widget_attributes(widget):
- """
- Get a dictionary-like object containing all HTML attributes
- of the rendered widget.
-
- Lookups on this object raise ValueError if there is more than one attribute
- of the given name in the HTML, and they have different values.
- """
- # For the tests that use this, it generally doesn't matter what the value
- # is, so we supply anything.
- rendered = widget.render('a_name', 'a_value')
- return AttrMap(rendered)
-
-
-class AttrMap(object):
- def __init__(self, html):
- dom = as_xml(html)
- self._attrs = defaultdict(set)
- self._build_attr_map(dom)
-
- def _build_attr_map(self, dom):
- for node in _walk_nodes(dom):
- if node.attributes is not None:
- for k, v in node.attributes.items():
- self._attrs[k].add(v)
-
- def __contains__(self, key):
- return key in self._attrs and len(self._attrs[key]) > 0
-
- def __getitem__(self, key):
- if key not in self:
- raise KeyError(key)
- vals = self._attrs[key]
- if len(vals) > 1:
- raise ValueError("More than one value for attribute {0}: {1}".
- format(key, ", ".join(vals)))
- else:
- return list(vals)[0]
-
-
-def _walk_nodes(dom):
- yield dom
- for child in dom.childNodes:
- for item in _walk_nodes(child):
- yield item
+++ /dev/null
-from mock import Mock
-
-from selectable.decorators import ajax_required, login_required, staff_member_required
-from selectable.tests.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 BaseFieldTestCase(BaseSelectableTestCase):
- 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(BaseFieldTestCase):
- 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(BaseFieldTestCase):
- 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 selectable.forms import BaseLookupForm
-from selectable.tests.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 selectable.forms import AutoCompleteSelectField, AutoCompleteSelectMultipleField
-from selectable.forms import AutoCompleteSelectWidget, AutoComboboxSelectWidget
-from selectable.tests import ManyThing, OtherThing, ThingLookup
-from selectable.tests.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)
-
-
-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 = 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 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 = SelectWidgetForm(data=data)
- self.assertFalse(form.is_valid())
- self.assertTrue('thing' in 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))
-
-
-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)
\ No newline at end of file
+++ /dev/null
-/* Test utility functions */
-(function ($) {
-
- window.createTextComplete = function (name, attrs) {
- var inputAttrs = {
- 'name': name,
- 'data-selectable-type': 'text',
- 'data-selectable-url': '/lookup/core-fruitlookup/',
- 'type': 'text'
- }, finalAttrs = $.extend({}, inputAttrs, attrs || {});
- return $('<input>', finalAttrs);
- };
-
- window.createTextCombobox = function (name, attrs) {
- // Force change of the name and type
- var inputAttrs = $.extend({
- 'data-selectable-type': 'combobox'
- }, attrs || {});
- return window.createTextComplete(name, inputAttrs);
- };
-
- window.createTextSelect = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0'
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden',
- 'type': 'hidden'
- };
- textInput = window.createTextComplete(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createComboboxSelect = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0'
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden',
- 'type': 'hidden'
- };
- textInput = window.createTextCombobox(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createTextSelectMultiple = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0',
- 'data-selectable-multiple': true,
- 'data-selectable-allow-new': false
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden-multiple',
- 'type': 'hidden'
- };
- textInput = window.createTextComplete(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.createComboboxSelectMultiple = function (name, attrs) {
- var inputAttrs = $.extend({
- 'name': name + '_0',
- 'data-selectable-multiple': true,
- 'data-selectable-allow-new': false
- }, attrs || {}), textInput, hiddenInput,
- hiddenAttrs = {
- 'name': name + '_1',
- 'data-selectable-type': 'hidden-multiple',
- 'type': 'hidden'
- };
- textInput = window.createTextCombobox(name, inputAttrs);
- hiddenInput = $('<input>', hiddenAttrs);
- return [textInput, hiddenInput];
- };
-
- window.simpleLookupResponse = function () {
- var meta = {
- "term": "ap",
- "limit": 25,
- "page": 1,
- "more": "Show more results"
- }, data = [
- {"id": 1, "value": "Apple", "label": "Apple"},
- {"id": 3, "value": "Grape", "label": "Grape"}
- ];
- return {"meta": meta, "data": data};
- };
-
- window.paginatedLookupResponse = function () {
- var meta = {
- "term": "ap",
- "limit": 2,
- "page": 1,
- "more": "Show more results"
- }, data = [
- {"id": 1, "value": "Apple", "label": "Apple"},
- {"id": 3, "value": "Grape", "label": "Grape"},
- {"id": null, "page": 2, "label": "Show more results"}
- ];
- return {"meta": meta, "data": data};
- };
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset="utf-8">
- <title>Django Selectable Test Suite</title>
- <link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.11.0.css" media="screen">
- <script src="jquery-loader.js"></script>
- <script src="http://code.jquery.com/qunit/qunit-1.11.0.js"></script>
- <script src="sinon-1.5.2.js"></script>
- <script src="helpers.js"></script>
- <script>QUnit.config.autostart = false;</script>
- <script data-main="main" src="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.4/require.min.js"></script>
-</head>
-<body>
- <h1 id="qunit-header">Django Selectable Test Suite</h1>
- <h2 id="qunit-banner"></h2>
- <div id="qunit-testrunner-toolbar"></div>
- <h2 id="qunit-userAgent"></h2>
- <ol id="qunit-tests"></ol>
- <div id="qunit-fixture"></div>
-</body>
-</html>
+++ /dev/null
-(function() {
- // Get any jquery=___ param from the query string.
- var jqversion = location.search.match(/[?&]jquery=(.*?)(?=&|$)/);
- var uiversion = location.search.match(/[?&]ui=(.*?)(?=&|$)/);
- var path;
- window.jqversion = jqversion && jqversion[1] || '1.11.2';
- window.uiversion = uiversion && uiversion[1] || '1.11.4';
- jqpath = 'http://code.jquery.com/jquery-' + window.jqversion + '.js';
- uipath = 'http://code.jquery.com/ui/' + window.uiversion + '/jquery-ui.js';
- // This is the only time I'll ever use document.write, I promise!
- document.write('<script src="' + jqpath + '"></script>');
- document.write('<script src="' + uipath + '"></script>');
-}());
+++ /dev/null
-/*global require, QUnit*/
-
-require.config({
- baseUrl: '../../static/selectable/js/',
- paths: {
- selectable: 'jquery.dj.selectable'
- },
- shim: {
- selectable: {
- exports: 'jQuery'
- }
- }
-});
-
-require(['test-methods.js', 'test-events.js', 'test-options.js'], function () {
- //Tests loaded, run Tests
- QUnit.load();
- QUnit.start();
-});
\ No newline at end of file
+++ /dev/null
-/**
- * Sinon.JS 1.5.2, 2012/11/27
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @author Contributors: https://github.com/cjohansen/Sinon.JS/blob/master/AUTHORS
- *
- * (The BSD License)
- *
- * Copyright (c) 2010-2012, Christian Johansen, christian@cjohansen.no
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- * * Neither the name of Christian Johansen nor the names of his contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-var sinon = (function () {
-"use strict";
-
-var buster = (function (setTimeout, B) {
- var isNode = typeof require == "function" && typeof module == "object";
- var div = typeof document != "undefined" && document.createElement("div");
- var F = function () {};
-
- var buster = {
- bind: function bind(obj, methOrProp) {
- var method = typeof methOrProp == "string" ? obj[methOrProp] : methOrProp;
- var args = Array.prototype.slice.call(arguments, 2);
- return function () {
- var allArgs = args.concat(Array.prototype.slice.call(arguments));
- return method.apply(obj, allArgs);
- };
- },
-
- partial: function partial(fn) {
- var args = [].slice.call(arguments, 1);
- return function () {
- return fn.apply(this, args.concat([].slice.call(arguments)));
- };
- },
-
- create: function create(object) {
- F.prototype = object;
- return new F();
- },
-
- extend: function extend(target) {
- if (!target) { return; }
- for (var i = 1, l = arguments.length, prop; i < l; ++i) {
- for (prop in arguments[i]) {
- target[prop] = arguments[i][prop];
- }
- }
- return target;
- },
-
- nextTick: function nextTick(callback) {
- if (typeof process != "undefined" && process.nextTick) {
- return process.nextTick(callback);
- }
- setTimeout(callback, 0);
- },
-
- functionName: function functionName(func) {
- if (!func) return "";
- if (func.displayName) return func.displayName;
- if (func.name) return func.name;
- var matches = func.toString().match(/function\s+([^\(]+)/m);
- return matches && matches[1] || "";
- },
-
- isNode: function isNode(obj) {
- if (!div) return false;
- try {
- obj.appendChild(div);
- obj.removeChild(div);
- } catch (e) {
- return false;
- }
- return true;
- },
-
- isElement: function isElement(obj) {
- return obj && obj.nodeType === 1 && buster.isNode(obj);
- },
-
- isArray: function isArray(arr) {
- return Object.prototype.toString.call(arr) == "[object Array]";
- },
-
- flatten: function flatten(arr) {
- var result = [], arr = arr || [];
- for (var i = 0, l = arr.length; i < l; ++i) {
- result = result.concat(buster.isArray(arr[i]) ? flatten(arr[i]) : arr[i]);
- }
- return result;
- },
-
- each: function each(arr, callback) {
- for (var i = 0, l = arr.length; i < l; ++i) {
- callback(arr[i]);
- }
- },
-
- map: function map(arr, callback) {
- var results = [];
- for (var i = 0, l = arr.length; i < l; ++i) {
- results.push(callback(arr[i]));
- }
- return results;
- },
-
- parallel: function parallel(fns, callback) {
- function cb(err, res) {
- if (typeof callback == "function") {
- callback(err, res);
- callback = null;
- }
- }
- if (fns.length == 0) { return cb(null, []); }
- var remaining = fns.length, results = [];
- function makeDone(num) {
- return function done(err, result) {
- if (err) { return cb(err); }
- results[num] = result;
- if (--remaining == 0) { cb(null, results); }
- };
- }
- for (var i = 0, l = fns.length; i < l; ++i) {
- fns[i](makeDone(i));
- }
- },
-
- series: function series(fns, callback) {
- function cb(err, res) {
- if (typeof callback == "function") {
- callback(err, res);
- }
- }
- var remaining = fns.slice();
- var results = [];
- function callNext() {
- if (remaining.length == 0) return cb(null, results);
- var promise = remaining.shift()(next);
- if (promise && typeof promise.then == "function") {
- promise.then(buster.partial(next, null), next);
- }
- }
- function next(err, result) {
- if (err) return cb(err);
- results.push(result);
- callNext();
- }
- callNext();
- },
-
- countdown: function countdown(num, done) {
- return function () {
- if (--num == 0) done();
- };
- }
- };
-
- if (typeof process === "object" &&
- typeof require === "function" && typeof module === "object") {
- var crypto = require("crypto");
- var path = require("path");
-
- buster.tmpFile = function (fileName) {
- var hashed = crypto.createHash("sha1");
- hashed.update(fileName);
- var tmpfileName = hashed.digest("hex");
-
- if (process.platform == "win32") {
- return path.join(process.env["TEMP"], tmpfileName);
- } else {
- return path.join("/tmp", tmpfileName);
- }
- };
- }
-
- if (Array.prototype.some) {
- buster.some = function (arr, fn, thisp) {
- return arr.some(fn, thisp);
- };
- } else {
- // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/some
- buster.some = function (arr, fun, thisp) {
- if (arr == null) { throw new TypeError(); }
- arr = Object(arr);
- var len = arr.length >>> 0;
- if (typeof fun !== "function") { throw new TypeError(); }
-
- for (var i = 0; i < len; i++) {
- if (arr.hasOwnProperty(i) && fun.call(thisp, arr[i], i, arr)) {
- return true;
- }
- }
-
- return false;
- };
- }
-
- if (Array.prototype.filter) {
- buster.filter = function (arr, fn, thisp) {
- return arr.filter(fn, thisp);
- };
- } else {
- // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
- buster.filter = function (fn, thisp) {
- if (this == null) { throw new TypeError(); }
-
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fn != "function") { throw new TypeError(); }
-
- var res = [];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- var val = t[i]; // in case fun mutates this
- if (fn.call(thisp, val, i, t)) { res.push(val); }
- }
- }
-
- return res;
- };
- }
-
- if (isNode) {
- module.exports = buster;
- buster.eventEmitter = require("./buster-event-emitter");
- Object.defineProperty(buster, "defineVersionGetter", {
- get: function () {
- return require("./define-version-getter");
- }
- });
- }
-
- return buster.extend(B || {}, buster);
-}(setTimeout, buster));
-if (typeof buster === "undefined") {
- var buster = {};
-}
-
-if (typeof module === "object" && typeof require === "function") {
- buster = require("buster-core");
-}
-
-buster.format = buster.format || {};
-buster.format.excludeConstructors = ["Object", /^.$/];
-buster.format.quoteStrings = true;
-
-buster.format.ascii = (function () {
-
- var hasOwn = Object.prototype.hasOwnProperty;
-
- var specialObjects = [];
- if (typeof global != "undefined") {
- specialObjects.push({ obj: global, value: "[object global]" });
- }
- if (typeof document != "undefined") {
- specialObjects.push({ obj: document, value: "[object HTMLDocument]" });
- }
- if (typeof window != "undefined") {
- specialObjects.push({ obj: window, value: "[object Window]" });
- }
-
- function keys(object) {
- var k = Object.keys && Object.keys(object) || [];
-
- if (k.length == 0) {
- for (var prop in object) {
- if (hasOwn.call(object, prop)) {
- k.push(prop);
- }
- }
- }
-
- return k.sort();
- }
-
- function isCircular(object, objects) {
- if (typeof object != "object") {
- return false;
- }
-
- for (var i = 0, l = objects.length; i < l; ++i) {
- if (objects[i] === object) {
- return true;
- }
- }
-
- return false;
- }
-
- function ascii(object, processed, indent) {
- if (typeof object == "string") {
- var quote = typeof this.quoteStrings != "boolean" || this.quoteStrings;
- return processed || quote ? '"' + object + '"' : object;
- }
-
- if (typeof object == "function" && !(object instanceof RegExp)) {
- return ascii.func(object);
- }
-
- processed = processed || [];
-
- if (isCircular(object, processed)) {
- return "[Circular]";
- }
-
- if (Object.prototype.toString.call(object) == "[object Array]") {
- return ascii.array.call(this, object, processed);
- }
-
- if (!object) {
- return "" + object;
- }
-
- if (buster.isElement(object)) {
- return ascii.element(object);
- }
-
- if (typeof object.toString == "function" &&
- object.toString !== Object.prototype.toString) {
- return object.toString();
- }
-
- for (var i = 0, l = specialObjects.length; i < l; i++) {
- if (object === specialObjects[i].obj) {
- return specialObjects[i].value;
- }
- }
-
- return ascii.object.call(this, object, processed, indent);
- }
-
- ascii.func = function (func) {
- return "function " + buster.functionName(func) + "() {}";
- };
-
- ascii.array = function (array, processed) {
- processed = processed || [];
- processed.push(array);
- var pieces = [];
-
- for (var i = 0, l = array.length; i < l; ++i) {
- pieces.push(ascii.call(this, array[i], processed));
- }
-
- return "[" + pieces.join(", ") + "]";
- };
-
- ascii.object = function (object, processed, indent) {
- processed = processed || [];
- processed.push(object);
- indent = indent || 0;
- var pieces = [], properties = keys(object), prop, str, obj;
- var is = "";
- var length = 3;
-
- for (var i = 0, l = indent; i < l; ++i) {
- is += " ";
- }
-
- for (i = 0, l = properties.length; i < l; ++i) {
- prop = properties[i];
- obj = object[prop];
-
- if (isCircular(obj, processed)) {
- str = "[Circular]";
- } else {
- str = ascii.call(this, obj, processed, indent + 2);
- }
-
- str = (/\s/.test(prop) ? '"' + prop + '"' : prop) + ": " + str;
- length += str.length;
- pieces.push(str);
- }
-
- var cons = ascii.constructorName.call(this, object);
- var prefix = cons ? "[" + cons + "] " : ""
-
- return (length + indent) > 80 ?
- prefix + "{\n " + is + pieces.join(",\n " + is) + "\n" + is + "}" :
- prefix + "{ " + pieces.join(", ") + " }";
- };
-
- ascii.element = function (element) {
- var tagName = element.tagName.toLowerCase();
- var attrs = element.attributes, attribute, pairs = [], attrName;
-
- for (var i = 0, l = attrs.length; i < l; ++i) {
- attribute = attrs.item(i);
- attrName = attribute.nodeName.toLowerCase().replace("html:", "");
-
- if (attrName == "contenteditable" && attribute.nodeValue == "inherit") {
- continue;
- }
-
- if (!!attribute.nodeValue) {
- pairs.push(attrName + "=\"" + attribute.nodeValue + "\"");
- }
- }
-
- var formatted = "<" + tagName + (pairs.length > 0 ? " " : "");
- var content = element.innerHTML;
-
- if (content.length > 20) {
- content = content.substr(0, 20) + "[...]";
- }
-
- var res = formatted + pairs.join(" ") + ">" + content + "</" + tagName + ">";
-
- return res.replace(/ contentEditable="inherit"/, "");
- };
-
- ascii.constructorName = function (object) {
- var name = buster.functionName(object && object.constructor);
- var excludes = this.excludeConstructors || buster.format.excludeConstructors || [];
-
- for (var i = 0, l = excludes.length; i < l; ++i) {
- if (typeof excludes[i] == "string" && excludes[i] == name) {
- return "";
- } else if (excludes[i].test && excludes[i].test(name)) {
- return "";
- }
- }
-
- return name;
- };
-
- return ascii;
-}());
-
-if (typeof module != "undefined") {
- module.exports = buster.format;
-}
-/*jslint eqeqeq: false, onevar: false, forin: true, nomen: false, regexp: false, plusplus: false*/
-/*global module, require, __dirname, document*/
-/**
- * Sinon core utilities. For internal use only.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-var sinon = (function (buster) {
- var div = typeof document != "undefined" && document.createElement("div");
- var hasOwn = Object.prototype.hasOwnProperty;
-
- function isDOMNode(obj) {
- var success = false;
-
- try {
- obj.appendChild(div);
- success = div.parentNode == obj;
- } catch (e) {
- return false;
- } finally {
- try {
- obj.removeChild(div);
- } catch (e) {
- // Remove failed, not much we can do about that
- }
- }
-
- return success;
- }
-
- function isElement(obj) {
- return div && obj && obj.nodeType === 1 && isDOMNode(obj);
- }
-
- function isFunction(obj) {
- return typeof obj === "function" || !!(obj && obj.constructor && obj.call && obj.apply);
- }
-
- function mirrorProperties(target, source) {
- for (var prop in source) {
- if (!hasOwn.call(target, prop)) {
- target[prop] = source[prop];
- }
- }
- }
-
- var sinon = {
- wrapMethod: function wrapMethod(object, property, method) {
- if (!object) {
- throw new TypeError("Should wrap property of object");
- }
-
- if (typeof method != "function") {
- throw new TypeError("Method wrapper should be function");
- }
-
- var wrappedMethod = object[property];
-
- if (!isFunction(wrappedMethod)) {
- throw new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
- property + " as function");
- }
-
- if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
- throw new TypeError("Attempted to wrap " + property + " which is already wrapped");
- }
-
- if (wrappedMethod.calledBefore) {
- var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
- throw new TypeError("Attempted to wrap " + property + " which is already " + verb);
- }
-
- // IE 8 does not support hasOwnProperty on the window object.
- var owned = hasOwn.call(object, property);
- object[property] = method;
- method.displayName = property;
-
- method.restore = function () {
- // For prototype properties try to reset by delete first.
- // If this fails (ex: localStorage on mobile safari) then force a reset
- // via direct assignment.
- if (!owned) {
- delete object[property];
- }
- if (object[property] === method) {
- object[property] = wrappedMethod;
- }
- };
-
- method.restore.sinon = true;
- mirrorProperties(method, wrappedMethod);
-
- return method;
- },
-
- extend: function extend(target) {
- for (var i = 1, l = arguments.length; i < l; i += 1) {
- for (var prop in arguments[i]) {
- if (arguments[i].hasOwnProperty(prop)) {
- target[prop] = arguments[i][prop];
- }
-
- // DONT ENUM bug, only care about toString
- if (arguments[i].hasOwnProperty("toString") &&
- arguments[i].toString != target.toString) {
- target.toString = arguments[i].toString;
- }
- }
- }
-
- return target;
- },
-
- create: function create(proto) {
- var F = function () {};
- F.prototype = proto;
- return new F();
- },
-
- deepEqual: function deepEqual(a, b) {
- if (sinon.match && sinon.match.isMatcher(a)) {
- return a.test(b);
- }
- if (typeof a != "object" || typeof b != "object") {
- return a === b;
- }
-
- if (isElement(a) || isElement(b)) {
- return a === b;
- }
-
- if (a === b) {
- return true;
- }
-
- if ((a === null && b !== null) || (a !== null && b === null)) {
- return false;
- }
-
- var aString = Object.prototype.toString.call(a);
- if (aString != Object.prototype.toString.call(b)) {
- return false;
- }
-
- if (aString == "[object Array]") {
- if (a.length !== b.length) {
- return false;
- }
-
- for (var i = 0, l = a.length; i < l; i += 1) {
- if (!deepEqual(a[i], b[i])) {
- return false;
- }
- }
-
- return true;
- }
-
- var prop, aLength = 0, bLength = 0;
-
- for (prop in a) {
- aLength += 1;
-
- if (!deepEqual(a[prop], b[prop])) {
- return false;
- }
- }
-
- for (prop in b) {
- bLength += 1;
- }
-
- if (aLength != bLength) {
- return false;
- }
-
- return true;
- },
-
- functionName: function functionName(func) {
- var name = func.displayName || func.name;
-
- // Use function decomposition as a last resort to get function
- // name. Does not rely on function decomposition to work - if it
- // doesn't debugging will be slightly less informative
- // (i.e. toString will say 'spy' rather than 'myFunc').
- if (!name) {
- var matches = func.toString().match(/function ([^\s\(]+)/);
- name = matches && matches[1];
- }
-
- return name;
- },
-
- functionToString: function toString() {
- if (this.getCall && this.callCount) {
- var thisValue, prop, i = this.callCount;
-
- while (i--) {
- thisValue = this.getCall(i).thisValue;
-
- for (prop in thisValue) {
- if (thisValue[prop] === this) {
- return prop;
- }
- }
- }
- }
-
- return this.displayName || "sinon fake";
- },
-
- getConfig: function (custom) {
- var config = {};
- custom = custom || {};
- var defaults = sinon.defaultConfig;
-
- for (var prop in defaults) {
- if (defaults.hasOwnProperty(prop)) {
- config[prop] = custom.hasOwnProperty(prop) ? custom[prop] : defaults[prop];
- }
- }
-
- return config;
- },
-
- format: function (val) {
- return "" + val;
- },
-
- defaultConfig: {
- injectIntoThis: true,
- injectInto: null,
- properties: ["spy", "stub", "mock", "clock", "server", "requests"],
- useFakeTimers: true,
- useFakeServer: true
- },
-
- timesInWords: function timesInWords(count) {
- return count == 1 && "once" ||
- count == 2 && "twice" ||
- count == 3 && "thrice" ||
- (count || 0) + " times";
- },
-
- calledInOrder: function (spies) {
- for (var i = 1, l = spies.length; i < l; i++) {
- if (!spies[i - 1].calledBefore(spies[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- orderByFirstCall: function (spies) {
- return spies.sort(function (a, b) {
- // uuid, won't ever be equal
- var aCall = a.getCall(0);
- var bCall = b.getCall(0);
- var aId = aCall && aCall.callId || -1;
- var bId = bCall && bCall.callId || -1;
-
- return aId < bId ? -1 : 1;
- });
- },
-
- log: function () {},
-
- logError: function (label, err) {
- var msg = label + " threw exception: "
- sinon.log(msg + "[" + err.name + "] " + err.message);
- if (err.stack) { sinon.log(err.stack); }
-
- setTimeout(function () {
- err.message = msg + err.message;
- throw err;
- }, 0);
- },
-
- typeOf: function (value) {
- if (value === null) {
- return "null";
- }
- else if (value === undefined) {
- return "undefined";
- }
- var string = Object.prototype.toString.call(value);
- return string.substring(8, string.length - 1).toLowerCase();
- }
- };
-
- var isNode = typeof module == "object" && typeof require == "function";
-
- if (isNode) {
- try {
- buster = { format: require("buster-format") };
- } catch (e) {}
- module.exports = sinon;
- module.exports.spy = require("./sinon/spy");
- module.exports.stub = require("./sinon/stub");
- module.exports.mock = require("./sinon/mock");
- module.exports.collection = require("./sinon/collection");
- module.exports.assert = require("./sinon/assert");
- module.exports.sandbox = require("./sinon/sandbox");
- module.exports.test = require("./sinon/test");
- module.exports.testCase = require("./sinon/test_case");
- module.exports.assert = require("./sinon/assert");
- module.exports.match = require("./sinon/match");
- }
-
- if (buster) {
- var formatter = sinon.create(buster.format);
- formatter.quoteStrings = false;
- sinon.format = function () {
- return formatter.ascii.apply(formatter, arguments);
- };
- } else if (isNode) {
- try {
- var util = require("util");
- sinon.format = function (value) {
- return typeof value == "object" && value.toString === Object.prototype.toString ? util.inspect(value) : value;
- };
- } catch (e) {
- /* Node, but no util module - would be very old, but better safe than
- sorry */
- }
- }
-
- return sinon;
-}(typeof buster == "object" && buster));
-
-/* @depend ../sinon.js */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Match functions
- *
- * @author Maximilian Antoni (mail@maxantoni.de)
- * @license BSD
- *
- * Copyright (c) 2012 Maximilian Antoni
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function assertType(value, type, name) {
- var actual = sinon.typeOf(value);
- if (actual !== type) {
- throw new TypeError("Expected type of " + name + " to be " +
- type + ", but was " + actual);
- }
- }
-
- var matcher = {
- toString: function () {
- return this.message;
- }
- };
-
- function isMatcher(object) {
- return matcher.isPrototypeOf(object);
- }
-
- function matchObject(expectation, actual) {
- if (actual === null || actual === undefined) {
- return false;
- }
- for (var key in expectation) {
- if (expectation.hasOwnProperty(key)) {
- var exp = expectation[key];
- var act = actual[key];
- if (match.isMatcher(exp)) {
- if (!exp.test(act)) {
- return false;
- }
- } else if (sinon.typeOf(exp) === "object") {
- if (!matchObject(exp, act)) {
- return false;
- }
- } else if (!sinon.deepEqual(exp, act)) {
- return false;
- }
- }
- }
- return true;
- }
-
- matcher.or = function (m2) {
- if (!isMatcher(m2)) {
- throw new TypeError("Matcher expected");
- }
- var m1 = this;
- var or = sinon.create(matcher);
- or.test = function (actual) {
- return m1.test(actual) || m2.test(actual);
- };
- or.message = m1.message + ".or(" + m2.message + ")";
- return or;
- };
-
- matcher.and = function (m2) {
- if (!isMatcher(m2)) {
- throw new TypeError("Matcher expected");
- }
- var m1 = this;
- var and = sinon.create(matcher);
- and.test = function (actual) {
- return m1.test(actual) && m2.test(actual);
- };
- and.message = m1.message + ".and(" + m2.message + ")";
- return and;
- };
-
- var match = function (expectation, message) {
- var m = sinon.create(matcher);
- var type = sinon.typeOf(expectation);
- switch (type) {
- case "object":
- if (typeof expectation.test === "function") {
- m.test = function (actual) {
- return expectation.test(actual) === true;
- };
- m.message = "match(" + sinon.functionName(expectation.test) + ")";
- return m;
- }
- var str = [];
- for (var key in expectation) {
- if (expectation.hasOwnProperty(key)) {
- str.push(key + ": " + expectation[key]);
- }
- }
- m.test = function (actual) {
- return matchObject(expectation, actual);
- };
- m.message = "match(" + str.join(", ") + ")";
- break;
- case "number":
- m.test = function (actual) {
- return expectation == actual;
- };
- break;
- case "string":
- m.test = function (actual) {
- if (typeof actual !== "string") {
- return false;
- }
- return actual.indexOf(expectation) !== -1;
- };
- m.message = "match(\"" + expectation + "\")";
- break;
- case "regexp":
- m.test = function (actual) {
- if (typeof actual !== "string") {
- return false;
- }
- return expectation.test(actual);
- };
- break;
- case "function":
- m.test = expectation;
- if (message) {
- m.message = message;
- } else {
- m.message = "match(" + sinon.functionName(expectation) + ")";
- }
- break;
- default:
- m.test = function (actual) {
- return sinon.deepEqual(expectation, actual);
- };
- }
- if (!m.message) {
- m.message = "match(" + expectation + ")";
- }
- return m;
- };
-
- match.isMatcher = isMatcher;
-
- match.any = match(function () {
- return true;
- }, "any");
-
- match.defined = match(function (actual) {
- return actual !== null && actual !== undefined;
- }, "defined");
-
- match.truthy = match(function (actual) {
- return !!actual;
- }, "truthy");
-
- match.falsy = match(function (actual) {
- return !actual;
- }, "falsy");
-
- match.same = function (expectation) {
- return match(function (actual) {
- return expectation === actual;
- }, "same(" + expectation + ")");
- };
-
- match.typeOf = function (type) {
- assertType(type, "string", "type");
- return match(function (actual) {
- return sinon.typeOf(actual) === type;
- }, "typeOf(\"" + type + "\")");
- };
-
- match.instanceOf = function (type) {
- assertType(type, "function", "type");
- return match(function (actual) {
- return actual instanceof type;
- }, "instanceOf(" + sinon.functionName(type) + ")");
- };
-
- function createPropertyMatcher(propertyTest, messagePrefix) {
- return function (property, value) {
- assertType(property, "string", "property");
- var onlyProperty = arguments.length === 1;
- var message = messagePrefix + "(\"" + property + "\"";
- if (!onlyProperty) {
- message += ", " + value;
- }
- message += ")";
- return match(function (actual) {
- if (actual === undefined || actual === null ||
- !propertyTest(actual, property)) {
- return false;
- }
- return onlyProperty || sinon.deepEqual(value, actual[property]);
- }, message);
- };
- }
-
- match.has = createPropertyMatcher(function (actual, property) {
- if (typeof actual === "object") {
- return property in actual;
- }
- return actual[property] !== undefined;
- }, "has");
-
- match.hasOwn = createPropertyMatcher(function (actual, property) {
- return actual.hasOwnProperty(property);
- }, "hasOwn");
-
- match.bool = match.typeOf("boolean");
- match.number = match.typeOf("number");
- match.string = match.typeOf("string");
- match.object = match.typeOf("object");
- match.func = match.typeOf("function");
- match.array = match.typeOf("array");
- match.regexp = match.typeOf("regexp");
- match.date = match.typeOf("date");
-
- if (commonJSModule) {
- module.exports = match;
- } else {
- sinon.match = match;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend match.js
- */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Spy functions
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var spyCall;
- var callId = 0;
- var push = [].push;
- var slice = Array.prototype.slice;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function spy(object, property) {
- if (!property && typeof object == "function") {
- return spy.create(object);
- }
-
- if (!object && !property) {
- return spy.create(function () {});
- }
-
- var method = object[property];
- return sinon.wrapMethod(object, property, spy.create(method));
- }
-
- sinon.extend(spy, (function () {
-
- function delegateToCalls(api, method, matchAny, actual, notCalled) {
- api[method] = function () {
- if (!this.called) {
- if (notCalled) {
- return notCalled.apply(this, arguments);
- }
- return false;
- }
-
- var currentCall;
- var matches = 0;
-
- for (var i = 0, l = this.callCount; i < l; i += 1) {
- currentCall = this.getCall(i);
-
- if (currentCall[actual || method].apply(currentCall, arguments)) {
- matches += 1;
-
- if (matchAny) {
- return true;
- }
- }
- }
-
- return matches === this.callCount;
- };
- }
-
- function matchingFake(fakes, args, strict) {
- if (!fakes) {
- return;
- }
-
- var alen = args.length;
-
- for (var i = 0, l = fakes.length; i < l; i++) {
- if (fakes[i].matches(args, strict)) {
- return fakes[i];
- }
- }
- }
-
- function incrementCallCount() {
- this.called = true;
- this.callCount += 1;
- this.notCalled = false;
- this.calledOnce = this.callCount == 1;
- this.calledTwice = this.callCount == 2;
- this.calledThrice = this.callCount == 3;
- }
-
- function createCallProperties() {
- this.firstCall = this.getCall(0);
- this.secondCall = this.getCall(1);
- this.thirdCall = this.getCall(2);
- this.lastCall = this.getCall(this.callCount - 1);
- }
-
- var vars = "a,b,c,d,e,f,g,h,i,j,k,l";
- function createProxy(func) {
- // Retain the function length:
- var p;
- if (func.length) {
- eval("p = (function proxy(" + vars.substring(0, func.length * 2 - 1) +
- ") { return p.invoke(func, this, slice.call(arguments)); });");
- }
- else {
- p = function proxy() {
- return p.invoke(func, this, slice.call(arguments));
- };
- }
- return p;
- }
-
- var uuid = 0;
-
- // Public API
- var spyApi = {
- reset: function () {
- this.called = false;
- this.notCalled = true;
- this.calledOnce = false;
- this.calledTwice = false;
- this.calledThrice = false;
- this.callCount = 0;
- this.firstCall = null;
- this.secondCall = null;
- this.thirdCall = null;
- this.lastCall = null;
- this.args = [];
- this.returnValues = [];
- this.thisValues = [];
- this.exceptions = [];
- this.callIds = [];
- if (this.fakes) {
- for (var i = 0; i < this.fakes.length; i++) {
- this.fakes[i].reset();
- }
- }
- },
-
- create: function create(func) {
- var name;
-
- if (typeof func != "function") {
- func = function () {};
- } else {
- name = sinon.functionName(func);
- }
-
- var proxy = createProxy(func);
-
- sinon.extend(proxy, spy);
- delete proxy.create;
- sinon.extend(proxy, func);
-
- proxy.reset();
- proxy.prototype = func.prototype;
- proxy.displayName = name || "spy";
- proxy.toString = sinon.functionToString;
- proxy._create = sinon.spy.create;
- proxy.id = "spy#" + uuid++;
-
- return proxy;
- },
-
- invoke: function invoke(func, thisValue, args) {
- var matching = matchingFake(this.fakes, args);
- var exception, returnValue;
-
- incrementCallCount.call(this);
- push.call(this.thisValues, thisValue);
- push.call(this.args, args);
- push.call(this.callIds, callId++);
-
- try {
- if (matching) {
- returnValue = matching.invoke(func, thisValue, args);
- } else {
- returnValue = (this.func || func).apply(thisValue, args);
- }
- } catch (e) {
- push.call(this.returnValues, undefined);
- exception = e;
- throw e;
- } finally {
- push.call(this.exceptions, exception);
- }
-
- push.call(this.returnValues, returnValue);
-
- createCallProperties.call(this);
-
- return returnValue;
- },
-
- getCall: function getCall(i) {
- if (i < 0 || i >= this.callCount) {
- return null;
- }
-
- return spyCall.create(this, this.thisValues[i], this.args[i],
- this.returnValues[i], this.exceptions[i],
- this.callIds[i]);
- },
-
- calledBefore: function calledBefore(spyFn) {
- if (!this.called) {
- return false;
- }
-
- if (!spyFn.called) {
- return true;
- }
-
- return this.callIds[0] < spyFn.callIds[spyFn.callIds.length - 1];
- },
-
- calledAfter: function calledAfter(spyFn) {
- if (!this.called || !spyFn.called) {
- return false;
- }
-
- return this.callIds[this.callCount - 1] > spyFn.callIds[spyFn.callCount - 1];
- },
-
- withArgs: function () {
- var args = slice.call(arguments);
-
- if (this.fakes) {
- var match = matchingFake(this.fakes, args, true);
-
- if (match) {
- return match;
- }
- } else {
- this.fakes = [];
- }
-
- var original = this;
- var fake = this._create();
- fake.matchingAguments = args;
- push.call(this.fakes, fake);
-
- fake.withArgs = function () {
- return original.withArgs.apply(original, arguments);
- };
-
- for (var i = 0; i < this.args.length; i++) {
- if (fake.matches(this.args[i])) {
- incrementCallCount.call(fake);
- push.call(fake.thisValues, this.thisValues[i]);
- push.call(fake.args, this.args[i]);
- push.call(fake.returnValues, this.returnValues[i]);
- push.call(fake.exceptions, this.exceptions[i]);
- push.call(fake.callIds, this.callIds[i]);
- }
- }
- createCallProperties.call(fake);
-
- return fake;
- },
-
- matches: function (args, strict) {
- var margs = this.matchingAguments;
-
- if (margs.length <= args.length &&
- sinon.deepEqual(margs, args.slice(0, margs.length))) {
- return !strict || margs.length == args.length;
- }
- },
-
- printf: function (format) {
- var spy = this;
- var args = slice.call(arguments, 1);
- var formatter;
-
- return (format || "").replace(/%(.)/g, function (match, specifyer) {
- formatter = spyApi.formatters[specifyer];
-
- if (typeof formatter == "function") {
- return formatter.call(null, spy, args);
- } else if (!isNaN(parseInt(specifyer), 10)) {
- return sinon.format(args[specifyer - 1]);
- }
-
- return "%" + specifyer;
- });
- }
- };
-
- delegateToCalls(spyApi, "calledOn", true);
- delegateToCalls(spyApi, "alwaysCalledOn", false, "calledOn");
- delegateToCalls(spyApi, "calledWith", true);
- delegateToCalls(spyApi, "calledWithMatch", true);
- delegateToCalls(spyApi, "alwaysCalledWith", false, "calledWith");
- delegateToCalls(spyApi, "alwaysCalledWithMatch", false, "calledWithMatch");
- delegateToCalls(spyApi, "calledWithExactly", true);
- delegateToCalls(spyApi, "alwaysCalledWithExactly", false, "calledWithExactly");
- delegateToCalls(spyApi, "neverCalledWith", false, "notCalledWith",
- function () { return true; });
- delegateToCalls(spyApi, "neverCalledWithMatch", false, "notCalledWithMatch",
- function () { return true; });
- delegateToCalls(spyApi, "threw", true);
- delegateToCalls(spyApi, "alwaysThrew", false, "threw");
- delegateToCalls(spyApi, "returned", true);
- delegateToCalls(spyApi, "alwaysReturned", false, "returned");
- delegateToCalls(spyApi, "calledWithNew", true);
- delegateToCalls(spyApi, "alwaysCalledWithNew", false, "calledWithNew");
- delegateToCalls(spyApi, "callArg", false, "callArgWith", function () {
- throw new Error(this.toString() + " cannot call arg since it was not yet invoked.");
- });
- spyApi.callArgWith = spyApi.callArg;
- delegateToCalls(spyApi, "yield", false, "yield", function () {
- throw new Error(this.toString() + " cannot yield since it was not yet invoked.");
- });
- // "invokeCallback" is an alias for "yield" since "yield" is invalid in strict mode.
- spyApi.invokeCallback = spyApi.yield;
- delegateToCalls(spyApi, "yieldTo", false, "yieldTo", function (property) {
- throw new Error(this.toString() + " cannot yield to '" + property +
- "' since it was not yet invoked.");
- });
-
- spyApi.formatters = {
- "c": function (spy) {
- return sinon.timesInWords(spy.callCount);
- },
-
- "n": function (spy) {
- return spy.toString();
- },
-
- "C": function (spy) {
- var calls = [];
-
- for (var i = 0, l = spy.callCount; i < l; ++i) {
- push.call(calls, " " + spy.getCall(i).toString());
- }
-
- return calls.length > 0 ? "\n" + calls.join("\n") : "";
- },
-
- "t": function (spy) {
- var objects = [];
-
- for (var i = 0, l = spy.callCount; i < l; ++i) {
- push.call(objects, sinon.format(spy.thisValues[i]));
- }
-
- return objects.join(", ");
- },
-
- "*": function (spy, args) {
- var formatted = [];
-
- for (var i = 0, l = args.length; i < l; ++i) {
- push.call(formatted, sinon.format(args[i]));
- }
-
- return formatted.join(", ");
- }
- };
-
- return spyApi;
- }()));
-
- spyCall = (function () {
-
- function throwYieldError(proxy, text, args) {
- var msg = sinon.functionName(proxy) + text;
- if (args.length) {
- msg += " Received [" + slice.call(args).join(", ") + "]";
- }
- throw new Error(msg);
- }
-
- var callApi = {
- create: function create(spy, thisValue, args, returnValue, exception, id) {
- var proxyCall = sinon.create(spyCall);
- delete proxyCall.create;
- proxyCall.proxy = spy;
- proxyCall.thisValue = thisValue;
- proxyCall.args = args;
- proxyCall.returnValue = returnValue;
- proxyCall.exception = exception;
- proxyCall.callId = typeof id == "number" && id || callId++;
-
- return proxyCall;
- },
-
- calledOn: function calledOn(thisValue) {
- if (sinon.match && sinon.match.isMatcher(thisValue)) {
- return thisValue.test(this.thisValue);
- }
- return this.thisValue === thisValue;
- },
-
- calledWith: function calledWith() {
- for (var i = 0, l = arguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(arguments[i], this.args[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- calledWithMatch: function calledWithMatch() {
- for (var i = 0, l = arguments.length; i < l; i += 1) {
- var actual = this.args[i];
- var expectation = arguments[i];
- if (!sinon.match || !sinon.match(expectation).test(actual)) {
- return false;
- }
- }
- return true;
- },
-
- calledWithExactly: function calledWithExactly() {
- return arguments.length == this.args.length &&
- this.calledWith.apply(this, arguments);
- },
-
- notCalledWith: function notCalledWith() {
- return !this.calledWith.apply(this, arguments);
- },
-
- notCalledWithMatch: function notCalledWithMatch() {
- return !this.calledWithMatch.apply(this, arguments);
- },
-
- returned: function returned(value) {
- return sinon.deepEqual(value, this.returnValue);
- },
-
- threw: function threw(error) {
- if (typeof error == "undefined" || !this.exception) {
- return !!this.exception;
- }
-
- if (typeof error == "string") {
- return this.exception.name == error;
- }
-
- return this.exception === error;
- },
-
- calledWithNew: function calledWithNew(thisValue) {
- return this.thisValue instanceof this.proxy;
- },
-
- calledBefore: function (other) {
- return this.callId < other.callId;
- },
-
- calledAfter: function (other) {
- return this.callId > other.callId;
- },
-
- callArg: function (pos) {
- this.args[pos]();
- },
-
- callArgWith: function (pos) {
- var args = slice.call(arguments, 1);
- this.args[pos].apply(null, args);
- },
-
- "yield": function () {
- var args = this.args;
- for (var i = 0, l = args.length; i < l; ++i) {
- if (typeof args[i] === "function") {
- args[i].apply(null, slice.call(arguments));
- return;
- }
- }
- throwYieldError(this.proxy, " cannot yield since no callback was passed.", args);
- },
-
- yieldTo: function (prop) {
- var args = this.args;
- for (var i = 0, l = args.length; i < l; ++i) {
- if (args[i] && typeof args[i][prop] === "function") {
- args[i][prop].apply(null, slice.call(arguments, 1));
- return;
- }
- }
- throwYieldError(this.proxy, " cannot yield to '" + prop +
- "' since no callback was passed.", args);
- },
-
- toString: function () {
- var callStr = this.proxy.toString() + "(";
- var args = [];
-
- for (var i = 0, l = this.args.length; i < l; ++i) {
- push.call(args, sinon.format(this.args[i]));
- }
-
- callStr = callStr + args.join(", ") + ")";
-
- if (typeof this.returnValue != "undefined") {
- callStr += " => " + sinon.format(this.returnValue);
- }
-
- if (this.exception) {
- callStr += " !" + this.exception.name;
-
- if (this.exception.message) {
- callStr += "(" + this.exception.message + ")";
- }
- }
-
- return callStr;
- }
- };
- callApi.invokeCallback = callApi.yield;
- return callApi;
- }());
-
- spy.spyCall = spyCall;
-
- // This steps outside the module sandbox and will be removed
- sinon.spyCall = spyCall;
-
- if (commonJSModule) {
- module.exports = spy;
- } else {
- sinon.spy = spy;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend spy.js
- */
-/*jslint eqeqeq: false, onevar: false*/
-/*global module, require, sinon*/
-/**
- * Stub functions
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function stub(object, property, func) {
- if (!!func && typeof func != "function") {
- throw new TypeError("Custom stub should be function");
- }
-
- var wrapper;
-
- if (func) {
- wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
- } else {
- wrapper = stub.create();
- }
-
- if (!object && !property) {
- return sinon.stub.create();
- }
-
- if (!property && !!object && typeof object == "object") {
- for (var prop in object) {
- if (typeof object[prop] === "function") {
- stub(object, prop);
- }
- }
-
- return object;
- }
-
- return sinon.wrapMethod(object, property, wrapper);
- }
-
- function getChangingValue(stub, property) {
- var index = stub.callCount - 1;
- var prop = index in stub[property] ? stub[property][index] : stub[property + "Last"];
- stub[property + "Last"] = prop;
-
- return prop;
- }
-
- function getCallback(stub, args) {
- var callArgAt = getChangingValue(stub, "callArgAts");
-
- if (callArgAt < 0) {
- var callArgProp = getChangingValue(stub, "callArgProps");
-
- for (var i = 0, l = args.length; i < l; ++i) {
- if (!callArgProp && typeof args[i] == "function") {
- return args[i];
- }
-
- if (callArgProp && args[i] &&
- typeof args[i][callArgProp] == "function") {
- return args[i][callArgProp];
- }
- }
-
- return null;
- }
-
- return args[callArgAt];
- }
-
- var join = Array.prototype.join;
-
- function getCallbackError(stub, func, args) {
- if (stub.callArgAtsLast < 0) {
- var msg;
-
- if (stub.callArgPropsLast) {
- msg = sinon.functionName(stub) +
- " expected to yield to '" + stub.callArgPropsLast +
- "', but no object with such a property was passed."
- } else {
- msg = sinon.functionName(stub) +
- " expected to yield, but no callback was passed."
- }
-
- if (args.length > 0) {
- msg += " Received [" + join.call(args, ", ") + "]";
- }
-
- return msg;
- }
-
- return "argument at index " + stub.callArgAtsLast + " is not a function: " + func;
- }
-
- var nextTick = (function () {
- if (typeof process === "object" && typeof process.nextTick === "function") {
- return process.nextTick;
- } else if (typeof msSetImmediate === "function") {
- return msSetImmediate.bind(window);
- } else if (typeof setImmediate === "function") {
- return setImmediate;
- } else {
- return function (callback) {
- setTimeout(callback, 0);
- };
- }
- })();
-
- function callCallback(stub, args) {
- if (stub.callArgAts.length > 0) {
- var func = getCallback(stub, args);
-
- if (typeof func != "function") {
- throw new TypeError(getCallbackError(stub, func, args));
- }
-
- var index = stub.callCount - 1;
-
- var callbackArguments = getChangingValue(stub, "callbackArguments");
- var callbackContext = getChangingValue(stub, "callbackContexts");
-
- if (stub.callbackAsync) {
- nextTick(function() {
- func.apply(callbackContext, callbackArguments);
- });
- } else {
- func.apply(callbackContext, callbackArguments);
- }
- }
- }
-
- var uuid = 0;
-
- sinon.extend(stub, (function () {
- var slice = Array.prototype.slice, proto;
-
- function throwsException(error, message) {
- if (typeof error == "string") {
- this.exception = new Error(message || "");
- this.exception.name = error;
- } else if (!error) {
- this.exception = new Error("Error");
- } else {
- this.exception = error;
- }
-
- return this;
- }
-
- proto = {
- create: function create() {
- var functionStub = function () {
-
- callCallback(functionStub, arguments);
-
- if (functionStub.exception) {
- throw functionStub.exception;
- } else if (typeof functionStub.returnArgAt == 'number') {
- return arguments[functionStub.returnArgAt];
- } else if (functionStub.returnThis) {
- return this;
- }
- return functionStub.returnValue;
- };
-
- functionStub.id = "stub#" + uuid++;
- var orig = functionStub;
- functionStub = sinon.spy.create(functionStub);
- functionStub.func = orig;
-
- functionStub.callArgAts = [];
- functionStub.callbackArguments = [];
- functionStub.callbackContexts = [];
- functionStub.callArgProps = [];
-
- sinon.extend(functionStub, stub);
- functionStub._create = sinon.stub.create;
- functionStub.displayName = "stub";
- functionStub.toString = sinon.functionToString;
-
- return functionStub;
- },
-
- returns: function returns(value) {
- this.returnValue = value;
-
- return this;
- },
-
- returnsArg: function returnsArg(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.returnArgAt = pos;
-
- return this;
- },
-
- returnsThis: function returnsThis() {
- this.returnThis = true;
-
- return this;
- },
-
- "throws": throwsException,
- throwsException: throwsException,
-
- callsArg: function callsArg(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push([]);
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgOn: function callsArgOn(pos, context) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push([]);
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgWith: function callsArgWith(pos) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- callsArgOnWith: function callsArgWith(pos, context) {
- if (typeof pos != "number") {
- throw new TypeError("argument index is not number");
- }
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(pos);
- this.callbackArguments.push(slice.call(arguments, 2));
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yields: function () {
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 0));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yieldsOn: function (context) {
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(context);
- this.callArgProps.push(undefined);
-
- return this;
- },
-
- yieldsTo: function (prop) {
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 1));
- this.callbackContexts.push(undefined);
- this.callArgProps.push(prop);
-
- return this;
- },
-
- yieldsToOn: function (prop, context) {
- if (typeof context != "object") {
- throw new TypeError("argument context is not an object");
- }
-
- this.callArgAts.push(-1);
- this.callbackArguments.push(slice.call(arguments, 2));
- this.callbackContexts.push(context);
- this.callArgProps.push(prop);
-
- return this;
- }
- };
-
- // create asynchronous versions of callsArg* and yields* methods
- for (var method in proto) {
- // need to avoid creating anotherasync versions of the newly added async methods
- if (proto.hasOwnProperty(method) &&
- method.match(/^(callsArg|yields|thenYields$)/) &&
- !method.match(/Async/)) {
- proto[method + 'Async'] = (function (syncFnName) {
- return function () {
- this.callbackAsync = true;
- return this[syncFnName].apply(this, arguments);
- };
- })(method);
- }
- }
-
- return proto;
-
- }()));
-
- if (commonJSModule) {
- module.exports = stub;
- } else {
- sinon.stub = stub;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- */
-/*jslint eqeqeq: false, onevar: false, nomen: false*/
-/*global module, require, sinon*/
-/**
- * Mock functions.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var push = [].push;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function mock(object) {
- if (!object) {
- return sinon.expectation.create("Anonymous mock");
- }
-
- return mock.create(object);
- }
-
- sinon.mock = mock;
-
- sinon.extend(mock, (function () {
- function each(collection, callback) {
- if (!collection) {
- return;
- }
-
- for (var i = 0, l = collection.length; i < l; i += 1) {
- callback(collection[i]);
- }
- }
-
- return {
- create: function create(object) {
- if (!object) {
- throw new TypeError("object is null");
- }
-
- var mockObject = sinon.extend({}, mock);
- mockObject.object = object;
- delete mockObject.create;
-
- return mockObject;
- },
-
- expects: function expects(method) {
- if (!method) {
- throw new TypeError("method is falsy");
- }
-
- if (!this.expectations) {
- this.expectations = {};
- this.proxies = [];
- }
-
- if (!this.expectations[method]) {
- this.expectations[method] = [];
- var mockObject = this;
-
- sinon.wrapMethod(this.object, method, function () {
- return mockObject.invokeMethod(method, this, arguments);
- });
-
- push.call(this.proxies, method);
- }
-
- var expectation = sinon.expectation.create(method);
- push.call(this.expectations[method], expectation);
-
- return expectation;
- },
-
- restore: function restore() {
- var object = this.object;
-
- each(this.proxies, function (proxy) {
- if (typeof object[proxy].restore == "function") {
- object[proxy].restore();
- }
- });
- },
-
- verify: function verify() {
- var expectations = this.expectations || {};
- var messages = [], met = [];
-
- each(this.proxies, function (proxy) {
- each(expectations[proxy], function (expectation) {
- if (!expectation.met()) {
- push.call(messages, expectation.toString());
- } else {
- push.call(met, expectation.toString());
- }
- });
- });
-
- this.restore();
-
- if (messages.length > 0) {
- sinon.expectation.fail(messages.concat(met).join("\n"));
- } else {
- sinon.expectation.pass(messages.concat(met).join("\n"));
- }
-
- return true;
- },
-
- invokeMethod: function invokeMethod(method, thisValue, args) {
- var expectations = this.expectations && this.expectations[method];
- var length = expectations && expectations.length || 0, i;
-
- for (i = 0; i < length; i += 1) {
- if (!expectations[i].met() &&
- expectations[i].allowsCall(thisValue, args)) {
- return expectations[i].apply(thisValue, args);
- }
- }
-
- var messages = [], available, exhausted = 0;
-
- for (i = 0; i < length; i += 1) {
- if (expectations[i].allowsCall(thisValue, args)) {
- available = available || expectations[i];
- } else {
- exhausted += 1;
- }
- push.call(messages, " " + expectations[i].toString());
- }
-
- if (exhausted === 0) {
- return available.apply(thisValue, args);
- }
-
- messages.unshift("Unexpected call: " + sinon.spyCall.toString.call({
- proxy: method,
- args: args
- }));
-
- sinon.expectation.fail(messages.join("\n"));
- }
- };
- }()));
-
- var times = sinon.timesInWords;
-
- sinon.expectation = (function () {
- var slice = Array.prototype.slice;
- var _invoke = sinon.spy.invoke;
-
- function callCountInWords(callCount) {
- if (callCount == 0) {
- return "never called";
- } else {
- return "called " + times(callCount);
- }
- }
-
- function expectedCallCountInWords(expectation) {
- var min = expectation.minCalls;
- var max = expectation.maxCalls;
-
- if (typeof min == "number" && typeof max == "number") {
- var str = times(min);
-
- if (min != max) {
- str = "at least " + str + " and at most " + times(max);
- }
-
- return str;
- }
-
- if (typeof min == "number") {
- return "at least " + times(min);
- }
-
- return "at most " + times(max);
- }
-
- function receivedMinCalls(expectation) {
- var hasMinLimit = typeof expectation.minCalls == "number";
- return !hasMinLimit || expectation.callCount >= expectation.minCalls;
- }
-
- function receivedMaxCalls(expectation) {
- if (typeof expectation.maxCalls != "number") {
- return false;
- }
-
- return expectation.callCount == expectation.maxCalls;
- }
-
- return {
- minCalls: 1,
- maxCalls: 1,
-
- create: function create(methodName) {
- var expectation = sinon.extend(sinon.stub.create(), sinon.expectation);
- delete expectation.create;
- expectation.method = methodName;
-
- return expectation;
- },
-
- invoke: function invoke(func, thisValue, args) {
- this.verifyCallAllowed(thisValue, args);
-
- return _invoke.apply(this, arguments);
- },
-
- atLeast: function atLeast(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not number");
- }
-
- if (!this.limitsSet) {
- this.maxCalls = null;
- this.limitsSet = true;
- }
-
- this.minCalls = num;
-
- return this;
- },
-
- atMost: function atMost(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not number");
- }
-
- if (!this.limitsSet) {
- this.minCalls = null;
- this.limitsSet = true;
- }
-
- this.maxCalls = num;
-
- return this;
- },
-
- never: function never() {
- return this.exactly(0);
- },
-
- once: function once() {
- return this.exactly(1);
- },
-
- twice: function twice() {
- return this.exactly(2);
- },
-
- thrice: function thrice() {
- return this.exactly(3);
- },
-
- exactly: function exactly(num) {
- if (typeof num != "number") {
- throw new TypeError("'" + num + "' is not a number");
- }
-
- this.atLeast(num);
- return this.atMost(num);
- },
-
- met: function met() {
- return !this.failed && receivedMinCalls(this);
- },
-
- verifyCallAllowed: function verifyCallAllowed(thisValue, args) {
- if (receivedMaxCalls(this)) {
- this.failed = true;
- sinon.expectation.fail(this.method + " already called " + times(this.maxCalls));
- }
-
- if ("expectedThis" in this && this.expectedThis !== thisValue) {
- sinon.expectation.fail(this.method + " called with " + thisValue + " as thisValue, expected " +
- this.expectedThis);
- }
-
- if (!("expectedArguments" in this)) {
- return;
- }
-
- if (!args) {
- sinon.expectation.fail(this.method + " received no arguments, expected " +
- sinon.format(this.expectedArguments));
- }
-
- if (args.length < this.expectedArguments.length) {
- sinon.expectation.fail(this.method + " received too few arguments (" + sinon.format(args) +
- "), expected " + sinon.format(this.expectedArguments));
- }
-
- if (this.expectsExactArgCount &&
- args.length != this.expectedArguments.length) {
- sinon.expectation.fail(this.method + " received too many arguments (" + sinon.format(args) +
- "), expected " + sinon.format(this.expectedArguments));
- }
-
- for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
- sinon.expectation.fail(this.method + " received wrong arguments " + sinon.format(args) +
- ", expected " + sinon.format(this.expectedArguments));
- }
- }
- },
-
- allowsCall: function allowsCall(thisValue, args) {
- if (this.met() && receivedMaxCalls(this)) {
- return false;
- }
-
- if ("expectedThis" in this && this.expectedThis !== thisValue) {
- return false;
- }
-
- if (!("expectedArguments" in this)) {
- return true;
- }
-
- args = args || [];
-
- if (args.length < this.expectedArguments.length) {
- return false;
- }
-
- if (this.expectsExactArgCount &&
- args.length != this.expectedArguments.length) {
- return false;
- }
-
- for (var i = 0, l = this.expectedArguments.length; i < l; i += 1) {
- if (!sinon.deepEqual(this.expectedArguments[i], args[i])) {
- return false;
- }
- }
-
- return true;
- },
-
- withArgs: function withArgs() {
- this.expectedArguments = slice.call(arguments);
- return this;
- },
-
- withExactArgs: function withExactArgs() {
- this.withArgs.apply(this, arguments);
- this.expectsExactArgCount = true;
- return this;
- },
-
- on: function on(thisValue) {
- this.expectedThis = thisValue;
- return this;
- },
-
- toString: function () {
- var args = (this.expectedArguments || []).slice();
-
- if (!this.expectsExactArgCount) {
- push.call(args, "[...]");
- }
-
- var callStr = sinon.spyCall.toString.call({
- proxy: this.method, args: args
- });
-
- var message = callStr.replace(", [...", "[, ...") + " " +
- expectedCallCountInWords(this);
-
- if (this.met()) {
- return "Expectation met: " + message;
- }
-
- return "Expected " + message + " (" +
- callCountInWords(this.callCount) + ")";
- },
-
- verify: function verify() {
- if (!this.met()) {
- sinon.expectation.fail(this.toString());
- } else {
- sinon.expectation.pass(this.toString());
- }
-
- return true;
- },
-
- pass: function(message) {
- sinon.assert.pass(message);
- },
- fail: function (message) {
- var exception = new Error(message);
- exception.name = "ExpectationError";
-
- throw exception;
- }
- };
- }());
-
- if (commonJSModule) {
- module.exports = mock;
- } else {
- sinon.mock = mock;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- * @depend mock.js
- */
-/*jslint eqeqeq: false, onevar: false, forin: true*/
-/*global module, require, sinon*/
-/**
- * Collections of stubs, spies and mocks.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var push = [].push;
- var hasOwnProperty = Object.prototype.hasOwnProperty;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function getFakes(fakeCollection) {
- if (!fakeCollection.fakes) {
- fakeCollection.fakes = [];
- }
-
- return fakeCollection.fakes;
- }
-
- function each(fakeCollection, method) {
- var fakes = getFakes(fakeCollection);
-
- for (var i = 0, l = fakes.length; i < l; i += 1) {
- if (typeof fakes[i][method] == "function") {
- fakes[i][method]();
- }
- }
- }
-
- function compact(fakeCollection) {
- var fakes = getFakes(fakeCollection);
- var i = 0;
- while (i < fakes.length) {
- fakes.splice(i, 1);
- }
- }
-
- var collection = {
- verify: function resolve() {
- each(this, "verify");
- },
-
- restore: function restore() {
- each(this, "restore");
- compact(this);
- },
-
- verifyAndRestore: function verifyAndRestore() {
- var exception;
-
- try {
- this.verify();
- } catch (e) {
- exception = e;
- }
-
- this.restore();
-
- if (exception) {
- throw exception;
- }
- },
-
- add: function add(fake) {
- push.call(getFakes(this), fake);
- return fake;
- },
-
- spy: function spy() {
- return this.add(sinon.spy.apply(sinon, arguments));
- },
-
- stub: function stub(object, property, value) {
- if (property) {
- var original = object[property];
-
- if (typeof original != "function") {
- if (!hasOwnProperty.call(object, property)) {
- throw new TypeError("Cannot stub non-existent own property " + property);
- }
-
- object[property] = value;
-
- return this.add({
- restore: function () {
- object[property] = original;
- }
- });
- }
- }
- if (!property && !!object && typeof object == "object") {
- var stubbedObj = sinon.stub.apply(sinon, arguments);
-
- for (var prop in stubbedObj) {
- if (typeof stubbedObj[prop] === "function") {
- this.add(stubbedObj[prop]);
- }
- }
-
- return stubbedObj;
- }
-
- return this.add(sinon.stub.apply(sinon, arguments));
- },
-
- mock: function mock() {
- return this.add(sinon.mock.apply(sinon, arguments));
- },
-
- inject: function inject(obj) {
- var col = this;
-
- obj.spy = function () {
- return col.spy.apply(col, arguments);
- };
-
- obj.stub = function () {
- return col.stub.apply(col, arguments);
- };
-
- obj.mock = function () {
- return col.mock.apply(col, arguments);
- };
-
- return obj;
- }
- };
-
- if (commonJSModule) {
- module.exports = collection;
- } else {
- sinon.collection = collection;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/*jslint eqeqeq: false, plusplus: false, evil: true, onevar: false, browser: true, forin: false*/
-/*global module, require, window*/
-/**
- * Fake timer API
- * setTimeout
- * setInterval
- * clearTimeout
- * clearInterval
- * tick
- * reset
- * Date
- *
- * Inspired by jsUnitMockTimeOut from JsUnit
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- var sinon = {};
-}
-
-(function (global) {
- var id = 1;
-
- function addTimer(args, recurring) {
- if (args.length === 0) {
- throw new Error("Function requires at least 1 parameter");
- }
-
- var toId = id++;
- var delay = args[1] || 0;
-
- if (!this.timeouts) {
- this.timeouts = {};
- }
-
- this.timeouts[toId] = {
- id: toId,
- func: args[0],
- callAt: this.now + delay,
- invokeArgs: Array.prototype.slice.call(args, 2)
- };
-
- if (recurring === true) {
- this.timeouts[toId].interval = delay;
- }
-
- return toId;
- }
-
- function parseTime(str) {
- if (!str) {
- return 0;
- }
-
- var strings = str.split(":");
- var l = strings.length, i = l;
- var ms = 0, parsed;
-
- if (l > 3 || !/^(\d\d:){0,2}\d\d?$/.test(str)) {
- throw new Error("tick only understands numbers and 'h:m:s'");
- }
-
- while (i--) {
- parsed = parseInt(strings[i], 10);
-
- if (parsed >= 60) {
- throw new Error("Invalid time " + str);
- }
-
- ms += parsed * Math.pow(60, (l - i - 1));
- }
-
- return ms * 1000;
- }
-
- function createObject(object) {
- var newObject;
-
- if (Object.create) {
- newObject = Object.create(object);
- } else {
- var F = function () {};
- F.prototype = object;
- newObject = new F();
- }
-
- newObject.Date.clock = newObject;
- return newObject;
- }
-
- sinon.clock = {
- now: 0,
-
- create: function create(now) {
- var clock = createObject(this);
-
- if (typeof now == "number") {
- clock.now = now;
- }
-
- if (!!now && typeof now == "object") {
- throw new TypeError("now should be milliseconds since UNIX epoch");
- }
-
- return clock;
- },
-
- setTimeout: function setTimeout(callback, timeout) {
- return addTimer.call(this, arguments, false);
- },
-
- clearTimeout: function clearTimeout(timerId) {
- if (!this.timeouts) {
- this.timeouts = [];
- }
-
- if (timerId in this.timeouts) {
- delete this.timeouts[timerId];
- }
- },
-
- setInterval: function setInterval(callback, timeout) {
- return addTimer.call(this, arguments, true);
- },
-
- clearInterval: function clearInterval(timerId) {
- this.clearTimeout(timerId);
- },
-
- tick: function tick(ms) {
- ms = typeof ms == "number" ? ms : parseTime(ms);
- var tickFrom = this.now, tickTo = this.now + ms, previous = this.now;
- var timer = this.firstTimerInRange(tickFrom, tickTo);
-
- var firstException;
- while (timer && tickFrom <= tickTo) {
- if (this.timeouts[timer.id]) {
- tickFrom = this.now = timer.callAt;
- try {
- this.callTimer(timer);
- } catch (e) {
- firstException = firstException || e;
- }
- }
-
- timer = this.firstTimerInRange(previous, tickTo);
- previous = tickFrom;
- }
-
- this.now = tickTo;
-
- if (firstException) {
- throw firstException;
- }
- },
-
- firstTimerInRange: function (from, to) {
- var timer, smallest, originalTimer;
-
- for (var id in this.timeouts) {
- if (this.timeouts.hasOwnProperty(id)) {
- if (this.timeouts[id].callAt < from || this.timeouts[id].callAt > to) {
- continue;
- }
-
- if (!smallest || this.timeouts[id].callAt < smallest) {
- originalTimer = this.timeouts[id];
- smallest = this.timeouts[id].callAt;
-
- timer = {
- func: this.timeouts[id].func,
- callAt: this.timeouts[id].callAt,
- interval: this.timeouts[id].interval,
- id: this.timeouts[id].id,
- invokeArgs: this.timeouts[id].invokeArgs
- };
- }
- }
- }
-
- return timer || null;
- },
-
- callTimer: function (timer) {
- if (typeof timer.interval == "number") {
- this.timeouts[timer.id].callAt += timer.interval;
- } else {
- delete this.timeouts[timer.id];
- }
-
- try {
- if (typeof timer.func == "function") {
- timer.func.apply(null, timer.invokeArgs);
- } else {
- eval(timer.func);
- }
- } catch (e) {
- var exception = e;
- }
-
- if (!this.timeouts[timer.id]) {
- if (exception) {
- throw exception;
- }
- return;
- }
-
- if (exception) {
- throw exception;
- }
- },
-
- reset: function reset() {
- this.timeouts = {};
- },
-
- Date: (function () {
- var NativeDate = Date;
-
- function ClockDate(year, month, date, hour, minute, second, ms) {
- // Defensive and verbose to avoid potential harm in passing
- // explicit undefined when user does not pass argument
- switch (arguments.length) {
- case 0:
- return new NativeDate(ClockDate.clock.now);
- case 1:
- return new NativeDate(year);
- case 2:
- return new NativeDate(year, month);
- case 3:
- return new NativeDate(year, month, date);
- case 4:
- return new NativeDate(year, month, date, hour);
- case 5:
- return new NativeDate(year, month, date, hour, minute);
- case 6:
- return new NativeDate(year, month, date, hour, minute, second);
- default:
- return new NativeDate(year, month, date, hour, minute, second, ms);
- }
- }
-
- return mirrorDateProperties(ClockDate, NativeDate);
- }())
- };
-
- function mirrorDateProperties(target, source) {
- if (source.now) {
- target.now = function now() {
- return target.clock.now;
- };
- } else {
- delete target.now;
- }
-
- if (source.toSource) {
- target.toSource = function toSource() {
- return source.toSource();
- };
- } else {
- delete target.toSource;
- }
-
- target.toString = function toString() {
- return source.toString();
- };
-
- target.prototype = source.prototype;
- target.parse = source.parse;
- target.UTC = source.UTC;
- target.prototype.toUTCString = source.prototype.toUTCString;
- return target;
- }
-
- var methods = ["Date", "setTimeout", "setInterval",
- "clearTimeout", "clearInterval"];
-
- function restore() {
- var method;
-
- for (var i = 0, l = this.methods.length; i < l; i++) {
- method = this.methods[i];
- if (global[method].hadOwnProperty) {
- global[method] = this["_" + method];
- } else {
- delete global[method];
- }
- }
-
- // Prevent multiple executions which will completely remove these props
- this.methods = [];
- }
-
- function stubGlobal(method, clock) {
- clock[method].hadOwnProperty = Object.prototype.hasOwnProperty.call(global, method);
- clock["_" + method] = global[method];
-
- if (method == "Date") {
- var date = mirrorDateProperties(clock[method], global[method]);
- global[method] = date;
- } else {
- global[method] = function () {
- return clock[method].apply(clock, arguments);
- };
-
- for (var prop in clock[method]) {
- if (clock[method].hasOwnProperty(prop)) {
- global[method][prop] = clock[method][prop];
- }
- }
- }
-
- global[method].clock = clock;
- }
-
- sinon.useFakeTimers = function useFakeTimers(now) {
- var clock = sinon.clock.create(now);
- clock.restore = restore;
- clock.methods = Array.prototype.slice.call(arguments,
- typeof now == "number" ? 1 : 0);
-
- if (clock.methods.length === 0) {
- clock.methods = methods;
- }
-
- for (var i = 0, l = clock.methods.length; i < l; i++) {
- stubGlobal(clock.methods[i], clock);
- }
-
- return clock;
- };
-}(typeof global != "undefined" && typeof global !== "function" ? global : this));
-
-sinon.timers = {
- setTimeout: setTimeout,
- clearTimeout: clearTimeout,
- setInterval: setInterval,
- clearInterval: clearInterval,
- Date: Date
-};
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/*jslint eqeqeq: false, onevar: false*/
-/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
-/**
- * Minimal Event interface implementation
- *
- * Original implementation by Sven Fuchs: https://gist.github.com/995028
- * Modifications and tests by Christian Johansen.
- *
- * @author Sven Fuchs (svenfuchs@artweb-design.de)
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2011 Sven Fuchs, Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- this.sinon = {};
-}
-
-(function () {
- var push = [].push;
-
- sinon.Event = function Event(type, bubbles, cancelable) {
- this.initEvent(type, bubbles, cancelable);
- };
-
- sinon.Event.prototype = {
- initEvent: function(type, bubbles, cancelable) {
- this.type = type;
- this.bubbles = bubbles;
- this.cancelable = cancelable;
- },
-
- stopPropagation: function () {},
-
- preventDefault: function () {
- this.defaultPrevented = true;
- }
- };
-
- sinon.EventTarget = {
- addEventListener: function addEventListener(event, listener, useCapture) {
- this.eventListeners = this.eventListeners || {};
- this.eventListeners[event] = this.eventListeners[event] || [];
- push.call(this.eventListeners[event], listener);
- },
-
- removeEventListener: function removeEventListener(event, listener, useCapture) {
- var listeners = this.eventListeners && this.eventListeners[event] || [];
-
- for (var i = 0, l = listeners.length; i < l; ++i) {
- if (listeners[i] == listener) {
- return listeners.splice(i, 1);
- }
- }
- },
-
- dispatchEvent: function dispatchEvent(event) {
- var type = event.type;
- var listeners = this.eventListeners && this.eventListeners[type] || [];
-
- for (var i = 0; i < listeners.length; i++) {
- if (typeof listeners[i] == "function") {
- listeners[i].call(this, event);
- } else {
- listeners[i].handleEvent(event);
- }
- }
-
- return !!event.defaultPrevented;
- }
- };
-}());
-
-/**
- * @depend ../../sinon.js
- * @depend event.js
- */
-/*jslint eqeqeq: false, onevar: false*/
-/*global sinon, module, require, ActiveXObject, XMLHttpRequest, DOMParser*/
-/**
- * Fake XMLHttpRequest object
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- this.sinon = {};
-}
-sinon.xhr = { XMLHttpRequest: this.XMLHttpRequest };
-
-// wrapper for global
-(function(global) {
- var xhr = sinon.xhr;
- xhr.GlobalXMLHttpRequest = global.XMLHttpRequest;
- xhr.GlobalActiveXObject = global.ActiveXObject;
- xhr.supportsActiveX = typeof xhr.GlobalActiveXObject != "undefined";
- xhr.supportsXHR = typeof xhr.GlobalXMLHttpRequest != "undefined";
- xhr.workingXHR = xhr.supportsXHR ? xhr.GlobalXMLHttpRequest : xhr.supportsActiveX
- ? function() { return new xhr.GlobalActiveXObject("MSXML2.XMLHTTP.3.0") } : false;
-
- /*jsl:ignore*/
- var unsafeHeaders = {
- "Accept-Charset": true,
- "Accept-Encoding": true,
- "Connection": true,
- "Content-Length": true,
- "Cookie": true,
- "Cookie2": true,
- "Content-Transfer-Encoding": true,
- "Date": true,
- "Expect": true,
- "Host": true,
- "Keep-Alive": true,
- "Referer": true,
- "TE": true,
- "Trailer": true,
- "Transfer-Encoding": true,
- "Upgrade": true,
- "User-Agent": true,
- "Via": true
- };
- /*jsl:end*/
-
- function FakeXMLHttpRequest() {
- this.readyState = FakeXMLHttpRequest.UNSENT;
- this.requestHeaders = {};
- this.requestBody = null;
- this.status = 0;
- this.statusText = "";
-
- if (typeof FakeXMLHttpRequest.onCreate == "function") {
- FakeXMLHttpRequest.onCreate(this);
- }
- }
-
- function verifyState(xhr) {
- if (xhr.readyState !== FakeXMLHttpRequest.OPENED) {
- throw new Error("INVALID_STATE_ERR");
- }
-
- if (xhr.sendFlag) {
- throw new Error("INVALID_STATE_ERR");
- }
- }
-
- // filtering to enable a white-list version of Sinon FakeXhr,
- // where whitelisted requests are passed through to real XHR
- function each(collection, callback) {
- if (!collection) return;
- for (var i = 0, l = collection.length; i < l; i += 1) {
- callback(collection[i]);
- }
- }
- function some(collection, callback) {
- for (var index = 0; index < collection.length; index++) {
- if(callback(collection[index]) === true) return true;
- };
- return false;
- }
- // largest arity in XHR is 5 - XHR#open
- var apply = function(obj,method,args) {
- switch(args.length) {
- case 0: return obj[method]();
- case 1: return obj[method](args[0]);
- case 2: return obj[method](args[0],args[1]);
- case 3: return obj[method](args[0],args[1],args[2]);
- case 4: return obj[method](args[0],args[1],args[2],args[3]);
- case 5: return obj[method](args[0],args[1],args[2],args[3],args[4]);
- };
- };
-
- FakeXMLHttpRequest.filters = [];
- FakeXMLHttpRequest.addFilter = function(fn) {
- this.filters.push(fn)
- };
- var IE6Re = /MSIE 6/;
- FakeXMLHttpRequest.defake = function(fakeXhr,xhrArgs) {
- var xhr = new sinon.xhr.workingXHR();
- each(["open","setRequestHeader","send","abort","getResponseHeader",
- "getAllResponseHeaders","addEventListener","overrideMimeType","removeEventListener"],
- function(method) {
- fakeXhr[method] = function() {
- return apply(xhr,method,arguments);
- };
- });
-
- var copyAttrs = function(args) {
- each(args, function(attr) {
- try {
- fakeXhr[attr] = xhr[attr]
- } catch(e) {
- if(!IE6Re.test(navigator.userAgent)) throw e;
- }
- });
- };
-
- var stateChange = function() {
- fakeXhr.readyState = xhr.readyState;
- if(xhr.readyState >= FakeXMLHttpRequest.HEADERS_RECEIVED) {
- copyAttrs(["status","statusText"]);
- }
- if(xhr.readyState >= FakeXMLHttpRequest.LOADING) {
- copyAttrs(["responseText"]);
- }
- if(xhr.readyState === FakeXMLHttpRequest.DONE) {
- copyAttrs(["responseXML"]);
- }
- if(fakeXhr.onreadystatechange) fakeXhr.onreadystatechange.call(fakeXhr);
- };
- if(xhr.addEventListener) {
- for(var event in fakeXhr.eventListeners) {
- if(fakeXhr.eventListeners.hasOwnProperty(event)) {
- each(fakeXhr.eventListeners[event],function(handler) {
- xhr.addEventListener(event, handler);
- });
- }
- }
- xhr.addEventListener("readystatechange",stateChange);
- } else {
- xhr.onreadystatechange = stateChange;
- }
- apply(xhr,"open",xhrArgs);
- };
- FakeXMLHttpRequest.useFilters = false;
-
- function verifyRequestSent(xhr) {
- if (xhr.readyState == FakeXMLHttpRequest.DONE) {
- throw new Error("Request done");
- }
- }
-
- function verifyHeadersReceived(xhr) {
- if (xhr.async && xhr.readyState != FakeXMLHttpRequest.HEADERS_RECEIVED) {
- throw new Error("No headers received");
- }
- }
-
- function verifyResponseBodyType(body) {
- if (typeof body != "string") {
- var error = new Error("Attempted to respond to fake XMLHttpRequest with " +
- body + ", which is not a string.");
- error.name = "InvalidBodyException";
- throw error;
- }
- }
-
- sinon.extend(FakeXMLHttpRequest.prototype, sinon.EventTarget, {
- async: true,
-
- open: function open(method, url, async, username, password) {
- this.method = method;
- this.url = url;
- this.async = typeof async == "boolean" ? async : true;
- this.username = username;
- this.password = password;
- this.responseText = null;
- this.responseXML = null;
- this.requestHeaders = {};
- this.sendFlag = false;
- if(sinon.FakeXMLHttpRequest.useFilters === true) {
- var xhrArgs = arguments;
- var defake = some(FakeXMLHttpRequest.filters,function(filter) {
- return filter.apply(this,xhrArgs)
- });
- if (defake) {
- return sinon.FakeXMLHttpRequest.defake(this,arguments);
- }
- }
- this.readyStateChange(FakeXMLHttpRequest.OPENED);
- },
-
- readyStateChange: function readyStateChange(state) {
- this.readyState = state;
-
- if (typeof this.onreadystatechange == "function") {
- try {
- this.onreadystatechange();
- } catch (e) {
- sinon.logError("Fake XHR onreadystatechange handler", e);
- }
- }
-
- this.dispatchEvent(new sinon.Event("readystatechange"));
- },
-
- setRequestHeader: function setRequestHeader(header, value) {
- verifyState(this);
-
- if (unsafeHeaders[header] || /^(Sec-|Proxy-)/.test(header)) {
- throw new Error("Refused to set unsafe header \"" + header + "\"");
- }
-
- if (this.requestHeaders[header]) {
- this.requestHeaders[header] += "," + value;
- } else {
- this.requestHeaders[header] = value;
- }
- },
-
- // Helps testing
- setResponseHeaders: function setResponseHeaders(headers) {
- this.responseHeaders = {};
-
- for (var header in headers) {
- if (headers.hasOwnProperty(header)) {
- this.responseHeaders[header] = headers[header];
- }
- }
-
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.HEADERS_RECEIVED);
- }
- },
-
- // Currently treats ALL data as a DOMString (i.e. no Document)
- send: function send(data) {
- verifyState(this);
-
- if (!/^(get|head)$/i.test(this.method)) {
- if (this.requestHeaders["Content-Type"]) {
- var value = this.requestHeaders["Content-Type"].split(";");
- this.requestHeaders["Content-Type"] = value[0] + ";charset=utf-8";
- } else {
- this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";
- }
-
- this.requestBody = data;
- }
-
- this.errorFlag = false;
- this.sendFlag = this.async;
- this.readyStateChange(FakeXMLHttpRequest.OPENED);
-
- if (typeof this.onSend == "function") {
- this.onSend(this);
- }
- },
-
- abort: function abort() {
- this.aborted = true;
- this.responseText = null;
- this.errorFlag = true;
- this.requestHeaders = {};
-
- if (this.readyState > sinon.FakeXMLHttpRequest.UNSENT && this.sendFlag) {
- this.readyStateChange(sinon.FakeXMLHttpRequest.DONE);
- this.sendFlag = false;
- }
-
- this.readyState = sinon.FakeXMLHttpRequest.UNSENT;
- },
-
- getResponseHeader: function getResponseHeader(header) {
- if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
- return null;
- }
-
- if (/^Set-Cookie2?$/i.test(header)) {
- return null;
- }
-
- header = header.toLowerCase();
-
- for (var h in this.responseHeaders) {
- if (h.toLowerCase() == header) {
- return this.responseHeaders[h];
- }
- }
-
- return null;
- },
-
- getAllResponseHeaders: function getAllResponseHeaders() {
- if (this.readyState < FakeXMLHttpRequest.HEADERS_RECEIVED) {
- return "";
- }
-
- var headers = "";
-
- for (var header in this.responseHeaders) {
- if (this.responseHeaders.hasOwnProperty(header) &&
- !/^Set-Cookie2?$/i.test(header)) {
- headers += header + ": " + this.responseHeaders[header] + "\r\n";
- }
- }
-
- return headers;
- },
-
- setResponseBody: function setResponseBody(body) {
- verifyRequestSent(this);
- verifyHeadersReceived(this);
- verifyResponseBodyType(body);
-
- var chunkSize = this.chunkSize || 10;
- var index = 0;
- this.responseText = "";
-
- do {
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.LOADING);
- }
-
- this.responseText += body.substring(index, index + chunkSize);
- index += chunkSize;
- } while (index < body.length);
-
- var type = this.getResponseHeader("Content-Type");
-
- if (this.responseText &&
- (!type || /(text\/xml)|(application\/xml)|(\+xml)/.test(type))) {
- try {
- this.responseXML = FakeXMLHttpRequest.parseXML(this.responseText);
- } catch (e) {
- // Unable to parse XML - no biggie
- }
- }
-
- if (this.async) {
- this.readyStateChange(FakeXMLHttpRequest.DONE);
- } else {
- this.readyState = FakeXMLHttpRequest.DONE;
- }
- },
-
- respond: function respond(status, headers, body) {
- this.setResponseHeaders(headers || {});
- this.status = typeof status == "number" ? status : 200;
- this.statusText = FakeXMLHttpRequest.statusCodes[this.status];
- this.setResponseBody(body || "");
- }
- });
-
- sinon.extend(FakeXMLHttpRequest, {
- UNSENT: 0,
- OPENED: 1,
- HEADERS_RECEIVED: 2,
- LOADING: 3,
- DONE: 4
- });
-
- // Borrowed from JSpec
- FakeXMLHttpRequest.parseXML = function parseXML(text) {
- var xmlDoc;
-
- if (typeof DOMParser != "undefined") {
- var parser = new DOMParser();
- xmlDoc = parser.parseFromString(text, "text/xml");
- } else {
- xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
- xmlDoc.async = "false";
- xmlDoc.loadXML(text);
- }
-
- return xmlDoc;
- };
-
- FakeXMLHttpRequest.statusCodes = {
- 100: "Continue",
- 101: "Switching Protocols",
- 200: "OK",
- 201: "Created",
- 202: "Accepted",
- 203: "Non-Authoritative Information",
- 204: "No Content",
- 205: "Reset Content",
- 206: "Partial Content",
- 300: "Multiple Choice",
- 301: "Moved Permanently",
- 302: "Found",
- 303: "See Other",
- 304: "Not Modified",
- 305: "Use Proxy",
- 307: "Temporary Redirect",
- 400: "Bad Request",
- 401: "Unauthorized",
- 402: "Payment Required",
- 403: "Forbidden",
- 404: "Not Found",
- 405: "Method Not Allowed",
- 406: "Not Acceptable",
- 407: "Proxy Authentication Required",
- 408: "Request Timeout",
- 409: "Conflict",
- 410: "Gone",
- 411: "Length Required",
- 412: "Precondition Failed",
- 413: "Request Entity Too Large",
- 414: "Request-URI Too Long",
- 415: "Unsupported Media Type",
- 416: "Requested Range Not Satisfiable",
- 417: "Expectation Failed",
- 422: "Unprocessable Entity",
- 500: "Internal Server Error",
- 501: "Not Implemented",
- 502: "Bad Gateway",
- 503: "Service Unavailable",
- 504: "Gateway Timeout",
- 505: "HTTP Version Not Supported"
- };
-
- sinon.useFakeXMLHttpRequest = function () {
- sinon.FakeXMLHttpRequest.restore = function restore(keepOnCreate) {
- if (xhr.supportsXHR) {
- global.XMLHttpRequest = xhr.GlobalXMLHttpRequest;
- }
-
- if (xhr.supportsActiveX) {
- global.ActiveXObject = xhr.GlobalActiveXObject;
- }
-
- delete sinon.FakeXMLHttpRequest.restore;
-
- if (keepOnCreate !== true) {
- delete sinon.FakeXMLHttpRequest.onCreate;
- }
- };
- if (xhr.supportsXHR) {
- global.XMLHttpRequest = sinon.FakeXMLHttpRequest;
- }
-
- if (xhr.supportsActiveX) {
- global.ActiveXObject = function ActiveXObject(objId) {
- if (objId == "Microsoft.XMLHTTP" || /^Msxml2\.XMLHTTP/i.test(objId)) {
-
- return new sinon.FakeXMLHttpRequest();
- }
-
- return new xhr.GlobalActiveXObject(objId);
- };
- }
-
- return sinon.FakeXMLHttpRequest;
- };
-
- sinon.FakeXMLHttpRequest = FakeXMLHttpRequest;
-})(this);
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/**
- * @depend fake_xml_http_request.js
- */
-/*jslint eqeqeq: false, onevar: false, regexp: false, plusplus: false*/
-/*global module, require, window*/
-/**
- * The Sinon "server" mimics a web server that receives requests from
- * sinon.FakeXMLHttpRequest and provides an API to respond to those requests,
- * both synchronously and asynchronously. To respond synchronuously, canned
- * answers have to be provided upfront.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof sinon == "undefined") {
- var sinon = {};
-}
-
-sinon.fakeServer = (function () {
- var push = [].push;
- function F() {}
-
- function create(proto) {
- F.prototype = proto;
- return new F();
- }
-
- function responseArray(handler) {
- var response = handler;
-
- if (Object.prototype.toString.call(handler) != "[object Array]") {
- response = [200, {}, handler];
- }
-
- if (typeof response[2] != "string") {
- throw new TypeError("Fake server response body should be string, but was " +
- typeof response[2]);
- }
-
- return response;
- }
-
- var wloc = typeof window !== "undefined" ? window.location : {};
- var rCurrLoc = new RegExp("^" + wloc.protocol + "//" + wloc.host);
-
- function matchOne(response, reqMethod, reqUrl) {
- var rmeth = response.method;
- var matchMethod = !rmeth || rmeth.toLowerCase() == reqMethod.toLowerCase();
- var url = response.url;
- var matchUrl = !url || url == reqUrl || (typeof url.test == "function" && url.test(reqUrl));
-
- return matchMethod && matchUrl;
- }
-
- function match(response, request) {
- var requestMethod = this.getHTTPMethod(request);
- var requestUrl = request.url;
-
- if (!/^https?:\/\//.test(requestUrl) || rCurrLoc.test(requestUrl)) {
- requestUrl = requestUrl.replace(rCurrLoc, "");
- }
-
- if (matchOne(response, this.getHTTPMethod(request), requestUrl)) {
- if (typeof response.response == "function") {
- var ru = response.url;
- var args = [request].concat(!ru ? [] : requestUrl.match(ru).slice(1));
- return response.response.apply(response, args);
- }
-
- return true;
- }
-
- return false;
- }
-
- return {
- create: function () {
- var server = create(this);
- this.xhr = sinon.useFakeXMLHttpRequest();
- server.requests = [];
-
- this.xhr.onCreate = function (xhrObj) {
- server.addRequest(xhrObj);
- };
-
- return server;
- },
-
- addRequest: function addRequest(xhrObj) {
- var server = this;
- push.call(this.requests, xhrObj);
-
- xhrObj.onSend = function () {
- server.handleRequest(this);
- };
-
- if (this.autoRespond && !this.responding) {
- setTimeout(function () {
- server.responding = false;
- server.respond();
- }, this.autoRespondAfter || 10);
-
- this.responding = true;
- }
- },
-
- getHTTPMethod: function getHTTPMethod(request) {
- if (this.fakeHTTPMethods && /post/i.test(request.method)) {
- var matches = (request.requestBody || "").match(/_method=([^\b;]+)/);
- return !!matches ? matches[1] : request.method;
- }
-
- return request.method;
- },
-
- handleRequest: function handleRequest(xhr) {
- if (xhr.async) {
- if (!this.queue) {
- this.queue = [];
- }
-
- push.call(this.queue, xhr);
- } else {
- this.processRequest(xhr);
- }
- },
-
- respondWith: function respondWith(method, url, body) {
- if (arguments.length == 1 && typeof method != "function") {
- this.response = responseArray(method);
- return;
- }
-
- if (!this.responses) { this.responses = []; }
-
- if (arguments.length == 1) {
- body = method;
- url = method = null;
- }
-
- if (arguments.length == 2) {
- body = url;
- url = method;
- method = null;
- }
-
- push.call(this.responses, {
- method: method,
- url: url,
- response: typeof body == "function" ? body : responseArray(body)
- });
- },
-
- respond: function respond() {
- if (arguments.length > 0) this.respondWith.apply(this, arguments);
- var queue = this.queue || [];
- var request;
-
- while(request = queue.shift()) {
- this.processRequest(request);
- }
- },
-
- processRequest: function processRequest(request) {
- try {
- if (request.aborted) {
- return;
- }
-
- var response = this.response || [404, {}, ""];
-
- if (this.responses) {
- for (var i = 0, l = this.responses.length; i < l; i++) {
- if (match.call(this, this.responses[i], request)) {
- response = this.responses[i].response;
- break;
- }
- }
- }
-
- if (request.readyState != 4) {
- request.respond(response[0], response[1], response[2]);
- }
- } catch (e) {
- sinon.logError("Fake server request processing", e);
- }
- },
-
- restore: function restore() {
- return this.xhr.restore && this.xhr.restore.apply(this.xhr, arguments);
- }
- };
-}());
-
-if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon;
-}
-
-/**
- * @depend fake_server.js
- * @depend fake_timers.js
- */
-/*jslint browser: true, eqeqeq: false, onevar: false*/
-/*global sinon*/
-/**
- * Add-on for sinon.fakeServer that automatically handles a fake timer along with
- * the FakeXMLHttpRequest. The direct inspiration for this add-on is jQuery
- * 1.3.x, which does not use xhr object's onreadystatehandler at all - instead,
- * it polls the object for completion with setInterval. Dispite the direct
- * motivation, there is nothing jQuery-specific in this file, so it can be used
- * in any environment where the ajax implementation depends on setInterval or
- * setTimeout.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function () {
- function Server() {}
- Server.prototype = sinon.fakeServer;
-
- sinon.fakeServerWithClock = new Server();
-
- sinon.fakeServerWithClock.addRequest = function addRequest(xhr) {
- if (xhr.async) {
- if (typeof setTimeout.clock == "object") {
- this.clock = setTimeout.clock;
- } else {
- this.clock = sinon.useFakeTimers();
- this.resetClock = true;
- }
-
- if (!this.longestTimeout) {
- var clockSetTimeout = this.clock.setTimeout;
- var clockSetInterval = this.clock.setInterval;
- var server = this;
-
- this.clock.setTimeout = function (fn, timeout) {
- server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
-
- return clockSetTimeout.apply(this, arguments);
- };
-
- this.clock.setInterval = function (fn, timeout) {
- server.longestTimeout = Math.max(timeout, server.longestTimeout || 0);
-
- return clockSetInterval.apply(this, arguments);
- };
- }
- }
-
- return sinon.fakeServer.addRequest.call(this, xhr);
- };
-
- sinon.fakeServerWithClock.respond = function respond() {
- var returnVal = sinon.fakeServer.respond.apply(this, arguments);
-
- if (this.clock) {
- this.clock.tick(this.longestTimeout || 0);
- this.longestTimeout = 0;
-
- if (this.resetClock) {
- this.clock.restore();
- this.resetClock = false;
- }
- }
-
- return returnVal;
- };
-
- sinon.fakeServerWithClock.restore = function restore() {
- if (this.clock) {
- this.clock.restore();
- }
-
- return sinon.fakeServer.restore.apply(this, arguments);
- };
-}());
-
-/**
- * @depend ../sinon.js
- * @depend collection.js
- * @depend util/fake_timers.js
- * @depend util/fake_server_with_clock.js
- */
-/*jslint eqeqeq: false, onevar: false, plusplus: false*/
-/*global require, module*/
-/**
- * Manages fake collections as well as fake utilities such as Sinon's
- * timers and fake XHR implementation in one convenient object.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-if (typeof module == "object" && typeof require == "function") {
- var sinon = require("../sinon");
- sinon.extend(sinon, require("./util/fake_timers"));
-}
-
-(function () {
- var push = [].push;
-
- function exposeValue(sandbox, config, key, value) {
- if (!value) {
- return;
- }
-
- if (config.injectInto) {
- config.injectInto[key] = value;
- } else {
- push.call(sandbox.args, value);
- }
- }
-
- function prepareSandboxFromConfig(config) {
- var sandbox = sinon.create(sinon.sandbox);
-
- if (config.useFakeServer) {
- if (typeof config.useFakeServer == "object") {
- sandbox.serverPrototype = config.useFakeServer;
- }
-
- sandbox.useFakeServer();
- }
-
- if (config.useFakeTimers) {
- if (typeof config.useFakeTimers == "object") {
- sandbox.useFakeTimers.apply(sandbox, config.useFakeTimers);
- } else {
- sandbox.useFakeTimers();
- }
- }
-
- return sandbox;
- }
-
- sinon.sandbox = sinon.extend(sinon.create(sinon.collection), {
- useFakeTimers: function useFakeTimers() {
- this.clock = sinon.useFakeTimers.apply(sinon, arguments);
-
- return this.add(this.clock);
- },
-
- serverPrototype: sinon.fakeServer,
-
- useFakeServer: function useFakeServer() {
- var proto = this.serverPrototype || sinon.fakeServer;
-
- if (!proto || !proto.create) {
- return null;
- }
-
- this.server = proto.create();
- return this.add(this.server);
- },
-
- inject: function (obj) {
- sinon.collection.inject.call(this, obj);
-
- if (this.clock) {
- obj.clock = this.clock;
- }
-
- if (this.server) {
- obj.server = this.server;
- obj.requests = this.server.requests;
- }
-
- return obj;
- },
-
- create: function (config) {
- if (!config) {
- return sinon.create(sinon.sandbox);
- }
-
- var sandbox = prepareSandboxFromConfig(config);
- sandbox.args = sandbox.args || [];
- var prop, value, exposed = sandbox.inject({});
-
- if (config.properties) {
- for (var i = 0, l = config.properties.length; i < l; i++) {
- prop = config.properties[i];
- value = exposed[prop] || prop == "sandbox" && sandbox;
- exposeValue(sandbox, config, prop, value);
- }
- } else {
- exposeValue(sandbox, config, "sandbox", value);
- }
-
- return sandbox;
- }
- });
-
- sinon.sandbox.useFakeXMLHttpRequest = sinon.sandbox.useFakeServer;
-
- if (typeof module == "object" && typeof require == "function") {
- module.exports = sinon.sandbox;
- }
-}());
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- * @depend mock.js
- * @depend sandbox.js
- */
-/*jslint eqeqeq: false, onevar: false, forin: true, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Test function, sandboxes fakes
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function test(callback) {
- var type = typeof callback;
-
- if (type != "function") {
- throw new TypeError("sinon.test needs to wrap a test function, got " + type);
- }
-
- return function () {
- var config = sinon.getConfig(sinon.config);
- config.injectInto = config.injectIntoThis && this || config.injectInto;
- var sandbox = sinon.sandbox.create(config);
- var exception, result;
- var args = Array.prototype.slice.call(arguments).concat(sandbox.args);
-
- try {
- result = callback.apply(this, args);
- } catch (e) {
- exception = e;
- }
-
- if (typeof exception !== "undefined") {
- sandbox.restore();
- throw exception;
- }
- else {
- sandbox.verifyAndRestore();
- }
-
- return result;
- };
- }
-
- test.config = {
- injectIntoThis: true,
- injectInto: null,
- properties: ["spy", "stub", "mock", "clock", "server", "requests"],
- useFakeTimers: true,
- useFakeServer: true
- };
-
- if (commonJSModule) {
- module.exports = test;
- } else {
- sinon.test = test;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend test.js
- */
-/*jslint eqeqeq: false, onevar: false, eqeqeq: false*/
-/*global module, require, sinon*/
-/**
- * Test case, sandboxes all test functions
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon || !Object.prototype.hasOwnProperty) {
- return;
- }
-
- function createTest(property, setUp, tearDown) {
- return function () {
- if (setUp) {
- setUp.apply(this, arguments);
- }
-
- var exception, result;
-
- try {
- result = property.apply(this, arguments);
- } catch (e) {
- exception = e;
- }
-
- if (tearDown) {
- tearDown.apply(this, arguments);
- }
-
- if (exception) {
- throw exception;
- }
-
- return result;
- };
- }
-
- function testCase(tests, prefix) {
- /*jsl:ignore*/
- if (!tests || typeof tests != "object") {
- throw new TypeError("sinon.testCase needs an object with test functions");
- }
- /*jsl:end*/
-
- prefix = prefix || "test";
- var rPrefix = new RegExp("^" + prefix);
- var methods = {}, testName, property, method;
- var setUp = tests.setUp;
- var tearDown = tests.tearDown;
-
- for (testName in tests) {
- if (tests.hasOwnProperty(testName)) {
- property = tests[testName];
-
- if (/^(setUp|tearDown)$/.test(testName)) {
- continue;
- }
-
- if (typeof property == "function" && rPrefix.test(testName)) {
- method = property;
-
- if (setUp || tearDown) {
- method = createTest(property, setUp, tearDown);
- }
-
- methods[testName] = sinon.test(method);
- } else {
- methods[testName] = tests[testName];
- }
- }
- }
-
- return methods;
- }
-
- if (commonJSModule) {
- module.exports = testCase;
- } else {
- sinon.testCase = testCase;
- }
-}(typeof sinon == "object" && sinon || null));
-
-/**
- * @depend ../sinon.js
- * @depend stub.js
- */
-/*jslint eqeqeq: false, onevar: false, nomen: false, plusplus: false*/
-/*global module, require, sinon*/
-/**
- * Assertions matching the test spy retrieval interface.
- *
- * @author Christian Johansen (christian@cjohansen.no)
- * @license BSD
- *
- * Copyright (c) 2010-2011 Christian Johansen
- */
-
-(function (sinon, global) {
- var commonJSModule = typeof module == "object" && typeof require == "function";
- var slice = Array.prototype.slice;
- var assert;
-
- if (!sinon && commonJSModule) {
- sinon = require("../sinon");
- }
-
- if (!sinon) {
- return;
- }
-
- function verifyIsStub() {
- var method;
-
- for (var i = 0, l = arguments.length; i < l; ++i) {
- method = arguments[i];
-
- if (!method) {
- assert.fail("fake is not a spy");
- }
-
- if (typeof method != "function") {
- assert.fail(method + " is not a function");
- }
-
- if (typeof method.getCall != "function") {
- assert.fail(method + " is not stubbed");
- }
- }
- }
-
- function failAssertion(object, msg) {
- object = object || global;
- var failMethod = object.fail || assert.fail;
- failMethod.call(object, msg);
- }
-
- function mirrorPropAsAssertion(name, method, message) {
- if (arguments.length == 2) {
- message = method;
- method = name;
- }
-
- assert[name] = function (fake) {
- verifyIsStub(fake);
-
- var args = slice.call(arguments, 1);
- var failed = false;
-
- if (typeof method == "function") {
- failed = !method(fake);
- } else {
- failed = typeof fake[method] == "function" ?
- !fake[method].apply(fake, args) : !fake[method];
- }
-
- if (failed) {
- failAssertion(this, fake.printf.apply(fake, [message].concat(args)));
- } else {
- assert.pass(name);
- }
- };
- }
-
- function exposedName(prefix, prop) {
- return !prefix || /^fail/.test(prop) ? prop :
- prefix + prop.slice(0, 1).toUpperCase() + prop.slice(1);
- };
-
- assert = {
- failException: "AssertError",
-
- fail: function fail(message) {
- var error = new Error(message);
- error.name = this.failException || assert.failException;
-
- throw error;
- },
-
- pass: function pass(assertion) {},
-
- callOrder: function assertCallOrder() {
- verifyIsStub.apply(null, arguments);
- var expected = "", actual = "";
-
- if (!sinon.calledInOrder(arguments)) {
- try {
- expected = [].join.call(arguments, ", ");
- actual = sinon.orderByFirstCall(slice.call(arguments)).join(", ");
- } catch (e) {
- // If this fails, we'll just fall back to the blank string
- }
-
- failAssertion(this, "expected " + expected + " to be " +
- "called in order but were called as " + actual);
- } else {
- assert.pass("callOrder");
- }
- },
-
- callCount: function assertCallCount(method, count) {
- verifyIsStub(method);
-
- if (method.callCount != count) {
- var msg = "expected %n to be called " + sinon.timesInWords(count) +
- " but was called %c%C";
- failAssertion(this, method.printf(msg));
- } else {
- assert.pass("callCount");
- }
- },
-
- expose: function expose(target, options) {
- if (!target) {
- throw new TypeError("target is null or undefined");
- }
-
- var o = options || {};
- var prefix = typeof o.prefix == "undefined" && "assert" || o.prefix;
- var includeFail = typeof o.includeFail == "undefined" || !!o.includeFail;
-
- for (var method in this) {
- if (method != "export" && (includeFail || !/^(fail)/.test(method))) {
- target[exposedName(prefix, method)] = this[method];
- }
- }
-
- return target;
- }
- };
-
- mirrorPropAsAssertion("called", "expected %n to have been called at least once but was never called");
- mirrorPropAsAssertion("notCalled", function (spy) { return !spy.called; },
- "expected %n to not have been called but was called %c%C");
- mirrorPropAsAssertion("calledOnce", "expected %n to be called once but was called %c%C");
- mirrorPropAsAssertion("calledTwice", "expected %n to be called twice but was called %c%C");
- mirrorPropAsAssertion("calledThrice", "expected %n to be called thrice but was called %c%C");
- mirrorPropAsAssertion("calledOn", "expected %n to be called with %1 as this but was called with %t");
- mirrorPropAsAssertion("alwaysCalledOn", "expected %n to always be called with %1 as this but was called with %t");
- mirrorPropAsAssertion("calledWithNew", "expected %n to be called with new");
- mirrorPropAsAssertion("alwaysCalledWithNew", "expected %n to always be called with new");
- mirrorPropAsAssertion("calledWith", "expected %n to be called with arguments %*%C");
- mirrorPropAsAssertion("calledWithMatch", "expected %n to be called with match %*%C");
- mirrorPropAsAssertion("alwaysCalledWith", "expected %n to always be called with arguments %*%C");
- mirrorPropAsAssertion("alwaysCalledWithMatch", "expected %n to always be called with match %*%C");
- mirrorPropAsAssertion("calledWithExactly", "expected %n to be called with exact arguments %*%C");
- mirrorPropAsAssertion("alwaysCalledWithExactly", "expected %n to always be called with exact arguments %*%C");
- mirrorPropAsAssertion("neverCalledWith", "expected %n to never be called with arguments %*%C");
- mirrorPropAsAssertion("neverCalledWithMatch", "expected %n to never be called with match %*%C");
- mirrorPropAsAssertion("threw", "%n did not throw exception%C");
- mirrorPropAsAssertion("alwaysThrew", "%n did not always throw exception%C");
-
- if (commonJSModule) {
- module.exports = assert;
- } else {
- sinon.assert = assert;
- }
-}(typeof sinon == "object" && sinon || null, typeof window != "undefined" ? window : global));
-
-return sinon;}.call(typeof window != 'undefined' && window || {}));
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- module("Basic Event Tests", {
- setup: function () {
- // Patch AJAX requests
- var self = this;
- this.xhr = sinon.useFakeXMLHttpRequest();
- this.requests = [];
- this.xhr.onCreate = function (xhr) {
- self.requests.push(xhr);
- };
- this.inputs = createTextSelect('autocompleteselect');
- this.textInput = this.inputs[0];
- this.hiddenInput = this.inputs[1];
- $('#qunit-fixture').append(this.textInput);
- $('#qunit-fixture').append(this.hiddenInput);
- bindSelectables('#qunit-fixture');
- },
- teardown: function () {
- this.xhr.restore();
- this.textInput.djselectable('destroy');
- }
- });
-
- test("Manual Selection", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- var item = {id: "1", value: 'foo'};
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableselect should fire once when manually selected.");
- });
-
- test("Manual Selection with Double Bind", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- bindSelectables('#qunit-fixture');
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableselect should fire once when manually selected.");
- });
-
- test("Menu Selection", function() {
- expect(2);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = simpleLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- equal(count, 1, "djselectableselect should only fire once.");
- start();
- }, 300);
- });
-
- test("Pagination Click", function() {
- expect(3);
- var count = 0,
- response = paginatedLookupResponse(),
- self = this;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- $('.ui-menu-item.selectable-paginator:visible a').trigger("mouseenter");
- $('.ui-menu-item.selectable-paginator:visible a').trigger("click");
- equal(self.requests.length, 2, "Another AJAX request should be triggered.");
- equal(count, 0, "djselectableselect should not fire for new page.");
- start();
- }, 300);
- });
-
- test("Pagination Enter", function() {
- expect(3);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = paginatedLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.bind('djselectableselect', function(e, item) {
- count = count + 1;
- });
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- equal(self.requests.length, 1, "AJAX request should be triggered.");
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- equal(self.requests.length, 2, "Another AJAX request should be triggered.");
- equal(count, 0, "djselectableselect should not fire for new page.");
- start();
- }, 300);
- });
-
- test("Pagination Render", function() {
- expect(2);
- var count = 0,
- down = jQuery.Event("keydown"),
- enter = jQuery.Event("keydown"),
- response = paginatedLookupResponse(),
- self = this;
- down.keyCode = $.ui.keyCode.DOWN;
- enter.keyCode = $.ui.keyCode.ENTER;
- this.textInput.val("ap").keydown();
- stop();
- setTimeout(function () {
- var options;
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- options = $('li.ui-menu-item:visible');
- equal(options.length, 3, "Currently 3 menu items.");
- // $('.selectable-paginator:visible').click();
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(down);
- self.textInput.trigger(enter);
- self.requests[1].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- options = $('li.ui-menu-item:visible');
- equal(options.length, 5, "Now 5 menu items.");
- start();
- }, 300);
- });
-
- module("Custom Event Tests", {
- setup: function () {
- this.inputs = createTextSelectMultiple('autocompleteselectmultiple');
- this.textInput = this.inputs[0];
- this.hiddenInput = this.inputs[1];
- $('#qunit-fixture').append(this.textInput);
- bindSelectables('#qunit-fixture');
- }
- });
-
- test("Add Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'};
- this.textInput.bind('djselectableadd', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- equal(count, 1, "djselectableadd should fire once when manually selected.");
- });
-
- test("Prevent Add Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableadd', function(e, item) {
- return false;
- });
- this.textInput.djselectable('select', item);
- deck = $('.selectable-deck', '#qunit-fixture');
- equal($('li', deck).length, 0, "Item should not be added.");
- });
-
- test("Remove Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableremove', function(e, item) {
- count = count + 1;
- });
- this.textInput.djselectable('select', item);
- $('.selectable-deck-remove', deck).click();
- equal(count, 1, "djselectableremove should fire once when item is removed.");
- });
-
- test("Prevent Remove Deck Item", function() {
- expect(1);
- var count = 0,
- item = {id: "1", value: 'foo'},
- deck = $('.selectable-deck', '#qunit-fixture');
- this.textInput.bind('djselectableremove', function(e, item) {
- return false;
- });
- var item = {id: "1", value: 'foo'};
- this.textInput.djselectable('select', item);
- $('.selectable-deck-remove', deck).click();
- equal($('li', deck).length, 1, "Item should not be removed.");
- });
-});
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- var expectedNamespace = 'djselectable',
- useData = true;
- if (window.uiversion.lastIndexOf('1.10', 0) === 0) {
- // jQuery UI 1.10 introduces a namespace change to include ui-prefix
- expectedNamespace = 'ui-' + expectedNamespace;
- }
- if (window.uiversion.lastIndexOf('1.11', 0) === 0) {
- // jQuery UI 1.11 introduces an instance method to get the current instance
- useData = false;
- }
-
- module("Autocomplete Text Methods Tests");
-
- test("Bind Input", function () {
- expect(2);
- var input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- ok(input.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(input.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(input.djselectable('instance'), "input should be bound with djselecable widget");
- }
- });
-
- test("Manual Selection", function () {
- expect(1);
- var item = {id: "1", value: 'foo'},
- input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- input.djselectable('select', item);
- equal(input.val(), item.value, "input should get item value");
- });
-
- test("Initial Data", function () {
- expect(1);
- var input = createTextComplete('autocomplete');
- input.val('Foo');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- equal(input.val(), 'Foo', "initial text value should not be lost");
- });
-
-
- module("Autocombobox Text Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var input = createTextCombobox('autocombobox'), button;
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(input.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(input.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(input.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(1);
- var item = {id: "1", value: 'foo'},
- input = createTextCombobox('autocombobox');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- input.djselectable('select', item);
- equal(input.val(), item.value, "input should get item value");
- });
-
- test("Initial Data", function () {
- expect(1);
- var input = createTextCombobox('autocombobox');
- input.val('Foo');
- $('#qunit-fixture').append(input);
- bindSelectables('#qunit-fixture');
- equal(input.val(), 'Foo', "initial text value should not be lost");
- });
-
- module("Autocomplete Select Methods Tests");
-
- test("Bind Input", function () {
- expect(2);
- var inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- equal(textInput.val(), item.value, "input should get item value");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(2);
- var inputs = createTextSelect('autocompleteselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- equal(textInput.val(), 'Foo', "initial text value should not be lost");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- });
-
- module("Autocombobox Select Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1], button;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- equal(textInput.val(), item.value, "input should get item value");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(2);
- var inputs = createComboboxSelect('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1];
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- equal(textInput.val(), 'Foo', "initial text value should not be lost");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- });
-
- module("Autocomplete Select Multiple Methods Tests");
-
- test("Bind Input", function () {
- expect(3);
- var inputs = createTextSelectMultiple('autocompletemultiple'),
- textInput = inputs[0], deck;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal($('li', deck).length, 0, "no initial deck items");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createTextSelectMultiple('autocompletemultiple'),
- textInput = inputs[0], hiddenInput;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- hiddenInput = $(':input[type=hidden][name=autocompletemultiple_1]', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(3);
- var inputs = createTextSelectMultiple('autocomboboxselect'),
- textInput = inputs[0], hiddenInput = inputs[1], deck;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- equal($('li', deck).length, 1, "one initial deck items");
- });
-
- module("Autocombobox Select Multiple Methods Tests");
-
- test("Bind Input", function () {
- expect(4);
- var inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], deck, button;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- button = $('.ui-combo-button', '#qunit-fixture');
- ok(textInput.hasClass('ui-autocomplete-input'), "input should be bound with djselecable widget");
- if (useData) {
- ok(textInput.data(expectedNamespace), "input should be bound with djselecable widget");
- } else {
- ok(textInput.djselectable('instance'), "input should be bound with djselecable widget");
- }
- equal($('li', deck).length, 0, "no initial deck items");
- equal(button.length, 1, "combobox button should be created");
- });
-
- test("Manual Selection", function () {
- expect(2);
- var item = {id: "1", value: 'foo'},
- inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], hiddenInput;
- $('#qunit-fixture').append(textInput);
- bindSelectables('#qunit-fixture');
- textInput.djselectable('select', item);
- hiddenInput = $(':input[type=hidden][name=autocomboboxmultiple_1]', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), item.id, "input should get item id");
- });
-
- test("Initial Data", function () {
- expect(3);
- var inputs = createComboboxSelectMultiple('autocomboboxmultiple'),
- textInput = inputs[0], hiddenInput = inputs[1], deck;
- $('#qunit-fixture').append(textInput);
- $('#qunit-fixture').append(hiddenInput);
- textInput.val('Foo');
- hiddenInput.val('1');
- bindSelectables('#qunit-fixture');
- deck = $('.selectable-deck', '#qunit-fixture');
- equal(textInput.val(), '', "input should be empty");
- equal(hiddenInput.val(), '1', "initial pk value should not be lost");
- equal($('li', deck).length, 1, "one initial deck items");
- });
-});
\ No newline at end of file
+++ /dev/null
-/*global define, module, test, expect, equal, ok*/
-
-define(['selectable'], function ($) {
-
- module("Plugin Options Tests", {
- setup: function () {
- // Patch AJAX requests
- var self = this;
- this.xhr = sinon.useFakeXMLHttpRequest();
- this.requests = [];
- this.xhr.onCreate = function (xhr) {
- self.requests.push(xhr);
- };
- this.input = createTextComplete('autocomplete');
- $('#qunit-fixture').append(this.input);
- bindSelectables('#qunit-fixture');
- },
- teardown: function () {
- this.xhr.restore();
- this.input.djselectable('destroy');
- }
- });
-
- test("Highlight Match On", function () {
- expect(2);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, highlight;
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- highlight = $('.highlight', item);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-
- test("Highlight Match Off", function () {
- expect(1);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, highlight;
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label String (No Highlight)", function () {
- expect(3);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return "<span class='custom'>" + label + "</span>";
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label jQuery Object (No Highlight)", function () {
- expect(3);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight
- function customFormat(label, item) {
- return $("<span>").addClass("custom").text(label);
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", false);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', item);
- equal(highlight.length, 0, "Highlight should not be present");
- start();
- }, 300);
- });
-
- test("Format Label String (With Highlight)", function () {
- expect(4);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return "<span class='custom'>" + label + "</span>";
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', custom);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-
- test("Format Label jQuery Object (With Highlight)", function () {
- expect(4);
- var response = simpleLookupResponse(),
- self = this,
- menu, item, custom, highlight;
- function customFormat(label, item) {
- return $("<span>").addClass("custom").text(label);
- }
- this.input.djselectable("option", "formatLabel", customFormat);
- this.input.djselectable("option", "highlightMatch", true);
- this.input.val("ap").keydown();
- stop();
- setTimeout(function () {
- self.requests[0].respond(200, {"Content-Type": "application/json"},
- JSON.stringify(response)
- );
- menu = $('ul.ui-autocomplete.ui-menu:visible');
- item = $('li', menu).eq(0);
- custom = $('.custom', item);
- equal(custom.length, 1, "Custom label should be present");
- equal(custom.text(), "Apple", "Label text should match");
- highlight = $('.highlight', custom);
- equal(highlight.length, 1, "Highlight should be present");
- equal(highlight.text(), "Ap", "Highlight text should match");
- start();
- }, 300);
- });
-});
\ No newline at end of file
+++ /dev/null
-from django.template import Template, Context
-
-from selectable.tests.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 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('that.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 ..forms import BaseLookupForm
-from .base import BaseSelectableTestCase
-
-
-__all__ = (
- 'BaseLookupFormTestCase',
-)
-
-
-class BaseLookupFormTestCase(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.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=25):
- 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'], 25)
-
- def test_no_max_set(self):
- """
- If SELECTABLE_MAX_LIMIT is not set but given then the form
- will return the given limit.
- """
-
- with self.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.
- """
-
- with self.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.
- """
-
- with self.settings(SELECTABLE_MAX_LIMIT=25):
- data = self.get_valid_data()
- data['limit'] = 125
- form = BaseLookupForm(data)
- self.assertTrue(form.is_valid(), "%s" % form.errors)
- self.assertEqual(form.cleaned_data['limit'], 25)
+++ /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
-
-
-__all__ = (
- 'FuncAutoCompleteSelectTestCase',
- 'FuncSelectModelChoiceTestCase',
- 'FuncComboboxModelChoiceTestCase',
- 'FuncManytoManyMultipleSelectTestCase',
- 'FuncFormTestCase',
-)
-
-
-class OtherThingForm(forms.ModelForm):
-
- thing = AutoCompleteSelectField(lookup_class=ThingLookup)
-
- class Meta(object):
- model = OtherThing
- fields = ('name', 'thing', )
-
-
-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()
- # Selected text should be populated
- self.assertInHTML(
- '''
- <input data-selectable-allow-new="false" data-selectable-type="text"
- data-selectable-url="/selectable-tests/selectable-thinglookup/"
- id="id_thing_0" name="thing_0" type="text" value="{}" {} />
- '''.format(self.test_thing.name,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
- # Selected pk should be populated
- self.assertInHTML(
- '''
- <input data-selectable-type="hidden" name="thing_1" id="id_thing_1"
- type="hidden" value="{}" {} />
- '''.format(self.test_thing.pk,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form,
- )
-
- 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()
- # Selected text should be populated
- self.assertInHTML(
- '''
- <input data-selectable-allow-new="false" data-selectable-type="text"
- data-selectable-url="/selectable-tests/selectable-thinglookup/"
- id="id_thing_0" name="thing_0" type="text" value="{}" {} />
- '''.format(self.test_thing.name,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
- # Selected pk should be populated
- self.assertInHTML(
- '''
- <input data-selectable-type="hidden" name="thing_1" id="id_thing_1"
- type="hidden" value="{}" {} />
- '''.format(self.test_thing.pk,
- 'required' if hasattr(form, 'use_required_attribute') else ''),
- rendered_form
- )
-
-
-class SelectWidgetForm(forms.ModelForm):
-
- class Meta(object):
- model = OtherThing
- fields = ('name', 'thing', )
- 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
- fields = ('name', 'thing', )
- 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
- fields = ('name', 'things', )
-
-
-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))
-
- def test_render_form(self):
- thing_1 = self.create_thing()
- manything = ManyThing.objects.create(name='Foo')
- manything.things.add(thing_1)
- form = ManyThingForm(instance=manything)
- rendered = form.as_p()
- self.assertIn('title="{0}"'.format(thing_1.name),
- rendered)
-
-
-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.12.4')
- self.assertUIVersion(result, '1.11.4')
-
- 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, 'smoothness', '1.11.4')
-
- 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.11.4')
-
- 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.11.4')
+++ /dev/null
-from __future__ import division
-
-import json
-
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.test import override_settings
-
-from . import ThingLookup
-from .base import BaseSelectableTestCase
-
-
-__all__ = (
- 'SelectableViewTest',
-)
-
-
-@override_settings(SELECTABLE_MAX_LIMIT=25)
-class SelectableViewTest(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 . import Thing, ThingLookup
-from ..compat import urlparse
-from ..forms import widgets
-from .base import BaseSelectableTestCase, parsed_inputs, parsed_widget_attributes
-
-__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('that.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_rendered_attrs(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- 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()
- attrs = parsed_widget_attributes(widget.widgets[1])
- 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)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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.assertEqual(widget_val, '1')
-
-
-class AutoComboboxWidgetTestCase(BaseSelectableTestCase, WidgetTestMixin):
- widget_cls = widgets.AutoComboboxWidget
- lookup_cls = ThingLookup
-
- def test_rendered_attrs(self):
- widget = self.get_widget_instance()
- attrs = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- 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 = parsed_widget_attributes(widget)
- 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()
- attrs = parsed_widget_attributes(widget.widgets[1])
- 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)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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()
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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()
- attrs = parsed_widget_attributes(widget.widgets[1])
- 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])
- rendered_value = widget.render('field_name', qs_val)
- inputs = parsed_inputs(rendered_value)
- found_values = []
- found_titles = []
- 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_titles.append(field.attributes['title'].value)
- found_values.append(field.attributes['value'].value)
- self.assertListEqual(found_values, [str(t1.pk), str(t2.pk)])
- self.assertListEqual(found_titles, [t1.name, t2.name])
-
- def test_update_query_parameters(self):
- params = {'active': 1}
- widget = self.get_widget_instance()
- widget.update_query_parameters(params)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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()
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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()
- attrs = parsed_widget_attributes(widget.widgets[1])
- 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])
- 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)
- attrs = parsed_widget_attributes(widget.widgets[0])
- url = attrs['data-selectable-url']
- parse = urlparse(url)
- query = parse.query
- self.assertEqual(query, urlencode(params))
-
- def test_limit_parameter(self):
- widget = self.get_widget_instance(limit=10)
- attrs = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- 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 = parsed_widget_attributes(widget.widgets[0])
- self.assertTrue('data-selectable-options' in attrs)
- self.assertEqual(attrs['data-selectable-options'], json.dumps(options))
+++ /dev/null
-from django.conf.urls import handler404, handler500, include, url
-
-
-handler404 = 'selectable.tests.views.test_404'
-handler500 = 'selectable.tests.views.test_500'
-
-urlpatterns = [
- url(r'^selectable-tests/', include('selectable.urls')),
-]
+++ /dev/null
-from django.http import HttpResponseNotFound, HttpResponseServerError
-
-
-def test_404(request):
- return HttpResponseNotFound()
-
-
-def test_500(request):
- return HttpResponseServerError()
+++ /dev/null
-import json
-
-from django import forms
-from django.utils.http import urlencode
-
-from selectable.compat import urlparse
-from selectable.forms import widgets
-from selectable.tests import Thing, ThingLookup
-from selectable.tests.base import BaseSelectableTestCase, parsed_inputs
-
-
-__all__ = (
- 'AutoCompleteWidgetTestCase',
- 'AutoCompleteSelectWidgetTestCase',
- 'AutoComboboxWidgetTestCase',
- 'AutoComboboxSelectWidgetTestCase',
- 'AutoCompleteSelectMultipleWidgetTestCase',
- 'AutoComboboxSelectMultipleWidgetTestCase',
-)
-
-
-class BaseWidgetTestCase(BaseSelectableTestCase):
- 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(BaseWidgetTestCase):
- 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(BaseWidgetTestCase):
- 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))
-
-
-class AutoComboboxWidgetTestCase(BaseWidgetTestCase):
- 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(BaseWidgetTestCase):
- 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(BaseWidgetTestCase):
- 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(BaseWidgetTestCase):
- 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))
+++ /dev/null
-from django.conf.urls import url
-
-from . import views
-
-
-urlpatterns = [
- url(r'^(?P<lookup_name>[-\w]+)/$', views.get_lookup, name="selectable-lookup"),
-]
+++ /dev/null
-from __future__ import unicode_literals
-
-from django.http import Http404
-
-from selectable.registry import registry
-
-
-def get_lookup(request, lookup_name):
-
- lookup_cls = registry.get(lookup_name)
- if lookup_cls is None:
- raise Http404('Lookup %s not found' % lookup_name)
-
- lookup = lookup_cls()
- return lookup.results(request)
-
+++ /dev/null
-[coverage:run]
-branch = true
-omit = */tests/*, example/*, .tox/*, setup.py, runtests.py
-source = .
-
-[coverage:report]
-show_missing = true
-
-
-[bdist_wheel]
-universal = 1
+++ /dev/null
-#!/usr/bin/env python
-import os
-from setuptools import setup, find_packages
-
-
-def read_file(filename):
- """Read a file into a string"""
- path = os.path.abspath(os.path.dirname(__file__))
- filepath = os.path.join(path, filename)
- try:
- return open(filepath).read()
- except IOError:
- return ''
-
-
-setup(
- name='django-selectable',
- version=__import__('selectable').__version__,
- author='Mark Lavin',
- author_email='markdlavin@gmail.com',
- packages=find_packages(exclude=['example']),
- include_package_data=True,
- url='https://github.com/mlavin/django-selectable',
- license='BSD',
- description=' '.join(__import__('selectable').__doc__.splitlines()).strip(),
- classifiers=[
- 'Development Status :: 5 - Production/Stable',
- 'Intended Audience :: Developers',
- 'Framework :: Django',
- 'License :: OSI Approved :: BSD License',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.3',
- 'Programming Language :: Python :: 3.4',
- 'Programming Language :: Python :: 3.5',
- 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
- ],
- long_description=read_file('README.rst'),
- test_suite="runtests.runtests",
- tests_require=['mock'],
- zip_safe=False, # because we're including media that Django needs
-)
-ul.selectable-deck li.selectable-deck-item a.selectable-deck-remove {
- float: none;
- margin-left: 10px;
-}
-
#new_notification {
width: 400px;
}
+++ /dev/null
-/*
- * django-selectable UI widget CSS
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-ul.selectable-deck, ul.ui-autocomplete {
- list-style: none outside none;
-}
-ul.selectable-deck li.selectable-deck-item,
-ul.ui-autocomplete li.ui-menu-item {
- margin: 0;
- list-style-type: none;
-}
-ul.selectable-deck li.selectable-deck-item .selectable-deck-remove {
- float: right;
-}
-ul.selectable-deck-bottom-inline,
-ul.selectable-deck-top-inline {
- padding: 0;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item,
-ul.selectable-deck-top-inline li.selectable-deck-item {
- display: inline;
-}
-ul.selectable-deck-bottom-inline li.selectable-deck-item .selectable-deck-remove,
-ul.selectable-deck-top-inline li.selectable-deck-item .selectable-deck-remove {
- margin-left: 0.4em;
- display: inline;
- float: none;
-}
-ul.ui-autocomplete li.ui-menu-item span.highlight {
- font-weight: bold;
-}
-input.ui-combo-input {
- margin-right: 0;
- line-height: 1.3;
-}
-a.ui-combo-button {
- margin-left: -1px;
-}
-a.ui-combo-button .ui-button-text {
- padding: 0;
-}
+++ /dev/null
-/*jshint trailing:true, indent:4*/
-/*
- * django-selectable UI widget
- * Source: https://github.com/mlavin/django-selectable
- * Docs: http://django-selectable.readthedocs.org/
- *
- * Depends:
- * - jQuery 1.7+
- * - jQuery UI 1.8 widget factory
- *
- * Copyright 2010-2014, Mark Lavin
- * BSD License
- *
-*/
-(function ($) {
-
- $.widget("ui.djselectable", $.ui.autocomplete, {
-
- options: {
- removeIcon: "ui-icon-close",
- comboboxIcon: "ui-icon-triangle-1-s",
- defaultClasses: {
- "text": "ui-widget ui-widget-content ui-corner-all",
- "combobox": "ui-widget ui-widget-content ui-corner-left ui-combo-input"
- },
- prepareQuery: null,
- highlightMatch: true,
- formatLabel: null
- },
-
- _initDeck: function () {
- /* Create list display for currently selected items for multi-select */
- var self = this;
- var data = $(this.element).data();
- var style = data.selectablePosition || data['selectable-position'] || 'bottom';
- this.deck = $('<ul>').addClass('ui-widget selectable-deck selectable-deck-' + style);
- if (style === 'bottom' || style === 'bottom-inline') {
- $(this.element).after(this.deck);
- } else {
- $(this.element).before(this.deck);
- }
- $(self.hiddenMultipleSelector).each(function (i, input) {
- self._addDeckItem(input);
- });
- },
-
- _addDeckItem: function (input) {
- /* Add new deck list item from a given hidden input */
- var self = this,
- li = $('<li>').addClass('selectable-deck-item'),
- item = {element: self.element, input: input, wrapper: li, deck: self.deck},
- button;
- li.html($(input).attr('title'));
- if (self._trigger("add", null, item) === false) {
- input.remove();
- } else {
- button = this._removeButtonTemplate(item);
- button.click(function (e) {
- e.preventDefault();
- if (self._trigger("remove", e, item) !== false) {
- $(input).remove();
- li.remove();
- }
- });
- li.append(button).appendTo(this.deck);
- }
- },
-
- _removeButtonTemplate: function (item) {
- var options = {
- icons: {
- primary: this.options.removeIcon
- },
- text: false
- },
- button = $('<a>')
- .attr('href', '#')
- .addClass('selectable-deck-remove')
- .button(options);
- return button;
- },
-
- select: function (item, event) {
- /* Trigger selection of a given item.
- Item should contain two properties: id and value
- Event is the original select event if there is one.
- Event should not be passed if triggered manually.
- */
- var $input = $(this.element);
- $input.removeClass('ui-state-error');
- this._setHidden(item);
- if (item) {
- if (this.allowMultiple) {
- $input.val("");
- this.term = "";
- if ($(this.hiddenMultipleSelector + '[value="' + item.id + '"]').length === 0) {
- var newInput = $('<input />', {
- 'type': 'hidden',
- 'name': this.hiddenName,
- 'value': item.id,
- 'title': item.value,
- 'data-selectable-type': 'hidden-multiple'
- });
- $input.after(newInput);
- this._addDeckItem(newInput);
- }
- return false;
- } else {
- $input.val(item.value);
- var ui = {item: item};
- if (typeof(event) === 'undefined' || event.type !== "djselectableselect") {
- this.element.trigger("djselectableselect", [ui ]);
- }
- }
- }
- },
-
- _setHidden: function (item) {
- /* Set or clear single hidden input */
- var $elem = $(this.hiddenSelector);
- if (item && item.id) {
- $elem.val(item.id);
- } else {
- $elem.val("");
- }
- },
-
- _comboButtonTemplate: function (input) {
- // Add show all items button
- var options = {
- icons: {
- primary: this.options.comboboxIcon
- },
- text: false
- },
- button = $("<a>")
- .html(" ")
- .attr("tabIndex", -1)
- .attr("title", "Show All Items")
- .button(options)
- .removeClass("ui-corner-all")
- .addClass("ui-corner-right ui-button-icon ui-combo-button");
- return button;
- },
-
- _create: function () {
- /* Initialize a new selectable widget */
- var self = this,
- $input = $(this.element),
- data = $input.data(),
- options, button;
- this.url = data.selectableUrl || data['selectable-url'];
- this.allowNew = data.selectableAllowNew || data['selectable-allow-new'];
- this.allowMultiple = data.selectableMultiple || data['selectable-multiple'];
- this.textName = $input.attr('name');
- this.hiddenName = this.textName.replace(new RegExp('_0$'), '_1');
- this.hiddenSelector = ':input[data-selectable-type=hidden][name=' + this.hiddenName + ']';
- this.hiddenMultipleSelector = ':input[data-selectable-type=hidden-multiple][name=' + this.hiddenName + ']';
- this.selectableType = data.selectableType || data['selectable-type'];
- if (this.allowMultiple) {
- this.allowNew = false;
- $input.val("");
- this._initDeck();
- }
- options = data.selectableOptions || data['selectable-options'];
- if (options) {
- this._setOptions(options);
- }
- // Call super-create
- // This could be replaced by this._super() with jQuery UI 1.9
- $.ui.autocomplete.prototype._create.call(this);
- $input.addClass(this.options.defaultClasses[this.selectableType]);
- // Additional work for combobox widgets
- if (this.selectableType === 'combobox') {
- // Add show all items button
- button = this._comboButtonTemplate($input);
- button.insertAfter($input).click(function (e) {
- e.preventDefault();
- // close if already visible
- if (self.widget().is(":visible")) {
- self.close();
- }
- // pass empty string as value to search for, displaying all results
- self._search("");
- $input.focus();
- });
- }
- },
-
- // Override the default source creation
- _initSource: function () {
- var self = this,
- $input = $(this.element);
- this.source = function dataSource(request, response) {
- /* Custom data source to uses the lookup url with pagination
- Adds hook for adjusting query parameters.
- Includes timestamp to prevent browser caching the lookup. */
- var now = new Date().getTime(),
- query = {term: request.term, timestamp: now},
- page = $input.data("page");
- if (self.options.prepareQuery) {
- self.options.prepareQuery.apply(self, [query]);
- }
- if (page) {
- query.page = page;
- }
- function unwrapResponse(data) {
- var results = data.data,
- meta = data.meta;
- if (meta.next_page && meta.more) {
- results.push({
- id: '',
- value: '',
- label: meta.more,
- page: meta.next_page,
- term: request.term
- });
- }
- return response(results);
- }
- $.getJSON(self.url, query, unwrapResponse);
- };
- },
- // Override the default auto-complete render.
- _renderItem: function (ul, item) {
- /* Adds hook for additional formatting, allows HTML in the label,
- highlights term matches and handles pagination. */
- var label = item.label,
- self = this,
- $input = $(this.element),
- re, html, li;
- if (this.options.formatLabel && !item.page) {
- label = this.options.formatLabel.apply(this, [label, item]);
- }
- if (this.options.highlightMatch && this.term && !item.page) {
- re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" +
- $.ui.autocomplete.escapeRegex(this.term) +
- ")(?![^<>]*>)(?![^&;]+;)", "gi");
- if (label.html) {
- html = label.html();
- html = html.replace(re, "<span class='highlight'>$1</span>");
- label.html(html);
- } else {
- label = label.replace(re, "<span class='highlight'>$1</span>");
- }
- }
- li = $("<li></li>")
- .data("item.autocomplete", item)
- .append($("<a></a>").append(label))
- .appendTo(ul);
- if (item.page) {
- li.addClass('selectable-paginator');
- }
- return li;
- },
- // Override the default auto-complete suggest.
- _suggest: function (items) {
- /* Needed for handling pagination links */
- var $input = $(this.element),
- page = $input.data('page'),
- ul = this.menu.element;
- if (page) {
- $('.selectable-paginator', ul).remove();
- } else {
- ul.empty();
- }
- $input.data('page', null);
- ul.zIndex($input.zIndex() + 1);
- this._renderMenu(ul, items);
- // jQuery UI menu does not define deactivate
- if (this.menu.deactivate) {
- this.menu.deactivate();
- }
- this.menu.refresh();
- // size and position menu
- ul.show();
- this._resizeMenu();
- ul.position($.extend({of: this.element}, this.options.position));
- if (this.options.autoFocus) {
- this.menu.next(new $.Event("mouseover"));
- } else if (page) {
- $input.focus();
- }
- },
- // Override default trigger for additional change/select logic
- _trigger: function (type, event, data) {
- var $input = $(this.element),
- self = this;
- if (type === "select") {
- $input.removeClass('ui-state-error');
- if (data.item.page) {
- $input.data("page", data.item.page);
- this._search(data.item.term);
- return false;
- }
- return this.select(data.item, event);
- } else if (type === "change") {
- $input.removeClass('ui-state-error');
- this._setHidden(data.item);
- if ($input.val() && !data.item) {
- if (!this.allowNew) {
- $input.addClass('ui-state-error');
- }
- }
- if (this.allowMultiple && !$input.hasClass('ui-state-error')) {
- $input.val("");
- this.term = "";
- }
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype._trigger.apply(this, arguments);
- },
- close: function (event) {
- var page = $(this.element).data('page');
- if (page !== null) {
- return;
- }
- // Call super trigger
- // This could be replaced by this._super() with jQuery UI 1.9
- return $.ui.autocomplete.prototype.close.apply(this, arguments);
- }
- });
-
- window.bindSelectables = function (context) {
- /* Bind all selectable widgets in a given context.
- Automatically called on document.ready.
- Additional calls can be made for dynamically added widgets.
- */
- $(":input[data-selectable-type=text]", context).djselectable();
- $(":input[data-selectable-type=combobox]", context).djselectable();
- };
-
- function djangoAdminPatches() {
- /* 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" &&
- typeof(windowname_to_id) !== "undefined" &&
- typeof(html_unescape) !== "undefined") {
- var django_dismissAddAnotherPopup = dismissAddAnotherPopup;
- dismissAddAnotherPopup = function (win, newId, newRepr) {
- /* See if the popup came from a selectable field.
- If not, pass control to Django's code.
- If so, handle it. */
- var fieldName = windowname_to_id(win.name); /* e.g. "id_fieldname" */
- var field = $('#' + fieldName);
- var multiField = $('#' + fieldName + '_0');
- /* Check for bound selectable */
- var singleWidget = field.data('djselectable');
- var multiWidget = multiField.data('djselectable');
- if (singleWidget || multiWidget) {
- // newId and newRepr are expected to have previously been escaped by
- // django.utils.html.escape.
- var item = {
- id: html_unescape(newId),
- value: html_unescape(newRepr)
- };
- if (singleWidget) {
- field.djselectable('select', item);
- }
- if (multiWidget) {
- multiField.djselectable('select', item);
- }
- win.close();
- } else {
- /* Not ours, pass on to original function. */
- return django_dismissAddAnotherPopup(win, newId, newRepr);
- }
- };
- }
- }
-
- $(document).ready(function () {
- // Patch the django admin JS
- if (typeof(djselectableAdminPatch) === "undefined" || djselectableAdminPatch) {
- djangoAdminPatches();
- }
- // Bind existing widgets on document ready
- if (typeof(djselectableAutoLoad) === "undefined" || djselectableAutoLoad) {
- window.bindSelectables('body');
- }
- });
-})(jQuery || grp.jQuery);
from django import forms
from django.contrib import admin
-from selectable.forms.widgets import AutoCompleteSelectWidget
-
-from pgweb.core.lookups import UserLookup
-
from .models import Contributor, ContributorType
class Meta:
model = Contributor
exclude = ()
- widgets = {
- 'user': AutoCompleteSelectWidget(lookup_class=UserLookup),
- }
def __init__(self, *args, **kwargs):
super(ContributorAdminForm, self).__init__(*args, **kwargs)
- self.fields['user'].widget.can_add_related = False
- self.fields['user'].widget.can_change_related = False
class ContributorAdmin(admin.ModelAdmin):
from django import forms
from django.contrib import admin
-from selectable.forms.widgets import AutoCompleteSelectMultipleWidget
-
from pgweb.core.models import Version, OrganisationType, Organisation
from pgweb.core.models import ImportedRSSFeed, ImportedRSSItem
from pgweb.core.models import ModerationNotification
-from pgweb.core.lookups import UserLookup
-
class OrganisationAdminForm(forms.ModelForm):
class Meta:
model = Organisation
exclude = ()
- widgets = {
- 'managers': AutoCompleteSelectMultipleWidget(lookup_class=UserLookup),
- }
def __init__(self, *args, **kwargs):
super(OrganisationAdminForm, self).__init__(*args, **kwargs)
- self.fields['managers'].widget.can_add_related = False
- self.fields['managers'].widget.can_change_related = False
- self.fields['managers'].widget.can_delete_related = False
class OrganisationAdmin(admin.ModelAdmin):
+++ /dev/null
-from django.contrib.auth.models import User
-from selectable.base import ModelLookup
-from selectable.registry import registry
-from selectable.decorators import staff_member_required
-
-
-@staff_member_required
-class UserLookup(ModelLookup):
- model = User
- search_fields = (
- 'username__icontains',
- 'first_name__icontains',
- 'last_name__icontains',
- )
- filters = {'is_active': True, }
-
- def get_item_value(self, item):
- # Display for currently selected item
- return "%s (%s)" % (item.username, item.get_full_name())
-
- def get_item_label(self, item):
- # Display for choice listings
- return "%s (%s)" % (item.username, item.get_full_name())
-
-
-registry.register(UserLookup)
'django.contrib.admin',
'django_markwhat',
'django.contrib.staticfiles',
- 'pgweb.selectable',
'pgweb.core',
'pgweb.mailqueue',
'pgweb.account',
url(r'^admin/purge/$', pgweb.core.views.admin_purge),
url(r'^admin/mergeorg/$', pgweb.core.views.admin_mergeorg),
- # We use selectable only for /admin/ for now, so put it there to avoid caching issues
- url(r'^admin/selectable/', include('selectable.urls')),
-
# Uncomment the next line to enable the admin:
url(r'^admin/', include(admin.site.urls)),
+++ /dev/null
-dep/django-selectable/selectable
\ No newline at end of file
{%block extrahead%}
<link rel="stylesheet" href="/media/css/jquery-ui.css" type="text/css">
-<link href="/media/selectable/css/dj.selectable.css" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="/media/js/jquery.min.js"></script>
<script type="text/javascript" src="/media/js/jquery-ui.min.js"></script>
-<script type="text/javascript" src="/media/selectable/js/jquery.dj.selectable.js"></script>
<link rel="stylesheet" type="text/css" href="/media/css/admin_pgweb.css">
{%endblock%}