From 91917fa5390602db22c713e6e3b30c1daf560817 Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Thu, 21 Feb 2019 00:54:53 +0300 Subject: [PATCH 1/6] Create substring expression Create order_by_alphanumeric Test order_by_alphanumeric --- catalog/expressions.py | 8 ++++++++ catalog/models.py | 31 ++++++++++++++++--------------- tests/catalog/test_models.py | 16 ++++++++++++++++ tests/test_settings.py | 2 -- 4 files changed, 40 insertions(+), 17 deletions(-) create mode 100644 catalog/expressions.py diff --git a/catalog/expressions.py b/catalog/expressions.py new file mode 100644 index 0000000..62fe43a --- /dev/null +++ b/catalog/expressions.py @@ -0,0 +1,8 @@ +from django.db.models import Func + + +class Substring(Func): + function = 'substring' + arity = 2 + + diff --git a/catalog/models.py b/catalog/models.py index 76e1994..e10b3af 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -13,6 +13,8 @@ from django.utils.translation import ugettext_lazy as _ from unidecode import unidecode +from catalog.expressions import Substring + SLUG_MAX_LENGTH = 50 @@ -226,26 +228,28 @@ def __str__(self): class TagQuerySet(models.QuerySet): - def filter_by_products(self, products: Iterable[AbstractProduct]): - ordering = settings.TAGS_ORDER - distinct = [order.lstrip('-') for order in ordering] + # @todo #273: 60m Create an index for order_by_alphanumeric query. + def order_by_alphanumeric(self): + """Sort the Tag by name's alphabetic chars and then by numeric chars.""" + return self.annotate( + tag_name=Substring(models.F('name'), models.Value('[a-zA-Zа-яА-Я]+')), + tag_value=models.functions.Cast( + Substring(models.F('name'), models.Value('[0-9]+\.?[0-9]*')), + models.FloatField(), + )).order_by('tag_name', 'tag_value') + def filter_by_products(self, products: Iterable[AbstractProduct]): return ( self .filter(products__in=products) - .order_by(*ordering) - .distinct(*distinct, 'id') + .distinct() ) def exclude_by_products(self, products: Iterable[AbstractProduct]): - ordering = settings.TAGS_ORDER - distinct = [order.lstrip('-') for order in ordering] - return ( self .exclude(products__in=products) - .order_by(*ordering) - .distinct(*distinct, 'id') + .distinct() ) def get_group_tags_pairs(self) -> List[Tuple[TagGroup, List['Tag']]]: @@ -378,11 +382,8 @@ def parsed(self, raw: str): class TagManager(models.Manager.from_queryset(TagQuerySet)): - def get_queryset(self): - return ( - super().get_queryset() - .order_by(*settings.TAGS_ORDER) - ) + def order_by_alphanumeric(self): + return self.get_queryset().order_by_alphanumeric() def get_group_tags_pairs(self): return self.get_queryset().get_group_tags_pairs() diff --git a/tests/catalog/test_models.py b/tests/catalog/test_models.py index 9778044..dec62d1 100644 --- a/tests/catalog/test_models.py +++ b/tests/catalog/test_models.py @@ -289,3 +289,19 @@ def test_long_name(self): self.assertLessEqual(len(tag.slug), catalog.models.SLUG_MAX_LENGTH) except DataError as e: self.assertTrue(False, f'Tag has too long name. {e}') + + def test_order_by_alphanumeric(self): + ordered_tags = [ + catalog_models.MockTag(name='a'), + catalog_models.MockTag(name='b'), + catalog_models.MockTag(name='1.2 В'), + catalog_models.MockTag(name='1.6 В'), + catalog_models.MockTag(name='5 В'), + catalog_models.MockTag(name='12 В'), + ] + + # revese just in case + catalog_models.MockTag.objects.bulk_create(ordered_tags[::-1]) + + for i, tag in enumerate(catalog_models.MockTag.objects.order_by_alphanumeric()): + self.assertEqual(tag, ordered_tags[i]) diff --git a/tests/test_settings.py b/tests/test_settings.py index 84e86e5..580140f 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -124,8 +124,6 @@ TAGS_TITLE_DELIMITER = ' или ' TAG_GROUPS_TITLE_DELIMITER = ' и ' -TAGS_ORDER = ['group__position', 'group__name', 'position', 'name'] - # random string to append to doubled slugs SLUG_HASH_SIZE = 5 From a4b374f5b54039bb3268ef38de549de04ebf3acc Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Thu, 21 Feb 2019 01:31:40 +0300 Subject: [PATCH 2/6] Fix todo --- catalog/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/catalog/models.py b/catalog/models.py index e10b3af..789c9a4 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -228,7 +228,7 @@ def __str__(self): class TagQuerySet(models.QuerySet): - # @todo #273: 60m Create an index for order_by_alphanumeric query. + # @todo #273:60m Create an index for order_by_alphanumeric query. def order_by_alphanumeric(self): """Sort the Tag by name's alphabetic chars and then by numeric chars.""" return self.annotate( From ea62464fb6887ee8db55c71b9adad4158c00dc67 Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Thu, 21 Feb 2019 01:32:21 +0300 Subject: [PATCH 3/6] Fix typo --- tests/catalog/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/catalog/test_models.py b/tests/catalog/test_models.py index dec62d1..fa69442 100644 --- a/tests/catalog/test_models.py +++ b/tests/catalog/test_models.py @@ -300,7 +300,7 @@ def test_order_by_alphanumeric(self): catalog_models.MockTag(name='12 В'), ] - # revese just in case + # reverse just in case catalog_models.MockTag.objects.bulk_create(ordered_tags[::-1]) for i, tag in enumerate(catalog_models.MockTag.objects.order_by_alphanumeric()): From 6c57ff0c2dda2aa697186d538466172d59153eb5 Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Thu, 21 Feb 2019 23:47:02 +0300 Subject: [PATCH 4/6] Review fixes --- tests/catalog/test_models.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/catalog/test_models.py b/tests/catalog/test_models.py index fa69442..cb863f4 100644 --- a/tests/catalog/test_models.py +++ b/tests/catalog/test_models.py @@ -1,5 +1,6 @@ """Defines tests for models in Catalog app.""" import unittest +from random import shuffle from django.db import DataError from django.test import TestCase @@ -292,16 +293,14 @@ def test_long_name(self): def test_order_by_alphanumeric(self): ordered_tags = [ - catalog_models.MockTag(name='a'), - catalog_models.MockTag(name='b'), - catalog_models.MockTag(name='1.2 В'), - catalog_models.MockTag(name='1.6 В'), - catalog_models.MockTag(name='5 В'), - catalog_models.MockTag(name='12 В'), + catalog_models.MockTag(name=name), + for name in [ + 'a', 'b', '1 A', '2.1 A', '1.2 В', '1.2 В', '1.6 В', '5 В', '12 В', + ] ] # reverse just in case - catalog_models.MockTag.objects.bulk_create(ordered_tags[::-1]) + catalog_models.MockTag.objects.bulk_create(shuffle(ordered_tags)) for i, tag in enumerate(catalog_models.MockTag.objects.order_by_alphanumeric()): self.assertEqual(tag, ordered_tags[i]) From ac81567b0cce4abad30407124ed5aa39b902518a Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Thu, 21 Feb 2019 23:48:29 +0300 Subject: [PATCH 5/6] Create additional todo to integrate order_by_alphanumeric --- catalog/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/catalog/models.py b/catalog/models.py index 789c9a4..36065b9 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -227,6 +227,7 @@ def __str__(self): class TagQuerySet(models.QuerySet): + # @todo #273:30m Apply new order_by_alphanumeric for SE/STB. # @todo #273:60m Create an index for order_by_alphanumeric query. def order_by_alphanumeric(self): From f4c3c27511b51981d6b2efd8ead4faad65480c26 Mon Sep 17 00:00:00 2001 From: Artemiy Rodionov Date: Fri, 22 Feb 2019 00:01:42 +0300 Subject: [PATCH 6/6] Fix typo --- tests/catalog/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/catalog/test_models.py b/tests/catalog/test_models.py index cb863f4..7fc6104 100644 --- a/tests/catalog/test_models.py +++ b/tests/catalog/test_models.py @@ -293,7 +293,7 @@ def test_long_name(self): def test_order_by_alphanumeric(self): ordered_tags = [ - catalog_models.MockTag(name=name), + catalog_models.MockTag(name=name) for name in [ 'a', 'b', '1 A', '2.1 A', '1.2 В', '1.2 В', '1.6 В', '5 В', '12 В', ]