1414regex_key_validator = RegexValidator (regex = r'^[a-z][-a-z0-9_:]*\Z' ,
1515 flags = re .IGNORECASE , code = 'invalid' )
1616
17+ default_excluded_keys = [
18+ "src" , "href" , "data" , "action" , "on*" ,
19+ ]
1720
1821class AttributesFormField (forms .CharField ):
1922 empty_values = [None , '' ]
2023
2124 def __init__ (self , * args , ** kwargs ):
2225 kwargs .setdefault ('widget' , AttributesWidget )
26+ self .excluded_keys = kwargs .pop ('excluded_keys' , []) + default_excluded_keys
2327 super ().__init__ (* args , ** kwargs )
2428
2529 def to_python (self , value ):
@@ -37,6 +41,49 @@ def validate(self, value):
3741 # This is required in older django versions.
3842 if value in self .empty_values and self .required :
3943 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 ))
4087
4188
4289class AttributesField (models .Field ):
@@ -64,7 +111,7 @@ def __init__(self, *args, **kwargs):
64111 kwargs ['default' ] = kwargs .get ('default' , dict )
65112 excluded_keys = kwargs .pop ('excluded_keys' , [])
66113 # 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.
68115 self .excluded_keys = [key .lower () for key in excluded_keys ]
69116 super ().__init__ (* args , ** kwargs )
70117 self .validate (self .get_default (), None )
@@ -75,6 +122,7 @@ def formfield(self, **kwargs):
75122 'widget' : AttributesWidget
76123 }
77124 defaults .update (** kwargs )
125+ defaults ["excluded_keys" ] = self .excluded_keys
78126 return super ().formfield (** defaults )
79127
80128 def from_db_value (self , value ,
@@ -134,48 +182,6 @@ def validate(self, value, model_instance):
134182 except ValueError :
135183 raise ValidationError (self .error_messages ['invalid' ] % value )
136184
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-
179185 def value_to_string (self , obj ):
180186 return self .value_from_object (obj )
181187
0 commit comments