Skip to content

Commit 2623352

Browse files
Fixed bug in 'clean_value'
1 parent 263fa4a commit 2623352

File tree

2 files changed

+127
-23
lines changed

2 files changed

+127
-23
lines changed

model_clone/tests/test_utils.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from django.test import TestCase
2+
3+
from model_clone.utils import clean_value
4+
5+
6+
class CleanValueTestCase(TestCase):
7+
def test_clean_value_with_single_digit(self):
8+
"""Test cleaning value with single digit suffix."""
9+
result = clean_value("Test Copy 1", "Copy")
10+
self.assertEqual(result, "Test")
11+
12+
def test_clean_value_with_multi_digit(self):
13+
"""Test cleaning value with multi-digit suffix."""
14+
result = clean_value("Test Copy 10", "Copy")
15+
self.assertEqual(result, "Test")
16+
17+
result = clean_value("Test Copy 123", "Copy")
18+
self.assertEqual(result, "Test")
19+
20+
def test_clean_value_with_hyphen_separator(self):
21+
"""Test cleaning value with hyphen separator."""
22+
result = clean_value("test-copy-1", "copy")
23+
self.assertEqual(result, "test")
24+
25+
result = clean_value("test-copy-42", "copy")
26+
self.assertEqual(result, "test")
27+
28+
def test_clean_value_case_insensitive(self):
29+
"""Test that cleaning is case insensitive."""
30+
result = clean_value("Test COPY 1", "copy")
31+
self.assertEqual(result, "Test")
32+
33+
result = clean_value("test copy 1", "COPY")
34+
self.assertEqual(result, "test")
35+
36+
def test_clean_value_with_regex_special_characters(self):
37+
"""Test cleaning value when suffix contains regex special characters."""
38+
result = clean_value("Test (Copy) 1", "(Copy)")
39+
self.assertEqual(result, "Test")
40+
41+
result = clean_value("Test Copy+ 2", "Copy+")
42+
self.assertEqual(result, "Test")
43+
44+
result = clean_value("Test Copy* 5", "Copy*")
45+
self.assertEqual(result, "Test")
46+
47+
result = clean_value("Test Copy? 3", "Copy?")
48+
self.assertEqual(result, "Test")
49+
50+
result = clean_value("Test Copy[1] 4", "Copy[1]")
51+
self.assertEqual(result, "Test")
52+
53+
def test_clean_value_with_dots_and_brackets(self):
54+
"""Test cleaning with complex regex characters."""
55+
result = clean_value("Test Copy.exe 1", "Copy.exe")
56+
self.assertEqual(result, "Test")
57+
58+
result = clean_value("Test (v2.0) 15", "(v2.0)")
59+
self.assertEqual(result, "Test")
60+
61+
def test_clean_value_no_match(self):
62+
"""Test that value is unchanged when pattern doesn't match."""
63+
result = clean_value("Test Copy", "Copy")
64+
self.assertEqual(result, "Test Copy")
65+
66+
result = clean_value("Test Different 1", "Copy")
67+
self.assertEqual(result, "Test Different 1")
68+
69+
result = clean_value("Test Copy A", "Copy")
70+
self.assertEqual(result, "Test Copy A")
71+
72+
def test_clean_value_partial_match(self):
73+
"""Test that partial matches are not cleaned."""
74+
result = clean_value("Test Copying 1", "Copy")
75+
self.assertEqual(result, "Test Copying 1")
76+
77+
result = clean_value("Test Copy Something 1", "Copy")
78+
self.assertEqual(result, "Test Copy Something 1")
79+
80+
def test_clean_value_middle_of_string(self):
81+
"""Test that pattern in middle of string is not cleaned."""
82+
result = clean_value("Test Copy 1 More", "Copy")
83+
self.assertEqual(result, "Test Copy 1 More")
84+
85+
def test_clean_value_with_empty_suffix(self):
86+
"""Test behavior with empty suffix."""
87+
result = clean_value("Test 1", "")
88+
self.assertEqual(result, "Test")
89+
90+
def test_clean_value_with_space_before_suffix(self):
91+
"""Test cleaning when there's a space before suffix."""
92+
result = clean_value("Test Copy 25", "Copy")
93+
self.assertEqual(result, "Test")
94+
95+
def test_clean_value_with_hyphen_before_suffix(self):
96+
"""Test cleaning when there's a hyphen before suffix."""
97+
result = clean_value("test-copy-99", "copy")
98+
self.assertEqual(result, "test")
99+
100+
def test_clean_value_zero_digit(self):
101+
"""Test cleaning with zero as digit."""
102+
result = clean_value("Test Copy 0", "Copy")
103+
self.assertEqual(result, "Test")
104+
105+
def test_clean_value_leading_zeros(self):
106+
"""Test cleaning with leading zeros in digits."""
107+
result = clean_value("Test Copy 001", "Copy")
108+
self.assertEqual(result, "Test")
109+
110+
result = clean_value("Test Copy 010", "Copy")
111+
self.assertEqual(result, "Test")
112+
113+
def test_clean_value_complex_example(self):
114+
"""Test with a complex real-world example."""
115+
result = clean_value("my-awesome-slug-copy-42", "copy")
116+
self.assertEqual(result, "my-awesome-slug")
117+
118+
result = clean_value("Product (v1.0) Copy 123", "(v1.0) Copy")
119+
self.assertEqual(result, "Product")

