14
14
regex_key_validator = RegexValidator (regex = r'^[a-z][-a-z0-9_:]*\Z' ,
15
15
flags = re .IGNORECASE , code = 'invalid' )
16
16
17
+ default_excluded_keys = [
18
+ "src" , "href" , "data" , "action" , "on*" ,
19
+ ]
17
20
18
21
class AttributesFormField (forms .CharField ):
19
22
empty_values = [None , '' ]
20
23
21
24
def __init__ (self , * args , ** kwargs ):
22
25
kwargs .setdefault ('widget' , AttributesWidget )
26
+ self .excluded_keys = kwargs .pop ('excluded_keys' , []) + default_excluded_keys
23
27
super ().__init__ (* args , ** kwargs )
24
28
25
29
def to_python (self , value ):
@@ -37,6 +41,49 @@ def validate(self, value):
37
41
# This is required in older django versions.
38
42
if value in self .empty_values and self .required :
39
43
raise forms .ValidationError (self .error_messages ['required' ], code = 'required' )
44
+ if isinstance (value , dict ):
45
+ for key , val in value .items ():
46
+ self .validate_key (key )
47
+ self .validate_value (key , val )
48
+
49
+ def validate_key (self , key ):
50
+ """
51
+ A key must start with a letter, but can otherwise contain letters,
52
+ numbers, dashes, colons or underscores. It must not also be part of
53
+ `excluded_keys` as configured in the field.
54
+
55
+ :param key: (str) The key to validate
56
+ """
57
+ # Verify the key is not one of `excluded_keys`.
58
+ for excluded_key in self .excluded_keys :
59
+ if key .lower () == excluded_key or excluded_key .endswith ("*" ) and key .lower ().startswith (excluded_key [:- 1 ]):
60
+ raise ValidationError (
61
+ _ ('"{key}" is excluded by configuration and cannot be used as '
62
+ 'a key.' ).format (key = key ))
63
+ # Also check that it fits our permitted syntax
64
+ try :
65
+ regex_key_validator (key )
66
+ except ValidationError :
67
+ # Seems silly to catch one then raise another ValidationError, but
68
+ # the RegExValidator doesn't use placeholders in its error message.
69
+ raise ValidationError (
70
+ _ ('"{key}" is not a valid key. Keys must start with at least '
71
+ 'one letter and consist only of the letters, numbers, '
72
+ 'underscores or hyphens.' ).format (key = key ))
73
+
74
+ def validate_value (self , key , value ):
75
+ """
76
+ A value can be anything that can be JSON-ified.
77
+
78
+ :param key: (str) The key of the value
79
+ :param value: (str) The value to validate
80
+ """
81
+ try :
82
+ json .dumps (value )
83
+ except (TypeError , ValueError ):
84
+ raise ValidationError (
85
+ _ ('The value for the key "{key}" is invalid. Please enter a '
86
+ 'value that can be represented in JSON.' ).format (key = key ))
40
87
41
88
42
89
class AttributesField (models .Field ):
@@ -64,7 +111,7 @@ def __init__(self, *args, **kwargs):
64
111
kwargs ['default' ] = kwargs .get ('default' , dict )
65
112
excluded_keys = kwargs .pop ('excluded_keys' , [])
66
113
# Note we accept uppercase letters in the param, but the comparison
67
- # is not case sensitive. So, we coerce the input to lowercase here.
114
+ # is not case- sensitive. So, we coerce the input to lowercase here.
68
115
self .excluded_keys = [key .lower () for key in excluded_keys ]
69
116
super ().__init__ (* args , ** kwargs )
70
117
self .validate (self .get_default (), None )
@@ -75,6 +122,7 @@ def formfield(self, **kwargs):
75
122
'widget' : AttributesWidget
76
123
}
77
124
defaults .update (** kwargs )
125
+ defaults ["excluded_keys" ] = self .excluded_keys
78
126
return super ().formfield (** defaults )
79
127
80
128
def from_db_value (self , value ,
@@ -134,48 +182,6 @@ def validate(self, value, model_instance):
134
182
except ValueError :
135
183
raise ValidationError (self .error_messages ['invalid' ] % value )
136
184
137
- for key , val in value .items ():
138
- self .validate_key (key )
139
- self .validate_value (key , val )
140
-
141
- def validate_key (self , key ):
142
- """
143
- A key must start with a letter, but can otherwise contain letters,
144
- numbers, dashes, colons or underscores. It must not also be part of
145
- `excluded_keys` as configured in the field.
146
-
147
- :param key: (str) The key to validate
148
- """
149
- # Verify the key is not one of `excluded_keys`.
150
- if key .lower () in self .excluded_keys :
151
- raise ValidationError (
152
- _ ('"{key}" is excluded by configuration and cannot be used as '
153
- 'a key.' ).format (key = key ))
154
- # Also check that it fits our permitted syntax
155
- try :
156
- regex_key_validator (key )
157
- except ValidationError :
158
- # Seems silly to catch one then raise another ValidationError, but
159
- # the RegExValidator doesn't use placeholders in its error message.
160
- raise ValidationError (
161
- _ ('"{key}" is not a valid key. Keys must start with at least '
162
- 'one letter and consist only of the letters, numbers, '
163
- 'underscores or hyphens.' ).format (key = key ))
164
-
165
- def validate_value (self , key , value ):
166
- """
167
- A value can be anything that can be JSON-ified.
168
-
169
- :param key: (str) The key of the value
170
- :param value: (str) The value to validate
171
- """
172
- try :
173
- json .dumps (value )
174
- except (TypeError , ValueError ):
175
- raise ValidationError (
176
- _ ('The value for the key "{key}" is invalid. Please enter a '
177
- 'value that can be represented in JSON.' ).format (key = key ))
178
-
179
185
def value_to_string (self , obj ):
180
186
return self .value_from_object (obj )
181
187
0 commit comments