##// END OF EJS Templates
gists: use colander schema to validate input data....
marcink -
r523:878882bd default
parent child Browse files
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
@@ -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 30 from pyramid.response import Response
31 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 35 from rhodecode.lib.auth import AuthUser
35 36 from rhodecode.lib.base import get_ip_addr
36 37 from rhodecode.lib.ext_json import json
@@ -127,6 +128,11 b' def exception_view(exc, request):'
127 128 if isinstance(exc, JSONRPCError):
128 129 fault_message = exc.message
129 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 136 elif isinstance(exc, JSONRPCForbidden):
131 137 fault_message = 'Access was denied to this resource.'
132 138 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
@@ -27,5 +27,13 b' class JSONRPCError(JSONRPCBaseError):'
27 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 37 class JSONRPCForbidden(JSONRPCBaseError):
31 38 pass
39
@@ -43,7 +43,7 b' class TestApiCreateGist(object):'
43 43 description='foobar-gist',
44 44 gist_type=gist_type,
45 45 acl_level=gist_acl_level,
46 files={'foobar': {'content': 'foo'}})
46 files={'foobar_ąć': {'content': 'foo'}})
47 47 response = api_call(self.app, params)
48 48 response_json = response.json
49 49 gist = response_json['result']['gist']
@@ -68,6 +68,32 b' class TestApiCreateGist(object):'
68 68 finally:
69 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 97 @mock.patch.object(GistModel, 'create', crash)
72 98 def test_api_create_gist_exception_occurred(self):
73 99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
@@ -34,8 +34,6 b' from rhodecode.lib.vcs.exceptions import'
34 34 log = logging.getLogger(__name__)
35 35
36 36
37
38
39 37 class OAttr(object):
40 38 """
41 39 Special Option that defines other attribute, and can default to them
@@ -23,6 +23,7 b' import logging'
23 23 import time
24 24
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.exc import JSONRPCValidationError
26 27 from rhodecode.api.utils import (
27 28 Optional, OAttr, get_gist_or_error, get_user_or_error,
28 29 has_superadmin_permission)
@@ -96,7 +97,8 b' def get_gists(request, apiuser, userid=O'
96 97
97 98 @jsonrpc_method()
98 99 def create_gist(
99 request, apiuser, files, owner=Optional(OAttr('apiuser')),
100 request, apiuser, files, gistid=Optional(None),
101 owner=Optional(OAttr('apiuser')),
100 102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
101 103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
102 104 description=Optional('')):
@@ -108,10 +110,11 b' def create_gist('
108 110 :param files: files to be added to the gist. The data structure has
109 111 to match the following example::
110 112
111 {'filename': {'content':'...', 'lexer': null},
112 'filename2': {'content':'...', 'lexer': null}}
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
113 114
114 115 :type files: dict
116 :param gistid: Set a custom id for the gist
117 :type gistid: Optional(str)
115 118 :param owner: Set the gist owner, defaults to api method caller
116 119 :type owner: Optional(str or int)
117 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
156
157 if isinstance(owner, Optional):
158 owner = apiuser.user_id
159
160 owner = get_user_or_error(owner)
161
162 lifetime = Optional.extract(lifetime)
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])
151 167
152 168 try:
153 if isinstance(owner, Optional):
154 owner = apiuser.user_id
169 nodes = gist_schema.nodes_to_sequence(
170 files, colander_node=schema.get('nodes'))
171
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 ))
155 180
156 owner = get_user_or_error(owner)
157 description = Optional.extract(description)
158 gist_type = Optional.extract(gist_type)
159 lifetime = Optional.extract(lifetime)
160 acl_level = Optional.extract(acl_level)
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)
161 187
162 gist = GistModel().create(description=description,
163 owner=owner,
164 gist_mapping=files,
165 gist_type=gist_type,
166 lifetime=lifetime,
167 gist_acl_level=acl_level)
188 try:
189 gist = GistModel().create(
190 owner=owner,
191 gist_id=schema_data['gistid'],
192 description=schema_data['description'],
193 gist_mapping=schema_data['nodes'],
194 gist_type=schema_data['gist_type'],
195 lifetime=schema_data['lifetime'],
196 gist_acl_level=schema_data['gist_acl_level'])
168 197 Session().commit()
169 198 return {
170 199 'msg': 'created new gist',
@@ -44,7 +44,7 b' from rhodecode.model.repo import RepoMod'
44 44 from rhodecode.model.repo_group import RepoGroupModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 from rhodecode.model.validation_schema import RepoSchema
47 from rhodecode.model.validation_schema.schemas import repo_schema
48 48
49 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 614 try:
615 615 data = schema.deserialize({
616 616 'repo_name': repo_name
@@ -34,7 +34,7 b' from rhodecode.lib.auth import ('
34 34 from rhodecode.model.db import Session, RepoGroup
35 35 from rhodecode.model.repo_group import RepoGroupModel
36 36 from rhodecode.model.scm import RepoGroupList
37 from rhodecode.model.validation_schema import RepoGroupSchema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38 38
39 39
40 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 197 try:
198 198 data = schema.deserialize({
199 199 'group_name': group_name
@@ -25,15 +25,18 b' gist controller for RhodeCode'
25 25
26 26 import time
27 27 import logging
28 import traceback
28
29 29 import formencode
30 import peppercorn
30 31 from formencode import htmlfill
31 32
32 33 from pylons import request, response, tmpl_context as c, url
33 34 from pylons.controllers.util import abort, redirect
34 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 40 from rhodecode.model.gist import GistModel
38 41 from rhodecode.model.meta import Session
39 42 from rhodecode.model.db import Gist, User
@@ -44,9 +47,10 b' from rhodecode.lib.auth import LoginRequ'
44 47 from rhodecode.lib.utils import jsonify
45 48 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
46 49 from rhodecode.lib.ext_json import json
47 from webob.exc import HTTPNotFound, HTTPForbidden
48 from sqlalchemy.sql.expression import or_
49 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 55 log = logging.getLogger(__name__)
52 56
@@ -56,11 +60,11 b' class GistsController(BaseController):'
56 60
57 61 def __load_defaults(self, extra_values=None):
58 62 c.lifetime_values = [
59 (str(-1), _('forever')),
60 (str(5), _('5 minutes')),
61 (str(60), _('1 hour')),
62 (str(60 * 24), _('1 day')),
63 (str(60 * 24 * 30), _('1 month')),
63 (-1, _('forever')),
64 (5, _('5 minutes')),
65 (60, _('1 hour')),
66 (60 * 24, _('1 day')),
67 (60 * 24 * 30, _('1 month')),
64 68 ]
65 69 if extra_values:
66 70 c.lifetime_values.append(extra_values)
@@ -136,40 +140,56 b' class GistsController(BaseController):'
136 140 """POST /admin/gists: Create a new item"""
137 141 # url('gists')
138 142 self.__load_defaults()
139 gist_form = GistForm([x[0] for x in c.lifetime_values],
140 [x[0] for x in c.acl_options])()
143
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 160 try:
142 form_result = gist_form.to_python(dict(request.POST))
143 # TODO: multiple files support, from the form
144 filename = form_result['filename'] or Gist.DEFAULT_FILENAME
145 nodes = {
146 filename: {
147 'content': form_result['content'],
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)
161
162 schema_data = schema.deserialize(data)
163 # convert to safer format with just KEYs so we sure no duplicates
164 schema_data['nodes'] = gist_schema.sequence_to_nodes(
165 schema_data['nodes'])
166
155 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 170 owner=c.rhodecode_user.user_id,
158 gist_mapping=nodes,
159 gist_type=gist_type,
160 lifetime=form_result['lifetime'],
161 gist_id=form_result['gistid'],
162 gist_acl_level=gist_acl_level
171 gist_mapping=schema_data['nodes'],
172 gist_type=schema_data['gist_type'],
173 lifetime=schema_data['lifetime'],
174 gist_acl_level=schema_data['gist_acl_level']
163 175 )
164 176 Session().commit()
165 177 new_gist_id = gist.gist_access_id
166 except formencode.Invalid as errors:
167 defaults = errors.value
178 except validation_schema.Invalid as errors:
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 189 return formencode.htmlfill.render(
170 190 render('admin/gists/new.html'),
171 191 defaults=defaults,
172 errors=errors.error_dict or {},
192 errors=errors,
173 193 prefix_error=False,
174 194 encoding="UTF-8",
175 195 force_defaults=False
@@ -243,7 +263,8 b' class GistsController(BaseController):'
243 263 log.exception("Exception in gist show")
244 264 raise HTTPNotFound()
245 265 if format == 'raw':
246 content = '\n\n'.join([f.content for f in c.files if (f_path is None or f.path == f_path)])
266 content = '\n\n'.join([f.content for f in c.files
267 if (f_path is None or f.path == f_path)])
247 268 response.content_type = 'text/plain'
248 269 return content
249 270 return render('admin/gists/show.html')
@@ -252,32 +273,35 b' class GistsController(BaseController):'
252 273 @NotAnonymous()
253 274 @auth.CSRFRequired()
254 275 def edit(self, gist_id):
276 self.__load_defaults()
255 277 self._add_gist_to_context(gist_id)
256 278
257 279 owner = c.gist.gist_owner == c.rhodecode_user.user_id
258 280 if not (h.HasPermissionAny('hg.admin')() or owner):
259 281 raise HTTPForbidden()
260 282
261 rpost = request.POST
262 nodes = {}
263 _file_data = zip(rpost.getall('org_files'), rpost.getall('files'),
264 rpost.getall('mimetypes'), rpost.getall('contents'))
265 for org_filename, filename, mimetype, content in _file_data:
266 nodes[org_filename] = {
267 'org_filename': org_filename,
268 'filename': filename,
269 'content': content,
270 'lexer': mimetype,
271 }
283 data = peppercorn.parse(request.POST.items())
284
285 schema = gist_schema.GistSchema()
286 schema = schema.bind(
287 # '0' is special value to leave lifetime untouched
288 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
289 )
290
272 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 297 GistModel().update(
274 298 gist=c.gist,
275 description=rpost['description'],
299 description=schema_data['description'],
276 300 owner=c.gist.owner,
277 gist_mapping=nodes,
278 gist_type=c.gist.gist_type,
279 lifetime=rpost['lifetime'],
280 gist_acl_level=rpost['acl_level']
301 gist_mapping=schema_data['nodes'],
302 gist_type=schema_data['gist_type'],
303 lifetime=schema_data['lifetime'],
304 gist_acl_level=schema_data['gist_acl_level']
281 305 )
282 306
283 307 Session().commit()
@@ -287,6 +311,10 b' class GistsController(BaseController):'
287 311 # store only DB stuff for gist
288 312 Session().commit()
289 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 318 except Exception:
291 319 log.exception("Exception in gist edit")
292 320 h.flash(_('Error occurred during update of gist %s') % gist_id,
@@ -317,7 +345,7 b' class GistsController(BaseController):'
317 345 # this cannot use timeago, since it's used in select2 as a value
318 346 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
319 347 self.__load_defaults(
320 extra_values=('0', _('%(expiry)s - current value') % {'expiry': expiry}))
348 extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry}))
321 349 return render('admin/gists/edit.html')
322 350
323 351 @LoginRequired()
@@ -35,6 +35,7 b' from rhodecode.lib.helpers import Page'
35 35 from rhodecode.lib.utils2 import safe_str, safe_int
36 36 from rhodecode.lib.index import searcher_from_config
37 37 from rhodecode.model import validation_schema
38 from rhodecode.model.validation_schema.schemas import search_schema
38 39
39 40 log = logging.getLogger(__name__)
40 41
@@ -48,7 +49,7 b' class SearchController(BaseRepoControlle'
48 49 formatted_results = []
49 50 execution_time = ''
50 51
51 schema = validation_schema.SearchParamsSchema()
52 schema = search_schema.SearchParamsSchema()
52 53
53 54 search_params = {}
54 55 errors = []
@@ -75,7 +76,6 b' class SearchController(BaseRepoControlle'
75 76 page_limit = search_params['page_limit']
76 77 requested_page = search_params['requested_page']
77 78
78
79 79 c.perm_user = AuthUser(user_id=c.rhodecode_user.user_id,
80 80 ip_addr=self.ip_addr)
81 81
@@ -555,23 +555,6 b' def PullRequestForm(repo_id):'
555 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 558 def IssueTrackerPatternsForm():
576 559 class _IssueTrackerPatternsForm(formencode.Schema):
577 560 allow_extra_fields = True
@@ -107,7 +107,7 b' class GistModel(BaseModel):'
107 107
108 108 :param description: description of the gist
109 109 :param owner: user who created this gist
110 :param gist_mapping: mapping {filename:{'content':content},...}
110 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
111 111 :param gist_type: type of gist private/public
112 112 :param lifetime: in minutes, -1 == forever
113 113 :param gist_acl_level: acl level for this gist
@@ -141,25 +141,10 b' class GistModel(BaseModel):'
141 141 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
142 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 144 # now create single multifile commit
160 145 message = 'added file'
161 message += 's: ' if len(processed_mapping) > 1 else ': '
162 message += ', '.join([x for x in processed_mapping])
146 message += 's: ' if len(gist_mapping) > 1 else ': '
147 message += ', '.join([x for x in gist_mapping])
163 148
164 149 # fake RhodeCode Repository object
165 150 fake_repo = AttributeDict({
@@ -170,7 +155,7 b' class GistModel(BaseModel):'
170 155 ScmModel().create_nodes(
171 156 user=owner.user_id, repo=fake_repo,
172 157 message=message,
173 nodes=processed_mapping,
158 nodes=gist_mapping,
174 159 trigger_push_hook=False
175 160 )
176 161
@@ -196,7 +181,6 b' class GistModel(BaseModel):'
196 181 gist = self._get_gist(gist)
197 182 gist_repo = gist.scm_instance()
198 183
199 lifetime = safe_int(lifetime, -1)
200 184 if lifetime == 0: # preserve old value
201 185 gist_expires = gist.gist_expires
202 186 else:
@@ -207,9 +191,9 b' class GistModel(BaseModel):'
207 191 gist_mapping_op = {}
208 192 for k, v in gist_mapping.items():
209 193 # add, mod, del
210 if not v['org_filename'] and v['filename']:
194 if not v['filename_org'] and v['filename']:
211 195 op = 'add'
212 elif v['org_filename'] and not v['filename']:
196 elif v['filename_org'] and not v['filename']:
213 197 op = 'del'
214 198 else:
215 199 op = 'mod'
@@ -970,22 +970,6 b' def FieldKey():'
970 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 973 def ValidAuthPlugins():
990 974 class _validator(formencode.validators.FancyValidator):
991 975 messages = {
@@ -1061,26 +1045,6 b' def ValidAuthPlugins():'
1061 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 1048 def ValidPattern():
1085 1049
1086 1050 class _Validator(formencode.validators.FancyValidator):
@@ -44,27 +44,31 b''
44 44 <label for='lifetime'>${_('Gist lifetime')}</label>
45 45 ${h.dropdownmenu('lifetime', '0', c.lifetime_options)}
46 46
47 <label for='acl_level'>${_('Gist access level')}</label>
48 ${h.dropdownmenu('acl_level', c.gist.acl_level, c.acl_options)}
47 <label for='gist_acl_level'>${_('Gist access level')}</label>
48 ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)}
49 49 </div>
50 50 </div>
51 51
52 ## peppercorn schema
53 <input type="hidden" name="__start__" value="nodes:sequence"/>
52 54 % for cnt, file in enumerate(c.files):
55 <input type="hidden" name="__start__" value="file:mapping"/>
53 56 <div id="codeblock" class="codeblock" >
54 57 <div class="code-header">
55 58 <div class="form">
56 59 <div class="fields">
57 <input type="hidden" value="${file.path}" name="org_files">
58 <input id="filename_${h.FID('f',file.path)}" name="files" size="30" type="text" value="${file.path}">
59 ${h.dropdownmenu('mimetypes' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
60 <input type="hidden" name="filename_org" value="${file.path}" >
61 <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}">
62 ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
60 63 </div>
61 64 </div>
62 65 </div>
63 66 <div class="editor_container">
64 67 <pre id="editor_pre"></pre>
65 <textarea id="editor_${h.FID('f',file.path)}" name="contents" >${file.content}</textarea>
68 <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea>
66 69 </div>
67 70 </div>
71 <input type="hidden" name="__end__" />
68 72
69 73 ## dynamic edit box.
70 74 <script type="text/javascript">
@@ -72,7 +76,7 b''
72 76 var myCodeMirror = initCodeMirror(
73 77 "editor_${h.FID('f',file.path)}", '');
74 78
75 var modes_select = $('#mimetype_${h.FID('f',file.path)}');
79 var modes_select = $("#mimetype_${h.FID('f',file.path)}");
76 80 fillCodeMirrorOptions(modes_select);
77 81
78 82 // try to detect the mode based on the file we edit
@@ -86,7 +90,7 b''
86 90 setCodeMirrorMode(myCodeMirror, detected_mode);
87 91 }
88 92
89 var filename_selector = '#filename_${h.FID('f',file.path)}';
93 var filename_selector = "#filename_${h.FID('f',file.path)}";
90 94 // on change of select field set mode
91 95 setCodeMirrorModeFromSelect(
92 96 modes_select, filename_selector, myCodeMirror, null);
@@ -96,8 +100,8 b''
96 100 modes_select, filename_selector, myCodeMirror, null);
97 101 });
98 102 </script>
99
100 103 %endfor
104 <input type="hidden" name="__end__" />
101 105
102 106 <div class="pull-right">
103 107 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
@@ -39,7 +39,7 b''
39 39 ${h.dropdownmenu('lifetime', '', c.lifetime_options)}
40 40
41 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 44 </div>
45 45 <div id="codeblock" class="codeblock">
@@ -129,40 +129,33 b' class TestGistsController(TestController'
129 129 for gist in GistModel.get_all():
130 130 response.mustcontain(no=['gist: %s' % gist.gist_access_id])
131 131
132 def test_create_missing_description(self):
132 def test_create(self):
133 133 self.log_user()
134 134 response = self.app.post(
135 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 params={'lifetime': -1,
145 'content': 'gist test',
146 'filename': 'foo',
147 'public': 'public',
148 'acl_level': Gist.ACL_LEVEL_PUBLIC,
149 'csrf_token': self.csrf_token},
150 status=302)
136 params={'lifetime': -1,
137 'content': 'gist test',
138 'filename': 'foo',
139 'public': 'public',
140 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
141 'csrf_token': self.csrf_token},
142 status=302)
151 143 response = response.follow()
152 144 response.mustcontain('added file: foo')
153 145 response.mustcontain('gist test')
154 146
155 147 def test_create_with_path_with_dirs(self):
156 148 self.log_user()
157 response = self.app.post(url('gists'),
158 params={'lifetime': -1,
159 'content': 'gist test',
160 'filename': '/home/foo',
161 'public': 'public',
162 'acl_level': Gist.ACL_LEVEL_PUBLIC,
163 'csrf_token': self.csrf_token},
164 status=200)
165 response.mustcontain('Filename cannot be inside a directory')
149 response = self.app.post(
150 url('gists'),
151 params={'lifetime': -1,
152 'content': 'gist test',
153 'filename': '/home/foo',
154 'public': 'public',
155 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
156 'csrf_token': self.csrf_token},
157 status=200)
158 response.mustcontain('Filename /home/foo cannot be inside a directory')
166 159
167 160 def test_access_expired_gist(self, create_gist):
168 161 self.log_user()
@@ -175,14 +168,15 b' class TestGistsController(TestController'
175 168
176 169 def test_create_private(self):
177 170 self.log_user()
178 response = self.app.post(url('gists'),
179 params={'lifetime': -1,
180 'content': 'private gist test',
181 'filename': 'private-foo',
182 'private': 'private',
183 'acl_level': Gist.ACL_LEVEL_PUBLIC,
184 'csrf_token': self.csrf_token},
185 status=302)
171 response = self.app.post(
172 url('gists'),
173 params={'lifetime': -1,
174 'content': 'private gist test',
175 'filename': 'private-foo',
176 'private': 'private',
177 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
178 'csrf_token': self.csrf_token},
179 status=302)
186 180 response = response.follow()
187 181 response.mustcontain('added file: private-foo<')
188 182 response.mustcontain('private gist test')
@@ -193,14 +187,15 b' class TestGistsController(TestController'
193 187
194 188 def test_create_private_acl_private(self):
195 189 self.log_user()
196 response = self.app.post(url('gists'),
197 params={'lifetime': -1,
198 'content': 'private gist test',
199 'filename': 'private-foo',
200 'private': 'private',
201 'acl_level': Gist.ACL_LEVEL_PRIVATE,
202 'csrf_token': self.csrf_token},
203 status=302)
190 response = self.app.post(
191 url('gists'),
192 params={'lifetime': -1,
193 'content': 'private gist test',
194 'filename': 'private-foo',
195 'private': 'private',
196 'gist_acl_level': Gist.ACL_LEVEL_PRIVATE,
197 'csrf_token': self.csrf_token},
198 status=302)
204 199 response = response.follow()
205 200 response.mustcontain('added file: private-foo<')
206 201 response.mustcontain('private gist test')
@@ -211,15 +206,16 b' class TestGistsController(TestController'
211 206
212 207 def test_create_with_description(self):
213 208 self.log_user()
214 response = self.app.post(url('gists'),
215 params={'lifetime': -1,
216 'content': 'gist test',
217 'filename': 'foo-desc',
218 'description': 'gist-desc',
219 'public': 'public',
220 'acl_level': Gist.ACL_LEVEL_PUBLIC,
221 'csrf_token': self.csrf_token},
222 status=302)
209 response = self.app.post(
210 url('gists'),
211 params={'lifetime': -1,
212 'content': 'gist test',
213 'filename': 'foo-desc',
214 'description': 'gist-desc',
215 'public': 'public',
216 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
217 'csrf_token': self.csrf_token},
218 status=302)
223 219 response = response.follow()
224 220 response.mustcontain('added file: foo-desc')
225 221 response.mustcontain('gist test')
@@ -233,7 +229,7 b' class TestGistsController(TestController'
233 229 'filename': 'foo-desc',
234 230 'description': 'gist-desc',
235 231 'public': 'public',
236 'acl_level': Gist.ACL_LEVEL_PUBLIC,
232 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
237 233 'csrf_token': self.csrf_token
238 234 }
239 235 response = self.app.post(url('gists'), params=params, status=302)
@@ -21,7 +21,7 b''
21 21 import colander
22 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 27 class TestGroupNameType(object):
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now