Skip to content

Commit bad0d33

Browse files
authored
Merge pull request #30 from maykinmedia/feature-validate-slug-fields
Add slug field validation
2 parents e2f4b2f + 9c38ec3 commit bad0d33

File tree

3 files changed

+60
-1
lines changed

3 files changed

+60
-1
lines changed

django_setup_configuration/fields.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.db import models
88
from django.db.models.fields import NOT_PROVIDED, Field
99

10+
from pydantic import constr
1011
from pydantic.fields import FieldInfo
1112

1213

@@ -47,6 +48,9 @@ class UNMAPPED_DJANGO_FIELD:
4748
pass
4849

4950

51+
_SLUG_RE = r"^[-a-zA-Z0-9_]+\z"
52+
53+
5054
class DjangoModelRefInfo(FieldInfo):
5155
"""
5256
A FieldInfo representing a reference to a field on a Django model.
@@ -153,8 +157,8 @@ def _get_python_type(
153157
models.TextField: str,
154158
models.EmailField: str,
155159
models.URLField: str,
156-
models.SlugField: str,
157160
models.UUIDField: str,
161+
models.SlugField: constr(pattern=_SLUG_RE),
158162
# Integer-based fields
159163
models.AutoField: int,
160164
models.SmallAutoField: int,

testapp/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class TestModel(models.Model):
1515
nullable_str = models.CharField(null=True, blank=False, max_length=1)
1616
nullable_and_blank_str = models.CharField(null=True, blank=False, max_length=1)
1717
blank_str = models.CharField(null=False, blank=True, max_length=1)
18+
slug = models.SlugField()
1819

1920
field_with_help_text = models.IntegerField(help_text="This is the help text")
2021
field_with_verbose_name = models.IntegerField(verbose_name="The Verbose Name")

tests/test_django_model_ref_field.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from typing import Literal
22

3+
from django.core.exceptions import ValidationError as DjangoValidationError
4+
from django.core.validators import validate_slug
5+
36
import pytest
7+
from pydantic import ValidationError
48
from pydantic.fields import PydanticUndefined
59

610
from django_setup_configuration.fields import DjangoModelRef
@@ -51,6 +55,56 @@ class Config(ConfigurationModel):
5155
assert field.is_required() is True
5256

5357

58+
@pytest.mark.parametrize(
59+
"invalid_values",
60+
(
61+
"",
62+
"hello world",
63+
64+
"$price",
65+
"my.variable",
66+
"résumé",
67+
"hello!",
68+
"!",
69+
"#",
70+
"+",
71+
),
72+
)
73+
def test_slug_validation_fails_on_both_pydantic_and_django(invalid_values):
74+
75+
class Config(ConfigurationModel):
76+
slug = DjangoModelRef(TestModel, "slug")
77+
78+
with pytest.raises(ValidationError):
79+
Config(slug=invalid_values)
80+
81+
with pytest.raises(DjangoValidationError):
82+
validate_slug(invalid_values)
83+
84+
85+
@pytest.mark.parametrize(
86+
"valid_values",
87+
(
88+
"a",
89+
"foo-bar",
90+
"foo_bar",
91+
"foo_bar_baz",
92+
"foo-bar-baz",
93+
"fO0-B4r-Baz",
94+
"foo-bar-baz",
95+
"foobarbaz",
96+
"FooBarBaz",
97+
),
98+
)
99+
def test_slug_validation_succeeds_on_both_pydantic_and_django(valid_values):
100+
101+
class Config(ConfigurationModel):
102+
slug = DjangoModelRef(TestModel, "slug")
103+
104+
Config.model_validate(dict(slug=valid_values))
105+
validate_slug(valid_values) # does not raise
106+
107+
54108
def test_no_default_makes_field_required():
55109

56110
class Config(ConfigurationModel):

0 commit comments

Comments
 (0)