refactor: move generate_slug to logic

This commit is contained in:
Mike 2022-08-12 14:23:41 -07:00
parent f6afc45613
commit 653ed7fd38
4 changed files with 52 additions and 20 deletions

View file

@ -1,27 +1,12 @@
import re import re
import secrets
import string
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from eav.forms import CSVFormField 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): class EavSlugField(models.SlugField):
""" """
The slug field used by :class:`~eav.models.Attribute` The slug field used by :class:`~eav.models.Attribute`

22
eav/logic/slug.py Normal file
View file

@ -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]

View file

@ -10,6 +10,7 @@ optional metaclass for each eav model class.
""" """
from copy import copy from copy import copy
from typing import Final
from django.contrib.contenttypes import fields as generic from django.contrib.contenttypes import fields as generic
from django.contrib.contenttypes.models import ContentType 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 import register
from eav.exceptions import IllegalAssignmentException 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.entity_pk import get_entity_pk_type
from eav.logic.slug import SLUGFIELD_MAX_LENGTH, generate_slug
from eav.validators import ( from eav.validators import (
validate_bool, validate_bool,
validate_csv, validate_csv,
@ -42,6 +44,9 @@ except ImportError:
from django_jsonfield_backport.models import JSONField from django_jsonfield_backport.models import JSONField
CHARFIELD_LENGTH: Final = 100
class EnumValue(models.Model): class EnumValue(models.Model):
""" """
*EnumValue* objects are the value 'choices' to multiple choice *TYPE_ENUM* *EnumValue* objects are the value 'choices' to multiple choice *TYPE_ENUM*
@ -73,7 +78,7 @@ class EnumValue(models.Model):
_('Value'), _('Value'),
db_index=True, db_index=True,
unique=True, unique=True,
max_length=50, max_length=SLUGFIELD_MAX_LENGTH,
) )
def __str__(self): def __str__(self):
@ -94,7 +99,7 @@ class EnumGroup(models.Model):
See :class:`EnumValue` for an example. 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')) values = models.ManyToManyField(EnumValue, verbose_name=_('Enum group'))
def __str__(self): def __str__(self):
@ -191,7 +196,7 @@ class Attribute(models.Model):
name = models.CharField( name = models.CharField(
verbose_name=_('Name'), verbose_name=_('Name'),
max_length=100, max_length=CHARFIELD_LENGTH,
help_text=_('User-friendly attribute name'), help_text=_('User-friendly attribute name'),
) )
@ -202,7 +207,7 @@ class Attribute(models.Model):
""" """
slug = models.SlugField( slug = models.SlugField(
verbose_name=_('Slug'), verbose_name=_('Slug'),
max_length=50, max_length=SLUGFIELD_MAX_LENGTH,
db_index=True, db_index=True,
unique=True, unique=True,
help_text=_('Short unique attribute label'), help_text=_('Short unique attribute label'),

20
tests/test_logic.py Normal file
View file

@ -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