Chained select2
This commit is contained in:
parent
5840622794
commit
1931a98240
|
@ -1,6 +1,9 @@
|
||||||
Changelog Summary
|
Changelog Summary
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
### v5.10.0
|
||||||
|
* Add support for dependent select fields [321](github.com/applegrew/django-select2/pull/321/).
|
||||||
|
|
||||||
### v5.9.0
|
### v5.9.0
|
||||||
* Add support for Django 1.11 LTS
|
* Add support for Django 1.11 LTS
|
||||||
* Drop support for Django 1.9
|
* Drop support for Django 1.9
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
"""
|
"""
|
||||||
This is a Django_ integration of Select2_.
|
This is a Django_ integration of Select2_.
|
||||||
|
|
||||||
The app includes Select2 driven Django Widgets and Form Fields.
|
The application includes Select2 driven Django Widgets and Form Fields.
|
||||||
|
|
||||||
.. _Django: https://www.djangoproject.com/
|
.. _Django: https://www.djangoproject.com/
|
||||||
.. _Select2: http://ivaynberg.github.com/select2/
|
.. _Select2: http://ivaynberg.github.com/select2/
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "5.9.0"
|
__version__ = "5.10.0"
|
||||||
|
|
|
@ -185,6 +185,8 @@ class Select2TagWidget(Select2TagMixin, Select2Mixin, forms.SelectMultiple):
|
||||||
class HeavySelect2Mixin(object):
|
class HeavySelect2Mixin(object):
|
||||||
"""Mixin that adds select2's AJAX options and registers itself on Django's cache."""
|
"""Mixin that adds select2's AJAX options and registers itself on Django's cache."""
|
||||||
|
|
||||||
|
dependent_fields = {}
|
||||||
|
|
||||||
def __init__(self, attrs=None, choices=(), **kwargs):
|
def __init__(self, attrs=None, choices=(), **kwargs):
|
||||||
"""
|
"""
|
||||||
Return HeavySelect2Mixin.
|
Return HeavySelect2Mixin.
|
||||||
|
@ -192,7 +194,12 @@ class HeavySelect2Mixin(object):
|
||||||
Args:
|
Args:
|
||||||
data_view (str): URL pattern name
|
data_view (str): URL pattern name
|
||||||
data_url (str): URL
|
data_url (str): URL
|
||||||
|
dependent_fields (dict): Dictionary of dependent parent fields.
|
||||||
|
The value of the dependent field will be passed as to :func:`.filter_queryset`.
|
||||||
|
It can be used to further restrict the search results. For example, a city
|
||||||
|
widget could be dependent on a country.
|
||||||
|
Key is a name of a field in a form.
|
||||||
|
Value is a name of a field in a model (used in `queryset`).
|
||||||
"""
|
"""
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
if attrs is not None:
|
if attrs is not None:
|
||||||
|
@ -202,6 +209,10 @@ class HeavySelect2Mixin(object):
|
||||||
|
|
||||||
self.data_view = kwargs.pop('data_view', None)
|
self.data_view = kwargs.pop('data_view', None)
|
||||||
self.data_url = kwargs.pop('data_url', None)
|
self.data_url = kwargs.pop('data_url', None)
|
||||||
|
|
||||||
|
dependent_fields = kwargs.pop('dependent_fields', None)
|
||||||
|
if dependent_fields is not None:
|
||||||
|
self.dependent_fields = dict(dependent_fields)
|
||||||
if not (self.data_view or self.data_url):
|
if not (self.data_view or self.data_url):
|
||||||
raise ValueError('You must ether specify "data_view" or "data_url".')
|
raise ValueError('You must ether specify "data_view" or "data_url".')
|
||||||
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
||||||
|
@ -224,6 +235,8 @@ class HeavySelect2Mixin(object):
|
||||||
attrs.setdefault('data-ajax--cache', "true")
|
attrs.setdefault('data-ajax--cache', "true")
|
||||||
attrs.setdefault('data-ajax--type', "GET")
|
attrs.setdefault('data-ajax--type', "GET")
|
||||||
attrs.setdefault('data-minimum-input-length', 2)
|
attrs.setdefault('data-minimum-input-length', 2)
|
||||||
|
if self.dependent_fields:
|
||||||
|
attrs.setdefault('data-select2-dependent-fields', " ".join(self.dependent_fields))
|
||||||
|
|
||||||
attrs['class'] += ' django-select2-heavy'
|
attrs['class'] += ' django-select2-heavy'
|
||||||
return attrs
|
return attrs
|
||||||
|
@ -334,7 +347,7 @@ class ModelSelect2Mixin(object):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
model (django.db.models.Model): Model to select choices from.
|
model (django.db.models.Model): Model to select choices from.
|
||||||
queryset (django.db.models.QuerySet): QuerySet to select choices from.
|
queryset (django.db.models.query.QuerySet): QuerySet to select choices from.
|
||||||
search_fields (list): List of model lookup strings.
|
search_fields (list): List of model lookup strings.
|
||||||
max_results (int): Max. JsonResponse view page size.
|
max_results (int): Max. JsonResponse view page size.
|
||||||
|
|
||||||
|
@ -364,14 +377,19 @@ class ModelSelect2Mixin(object):
|
||||||
'search_fields': self.search_fields,
|
'search_fields': self.search_fields,
|
||||||
'max_results': self.max_results,
|
'max_results': self.max_results,
|
||||||
'url': self.get_url(),
|
'url': self.get_url(),
|
||||||
|
'dependent_fields': self.dependent_fields,
|
||||||
})
|
})
|
||||||
|
|
||||||
def filter_queryset(self, term, queryset=None):
|
def filter_queryset(self, term, queryset=None, **dependent_fields):
|
||||||
"""
|
"""
|
||||||
Return QuerySet filtered by search_fields matching the passed term.
|
Return QuerySet filtered by search_fields matching the passed term.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
term (str): Search term
|
term (str): Search term
|
||||||
|
queryset (django.db.models.query.QuerySet): QuerySet to select choices from.
|
||||||
|
**dependent_fields: Dependent fields and their values. If you want to inherit
|
||||||
|
from ModelSelect2Mixin and later call to this method, be sure to pop
|
||||||
|
from kwargs everything if it is not a dependent field.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
QuerySet: Filtered QuerySet
|
QuerySet: Filtered QuerySet
|
||||||
|
@ -386,6 +404,9 @@ class ModelSelect2Mixin(object):
|
||||||
for t in [t for t in term.split(' ') if not t == '']:
|
for t in [t for t in term.split(' ') if not t == '']:
|
||||||
select &= reduce(lambda x, y: x | Q(**{y: t}), search_fields,
|
select &= reduce(lambda x, y: x | Q(**{y: t}), search_fields,
|
||||||
Q(**{search_fields[0]: t}))
|
Q(**{search_fields[0]: t}))
|
||||||
|
if dependent_fields:
|
||||||
|
select &= Q(**dependent_fields)
|
||||||
|
|
||||||
return queryset.filter(select).distinct()
|
return queryset.filter(select).distinct()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -7,11 +7,21 @@
|
||||||
var settings = $.extend({
|
var settings = $.extend({
|
||||||
ajax: {
|
ajax: {
|
||||||
data: function (params) {
|
data: function (params) {
|
||||||
return {
|
var result = {
|
||||||
term: params.term,
|
term: params.term,
|
||||||
page: params.page,
|
page: params.page,
|
||||||
field_id: $element.data('field_id')
|
field_id: $element.data('field_id')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dependentFields = $element.data('select2-dependent-fields')
|
||||||
|
if (dependentFields) {
|
||||||
|
dependentFields = dependentFields.trim().split(/\s+/)
|
||||||
|
$.each(dependentFields, function (i, dependentField) {
|
||||||
|
result[dependentField] = $('[name=' + dependentField + ']', $element.closest('form')).val()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
},
|
},
|
||||||
processResults: function (data, page) {
|
processResults: function (data, page) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -52,7 +52,12 @@ class AutoResponseView(BaseListView):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
"""Get QuerySet from cached widget."""
|
"""Get QuerySet from cached widget."""
|
||||||
return self.widget.filter_queryset(self.term, self.queryset)
|
kwargs = {
|
||||||
|
model_field_name: self.request.GET.get(form_field_name)
|
||||||
|
for form_field_name, model_field_name in self.widget.dependent_fields.items()
|
||||||
|
if form_field_name in self.request.GET and self.request.GET.get(form_field_name, '') != ''
|
||||||
|
}
|
||||||
|
return self.widget.filter_queryset(self.term, self.queryset, **kwargs)
|
||||||
|
|
||||||
def get_paginate_by(self, queryset):
|
def get_paginate_by(self, queryset):
|
||||||
"""Paginate response by size of widget's `max_results` parameter."""
|
"""Paginate response by size of widget's `max_results` parameter."""
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
Extra
|
||||||
|
=====
|
||||||
|
|
||||||
|
Chained select2
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Suppose you have an address form where a user should choose a Country and a City.
|
||||||
|
When the user selects a country we want to show only cities belonging to that country.
|
||||||
|
So the one selector depends on another one.
|
||||||
|
|
||||||
|
Models
|
||||||
|
``````
|
||||||
|
|
||||||
|
Here are our two models:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Country(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
|
||||||
|
class City(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
country = models.ForeignKey('Country', related_name="cities")
|
||||||
|
|
||||||
|
|
||||||
|
Customizing a Form
|
||||||
|
``````````````````
|
||||||
|
|
||||||
|
Lets link two widgets via *dependent_fields*.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 15
|
||||||
|
|
||||||
|
class AddressForm(forms.Form):
|
||||||
|
country = forms.ModelChoiceField(
|
||||||
|
queryset=Country.objects.all(),
|
||||||
|
label=u"Country",
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
city = forms.ModelChoiceField(
|
||||||
|
queryset=City.objects.all(),
|
||||||
|
label=u"City",
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
dependent_fields={'country': 'country'},
|
||||||
|
max_results=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Interdependent select2
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Also you may want not to restrict the user to which field should be selected first.
|
||||||
|
Instead you want to suggest to the user options for any select2 depending of his selection in another one.
|
||||||
|
|
||||||
|
Customize the form in a manner:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 7
|
||||||
|
|
||||||
|
class AddressForm(forms.Form):
|
||||||
|
country = forms.ModelChoiceField(
|
||||||
|
queryset=Country.objects.all(),
|
||||||
|
label=u"Country",
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
dependent_fields={'city': 'cities'},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
city = forms.ModelChoiceField(
|
||||||
|
queryset=City.objects.all(),
|
||||||
|
label=u"City",
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
dependent_fields={'country': 'country'},
|
||||||
|
max_results=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Take attention to country's dependent_fields. The value of 'city' is 'cities' because of
|
||||||
|
related name used in a filter condition `cities` which differs from widget field name `city`.
|
||||||
|
|
||||||
|
.. caution::
|
||||||
|
Be aware of using interdependent select2 in parent-child relation.
|
||||||
|
When a child is selected, you are restricted to change parent (only one value is available).
|
||||||
|
Probably you should let the user reset the child first to release parent select2.
|
||||||
|
|
||||||
|
|
||||||
|
Multi-dependent select2
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Furthermore you may want to filter options on two or more select2 selections (some code is dropped for clarity):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
:emphasize-lines: 14
|
||||||
|
|
||||||
|
class SomeForm(forms.Form):
|
||||||
|
field1 = forms.ModelChoiceField(
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
field2 = forms.ModelChoiceField(
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
field3 = forms.ModelChoiceField(
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
dependent_fields={'field1': 'field1', 'field2': 'field2'},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -14,6 +14,7 @@ Contents:
|
||||||
|
|
||||||
get_started
|
get_started
|
||||||
django_select2
|
django_select2
|
||||||
|
extra
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -25,6 +25,11 @@ def random_string(n):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def random_name(n):
|
||||||
|
words = ''.join(random.choice(string.ascii_lowercase + ' ') for _ in range(n)).strip().split(' ')
|
||||||
|
return '-'.join([x.capitalize() for x in words])
|
||||||
|
|
||||||
|
|
||||||
@pytest.yield_fixture(scope='session', params=sorted(browsers.keys()))
|
@pytest.yield_fixture(scope='session', params=sorted(browsers.keys()))
|
||||||
def driver(request):
|
def driver(request):
|
||||||
if 'DISPLAY' not in os.environ:
|
if 'DISPLAY' not in os.environ:
|
||||||
|
@ -58,3 +63,19 @@ def artists(db):
|
||||||
return Artist.objects.bulk_create(
|
return Artist.objects.bulk_create(
|
||||||
[Artist(pk=pk, title=random_string(50)) for pk in range(100)]
|
[Artist(pk=pk, title=random_string(50)) for pk in range(100)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def countries(db):
|
||||||
|
from .testapp.models import Country
|
||||||
|
return Country.objects.bulk_create(
|
||||||
|
[Country(pk=pk, name=random_name(random.randint(10, 20))) for pk in range(10)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def cities(db, countries):
|
||||||
|
from .testapp.models import City
|
||||||
|
return City.objects.bulk_create(
|
||||||
|
[City(pk=pk, name=random_name(random.randint(5, 15)), country=random.choice(countries)) for pk in range(100)]
|
||||||
|
)
|
||||||
|
|
|
@ -23,7 +23,7 @@ from tests.testapp import forms
|
||||||
from tests.testapp.forms import (
|
from tests.testapp.forms import (
|
||||||
NUMBER_CHOICES, HeavySelect2MultipleWidgetForm, TitleModelSelect2Widget
|
NUMBER_CHOICES, HeavySelect2MultipleWidgetForm, TitleModelSelect2Widget
|
||||||
)
|
)
|
||||||
from tests.testapp.models import Genre
|
from tests.testapp.models import City, Country, Genre
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -359,3 +359,65 @@ class TestHeavySelect2MultipleWidget(object):
|
||||||
# genres should still have One as selected option
|
# genres should still have One as selected option
|
||||||
result_title = driver.find_element_by_css_selector('.select2-selection--multiple li').get_attribute('title')
|
result_title = driver.find_element_by_css_selector('.select2-selection--multiple li').get_attribute('title')
|
||||||
assert result_title == 'One'
|
assert result_title == 'One'
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddressChainedSelect2Widget(object):
|
||||||
|
url = reverse('model_chained_select2_widget')
|
||||||
|
form = forms.AddressChainedSelect2WidgetForm()
|
||||||
|
|
||||||
|
def test_widgets_selected_after_validation_error(self, db, live_server, driver, countries, cities):
|
||||||
|
driver.get(live_server + self.url)
|
||||||
|
|
||||||
|
WebDriverWait(driver, 60).until(
|
||||||
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-selection--single'))
|
||||||
|
)
|
||||||
|
country_container, city_container = driver.find_elements_by_css_selector('.select2-selection--single')
|
||||||
|
|
||||||
|
# clicking city select2 lists all available cities
|
||||||
|
city_container.click()
|
||||||
|
WebDriverWait(driver, 60).until(
|
||||||
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li'))
|
||||||
|
)
|
||||||
|
city_options = driver.find_elements_by_css_selector('.select2-results li')
|
||||||
|
city_names_from_browser = {option.text for option in city_options}
|
||||||
|
city_names_from_db = set(City.objects.values_list('name', flat=True))
|
||||||
|
assert len(city_names_from_browser) == City.objects.count()
|
||||||
|
assert city_names_from_browser == city_names_from_db
|
||||||
|
|
||||||
|
# selecting a country really does it
|
||||||
|
country_container.click()
|
||||||
|
WebDriverWait(driver, 60).until(
|
||||||
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:nth-child(2)'))
|
||||||
|
)
|
||||||
|
country_option = driver.find_element_by_css_selector('.select2-results li:nth-child(2)')
|
||||||
|
country_name = country_option.text
|
||||||
|
country_option.click()
|
||||||
|
assert country_name == country_container.text
|
||||||
|
|
||||||
|
# clicking city select2 lists reduced list of cities belonging to the country
|
||||||
|
city_container.click()
|
||||||
|
WebDriverWait(driver, 60).until(
|
||||||
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li'))
|
||||||
|
)
|
||||||
|
city_options = driver.find_elements_by_css_selector('.select2-results li')
|
||||||
|
city_names_from_browser = {option.text for option in city_options}
|
||||||
|
city_names_from_db = set(Country.objects.get(name=country_name).cities.values_list('name', flat=True))
|
||||||
|
assert len(city_names_from_browser) != City.objects.count()
|
||||||
|
assert city_names_from_browser == city_names_from_db
|
||||||
|
|
||||||
|
# selecting a city reaaly does it
|
||||||
|
city_option = driver.find_element_by_css_selector('.select2-results li:nth-child(2)')
|
||||||
|
city_name = city_option.text
|
||||||
|
city_option.click()
|
||||||
|
assert city_name == city_container.text
|
||||||
|
|
||||||
|
# clicking country select2 lists reduced list to the only country available to the city
|
||||||
|
country_container.click()
|
||||||
|
WebDriverWait(driver, 60).until(
|
||||||
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li'))
|
||||||
|
)
|
||||||
|
country_options = driver.find_elements_by_css_selector('.select2-results li')
|
||||||
|
country_names_from_browser = {option.text for option in country_options}
|
||||||
|
country_names_from_db = {City.objects.get(name=city_name).country.name}
|
||||||
|
assert len(country_names_from_browser) != Country.objects.count()
|
||||||
|
assert country_names_from_browser == country_names_from_db
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
!coverage.py: This is a private format, don't read it directly!{"lines":{}}
|
|
@ -10,7 +10,7 @@ from django_select2.forms import (
|
||||||
Select2Widget
|
Select2Widget
|
||||||
)
|
)
|
||||||
from tests.testapp import models
|
from tests.testapp import models
|
||||||
from tests.testapp.models import Album
|
from tests.testapp.models import Album, City, Country
|
||||||
|
|
||||||
|
|
||||||
class TitleSearchFieldMixin(object):
|
class TitleSearchFieldMixin(object):
|
||||||
|
@ -174,3 +174,27 @@ class ModelSelect2TagWidgetForm(forms.ModelForm):
|
||||||
widgets = {
|
widgets = {
|
||||||
'genres': GenreSelect2TagWidget
|
'genres': GenreSelect2TagWidget
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AddressChainedSelect2WidgetForm(forms.Form):
|
||||||
|
country = forms.ModelChoiceField(
|
||||||
|
queryset=Country.objects.all(),
|
||||||
|
label='Country',
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
model=Country,
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
max_results=500,
|
||||||
|
dependent_fields={'city': 'cities'},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
city = forms.ModelChoiceField(
|
||||||
|
queryset=City.objects.all(),
|
||||||
|
label='City',
|
||||||
|
widget=ModelSelect2Widget(
|
||||||
|
model=City,
|
||||||
|
search_fields=['name__icontains'],
|
||||||
|
dependent_fields={'country': 'country'},
|
||||||
|
max_results=500,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -33,3 +33,20 @@ class Album(models.Model):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class Country(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class City(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
country = models.ForeignKey('Country', related_name="cities", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
|
@ -4,8 +4,9 @@ from __future__ import absolute_import, unicode_literals
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
AlbumModelSelect2WidgetForm, HeavySelect2MultipleWidgetForm,
|
AddressChainedSelect2WidgetForm, AlbumModelSelect2WidgetForm,
|
||||||
HeavySelect2WidgetForm, ModelSelect2TagWidgetForm, Select2WidgetForm
|
HeavySelect2MultipleWidgetForm, HeavySelect2WidgetForm,
|
||||||
|
ModelSelect2TagWidgetForm, Select2WidgetForm
|
||||||
)
|
)
|
||||||
from .views import TemplateFormView, heavy_data_1, heavy_data_2
|
from .views import TemplateFormView, heavy_data_1, heavy_data_2
|
||||||
|
|
||||||
|
@ -26,6 +27,10 @@ urlpatterns = [
|
||||||
TemplateFormView.as_view(form_class=ModelSelect2TagWidgetForm),
|
TemplateFormView.as_view(form_class=ModelSelect2TagWidgetForm),
|
||||||
name='model_select2_tag_widget'),
|
name='model_select2_tag_widget'),
|
||||||
|
|
||||||
|
url(r'^model_chained_select2_widget/$',
|
||||||
|
TemplateFormView.as_view(form_class=AddressChainedSelect2WidgetForm),
|
||||||
|
name='model_chained_select2_widget'),
|
||||||
|
|
||||||
url(r'^heavy_data_1/$', heavy_data_1, name='heavy_data_1'),
|
url(r'^heavy_data_1/$', heavy_data_1, name='heavy_data_1'),
|
||||||
url(r'^heavy_data_2/$', heavy_data_2, name='heavy_data_2'),
|
url(r'^heavy_data_2/$', heavy_data_2, name='heavy_data_2'),
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue