mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-05-28 16:58:24 +00:00
refactor: move generate_slug to logic
This commit is contained in:
parent
f6afc45613
commit
653ed7fd38
4 changed files with 52 additions and 20 deletions
|
|
@ -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
22
eav/logic/slug.py
Normal 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]
|
||||||
|
|
@ -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
20
tests/test_logic.py
Normal 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
|
||||||
Loading…
Reference in a new issue