Skip to content

Commit

Permalink
Merge pull request #90 from joehybird/master
Browse files Browse the repository at this point in the history
Fix AgnocompleteWidgetMixin for template-based widgets (Django>=1.11)
  • Loading branch information
wo0dyn authored Jan 26, 2018
2 parents 19c4020 + 94042b0 commit 7af8bfb
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 22 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Changelog for django-agnocomplete
master (unreleased)
===================

Nothing here yet.
* Fix bug in AgnocompleteWidgetMixin when template-based widgets are used (Django>=1.11).

0.11.0 (2018-01-25)
===================
Expand Down
55 changes: 41 additions & 14 deletions agnocomplete/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,6 @@ def _agnocomplete_build_attrs(self, attrs):

return attrs

def render_options(self, *args):
# Django >= 1.10, only "selected_choices" in the arg list
if len(args) == 1:
selected_choices = args[0]
else:
# Django < 1.10 - selected_choices is the second arg.
_, selected_choices = args
selected_choices = set(text(v) for v in selected_choices)
selected_choices_tuples = self.agnocomplete.selected(selected_choices)
output = []
for option_value, option_label in selected_choices_tuples:
output.append(self.render_option(selected_choices, option_value, option_label)) # noqa
return '\n'.join(output)


if StrictVersion(get_version()) < StrictVersion('1.11'):
class AgnocompleteWidgetMixin(_AgnocompleteWidgetMixin):
Expand All @@ -69,6 +55,20 @@ def build_attrs(self, extra_attrs=None, **kwargs):
attrs = super(AgnocompleteWidgetMixin, self).build_attrs(
extra_attrs, **kwargs)
return self._agnocomplete_build_attrs(attrs)

def render_options(self, *args):
# Django >= 1.10, only "selected_choices" in the arg list
if len(args) == 1:
selected_choices = args[0]
else:
# Django < 1.10 - selected_choices is the second arg.
_, selected_choices = args
selected_choices = set(text(v) for v in selected_choices)
selected_choices_tuples = self.agnocomplete.selected(selected_choices)
output = []
for option_value, option_label in selected_choices_tuples:
output.append(self.render_option(selected_choices, option_value, option_label)) # noqa
return '\n'.join(output)
else:
class AgnocompleteWidgetMixin(_AgnocompleteWidgetMixin):
"""
Expand All @@ -79,6 +79,33 @@ def build_attrs(self, base_attrs, extra_attrs=None):
base_attrs, extra_attrs)
return self._agnocomplete_build_attrs(attrs)

"""
Returns the selected option set in order to retrieve the behaviour of
AgnocompleteWidgetMixin.render_options()
"""
def _agnocomplete_selected_options(self, options, value):
selected_options = {}

for opt in options:
opt_value = text(opt.get('value'))
opt_selected = (opt_value in value)

opt['selected'] = opt_selected
opt['attrs']['selected'] = opt_selected

if opt_selected:
selected_options[opt_value] = opt

return list(selected_options.values())

"""
Render only selected options
"""
def optgroups(self, name, value, attrs=None):
_selected_options = self._agnocomplete_selected_options
for name, options, index in super(AgnocompleteWidgetMixin, self).optgroups(name, value, attrs):
yield (name, _selected_options(options, value), index)


class AgnocompleteSelect(AgnocompleteWidgetMixin, widgets.Select):
"""
Expand Down
148 changes: 141 additions & 7 deletions demo/tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
from django.test import TestCase
from django.core.urlresolvers import reverse
from distutils.version import StrictVersion

from django import forms, get_version
from django.core.urlresolvers import reverse
from django.test import TestCase
import six

from agnocomplete import fields
from agnocomplete.exceptions import UnregisteredAgnocompleteException
from agnocomplete.fields import (
AgnocompleteField,
AgnocompleteMultipleField,
AgnocompleteModelMultipleField,
)
from agnocomplete.exceptions import UnregisteredAgnocompleteException

from agnocomplete.forms import UserContextFormMixin
from demo.autocomplete import (
AutocompleteColor,
HiddenAutocompleteURL,
HiddenAutocompleteURLReverse,
AutocompleteTag,
AutocompletePersonDomain,
)
from demo.models import Tag
from demo.models import Tag, Person
from demo.tests import LoaddataTestCase


