Show More
@@ -0,0 +1,24 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import colander | |||
|
22 | ||||
|
23 | from colander import Invalid # noqa, don't remove this | |||
|
24 |
@@ -0,0 +1,89 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import unicodedata | |||
|
22 | ||||
|
23 | ||||
|
24 | ||||
|
25 | def strip_preparer(value): | |||
|
26 | """ | |||
|
27 | strips given values using .strip() function | |||
|
28 | """ | |||
|
29 | ||||
|
30 | if value: | |||
|
31 | value = value.strip() | |||
|
32 | return value | |||
|
33 | ||||
|
34 | ||||
|
35 | def slugify_preparer(value): | |||
|
36 | """ | |||
|
37 | Slugify given value to a safe representation for url/id | |||
|
38 | """ | |||
|
39 | from rhodecode.lib.utils import repo_name_slug | |||
|
40 | if value: | |||
|
41 | value = repo_name_slug(value.lower()) | |||
|
42 | return value | |||
|
43 | ||||
|
44 | ||||
|
45 | def non_ascii_strip_preparer(value): | |||
|
46 | """ | |||
|
47 | trie to replace non-ascii letters to their ascii representation | |||
|
48 | eg:: | |||
|
49 | ||||
|
50 | `żołw` converts into `zolw` | |||
|
51 | """ | |||
|
52 | if value: | |||
|
53 | value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') | |||
|
54 | return value | |||
|
55 | ||||
|
56 | ||||
|
57 | def unique_list_preparer(value): | |||
|
58 | """ | |||
|
59 | Converts an list to a list with only unique values | |||
|
60 | """ | |||
|
61 | ||||
|
62 | def make_unique(value): | |||
|
63 | seen = [] | |||
|
64 | return [c for c in value if | |||
|
65 | not (c in seen or seen.append(c))] | |||
|
66 | ||||
|
67 | if isinstance(value, list): | |||
|
68 | ret_val = make_unique(value) | |||
|
69 | elif isinstance(value, set): | |||
|
70 | ret_val = list(value) | |||
|
71 | elif isinstance(value, tuple): | |||
|
72 | ret_val = make_unique(value) | |||
|
73 | elif value is None: | |||
|
74 | ret_val = [] | |||
|
75 | else: | |||
|
76 | ret_val = [value] | |||
|
77 | ||||
|
78 | return ret_val | |||
|
79 | ||||
|
80 | ||||
|
81 | def unique_list_from_str_preparer(value): | |||
|
82 | """ | |||
|
83 | Converts an list to a list with only unique values | |||
|
84 | """ | |||
|
85 | from rhodecode.lib.utils2 import aslist | |||
|
86 | ||||
|
87 | if isinstance(value, basestring): | |||
|
88 | value = aslist(value, ',') | |||
|
89 | return unique_list_preparer(value) No newline at end of file |
@@ -0,0 +1,25 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | """ | |||
|
22 | Colander Schema nodes | |||
|
23 | http://docs.pylonsproject.org/projects/colander/en/latest/basics.html#schema-node-objects | |||
|
24 | """ | |||
|
25 |
@@ -0,0 +1,185 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import os | |||
|
22 | ||||
|
23 | import colander | |||
|
24 | ||||
|
25 | from rhodecode.translation import _ | |||
|
26 | from rhodecode.model.validation_schema import validators, preparers | |||
|
27 | ||||
|
28 | ||||
|
29 | def nodes_to_sequence(nodes, colander_node=None): | |||
|
30 | """ | |||
|
31 | Converts old style dict nodes to new list of dicts | |||
|
32 | ||||
|
33 | :param nodes: dict with key beeing name of the file | |||
|
34 | ||||
|
35 | """ | |||
|
36 | if not isinstance(nodes, dict): | |||
|
37 | msg = 'Nodes needs to be a dict, got {}'.format(type(nodes)) | |||
|
38 | raise colander.Invalid(colander_node, msg) | |||
|
39 | out = [] | |||
|
40 | ||||
|
41 | for key, val in nodes.items(): | |||
|
42 | val = (isinstance(val, dict) and val) or {} | |||
|
43 | out.append(dict( | |||
|
44 | filename=key, | |||
|
45 | content=val.get('content'), | |||
|
46 | mimetype=val.get('mimetype') | |||
|
47 | )) | |||
|
48 | ||||
|
49 | out = Nodes().deserialize(out) | |||
|
50 | return out | |||
|
51 | ||||
|
52 | ||||
|
53 | def sequence_to_nodes(nodes, colander_node=None): | |||
|
54 | if not isinstance(nodes, list): | |||
|
55 | msg = 'Nodes needs to be a list, got {}'.format(type(nodes)) | |||
|
56 | raise colander.Invalid(colander_node, msg) | |||
|
57 | nodes = Nodes().deserialize(nodes) | |||
|
58 | ||||
|
59 | out = {} | |||
|
60 | try: | |||
|
61 | for file_data in nodes: | |||
|
62 | file_data_skip = file_data.copy() | |||
|
63 | # if we got filename_org we use it as a key so we keep old | |||
|
64 | # name as input and rename is-reflected inside the values as | |||
|
65 | # filename and filename_org differences. | |||
|
66 | filename_org = file_data.get('filename_org') | |||
|
67 | filename = filename_org or file_data['filename'] | |||
|
68 | out[filename] = {} | |||
|
69 | out[filename].update(file_data_skip) | |||
|
70 | ||||
|
71 | except Exception as e: | |||
|
72 | msg = 'Invalid data format org_exc:`{}`'.format(repr(e)) | |||
|
73 | raise colander.Invalid(colander_node, msg) | |||
|
74 | return out | |||
|
75 | ||||
|
76 | ||||
|
77 | @colander.deferred | |||
|
78 | def deferred_lifetime_validator(node, kw): | |||
|
79 | options = kw.get('lifetime_options', []) | |||
|
80 | return colander.All( | |||
|
81 | colander.Range(min=-1, max=60 * 24 * 30 * 12), | |||
|
82 | colander.OneOf([x for x in options])) | |||
|
83 | ||||
|
84 | ||||
|
85 | def unique_gist_validator(node, value): | |||
|
86 | from rhodecode.model.db import Gist | |||
|
87 | existing = Gist.get_by_access_id(value) | |||
|
88 | if existing: | |||
|
89 | msg = _(u'Gist with name {} already exists').format(value) | |||
|
90 | raise colander.Invalid(node, msg) | |||
|
91 | ||||
|
92 | ||||
|
93 | def filename_validator(node, value): | |||
|
94 | if value != os.path.basename(value): | |||
|
95 | msg = _(u'Filename {} cannot be inside a directory').format(value) | |||
|
96 | raise colander.Invalid(node, msg) | |||
|
97 | ||||
|
98 | ||||
|
99 | class NodeSchema(colander.MappingSchema): | |||
|
100 | # if we perform rename this will be org filename | |||
|
101 | filename_org = colander.SchemaNode( | |||
|
102 | colander.String(), | |||
|
103 | preparer=[preparers.strip_preparer, | |||
|
104 | preparers.non_ascii_strip_preparer], | |||
|
105 | validator=filename_validator, | |||
|
106 | missing=None) | |||
|
107 | ||||
|
108 | filename = colander.SchemaNode( | |||
|
109 | colander.String(), | |||
|
110 | preparer=[preparers.strip_preparer, | |||
|
111 | preparers.non_ascii_strip_preparer], | |||
|
112 | validator=filename_validator) | |||
|
113 | ||||
|
114 | content = colander.SchemaNode( | |||
|
115 | colander.String()) | |||
|
116 | mimetype = colander.SchemaNode( | |||
|
117 | colander.String(), | |||
|
118 | missing=None) | |||
|
119 | ||||
|
120 | ||||
|
121 | class Nodes(colander.SequenceSchema): | |||
|
122 | filenames = NodeSchema() | |||
|
123 | ||||
|
124 | def validator(self, node, cstruct): | |||
|
125 | if not isinstance(cstruct, list): | |||
|
126 | return | |||
|
127 | ||||
|
128 | found_filenames = [] | |||
|
129 | for data in cstruct: | |||
|
130 | filename = data['filename'] | |||
|
131 | if filename in found_filenames: | |||
|
132 | msg = _('Duplicated value for filename found: `{}`').format( | |||
|
133 | filename) | |||
|
134 | raise colander.Invalid(node, msg) | |||
|
135 | found_filenames.append(filename) | |||
|
136 | ||||
|
137 | ||||
|
138 | class GistSchema(colander.MappingSchema): | |||
|
139 | """ | |||
|
140 | schema = GistSchema() | |||
|
141 | schema.bind( | |||
|
142 | lifetime_options = [1,2,3] | |||
|
143 | ) | |||
|
144 | out = schema.deserialize(dict( | |||
|
145 | nodes=[ | |||
|
146 | {'filename': 'x', 'content': 'xxx', }, | |||
|
147 | {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'}, | |||
|
148 | ] | |||
|
149 | )) | |||
|
150 | """ | |||
|
151 | ||||
|
152 | from rhodecode.model.db import Gist | |||
|
153 | ||||
|
154 | gistid = colander.SchemaNode( | |||
|
155 | colander.String(), | |||
|
156 | missing=None, | |||
|
157 | preparer=[preparers.strip_preparer, | |||
|
158 | preparers.non_ascii_strip_preparer, | |||
|
159 | preparers.slugify_preparer], | |||
|
160 | validator=colander.All( | |||
|
161 | colander.Length(min=3), | |||
|
162 | unique_gist_validator | |||
|
163 | )) | |||
|
164 | ||||
|
165 | description = colander.SchemaNode( | |||
|
166 | colander.String(), | |||
|
167 | missing=u'') | |||
|
168 | ||||
|
169 | lifetime = colander.SchemaNode( | |||
|
170 | colander.Integer(), | |||
|
171 | validator=deferred_lifetime_validator) | |||
|
172 | ||||
|
173 | gist_acl_level = colander.SchemaNode( | |||
|
174 | colander.String(), | |||
|
175 | validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC, | |||
|
176 | Gist.ACL_LEVEL_PRIVATE])) | |||
|
177 | ||||
|
178 | gist_type = colander.SchemaNode( | |||
|
179 | colander.String(), | |||
|
180 | missing=Gist.ACL_LEVEL_PUBLIC, | |||
|
181 | validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC])) | |||
|
182 | ||||
|
183 | nodes = Nodes() | |||
|
184 | ||||
|
185 |
@@ -0,0 +1,29 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | ||||
|
22 | import colander | |||
|
23 | ||||
|
24 | ||||
|
25 | from rhodecode.model.validation_schema import validators, preparers, types | |||
|
26 | ||||
|
27 | ||||
|
28 | class RepoGroupSchema(colander.Schema): | |||
|
29 | group_name = colander.SchemaNode(types.GroupNameType()) |
@@ -0,0 +1,27 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import colander | |||
|
22 | ||||
|
23 | from rhodecode.model.validation_schema import validators, preparers, types | |||
|
24 | ||||
|
25 | ||||
|
26 | class RepoSchema(colander.Schema): | |||
|
27 | repo_name = colander.SchemaNode(types.GroupNameType()) |
@@ -0,0 +1,44 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | ||||
|
22 | import colander | |||
|
23 | ||||
|
24 | ||||
|
25 | class SearchParamsSchema(colander.MappingSchema): | |||
|
26 | search_query = colander.SchemaNode( | |||
|
27 | colander.String(), | |||
|
28 | missing='') | |||
|
29 | search_type = colander.SchemaNode( | |||
|
30 | colander.String(), | |||
|
31 | missing='content', | |||
|
32 | validator=colander.OneOf(['content', 'path', 'commit', 'repository'])) | |||
|
33 | search_sort = colander.SchemaNode( | |||
|
34 | colander.String(), | |||
|
35 | missing='newfirst', | |||
|
36 | validator=colander.OneOf( | |||
|
37 | ['oldfirst', 'newfirst'])) | |||
|
38 | page_limit = colander.SchemaNode( | |||
|
39 | colander.Integer(), | |||
|
40 | missing=10, | |||
|
41 | validator=colander.Range(1, 500)) | |||
|
42 | requested_page = colander.SchemaNode( | |||
|
43 | colander.Integer(), | |||
|
44 | missing=1) |
@@ -0,0 +1,34 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import colander | |||
|
22 | ||||
|
23 | ||||
|
24 | class GroupNameType(colander.String): | |||
|
25 | SEPARATOR = '/' | |||
|
26 | ||||
|
27 | def deserialize(self, node, cstruct): | |||
|
28 | result = super(GroupNameType, self).deserialize(node, cstruct) | |||
|
29 | return self._replace_extra_slashes(result) | |||
|
30 | ||||
|
31 | def _replace_extra_slashes(self, path): | |||
|
32 | path = path.split(self.SEPARATOR) | |||
|
33 | path = [item for item in path if item] | |||
|
34 | return self.SEPARATOR.join(path) |
@@ -0,0 +1,19 b'' | |||||
|
1 | import os | |||
|
2 | ||||
|
3 | import ipaddress | |||
|
4 | import colander | |||
|
5 | ||||
|
6 | from rhodecode.translation import _ | |||
|
7 | ||||
|
8 | ||||
|
9 | def ip_addr_validator(node, value): | |||
|
10 | try: | |||
|
11 | # this raises an ValueError if address is not IpV4 or IpV6 | |||
|
12 | ipaddress.ip_network(value, strict=False) | |||
|
13 | except ValueError: | |||
|
14 | msg = _(u'Please enter a valid IPv4 or IpV6 address') | |||
|
15 | raise colander.Invalid(node, msg) | |||
|
16 | ||||
|
17 | ||||
|
18 | ||||
|
19 |
1 | NO CONTENT: new file 100644 |
|
NO CONTENT: new file 100644 |
@@ -0,0 +1,100 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | ||||
|
3 | # Copyright (C) 2016-2016 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 | ||||
|
21 | import colander | |||
|
22 | import pytest | |||
|
23 | ||||
|
24 | from rhodecode.model import validation_schema | |||
|
25 | from rhodecode.model.validation_schema.schemas import gist_schema | |||
|
26 | ||||
|
27 | ||||
|
28 | class TestGistSchema(object): | |||
|
29 | ||||
|
30 | def test_deserialize_bad_data(self): | |||
|
31 | schema = gist_schema.GistSchema().bind( | |||
|
32 | lifetime_options=[1, 2, 3] | |||
|
33 | ) | |||
|
34 | with pytest.raises(validation_schema.Invalid) as exc_info: | |||
|
35 | schema.deserialize('err') | |||
|
36 | err = exc_info.value.asdict() | |||
|
37 | assert err[''] == '"err" is not a mapping type: ' \ | |||
|
38 | 'Does not implement dict-like functionality.' | |||
|
39 | ||||
|
40 | def test_deserialize_bad_lifetime_options(self): | |||
|
41 | schema = gist_schema.GistSchema().bind( | |||
|
42 | lifetime_options=[1, 2, 3] | |||
|
43 | ) | |||
|
44 | with pytest.raises(validation_schema.Invalid) as exc_info: | |||
|
45 | schema.deserialize(dict( | |||
|
46 | lifetime=10 | |||
|
47 | )) | |||
|
48 | err = exc_info.value.asdict() | |||
|
49 | assert err['lifetime'] == '"10" is not one of 1, 2, 3' | |||
|
50 | ||||
|
51 | with pytest.raises(validation_schema.Invalid) as exc_info: | |||
|
52 | schema.deserialize(dict( | |||
|
53 | lifetime='x' | |||
|
54 | )) | |||
|
55 | err = exc_info.value.asdict() | |||
|
56 | assert err['lifetime'] == '"x" is not a number' | |||
|
57 | ||||
|
58 | def test_serialize_data_correctly(self): | |||
|
59 | schema = gist_schema.GistSchema().bind( | |||
|
60 | lifetime_options=[1, 2, 3] | |||
|
61 | ) | |||
|
62 | nodes = [{ | |||
|
63 | 'filename': 'foobar', | |||
|
64 | 'filename_org': 'foobar', | |||
|
65 | 'content': 'content', | |||
|
66 | 'mimetype': 'xx' | |||
|
67 | }] | |||
|
68 | schema_data = schema.deserialize(dict( | |||
|
69 | lifetime=2, | |||
|
70 | gist_type='public', | |||
|
71 | gist_acl_level='acl_public', | |||
|
72 | nodes=nodes, | |||
|
73 | )) | |||
|
74 | ||||
|
75 | assert schema_data['nodes'] == nodes | |||
|
76 | ||||
|
77 | def test_serialize_data_correctly_with_conversion(self): | |||
|
78 | schema = gist_schema.GistSchema().bind( | |||
|
79 | lifetime_options=[1, 2, 3], | |||
|
80 | convert_nodes=True | |||
|
81 | ) | |||
|
82 | nodes = [{ | |||
|
83 | 'filename': 'foobar', | |||
|
84 | 'filename_org': None, | |||
|
85 | 'content': 'content', | |||
|
86 | 'mimetype': 'xx' | |||
|
87 | }] | |||
|
88 | schema_data = schema.deserialize(dict( | |||
|
89 | lifetime=2, | |||
|
90 | gist_type='public', | |||
|
91 | gist_acl_level='acl_public', | |||
|
92 | nodes=nodes, | |||
|
93 | )) | |||
|
94 | ||||
|
95 | assert schema_data['nodes'] == nodes | |||
|
96 | ||||
|
97 | seq_nodes = gist_schema.sequence_to_nodes(nodes) | |||
|
98 | assert isinstance(seq_nodes, dict) | |||
|
99 | seq_nodes = gist_schema.nodes_to_sequence(seq_nodes) | |||
|
100 | assert nodes == seq_nodes |
@@ -30,7 +30,8 b' from pyramid.renderers import render' | |||||
30 | from pyramid.response import Response |
|
30 | from pyramid.response import Response | |
31 | from pyramid.httpexceptions import HTTPNotFound |
|
31 | from pyramid.httpexceptions import HTTPNotFound | |
32 |
|
32 | |||
33 | from rhodecode.api.exc import JSONRPCBaseError, JSONRPCError, JSONRPCForbidden |
|
33 | from rhodecode.api.exc import ( | |
|
34 | JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError) | |||
34 | from rhodecode.lib.auth import AuthUser |
|
35 | from rhodecode.lib.auth import AuthUser | |
35 | from rhodecode.lib.base import get_ip_addr |
|
36 | from rhodecode.lib.base import get_ip_addr | |
36 | from rhodecode.lib.ext_json import json |
|
37 | from rhodecode.lib.ext_json import json | |
@@ -127,6 +128,11 b' def exception_view(exc, request):' | |||||
127 | if isinstance(exc, JSONRPCError): |
|
128 | if isinstance(exc, JSONRPCError): | |
128 | fault_message = exc.message |
|
129 | fault_message = exc.message | |
129 | log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message) |
|
130 | log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message) | |
|
131 | elif isinstance(exc, JSONRPCValidationError): | |||
|
132 | colander_exc = exc.colander_exception | |||
|
133 | #TODO: think maybe of nicer way to serialize errors ? | |||
|
134 | fault_message = colander_exc.asdict() | |||
|
135 | log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message) | |||
130 | elif isinstance(exc, JSONRPCForbidden): |
|
136 | elif isinstance(exc, JSONRPCForbidden): | |
131 | fault_message = 'Access was denied to this resource.' |
|
137 | fault_message = 'Access was denied to this resource.' | |
132 | log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message) |
|
138 | log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message) |
@@ -27,5 +27,13 b' class JSONRPCError(JSONRPCBaseError):' | |||||
27 | pass |
|
27 | pass | |
28 |
|
28 | |||
29 |
|
29 | |||
|
30 | class JSONRPCValidationError(JSONRPCBaseError): | |||
|
31 | ||||
|
32 | def __init__(self, *args, **kwargs): | |||
|
33 | self.colander_exception = kwargs.pop('colander_exc') | |||
|
34 | super(JSONRPCValidationError, self).__init__(*args, **kwargs) | |||
|
35 | ||||
|
36 | ||||
30 | class JSONRPCForbidden(JSONRPCBaseError): |
|
37 | class JSONRPCForbidden(JSONRPCBaseError): | |
31 | pass |
|
38 | pass | |
|
39 |
@@ -43,7 +43,7 b' class TestApiCreateGist(object):' | |||||
43 | description='foobar-gist', |
|
43 | description='foobar-gist', | |
44 | gist_type=gist_type, |
|
44 | gist_type=gist_type, | |
45 | acl_level=gist_acl_level, |
|
45 | acl_level=gist_acl_level, | |
46 | files={'foobar': {'content': 'foo'}}) |
|
46 | files={'foobar_ąć': {'content': 'foo'}}) | |
47 | response = api_call(self.app, params) |
|
47 | response = api_call(self.app, params) | |
48 | response_json = response.json |
|
48 | response_json = response.json | |
49 | gist = response_json['result']['gist'] |
|
49 | gist = response_json['result']['gist'] | |
@@ -68,6 +68,32 b' class TestApiCreateGist(object):' | |||||
68 | finally: |
|
68 | finally: | |
69 | Fixture().destroy_gists() |
|
69 | Fixture().destroy_gists() | |
70 |
|
70 | |||
|
71 | @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [ | |||
|
72 | ({'gist_type': '"ups" is not one of private, public'}, | |||
|
73 | 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}), | |||
|
74 | ||||
|
75 | ({'lifetime': '-120 is less than minimum value -1'}, | |||
|
76 | -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}), | |||
|
77 | ||||
|
78 | ({'0.content': 'Required'}, | |||
|
79 | 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}), | |||
|
80 | ]) | |||
|
81 | def test_api_try_create_gist( | |||
|
82 | self, expected, lifetime, gist_type, gist_acl_level, files): | |||
|
83 | id_, params = build_data( | |||
|
84 | self.apikey_regular, 'create_gist', | |||
|
85 | lifetime=lifetime, | |||
|
86 | description='foobar-gist', | |||
|
87 | gist_type=gist_type, | |||
|
88 | acl_level=gist_acl_level, | |||
|
89 | files=files) | |||
|
90 | response = api_call(self.app, params) | |||
|
91 | ||||
|
92 | try: | |||
|
93 | assert_error(id_, expected, given=response.body) | |||
|
94 | finally: | |||
|
95 | Fixture().destroy_gists() | |||
|
96 | ||||
71 | @mock.patch.object(GistModel, 'create', crash) |
|
97 | @mock.patch.object(GistModel, 'create', crash) | |
72 | def test_api_create_gist_exception_occurred(self): |
|
98 | def test_api_create_gist_exception_occurred(self): | |
73 | id_, params = build_data(self.apikey_regular, 'create_gist', files={}) |
|
99 | id_, params = build_data(self.apikey_regular, 'create_gist', files={}) |
@@ -34,8 +34,6 b' from rhodecode.lib.vcs.exceptions import' | |||||
34 | log = logging.getLogger(__name__) |
|
34 | log = logging.getLogger(__name__) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 |
|
||||
38 |
|
||||
39 | class OAttr(object): |
|
37 | class OAttr(object): | |
40 | """ |
|
38 | """ | |
41 | Special Option that defines other attribute, and can default to them |
|
39 | Special Option that defines other attribute, and can default to them |
@@ -23,6 +23,7 b' import logging' | |||||
23 | import time |
|
23 | import time | |
24 |
|
24 | |||
25 | from rhodecode.api import jsonrpc_method, JSONRPCError |
|
25 | from rhodecode.api import jsonrpc_method, JSONRPCError | |
|
26 | from rhodecode.api.exc import JSONRPCValidationError | |||
26 | from rhodecode.api.utils import ( |
|
27 | from rhodecode.api.utils import ( | |
27 | Optional, OAttr, get_gist_or_error, get_user_or_error, |
|
28 | Optional, OAttr, get_gist_or_error, get_user_or_error, | |
28 | has_superadmin_permission) |
|
29 | has_superadmin_permission) | |
@@ -96,7 +97,8 b' def get_gists(request, apiuser, userid=O' | |||||
96 |
|
97 | |||
97 | @jsonrpc_method() |
|
98 | @jsonrpc_method() | |
98 | def create_gist( |
|
99 | def create_gist( | |
99 |
request, apiuser, files, |
|
100 | request, apiuser, files, gistid=Optional(None), | |
|
101 | owner=Optional(OAttr('apiuser')), | |||
100 | gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), |
|
102 | gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), | |
101 | acl_level=Optional(Gist.ACL_LEVEL_PUBLIC), |
|
103 | acl_level=Optional(Gist.ACL_LEVEL_PUBLIC), | |
102 | description=Optional('')): |
|
104 | description=Optional('')): | |
@@ -108,10 +110,11 b' def create_gist(' | |||||
108 | :param files: files to be added to the gist. The data structure has |
|
110 | :param files: files to be added to the gist. The data structure has | |
109 | to match the following example:: |
|
111 | to match the following example:: | |
110 |
|
112 | |||
111 |
{'filename': {'content':'...' |
|
113 | {'filename1': {'content':'...'}, 'filename2': {'content':'...'}} | |
112 | 'filename2': {'content':'...', 'lexer': null}} |
|
|||
113 |
|
114 | |||
114 | :type files: dict |
|
115 | :type files: dict | |
|
116 | :param gistid: Set a custom id for the gist | |||
|
117 | :type gistid: Optional(str) | |||
115 | :param owner: Set the gist owner, defaults to api method caller |
|
118 | :param owner: Set the gist owner, defaults to api method caller | |
116 | :type owner: Optional(str or int) |
|
119 | :type owner: Optional(str or int) | |
117 | :param gist_type: type of gist ``public`` or ``private`` |
|
120 | :param gist_type: type of gist ``public`` or ``private`` | |
@@ -148,23 +151,49 b' def create_gist(' | |||||
148 | } |
|
151 | } | |
149 |
|
152 | |||
150 | """ |
|
153 | """ | |
|
154 | from rhodecode.model import validation_schema | |||
|
155 | from rhodecode.model.validation_schema.schemas import gist_schema | |||
151 |
|
156 | |||
152 | try: |
|
|||
153 |
|
|
157 | if isinstance(owner, Optional): | |
154 |
|
|
158 | owner = apiuser.user_id | |
155 |
|
159 | |||
156 |
|
|
160 | owner = get_user_or_error(owner) | |
157 | description = Optional.extract(description) |
|
161 | ||
158 | gist_type = Optional.extract(gist_type) |
|
|||
159 |
|
|
162 | lifetime = Optional.extract(lifetime) | |
160 | acl_level = Optional.extract(acl_level) |
|
163 | schema = gist_schema.GistSchema().bind( | |
|
164 | # bind the given values if it's allowed, however the deferred | |||
|
165 | # validator will still validate it according to other rules | |||
|
166 | lifetime_options=[lifetime]) | |||
|
167 | ||||
|
168 | try: | |||
|
169 | nodes = gist_schema.nodes_to_sequence( | |||
|
170 | files, colander_node=schema.get('nodes')) | |||
161 |
|
171 | |||
162 | gist = GistModel().create(description=description, |
|
172 | schema_data = schema.deserialize(dict( | |
|
173 | gistid=Optional.extract(gistid), | |||
|
174 | description=Optional.extract(description), | |||
|
175 | gist_type=Optional.extract(gist_type), | |||
|
176 | lifetime=lifetime, | |||
|
177 | gist_acl_level=Optional.extract(acl_level), | |||
|
178 | nodes=nodes | |||
|
179 | )) | |||
|
180 | ||||
|
181 | # convert to safer format with just KEYs so we sure no duplicates | |||
|
182 | schema_data['nodes'] = gist_schema.sequence_to_nodes( | |||
|
183 | schema_data['nodes'], colander_node=schema.get('nodes')) | |||
|
184 | ||||
|
185 | except validation_schema.Invalid as err: | |||
|
186 | raise JSONRPCValidationError(colander_exc=err) | |||
|
187 | ||||
|
188 | try: | |||
|
189 | gist = GistModel().create( | |||
163 |
|
|
190 | owner=owner, | |
164 | gist_mapping=files, |
|
191 | gist_id=schema_data['gistid'], | |
165 | gist_type=gist_type, |
|
192 | description=schema_data['description'], | |
166 | lifetime=lifetime, |
|
193 | gist_mapping=schema_data['nodes'], | |
167 | gist_acl_level=acl_level) |
|
194 | gist_type=schema_data['gist_type'], | |
|
195 | lifetime=schema_data['lifetime'], | |||
|
196 | gist_acl_level=schema_data['gist_acl_level']) | |||
168 | Session().commit() |
|
197 | Session().commit() | |
169 | return { |
|
198 | return { | |
170 | 'msg': 'created new gist', |
|
199 | 'msg': 'created new gist', |
@@ -44,7 +44,7 b' from rhodecode.model.repo import RepoMod' | |||||
44 | from rhodecode.model.repo_group import RepoGroupModel |
|
44 | from rhodecode.model.repo_group import RepoGroupModel | |
45 | from rhodecode.model.scm import ScmModel, RepoList |
|
45 | from rhodecode.model.scm import ScmModel, RepoList | |
46 | from rhodecode.model.settings import SettingsModel, VcsSettingsModel |
|
46 | from rhodecode.model.settings import SettingsModel, VcsSettingsModel | |
47 |
from rhodecode.model.validation_schema import |
|
47 | from rhodecode.model.validation_schema.schemas import repo_schema | |
48 |
|
48 | |||
49 | log = logging.getLogger(__name__) |
|
49 | log = logging.getLogger(__name__) | |
50 |
|
50 | |||
@@ -610,7 +610,7 b' def create_repo(request, apiuser, repo_n' | |||||
610 | } |
|
610 | } | |
611 |
|
611 | |||
612 | """ |
|
612 | """ | |
613 | schema = RepoSchema() |
|
613 | schema = repo_schema.RepoSchema() | |
614 | try: |
|
614 | try: | |
615 | data = schema.deserialize({ |
|
615 | data = schema.deserialize({ | |
616 | 'repo_name': repo_name |
|
616 | 'repo_name': repo_name |
@@ -34,7 +34,7 b' from rhodecode.lib.auth import (' | |||||
34 | from rhodecode.model.db import Session, RepoGroup |
|
34 | from rhodecode.model.db import Session, RepoGroup | |
35 | from rhodecode.model.repo_group import RepoGroupModel |
|
35 | from rhodecode.model.repo_group import RepoGroupModel | |
36 | from rhodecode.model.scm import RepoGroupList |
|
36 | from rhodecode.model.scm import RepoGroupList | |
37 |
from rhodecode.model.validation_schema import |
|
37 | from rhodecode.model.validation_schema.schemas import repo_group_schema | |
38 |
|
38 | |||
39 |
|
39 | |||
40 | log = logging.getLogger(__name__) |
|
40 | log = logging.getLogger(__name__) | |
@@ -193,7 +193,7 b' def create_repo_group(request, apiuser, ' | |||||
193 |
|
193 | |||
194 | """ |
|
194 | """ | |
195 |
|
195 | |||
196 | schema = RepoGroupSchema() |
|
196 | schema = repo_group_schema.RepoGroupSchema() | |
197 | try: |
|
197 | try: | |
198 | data = schema.deserialize({ |
|
198 | data = schema.deserialize({ | |
199 | 'group_name': group_name |
|
199 | 'group_name': group_name |
@@ -25,15 +25,18 b' gist controller for RhodeCode' | |||||
25 |
|
25 | |||
26 | import time |
|
26 | import time | |
27 | import logging |
|
27 | import logging | |
28 | import traceback |
|
28 | ||
29 | import formencode |
|
29 | import formencode | |
|
30 | import peppercorn | |||
30 | from formencode import htmlfill |
|
31 | from formencode import htmlfill | |
31 |
|
32 | |||
32 | from pylons import request, response, tmpl_context as c, url |
|
33 | from pylons import request, response, tmpl_context as c, url | |
33 | from pylons.controllers.util import abort, redirect |
|
34 | from pylons.controllers.util import abort, redirect | |
34 | from pylons.i18n.translation import _ |
|
35 | from pylons.i18n.translation import _ | |
|
36 | from webob.exc import HTTPNotFound, HTTPForbidden | |||
|
37 | from sqlalchemy.sql.expression import or_ | |||
35 |
|
38 | |||
36 | from rhodecode.model.forms import GistForm |
|
39 | ||
37 | from rhodecode.model.gist import GistModel |
|
40 | from rhodecode.model.gist import GistModel | |
38 | from rhodecode.model.meta import Session |
|
41 | from rhodecode.model.meta import Session | |
39 | from rhodecode.model.db import Gist, User |
|
42 | from rhodecode.model.db import Gist, User | |
@@ -44,9 +47,10 b' from rhodecode.lib.auth import LoginRequ' | |||||
44 | from rhodecode.lib.utils import jsonify |
|
47 | from rhodecode.lib.utils import jsonify | |
45 | from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime |
|
48 | from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime | |
46 | from rhodecode.lib.ext_json import json |
|
49 | from rhodecode.lib.ext_json import json | |
47 | from webob.exc import HTTPNotFound, HTTPForbidden |
|
|||
48 | from sqlalchemy.sql.expression import or_ |
|
|||
49 | from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError |
|
50 | from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError | |
|
51 | from rhodecode.model import validation_schema | |||
|
52 | from rhodecode.model.validation_schema.schemas import gist_schema | |||
|
53 | ||||
50 |
|
54 | |||
51 | log = logging.getLogger(__name__) |
|
55 | log = logging.getLogger(__name__) | |
52 |
|
56 | |||
@@ -56,11 +60,11 b' class GistsController(BaseController):' | |||||
56 |
|
60 | |||
57 | def __load_defaults(self, extra_values=None): |
|
61 | def __load_defaults(self, extra_values=None): | |
58 | c.lifetime_values = [ |
|
62 | c.lifetime_values = [ | |
59 |
( |
|
63 | (-1, _('forever')), | |
60 |
( |
|
64 | (5, _('5 minutes')), | |
61 |
( |
|
65 | (60, _('1 hour')), | |
62 |
|
|
66 | (60 * 24, _('1 day')), | |
63 |
|
|
67 | (60 * 24 * 30, _('1 month')), | |
64 | ] |
|
68 | ] | |
65 | if extra_values: |
|
69 | if extra_values: | |
66 | c.lifetime_values.append(extra_values) |
|
70 | c.lifetime_values.append(extra_values) | |
@@ -136,40 +140,56 b' class GistsController(BaseController):' | |||||
136 | """POST /admin/gists: Create a new item""" |
|
140 | """POST /admin/gists: Create a new item""" | |
137 | # url('gists') |
|
141 | # url('gists') | |
138 | self.__load_defaults() |
|
142 | self.__load_defaults() | |
139 | gist_form = GistForm([x[0] for x in c.lifetime_values], |
|
143 | ||
140 | [x[0] for x in c.acl_options])() |
|
144 | data = dict(request.POST) | |
|
145 | data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME | |||
|
146 | data['nodes'] = [{ | |||
|
147 | 'filename': data['filename'], | |||
|
148 | 'content': data.get('content'), | |||
|
149 | 'mimetype': data.get('mimetype') # None is autodetect | |||
|
150 | }] | |||
|
151 | ||||
|
152 | data['gist_type'] = ( | |||
|
153 | Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE) | |||
|
154 | data['gist_acl_level'] = ( | |||
|
155 | data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE) | |||
|
156 | ||||
|
157 | schema = gist_schema.GistSchema().bind( | |||
|
158 | lifetime_options=[x[0] for x in c.lifetime_values]) | |||
|
159 | ||||
141 | try: |
|
160 | try: | |
142 | form_result = gist_form.to_python(dict(request.POST)) |
|
161 | ||
143 | # TODO: multiple files support, from the form |
|
162 | schema_data = schema.deserialize(data) | |
144 | filename = form_result['filename'] or Gist.DEFAULT_FILENAME |
|
163 | # convert to safer format with just KEYs so we sure no duplicates | |
145 | nodes = { |
|
164 | schema_data['nodes'] = gist_schema.sequence_to_nodes( | |
146 | filename: { |
|
165 | schema_data['nodes']) | |
147 | 'content': form_result['content'], |
|
166 | ||
148 | 'lexer': form_result['mimetype'] # None is autodetect |
|
|||
149 | } |
|
|||
150 | } |
|
|||
151 | _public = form_result['public'] |
|
|||
152 | gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE |
|
|||
153 | gist_acl_level = form_result.get( |
|
|||
154 | 'acl_level', Gist.ACL_LEVEL_PRIVATE) |
|
|||
155 | gist = GistModel().create( |
|
167 | gist = GistModel().create( | |
156 | description=form_result['description'], |
|
168 | gist_id=schema_data['gistid'], # custom access id not real ID | |
|
169 | description=schema_data['description'], | |||
157 | owner=c.rhodecode_user.user_id, |
|
170 | owner=c.rhodecode_user.user_id, | |
158 | gist_mapping=nodes, |
|
171 | gist_mapping=schema_data['nodes'], | |
159 | gist_type=gist_type, |
|
172 | gist_type=schema_data['gist_type'], | |
160 |
lifetime= |
|
173 | lifetime=schema_data['lifetime'], | |
161 | gist_id=form_result['gistid'], |
|
174 | gist_acl_level=schema_data['gist_acl_level'] | |
162 | gist_acl_level=gist_acl_level |
|
|||
163 | ) |
|
175 | ) | |
164 | Session().commit() |
|
176 | Session().commit() | |
165 | new_gist_id = gist.gist_access_id |
|
177 | new_gist_id = gist.gist_access_id | |
166 |
except |
|
178 | except validation_schema.Invalid as errors: | |
167 |
defaults = |
|
179 | defaults = data | |
|
180 | errors = errors.asdict() | |||
|
181 | ||||
|
182 | if 'nodes.0.content' in errors: | |||
|
183 | errors['content'] = errors['nodes.0.content'] | |||
|
184 | del errors['nodes.0.content'] | |||
|
185 | if 'nodes.0.filename' in errors: | |||
|
186 | errors['filename'] = errors['nodes.0.filename'] | |||
|
187 | del errors['nodes.0.filename'] | |||
168 |
|
188 | |||
169 | return formencode.htmlfill.render( |
|
189 | return formencode.htmlfill.render( | |
170 | render('admin/gists/new.html'), |
|
190 | render('admin/gists/new.html'), | |
171 | defaults=defaults, |
|
191 | defaults=defaults, | |
172 |
errors=errors |
|
192 | errors=errors, | |
173 | prefix_error=False, |
|
193 | prefix_error=False, | |
174 | encoding="UTF-8", |
|
194 | encoding="UTF-8", | |
175 | force_defaults=False |
|
195 | force_defaults=False | |
@@ -243,7 +263,8 b' class GistsController(BaseController):' | |||||
243 | log.exception("Exception in gist show") |
|
263 | log.exception("Exception in gist show") | |
244 | raise HTTPNotFound() |
|
264 | raise HTTPNotFound() | |
245 | if format == 'raw': |
|
265 | if format == 'raw': | |
246 |
content = '\n\n'.join([f.content for f in c.files |
|
266 | content = '\n\n'.join([f.content for f in c.files | |
|
267 | if (f_path is None or f.path == f_path)]) | |||
247 | response.content_type = 'text/plain' |
|
268 | response.content_type = 'text/plain' | |
248 | return content |
|
269 | return content | |
249 | return render('admin/gists/show.html') |
|
270 | return render('admin/gists/show.html') | |
@@ -252,32 +273,35 b' class GistsController(BaseController):' | |||||
252 | @NotAnonymous() |
|
273 | @NotAnonymous() | |
253 | @auth.CSRFRequired() |
|
274 | @auth.CSRFRequired() | |
254 | def edit(self, gist_id): |
|
275 | def edit(self, gist_id): | |
|
276 | self.__load_defaults() | |||
255 | self._add_gist_to_context(gist_id) |
|
277 | self._add_gist_to_context(gist_id) | |
256 |
|
278 | |||
257 | owner = c.gist.gist_owner == c.rhodecode_user.user_id |
|
279 | owner = c.gist.gist_owner == c.rhodecode_user.user_id | |
258 | if not (h.HasPermissionAny('hg.admin')() or owner): |
|
280 | if not (h.HasPermissionAny('hg.admin')() or owner): | |
259 | raise HTTPForbidden() |
|
281 | raise HTTPForbidden() | |
260 |
|
282 | |||
261 | rpost = request.POST |
|
283 | data = peppercorn.parse(request.POST.items()) | |
262 | nodes = {} |
|
284 | ||
263 | _file_data = zip(rpost.getall('org_files'), rpost.getall('files'), |
|
285 | schema = gist_schema.GistSchema() | |
264 | rpost.getall('mimetypes'), rpost.getall('contents')) |
|
286 | schema = schema.bind( | |
265 | for org_filename, filename, mimetype, content in _file_data: |
|
287 | # '0' is special value to leave lifetime untouched | |
266 | nodes[org_filename] = { |
|
288 | lifetime_options=[x[0] for x in c.lifetime_values] + [0], | |
267 | 'org_filename': org_filename, |
|
289 | ) | |
268 | 'filename': filename, |
|
290 | ||
269 | 'content': content, |
|
|||
270 | 'lexer': mimetype, |
|
|||
271 | } |
|
|||
272 | try: |
|
291 | try: | |
|
292 | schema_data = schema.deserialize(data) | |||
|
293 | # convert to safer format with just KEYs so we sure no duplicates | |||
|
294 | schema_data['nodes'] = gist_schema.sequence_to_nodes( | |||
|
295 | schema_data['nodes']) | |||
|
296 | ||||
273 | GistModel().update( |
|
297 | GistModel().update( | |
274 | gist=c.gist, |
|
298 | gist=c.gist, | |
275 |
description= |
|
299 | description=schema_data['description'], | |
276 | owner=c.gist.owner, |
|
300 | owner=c.gist.owner, | |
277 | gist_mapping=nodes, |
|
301 | gist_mapping=schema_data['nodes'], | |
278 |
gist_type= |
|
302 | gist_type=schema_data['gist_type'], | |
279 |
lifetime= |
|
303 | lifetime=schema_data['lifetime'], | |
280 |
gist_acl_level= |
|
304 | gist_acl_level=schema_data['gist_acl_level'] | |
281 | ) |
|
305 | ) | |
282 |
|
306 | |||
283 | Session().commit() |
|
307 | Session().commit() | |
@@ -287,6 +311,10 b' class GistsController(BaseController):' | |||||
287 | # store only DB stuff for gist |
|
311 | # store only DB stuff for gist | |
288 | Session().commit() |
|
312 | Session().commit() | |
289 | h.flash(_('Successfully updated gist data'), category='success') |
|
313 | h.flash(_('Successfully updated gist data'), category='success') | |
|
314 | except validation_schema.Invalid as errors: | |||
|
315 | errors = errors.asdict() | |||
|
316 | h.flash(_('Error occurred during update of gist {}: {}').format( | |||
|
317 | gist_id, errors), category='error') | |||
290 | except Exception: |
|
318 | except Exception: | |
291 | log.exception("Exception in gist edit") |
|
319 | log.exception("Exception in gist edit") | |
292 | h.flash(_('Error occurred during update of gist %s') % gist_id, |
|
320 | h.flash(_('Error occurred during update of gist %s') % gist_id, | |
@@ -317,7 +345,7 b' class GistsController(BaseController):' | |||||
317 | # this cannot use timeago, since it's used in select2 as a value |
|
345 | # this cannot use timeago, since it's used in select2 as a value | |
318 | expiry = h.age(h.time_to_datetime(c.gist.gist_expires)) |
|
346 | expiry = h.age(h.time_to_datetime(c.gist.gist_expires)) | |
319 | self.__load_defaults( |
|
347 | self.__load_defaults( | |
320 |
extra_values=( |
|
348 | extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry})) | |
321 | return render('admin/gists/edit.html') |
|
349 | return render('admin/gists/edit.html') | |
322 |
|
350 | |||
323 | @LoginRequired() |
|
351 | @LoginRequired() |
@@ -35,6 +35,7 b' from rhodecode.lib.helpers import Page' | |||||
35 | from rhodecode.lib.utils2 import safe_str, safe_int |
|
35 | from rhodecode.lib.utils2 import safe_str, safe_int | |
36 | from rhodecode.lib.index import searcher_from_config |
|
36 | from rhodecode.lib.index import searcher_from_config | |
37 | from rhodecode.model import validation_schema |
|
37 | from rhodecode.model import validation_schema | |
|
38 | from rhodecode.model.validation_schema.schemas import search_schema | |||
38 |
|
39 | |||
39 | log = logging.getLogger(__name__) |
|
40 | log = logging.getLogger(__name__) | |
40 |
|
41 | |||
@@ -48,7 +49,7 b' class SearchController(BaseRepoControlle' | |||||
48 | formatted_results = [] |
|
49 | formatted_results = [] | |
49 | execution_time = '' |
|
50 | execution_time = '' | |
50 |
|
51 | |||
51 |
schema = |
|
52 | schema = search_schema.SearchParamsSchema() | |
52 |
|
53 | |||
53 | search_params = {} |
|
54 | search_params = {} | |
54 | errors = [] |
|
55 | errors = [] | |
@@ -75,7 +76,6 b' class SearchController(BaseRepoControlle' | |||||
75 | page_limit = search_params['page_limit'] |
|
76 | page_limit = search_params['page_limit'] | |
76 | requested_page = search_params['requested_page'] |
|
77 | requested_page = search_params['requested_page'] | |
77 |
|
78 | |||
78 |
|
||||
79 | c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id, |
|
79 | c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id, | |
80 | ip_addr=self.ip_addr) |
|
80 | ip_addr=self.ip_addr) | |
81 |
|
81 |
@@ -555,23 +555,6 b' def PullRequestForm(repo_id):' | |||||
555 | return _PullRequestForm |
|
555 | return _PullRequestForm | |
556 |
|
556 | |||
557 |
|
557 | |||
558 | def GistForm(lifetime_options, acl_level_options): |
|
|||
559 | class _GistForm(formencode.Schema): |
|
|||
560 |
|
||||
561 | gistid = All(v.UniqGistId(), v.UnicodeString(strip=True, min=3, not_empty=False, if_missing=None)) |
|
|||
562 | filename = All(v.BasePath()(), |
|
|||
563 | v.UnicodeString(strip=True, required=False)) |
|
|||
564 | description = v.UnicodeString(required=False, if_missing=u'') |
|
|||
565 | lifetime = v.OneOf(lifetime_options) |
|
|||
566 | mimetype = v.UnicodeString(required=False, if_missing=None) |
|
|||
567 | content = v.UnicodeString(required=True, not_empty=True) |
|
|||
568 | public = v.UnicodeString(required=False, if_missing=u'') |
|
|||
569 | private = v.UnicodeString(required=False, if_missing=u'') |
|
|||
570 | acl_level = v.OneOf(acl_level_options) |
|
|||
571 |
|
||||
572 | return _GistForm |
|
|||
573 |
|
||||
574 |
|
||||
575 | def IssueTrackerPatternsForm(): |
|
558 | def IssueTrackerPatternsForm(): | |
576 | class _IssueTrackerPatternsForm(formencode.Schema): |
|
559 | class _IssueTrackerPatternsForm(formencode.Schema): | |
577 | allow_extra_fields = True |
|
560 | allow_extra_fields = True |
@@ -107,7 +107,7 b' class GistModel(BaseModel):' | |||||
107 |
|
107 | |||
108 | :param description: description of the gist |
|
108 | :param description: description of the gist | |
109 | :param owner: user who created this gist |
|
109 | :param owner: user who created this gist | |
110 |
:param gist_mapping: mapping {filename |
|
110 | :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}] | |
111 | :param gist_type: type of gist private/public |
|
111 | :param gist_type: type of gist private/public | |
112 | :param lifetime: in minutes, -1 == forever |
|
112 | :param lifetime: in minutes, -1 == forever | |
113 | :param gist_acl_level: acl level for this gist |
|
113 | :param gist_acl_level: acl level for this gist | |
@@ -141,25 +141,10 b' class GistModel(BaseModel):' | |||||
141 | repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC, |
|
141 | repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC, | |
142 | use_global_config=True) |
|
142 | use_global_config=True) | |
143 |
|
143 | |||
144 | processed_mapping = {} |
|
|||
145 | for filename in gist_mapping: |
|
|||
146 | if filename != os.path.basename(filename): |
|
|||
147 | raise Exception('Filename cannot be inside a directory') |
|
|||
148 |
|
||||
149 | content = gist_mapping[filename]['content'] |
|
|||
150 | # TODO: expand support for setting explicit lexers |
|
|||
151 | # if lexer is None: |
|
|||
152 | # try: |
|
|||
153 | # guess_lexer = pygments.lexers.guess_lexer_for_filename |
|
|||
154 | # lexer = guess_lexer(filename,content) |
|
|||
155 | # except pygments.util.ClassNotFound: |
|
|||
156 | # lexer = 'text' |
|
|||
157 | processed_mapping[filename] = {'content': content} |
|
|||
158 |
|
||||
159 | # now create single multifile commit |
|
144 | # now create single multifile commit | |
160 | message = 'added file' |
|
145 | message = 'added file' | |
161 |
message += 's: ' if len( |
|
146 | message += 's: ' if len(gist_mapping) > 1 else ': ' | |
162 |
message += ', '.join([x for x in |
|
147 | message += ', '.join([x for x in gist_mapping]) | |
163 |
|
148 | |||
164 | # fake RhodeCode Repository object |
|
149 | # fake RhodeCode Repository object | |
165 | fake_repo = AttributeDict({ |
|
150 | fake_repo = AttributeDict({ | |
@@ -170,7 +155,7 b' class GistModel(BaseModel):' | |||||
170 | ScmModel().create_nodes( |
|
155 | ScmModel().create_nodes( | |
171 | user=owner.user_id, repo=fake_repo, |
|
156 | user=owner.user_id, repo=fake_repo, | |
172 | message=message, |
|
157 | message=message, | |
173 |
nodes= |
|
158 | nodes=gist_mapping, | |
174 | trigger_push_hook=False |
|
159 | trigger_push_hook=False | |
175 | ) |
|
160 | ) | |
176 |
|
161 | |||
@@ -196,7 +181,6 b' class GistModel(BaseModel):' | |||||
196 | gist = self._get_gist(gist) |
|
181 | gist = self._get_gist(gist) | |
197 | gist_repo = gist.scm_instance() |
|
182 | gist_repo = gist.scm_instance() | |
198 |
|
183 | |||
199 | lifetime = safe_int(lifetime, -1) |
|
|||
200 | if lifetime == 0: # preserve old value |
|
184 | if lifetime == 0: # preserve old value | |
201 | gist_expires = gist.gist_expires |
|
185 | gist_expires = gist.gist_expires | |
202 | else: |
|
186 | else: | |
@@ -207,9 +191,9 b' class GistModel(BaseModel):' | |||||
207 | gist_mapping_op = {} |
|
191 | gist_mapping_op = {} | |
208 | for k, v in gist_mapping.items(): |
|
192 | for k, v in gist_mapping.items(): | |
209 | # add, mod, del |
|
193 | # add, mod, del | |
210 |
if not v[' |
|
194 | if not v['filename_org'] and v['filename']: | |
211 | op = 'add' |
|
195 | op = 'add' | |
212 |
elif v[' |
|
196 | elif v['filename_org'] and not v['filename']: | |
213 | op = 'del' |
|
197 | op = 'del' | |
214 | else: |
|
198 | else: | |
215 | op = 'mod' |
|
199 | op = 'mod' |
@@ -970,22 +970,6 b' def FieldKey():' | |||||
970 | return _validator |
|
970 | return _validator | |
971 |
|
971 | |||
972 |
|
972 | |||
973 | def BasePath(): |
|
|||
974 | class _validator(formencode.validators.FancyValidator): |
|
|||
975 | messages = { |
|
|||
976 | 'badPath': _(u'Filename cannot be inside a directory'), |
|
|||
977 | } |
|
|||
978 |
|
||||
979 | def _to_python(self, value, state): |
|
|||
980 | return value |
|
|||
981 |
|
||||
982 | def validate_python(self, value, state): |
|
|||
983 | if value != os.path.basename(value): |
|
|||
984 | raise formencode.Invalid(self.message('badPath', state), |
|
|||
985 | value, state) |
|
|||
986 | return _validator |
|
|||
987 |
|
||||
988 |
|
||||
989 | def ValidAuthPlugins(): |
|
973 | def ValidAuthPlugins(): | |
990 | class _validator(formencode.validators.FancyValidator): |
|
974 | class _validator(formencode.validators.FancyValidator): | |
991 | messages = { |
|
975 | messages = { | |
@@ -1061,26 +1045,6 b' def ValidAuthPlugins():' | |||||
1061 | return _validator |
|
1045 | return _validator | |
1062 |
|
1046 | |||
1063 |
|
1047 | |||
1064 | def UniqGistId(): |
|
|||
1065 | class _validator(formencode.validators.FancyValidator): |
|
|||
1066 | messages = { |
|
|||
1067 | 'gistid_taken': _(u'This gistid is already in use') |
|
|||
1068 | } |
|
|||
1069 |
|
||||
1070 | def _to_python(self, value, state): |
|
|||
1071 | return repo_name_slug(value.lower()) |
|
|||
1072 |
|
||||
1073 | def validate_python(self, value, state): |
|
|||
1074 | existing = Gist.get_by_access_id(value) |
|
|||
1075 | if existing: |
|
|||
1076 | msg = M(self, 'gistid_taken', state) |
|
|||
1077 | raise formencode.Invalid( |
|
|||
1078 | msg, value, state, error_dict={'gistid': msg} |
|
|||
1079 | ) |
|
|||
1080 |
|
||||
1081 | return _validator |
|
|||
1082 |
|
||||
1083 |
|
||||
1084 | def ValidPattern(): |
|
1048 | def ValidPattern(): | |
1085 |
|
1049 | |||
1086 | class _Validator(formencode.validators.FancyValidator): |
|
1050 | class _Validator(formencode.validators.FancyValidator): |
@@ -44,27 +44,31 b'' | |||||
44 | <label for='lifetime'>${_('Gist lifetime')}</label> |
|
44 | <label for='lifetime'>${_('Gist lifetime')}</label> | |
45 | ${h.dropdownmenu('lifetime', '0', c.lifetime_options)} |
|
45 | ${h.dropdownmenu('lifetime', '0', c.lifetime_options)} | |
46 |
|
46 | |||
47 | <label for='acl_level'>${_('Gist access level')}</label> |
|
47 | <label for='gist_acl_level'>${_('Gist access level')}</label> | |
48 | ${h.dropdownmenu('acl_level', c.gist.acl_level, c.acl_options)} |
|
48 | ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)} | |
49 | </div> |
|
49 | </div> | |
50 | </div> |
|
50 | </div> | |
51 |
|
51 | |||
|
52 | ## peppercorn schema | |||
|
53 | <input type="hidden" name="__start__" value="nodes:sequence"/> | |||
52 | % for cnt, file in enumerate(c.files): |
|
54 | % for cnt, file in enumerate(c.files): | |
|
55 | <input type="hidden" name="__start__" value="file:mapping"/> | |||
53 | <div id="codeblock" class="codeblock" > |
|
56 | <div id="codeblock" class="codeblock" > | |
54 | <div class="code-header"> |
|
57 | <div class="code-header"> | |
55 | <div class="form"> |
|
58 | <div class="form"> | |
56 | <div class="fields"> |
|
59 | <div class="fields"> | |
57 |
<input type="hidden" value="${file.path}" |
|
60 | <input type="hidden" name="filename_org" value="${file.path}" > | |
58 |
<input id="filename_${h.FID('f',file.path)}" name="file |
|
61 | <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}"> | |
59 |
${h.dropdownmenu('mimetype |
|
62 | ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))} | |
60 | </div> |
|
63 | </div> | |
61 | </div> |
|
64 | </div> | |
62 | </div> |
|
65 | </div> | |
63 | <div class="editor_container"> |
|
66 | <div class="editor_container"> | |
64 | <pre id="editor_pre"></pre> |
|
67 | <pre id="editor_pre"></pre> | |
65 |
<textarea id="editor_${h.FID('f',file.path)}" name="content |
|
68 | <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea> | |
66 | </div> |
|
69 | </div> | |
67 | </div> |
|
70 | </div> | |
|
71 | <input type="hidden" name="__end__" /> | |||
68 |
|
72 | |||
69 | ## dynamic edit box. |
|
73 | ## dynamic edit box. | |
70 | <script type="text/javascript"> |
|
74 | <script type="text/javascript"> | |
@@ -72,7 +76,7 b'' | |||||
72 | var myCodeMirror = initCodeMirror( |
|
76 | var myCodeMirror = initCodeMirror( | |
73 | "editor_${h.FID('f',file.path)}", ''); |
|
77 | "editor_${h.FID('f',file.path)}", ''); | |
74 |
|
78 | |||
75 |
var modes_select = $( |
|
79 | var modes_select = $("#mimetype_${h.FID('f',file.path)}"); | |
76 | fillCodeMirrorOptions(modes_select); |
|
80 | fillCodeMirrorOptions(modes_select); | |
77 |
|
81 | |||
78 | // try to detect the mode based on the file we edit |
|
82 | // try to detect the mode based on the file we edit | |
@@ -86,7 +90,7 b'' | |||||
86 | setCodeMirrorMode(myCodeMirror, detected_mode); |
|
90 | setCodeMirrorMode(myCodeMirror, detected_mode); | |
87 | } |
|
91 | } | |
88 |
|
92 | |||
89 |
var filename_selector = |
|
93 | var filename_selector = "#filename_${h.FID('f',file.path)}"; | |
90 | // on change of select field set mode |
|
94 | // on change of select field set mode | |
91 | setCodeMirrorModeFromSelect( |
|
95 | setCodeMirrorModeFromSelect( | |
92 | modes_select, filename_selector, myCodeMirror, null); |
|
96 | modes_select, filename_selector, myCodeMirror, null); | |
@@ -96,8 +100,8 b'' | |||||
96 | modes_select, filename_selector, myCodeMirror, null); |
|
100 | modes_select, filename_selector, myCodeMirror, null); | |
97 | }); |
|
101 | }); | |
98 | </script> |
|
102 | </script> | |
99 |
|
||||
100 | %endfor |
|
103 | %endfor | |
|
104 | <input type="hidden" name="__end__" /> | |||
101 |
|
105 | |||
102 | <div class="pull-right"> |
|
106 | <div class="pull-right"> | |
103 | ${h.submit('update',_('Update Gist'),class_="btn btn-success")} |
|
107 | ${h.submit('update',_('Update Gist'),class_="btn btn-success")} |
@@ -39,7 +39,7 b'' | |||||
39 | ${h.dropdownmenu('lifetime', '', c.lifetime_options)} |
|
39 | ${h.dropdownmenu('lifetime', '', c.lifetime_options)} | |
40 |
|
40 | |||
41 | <label for='acl_level'>${_('Gist access level')}</label> |
|
41 | <label for='acl_level'>${_('Gist access level')}</label> | |
42 | ${h.dropdownmenu('acl_level', '', c.acl_options)} |
|
42 | ${h.dropdownmenu('gist_acl_level', '', c.acl_options)} | |
43 |
|
43 | |||
44 | </div> |
|
44 | </div> | |
45 | <div id="codeblock" class="codeblock"> |
|
45 | <div id="codeblock" class="codeblock"> |
@@ -129,23 +129,15 b' class TestGistsController(TestController' | |||||
129 | for gist in GistModel.get_all(): |
|
129 | for gist in GistModel.get_all(): | |
130 | response.mustcontain(no=['gist: %s' % gist.gist_access_id]) |
|
130 | response.mustcontain(no=['gist: %s' % gist.gist_access_id]) | |
131 |
|
131 | |||
132 |
def test_create |
|
132 | def test_create(self): | |
133 | self.log_user() |
|
133 | self.log_user() | |
134 | response = self.app.post( |
|
134 | response = self.app.post( | |
135 | url('gists'), |
|
135 | url('gists'), | |
136 | params={'lifetime': -1, 'csrf_token': self.csrf_token}, |
|
|||
137 | status=200) |
|
|||
138 |
|
||||
139 | response.mustcontain('Missing value') |
|
|||
140 |
|
||||
141 | def test_create(self): |
|
|||
142 | self.log_user() |
|
|||
143 | response = self.app.post(url('gists'), |
|
|||
144 |
|
|
136 | params={'lifetime': -1, | |
145 |
|
|
137 | 'content': 'gist test', | |
146 |
|
|
138 | 'filename': 'foo', | |
147 |
|
|
139 | 'public': 'public', | |
148 |
|
|
140 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, | |
149 |
|
|
141 | 'csrf_token': self.csrf_token}, | |
150 |
|
|
142 | status=302) | |
151 | response = response.follow() |
|
143 | response = response.follow() | |
@@ -154,15 +146,16 b' class TestGistsController(TestController' | |||||
154 |
|
146 | |||
155 | def test_create_with_path_with_dirs(self): |
|
147 | def test_create_with_path_with_dirs(self): | |
156 | self.log_user() |
|
148 | self.log_user() | |
157 |
response = self.app.post( |
|
149 | response = self.app.post( | |
|
150 | url('gists'), | |||
158 |
|
|
151 | params={'lifetime': -1, | |
159 |
|
|
152 | 'content': 'gist test', | |
160 |
|
|
153 | 'filename': '/home/foo', | |
161 |
|
|
154 | 'public': 'public', | |
162 |
|
|
155 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, | |
163 |
|
|
156 | 'csrf_token': self.csrf_token}, | |
164 |
|
|
157 | status=200) | |
165 | response.mustcontain('Filename cannot be inside a directory') |
|
158 | response.mustcontain('Filename /home/foo cannot be inside a directory') | |
166 |
|
159 | |||
167 | def test_access_expired_gist(self, create_gist): |
|
160 | def test_access_expired_gist(self, create_gist): | |
168 | self.log_user() |
|
161 | self.log_user() | |
@@ -175,12 +168,13 b' class TestGistsController(TestController' | |||||
175 |
|
168 | |||
176 | def test_create_private(self): |
|
169 | def test_create_private(self): | |
177 | self.log_user() |
|
170 | self.log_user() | |
178 |
response = self.app.post( |
|
171 | response = self.app.post( | |
|
172 | url('gists'), | |||
179 |
|
|
173 | params={'lifetime': -1, | |
180 |
|
|
174 | 'content': 'private gist test', | |
181 |
|
|
175 | 'filename': 'private-foo', | |
182 |
|
|
176 | 'private': 'private', | |
183 |
|
|
177 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, | |
184 |
|
|
178 | 'csrf_token': self.csrf_token}, | |
185 |
|
|
179 | status=302) | |
186 | response = response.follow() |
|
180 | response = response.follow() | |
@@ -193,12 +187,13 b' class TestGistsController(TestController' | |||||
193 |
|
187 | |||
194 | def test_create_private_acl_private(self): |
|
188 | def test_create_private_acl_private(self): | |
195 | self.log_user() |
|
189 | self.log_user() | |
196 |
response = self.app.post( |
|
190 | response = self.app.post( | |
|
191 | url('gists'), | |||
197 |
|
|
192 | params={'lifetime': -1, | |
198 |
|
|
193 | 'content': 'private gist test', | |
199 |
|
|
194 | 'filename': 'private-foo', | |
200 |
|
|
195 | 'private': 'private', | |
201 |
|
|
196 | 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE, | |
202 |
|
|
197 | 'csrf_token': self.csrf_token}, | |
203 |
|
|
198 | status=302) | |
204 | response = response.follow() |
|
199 | response = response.follow() | |
@@ -211,13 +206,14 b' class TestGistsController(TestController' | |||||
211 |
|
206 | |||
212 | def test_create_with_description(self): |
|
207 | def test_create_with_description(self): | |
213 | self.log_user() |
|
208 | self.log_user() | |
214 |
response = self.app.post( |
|
209 | response = self.app.post( | |
|
210 | url('gists'), | |||
215 |
|
|
211 | params={'lifetime': -1, | |
216 |
|
|
212 | 'content': 'gist test', | |
217 |
|
|
213 | 'filename': 'foo-desc', | |
218 |
|
|
214 | 'description': 'gist-desc', | |
219 |
|
|
215 | 'public': 'public', | |
220 |
|
|
216 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, | |
221 |
|
|
217 | 'csrf_token': self.csrf_token}, | |
222 |
|
|
218 | status=302) | |
223 | response = response.follow() |
|
219 | response = response.follow() | |
@@ -233,7 +229,7 b' class TestGistsController(TestController' | |||||
233 | 'filename': 'foo-desc', |
|
229 | 'filename': 'foo-desc', | |
234 | 'description': 'gist-desc', |
|
230 | 'description': 'gist-desc', | |
235 | 'public': 'public', |
|
231 | 'public': 'public', | |
236 | 'acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
232 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, | |
237 | 'csrf_token': self.csrf_token |
|
233 | 'csrf_token': self.csrf_token | |
238 | } |
|
234 | } | |
239 | response = self.app.post(url('gists'), params=params, status=302) |
|
235 | response = self.app.post(url('gists'), params=params, status=302) |
@@ -21,7 +21,7 b'' | |||||
21 | import colander |
|
21 | import colander | |
22 | import pytest |
|
22 | import pytest | |
23 |
|
23 | |||
24 | from rhodecode.model.validation_schema import GroupNameType |
|
24 | from rhodecode.model.validation_schema.types import GroupNameType | |
25 |
|
25 | |||
26 |
|
26 | |||
27 | class TestGroupNameType(object): |
|
27 | class TestGroupNameType(object): |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now