##// END OF EJS Templates
email: add whitespace stripping during registration of users on email addresses.
ergo -
r2385:2808e7be default
parent child Browse files
Show More
@@ -1,614 +1,617 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 this is forms validation classes
22 this is forms validation classes
23 http://formencode.org/module-formencode.validators.html
23 http://formencode.org/module-formencode.validators.html
24 for list off all availible validators
24 for list off all availible validators
25
25
26 we can create our own validators
26 we can create our own validators
27
27
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 pre_validators [] These validators will be applied before the schema
29 pre_validators [] These validators will be applied before the schema
30 chained_validators [] These validators will be applied after the schema
30 chained_validators [] These validators will be applied after the schema
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35
35
36
36
37 <name> = formencode.validators.<name of validator>
37 <name> = formencode.validators.<name of validator>
38 <name> must equal form name
38 <name> must equal form name
39 list=[1,2,3,4,5]
39 list=[1,2,3,4,5]
40 for SELECT use formencode.All(OneOf(list), Int())
40 for SELECT use formencode.All(OneOf(list), Int())
41
41
42 """
42 """
43
43
44 import deform
44 import deform
45 import logging
45 import logging
46 import formencode
46 import formencode
47
47
48 from pkg_resources import resource_filename
48 from pkg_resources import resource_filename
49 from formencode import All, Pipe
49 from formencode import All, Pipe
50
50
51 from pyramid.threadlocal import get_current_request
51 from pyramid.threadlocal import get_current_request
52
52
53 from rhodecode import BACKENDS
53 from rhodecode import BACKENDS
54 from rhodecode.lib import helpers
54 from rhodecode.lib import helpers
55 from rhodecode.model import validators as v
55 from rhodecode.model import validators as v
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 deform_templates = resource_filename('deform', 'templates')
60 deform_templates = resource_filename('deform', 'templates')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
61 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 search_path = (rhodecode_templates, deform_templates)
62 search_path = (rhodecode_templates, deform_templates)
63
63
64
64
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
65 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
66 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 def __call__(self, template_name, **kw):
67 def __call__(self, template_name, **kw):
68 kw['h'] = helpers
68 kw['h'] = helpers
69 kw['request'] = get_current_request()
69 kw['request'] = get_current_request()
70 return self.load(template_name)(**kw)
70 return self.load(template_name)(**kw)
71
71
72
72
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
74 deform.Form.set_default_renderer(form_renderer)
74 deform.Form.set_default_renderer(form_renderer)
75
75
76
76
77 def LoginForm(localizer):
77 def LoginForm(localizer):
78 _ = localizer
78 _ = localizer
79
79
80 class _LoginForm(formencode.Schema):
80 class _LoginForm(formencode.Schema):
81 allow_extra_fields = True
81 allow_extra_fields = True
82 filter_extra_fields = True
82 filter_extra_fields = True
83 username = v.UnicodeString(
83 username = v.UnicodeString(
84 strip=True,
84 strip=True,
85 min=1,
85 min=1,
86 not_empty=True,
86 not_empty=True,
87 messages={
87 messages={
88 'empty': _(u'Please enter a login'),
88 'empty': _(u'Please enter a login'),
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
89 'tooShort': _(u'Enter a value %(min)i characters long or more')
90 }
90 }
91 )
91 )
92
92
93 password = v.UnicodeString(
93 password = v.UnicodeString(
94 strip=False,
94 strip=False,
95 min=3,
95 min=3,
96 max=72,
96 max=72,
97 not_empty=True,
97 not_empty=True,
98 messages={
98 messages={
99 'empty': _(u'Please enter a password'),
99 'empty': _(u'Please enter a password'),
100 'tooShort': _(u'Enter %(min)i characters or more')}
100 'tooShort': _(u'Enter %(min)i characters or more')}
101 )
101 )
102
102
103 remember = v.StringBoolean(if_missing=False)
103 remember = v.StringBoolean(if_missing=False)
104
104
105 chained_validators = [v.ValidAuth(localizer)]
105 chained_validators = [v.ValidAuth(localizer)]
106 return _LoginForm
106 return _LoginForm
107
107
108
108
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
109 def UserForm(localizer, edit=False, available_languages=None, old_data=None):
110 old_data = old_data or {}
110 old_data = old_data or {}
111 available_languages = available_languages or []
111 available_languages = available_languages or []
112 _ = localizer
112 _ = localizer
113
113
114 class _UserForm(formencode.Schema):
114 class _UserForm(formencode.Schema):
115 allow_extra_fields = True
115 allow_extra_fields = True
116 filter_extra_fields = True
116 filter_extra_fields = True
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
117 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
118 v.ValidUsername(localizer, edit, old_data))
118 v.ValidUsername(localizer, edit, old_data))
119 if edit:
119 if edit:
120 new_password = All(
120 new_password = All(
121 v.ValidPassword(localizer),
121 v.ValidPassword(localizer),
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
122 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
123 )
123 )
124 password_confirmation = All(
124 password_confirmation = All(
125 v.ValidPassword(localizer),
125 v.ValidPassword(localizer),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
126 v.UnicodeString(strip=False, min=6, max=72, not_empty=False),
127 )
127 )
128 admin = v.StringBoolean(if_missing=False)
128 admin = v.StringBoolean(if_missing=False)
129 else:
129 else:
130 password = All(
130 password = All(
131 v.ValidPassword(localizer),
131 v.ValidPassword(localizer),
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
132 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
133 )
133 )
134 password_confirmation = All(
134 password_confirmation = All(
135 v.ValidPassword(localizer),
135 v.ValidPassword(localizer),
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
136 v.UnicodeString(strip=False, min=6, max=72, not_empty=False)
137 )
137 )
138
138
139 password_change = v.StringBoolean(if_missing=False)
139 password_change = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
140 create_repo_group = v.StringBoolean(if_missing=False)
141
141
142 active = v.StringBoolean(if_missing=False)
142 active = v.StringBoolean(if_missing=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
143 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
144 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
145 email = All(v.Email(not_empty=True), v.UniqSystemEmail(localizer, old_data))
145 email = All(v.Email(not_empty=True), v.UniqSystemEmail(localizer, old_data))
146 extern_name = v.UnicodeString(strip=True)
146 extern_name = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
147 extern_type = v.UnicodeString(strip=True)
148 language = v.OneOf(available_languages, hideList=False,
148 language = v.OneOf(available_languages, hideList=False,
149 testValueList=True, if_missing=None)
149 testValueList=True, if_missing=None)
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
150 chained_validators = [v.ValidPasswordsMatch(localizer)]
151 return _UserForm
151 return _UserForm
152
152
153
153
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
154 def UserGroupForm(localizer, edit=False, old_data=None, allow_disabled=False):
155 old_data = old_data or {}
155 old_data = old_data or {}
156 _ = localizer
156 _ = localizer
157
157
158 class _UserGroupForm(formencode.Schema):
158 class _UserGroupForm(formencode.Schema):
159 allow_extra_fields = True
159 allow_extra_fields = True
160 filter_extra_fields = True
160 filter_extra_fields = True
161
161
162 users_group_name = All(
162 users_group_name = All(
163 v.UnicodeString(strip=True, min=1, not_empty=True),
163 v.UnicodeString(strip=True, min=1, not_empty=True),
164 v.ValidUserGroup(localizer, edit, old_data)
164 v.ValidUserGroup(localizer, edit, old_data)
165 )
165 )
166 user_group_description = v.UnicodeString(strip=True, min=1,
166 user_group_description = v.UnicodeString(strip=True, min=1,
167 not_empty=False)
167 not_empty=False)
168
168
169 users_group_active = v.StringBoolean(if_missing=False)
169 users_group_active = v.StringBoolean(if_missing=False)
170
170
171 if edit:
171 if edit:
172 # this is user group owner
172 # this is user group owner
173 user = All(
173 user = All(
174 v.UnicodeString(not_empty=True),
174 v.UnicodeString(not_empty=True),
175 v.ValidRepoUser(localizer, allow_disabled))
175 v.ValidRepoUser(localizer, allow_disabled))
176 return _UserGroupForm
176 return _UserGroupForm
177
177
178
178
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
179 def RepoGroupForm(localizer, edit=False, old_data=None, available_groups=None,
180 can_create_in_root=False, allow_disabled=False):
180 can_create_in_root=False, allow_disabled=False):
181 _ = localizer
181 _ = localizer
182 old_data = old_data or {}
182 old_data = old_data or {}
183 available_groups = available_groups or []
183 available_groups = available_groups or []
184
184
185 class _RepoGroupForm(formencode.Schema):
185 class _RepoGroupForm(formencode.Schema):
186 allow_extra_fields = True
186 allow_extra_fields = True
187 filter_extra_fields = False
187 filter_extra_fields = False
188
188
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
189 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
190 v.SlugifyName(localizer),)
190 v.SlugifyName(localizer),)
191 group_description = v.UnicodeString(strip=True, min=1,
191 group_description = v.UnicodeString(strip=True, min=1,
192 not_empty=False)
192 not_empty=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
193 group_copy_permissions = v.StringBoolean(if_missing=False)
194
194
195 group_parent_id = v.OneOf(available_groups, hideList=False,
195 group_parent_id = v.OneOf(available_groups, hideList=False,
196 testValueList=True, not_empty=True)
196 testValueList=True, not_empty=True)
197 enable_locking = v.StringBoolean(if_missing=False)
197 enable_locking = v.StringBoolean(if_missing=False)
198 chained_validators = [
198 chained_validators = [
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
199 v.ValidRepoGroup(localizer, edit, old_data, can_create_in_root)]
200
200
201 if edit:
201 if edit:
202 # this is repo group owner
202 # this is repo group owner
203 user = All(
203 user = All(
204 v.UnicodeString(not_empty=True),
204 v.UnicodeString(not_empty=True),
205 v.ValidRepoUser(localizer, allow_disabled))
205 v.ValidRepoUser(localizer, allow_disabled))
206 return _RepoGroupForm
206 return _RepoGroupForm
207
207
208
208
209 def RegisterForm(localizer, edit=False, old_data=None):
209 def RegisterForm(localizer, edit=False, old_data=None):
210 _ = localizer
210 _ = localizer
211 old_data = old_data or {}
211 old_data = old_data or {}
212
212
213 class _RegisterForm(formencode.Schema):
213 class _RegisterForm(formencode.Schema):
214 allow_extra_fields = True
214 allow_extra_fields = True
215 filter_extra_fields = True
215 filter_extra_fields = True
216 username = All(
216 username = All(
217 v.ValidUsername(localizer, edit, old_data),
217 v.ValidUsername(localizer, edit, old_data),
218 v.UnicodeString(strip=True, min=1, not_empty=True)
218 v.UnicodeString(strip=True, min=1, not_empty=True)
219 )
219 )
220 password = All(
220 password = All(
221 v.ValidPassword(localizer),
221 v.ValidPassword(localizer),
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
222 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
223 )
223 )
224 password_confirmation = All(
224 password_confirmation = All(
225 v.ValidPassword(localizer),
225 v.ValidPassword(localizer),
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
226 v.UnicodeString(strip=False, min=6, max=72, not_empty=True)
227 )
227 )
228 active = v.StringBoolean(if_missing=False)
228 active = v.StringBoolean(if_missing=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
229 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
230 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
231 email = All(v.Email(not_empty=True), v.UniqSystemEmail(localizer, old_data))
231 email = All(
232 v.Email(not_empty=True),
233 v.UniqSystemEmail(localizer, old_data),
234 v.UnicodeString(strip=True, min=3))
232
235
233 chained_validators = [v.ValidPasswordsMatch(localizer)]
236 chained_validators = [v.ValidPasswordsMatch(localizer)]
234 return _RegisterForm
237 return _RegisterForm
235
238
236
239
237 def PasswordResetForm(localizer):
240 def PasswordResetForm(localizer):
238 _ = localizer
241 _ = localizer
239
242
240 class _PasswordResetForm(formencode.Schema):
243 class _PasswordResetForm(formencode.Schema):
241 allow_extra_fields = True
244 allow_extra_fields = True
242 filter_extra_fields = True
245 filter_extra_fields = True
243 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
246 email = All(v.ValidSystemEmail(localizer), v.Email(not_empty=True))
244 return _PasswordResetForm
247 return _PasswordResetForm
245
248
246
249
247 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
250 def RepoForm(localizer, edit=False, old_data=None, repo_groups=None,
248 landing_revs=None, allow_disabled=False):
251 landing_revs=None, allow_disabled=False):
249 _ = localizer
252 _ = localizer
250 old_data = old_data or {}
253 old_data = old_data or {}
251 repo_groups = repo_groups or []
254 repo_groups = repo_groups or []
252 landing_revs = landing_revs or []
255 landing_revs = landing_revs or []
253 supported_backends = BACKENDS.keys()
256 supported_backends = BACKENDS.keys()
254
257
255 class _RepoForm(formencode.Schema):
258 class _RepoForm(formencode.Schema):
256 allow_extra_fields = True
259 allow_extra_fields = True
257 filter_extra_fields = False
260 filter_extra_fields = False
258 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
261 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
259 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
262 v.SlugifyName(localizer), v.CannotHaveGitSuffix(localizer))
260 repo_group = All(v.CanWriteGroup(localizer, old_data),
263 repo_group = All(v.CanWriteGroup(localizer, old_data),
261 v.OneOf(repo_groups, hideList=True))
264 v.OneOf(repo_groups, hideList=True))
262 repo_type = v.OneOf(supported_backends, required=False,
265 repo_type = v.OneOf(supported_backends, required=False,
263 if_missing=old_data.get('repo_type'))
266 if_missing=old_data.get('repo_type'))
264 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
267 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
265 repo_private = v.StringBoolean(if_missing=False)
268 repo_private = v.StringBoolean(if_missing=False)
266 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
269 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
267 repo_copy_permissions = v.StringBoolean(if_missing=False)
270 repo_copy_permissions = v.StringBoolean(if_missing=False)
268 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
271 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
269
272
270 repo_enable_statistics = v.StringBoolean(if_missing=False)
273 repo_enable_statistics = v.StringBoolean(if_missing=False)
271 repo_enable_downloads = v.StringBoolean(if_missing=False)
274 repo_enable_downloads = v.StringBoolean(if_missing=False)
272 repo_enable_locking = v.StringBoolean(if_missing=False)
275 repo_enable_locking = v.StringBoolean(if_missing=False)
273
276
274 if edit:
277 if edit:
275 # this is repo owner
278 # this is repo owner
276 user = All(
279 user = All(
277 v.UnicodeString(not_empty=True),
280 v.UnicodeString(not_empty=True),
278 v.ValidRepoUser(localizer, allow_disabled))
281 v.ValidRepoUser(localizer, allow_disabled))
279 clone_uri_change = v.UnicodeString(
282 clone_uri_change = v.UnicodeString(
280 not_empty=False, if_missing=v.Missing)
283 not_empty=False, if_missing=v.Missing)
281
284
282 chained_validators = [v.ValidCloneUri(localizer),
285 chained_validators = [v.ValidCloneUri(localizer),
283 v.ValidRepoName(localizer, edit, old_data)]
286 v.ValidRepoName(localizer, edit, old_data)]
284 return _RepoForm
287 return _RepoForm
285
288
286
289
287 def RepoPermsForm(localizer):
290 def RepoPermsForm(localizer):
288 _ = localizer
291 _ = localizer
289
292
290 class _RepoPermsForm(formencode.Schema):
293 class _RepoPermsForm(formencode.Schema):
291 allow_extra_fields = True
294 allow_extra_fields = True
292 filter_extra_fields = False
295 filter_extra_fields = False
293 chained_validators = [v.ValidPerms(localizer, type_='repo')]
296 chained_validators = [v.ValidPerms(localizer, type_='repo')]
294 return _RepoPermsForm
297 return _RepoPermsForm
295
298
296
299
297 def RepoGroupPermsForm(localizer, valid_recursive_choices):
300 def RepoGroupPermsForm(localizer, valid_recursive_choices):
298 _ = localizer
301 _ = localizer
299
302
300 class _RepoGroupPermsForm(formencode.Schema):
303 class _RepoGroupPermsForm(formencode.Schema):
301 allow_extra_fields = True
304 allow_extra_fields = True
302 filter_extra_fields = False
305 filter_extra_fields = False
303 recursive = v.OneOf(valid_recursive_choices)
306 recursive = v.OneOf(valid_recursive_choices)
304 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
307 chained_validators = [v.ValidPerms(localizer, type_='repo_group')]
305 return _RepoGroupPermsForm
308 return _RepoGroupPermsForm
306
309
307
310
308 def UserGroupPermsForm(localizer):
311 def UserGroupPermsForm(localizer):
309 _ = localizer
312 _ = localizer
310
313
311 class _UserPermsForm(formencode.Schema):
314 class _UserPermsForm(formencode.Schema):
312 allow_extra_fields = True
315 allow_extra_fields = True
313 filter_extra_fields = False
316 filter_extra_fields = False
314 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
317 chained_validators = [v.ValidPerms(localizer, type_='user_group')]
315 return _UserPermsForm
318 return _UserPermsForm
316
319
317
320
318 def RepoFieldForm(localizer):
321 def RepoFieldForm(localizer):
319 _ = localizer
322 _ = localizer
320
323
321 class _RepoFieldForm(formencode.Schema):
324 class _RepoFieldForm(formencode.Schema):
322 filter_extra_fields = True
325 filter_extra_fields = True
323 allow_extra_fields = True
326 allow_extra_fields = True
324
327
325 new_field_key = All(v.FieldKey(localizer),
328 new_field_key = All(v.FieldKey(localizer),
326 v.UnicodeString(strip=True, min=3, not_empty=True))
329 v.UnicodeString(strip=True, min=3, not_empty=True))
327 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
330 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
328 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
331 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
329 if_missing='str')
332 if_missing='str')
330 new_field_label = v.UnicodeString(not_empty=False)
333 new_field_label = v.UnicodeString(not_empty=False)
331 new_field_desc = v.UnicodeString(not_empty=False)
334 new_field_desc = v.UnicodeString(not_empty=False)
332 return _RepoFieldForm
335 return _RepoFieldForm
333
336
334
337
335 def RepoForkForm(localizer, edit=False, old_data=None,
338 def RepoForkForm(localizer, edit=False, old_data=None,
336 supported_backends=BACKENDS.keys(), repo_groups=None,
339 supported_backends=BACKENDS.keys(), repo_groups=None,
337 landing_revs=None):
340 landing_revs=None):
338 _ = localizer
341 _ = localizer
339 old_data = old_data or {}
342 old_data = old_data or {}
340 repo_groups = repo_groups or []
343 repo_groups = repo_groups or []
341 landing_revs = landing_revs or []
344 landing_revs = landing_revs or []
342
345
343 class _RepoForkForm(formencode.Schema):
346 class _RepoForkForm(formencode.Schema):
344 allow_extra_fields = True
347 allow_extra_fields = True
345 filter_extra_fields = False
348 filter_extra_fields = False
346 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
349 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
347 v.SlugifyName(localizer))
350 v.SlugifyName(localizer))
348 repo_group = All(v.CanWriteGroup(localizer, ),
351 repo_group = All(v.CanWriteGroup(localizer, ),
349 v.OneOf(repo_groups, hideList=True))
352 v.OneOf(repo_groups, hideList=True))
350 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
353 repo_type = All(v.ValidForkType(localizer, old_data), v.OneOf(supported_backends))
351 description = v.UnicodeString(strip=True, min=1, not_empty=True)
354 description = v.UnicodeString(strip=True, min=1, not_empty=True)
352 private = v.StringBoolean(if_missing=False)
355 private = v.StringBoolean(if_missing=False)
353 copy_permissions = v.StringBoolean(if_missing=False)
356 copy_permissions = v.StringBoolean(if_missing=False)
354 fork_parent_id = v.UnicodeString()
357 fork_parent_id = v.UnicodeString()
355 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
358 chained_validators = [v.ValidForkName(localizer, edit, old_data)]
356 landing_rev = v.OneOf(landing_revs, hideList=True)
359 landing_rev = v.OneOf(landing_revs, hideList=True)
357 return _RepoForkForm
360 return _RepoForkForm
358
361
359
362
360 def ApplicationSettingsForm(localizer):
363 def ApplicationSettingsForm(localizer):
361 _ = localizer
364 _ = localizer
362
365
363 class _ApplicationSettingsForm(formencode.Schema):
366 class _ApplicationSettingsForm(formencode.Schema):
364 allow_extra_fields = True
367 allow_extra_fields = True
365 filter_extra_fields = False
368 filter_extra_fields = False
366 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
369 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
367 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
370 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
368 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
369 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
372 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
370 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
373 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
371 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
374 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
372 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
375 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
373 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
376 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
374 return _ApplicationSettingsForm
377 return _ApplicationSettingsForm
375
378
376
379
377 def ApplicationVisualisationForm(localizer):
380 def ApplicationVisualisationForm(localizer):
378 _ = localizer
381 _ = localizer
379
382
380 class _ApplicationVisualisationForm(formencode.Schema):
383 class _ApplicationVisualisationForm(formencode.Schema):
381 allow_extra_fields = True
384 allow_extra_fields = True
382 filter_extra_fields = False
385 filter_extra_fields = False
383 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
386 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
384 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
387 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
385 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
388 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
386
389
387 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
390 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
388 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
391 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
389 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
392 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
390 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
393 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
391 rhodecode_show_version = v.StringBoolean(if_missing=False)
394 rhodecode_show_version = v.StringBoolean(if_missing=False)
392 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
395 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
393 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
396 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
394 rhodecode_gravatar_url = v.UnicodeString(min=3)
397 rhodecode_gravatar_url = v.UnicodeString(min=3)
395 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
398 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
396 rhodecode_support_url = v.UnicodeString()
399 rhodecode_support_url = v.UnicodeString()
397 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
400 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
398 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
401 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
399 return _ApplicationVisualisationForm
402 return _ApplicationVisualisationForm
400
403
401
404
402 class _BaseVcsSettingsForm(formencode.Schema):
405 class _BaseVcsSettingsForm(formencode.Schema):
403
406
404 allow_extra_fields = True
407 allow_extra_fields = True
405 filter_extra_fields = False
408 filter_extra_fields = False
406 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
409 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
407 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
410 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
408 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
411 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
409
412
410 # PR/Code-review
413 # PR/Code-review
411 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
414 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
412 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
415 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
413
416
414 # hg
417 # hg
415 extensions_largefiles = v.StringBoolean(if_missing=False)
418 extensions_largefiles = v.StringBoolean(if_missing=False)
416 extensions_evolve = v.StringBoolean(if_missing=False)
419 extensions_evolve = v.StringBoolean(if_missing=False)
417 phases_publish = v.StringBoolean(if_missing=False)
420 phases_publish = v.StringBoolean(if_missing=False)
418
421
419 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
422 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
420 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
423 rhodecode_hg_close_branch_before_merging = v.StringBoolean(if_missing=False)
421
424
422 # git
425 # git
423 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
426 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
424 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
427 rhodecode_git_use_rebase_for_merging = v.StringBoolean(if_missing=False)
425 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
428 rhodecode_git_close_branch_before_merging = v.StringBoolean(if_missing=False)
426
429
427 # svn
430 # svn
428 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
431 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
429 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
432 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
430
433
431
434
432 def ApplicationUiSettingsForm(localizer):
435 def ApplicationUiSettingsForm(localizer):
433 _ = localizer
436 _ = localizer
434
437
435 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
438 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
436 web_push_ssl = v.StringBoolean(if_missing=False)
439 web_push_ssl = v.StringBoolean(if_missing=False)
437 paths_root_path = All(
440 paths_root_path = All(
438 v.ValidPath(localizer),
441 v.ValidPath(localizer),
439 v.UnicodeString(strip=True, min=1, not_empty=True)
442 v.UnicodeString(strip=True, min=1, not_empty=True)
440 )
443 )
441 largefiles_usercache = All(
444 largefiles_usercache = All(
442 v.ValidPath(localizer),
445 v.ValidPath(localizer),
443 v.UnicodeString(strip=True, min=2, not_empty=True))
446 v.UnicodeString(strip=True, min=2, not_empty=True))
444 vcs_git_lfs_store_location = All(
447 vcs_git_lfs_store_location = All(
445 v.ValidPath(localizer),
448 v.ValidPath(localizer),
446 v.UnicodeString(strip=True, min=2, not_empty=True))
449 v.UnicodeString(strip=True, min=2, not_empty=True))
447 extensions_hgsubversion = v.StringBoolean(if_missing=False)
450 extensions_hgsubversion = v.StringBoolean(if_missing=False)
448 extensions_hggit = v.StringBoolean(if_missing=False)
451 extensions_hggit = v.StringBoolean(if_missing=False)
449 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
452 new_svn_branch = v.ValidSvnPattern(localizer, section='vcs_svn_branch')
450 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
453 new_svn_tag = v.ValidSvnPattern(localizer, section='vcs_svn_tag')
451 return _ApplicationUiSettingsForm
454 return _ApplicationUiSettingsForm
452
455
453
456
454 def RepoVcsSettingsForm(localizer, repo_name):
457 def RepoVcsSettingsForm(localizer, repo_name):
455 _ = localizer
458 _ = localizer
456
459
457 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
460 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
458 inherit_global_settings = v.StringBoolean(if_missing=False)
461 inherit_global_settings = v.StringBoolean(if_missing=False)
459 new_svn_branch = v.ValidSvnPattern(localizer,
462 new_svn_branch = v.ValidSvnPattern(localizer,
460 section='vcs_svn_branch', repo_name=repo_name)
463 section='vcs_svn_branch', repo_name=repo_name)
461 new_svn_tag = v.ValidSvnPattern(localizer,
464 new_svn_tag = v.ValidSvnPattern(localizer,
462 section='vcs_svn_tag', repo_name=repo_name)
465 section='vcs_svn_tag', repo_name=repo_name)
463 return _RepoVcsSettingsForm
466 return _RepoVcsSettingsForm
464
467
465
468
466 def LabsSettingsForm(localizer):
469 def LabsSettingsForm(localizer):
467 _ = localizer
470 _ = localizer
468
471
469 class _LabSettingsForm(formencode.Schema):
472 class _LabSettingsForm(formencode.Schema):
470 allow_extra_fields = True
473 allow_extra_fields = True
471 filter_extra_fields = False
474 filter_extra_fields = False
472 return _LabSettingsForm
475 return _LabSettingsForm
473
476
474
477
475 def ApplicationPermissionsForm(
478 def ApplicationPermissionsForm(
476 localizer, register_choices, password_reset_choices,
479 localizer, register_choices, password_reset_choices,
477 extern_activate_choices):
480 extern_activate_choices):
478 _ = localizer
481 _ = localizer
479
482
480 class _DefaultPermissionsForm(formencode.Schema):
483 class _DefaultPermissionsForm(formencode.Schema):
481 allow_extra_fields = True
484 allow_extra_fields = True
482 filter_extra_fields = True
485 filter_extra_fields = True
483
486
484 anonymous = v.StringBoolean(if_missing=False)
487 anonymous = v.StringBoolean(if_missing=False)
485 default_register = v.OneOf(register_choices)
488 default_register = v.OneOf(register_choices)
486 default_register_message = v.UnicodeString()
489 default_register_message = v.UnicodeString()
487 default_password_reset = v.OneOf(password_reset_choices)
490 default_password_reset = v.OneOf(password_reset_choices)
488 default_extern_activate = v.OneOf(extern_activate_choices)
491 default_extern_activate = v.OneOf(extern_activate_choices)
489 return _DefaultPermissionsForm
492 return _DefaultPermissionsForm
490
493
491
494
492 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
495 def ObjectPermissionsForm(localizer, repo_perms_choices, group_perms_choices,
493 user_group_perms_choices):
496 user_group_perms_choices):
494 _ = localizer
497 _ = localizer
495
498
496 class _ObjectPermissionsForm(formencode.Schema):
499 class _ObjectPermissionsForm(formencode.Schema):
497 allow_extra_fields = True
500 allow_extra_fields = True
498 filter_extra_fields = True
501 filter_extra_fields = True
499 overwrite_default_repo = v.StringBoolean(if_missing=False)
502 overwrite_default_repo = v.StringBoolean(if_missing=False)
500 overwrite_default_group = v.StringBoolean(if_missing=False)
503 overwrite_default_group = v.StringBoolean(if_missing=False)
501 overwrite_default_user_group = v.StringBoolean(if_missing=False)
504 overwrite_default_user_group = v.StringBoolean(if_missing=False)
502 default_repo_perm = v.OneOf(repo_perms_choices)
505 default_repo_perm = v.OneOf(repo_perms_choices)
503 default_group_perm = v.OneOf(group_perms_choices)
506 default_group_perm = v.OneOf(group_perms_choices)
504 default_user_group_perm = v.OneOf(user_group_perms_choices)
507 default_user_group_perm = v.OneOf(user_group_perms_choices)
505 return _ObjectPermissionsForm
508 return _ObjectPermissionsForm
506
509
507
510
508 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
511 def UserPermissionsForm(localizer, create_choices, create_on_write_choices,
509 repo_group_create_choices, user_group_create_choices,
512 repo_group_create_choices, user_group_create_choices,
510 fork_choices, inherit_default_permissions_choices):
513 fork_choices, inherit_default_permissions_choices):
511 _ = localizer
514 _ = localizer
512
515
513 class _DefaultPermissionsForm(formencode.Schema):
516 class _DefaultPermissionsForm(formencode.Schema):
514 allow_extra_fields = True
517 allow_extra_fields = True
515 filter_extra_fields = True
518 filter_extra_fields = True
516
519
517 anonymous = v.StringBoolean(if_missing=False)
520 anonymous = v.StringBoolean(if_missing=False)
518
521
519 default_repo_create = v.OneOf(create_choices)
522 default_repo_create = v.OneOf(create_choices)
520 default_repo_create_on_write = v.OneOf(create_on_write_choices)
523 default_repo_create_on_write = v.OneOf(create_on_write_choices)
521 default_user_group_create = v.OneOf(user_group_create_choices)
524 default_user_group_create = v.OneOf(user_group_create_choices)
522 default_repo_group_create = v.OneOf(repo_group_create_choices)
525 default_repo_group_create = v.OneOf(repo_group_create_choices)
523 default_fork_create = v.OneOf(fork_choices)
526 default_fork_create = v.OneOf(fork_choices)
524 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
527 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
525 return _DefaultPermissionsForm
528 return _DefaultPermissionsForm
526
529
527
530
528 def UserIndividualPermissionsForm(localizer):
531 def UserIndividualPermissionsForm(localizer):
529 _ = localizer
532 _ = localizer
530
533
531 class _DefaultPermissionsForm(formencode.Schema):
534 class _DefaultPermissionsForm(formencode.Schema):
532 allow_extra_fields = True
535 allow_extra_fields = True
533 filter_extra_fields = True
536 filter_extra_fields = True
534
537
535 inherit_default_permissions = v.StringBoolean(if_missing=False)
538 inherit_default_permissions = v.StringBoolean(if_missing=False)
536 return _DefaultPermissionsForm
539 return _DefaultPermissionsForm
537
540
538
541
539 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
542 def DefaultsForm(localizer, edit=False, old_data=None, supported_backends=BACKENDS.keys()):
540 _ = localizer
543 _ = localizer
541 old_data = old_data or {}
544 old_data = old_data or {}
542
545
543 class _DefaultsForm(formencode.Schema):
546 class _DefaultsForm(formencode.Schema):
544 allow_extra_fields = True
547 allow_extra_fields = True
545 filter_extra_fields = True
548 filter_extra_fields = True
546 default_repo_type = v.OneOf(supported_backends)
549 default_repo_type = v.OneOf(supported_backends)
547 default_repo_private = v.StringBoolean(if_missing=False)
550 default_repo_private = v.StringBoolean(if_missing=False)
548 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
551 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
549 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
552 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
550 default_repo_enable_locking = v.StringBoolean(if_missing=False)
553 default_repo_enable_locking = v.StringBoolean(if_missing=False)
551 return _DefaultsForm
554 return _DefaultsForm
552
555
553
556
554 def AuthSettingsForm(localizer):
557 def AuthSettingsForm(localizer):
555 _ = localizer
558 _ = localizer
556
559
557 class _AuthSettingsForm(formencode.Schema):
560 class _AuthSettingsForm(formencode.Schema):
558 allow_extra_fields = True
561 allow_extra_fields = True
559 filter_extra_fields = True
562 filter_extra_fields = True
560 auth_plugins = All(v.ValidAuthPlugins(localizer),
563 auth_plugins = All(v.ValidAuthPlugins(localizer),
561 v.UniqueListFromString(localizer)(not_empty=True))
564 v.UniqueListFromString(localizer)(not_empty=True))
562 return _AuthSettingsForm
565 return _AuthSettingsForm
563
566
564
567
565 def UserExtraEmailForm(localizer):
568 def UserExtraEmailForm(localizer):
566 _ = localizer
569 _ = localizer
567
570
568 class _UserExtraEmailForm(formencode.Schema):
571 class _UserExtraEmailForm(formencode.Schema):
569 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
572 email = All(v.UniqSystemEmail(localizer), v.Email(not_empty=True))
570 return _UserExtraEmailForm
573 return _UserExtraEmailForm
571
574
572
575
573 def UserExtraIpForm(localizer):
576 def UserExtraIpForm(localizer):
574 _ = localizer
577 _ = localizer
575
578
576 class _UserExtraIpForm(formencode.Schema):
579 class _UserExtraIpForm(formencode.Schema):
577 ip = v.ValidIp(localizer)(not_empty=True)
580 ip = v.ValidIp(localizer)(not_empty=True)
578 return _UserExtraIpForm
581 return _UserExtraIpForm
579
582
580
583
581 def PullRequestForm(localizer, repo_id):
584 def PullRequestForm(localizer, repo_id):
582 _ = localizer
585 _ = localizer
583
586
584 class ReviewerForm(formencode.Schema):
587 class ReviewerForm(formencode.Schema):
585 user_id = v.Int(not_empty=True)
588 user_id = v.Int(not_empty=True)
586 reasons = All()
589 reasons = All()
587 mandatory = v.StringBoolean()
590 mandatory = v.StringBoolean()
588
591
589 class _PullRequestForm(formencode.Schema):
592 class _PullRequestForm(formencode.Schema):
590 allow_extra_fields = True
593 allow_extra_fields = True
591 filter_extra_fields = True
594 filter_extra_fields = True
592
595
593 common_ancestor = v.UnicodeString(strip=True, required=True)
596 common_ancestor = v.UnicodeString(strip=True, required=True)
594 source_repo = v.UnicodeString(strip=True, required=True)
597 source_repo = v.UnicodeString(strip=True, required=True)
595 source_ref = v.UnicodeString(strip=True, required=True)
598 source_ref = v.UnicodeString(strip=True, required=True)
596 target_repo = v.UnicodeString(strip=True, required=True)
599 target_repo = v.UnicodeString(strip=True, required=True)
597 target_ref = v.UnicodeString(strip=True, required=True)
600 target_ref = v.UnicodeString(strip=True, required=True)
598 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
601 revisions = All(#v.NotReviewedRevisions(localizer, repo_id)(),
599 v.UniqueList(localizer)(not_empty=True))
602 v.UniqueList(localizer)(not_empty=True))
600 review_members = formencode.ForEach(ReviewerForm())
603 review_members = formencode.ForEach(ReviewerForm())
601 pullrequest_title = v.UnicodeString(strip=True, required=True)
604 pullrequest_title = v.UnicodeString(strip=True, required=True)
602 pullrequest_desc = v.UnicodeString(strip=True, required=False)
605 pullrequest_desc = v.UnicodeString(strip=True, required=False)
603
606
604 return _PullRequestForm
607 return _PullRequestForm
605
608
606
609
607 def IssueTrackerPatternsForm(localizer):
610 def IssueTrackerPatternsForm(localizer):
608 _ = localizer
611 _ = localizer
609
612
610 class _IssueTrackerPatternsForm(formencode.Schema):
613 class _IssueTrackerPatternsForm(formencode.Schema):
611 allow_extra_fields = True
614 allow_extra_fields = True
612 filter_extra_fields = False
615 filter_extra_fields = False
613 chained_validators = [v.ValidPattern(localizer)]
616 chained_validators = [v.ValidPattern(localizer)]
614 return _IssueTrackerPatternsForm
617 return _IssueTrackerPatternsForm
General Comments 0
You need to be logged in to leave comments. Login now