Show More
@@ -1,178 +1,179 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2011-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2011-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 | import logging |
|
21 | import logging | |
22 |
|
22 | |||
23 | import deform |
|
23 | import deform | |
24 | from pyramid.httpexceptions import HTTPFound |
|
24 | from pyramid.httpexceptions import HTTPFound | |
25 | from pyramid.view import view_config |
|
25 | from pyramid.view import view_config | |
26 |
|
26 | |||
27 | from rhodecode.apps._base import RepoAppView |
|
27 | from rhodecode.apps._base import RepoAppView | |
28 | from rhodecode.forms import RcForm |
|
28 | from rhodecode.forms import RcForm | |
29 | from rhodecode.lib import helpers as h |
|
29 | from rhodecode.lib import helpers as h | |
30 | from rhodecode.lib import audit_logger |
|
30 | from rhodecode.lib import audit_logger | |
31 | from rhodecode.lib.auth import ( |
|
31 | from rhodecode.lib.auth import ( | |
32 | LoginRequired, HasRepoPermissionAnyDecorator, |
|
32 | LoginRequired, HasRepoPermissionAnyDecorator, | |
33 | HasRepoPermissionAllDecorator, CSRFRequired) |
|
33 | HasRepoPermissionAllDecorator, CSRFRequired) | |
34 | from rhodecode.model.db import RepositoryField, RepoGroup |
|
34 | from rhodecode.model.db import RepositoryField, RepoGroup | |
35 | from rhodecode.model.meta import Session |
|
35 | from rhodecode.model.meta import Session | |
36 | from rhodecode.model.repo import RepoModel |
|
36 | from rhodecode.model.repo import RepoModel | |
37 | from rhodecode.model.scm import RepoGroupList, ScmModel |
|
37 | from rhodecode.model.scm import RepoGroupList, ScmModel | |
38 | from rhodecode.model.validation_schema.schemas import repo_schema |
|
38 | from rhodecode.model.validation_schema.schemas import repo_schema | |
39 |
|
39 | |||
40 | log = logging.getLogger(__name__) |
|
40 | log = logging.getLogger(__name__) | |
41 |
|
41 | |||
42 |
|
42 | |||
43 | class RepoSettingsView(RepoAppView): |
|
43 | class RepoSettingsView(RepoAppView): | |
44 |
|
44 | |||
45 | def load_default_context(self): |
|
45 | def load_default_context(self): | |
46 | c = self._get_local_tmpl_context() |
|
46 | c = self._get_local_tmpl_context() | |
47 |
|
47 | |||
48 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead |
|
48 | # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead | |
49 | c.repo_info = self.db_repo |
|
49 | c.repo_info = self.db_repo | |
50 |
|
50 | |||
51 | acl_groups = RepoGroupList( |
|
51 | acl_groups = RepoGroupList( | |
52 | RepoGroup.query().all(), |
|
52 | RepoGroup.query().all(), | |
53 | perm_set=['group.write', 'group.admin']) |
|
53 | perm_set=['group.write', 'group.admin']) | |
54 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) |
|
54 | c.repo_groups = RepoGroup.groups_choices(groups=acl_groups) | |
55 | c.repo_groups_choices = map(lambda k: k[0], c.repo_groups) |
|
55 | c.repo_groups_choices = map(lambda k: k[0], c.repo_groups) | |
56 |
|
56 | |||
57 | # in case someone no longer have a group.write access to a repository |
|
57 | # in case someone no longer have a group.write access to a repository | |
58 | # pre fill the list with this entry, we don't care if this is the same |
|
58 | # pre fill the list with this entry, we don't care if this is the same | |
59 | # but it will allow saving repo data properly. |
|
59 | # but it will allow saving repo data properly. | |
60 | repo_group = self.db_repo.group |
|
60 | repo_group = self.db_repo.group | |
61 | if repo_group and repo_group.group_id not in c.repo_groups_choices: |
|
61 | if repo_group and repo_group.group_id not in c.repo_groups_choices: | |
62 | c.repo_groups_choices.append(repo_group.group_id) |
|
62 | c.repo_groups_choices.append(repo_group.group_id) | |
63 | c.repo_groups.append(RepoGroup._generate_choice(repo_group)) |
|
63 | c.repo_groups.append(RepoGroup._generate_choice(repo_group)) | |
64 |
|
64 | |||
65 | if c.repository_requirements_missing or self.rhodecode_vcs_repo is None: |
|
65 | if c.repository_requirements_missing or self.rhodecode_vcs_repo is None: | |
66 | # we might be in missing requirement state, so we load things |
|
66 | # we might be in missing requirement state, so we load things | |
67 | # without touching scm_instance() |
|
67 | # without touching scm_instance() | |
68 | c.landing_revs_choices, c.landing_revs = \ |
|
68 | c.landing_revs_choices, c.landing_revs = \ | |
69 | ScmModel().get_repo_landing_revs() |
|
69 | ScmModel().get_repo_landing_revs() | |
70 | else: |
|
70 | else: | |
71 | c.landing_revs_choices, c.landing_revs = \ |
|
71 | c.landing_revs_choices, c.landing_revs = \ | |
72 | ScmModel().get_repo_landing_revs(self.db_repo) |
|
72 | ScmModel().get_repo_landing_revs(self.db_repo) | |
73 |
|
73 | |||
74 | c.personal_repo_group = c.auth_user.personal_repo_group |
|
74 | c.personal_repo_group = c.auth_user.personal_repo_group | |
75 | c.repo_fields = RepositoryField.query()\ |
|
75 | c.repo_fields = RepositoryField.query()\ | |
76 | .filter(RepositoryField.repository == self.db_repo).all() |
|
76 | .filter(RepositoryField.repository == self.db_repo).all() | |
77 |
|
77 | |||
78 | self._register_global_c(c) |
|
78 | self._register_global_c(c) | |
79 | return c |
|
79 | return c | |
80 |
|
80 | |||
81 | def _get_schema(self, c, old_values=None): |
|
81 | def _get_schema(self, c, old_values=None): | |
82 | return repo_schema.RepoSettingsSchema().bind( |
|
82 | return repo_schema.RepoSettingsSchema().bind( | |
|
83 | repo_type=self.db_repo.repo_type, | |||
83 | repo_type_options=[self.db_repo.repo_type], |
|
84 | repo_type_options=[self.db_repo.repo_type], | |
84 | repo_ref_options=c.landing_revs_choices, |
|
85 | repo_ref_options=c.landing_revs_choices, | |
85 | repo_ref_items=c.landing_revs, |
|
86 | repo_ref_items=c.landing_revs, | |
86 | repo_repo_group_options=c.repo_groups_choices, |
|
87 | repo_repo_group_options=c.repo_groups_choices, | |
87 | repo_repo_group_items=c.repo_groups, |
|
88 | repo_repo_group_items=c.repo_groups, | |
88 | # user caller |
|
89 | # user caller | |
89 | user=self._rhodecode_user, |
|
90 | user=self._rhodecode_user, | |
90 | old_values=old_values |
|
91 | old_values=old_values | |
91 | ) |
|
92 | ) | |
92 |
|
93 | |||
93 | @LoginRequired() |
|
94 | @LoginRequired() | |
94 | @HasRepoPermissionAnyDecorator('repository.admin') |
|
95 | @HasRepoPermissionAnyDecorator('repository.admin') | |
95 | @view_config( |
|
96 | @view_config( | |
96 | route_name='edit_repo', request_method='GET', |
|
97 | route_name='edit_repo', request_method='GET', | |
97 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') |
|
98 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') | |
98 | def edit_settings(self): |
|
99 | def edit_settings(self): | |
99 | c = self.load_default_context() |
|
100 | c = self.load_default_context() | |
100 | c.active = 'settings' |
|
101 | c.active = 'settings' | |
101 |
|
102 | |||
102 | defaults = RepoModel()._get_defaults(self.db_repo_name) |
|
103 | defaults = RepoModel()._get_defaults(self.db_repo_name) | |
103 | defaults['repo_owner'] = defaults['user'] |
|
104 | defaults['repo_owner'] = defaults['user'] | |
104 | defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev'] |
|
105 | defaults['repo_landing_commit_ref'] = defaults['repo_landing_rev'] | |
105 |
|
106 | |||
106 | schema = self._get_schema(c) |
|
107 | schema = self._get_schema(c) | |
107 | c.form = RcForm(schema, appstruct=defaults) |
|
108 | c.form = RcForm(schema, appstruct=defaults) | |
108 | return self._get_template_context(c) |
|
109 | return self._get_template_context(c) | |
109 |
|
110 | |||
110 | @LoginRequired() |
|
111 | @LoginRequired() | |
111 | @HasRepoPermissionAllDecorator('repository.admin') |
|
112 | @HasRepoPermissionAllDecorator('repository.admin') | |
112 | @CSRFRequired() |
|
113 | @CSRFRequired() | |
113 | @view_config( |
|
114 | @view_config( | |
114 | route_name='edit_repo', request_method='POST', |
|
115 | route_name='edit_repo', request_method='POST', | |
115 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') |
|
116 | renderer='rhodecode:templates/admin/repos/repo_edit.mako') | |
116 | def edit_settings_update(self): |
|
117 | def edit_settings_update(self): | |
117 | _ = self.request.translate |
|
118 | _ = self.request.translate | |
118 | c = self.load_default_context() |
|
119 | c = self.load_default_context() | |
119 | c.active = 'settings' |
|
120 | c.active = 'settings' | |
120 | old_repo_name = self.db_repo_name |
|
121 | old_repo_name = self.db_repo_name | |
121 |
|
122 | |||
122 | old_values = self.db_repo.get_api_data() |
|
123 | old_values = self.db_repo.get_api_data() | |
123 | schema = self._get_schema(c, old_values=old_values) |
|
124 | schema = self._get_schema(c, old_values=old_values) | |
124 |
|
125 | |||
125 | c.form = RcForm(schema) |
|
126 | c.form = RcForm(schema) | |
126 | pstruct = self.request.POST.items() |
|
127 | pstruct = self.request.POST.items() | |
127 | pstruct.append(('repo_type', self.db_repo.repo_type)) |
|
128 | pstruct.append(('repo_type', self.db_repo.repo_type)) | |
128 | try: |
|
129 | try: | |
129 | schema_data = c.form.validate(pstruct) |
|
130 | schema_data = c.form.validate(pstruct) | |
130 | except deform.ValidationFailure as err_form: |
|
131 | except deform.ValidationFailure as err_form: | |
131 | return self._get_template_context(c) |
|
132 | return self._get_template_context(c) | |
132 |
|
133 | |||
133 | # data is now VALID, proceed with updates |
|
134 | # data is now VALID, proceed with updates | |
134 | # save validated data back into the updates dict |
|
135 | # save validated data back into the updates dict | |
135 | validated_updates = dict( |
|
136 | validated_updates = dict( | |
136 | repo_name=schema_data['repo_group']['repo_name_without_group'], |
|
137 | repo_name=schema_data['repo_group']['repo_name_without_group'], | |
137 | repo_group=schema_data['repo_group']['repo_group_id'], |
|
138 | repo_group=schema_data['repo_group']['repo_group_id'], | |
138 |
|
139 | |||
139 | user=schema_data['repo_owner'], |
|
140 | user=schema_data['repo_owner'], | |
140 | repo_description=schema_data['repo_description'], |
|
141 | repo_description=schema_data['repo_description'], | |
141 | repo_private=schema_data['repo_private'], |
|
142 | repo_private=schema_data['repo_private'], | |
142 | clone_uri=schema_data['repo_clone_uri'], |
|
143 | clone_uri=schema_data['repo_clone_uri'], | |
143 | repo_landing_rev=schema_data['repo_landing_commit_ref'], |
|
144 | repo_landing_rev=schema_data['repo_landing_commit_ref'], | |
144 | repo_enable_statistics=schema_data['repo_enable_statistics'], |
|
145 | repo_enable_statistics=schema_data['repo_enable_statistics'], | |
145 | repo_enable_locking=schema_data['repo_enable_locking'], |
|
146 | repo_enable_locking=schema_data['repo_enable_locking'], | |
146 | repo_enable_downloads=schema_data['repo_enable_downloads'], |
|
147 | repo_enable_downloads=schema_data['repo_enable_downloads'], | |
147 | ) |
|
148 | ) | |
148 | # detect if CLONE URI changed, if we get OLD means we keep old values |
|
149 | # detect if CLONE URI changed, if we get OLD means we keep old values | |
149 | if schema_data['repo_clone_uri_change'] == 'OLD': |
|
150 | if schema_data['repo_clone_uri_change'] == 'OLD': | |
150 | validated_updates['clone_uri'] = self.db_repo.clone_uri |
|
151 | validated_updates['clone_uri'] = self.db_repo.clone_uri | |
151 |
|
152 | |||
152 | # use the new full name for redirect |
|
153 | # use the new full name for redirect | |
153 | new_repo_name = schema_data['repo_group']['repo_name_with_group'] |
|
154 | new_repo_name = schema_data['repo_group']['repo_name_with_group'] | |
154 |
|
155 | |||
155 | # save extra fields into our validated data |
|
156 | # save extra fields into our validated data | |
156 | for key, value in pstruct: |
|
157 | for key, value in pstruct: | |
157 | if key.startswith(RepositoryField.PREFIX): |
|
158 | if key.startswith(RepositoryField.PREFIX): | |
158 | validated_updates[key] = value |
|
159 | validated_updates[key] = value | |
159 |
|
160 | |||
160 | try: |
|
161 | try: | |
161 | RepoModel().update(self.db_repo, **validated_updates) |
|
162 | RepoModel().update(self.db_repo, **validated_updates) | |
162 | ScmModel().mark_for_invalidation(new_repo_name) |
|
163 | ScmModel().mark_for_invalidation(new_repo_name) | |
163 |
|
164 | |||
164 | audit_logger.store( |
|
165 | audit_logger.store( | |
165 | 'repo.edit', action_data={'old_data': old_values}, |
|
166 | 'repo.edit', action_data={'old_data': old_values}, | |
166 | user=self._rhodecode_user, repo=self.db_repo) |
|
167 | user=self._rhodecode_user, repo=self.db_repo) | |
167 |
|
168 | |||
168 | Session().commit() |
|
169 | Session().commit() | |
169 |
|
170 | |||
170 | h.flash(_('Repository {} updated successfully').format( |
|
171 | h.flash(_('Repository {} updated successfully').format( | |
171 | old_repo_name), category='success') |
|
172 | old_repo_name), category='success') | |
172 | except Exception: |
|
173 | except Exception: | |
173 | log.exception("Exception during update of repository") |
|
174 | log.exception("Exception during update of repository") | |
174 | h.flash(_('Error occurred during update of repository {}').format( |
|
175 | h.flash(_('Error occurred during update of repository {}').format( | |
175 | old_repo_name), category='error') |
|
176 | old_repo_name), category='error') | |
176 |
|
177 | |||
177 | raise HTTPFound( |
|
178 | raise HTTPFound( | |
178 | self.request.route_path('edit_repo', repo_name=new_repo_name)) |
|
179 | self.request.route_path('edit_repo', repo_name=new_repo_name)) |
@@ -1,360 +1,414 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
1 | # -*- coding: utf-8 -*- | |
2 |
|
2 | |||
3 | # Copyright (C) 2016-2017 RhodeCode GmbH |
|
3 | # Copyright (C) 2016-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 | import colander |
|
21 | import colander | |
22 | import deform.widget |
|
22 | import deform.widget | |
23 |
|
23 | |||
24 | from rhodecode.translation import _ |
|
24 | from rhodecode.translation import _ | |
25 | from rhodecode.model.validation_schema.utils import convert_to_optgroup |
|
25 | from rhodecode.model.validation_schema.utils import convert_to_optgroup | |
26 | from rhodecode.model.validation_schema import validators, preparers, types |
|
26 | from rhodecode.model.validation_schema import validators, preparers, types | |
27 |
|
27 | |||
28 | DEFAULT_LANDING_REF = 'rev:tip' |
|
28 | DEFAULT_LANDING_REF = 'rev:tip' | |
29 |
|
29 | |||
30 |
|
30 | |||
31 | def get_group_and_repo(repo_name): |
|
31 | def get_group_and_repo(repo_name): | |
32 | from rhodecode.model.repo_group import RepoGroupModel |
|
32 | from rhodecode.model.repo_group import RepoGroupModel | |
33 | return RepoGroupModel()._get_group_name_and_parent( |
|
33 | return RepoGroupModel()._get_group_name_and_parent( | |
34 | repo_name, get_object=True) |
|
34 | repo_name, get_object=True) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | def get_repo_group(repo_group_id): |
|
37 | def get_repo_group(repo_group_id): | |
38 | from rhodecode.model.repo_group import RepoGroup |
|
38 | from rhodecode.model.repo_group import RepoGroup | |
39 | return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR |
|
39 | return RepoGroup.get(repo_group_id), RepoGroup.CHOICES_SEPARATOR | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | @colander.deferred |
|
42 | @colander.deferred | |
43 | def deferred_repo_type_validator(node, kw): |
|
43 | def deferred_repo_type_validator(node, kw): | |
44 | options = kw.get('repo_type_options', []) |
|
44 | options = kw.get('repo_type_options', []) | |
45 | return colander.OneOf([x for x in options]) |
|
45 | return colander.OneOf([x for x in options]) | |
46 |
|
46 | |||
47 |
|
47 | |||
48 | @colander.deferred |
|
48 | @colander.deferred | |
49 | def deferred_repo_owner_validator(node, kw): |
|
49 | def deferred_repo_owner_validator(node, kw): | |
50 |
|
50 | |||
51 | def repo_owner_validator(node, value): |
|
51 | def repo_owner_validator(node, value): | |
52 | from rhodecode.model.db import User |
|
52 | from rhodecode.model.db import User | |
53 | existing = User.get_by_username(value) |
|
53 | existing = User.get_by_username(value) | |
54 | if not existing: |
|
54 | if not existing: | |
55 | msg = _(u'Repo owner with id `{}` does not exists').format(value) |
|
55 | msg = _(u'Repo owner with id `{}` does not exists').format(value) | |
56 | raise colander.Invalid(node, msg) |
|
56 | raise colander.Invalid(node, msg) | |
57 |
|
57 | |||
58 | return repo_owner_validator |
|
58 | return repo_owner_validator | |
59 |
|
59 | |||
60 |
|
60 | |||
61 | @colander.deferred |
|
61 | @colander.deferred | |
62 | def deferred_landing_ref_validator(node, kw): |
|
62 | def deferred_landing_ref_validator(node, kw): | |
63 | options = kw.get( |
|
63 | options = kw.get( | |
64 | 'repo_ref_options', [DEFAULT_LANDING_REF]) |
|
64 | 'repo_ref_options', [DEFAULT_LANDING_REF]) | |
65 | return colander.OneOf([x for x in options]) |
|
65 | return colander.OneOf([x for x in options]) | |
66 |
|
66 | |||
67 |
|
67 | |||
68 | @colander.deferred |
|
68 | @colander.deferred | |
|
69 | def deferred_clone_uri_validator(node, kw): | |||
|
70 | repo_type = kw.get('repo_type') | |||
|
71 | validator = validators.CloneUriValidator(repo_type) | |||
|
72 | return validator | |||
|
73 | ||||
|
74 | ||||
|
75 | @colander.deferred | |||
69 | def deferred_landing_ref_widget(node, kw): |
|
76 | def deferred_landing_ref_widget(node, kw): | |
70 | items = kw.get( |
|
77 | items = kw.get( | |
71 | 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)]) |
|
78 | 'repo_ref_items', [(DEFAULT_LANDING_REF, DEFAULT_LANDING_REF)]) | |
72 | items = convert_to_optgroup(items) |
|
79 | items = convert_to_optgroup(items) | |
73 | return deform.widget.Select2Widget(values=items) |
|
80 | return deform.widget.Select2Widget(values=items) | |
74 |
|
81 | |||
75 |
|
82 | |||
76 | @colander.deferred |
|
83 | @colander.deferred | |
77 | def deferred_fork_of_validator(node, kw): |
|
84 | def deferred_fork_of_validator(node, kw): | |
78 | old_values = kw.get('old_values') or {} |
|
85 | old_values = kw.get('old_values') or {} | |
79 |
|
86 | |||
80 | def fork_of_validator(node, value): |
|
87 | def fork_of_validator(node, value): | |
81 | from rhodecode.model.db import Repository, RepoGroup |
|
88 | from rhodecode.model.db import Repository, RepoGroup | |
82 | existing = Repository.get_by_repo_name(value) |
|
89 | existing = Repository.get_by_repo_name(value) | |
83 | if not existing: |
|
90 | if not existing: | |
84 | msg = _(u'Fork with id `{}` does not exists').format(value) |
|
91 | msg = _(u'Fork with id `{}` does not exists').format(value) | |
85 | raise colander.Invalid(node, msg) |
|
92 | raise colander.Invalid(node, msg) | |
86 | elif old_values['repo_name'] == existing.repo_name: |
|
93 | elif old_values['repo_name'] == existing.repo_name: | |
87 | msg = _(u'Cannot set fork of ' |
|
94 | msg = _(u'Cannot set fork of ' | |
88 | u'parameter of this repository to itself').format(value) |
|
95 | u'parameter of this repository to itself').format(value) | |
89 | raise colander.Invalid(node, msg) |
|
96 | raise colander.Invalid(node, msg) | |
90 |
|
97 | |||
91 | return fork_of_validator |
|
98 | return fork_of_validator | |
92 |
|
99 | |||
93 |
|
100 | |||
94 | @colander.deferred |
|
101 | @colander.deferred | |
95 | def deferred_can_write_to_group_validator(node, kw): |
|
102 | def deferred_can_write_to_group_validator(node, kw): | |
96 | request_user = kw.get('user') |
|
103 | request_user = kw.get('user') | |
97 | old_values = kw.get('old_values') or {} |
|
104 | old_values = kw.get('old_values') or {} | |
98 |
|
105 | |||
99 | def can_write_to_group_validator(node, value): |
|
106 | def can_write_to_group_validator(node, value): | |
100 | """ |
|
107 | """ | |
101 | Checks if given repo path is writable by user. This includes checks if |
|
108 | Checks if given repo path is writable by user. This includes checks if | |
102 | user is allowed to create repositories under root path or under |
|
109 | user is allowed to create repositories under root path or under | |
103 | repo group paths |
|
110 | repo group paths | |
104 | """ |
|
111 | """ | |
105 |
|
112 | |||
106 | from rhodecode.lib.auth import ( |
|
113 | from rhodecode.lib.auth import ( | |
107 | HasPermissionAny, HasRepoGroupPermissionAny) |
|
114 | HasPermissionAny, HasRepoGroupPermissionAny) | |
108 | from rhodecode.model.repo_group import RepoGroupModel |
|
115 | from rhodecode.model.repo_group import RepoGroupModel | |
109 |
|
116 | |||
110 | messages = { |
|
117 | messages = { | |
111 | 'invalid_repo_group': |
|
118 | 'invalid_repo_group': | |
112 | _(u"Repository group `{}` does not exist"), |
|
119 | _(u"Repository group `{}` does not exist"), | |
113 | # permissions denied we expose as not existing, to prevent |
|
120 | # permissions denied we expose as not existing, to prevent | |
114 | # resource discovery |
|
121 | # resource discovery | |
115 | 'permission_denied': |
|
122 | 'permission_denied': | |
116 | _(u"Repository group `{}` does not exist"), |
|
123 | _(u"Repository group `{}` does not exist"), | |
117 | 'permission_denied_root': |
|
124 | 'permission_denied_root': | |
118 | _(u"You do not have the permission to store " |
|
125 | _(u"You do not have the permission to store " | |
119 | u"repositories in the root location.") |
|
126 | u"repositories in the root location.") | |
120 | } |
|
127 | } | |
121 |
|
128 | |||
122 | value = value['repo_group_name'] |
|
129 | value = value['repo_group_name'] | |
123 |
|
130 | |||
124 | is_root_location = value is types.RootLocation |
|
131 | is_root_location = value is types.RootLocation | |
125 | # NOT initialized validators, we must call them |
|
132 | # NOT initialized validators, we must call them | |
126 | can_create_repos_at_root = HasPermissionAny( |
|
133 | can_create_repos_at_root = HasPermissionAny( | |
127 | 'hg.admin', 'hg.create.repository') |
|
134 | 'hg.admin', 'hg.create.repository') | |
128 |
|
135 | |||
129 | # if values is root location, we simply need to check if we can write |
|
136 | # if values is root location, we simply need to check if we can write | |
130 | # to root location ! |
|
137 | # to root location ! | |
131 | if is_root_location: |
|
138 | if is_root_location: | |
132 | if can_create_repos_at_root(user=request_user): |
|
139 | if can_create_repos_at_root(user=request_user): | |
133 | # we can create repo group inside tool-level. No more checks |
|
140 | # we can create repo group inside tool-level. No more checks | |
134 | # are required |
|
141 | # are required | |
135 | return |
|
142 | return | |
136 | else: |
|
143 | else: | |
137 | # "fake" node name as repo_name, otherwise we oddly report |
|
144 | # "fake" node name as repo_name, otherwise we oddly report | |
138 | # the error as if it was coming form repo_group |
|
145 | # the error as if it was coming form repo_group | |
139 | # however repo_group is empty when using root location. |
|
146 | # however repo_group is empty when using root location. | |
140 | node.name = 'repo_name' |
|
147 | node.name = 'repo_name' | |
141 | raise colander.Invalid(node, messages['permission_denied_root']) |
|
148 | raise colander.Invalid(node, messages['permission_denied_root']) | |
142 |
|
149 | |||
143 | # parent group not exists ? throw an error |
|
150 | # parent group not exists ? throw an error | |
144 | repo_group = RepoGroupModel().get_by_group_name(value) |
|
151 | repo_group = RepoGroupModel().get_by_group_name(value) | |
145 | if value and not repo_group: |
|
152 | if value and not repo_group: | |
146 | raise colander.Invalid( |
|
153 | raise colander.Invalid( | |
147 | node, messages['invalid_repo_group'].format(value)) |
|
154 | node, messages['invalid_repo_group'].format(value)) | |
148 |
|
155 | |||
149 | gr_name = repo_group.group_name |
|
156 | gr_name = repo_group.group_name | |
150 |
|
157 | |||
151 | # create repositories with write permission on group is set to true |
|
158 | # create repositories with write permission on group is set to true | |
152 | create_on_write = HasPermissionAny( |
|
159 | create_on_write = HasPermissionAny( | |
153 | 'hg.create.write_on_repogroup.true')(user=request_user) |
|
160 | 'hg.create.write_on_repogroup.true')(user=request_user) | |
154 |
|
161 | |||
155 | group_admin = HasRepoGroupPermissionAny('group.admin')( |
|
162 | group_admin = HasRepoGroupPermissionAny('group.admin')( | |
156 | gr_name, 'can write into group validator', user=request_user) |
|
163 | gr_name, 'can write into group validator', user=request_user) | |
157 | group_write = HasRepoGroupPermissionAny('group.write')( |
|
164 | group_write = HasRepoGroupPermissionAny('group.write')( | |
158 | gr_name, 'can write into group validator', user=request_user) |
|
165 | gr_name, 'can write into group validator', user=request_user) | |
159 |
|
166 | |||
160 | forbidden = not (group_admin or (group_write and create_on_write)) |
|
167 | forbidden = not (group_admin or (group_write and create_on_write)) | |
161 |
|
168 | |||
162 | # TODO: handling of old values, and detecting no-change in path |
|
169 | # TODO: handling of old values, and detecting no-change in path | |
163 | # to skip permission checks in such cases. This only needs to be |
|
170 | # to skip permission checks in such cases. This only needs to be | |
164 | # implemented if we use this schema in forms as well |
|
171 | # implemented if we use this schema in forms as well | |
165 |
|
172 | |||
166 | # gid = (old_data['repo_group'].get('group_id') |
|
173 | # gid = (old_data['repo_group'].get('group_id') | |
167 | # if (old_data and 'repo_group' in old_data) else None) |
|
174 | # if (old_data and 'repo_group' in old_data) else None) | |
168 | # value_changed = gid != safe_int(value) |
|
175 | # value_changed = gid != safe_int(value) | |
169 | # new = not old_data |
|
176 | # new = not old_data | |
170 |
|
177 | |||
171 | # do check if we changed the value, there's a case that someone got |
|
178 | # do check if we changed the value, there's a case that someone got | |
172 | # revoked write permissions to a repository, he still created, we |
|
179 | # revoked write permissions to a repository, he still created, we | |
173 | # don't need to check permission if he didn't change the value of |
|
180 | # don't need to check permission if he didn't change the value of | |
174 | # groups in form box |
|
181 | # groups in form box | |
175 | # if value_changed or new: |
|
182 | # if value_changed or new: | |
176 | # # parent group need to be existing |
|
183 | # # parent group need to be existing | |
177 | # TODO: ENDS HERE |
|
184 | # TODO: ENDS HERE | |
178 |
|
185 | |||
179 | if repo_group and forbidden: |
|
186 | if repo_group and forbidden: | |
180 | msg = messages['permission_denied'].format(value) |
|
187 | msg = messages['permission_denied'].format(value) | |
181 | raise colander.Invalid(node, msg) |
|
188 | raise colander.Invalid(node, msg) | |
182 |
|
189 | |||
183 | return can_write_to_group_validator |
|
190 | return can_write_to_group_validator | |
184 |
|
191 | |||
185 |
|
192 | |||
186 | @colander.deferred |
|
193 | @colander.deferred | |
187 | def deferred_unique_name_validator(node, kw): |
|
194 | def deferred_unique_name_validator(node, kw): | |
188 | request_user = kw.get('user') |
|
195 | request_user = kw.get('user') | |
189 | old_values = kw.get('old_values') or {} |
|
196 | old_values = kw.get('old_values') or {} | |
190 |
|
197 | |||
191 | def unique_name_validator(node, value): |
|
198 | def unique_name_validator(node, value): | |
192 | from rhodecode.model.db import Repository, RepoGroup |
|
199 | from rhodecode.model.db import Repository, RepoGroup | |
193 | name_changed = value != old_values.get('repo_name') |
|
200 | name_changed = value != old_values.get('repo_name') | |
194 |
|
201 | |||
195 | existing = Repository.get_by_repo_name(value) |
|
202 | existing = Repository.get_by_repo_name(value) | |
196 | if name_changed and existing: |
|
203 | if name_changed and existing: | |
197 | msg = _(u'Repository with name `{}` already exists').format(value) |
|
204 | msg = _(u'Repository with name `{}` already exists').format(value) | |
198 | raise colander.Invalid(node, msg) |
|
205 | raise colander.Invalid(node, msg) | |
199 |
|
206 | |||
200 | existing_group = RepoGroup.get_by_group_name(value) |
|
207 | existing_group = RepoGroup.get_by_group_name(value) | |
201 | if name_changed and existing_group: |
|
208 | if name_changed and existing_group: | |
202 | msg = _(u'Repository group with name `{}` already exists').format( |
|
209 | msg = _(u'Repository group with name `{}` already exists').format( | |
203 | value) |
|
210 | value) | |
204 | raise colander.Invalid(node, msg) |
|
211 | raise colander.Invalid(node, msg) | |
205 | return unique_name_validator |
|
212 | return unique_name_validator | |
206 |
|
213 | |||
207 |
|
214 | |||
208 | @colander.deferred |
|
215 | @colander.deferred | |
209 | def deferred_repo_name_validator(node, kw): |
|
216 | def deferred_repo_name_validator(node, kw): | |
210 | def no_git_suffix_validator(node, value): |
|
217 | def no_git_suffix_validator(node, value): | |
211 | if value.endswith('.git'): |
|
218 | if value.endswith('.git'): | |
212 | msg = _('Repository name cannot end with .git') |
|
219 | msg = _('Repository name cannot end with .git') | |
213 | raise colander.Invalid(node, msg) |
|
220 | raise colander.Invalid(node, msg) | |
214 | return colander.All( |
|
221 | return colander.All( | |
215 | no_git_suffix_validator, validators.valid_name_validator) |
|
222 | no_git_suffix_validator, validators.valid_name_validator) | |
216 |
|
223 | |||
217 |
|
224 | |||
218 | @colander.deferred |
|
225 | @colander.deferred | |
219 | def deferred_repo_group_validator(node, kw): |
|
226 | def deferred_repo_group_validator(node, kw): | |
220 | options = kw.get( |
|
227 | options = kw.get( | |
221 | 'repo_repo_group_options') |
|
228 | 'repo_repo_group_options') | |
222 | return colander.OneOf([x for x in options]) |
|
229 | return colander.OneOf([x for x in options]) | |
223 |
|
230 | |||
224 |
|
231 | |||
225 | @colander.deferred |
|
232 | @colander.deferred | |
226 | def deferred_repo_group_widget(node, kw): |
|
233 | def deferred_repo_group_widget(node, kw): | |
227 | items = kw.get('repo_repo_group_items') |
|
234 | items = kw.get('repo_repo_group_items') | |
228 | return deform.widget.Select2Widget(values=items) |
|
235 | return deform.widget.Select2Widget(values=items) | |
229 |
|
236 | |||
230 |
|
237 | |||
231 | class GroupType(colander.Mapping): |
|
238 | class GroupType(colander.Mapping): | |
232 | def _validate(self, node, value): |
|
239 | def _validate(self, node, value): | |
233 | try: |
|
240 | try: | |
234 | return dict(repo_group_name=value) |
|
241 | return dict(repo_group_name=value) | |
235 | except Exception as e: |
|
242 | except Exception as e: | |
236 | raise colander.Invalid( |
|
243 | raise colander.Invalid( | |
237 | node, '"${val}" is not a mapping type: ${err}'.format( |
|
244 | node, '"${val}" is not a mapping type: ${err}'.format( | |
238 | val=value, err=e)) |
|
245 | val=value, err=e)) | |
239 |
|
246 | |||
240 | def deserialize(self, node, cstruct): |
|
247 | def deserialize(self, node, cstruct): | |
241 | if cstruct is colander.null: |
|
248 | if cstruct is colander.null: | |
242 | return cstruct |
|
249 | return cstruct | |
243 |
|
250 | |||
244 | appstruct = super(GroupType, self).deserialize(node, cstruct) |
|
251 | appstruct = super(GroupType, self).deserialize(node, cstruct) | |
245 | validated_name = appstruct['repo_group_name'] |
|
252 | validated_name = appstruct['repo_group_name'] | |
246 |
|
253 | |||
247 | # inject group based on once deserialized data |
|
254 | # inject group based on once deserialized data | |
248 | (repo_name_without_group, |
|
255 | (repo_name_without_group, | |
249 | parent_group_name, |
|
256 | parent_group_name, | |
250 | parent_group) = get_group_and_repo(validated_name) |
|
257 | parent_group) = get_group_and_repo(validated_name) | |
251 |
|
258 | |||
252 | appstruct['repo_name_with_group'] = validated_name |
|
259 | appstruct['repo_name_with_group'] = validated_name | |
253 | appstruct['repo_name_without_group'] = repo_name_without_group |
|
260 | appstruct['repo_name_without_group'] = repo_name_without_group | |
254 | appstruct['repo_group_name'] = parent_group_name or types.RootLocation |
|
261 | appstruct['repo_group_name'] = parent_group_name or types.RootLocation | |
255 |
|
262 | |||
256 | if parent_group: |
|
263 | if parent_group: | |
257 | appstruct['repo_group_id'] = parent_group.group_id |
|
264 | appstruct['repo_group_id'] = parent_group.group_id | |
258 |
|
265 | |||
259 | return appstruct |
|
266 | return appstruct | |
260 |
|
267 | |||
261 |
|
268 | |||
262 | class GroupSchema(colander.SchemaNode): |
|
269 | class GroupSchema(colander.SchemaNode): | |
263 | schema_type = GroupType |
|
270 | schema_type = GroupType | |
264 | validator = deferred_can_write_to_group_validator |
|
271 | validator = deferred_can_write_to_group_validator | |
265 | missing = colander.null |
|
272 | missing = colander.null | |
266 |
|
273 | |||
267 |
|
274 | |||
268 | class RepoGroup(GroupSchema): |
|
275 | class RepoGroup(GroupSchema): | |
269 | repo_group_name = colander.SchemaNode( |
|
276 | repo_group_name = colander.SchemaNode( | |
270 | types.GroupNameType()) |
|
277 | types.GroupNameType()) | |
271 | repo_group_id = colander.SchemaNode( |
|
278 | repo_group_id = colander.SchemaNode( | |
272 | colander.String(), missing=None) |
|
279 | colander.String(), missing=None) | |
273 | repo_name_without_group = colander.SchemaNode( |
|
280 | repo_name_without_group = colander.SchemaNode( | |
274 | colander.String(), missing=None) |
|
281 | colander.String(), missing=None) | |
275 |
|
282 | |||
276 |
|
283 | |||
277 | class RepoGroupAccessSchema(colander.MappingSchema): |
|
284 | class RepoGroupAccessSchema(colander.MappingSchema): | |
278 | repo_group = RepoGroup() |
|
285 | repo_group = RepoGroup() | |
279 |
|
286 | |||
280 |
|
287 | |||
281 | class RepoNameUniqueSchema(colander.MappingSchema): |
|
288 | class RepoNameUniqueSchema(colander.MappingSchema): | |
282 | unique_repo_name = colander.SchemaNode( |
|
289 | unique_repo_name = colander.SchemaNode( | |
283 | colander.String(), |
|
290 | colander.String(), | |
284 | validator=deferred_unique_name_validator) |
|
291 | validator=deferred_unique_name_validator) | |
285 |
|
292 | |||
286 |
|
293 | |||
287 | class RepoSchema(colander.MappingSchema): |
|
294 | class RepoSchema(colander.MappingSchema): | |
288 |
|
295 | |||
289 | repo_name = colander.SchemaNode( |
|
296 | repo_name = colander.SchemaNode( | |
290 | types.RepoNameType(), |
|
297 | types.RepoNameType(), | |
291 | validator=deferred_repo_name_validator) |
|
298 | validator=deferred_repo_name_validator) | |
292 |
|
299 | |||
293 | repo_type = colander.SchemaNode( |
|
300 | repo_type = colander.SchemaNode( | |
294 | colander.String(), |
|
301 | colander.String(), | |
295 | validator=deferred_repo_type_validator) |
|
302 | validator=deferred_repo_type_validator) | |
296 |
|
303 | |||
297 | repo_owner = colander.SchemaNode( |
|
304 | repo_owner = colander.SchemaNode( | |
298 | colander.String(), |
|
305 | colander.String(), | |
299 | validator=deferred_repo_owner_validator, |
|
306 | validator=deferred_repo_owner_validator, | |
300 | widget=deform.widget.TextInputWidget()) |
|
307 | widget=deform.widget.TextInputWidget()) | |
301 |
|
308 | |||
302 | repo_description = colander.SchemaNode( |
|
309 | repo_description = colander.SchemaNode( | |
303 | colander.String(), missing='', |
|
310 | colander.String(), missing='', | |
304 | widget=deform.widget.TextAreaWidget()) |
|
311 | widget=deform.widget.TextAreaWidget()) | |
305 |
|
312 | |||
306 | repo_landing_commit_ref = colander.SchemaNode( |
|
313 | repo_landing_commit_ref = colander.SchemaNode( | |
307 | colander.String(), |
|
314 | colander.String(), | |
308 | validator=deferred_landing_ref_validator, |
|
315 | validator=deferred_landing_ref_validator, | |
309 | preparers=[preparers.strip_preparer], |
|
316 | preparers=[preparers.strip_preparer], | |
310 | missing=DEFAULT_LANDING_REF, |
|
317 | missing=DEFAULT_LANDING_REF, | |
311 | widget=deferred_landing_ref_widget) |
|
318 | widget=deferred_landing_ref_widget) | |
312 |
|
319 | |||
313 | repo_clone_uri = colander.SchemaNode( |
|
320 | repo_clone_uri = colander.SchemaNode( | |
314 | colander.String(), |
|
321 | colander.String(), | |
315 | validator=colander.All(colander.Length(min=1)), |
|
322 | validator=colander.All(colander.Length(min=1)), | |
316 | preparers=[preparers.strip_preparer], |
|
323 | preparers=[preparers.strip_preparer], | |
317 | missing='') |
|
324 | missing='') | |
318 |
|
325 | |||
319 | repo_fork_of = colander.SchemaNode( |
|
326 | repo_fork_of = colander.SchemaNode( | |
320 | colander.String(), |
|
327 | colander.String(), | |
321 | validator=deferred_fork_of_validator, |
|
328 | validator=deferred_fork_of_validator, | |
322 | missing=None) |
|
329 | missing=None) | |
323 |
|
330 | |||
324 | repo_private = colander.SchemaNode( |
|
331 | repo_private = colander.SchemaNode( | |
325 | types.StringBooleanType(), |
|
332 | types.StringBooleanType(), | |
326 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
333 | missing=False, widget=deform.widget.CheckboxWidget()) | |
327 | repo_copy_permissions = colander.SchemaNode( |
|
334 | repo_copy_permissions = colander.SchemaNode( | |
328 | types.StringBooleanType(), |
|
335 | types.StringBooleanType(), | |
329 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
336 | missing=False, widget=deform.widget.CheckboxWidget()) | |
330 | repo_enable_statistics = colander.SchemaNode( |
|
337 | repo_enable_statistics = colander.SchemaNode( | |
331 | types.StringBooleanType(), |
|
338 | types.StringBooleanType(), | |
332 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
339 | missing=False, widget=deform.widget.CheckboxWidget()) | |
333 | repo_enable_downloads = colander.SchemaNode( |
|
340 | repo_enable_downloads = colander.SchemaNode( | |
334 | types.StringBooleanType(), |
|
341 | types.StringBooleanType(), | |
335 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
342 | missing=False, widget=deform.widget.CheckboxWidget()) | |
336 | repo_enable_locking = colander.SchemaNode( |
|
343 | repo_enable_locking = colander.SchemaNode( | |
337 | types.StringBooleanType(), |
|
344 | types.StringBooleanType(), | |
338 | missing=False, widget=deform.widget.CheckboxWidget()) |
|
345 | missing=False, widget=deform.widget.CheckboxWidget()) | |
339 |
|
346 | |||
340 | def deserialize(self, cstruct): |
|
347 | def deserialize(self, cstruct): | |
341 | """ |
|
348 | """ | |
342 | Custom deserialize that allows to chain validation, and verify |
|
349 | Custom deserialize that allows to chain validation, and verify | |
343 | permissions, and as last step uniqueness |
|
350 | permissions, and as last step uniqueness | |
344 | """ |
|
351 | """ | |
345 |
|
352 | |||
346 | # first pass, to validate given data |
|
353 | # first pass, to validate given data | |
347 | appstruct = super(RepoSchema, self).deserialize(cstruct) |
|
354 | appstruct = super(RepoSchema, self).deserialize(cstruct) | |
348 | validated_name = appstruct['repo_name'] |
|
355 | validated_name = appstruct['repo_name'] | |
349 |
|
356 | |||
350 | # second pass to validate permissions to repo_group |
|
357 | # second pass to validate permissions to repo_group | |
351 | second = RepoGroupAccessSchema().bind(**self.bindings) |
|
358 | second = RepoGroupAccessSchema().bind(**self.bindings) | |
352 | appstruct_second = second.deserialize({'repo_group': validated_name}) |
|
359 | appstruct_second = second.deserialize({'repo_group': validated_name}) | |
353 | # save result |
|
360 | # save result | |
354 | appstruct['repo_group'] = appstruct_second['repo_group'] |
|
361 | appstruct['repo_group'] = appstruct_second['repo_group'] | |
355 |
|
362 | |||
356 | # thirds to validate uniqueness |
|
363 | # thirds to validate uniqueness | |
357 | third = RepoNameUniqueSchema().bind(**self.bindings) |
|
364 | third = RepoNameUniqueSchema().bind(**self.bindings) | |
358 | third.deserialize({'unique_repo_name': validated_name}) |
|
365 | third.deserialize({'unique_repo_name': validated_name}) | |
359 |
|
366 | |||
360 | return appstruct |
|
367 | return appstruct | |
|
368 | ||||
|
369 | ||||
|
370 | class RepoSettingsSchema(RepoSchema): | |||
|
371 | repo_group = colander.SchemaNode( | |||
|
372 | colander.Integer(), | |||
|
373 | validator=deferred_repo_group_validator, | |||
|
374 | widget=deferred_repo_group_widget, | |||
|
375 | missing='') | |||
|
376 | ||||
|
377 | repo_clone_uri_change = colander.SchemaNode( | |||
|
378 | colander.String(), | |||
|
379 | missing='NEW') | |||
|
380 | ||||
|
381 | repo_clone_uri = colander.SchemaNode( | |||
|
382 | colander.String(), | |||
|
383 | preparers=[preparers.strip_preparer], | |||
|
384 | validator=deferred_clone_uri_validator, | |||
|
385 | missing='') | |||
|
386 | ||||
|
387 | def deserialize(self, cstruct): | |||
|
388 | """ | |||
|
389 | Custom deserialize that allows to chain validation, and verify | |||
|
390 | permissions, and as last step uniqueness | |||
|
391 | """ | |||
|
392 | ||||
|
393 | # first pass, to validate given data | |||
|
394 | appstruct = super(RepoSchema, self).deserialize(cstruct) | |||
|
395 | validated_name = appstruct['repo_name'] | |||
|
396 | # because of repoSchema adds repo-group as an ID, we inject it as | |||
|
397 | # full name here because validators require it, it's unwrapped later | |||
|
398 | # so it's safe to use and final name is going to be without group anyway | |||
|
399 | ||||
|
400 | group, separator = get_repo_group(appstruct['repo_group']) | |||
|
401 | if group: | |||
|
402 | validated_name = separator.join([group.group_name, validated_name]) | |||
|
403 | ||||
|
404 | # second pass to validate permissions to repo_group | |||
|
405 | second = RepoGroupAccessSchema().bind(**self.bindings) | |||
|
406 | appstruct_second = second.deserialize({'repo_group': validated_name}) | |||
|
407 | # save result | |||
|
408 | appstruct['repo_group'] = appstruct_second['repo_group'] | |||
|
409 | ||||
|
410 | # thirds to validate uniqueness | |||
|
411 | third = RepoNameUniqueSchema().bind(**self.bindings) | |||
|
412 | third.deserialize({'unique_repo_name': validated_name}) | |||
|
413 | ||||
|
414 | return appstruct |
@@ -1,48 +1,140 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2011-2017 RhodeCode GmbH | |||
|
4 | # | |||
|
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 | |||
|
7 | # (only), as published by the Free Software Foundation. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
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/>. | |||
|
16 | # | |||
|
17 | # This program is dual-licensed. If you wish to learn more about the | |||
|
18 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
20 | ||||
1 | import os |
|
21 | import os | |
2 | import re |
|
22 | import re | |
|
23 | import logging | |||
|
24 | ||||
3 |
|
25 | |||
4 | import ipaddress |
|
26 | import ipaddress | |
5 | import colander |
|
27 | import colander | |
6 |
|
28 | |||
7 | from rhodecode.translation import _ |
|
29 | from rhodecode.translation import _ | |
8 | from rhodecode.lib.utils2 import glob2re |
|
30 | from rhodecode.lib.utils2 import glob2re | |
9 |
|
31 | |||
|
32 | log = logging.getLogger(__name__) | |||
|
33 | ||||
10 |
|
34 | |||
11 | def ip_addr_validator(node, value): |
|
35 | def ip_addr_validator(node, value): | |
12 | try: |
|
36 | try: | |
13 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
37 | # this raises an ValueError if address is not IpV4 or IpV6 | |
14 | ipaddress.ip_network(value, strict=False) |
|
38 | ipaddress.ip_network(value, strict=False) | |
15 | except ValueError: |
|
39 | except ValueError: | |
16 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
40 | msg = _(u'Please enter a valid IPv4 or IpV6 address') | |
17 | raise colander.Invalid(node, msg) |
|
41 | raise colander.Invalid(node, msg) | |
18 |
|
42 | |||
19 |
|
43 | |||
20 | class IpAddrValidator(object): |
|
44 | class IpAddrValidator(object): | |
21 | def __init__(self, strict=True): |
|
45 | def __init__(self, strict=True): | |
22 | self.strict = strict |
|
46 | self.strict = strict | |
23 |
|
47 | |||
24 | def __call__(self, node, value): |
|
48 | def __call__(self, node, value): | |
25 | try: |
|
49 | try: | |
26 | # this raises an ValueError if address is not IpV4 or IpV6 |
|
50 | # this raises an ValueError if address is not IpV4 or IpV6 | |
27 | ipaddress.ip_network(value, strict=self.strict) |
|
51 | ipaddress.ip_network(value, strict=self.strict) | |
28 | except ValueError: |
|
52 | except ValueError: | |
29 | msg = _(u'Please enter a valid IPv4 or IpV6 address') |
|
53 | msg = _(u'Please enter a valid IPv4 or IpV6 address') | |
30 | raise colander.Invalid(node, msg) |
|
54 | raise colander.Invalid(node, msg) | |
31 |
|
55 | |||
32 |
|
56 | |||
33 | def glob_validator(node, value): |
|
57 | def glob_validator(node, value): | |
34 | try: |
|
58 | try: | |
35 | re.compile('^' + glob2re(value) + '$') |
|
59 | re.compile('^' + glob2re(value) + '$') | |
36 | except Exception: |
|
60 | except Exception: | |
37 | msg = _(u'Invalid glob pattern') |
|
61 | msg = _(u'Invalid glob pattern') | |
38 | raise colander.Invalid(node, msg) |
|
62 | raise colander.Invalid(node, msg) | |
39 |
|
63 | |||
40 |
|
64 | |||
41 | def valid_name_validator(node, value): |
|
65 | def valid_name_validator(node, value): | |
42 | from rhodecode.model.validation_schema import types |
|
66 | from rhodecode.model.validation_schema import types | |
43 | if value is types.RootLocation: |
|
67 | if value is types.RootLocation: | |
44 | return |
|
68 | return | |
45 |
|
69 | |||
46 | msg = _('Name must start with a letter or number. Got `{}`').format(value) |
|
70 | msg = _('Name must start with a letter or number. Got `{}`').format(value) | |
47 | if not re.match(r'^[a-zA-z0-9]{1,}', value): |
|
71 | if not re.match(r'^[a-zA-z0-9]{1,}', value): | |
48 | raise colander.Invalid(node, msg) |
|
72 | raise colander.Invalid(node, msg) | |
|
73 | ||||
|
74 | ||||
|
75 | class InvalidCloneUrl(Exception): | |||
|
76 | allowed_prefixes = () | |||
|
77 | ||||
|
78 | ||||
|
79 | def url_validator(url, repo_type, config): | |||
|
80 | from rhodecode.lib.vcs.backends.hg import MercurialRepository | |||
|
81 | from rhodecode.lib.vcs.backends.git import GitRepository | |||
|
82 | from rhodecode.lib.vcs.backends.svn import SubversionRepository | |||
|
83 | ||||
|
84 | if repo_type == 'hg': | |||
|
85 | allowed_prefixes = ('http', 'svn+http', 'git+http') | |||
|
86 | ||||
|
87 | if 'http' in url[:4]: | |||
|
88 | # initially check if it's at least the proper URL | |||
|
89 | # or does it pass basic auth | |||
|
90 | ||||
|
91 | MercurialRepository.check_url(url, config) | |||
|
92 | elif 'svn+http' in url[:8]: # svn->hg import | |||
|
93 | SubversionRepository.check_url(url, config) | |||
|
94 | elif 'git+http' in url[:8]: # git->hg import | |||
|
95 | raise NotImplementedError() | |||
|
96 | else: | |||
|
97 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |||
|
98 | 'Allowed url must start with one of %s' | |||
|
99 | % (url, ','.join(allowed_prefixes))) | |||
|
100 | exc.allowed_prefixes = allowed_prefixes | |||
|
101 | raise exc | |||
|
102 | ||||
|
103 | elif repo_type == 'git': | |||
|
104 | allowed_prefixes = ('http', 'svn+http', 'hg+http') | |||
|
105 | if 'http' in url[:4]: | |||
|
106 | # initially check if it's at least the proper URL | |||
|
107 | # or does it pass basic auth | |||
|
108 | GitRepository.check_url(url, config) | |||
|
109 | elif 'svn+http' in url[:8]: # svn->git import | |||
|
110 | raise NotImplementedError() | |||
|
111 | elif 'hg+http' in url[:8]: # hg->git import | |||
|
112 | raise NotImplementedError() | |||
|
113 | else: | |||
|
114 | exc = InvalidCloneUrl('Clone from URI %s not allowed. ' | |||
|
115 | 'Allowed url must start with one of %s' | |||
|
116 | % (url, ','.join(allowed_prefixes))) | |||
|
117 | exc.allowed_prefixes = allowed_prefixes | |||
|
118 | raise exc | |||
|
119 | ||||
|
120 | ||||
|
121 | class CloneUriValidator(object): | |||
|
122 | def __init__(self, repo_type): | |||
|
123 | self.repo_type = repo_type | |||
|
124 | ||||
|
125 | def __call__(self, node, value): | |||
|
126 | from rhodecode.lib.utils import make_db_config | |||
|
127 | try: | |||
|
128 | config = make_db_config(clear_session=False) | |||
|
129 | url_validator(value, self.repo_type, config) | |||
|
130 | except InvalidCloneUrl as e: | |||
|
131 | log.warning(e) | |||
|
132 | msg = _(u'Invalid clone url, provide a valid clone ' | |||
|
133 | u'url starting with one of {allowed_prefixes}').format( | |||
|
134 | allowed_prefixes=e.allowed_prefixes) | |||
|
135 | raise colander.Invalid(node, msg) | |||
|
136 | except Exception: | |||
|
137 | log.exception('Url validation failed') | |||
|
138 | msg = _(u'invalid clone url for {repo_type} repository').format( | |||
|
139 | repo_type=self.repo_type) | |||
|
140 | raise colander.Invalid(node, msg) |
@@ -1,249 +1,260 b'' | |||||
1 | ## -*- coding: utf-8 -*- |
|
1 | ## -*- coding: utf-8 -*- | |
2 | <%namespace name="base" file="/base/base.mako"/> |
|
2 | <%namespace name="base" file="/base/base.mako"/> | |
3 |
|
3 | |||
4 | <div class="panel panel-default"> |
|
4 | <div class="panel panel-default"> | |
5 | <div class="panel-heading"> |
|
5 | <div class="panel-heading"> | |
6 | <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3> |
|
6 | <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3> | |
7 | </div> |
|
7 | </div> | |
8 | <div class="panel-body"> |
|
8 | <div class="panel-body"> | |
9 | ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')} |
|
9 | ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')} | |
10 | <div class="form"> |
|
10 | <div class="form"> | |
11 | <!-- fields --> |
|
11 | <!-- fields --> | |
12 | <div class="fields"> |
|
12 | <div class="fields"> | |
13 |
|
13 | |||
14 | <div class="field"> |
|
14 | <div class="field"> | |
15 | <div class="label"> |
|
15 | <div class="label"> | |
16 | <label for="repo_name">${_('Name')}:</label> |
|
16 | <label for="repo_name">${_('Name')}:</label> | |
17 | </div> |
|
17 | </div> | |
18 | <div class="input"> |
|
18 | <div class="input"> | |
19 | ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n} |
|
19 | ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n} | |
20 | ${c.form.render_error(request, c.form['repo_name'])|n} |
|
20 | ${c.form.render_error(request, c.form['repo_name'])|n} | |
21 |
|
21 | |||
22 | <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p> |
|
22 | <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p> | |
23 | <p id="clone_id" style="display:none;"> |
|
23 | <p id="clone_id" style="display:none;"> | |
24 | ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/> |
|
24 | ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/> | |
25 | ${_('''In case this repository is renamed or moved into another group the repository url changes. |
|
25 | ${_('''In case this repository is renamed or moved into another group the repository url changes. | |
26 | Using above url guarantees that this repository will always be accessible under such url. |
|
26 | Using above url guarantees that this repository will always be accessible under such url. | |
27 | Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p> |
|
27 | Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p> | |
28 | </div> |
|
28 | </div> | |
29 | </div> |
|
29 | </div> | |
30 |
|
30 | |||
31 | <div class="field"> |
|
31 | <div class="field"> | |
32 | <div class="label"> |
|
32 | <div class="label"> | |
33 | <label for="repo_group">${_('Repository group')}:</label> |
|
33 | <label for="repo_group">${_('Repository group')}:</label> | |
34 | </div> |
|
34 | </div> | |
35 | <div class="select"> |
|
35 | <div class="select"> | |
36 | ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n} |
|
36 | ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n} | |
37 | ${c.form.render_error(request, c.form['repo_group'])|n} |
|
37 | ${c.form.render_error(request, c.form['repo_group'])|n} | |
38 |
|
38 | |||
39 | % if c.personal_repo_group: |
|
39 | % if c.personal_repo_group: | |
40 | <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false"> |
|
40 | <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false"> | |
41 | ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}} |
|
41 | ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}} | |
42 | </a> |
|
42 | </a> | |
43 | % endif |
|
43 | % endif | |
44 | <p class="help-block">${_('Optional select a group to put this repository into.')}</p> |
|
44 | <p class="help-block">${_('Optional select a group to put this repository into.')}</p> | |
45 | </div> |
|
45 | </div> | |
46 | </div> |
|
46 | </div> | |
47 |
|
47 | |||
48 | % if c.rhodecode_db_repo.repo_type != 'svn': |
|
48 | % if c.rhodecode_db_repo.repo_type != 'svn': | |
49 | <div class="field"> |
|
49 | <div class="field"> | |
50 | <div class="label"> |
|
50 | <div class="label"> | |
51 | <label for="clone_uri">${_('Remote uri')}:</label> |
|
51 | <label for="clone_uri">${_('Remote uri')}:</label> | |
52 | </div> |
|
52 | </div> | |
53 | <div class="input"> |
|
53 | <div class="input"> | |
54 | %if c.rhodecode_db_repo.clone_uri: |
|
54 | %if c.rhodecode_db_repo.clone_uri: | |
55 | ## display |
|
55 | ## display, if we don't have any errors | |
|
56 | % if not c.form['repo_clone_uri'].error: | |||
56 |
|
|
57 | <div id="clone_uri_hidden" class='text-as-placeholder'> | |
57 | <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span> |
|
58 | <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span> | |
58 | <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span> |
|
59 | <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span> | |
59 | </div> |
|
60 | </div> | |
|
61 | % endif | |||
|
62 | ||||
60 | ## alter field |
|
63 | ## alter field | |
61 | <div id="alter_clone_uri" style="display: none"> |
|
64 | <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}"> | |
62 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n} |
|
65 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n} | |
63 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} |
|
66 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} | |
64 |
|
|
67 | % if c.form['repo_clone_uri'].error: | |
|
68 | ## we got error from form subit, means we modify the url | |||
|
69 | ${h.hidden('repo_clone_uri_change', 'MOD')} | |||
|
70 | % else: | |||
|
71 | ${h.hidden('repo_clone_uri_change', 'OLD')} | |||
|
72 | % endif | |||
65 |
|
73 | |||
|
74 | % if not c.form['repo_clone_uri'].error: | |||
66 | <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span> |
|
75 | <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span> | |
|
76 | % endif | |||
|
77 | ||||
67 | </div> |
|
78 | </div> | |
68 | %else: |
|
79 | %else: | |
69 | ## not set yet, display form to set it |
|
80 | ## not set yet, display form to set it | |
70 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n} |
|
81 | ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n} | |
71 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} |
|
82 | ${c.form.render_error(request, c.form['repo_clone_uri'])|n} | |
72 | ${h.hidden('repo_clone_uri_change', 'NEW')} |
|
83 | ${h.hidden('repo_clone_uri_change', 'NEW')} | |
73 | %endif |
|
84 | %endif | |
74 | <p id="alter_clone_uri_help_block" class="help-block"> |
|
85 | <p id="alter_clone_uri_help_block" class="help-block"> | |
75 | <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %> |
|
86 | <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %> | |
76 | ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/> |
|
87 | ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/> | |
77 | ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')} |
|
88 | ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')} | |
78 | </p> |
|
89 | </p> | |
79 | </div> |
|
90 | </div> | |
80 | </div> |
|
91 | </div> | |
81 | % else: |
|
92 | % else: | |
82 | ${h.hidden('repo_clone_uri', '')} |
|
93 | ${h.hidden('repo_clone_uri', '')} | |
83 | % endif |
|
94 | % endif | |
84 |
|
95 | |||
85 | <div class="field"> |
|
96 | <div class="field"> | |
86 | <div class="label"> |
|
97 | <div class="label"> | |
87 | <label for="repo_landing_commit_ref">${_('Landing commit')}:</label> |
|
98 | <label for="repo_landing_commit_ref">${_('Landing commit')}:</label> | |
88 | </div> |
|
99 | </div> | |
89 | <div class="select"> |
|
100 | <div class="select"> | |
90 | ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n} |
|
101 | ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n} | |
91 | ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n} |
|
102 | ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n} | |
92 | <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p> |
|
103 | <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p> | |
93 | </div> |
|
104 | </div> | |
94 | </div> |
|
105 | </div> | |
95 |
|
106 | |||
96 | <div class="field badged-field"> |
|
107 | <div class="field badged-field"> | |
97 | <div class="label"> |
|
108 | <div class="label"> | |
98 | <label for="repo_owner">${_('Owner')}:</label> |
|
109 | <label for="repo_owner">${_('Owner')}:</label> | |
99 | </div> |
|
110 | </div> | |
100 | <div class="input"> |
|
111 | <div class="input"> | |
101 | <div class="badge-input-container"> |
|
112 | <div class="badge-input-container"> | |
102 | <div class="user-badge"> |
|
113 | <div class="user-badge"> | |
103 | ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, show_disabled=not c.rhodecode_db_repo.user.active)} |
|
114 | ${base.gravatar_with_user(c.rhodecode_db_repo.user.email, show_disabled=not c.rhodecode_db_repo.user.active)} | |
104 | </div> |
|
115 | </div> | |
105 | <div class="badge-input-wrap"> |
|
116 | <div class="badge-input-wrap"> | |
106 | ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n} |
|
117 | ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n} | |
107 | </div> |
|
118 | </div> | |
108 | </div> |
|
119 | </div> | |
109 | ${c.form.render_error(request, c.form['repo_owner'])|n} |
|
120 | ${c.form.render_error(request, c.form['repo_owner'])|n} | |
110 | <p class="help-block">${_('Change owner of this repository.')}</p> |
|
121 | <p class="help-block">${_('Change owner of this repository.')}</p> | |
111 | </div> |
|
122 | </div> | |
112 | </div> |
|
123 | </div> | |
113 |
|
124 | |||
114 | <div class="field"> |
|
125 | <div class="field"> | |
115 | <div class="label label-textarea"> |
|
126 | <div class="label label-textarea"> | |
116 | <label for="repo_description">${_('Description')}:</label> |
|
127 | <label for="repo_description">${_('Description')}:</label> | |
117 | </div> |
|
128 | </div> | |
118 | <div class="textarea text-area editor"> |
|
129 | <div class="textarea text-area editor"> | |
119 | ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n} |
|
130 | ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n} | |
120 | ${c.form.render_error(request, c.form['repo_description'])|n} |
|
131 | ${c.form.render_error(request, c.form['repo_description'])|n} | |
121 | <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p> |
|
132 | <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p> | |
122 | </div> |
|
133 | </div> | |
123 | </div> |
|
134 | </div> | |
124 |
|
135 | |||
125 | <div class="field"> |
|
136 | <div class="field"> | |
126 | <div class="label label-checkbox"> |
|
137 | <div class="label label-checkbox"> | |
127 | <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label> |
|
138 | <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label> | |
128 | </div> |
|
139 | </div> | |
129 | <div class="checkboxes"> |
|
140 | <div class="checkboxes"> | |
130 | ${c.form['repo_private'].render(css_class='medium')|n} |
|
141 | ${c.form['repo_private'].render(css_class='medium')|n} | |
131 | ${c.form.render_error(request, c.form['repo_private'])|n} |
|
142 | ${c.form.render_error(request, c.form['repo_private'])|n} | |
132 | <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span> |
|
143 | <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span> | |
133 | </div> |
|
144 | </div> | |
134 | </div> |
|
145 | </div> | |
135 | <div class="field"> |
|
146 | <div class="field"> | |
136 | <div class="label label-checkbox"> |
|
147 | <div class="label label-checkbox"> | |
137 | <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label> |
|
148 | <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label> | |
138 | </div> |
|
149 | </div> | |
139 | <div class="checkboxes"> |
|
150 | <div class="checkboxes"> | |
140 | ${c.form['repo_enable_statistics'].render(css_class='medium')|n} |
|
151 | ${c.form['repo_enable_statistics'].render(css_class='medium')|n} | |
141 | ${c.form.render_error(request, c.form['repo_enable_statistics'])|n} |
|
152 | ${c.form.render_error(request, c.form['repo_enable_statistics'])|n} | |
142 | <span class="help-block">${_('Enable statistics window on summary page.')}</span> |
|
153 | <span class="help-block">${_('Enable statistics window on summary page.')}</span> | |
143 | </div> |
|
154 | </div> | |
144 | </div> |
|
155 | </div> | |
145 | <div class="field"> |
|
156 | <div class="field"> | |
146 | <div class="label label-checkbox"> |
|
157 | <div class="label label-checkbox"> | |
147 | <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label> |
|
158 | <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label> | |
148 | </div> |
|
159 | </div> | |
149 | <div class="checkboxes"> |
|
160 | <div class="checkboxes"> | |
150 | ${c.form['repo_enable_downloads'].render(css_class='medium')|n} |
|
161 | ${c.form['repo_enable_downloads'].render(css_class='medium')|n} | |
151 | ${c.form.render_error(request, c.form['repo_enable_downloads'])|n} |
|
162 | ${c.form.render_error(request, c.form['repo_enable_downloads'])|n} | |
152 | <span class="help-block">${_('Enable download menu on summary page.')}</span> |
|
163 | <span class="help-block">${_('Enable download menu on summary page.')}</span> | |
153 | </div> |
|
164 | </div> | |
154 | </div> |
|
165 | </div> | |
155 | <div class="field"> |
|
166 | <div class="field"> | |
156 | <div class="label label-checkbox"> |
|
167 | <div class="label label-checkbox"> | |
157 | <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label> |
|
168 | <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label> | |
158 | </div> |
|
169 | </div> | |
159 | <div class="checkboxes"> |
|
170 | <div class="checkboxes"> | |
160 | ${c.form['repo_enable_locking'].render(css_class='medium')|n} |
|
171 | ${c.form['repo_enable_locking'].render(css_class='medium')|n} | |
161 | ${c.form.render_error(request, c.form['repo_enable_locking'])|n} |
|
172 | ${c.form.render_error(request, c.form['repo_enable_locking'])|n} | |
162 | <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span> |
|
173 | <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span> | |
163 | </div> |
|
174 | </div> | |
164 | </div> |
|
175 | </div> | |
165 |
|
176 | |||
166 | %if c.visual.repository_fields: |
|
177 | %if c.visual.repository_fields: | |
167 | ## EXTRA FIELDS |
|
178 | ## EXTRA FIELDS | |
168 | %for field in c.repo_fields: |
|
179 | %for field in c.repo_fields: | |
169 | <div class="field"> |
|
180 | <div class="field"> | |
170 | <div class="label"> |
|
181 | <div class="label"> | |
171 | <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label> |
|
182 | <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label> | |
172 | </div> |
|
183 | </div> | |
173 | <div class="input input-medium"> |
|
184 | <div class="input input-medium"> | |
174 | ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} |
|
185 | ${h.text(field.field_key_prefixed, field.field_value, class_='medium')} | |
175 | %if field.field_desc: |
|
186 | %if field.field_desc: | |
176 | <span class="help-block">${field.field_desc}</span> |
|
187 | <span class="help-block">${field.field_desc}</span> | |
177 | %endif |
|
188 | %endif | |
178 | </div> |
|
189 | </div> | |
179 | </div> |
|
190 | </div> | |
180 | %endfor |
|
191 | %endfor | |
181 | %endif |
|
192 | %endif | |
182 | <div class="buttons"> |
|
193 | <div class="buttons"> | |
183 | ${h.submit('save',_('Save'),class_="btn")} |
|
194 | ${h.submit('save',_('Save'),class_="btn")} | |
184 | ${h.reset('reset',_('Reset'),class_="btn")} |
|
195 | ${h.reset('reset',_('Reset'),class_="btn")} | |
185 | </div> |
|
196 | </div> | |
186 | </div> |
|
197 | </div> | |
187 | </div> |
|
198 | </div> | |
188 | ${h.end_form()} |
|
199 | ${h.end_form()} | |
189 | </div> |
|
200 | </div> | |
190 | </div> |
|
201 | </div> | |
191 |
|
202 | |||
192 | <script> |
|
203 | <script> | |
193 | $(document).ready(function(){ |
|
204 | $(document).ready(function(){ | |
194 | var cloneUrl = function() { |
|
205 | var cloneUrl = function() { | |
195 | var alterButton = $('#alter_clone_uri'); |
|
206 | var alterButton = $('#alter_clone_uri'); | |
196 | var editButton = $('#edit_clone_uri'); |
|
207 | var editButton = $('#edit_clone_uri'); | |
197 | var cancelEditButton = $('#cancel_edit_clone_uri'); |
|
208 | var cancelEditButton = $('#cancel_edit_clone_uri'); | |
198 | var hiddenUrl = $('#clone_uri_hidden'); |
|
209 | var hiddenUrl = $('#clone_uri_hidden'); | |
199 | var hiddenUrlValue = $('#clone_uri_hidden_value'); |
|
210 | var hiddenUrlValue = $('#clone_uri_hidden_value'); | |
200 | var input = $('#clone_uri'); |
|
211 | var input = $('#clone_uri'); | |
201 | var helpBlock = $('#alter_clone_uri_help_block'); |
|
212 | var helpBlock = $('#alter_clone_uri_help_block'); | |
202 | var changedFlag = $('#repo_clone_uri_change'); |
|
213 | var changedFlag = $('#repo_clone_uri_change'); | |
203 | var originalText = helpBlock.html(); |
|
214 | var originalText = helpBlock.html(); | |
204 | var obfuscatedUrl = hiddenUrlValue.html(); |
|
215 | var obfuscatedUrl = hiddenUrlValue.html(); | |
205 |
|
216 | |||
206 | var edit = function(e) { |
|
217 | var edit = function(e) { | |
207 | alterButton.show(); |
|
218 | alterButton.show(); | |
208 | editButton.hide(); |
|
219 | editButton.hide(); | |
209 | hiddenUrl.hide(); |
|
220 | hiddenUrl.hide(); | |
210 |
|
221 | |||
211 | //add the old value next to input for verification |
|
222 | //add the old value next to input for verification | |
212 | helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText); |
|
223 | helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText); | |
213 | changedFlag.val('MOD'); |
|
224 | changedFlag.val('MOD'); | |
214 | }; |
|
225 | }; | |
215 |
|
226 | |||
216 | var cancelEdit = function(e) { |
|
227 | var cancelEdit = function(e) { | |
217 | alterButton.hide(); |
|
228 | alterButton.hide(); | |
218 | editButton.show(); |
|
229 | editButton.show(); | |
219 | hiddenUrl.show(); |
|
230 | hiddenUrl.show(); | |
220 |
|
231 | |||
221 | helpBlock.html(originalText); |
|
232 | helpBlock.html(originalText); | |
222 | changedFlag.val('OLD'); |
|
233 | changedFlag.val('OLD'); | |
223 | input.val(''); |
|
234 | input.val(''); | |
224 | }; |
|
235 | }; | |
225 |
|
236 | |||
226 | var initEvents = function() { |
|
237 | var initEvents = function() { | |
227 | editButton.on('click', edit); |
|
238 | editButton.on('click', edit); | |
228 | cancelEditButton.on('click', cancelEdit); |
|
239 | cancelEditButton.on('click', cancelEdit); | |
229 | }; |
|
240 | }; | |
230 |
|
241 | |||
231 | var setInitialState = function() { |
|
242 | var setInitialState = function() { | |
232 | if (input.hasClass('error')) { |
|
243 | if (input.hasClass('error')) { | |
233 | alterButton.show(); |
|
244 | alterButton.show(); | |
234 | editButton.hide(); |
|
245 | editButton.hide(); | |
235 | hiddenUrl.hide(); |
|
246 | hiddenUrl.hide(); | |
236 | } |
|
247 | } | |
237 | }; |
|
248 | }; | |
238 |
|
249 | |||
239 | setInitialState(); |
|
250 | setInitialState(); | |
240 | initEvents(); |
|
251 | initEvents(); | |
241 | }(); |
|
252 | }(); | |
242 |
|
253 | |||
243 | selectMyGroup = function(element) { |
|
254 | selectMyGroup = function(element) { | |
244 | $("#repo_group").val($(element).data('personalGroupId')).trigger("change"); |
|
255 | $("#repo_group").val($(element).data('personalGroupId')).trigger("change"); | |
245 | }; |
|
256 | }; | |
246 |
|
257 | |||
247 | UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}'); |
|
258 | UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}'); | |
248 | }); |
|
259 | }); | |
249 | </script> |
|
260 | </script> |
General Comments 0
You need to be logged in to leave comments.
Login now