def is_selected_attr():
if StrictVersion(get_version()) < StrictVersion('1.11'):
return 'selected="selected"'
else:
return 'selected'


class AgnocompleteInstanceTest(TestCase):
Expand Down Expand Up @@ -70,8 +82,57 @@ def test_class_url_reversed(self):
)


class MultipleSelectTest(TestCase):
class ModelSelectTest(LoaddataTestCase):
class _Form(UserContextFormMixin, forms.Form):
person = fields.AgnocompleteModelField(AutocompletePersonDomain,
to_field_name='email')

def setUp(self):
super(ModelSelectTest, self).setUp()
self.alice = Person.objects.get(pk=1)
self.bob = Person.objects.get(pk=3)

self.invalid_pk = Person.objects.order_by('pk').last().pk + 1

def test_render(self):
form = self._Form(
user=None,
data={'person': self.bob.email},
)

# bob is selected => only bob <option>
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>'.format(self.bob.email, is_selected_attr()),
html_form
)
self.assertNotIn('<option value="{}">'.format(self.alice.email),
html_form
)

# alice is selected => only alice <option>
form = self._Form(
user=None,
data={'person': self.alice.email},
)
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>'.format(self.alice.email, is_selected_attr()),
html_form
)
self.assertNotIn('<option value="{}">'.format(self.bob.email),
html_form
)

def test_render_no_selection(self):
# none is selected => no <option>
form = self._Form(
user=None,
data={'person': self.invalid_pk},
)
html_form = "{}".format(form)
self.assertNotIn('<option', html_form)


class MultipleSelectTest(TestCase):
def test_empty(self):
field = AgnocompleteMultipleField(
AutocompleteTag,
Expand All @@ -82,7 +143,21 @@ def test_empty(self):
self.assertEqual(field.clean([""]), [])


class MultipleModelSelectTest(TestCase):
class MultipleModelSelectTest(LoaddataTestCase):
class _Form(forms.Form):
persons = fields.AgnocompleteModelMultipleField(AutocompletePersonDomain,
to_field_name='email')

def setUp(self):
super(MultipleModelSelectTest, self).setUp()
self.alice = Person.objects.get(pk=1)
self.bob = Person.objects.get(pk=3)

self.aliceinchains = Person.objects.get(pk=2)
self.aliceinchains.email = self.alice.email
self.aliceinchains.save()

self.invalid_pk = Person.objects.order_by('pk').last().pk + 1

def test_empty_list(self):
field = AgnocompleteModelMultipleField(
Expand All @@ -94,3 +169,62 @@ def test_empty_list(self):
self.assertQuerysetEqual(field.clean(""), empty_qs)
self.assertQuerysetEqual(field.clean([]), empty_qs)
self.assertQuerysetEqual(field.clean([""]), empty_qs)

def test_render(self):
form = self._Form(
data={'persons': [self.alice.email]},
)

# linux is selected => only linux <option>
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>'.format(self.alice.email, is_selected_attr()),
html_form
)
self.assertNotIn('<option value="{}">'.format(self.bob.email),
html_form
)

# python is selected => only python <option>
form = self._Form(
data={'persons': [self.bob.email]},
)
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>'.format(self.bob.email, is_selected_attr()),
html_form
)
self.assertNotIn('<option value="{}">'.format(self.alice.email),
html_form
)

def test_render_multiple(self):
# both linux and python are selected => all <option>
form = self._Form(
data={'persons': [self.alice.email, self.bob.email]},
)
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>'.format(self.alice.email, is_selected_attr()),
html_form
)
self.assertIn('<option value="{}" {}>'.format(self.bob.email, is_selected_attr()),
html_form
)

def test_render_same_key(self):
# both linux and python are selected => all <option>
form = self._Form(
data={'persons': [self.alice.email, self.aliceinchains.email]},
)
html_form = "{}".format(form)
self.assertIn('<option value="{}" {}>{}</option>'.format(
self.aliceinchains.email, is_selected_attr(), self.aliceinchains
),
html_form
)

def test_render_no_selection(self):
# none is selected => no <option>
form = self._Form(
data={'persons': [self.invalid_pk]},
)
html_form = "{}".format(form)
self.assertNotIn('<option', html_form)

0 comments on commit 7af8bfb

Please sign in to comment.