[damned-lies] Make the order of the list of languages case insensitive in a user's page
- From: Claude Paroz <claudep src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [damned-lies] Make the order of the list of languages case insensitive in a user's page
- Date: Sat, 15 Aug 2015 09:12:51 +0000 (UTC)
commit c1ff657e0dec62fc58cc6bb5872b7e2188908d69
Author: Grégoire Détrez <gregoire detrez gu se>
Date: Thu Aug 13 11:33:14 2015 +0200
Make the order of the list of languages case insensitive in a user's page
This adds a new util function lc_sorted which is a localized version
of the built-in sorted function (using pyicu).
The new function is used to sort the list of available languages presented
to the user.
trans_sort_object_list is also been refactored to use the new function.
This patch adds a new dependency to the mock package, but only for the
test suite (the mock package is included in the standard library in
later versions of python but not yet in 2.X).
https://bugzilla.gnome.org/show_bug.cgi?id=753579
common/tests.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
common/utils.py | 24 ++++++++++---------
people/tests.py | 19 +++++++++++++++
people/views.py | 3 +-
4 files changed, 101 insertions(+), 12 deletions(-)
---
diff --git a/common/tests.py b/common/tests.py
index 8d9f77e..a255769 100644
--- a/common/tests.py
+++ b/common/tests.py
@@ -19,13 +19,19 @@
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from datetime import datetime, timedelta
+import operator
+from unittest import skipUnless
from django.core.urlresolvers import reverse
from django.test import TestCase
+from django.utils import translation
+from mock import MagicMock, patch
from people.models import Person
from teams.models import Team, Role
+from .utils import lc_sorted, pyicu_present, trans_sort_object_list
+
class CommonTest(TestCase):
def setUp(self):
@@ -92,3 +98,64 @@ class CommonTest(TestCase):
jt = Person.objects.get(first_name='John', last_name='Translator')
for role in Role.objects.filter(person=jt):
self.assertFalse(role.is_active)
+
+
+class LcSortedTest(TestCase):
+
+ @skipUnless(pyicu_present, "PyICU package is required for this test")
+ def test_lc_sorted_en_case_insensitive(self):
+ with translation.override('en'):
+ self.assertEqual(['A','b', 'C', 'z'], lc_sorted(['z', 'C', 'b', "A"]))
+
+ @skipUnless(pyicu_present, "PyICU package is required for this test")
+ def test_lc_sorted_fr_eacute_between_d_and_f(self):
+ with translation.override('fr'):
+ self.assertEqual(['d', 'é', 'f', 'z'], lc_sorted(['é', 'z', 'd', "f"]))
+
+ @skipUnless(pyicu_present, "PyICU package is required for this test")
+ def test_lc_sorted_sv_ouml_after_z(self):
+ with translation.override('sv'):
+ self.assertEqual(['a', 'o', 'z', 'ö'], lc_sorted(['z', 'ö', 'o', "a"]))
+
+ def test_lc_sorted_with_custom_key(self):
+ mykey = operator.itemgetter(1)
+ with translation.override('fr'):
+ self.assertEqual([('z', 'a'), ('a', 'z')],
+ lc_sorted([('a', 'z'), ('z', 'a')], key=mykey))
+
+
+class TransSortObjectListTest(TestCase):
+
+ def mkItem(self, name):
+ item = MagicMock(name=name)
+ item.name = name
+ return item
+
+ @patch('common.utils._', return_value="bar")
+ def test_translate_object_name(self, gettext):
+ """
+ The given field is translated using gettext and the translation is stored
+ in translated_name.
+ """
+ item = self.mkItem("foo")
+ trans_sort_object_list([item], 'name')
+ self.assertEqual("bar", item.translated_name)
+
+ @patch('common.utils._', side_effect=lambda s: s[::-1])
+ def test_sort_list_according_to_translation(self, _):
+ """
+ The list is sorted according to the computed translated_name
+ (here gettext reverses the string so foo (oof) comes before bar (rab)).
+ """
+ foo, bar = self.mkItem('foo'), self.mkItem('bar')
+ actual = trans_sort_object_list([foo,bar], 'name')
+ self.assertEqual([foo, bar], actual, "wrong order")
+
+ @skipUnless(pyicu_present, "PyICU package is required for this test")
+ @patch('common.utils._', side_effect=lambda x: x)
+ def test_uses_localized_ordering(self, _):
+ """Consider the current locale when ordering elements."""
+ d, eacute, f = self.mkItem('d'), self.mkItem('é'), self.mkItem('f')
+ with translation.override('fr'):
+ actual = trans_sort_object_list([f, eacute, d], 'name')
+ self.assertEqual([d, eacute, f], actual)
diff --git a/common/utils.py b/common/utils.py
index e8b4508..6e65bad 100644
--- a/common/utils.py
+++ b/common/utils.py
@@ -23,9 +23,9 @@ from django.conf import settings
from django.utils.translation import ugettext as _, get_language
from languages.models import Language
try:
- import PyICU
+ import icu
pyicu_present = True
-except:
+except ImportError:
pyicu_present = False
MIME_TYPES = {
@@ -33,17 +33,21 @@ MIME_TYPES = {
'xml': 'text/xml'
}
+def lc_sorted(*args, **kwargs):
+ """
+ Same as the built-in function ``sorted`` but according to the current locale.
+ """
+ if pyicu_present:
+ collator = icu.Collator.createInstance(icu.Locale(str(get_language())))
+ key = kwargs.get('key', lambda x: x)
+ kwargs['key'] = lambda x: collator.getSortKey(key(x))
+ return sorted(*args, **kwargs)
+
def trans_sort_object_list(lst, tr_field):
"""Sort an object list with translated_name"""
for l in lst:
l.translated_name = _(getattr(l, tr_field))
- templist = [(obj_.translated_name.lower(), obj_) for obj_ in lst]
- if pyicu_present:
- collator = PyICU.Collator.createInstance(PyICU.Locale(str(get_language())))
- templist.sort(key=operator.itemgetter(0), cmp=collator.compare)
- else:
- templist.sort()
- return [obj_ for (key1, obj_) in templist]
+ return lc_sorted(lst, key=lambda o: o.translated_name.lower())
def merge_sorted_by_field(object_list1, object_list2, field):
"""
@@ -112,8 +116,6 @@ def imerge_sorted_by_field(object_list1, object_list2, field):
>>> [el.num for el in imerge_sorted_by_field(l1, l2, '-num')]
[6, 5, 4, 4, 4, 2, 1, 1]
"""
- import operator
-
if field is not None and field[0] == '-':
# Reverse the sort order
field = field[1:]
diff --git a/people/tests.py b/people/tests.py
index 10d8ba8..d331850 100644
--- a/people/tests.py
+++ b/people/tests.py
@@ -20,11 +20,13 @@
from __future__ import unicode_literals
import datetime
+from unittest import skipUnless
from django.test import TestCase
from django.core.urlresolvers import reverse
from django.utils.safestring import SafeData
+from common.utils import pyicu_present
from people.models import Person, obfuscate_email
from people import forms
@@ -116,3 +118,20 @@ class PeopleTestCase(TestCase):
'Me <me dot company at domain dot org>\n'
'You <some-address at example dot com>'
)
+
+ @skipUnless(pyicu_present, "PyICU package is required for this test")
+ def test_all_languages_list_order(self):
+ """
+ The order of the languages should be case insensitive.
+ This tests that "français" appears before "Frisian".
+ """
+ Person.objects.create(username='jn', password='password')
+ self.client.login(username='jn', password='password')
+ url = reverse('person_detail_username', kwargs={'slug': 'jn'})
+ response = self.client.get(url)
+ all_languages = response.context['all_languages']
+ self.assertGreater(
+ all_languages.index(('fy-nl', 'Frisian')),
+ all_languages.index(('fr', "français")),
+ "français is not before Frisian"
+ )
diff --git a/people/views.py b/people/views.py
index ee9be3c..88bf7ae 100644
--- a/people/views.py
+++ b/people/views.py
@@ -32,6 +32,7 @@ from django.shortcuts import render, get_object_or_404
from django.utils.translation import ugettext_lazy, ugettext as _
from django.views.generic import ListView, DetailView, UpdateView
+from common.utils import lc_sorted
from people.models import Person
from teams.models import Team, Role
from people.forms import TeamJoinForm, DetailForm
@@ -55,7 +56,7 @@ class PersonDetailView(DetailView):
context = super(PersonDetailView, self).get_context_data(**kwargs)
states = State.objects.filter(action__person=self.object).distinct()
all_languages = [(lg[0], LANG_INFO.get(lg[0], {'name_local': lg[1]})['name_local']) for lg in
settings.LANGUAGES]
- all_languages.sort(key=itemgetter(1))
+ all_languages = lc_sorted(all_languages, key=itemgetter(1))
context.update({
'pageSection': "teams",
'all_languages': all_languages,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]