From 653ed7fd38e65d2c1c52bfd365ec6d3a74dd4bb1 Mon Sep 17 00:00:00 2001 From: Mike <22396211+Dresdn@users.noreply.github.com> Date: Fri, 12 Aug 2022 14:23:41 -0700 Subject: [PATCH] refactor: move generate_slug to logic --- eav/fields.py | 15 --------------- eav/logic/slug.py | 22 ++++++++++++++++++++++ eav/models.py | 15 ++++++++++----- tests/test_logic.py | 20 ++++++++++++++++++++ 4 files changed, 52 insertions(+), 20 deletions(-) create mode 100644 eav/logic/slug.py create mode 100644 tests/test_logic.py diff --git a/eav/fields.py b/eav/fields.py index 24a8ac3..ee1c168 100644 --- a/eav/fields.py +++ b/eav/fields.py @@ -1,27 +1,12 @@ import re -import secrets -import string from django.core.exceptions import ValidationError from django.db import models -from django.utils.text import slugify from django.utils.translation import gettext_lazy as _ from eav.forms import CSVFormField -def generate_slug(name: str) -> str: - """Generates a valid slug based on ``name``.""" - slug = slugify(name, allow_unicode=False) - - if not slug: - # Fallback to ensure a slug is always generated by using a random one - chars = string.ascii_lowercase + string.digits - randstr = ''.join(secrets.choice(chars) for _ in range(8)) - slug = 'rand-{0}'.format(randstr) - return slug.encode('utf-8', 'surrogateescape').decode() - - class EavSlugField(models.SlugField): """ The slug field used by :class:`~eav.models.Attribute` diff --git a/eav/logic/slug.py b/eav/logic/slug.py new file mode 100644 index 0000000..4984566 --- /dev/null +++ b/eav/logic/slug.py @@ -0,0 +1,22 @@ +import secrets +import string +from typing import Final + +from django.utils.text import slugify + +SLUGFIELD_MAX_LENGTH: Final = 50 + + +def generate_slug(name: str) -> str: + """Generates a valid slug based on ``name``.""" + slug = slugify(name, allow_unicode=False) + + if not slug: + # Fallback to ensure a slug is always generated by using a random one + chars = string.ascii_lowercase + string.digits + randstr = ''.join(secrets.choice(chars) for _ in range(8)) + slug = 'rand-{0}'.format(randstr) + + slug = slug.encode('utf-8', 'surrogateescape').decode() + + return slug[:SLUGFIELD_MAX_LENGTH] diff --git a/eav/models.py b/eav/models.py index 1bced00..55a28bd 100644 --- a/eav/models.py +++ b/eav/models.py @@ -10,6 +10,7 @@ optional metaclass for each eav model class. """ from copy import copy +from typing import Final from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes.models import ContentType @@ -22,8 +23,9 @@ from django.utils.translation import gettext_lazy as _ from eav import register from eav.exceptions import IllegalAssignmentException -from eav.fields import CSVField, EavDatatypeField, EavSlugField, generate_slug +from eav.fields import CSVField, EavDatatypeField from eav.logic.entity_pk import get_entity_pk_type +from eav.logic.slug import SLUGFIELD_MAX_LENGTH, generate_slug from eav.validators import ( validate_bool, validate_csv, @@ -42,6 +44,9 @@ except ImportError: from django_jsonfield_backport.models import JSONField +CHARFIELD_LENGTH: Final = 100 + + class EnumValue(models.Model): """ *EnumValue* objects are the value 'choices' to multiple choice *TYPE_ENUM* @@ -73,7 +78,7 @@ class EnumValue(models.Model): _('Value'), db_index=True, unique=True, - max_length=50, + max_length=SLUGFIELD_MAX_LENGTH, ) def __str__(self): @@ -94,7 +99,7 @@ class EnumGroup(models.Model): See :class:`EnumValue` for an example. """ - name = models.CharField(_('Name'), unique=True, max_length=100) + name = models.CharField(_('Name'), unique=True, max_length=CHARFIELD_LENGTH) values = models.ManyToManyField(EnumValue, verbose_name=_('Enum group')) def __str__(self): @@ -191,7 +196,7 @@ class Attribute(models.Model): name = models.CharField( verbose_name=_('Name'), - max_length=100, + max_length=CHARFIELD_LENGTH, help_text=_('User-friendly attribute name'), ) @@ -202,7 +207,7 @@ class Attribute(models.Model): """ slug = models.SlugField( verbose_name=_('Slug'), - max_length=50, + max_length=SLUGFIELD_MAX_LENGTH, db_index=True, unique=True, help_text=_('Short unique attribute label'), diff --git a/tests/test_logic.py b/tests/test_logic.py new file mode 100644 index 0000000..6169a66 --- /dev/null +++ b/tests/test_logic.py @@ -0,0 +1,20 @@ +from hypothesis import given +from hypothesis import strategies as st + +from eav.logic.slug import SLUGFIELD_MAX_LENGTH, generate_slug + + +@given(st.text()) +def test_generate_slug(name: str) -> None: + """Ensures slug generation works properly.""" + slug = generate_slug(name) + + assert slug + + +@given(st.text(min_size=SLUGFIELD_MAX_LENGTH)) +def test_generate_long_slug_text(name: str) -> None: + """Ensures a slug isn't generated longer than maximum allowed length.""" + slug = generate_slug(name) + + assert len(slug) <= SLUGFIELD_MAX_LENGTH