model_clone/utils.py

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
from django.db.transaction import TransactionManagementError
77

88

9-
def create_copy_of_instance(
10-
instance, attrs=None, exclude=(), save_new=True, using=None
11-
):
9+
def create_copy_of_instance(instance, attrs=None, exclude=(), save_new=True, using=None):
1210
"""
1311
Clone an instance of `django.db.models.Model`.
1412
@@ -113,9 +111,7 @@ def unpack_unique_constraints(opts, only_fields=()):
113111
:return: Flat list of fields.
114112
"""
115113
fields = []
116-
constraints = getattr(
117-
opts, "total_unique_constraints", getattr(opts, "constraints", [])
118-
)
114+
constraints = getattr(opts, "total_unique_constraints", getattr(opts, "constraints", []))
119115
for constraint in constraints:
120116
fields.extend([f for f in constraint.fields if f in only_fields])
121117
return fields
@@ -149,7 +145,8 @@ def clean_value(value, suffix):
149145
:rtype: `str`
150146
"""
151147
# type: (str, str) -> str
152-
return re.sub(r"([\s-]?){}[\s-][\d]$".format(suffix), "", value, flags=re.I)
148+
escaped_suffix = re.escape(suffix)
149+
return re.sub(r"([\s-]?){}[\s-]\d+$".format(escaped_suffix), "", value, flags=re.I)
153150

154151

155152
@contextlib.contextmanager
@@ -243,9 +240,7 @@ def generate_value(value, suffix, transform, max_length, max_attempts):
243240
yield get_value(value, suffix, transform, max_length, i)
244241

245242
raise StopIteration(
246-
"CloneError: max unique attempts for {} exceeded ({})".format(
247-
value, max_attempts
248-
)
243+
"CloneError: max unique attempts for {} exceeded ({})".format(value, max_attempts)
249244
)
250245

251246

@@ -298,19 +293,11 @@ def get_fields_and_unique_fields_from_cls(
298293
if not getattr(f, "primary_key", False):
299294
if clone_fields and not force and not getattr(f, "one_to_one", False):
300295
valid = f.name in clone_fields
301-
elif (
302-
clone_excluded_fields
303-
and not force
304-
and not getattr(f, "one_to_one", False)
305-
):
296+
elif clone_excluded_fields and not force and not getattr(f, "one_to_one", False):
306297
valid = f.name not in clone_excluded_fields
307298
elif clone_o2o_fields and not force and getattr(f, "one_to_one", False):
308299
valid = f.name in clone_o2o_fields
309-
elif (
310-
clone_excluded_o2o_fields
311-
and not force
312-
and getattr(f, "one_to_one", False)
313-
):
300+
elif clone_excluded_o2o_fields and not force and getattr(f, "one_to_one", False):
314301
valid = f.name not in clone_excluded_o2o_fields # pragma: no cover
315302
else:
316303
valid = True
@@ -333,9 +320,7 @@ def get_fields_and_unique_fields_from_cls(
333320
for f in fields
334321
if not f.auto_created
335322
and (
336-
f.unique
337-
or f.name in unique_field_names
338-
or f.name in unique_constraint_field_names
323+
f.unique or f.name in unique_field_names or f.name in unique_constraint_field_names
339324
)
340325
]
341326

0 commit comments

Comments
 (0)