Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#273 Alphanumeric ordering #274

Merged
merged 6 commits into from
Feb 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions catalog/expressions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.db.models import Func


class Substring(Func):
function = 'substring'
arity = 2


32 changes: 17 additions & 15 deletions catalog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -225,27 +227,30 @@ 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):
"""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]):
ordering = settings.TAGS_ORDER
distinct = [order.lstrip('-') for order in ordering]

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']]]:
Expand Down Expand Up @@ -378,11 +383,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()
Expand Down
15 changes: 15 additions & 0 deletions tests/catalog/test_models.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -289,3 +290,17 @@ 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=name)
for name in [
'a', 'b', '1 A', '2.1 A', '1.2 В', '1.2 В', '1.6 В', '5 В', '12 В',
]
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be good to add numeric values with another dimension: ['1.2 A', '6 A'] for example


# reverse just in case
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])
2 changes: 0 additions & 2 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down