From d659d426a819b888c4e0b465dfc6a65218347447 Mon Sep 17 00:00:00 2001 From: duker33 Date: Wed, 6 Mar 2019 22:04:28 +0300 Subject: [PATCH] #302 Slugify tags with theirs groups (#304) * #302 Pdd task for more good processing request symbols for slugs * #302 Add group part to tag slugs * #302 Fix tests * #302 Review#1 minor fixes * #302 Fix test --- catalog/models.py | 32 +++++++++++++++++++++++++++++--- tests/catalog/test_models.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/catalog/models.py b/catalog/models.py index 9b59438..f885c47 100644 --- a/catalog/models.py +++ b/catalog/models.py @@ -210,6 +210,9 @@ def get_siblings(self, offset): # is not arch design, but about ORM hack. class TagGroup(models.Model): + SLUG_HASH_SIZE = 5 + SLUG_MAX_LENGTH = 25 + class Meta: abstract = True @@ -220,6 +223,28 @@ class Meta: default=0, blank=True, db_index=True, verbose_name=_('position'), ) + # @todo #302:30m Resolve slug code doubling. + # See `TagGroup.slug` and `Tag._get_slug`. + @property + def slug(self) -> str: + """Make a slug from the name.""" + # Translate all punctuation chars to "_". + # It doesn't conflict with `slugify`, which translate spaces to "-" + # and punctuation chars to "". + slug = slugify(unidecode(self.name.translate( + {ord(p): '_' for p in string.punctuation} + ))) + + # Keep the slug length less then SLUG_MAX_LENGTH + if len(slug) < self.SLUG_MAX_LENGTH: + return slug + + slug_length = self.SLUG_MAX_LENGTH - self.SLUG_HASH_SIZE - 1 + return randomize_slug( + slug[:slug_length], + hash_size=self.SLUG_HASH_SIZE + ) + def __str__(self): return self.name @@ -401,8 +426,7 @@ class Meta: def __str__(self): return self.name - def _slugify(self) -> None: - """Make a slug from the name.""" + def _get_slug(self) -> str: # Translate all punctuation chars to "_". # It doesn't conflict with `slugify`, which translate spaces to "-" # and punctuation chars to "". @@ -410,6 +434,8 @@ def _slugify(self) -> None: {ord(p): '_' for p in string.punctuation} ))) + slug = '__'.join([self.group.slug, slug]) + # Keep the slug length less then SLUG_MAX_LENGTH if len(slug) < self.SLUG_MAX_LENGTH: return slug @@ -422,7 +448,7 @@ def _slugify(self) -> None: def save(self, *args, **kwargs): if not self.slug: - self.slug = self._slugify() + self.slug = self._get_slug() super(Tag, self).save(*args, **kwargs) # @todo #168:15m Move `Tags.parse_url_tags` Tags context. diff --git a/tests/catalog/test_models.py b/tests/catalog/test_models.py index 5a9b559..a18342a 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 string +import unittest from django.db import DataError from django.test import TestCase @@ -249,14 +250,15 @@ class Tag(TestCase): def test_tag_doubled_save_slug_postfix(self): """Tag should preserve it's slug value after several saves.""" + slug = '12-v' group = catalog_models.MockTagGroup.objects.create(name='Напряжение вход') tag = catalog_models.MockTag.objects.create( name='12 В', group=group ) - self.assertEqual(tag.slug, '12-v') + self.assertEqual(tag.slug[-len(slug):], slug) tag.save() - self.assertEqual(tag.slug, '12-v') + self.assertEqual(tag.slug[-len(slug):], slug) def test_long_name(self): """ @@ -266,7 +268,7 @@ def test_long_name(self): It may create problems for tag with long name. """ name = 'Имя ' * 50 - group = catalog_models.MockTagGroup.objects.first() + group = catalog_models.MockTagGroup.objects.create(name='Some group') try: tag = catalog_models.MockTag.objects.create(group=group, name=name) self.assertLessEqual(len(tag.slug), catalog.models.Tag.SLUG_MAX_LENGTH) @@ -274,13 +276,37 @@ def test_long_name(self): self.assertTrue(False, f'Tag has too long name. {e}') def test_slugify_conflicts(self): + group = catalog_models.MockTagGroup.objects.create(name='Some group') slugs = [ - catalog_models.MockTag.objects.create(name=name).slug + catalog_models.MockTag.objects.create(group=group, name=name).slug for name in ['11 A', '1/1 A', '1 1 A'] ] self.assertEqual(len(slugs), len(set(slugs)), msg=slugs) + # @todo #302:30m Process more special symbols for slugs. + @unittest.expectedFailure + def test_slug_special_symbols(self): + slugs = [ + catalog_models.MockTag.objects.create(name=name).slug + for name in ['11 A', '1/1 A', '1 1 A', '1.1 A', '1-1 A', '1_1 A'] + ] + + self.assertEqual(len(slugs), len(set(slugs)), msg=slugs) + + def test_slugs_for_cloned_tag_values(self): + groups = [ + catalog_models.MockTagGroup.objects.create(name=name) + for name in ['Length', 'Width', 'Height'] + ] + values = ['11 A']*3 + slugs = [ + catalog_models.MockTag.objects.create(group=group, name=value).slug + for group, value in zip(groups, values) + ] + + self.assertEqual(len(slugs), len(set(slugs)), msg=slugs) + def test_group_tags(self): groups = [ catalog_models.MockTagGroup.objects.create(name=name)