mirror of
https://github.com/jazzband/django-eav2.git
synced 2026-03-16 22:40:26 +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 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`
|
||||
|
|
|
|||
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 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'),
|
||||
|
|
|
|||
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