Skip to content

Commit cdfe87a

Browse files
committed
validate slugs on model save
- prevent generation of tag with non-unique slug - prevent generation of tag with name containing only invalid characters
1 parent e817126 commit cdfe87a

File tree

4 files changed

+57
-13
lines changed

4 files changed

+57
-13
lines changed

project/resources/migrations/0006_auto_20200303_1257.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
88

99
dependencies = [
1010
('resources', '0005_auto_20200118_1036'),
11-
('tagging', '__latest__')
11+
('tagging', '0001_initial')
1212
]
1313

1414
operations = [
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 2.2.4 on 2020-04-05 10:12
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('tagging', '0001_initial'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='customtag',
15+
name='slug',
16+
field=models.SlugField(allow_unicode=True, max_length=100, unique=True, verbose_name='Slug'),
17+
),
18+
]

project/tagging/models.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
class CustomTag(TagBase):
1010
guid = models.UUIDField(default=uuid.uuid1, editable=False)
11-
slug = models.SlugField(verbose_name=_("Slug"), unique=True, max_length=100)
11+
slug = models.SlugField(
12+
verbose_name=_("Slug"),
13+
unique=True,
14+
max_length=100,
15+
allow_unicode=True
16+
)
1217
name = models.CharField(verbose_name=_("Name"), unique=True, max_length=100)
1318

1419
class Meta:
@@ -17,15 +22,13 @@ class Meta:
1722
verbose_name_plural = _("Tags")
1823
app_label = 'tagging'
1924

25+
def save(self, *args, **kwargs):
26+
self.slug = self.slugify(self.name)
27+
self.full_clean()
28+
return super().save(*args, **kwargs)
2029

2130
def slugify(self, tag, i=None):
22-
slug = slugify(tag, allow_unicode=True)
23-
24-
if i is not None:
25-
slug += "_%d" % i
26-
27-
return slug
28-
31+
return slugify(tag, allow_unicode=True)
2932

3033
class TaggedItems(GenericTaggedItemBase, TaggedItemBase):
3134
tag = models.ForeignKey(CustomTag,

project/tagging/tests.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
from django.core.exceptions import ValidationError
12
from django.test import TestCase
23
from .models import CustomTag
34

45

56
class CustomTagTests(TestCase):
6-
def test_slugging(self):
7+
def test_valid_slugs(self):
78
test_tags = [
89
{"name": "programming", "expected_slug": "programming"},
910
{"name": "PyCon", "expected_slug": "pycon"},
@@ -33,12 +34,34 @@ def test_slugging(self):
3334
{"name": "प्रयास है", "expected_slug": "परयस-ह"},
3435
{"name": "stòran-dàta", "expected_slug": "stòran-dàta"},
3536
{"name": "స్వయంచాలక", "expected_slug": "సవయచలక"},
36-
37-
{"name": "❤", "expected_slug": ""},
38-
{"name": "🐸", "expected_slug": "_1"},
3937
]
4038

4139
for entry in test_tags:
4240
tag = CustomTag(name=entry["name"])
4341
tag.save()
4442
self.assertEqual(tag.slug, entry["expected_slug"])
43+
44+
def test_invalid_slugs(self):
45+
invalid_tag_names = [
46+
"❤🐸",
47+
"🐸",
48+
" %",
49+
"//",
50+
]
51+
for name in invalid_tag_names:
52+
with self.assertRaises(ValidationError):
53+
tag = CustomTag(name=name)
54+
tag.save()
55+
56+
def test_duplicates(self):
57+
tag1 = CustomTag(name='javascript')
58+
tag1.save()
59+
60+
# fail if we try to generate more tags with the same slug
61+
with self.assertRaises(ValidationError):
62+
tag2 = CustomTag(name='Javascript')
63+
tag2.save()
64+
65+
with self.assertRaises(ValidationError):
66+
tag3 = CustomTag(name='JaVascripT%/')
67+
tag3.save()

0 commit comments

Comments
 (0)