##// END OF EJS Templates
gists: migrated gists controller to pyramid view.
dan -
r1891:485023e6 default
parent child Browse files
Show More
@@ -0,0 +1,62 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 from rhodecode.apps._base import ADMIN_PREFIX
21
22
23 def admin_routes(config):
24 config.add_route(
25 name='gists_show', pattern='/gists')
26 config.add_route(
27 name='gists_new', pattern='/gists/new')
28 config.add_route(
29 name='gists_create', pattern='/gists/create')
30
31 config.add_route(
32 name='gist_show', pattern='/gists/{gist_id}')
33
34 config.add_route(
35 name='gist_delete', pattern='/gists/{gist_id}/delete')
36
37 config.add_route(
38 name='gist_edit', pattern='/gists/{gist_id}/edit')
39
40 config.add_route(
41 name='gist_edit_check_revision',
42 pattern='/gists/{gist_id}/edit/check_revision')
43
44 config.add_route(
45 name='gist_update', pattern='/gists/{gist_id}/update')
46
47 config.add_route(
48 name='gist_show_rev',
49 pattern='/gists/{gist_id}/{revision}')
50 config.add_route(
51 name='gist_show_formatted',
52 pattern='/gists/{gist_id}/{revision}/{format}')
53
54 config.add_route(
55 name='gist_show_formatted_path',
56 pattern='/gists/{gist_id}/{revision}/{format}/{f_path:.*}')
57
58
59 def includeme(config):
60 config.include(admin_routes, route_prefix=ADMIN_PREFIX)
61 # Scan module for configuration decorators.
62 config.scan()
@@ -0,0 +1,20 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
@@ -0,0 +1,412 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import time
22 import logging
23
24 import formencode
25 import peppercorn
26
27 from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden, HTTPFound
28 from pyramid.view import view_config
29 from pyramid.renderers import render
30 from pyramid.response import Response
31
32 from rhodecode.apps._base import BaseAppView
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.model.gist import GistModel
39 from rhodecode.model.meta import Session
40 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model import validation_schema
42 from rhodecode.model.validation_schema.schemas import gist_schema
43
44
45 log = logging.getLogger(__name__)
46
47
48 class GistView(BaseAppView):
49
50 def load_default_context(self):
51 _ = self.request.translate
52 c = self._get_local_tmpl_context()
53 c.user = c.auth_user.get_instance()
54
55 c.lifetime_values = [
56 (-1, _('forever')),
57 (5, _('5 minutes')),
58 (60, _('1 hour')),
59 (60 * 24, _('1 day')),
60 (60 * 24 * 30, _('1 month')),
61 ]
62
63 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.acl_options = [
65 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 ]
68
69 self._register_global_c(c)
70 return c
71
72 @LoginRequired()
73 @view_config(
74 route_name='gists_show', request_method='GET',
75 renderer='rhodecode:templates/admin/gists/index.mako')
76 def gist_show_all(self):
77 c = self.load_default_context()
78
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 c.show_private = self.request.GET.get('private') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83
84 gists = _gists = Gist().query()\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .order_by(Gist.created_on.desc())
87
88 c.active = 'public'
89 # MY private
90 if c.show_private and not c.show_public:
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 c.active = 'my_private'
94 # MY public
95 elif c.show_public and not c.show_private:
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 c.active = 'my_public'
99 # MY public+private
100 elif c.show_private and c.show_public:
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 Gist.gist_type == Gist.GIST_PRIVATE))\
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 c.active = 'my_all'
105 # Show all by super-admin
106 elif c.show_all:
107 c.active = 'all'
108 gists = _gists
109
110 # default show ALL public gists
111 if not c.show_public and not c.show_private and not c.show_all:
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 c.active = 'public'
114
115 from rhodecode.lib.utils import PartialRenderer
116 _render = PartialRenderer('data_table/_dt_elements.mako')
117
118 data = []
119
120 for gist in gists:
121 data.append({
122 'created_on': _render('gist_created', gist.created_on),
123 'created_on_raw': gist.created_on,
124 'type': _render('gist_type', gist.gist_type),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author_raw': h.escape(gist.owner.full_contact),
128 'expires': _render('gist_expires', gist.gist_expires),
129 'description': _render('gist_description', gist.gist_description)
130 })
131 c.data = json.dumps(data)
132
133 return self._get_template_context(c)
134
135 @LoginRequired()
136 @NotAnonymous()
137 @view_config(
138 route_name='gists_new', request_method='GET',
139 renderer='rhodecode:templates/admin/gists/new.mako')
140 def gist_new(self):
141 c = self.load_default_context()
142 return self._get_template_context(c)
143
144 @LoginRequired()
145 @NotAnonymous()
146 @CSRFRequired()
147 @view_config(
148 route_name='gists_create', request_method='POST',
149 renderer='rhodecode:templates/admin/gists/new.mako')
150 def gist_create(self):
151 _ = self.request.translate
152 c = self.load_default_context()
153
154 data = dict(self.request.POST)
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 data['nodes'] = [{
157 'filename': data['filename'],
158 'content': data.get('content'),
159 'mimetype': data.get('mimetype') # None is autodetect
160 }]
161
162 data['gist_type'] = (
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 data['gist_acl_level'] = (
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166
167 schema = gist_schema.GistSchema().bind(
168 lifetime_options=[x[0] for x in c.lifetime_values])
169
170 try:
171
172 schema_data = schema.deserialize(data)
173 # convert to safer format with just KEYs so we sure no duplicates
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 schema_data['nodes'])
176
177 gist = GistModel().create(
178 gist_id=schema_data['gistid'], # custom access id not real ID
179 description=schema_data['description'],
180 owner=self._rhodecode_user.user_id,
181 gist_mapping=schema_data['nodes'],
182 gist_type=schema_data['gist_type'],
183 lifetime=schema_data['lifetime'],
184 gist_acl_level=schema_data['gist_acl_level']
185 )
186 Session().commit()
187 new_gist_id = gist.gist_access_id
188 except validation_schema.Invalid as errors:
189 defaults = data
190 errors = errors.asdict()
191
192 if 'nodes.0.content' in errors:
193 errors['content'] = errors['nodes.0.content']
194 del errors['nodes.0.content']
195 if 'nodes.0.filename' in errors:
196 errors['filename'] = errors['nodes.0.filename']
197 del errors['nodes.0.filename']
198
199 data = render('rhodecode:templates/admin/gists/new.mako',
200 self._get_template_context(c), self.request)
201 html = formencode.htmlfill.render(
202 data,
203 defaults=defaults,
204 errors=errors,
205 prefix_error=False,
206 encoding="UTF-8",
207 force_defaults=False
208 )
209 return Response(html)
210
211 except Exception:
212 log.exception("Exception while trying to create a gist")
213 h.flash(_('Error occurred during gist creation'), category='error')
214 raise HTTPFound(h.route_url('gists_new'))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216
217 @LoginRequired()
218 @NotAnonymous()
219 @CSRFRequired()
220 @view_config(
221 route_name='gist_delete', request_method='POST')
222 def gist_delete(self):
223 _ = self.request.translate
224 gist_id = self.request.matchdict['gist_id']
225
226 c = self.load_default_context()
227 c.gist = Gist.get_or_404(gist_id)
228
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 if not (h.HasPermissionAny('hg.admin')() or owner):
231 log.warning('Deletion of Gist was forbidden '
232 'by unauthorized user: `%s`', self._rhodecode_user)
233 raise HTTPNotFound()
234
235 GistModel().delete(c.gist)
236 Session().commit()
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238
239 raise HTTPFound(h.route_url('gists_show'))
240
241 def _get_gist(self, gist_id):
242
243 gist = Gist.get_or_404(gist_id)
244
245 # Check if this gist is expired
246 if gist.gist_expires != -1:
247 if time.time() > gist.gist_expires:
248 log.error(
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 raise HTTPNotFound()
251
252 # check if this gist requires a login
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
255 log.error("Anonymous user %s tried to access protected gist `%s`",
256 self._rhodecode_user, gist_id)
257 raise HTTPNotFound()
258 return gist
259
260 @LoginRequired()
261 @view_config(
262 route_name='gist_show', request_method='GET',
263 renderer='rhodecode:templates/admin/gists/show.mako')
264 @view_config(
265 route_name='gist_show_rev', request_method='GET',
266 renderer='rhodecode:templates/admin/gists/show.mako')
267 @view_config(
268 route_name='gist_show_formatted', request_method='GET',
269 renderer=None)
270 @view_config(
271 route_name='gist_show_formatted_path', request_method='GET',
272 renderer=None)
273 def show(self):
274 gist_id = self.request.matchdict['gist_id']
275
276 # TODO(marcink): expose those via matching dict
277 revision = self.request.matchdict.get('revision', 'tip')
278 f_path = self.request.matchdict.get('f_path', None)
279 return_format = self.request.matchdict.get('format')
280
281 c = self.load_default_context()
282 c.gist = self._get_gist(gist_id)
283 c.render = not self.request.GET.get('no-render', False)
284
285 try:
286 c.file_last_commit, c.files = GistModel().get_gist_files(
287 gist_id, revision=revision)
288 except VCSError:
289 log.exception("Exception in gist show")
290 raise HTTPNotFound()
291
292 if return_format == 'raw':
293 content = '\n\n'.join([f.content for f in c.files
294 if (f_path is None or f.path == f_path)])
295 response = Response(content)
296 response.content_type = 'text/plain'
297 return response
298
299 return self._get_template_context(c)
300
301 @LoginRequired()
302 @NotAnonymous()
303 @view_config(
304 route_name='gist_edit', request_method='GET',
305 renderer='rhodecode:templates/admin/gists/edit.mako')
306 def gist_edit(self):
307 _ = self.request.translate
308 gist_id = self.request.matchdict['gist_id']
309 c = self.load_default_context()
310 c.gist = self._get_gist(gist_id)
311
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
313 if not (h.HasPermissionAny('hg.admin')() or owner):
314 raise HTTPNotFound()
315
316 try:
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
318 except VCSError:
319 log.exception("Exception in gist edit")
320 raise HTTPNotFound()
321
322 if c.gist.gist_expires == -1:
323 expiry = _('never')
324 else:
325 # this cannot use timeago, since it's used in select2 as a value
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
327
328 c.lifetime_values.append(
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
330 )
331
332 return self._get_template_context(c)
333
334 @LoginRequired()
335 @NotAnonymous()
336 @CSRFRequired()
337 @view_config(
338 route_name='gist_update', request_method='POST',
339 renderer='rhodecode:templates/admin/gists/edit.mako')
340 def gist_update(self):
341 _ = self.request.translate
342 gist_id = self.request.matchdict['gist_id']
343 c = self.load_default_context()
344 c.gist = self._get_gist(gist_id)
345
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
347 if not (h.HasPermissionAny('hg.admin')() or owner):
348 raise HTTPNotFound()
349
350 data = peppercorn.parse(self.request.POST.items())
351
352 schema = gist_schema.GistSchema()
353 schema = schema.bind(
354 # '0' is special value to leave lifetime untouched
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
356 )
357
358 try:
359 schema_data = schema.deserialize(data)
360 # convert to safer format with just KEYs so we sure no duplicates
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
362 schema_data['nodes'])
363
364 GistModel().update(
365 gist=c.gist,
366 description=schema_data['description'],
367 owner=c.gist.owner,
368 gist_mapping=schema_data['nodes'],
369 lifetime=schema_data['lifetime'],
370 gist_acl_level=schema_data['gist_acl_level']
371 )
372
373 Session().commit()
374 h.flash(_('Successfully updated gist content'), category='success')
375 except NodeNotChangedError:
376 # raised if nothing was changed in repo itself. We anyway then
377 # store only DB stuff for gist
378 Session().commit()
379 h.flash(_('Successfully updated gist data'), category='success')
380 except validation_schema.Invalid as errors:
381 errors = errors.asdict()
382 h.flash(_('Error occurred during update of gist {}: {}').format(
383 gist_id, errors), category='error')
384 except Exception:
385 log.exception("Exception in gist edit")
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
387 category='error')
388
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
390
391 @LoginRequired()
392 @NotAnonymous()
393 @view_config(
394 route_name='gist_edit_check_revision', request_method='GET',
395 renderer='json_ext')
396 def gist_edit_check_revision(self):
397 _ = self.request.translate
398 gist_id = self.request.matchdict['gist_id']
399 c = self.load_default_context()
400 c.gist = self._get_gist(gist_id)
401
402 last_rev = c.gist.scm_instance().get_commit()
403 success = True
404 revision = self.request.GET.get('revision')
405
406 if revision != last_rev.raw_id:
407 log.error('Last revision %s is different then submitted %s'
408 % (revision, last_rev))
409 # our gist has newer version than we
410 success = False
411
412 return {'success': success}
@@ -28,7 +28,7 b' from rhodecode.api.tests.utils import ('
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util, http_host_stub):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
@@ -45,14 +45,14 b' class TestApiGetGist(object):'
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util, http_host_stub):
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 mapping = {
56 mapping = {
57 u'filename1.txt': {'content': u'hello world'},
57 u'filename1.txt': {'content': u'hello world'},
58 u'filename1ą.txt': {'content': u'hello worldę'}
58 u'filename1ą.txt': {'content': u'hello worldę'}
@@ -73,7 +73,7 b' class TestApiGetGist(object):'
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://%s/_admin/gists/%s' % (http_host_stub, gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
@@ -27,7 +27,31 b' from rhodecode.model.gist import GistMod'
27 from rhodecode.model.meta import Session
27 from rhodecode.model.meta import Session
28 from rhodecode.tests import (
28 from rhodecode.tests import (
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
29 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
30 TestController, assert_session_flash, url)
30 TestController, assert_session_flash)
31
32
33 def route_path(name, params=None, **kwargs):
34 import urllib
35 from rhodecode.apps._base import ADMIN_PREFIX
36
37 base_url = {
38 'gists_show': ADMIN_PREFIX + '/gists',
39 'gists_new': ADMIN_PREFIX + '/gists/new',
40 'gists_create': ADMIN_PREFIX + '/gists/create',
41 'gist_show': ADMIN_PREFIX + '/gists/{gist_id}',
42 'gist_delete': ADMIN_PREFIX + '/gists/{gist_id}/delete',
43 'gist_edit': ADMIN_PREFIX + '/gists/{gist_id}/edit',
44 'gist_edit_check_revision': ADMIN_PREFIX + '/gists/{gist_id}/edit/check_revision',
45 'gist_update': ADMIN_PREFIX + '/gists/{gist_id}/update',
46 'gist_show_rev': ADMIN_PREFIX + '/gists/{gist_id}/{revision}',
47 'gist_show_formatted': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}',
48 'gist_show_formatted_path': ADMIN_PREFIX + '/gists/{gist_id}/{revision}/{format}/{f_path}',
49
50 }[name].format(**kwargs)
51
52 if params:
53 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
54 return base_url
31
55
32
56
33 class GistUtility(object):
57 class GistUtility(object):
@@ -70,7 +94,7 b' class TestGistsController(TestController'
70
94
71 def test_index_empty(self, create_gist):
95 def test_index_empty(self, create_gist):
72 self.log_user()
96 self.log_user()
73 response = self.app.get(url('gists'))
97 response = self.app.get(route_path('gists_show'))
74 response.mustcontain('data: [],')
98 response.mustcontain('data: [],')
75
99
76 def test_index(self, create_gist):
100 def test_index(self, create_gist):
@@ -79,7 +103,7 b' class TestGistsController(TestController'
79 g2 = create_gist('gist2', lifetime=1400)
103 g2 = create_gist('gist2', lifetime=1400)
80 g3 = create_gist('gist3', description='gist3-desc')
104 g3 = create_gist('gist3', description='gist3-desc')
81 g4 = create_gist('gist4', gist_type='private').gist_access_id
105 g4 = create_gist('gist4', gist_type='private').gist_access_id
82 response = self.app.get(url('gists'))
106 response = self.app.get(route_path('gists_show'))
83
107
84 response.mustcontain('gist: %s' % g1.gist_access_id)
108 response.mustcontain('gist: %s' % g1.gist_access_id)
85 response.mustcontain('gist: %s' % g2.gist_access_id)
109 response.mustcontain('gist: %s' % g2.gist_access_id)
@@ -95,7 +119,7 b' class TestGistsController(TestController'
95 def test_index_private_gists(self, create_gist):
119 def test_index_private_gists(self, create_gist):
96 self.log_user()
120 self.log_user()
97 gist = create_gist('gist5', gist_type='private')
121 gist = create_gist('gist5', gist_type='private')
98 response = self.app.get(url('gists', private=1))
122 response = self.app.get(route_path('gists_show', params=dict(private=1)))
99
123
100 # and privates
124 # and privates
101 response.mustcontain('gist: %s' % gist.gist_access_id)
125 response.mustcontain('gist: %s' % gist.gist_access_id)
@@ -107,7 +131,7 b' class TestGistsController(TestController'
107 create_gist('gist3', description='gist3-desc')
131 create_gist('gist3', description='gist3-desc')
108 create_gist('gist4', gist_type='private')
132 create_gist('gist4', gist_type='private')
109
133
110 response = self.app.get(url('gists', all=1))
134 response = self.app.get(route_path('gists_show', params=dict(all=1)))
111
135
112 assert len(GistModel.get_all()) == 4
136 assert len(GistModel.get_all()) == 4
113 # and privates
137 # and privates
@@ -120,7 +144,7 b' class TestGistsController(TestController'
120 create_gist('gist3', gist_type='private')
144 create_gist('gist3', gist_type='private')
121 create_gist('gist4', gist_type='private')
145 create_gist('gist4', gist_type='private')
122
146
123 response = self.app.get(url('gists', all=1))
147 response = self.app.get(route_path('gists_show', params=dict(all=1)))
124
148
125 assert len(GistModel.get_all()) == 3
149 assert len(GistModel.get_all()) == 3
126 # since we don't have access to private in this view, we
150 # since we don't have access to private in this view, we
@@ -131,7 +155,7 b' class TestGistsController(TestController'
131 def test_create(self):
155 def test_create(self):
132 self.log_user()
156 self.log_user()
133 response = self.app.post(
157 response = self.app.post(
134 url('gists'),
158 route_path('gists_create'),
135 params={'lifetime': -1,
159 params={'lifetime': -1,
136 'content': 'gist test',
160 'content': 'gist test',
137 'filename': 'foo',
161 'filename': 'foo',
@@ -146,7 +170,7 b' class TestGistsController(TestController'
146 def test_create_with_path_with_dirs(self):
170 def test_create_with_path_with_dirs(self):
147 self.log_user()
171 self.log_user()
148 response = self.app.post(
172 response = self.app.post(
149 url('gists'),
173 route_path('gists_create'),
150 params={'lifetime': -1,
174 params={'lifetime': -1,
151 'content': 'gist test',
175 'content': 'gist test',
152 'filename': '/home/foo',
176 'filename': '/home/foo',
@@ -163,12 +187,13 b' class TestGistsController(TestController'
163 Session().add(gist)
187 Session().add(gist)
164 Session().commit()
188 Session().commit()
165
189
166 self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
190 self.app.get(route_path('gist_show', gist_id=gist.gist_access_id),
191 status=404)
167
192
168 def test_create_private(self):
193 def test_create_private(self):
169 self.log_user()
194 self.log_user()
170 response = self.app.post(
195 response = self.app.post(
171 url('gists'),
196 route_path('gists_create'),
172 params={'lifetime': -1,
197 params={'lifetime': -1,
173 'content': 'private gist test',
198 'content': 'private gist test',
174 'filename': 'private-foo',
199 'filename': 'private-foo',
@@ -187,7 +212,7 b' class TestGistsController(TestController'
187 def test_create_private_acl_private(self):
212 def test_create_private_acl_private(self):
188 self.log_user()
213 self.log_user()
189 response = self.app.post(
214 response = self.app.post(
190 url('gists'),
215 route_path('gists_create'),
191 params={'lifetime': -1,
216 params={'lifetime': -1,
192 'content': 'private gist test',
217 'content': 'private gist test',
193 'filename': 'private-foo',
218 'filename': 'private-foo',
@@ -206,7 +231,7 b' class TestGistsController(TestController'
206 def test_create_with_description(self):
231 def test_create_with_description(self):
207 self.log_user()
232 self.log_user()
208 response = self.app.post(
233 response = self.app.post(
209 url('gists'),
234 route_path('gists_create'),
210 params={'lifetime': -1,
235 params={'lifetime': -1,
211 'content': 'gist test',
236 'content': 'gist test',
212 'filename': 'foo-desc',
237 'filename': 'foo-desc',
@@ -231,7 +256,8 b' class TestGistsController(TestController'
231 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
256 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC,
232 'csrf_token': self.csrf_token
257 'csrf_token': self.csrf_token
233 }
258 }
234 response = self.app.post(url('gists'), params=params, status=302)
259 response = self.app.post(
260 route_path('gists_create'), params=params, status=302)
235 self.logout_user()
261 self.logout_user()
236 response = response.follow()
262 response = response.follow()
237 response.mustcontain('added file: foo-desc')
263 response.mustcontain('added file: foo-desc')
@@ -240,35 +266,36 b' class TestGistsController(TestController'
240
266
241 def test_new(self):
267 def test_new(self):
242 self.log_user()
268 self.log_user()
243 self.app.get(url('new_gist'))
269 self.app.get(route_path('gists_new'))
244
270
245 def test_delete(self, create_gist):
271 def test_delete(self, create_gist):
246 self.log_user()
272 self.log_user()
247 gist = create_gist('delete-me')
273 gist = create_gist('delete-me')
248 response = self.app.post(
274 response = self.app.post(
249 url('gist', gist_id=gist.gist_id),
275 route_path('gist_delete', gist_id=gist.gist_id),
250 params={'_method': 'delete', 'csrf_token': self.csrf_token})
276 params={'csrf_token': self.csrf_token})
251 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
277 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
252
278
253 def test_delete_normal_user_his_gist(self, create_gist):
279 def test_delete_normal_user_his_gist(self, create_gist):
254 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
280 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
255 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
281 gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN)
282
256 response = self.app.post(
283 response = self.app.post(
257 url('gist', gist_id=gist.gist_id),
284 route_path('gist_delete', gist_id=gist.gist_id),
258 params={'_method': 'delete', 'csrf_token': self.csrf_token})
285 params={'csrf_token': self.csrf_token})
259 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
286 assert_session_flash(response, 'Deleted gist %s' % gist.gist_id)
260
287
261 def test_delete_normal_user_not_his_own_gist(self, create_gist):
288 def test_delete_normal_user_not_his_own_gist(self, create_gist):
262 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
289 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
263 gist = create_gist('delete-me')
290 gist = create_gist('delete-me-2')
291
264 self.app.post(
292 self.app.post(
265 url('gist', gist_id=gist.gist_id),
293 route_path('gist_delete', gist_id=gist.gist_id),
266 params={'_method': 'delete', 'csrf_token': self.csrf_token},
294 params={'csrf_token': self.csrf_token}, status=404)
267 status=403)
268
295
269 def test_show(self, create_gist):
296 def test_show(self, create_gist):
270 gist = create_gist('gist-show-me')
297 gist = create_gist('gist-show-me')
271 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
298 response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id))
272
299
273 response.mustcontain('added file: gist-show-me<')
300 response.mustcontain('added file: gist-show-me<')
274
301
@@ -283,16 +310,19 b' class TestGistsController(TestController'
283 with mock.patch(
310 with mock.patch(
284 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
311 'rhodecode.lib.vcs.settings.ALIASES', ['git']):
285 gist = create_gist('gist-show-me-again')
312 gist = create_gist('gist-show-me-again')
286 self.app.get(url('gist', gist_id=gist.gist_access_id), status=200)
313 self.app.get(
314 route_path('gist_show', gist_id=gist.gist_access_id), status=200)
287
315
288 def test_show_acl_private(self, create_gist):
316 def test_show_acl_private(self, create_gist):
289 gist = create_gist('gist-show-me-only-when-im-logged-in',
317 gist = create_gist('gist-show-me-only-when-im-logged-in',
290 acl_level=Gist.ACL_LEVEL_PRIVATE)
318 acl_level=Gist.ACL_LEVEL_PRIVATE)
291 self.app.get(url('gist', gist_id=gist.gist_access_id), status=404)
319 self.app.get(
320 route_path('gist_show', gist_id=gist.gist_access_id), status=404)
292
321
293 # now we log-in we should see thi gist
322 # now we log-in we should see thi gist
294 self.log_user()
323 self.log_user()
295 response = self.app.get(url('gist', gist_id=gist.gist_access_id))
324 response = self.app.get(
325 route_path('gist_show', gist_id=gist.gist_access_id))
296 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
326 response.mustcontain('added file: gist-show-me-only-when-im-logged-in')
297
327
298 assert_response = response.assert_response()
328 assert_response = response.assert_response()
@@ -303,36 +333,42 b' class TestGistsController(TestController'
303
333
304 def test_show_as_raw(self, create_gist):
334 def test_show_as_raw(self, create_gist):
305 gist = create_gist('gist-show-me', content='GIST CONTENT')
335 gist = create_gist('gist-show-me', content='GIST CONTENT')
306 response = self.app.get(url('formatted_gist',
336 response = self.app.get(
307 gist_id=gist.gist_access_id, format='raw'))
337 route_path('gist_show_formatted',
338 gist_id=gist.gist_access_id, revision='tip',
339 format='raw'))
308 assert response.body == 'GIST CONTENT'
340 assert response.body == 'GIST CONTENT'
309
341
310 def test_show_as_raw_individual_file(self, create_gist):
342 def test_show_as_raw_individual_file(self, create_gist):
311 gist = create_gist('gist-show-me-raw', content='GIST BODY')
343 gist = create_gist('gist-show-me-raw', content='GIST BODY')
312 response = self.app.get(url('formatted_gist_file',
344 response = self.app.get(
313 gist_id=gist.gist_access_id, format='raw',
345 route_path('gist_show_formatted_path',
314 revision='tip', f_path='gist-show-me-raw'))
346 gist_id=gist.gist_access_id, format='raw',
347 revision='tip', f_path='gist-show-me-raw'))
315 assert response.body == 'GIST BODY'
348 assert response.body == 'GIST BODY'
316
349
317 def test_edit_page(self, create_gist):
350 def test_edit_page(self, create_gist):
318 self.log_user()
351 self.log_user()
319 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
352 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
320 response = self.app.get(url('edit_gist', gist_id=gist.gist_access_id))
353 response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id))
321 response.mustcontain('GIST EDIT BODY')
354 response.mustcontain('GIST EDIT BODY')
322
355
323 def test_edit_page_non_logged_user(self, create_gist):
356 def test_edit_page_non_logged_user(self, create_gist):
324 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
357 gist = create_gist('gist-for-edit', content='GIST EDIT BODY')
325 self.app.get(url('edit_gist', gist_id=gist.gist_access_id), status=302)
358 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
359 status=302)
326
360
327 def test_edit_normal_user_his_gist(self, create_gist):
361 def test_edit_normal_user_his_gist(self, create_gist):
328 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
362 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
329 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
363 gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN)
330 self.app.get(url('edit_gist', gist_id=gist.gist_access_id, status=200))
364 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id,
365 status=200))
331
366
332 def test_edit_normal_user_not_his_own_gist(self, create_gist):
367 def test_edit_normal_user_not_his_own_gist(self, create_gist):
333 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
368 self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
334 gist = create_gist('delete-me')
369 gist = create_gist('delete-me')
335 self.app.get(url('edit_gist', gist_id=gist.gist_access_id), status=403)
370 self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id),
371 status=404)
336
372
337 def test_user_first_name_is_escaped(self, user_util, create_gist):
373 def test_user_first_name_is_escaped(self, user_util, create_gist):
338 xss_atack_string = '"><script>alert(\'First Name\')</script>'
374 xss_atack_string = '"><script>alert(\'First Name\')</script>'
@@ -341,7 +377,7 b' class TestGistsController(TestController'
341 user = user_util.create_user(
377 user = user_util.create_user(
342 firstname=xss_atack_string, password=password)
378 firstname=xss_atack_string, password=password)
343 create_gist('gist', gist_type='public', owner=user.username)
379 create_gist('gist', gist_type='public', owner=user.username)
344 response = self.app.get(url('gists'))
380 response = self.app.get(route_path('gists_show'))
345 response.mustcontain(xss_escaped_string)
381 response.mustcontain(xss_escaped_string)
346
382
347 def test_user_last_name_is_escaped(self, user_util, create_gist):
383 def test_user_last_name_is_escaped(self, user_util, create_gist):
@@ -351,5 +387,5 b' class TestGistsController(TestController'
351 user = user_util.create_user(
387 user = user_util.create_user(
352 lastname=xss_atack_string, password=password)
388 lastname=xss_atack_string, password=password)
353 create_gist('gist', gist_type='public', owner=user.username)
389 create_gist('gist', gist_type='public', owner=user.username)
354 response = self.app.get(url('gists'))
390 response = self.app.get(route_path('gists_show'))
355 response.mustcontain(xss_escaped_string)
391 response.mustcontain(xss_escaped_string)
@@ -303,6 +303,7 b' def includeme(config):'
303 config.include('rhodecode.apps.user_profile')
303 config.include('rhodecode.apps.user_profile')
304 config.include('rhodecode.apps.my_account')
304 config.include('rhodecode.apps.my_account')
305 config.include('rhodecode.apps.svn_support')
305 config.include('rhodecode.apps.svn_support')
306 config.include('rhodecode.apps.gist')
306
307
307 config.include('rhodecode.tweens')
308 config.include('rhodecode.tweens')
308 config.include('rhodecode.api')
309 config.include('rhodecode.api')
@@ -489,39 +489,6 b' def make_map(config):'
489 m.connect('notification', '/notifications/{notification_id}',
489 m.connect('notification', '/notifications/{notification_id}',
490 action='show', conditions={'method': ['GET']})
490 action='show', conditions={'method': ['GET']})
491
491
492 # ADMIN GIST
493 with rmap.submapper(path_prefix=ADMIN_PREFIX,
494 controller='admin/gists') as m:
495 m.connect('gists', '/gists',
496 action='create', conditions={'method': ['POST']})
497 m.connect('gists', '/gists', jsroute=True,
498 action='index', conditions={'method': ['GET']})
499 m.connect('new_gist', '/gists/new', jsroute=True,
500 action='new', conditions={'method': ['GET']})
501
502 m.connect('/gists/{gist_id}',
503 action='delete', conditions={'method': ['DELETE']})
504 m.connect('edit_gist', '/gists/{gist_id}/edit',
505 action='edit_form', conditions={'method': ['GET']})
506 m.connect('edit_gist', '/gists/{gist_id}/edit',
507 action='edit', conditions={'method': ['POST']})
508 m.connect(
509 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
510 action='check_revision', conditions={'method': ['GET']})
511
512 m.connect('gist', '/gists/{gist_id}',
513 action='show', conditions={'method': ['GET']})
514 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
515 revision='tip',
516 action='show', conditions={'method': ['GET']})
517 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
518 revision='tip',
519 action='show', conditions={'method': ['GET']})
520 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
521 revision='tip',
522 action='show', conditions={'method': ['GET']},
523 requirements=URL_NAME_REQUIREMENTS)
524
525 # USER JOURNAL
492 # USER JOURNAL
526 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
493 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
527 controller='journal', action='index')
494 controller='journal', action='index')
@@ -67,15 +67,25 b' class CSRFDetector(object):'
67 '/error/document',
67 '/error/document',
68 ))
68 ))
69
69
70 _SKIP_PATTERN = frozenset((
71 '/_admin/gists/',
72 ))
73
70 def __init__(self, app):
74 def __init__(self, app):
71 self._app = app
75 self._app = app
72
76
73 def __call__(self, environ, start_response):
77 def __call__(self, environ, start_response):
74 if environ['REQUEST_METHOD'].upper() not in ('GET', 'POST'):
78 if environ['REQUEST_METHOD'].upper() not in ('GET', 'POST'):
75 raise Exception(self._PUT_DELETE_MESSAGE)
79 raise Exception(self._PUT_DELETE_MESSAGE)
80 token_expected = environ['PATH_INFO'] not in self._PATHS_WITHOUT_TOKEN
81 allowed = True
82 for pattern in self._SKIP_PATTERN:
83 if environ['PATH_INFO'].startswith(pattern):
84 allowed = False
85 break
76
86
77 if (environ['REQUEST_METHOD'] == 'POST' and
87 if (environ['REQUEST_METHOD'] == 'POST' and
78 environ['PATH_INFO'] not in self._PATHS_WITHOUT_TOKEN and
88 token_expected and allowed and
79 routes.middleware.is_form_post(environ)):
89 routes.middleware.is_form_post(environ)):
80 body = environ['wsgi.input']
90 body = environ['wsgi.input']
81 if body.seekable():
91 if body.seekable():
@@ -3788,14 +3788,8 b' class Gist(Base, BaseModel):'
3788 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3788 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
3789
3789
3790 def gist_url(self):
3790 def gist_url(self):
3791 import rhodecode
3791 from rhodecode.model.gist import GistModel
3792 from pylons import url
3792 return GistModel().get_url(self)
3793
3794 alias_url = rhodecode.CONFIG.get('gist_alias_url')
3795 if alias_url:
3796 return alias_url.replace('{gistid}', self.gist_access_id)
3797
3798 return url('gist', gist_id=self.gist_access_id, qualified=True)
3799
3793
3800 @classmethod
3794 @classmethod
3801 def base_path(cls):
3795 def base_path(cls):
@@ -28,6 +28,8 b' import logging'
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pyramid.threadlocal import get_current_request
32
31 from rhodecode.lib.utils2 import (
33 from rhodecode.lib.utils2 import (
32 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
34 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
33 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
@@ -233,3 +235,16 b' class GistModel(BaseModel):'
233 )
235 )
234
236
235 return gist
237 return gist
238
239 def get_url(self, gist, request=None):
240 import rhodecode
241
242 if not request:
243 request = get_current_request()
244
245 alias_url = rhodecode.CONFIG.get('gist_alias_url')
246 if alias_url:
247 return alias_url.replace('{gistid}', gist.gist_access_id)
248
249 return request.route_url('gist_show', gist_id=gist.gist_access_id)
250
@@ -46,13 +46,13 b' function setRCMouseBindings(repoName, re'
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54 Mousetrap.bind(['n g'], function(e) {
54 Mousetrap.bind(['n g'], function(e) {
55 window.location = pyroutes.url('new_gist');
55 window.location = pyroutes.url('gists_new');
56 });
56 });
57 Mousetrap.bind(['n r'], function(e) {
57 Mousetrap.bind(['n r'], function(e) {
58 window.location = pyroutes.url('new_repo');
58 window.location = pyroutes.url('new_repo');
@@ -15,8 +15,6 b' function registerRCRoutes() {'
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
15 pyroutes.register('new_repo', '/_admin/create_repository', []);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
16 pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
17 pyroutes.register('edit_user_group_members', '/_admin/user_groups/%(user_group_id)s/edit/members', ['user_group_id']);
18 pyroutes.register('gists', '/_admin/gists', []);
19 pyroutes.register('new_gist', '/_admin/gists/new', []);
20 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
18 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
21 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
19 pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']);
22 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
20 pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']);
@@ -152,5 +150,16 b' function registerRCRoutes() {'
152 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
150 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
153 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
151 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
154 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
152 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
153 pyroutes.register('gists_show', '/_admin/gists', []);
154 pyroutes.register('gists_new', '/_admin/gists/new', []);
155 pyroutes.register('gists_create', '/_admin/gists/create', []);
156 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
157 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
158 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
159 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
160 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
161 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
162 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
163 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
155 pyroutes.register('apiv2', '/_admin/api', []);
164 pyroutes.register('apiv2', '/_admin/api', []);
156 }
165 }
@@ -26,7 +26,7 b''
26 <div class="table">
26 <div class="table">
27
27
28 <div id="files_data">
28 <div id="files_data">
29 ${h.secure_form(h.url('edit_gist', gist_id=c.gist.gist_access_id), method='post', id='eform')}
29 ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', method='POST')}
30 <div>
30 <div>
31 <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash">
31 <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash">
32 <textarea id="description" name="description"
32 <textarea id="description" name="description"
@@ -99,7 +99,7 b''
99
99
100 <div class="pull-right">
100 <div class="pull-right">
101 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
101 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
102 <a class="btn" href="${h.url('gist', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
102 <a class="btn" href="${h.route_path('gist_show', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
103 </div>
103 </div>
104 ${h.end_form()}
104 ${h.end_form()}
105 </div>
105 </div>
@@ -109,9 +109,12 b''
109 <script>
109 <script>
110 $('#update').on('click', function(e){
110 $('#update').on('click', function(e){
111 e.preventDefault();
111 e.preventDefault();
112
113 $(this).val('Updating...');
114 $(this).attr('disabled', 'disabled');
112 // check for newer version.
115 // check for newer version.
113 $.ajax({
116 $.ajax({
114 url: "${h.url('edit_gist_check_revision', gist_id=c.gist.gist_access_id)}",
117 url: "${h.route_path('gist_edit_check_revision', gist_id=c.gist.gist_access_id)}",
115 data: {
118 data: {
116 'revision': '${c.file_last_commit.raw_id}'
119 'revision': '${c.file_last_commit.raw_id}'
117 },
120 },
@@ -120,7 +123,7 b''
120 success: function(data) {
123 success: function(data) {
121 if(data.success === false){
124 if(data.success === false){
122 message = '${h.literal(_('Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version.')
125 message = '${h.literal(_('Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version.')
123 % {'here': h.link_to('here',h.url('edit_gist', gist_id=c.gist.gist_access_id))})}'
126 % {'here': h.link_to('here', h.route_path('gist_edit', gist_id=c.gist.gist_access_id))})}'
124 alertMessage = [{"message": {
127 alertMessage = [{"message": {
125 "message": message, "force": "true", "level": "warning"}}];
128 "message": message, "force": "true", "level": "warning"}}];
126 $.Topic('/notifications').publish(alertMessage[0]);
129 $.Topic('/notifications').publish(alertMessage[0]);
@@ -41,7 +41,7 b''
41 %if c.rhodecode_user.username != h.DEFAULT_USER:
41 %if c.rhodecode_user.username != h.DEFAULT_USER:
42 <ul class="links block-right">
42 <ul class="links block-right">
43 <li>
43 <li>
44 <a href="${h.url('new_gist')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
44 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
45 </li>
45 </li>
46 </ul>
46 </ul>
47 %endif
47 %endif
@@ -53,13 +53,13 b''
53 <div class="sidebar">
53 <div class="sidebar">
54 <ul class="nav nav-pills nav-stacked">
54 <ul class="nav nav-pills nav-stacked">
55 % if h.HasPermissionAll('hg.admin')('access admin gists page'):
55 % if h.HasPermissionAll('hg.admin')('access admin gists page'):
56 <li class="${'active' if c.active=='all' else ''}"><a href="${h.url('gists', all=1)}">${_('All gists')}</a></li>
56 <li class="${'active' if c.active=='all' else ''}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li>
57 %endif
57 %endif
58 <li class="${'active' if c.active=='public' else ''}"><a href="${h.url('gists')}">${_('All public')}</a></li>
58 <li class="${'active' if c.active=='public' else ''}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li>
59 %if c.rhodecode_user.username != h.DEFAULT_USER:
59 %if c.rhodecode_user.username != h.DEFAULT_USER:
60 <li class="${'active' if c.active=='my_all' else ''}"><a href="${h.url('gists', public=1, private=1)}">${_('My gists')}</a></li>
60 <li class="${'active' if c.active=='my_all' else ''}"><a href="${h.route_path('gists_show', _query={'public':1, 'private': 1})}">${_('My gists')}</a></li>
61 <li class="${'active' if c.active=='my_private' else ''}"><a href="${h.url('gists', private=1)}">${_('My private')}</a></li>
61 <li class="${'active' if c.active=='my_private' else ''}"><a href="${h.route_path('gists_show', _query={'private': 1})}">${_('My private')}</a></li>
62 <li class="${'active' if c.active=='my_public' else ''}"><a href="${h.url('gists', public=1)}">${_('My public')}</a></li>
62 <li class="${'active' if c.active=='my_public' else ''}"><a href="${h.route_path('gists_show', _query={'public': 1})}">${_('My public')}</a></li>
63 %endif
63 %endif
64 </ul>
64 </ul>
65 </div>
65 </div>
@@ -25,7 +25,7 b''
25
25
26 <div class="table">
26 <div class="table">
27 <div id="files_data">
27 <div id="files_data">
28 ${h.secure_form(h.url('gists'), method='post',id='eform')}
28 ${h.secure_form(h.route_path('gists_create'), id='eform', method='POST')}
29 <div>
29 <div>
30 <textarea id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
30 <textarea id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
31
31
@@ -18,7 +18,6 b''
18
18
19 <%def name="breadcrumbs_links()">
19 <%def name="breadcrumbs_links()">
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
21 / ${_('URL')}: ${c.gist.gist_url()}
22 </%def>
21 </%def>
23
22
24 <%def name="menu_bar_nav()">
23 <%def name="menu_bar_nav()">
@@ -33,11 +32,12 b''
33 %if c.rhodecode_user.username != h.DEFAULT_USER:
32 %if c.rhodecode_user.username != h.DEFAULT_USER:
34 <ul class="links">
33 <ul class="links">
35 <li>
34 <li>
36 <a href="${h.url('new_gist')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
35 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
37 </li>
36 </li>
38 </ul>
37 </ul>
39 %endif
38 %endif
40 </div>
39 </div>
40 <code>${c.gist.gist_url()}</code>
41 <div class="table">
41 <div class="table">
42 <div id="files_data">
42 <div id="files_data">
43 <div id="codeblock" class="codeblock">
43 <div id="codeblock" class="codeblock">
@@ -45,7 +45,7 b''
45 <div class="stats">
45 <div class="stats">
46 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
46 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
47 <div class="remove_gist">
47 <div class="remove_gist">
48 ${h.secure_form(url('gist', gist_id=c.gist.gist_access_id),method='delete')}
48 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), method='POST')}
49 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
49 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
50 ${h.end_form()}
50 ${h.end_form()}
51 </div>
51 </div>
@@ -53,9 +53,9 b''
53 <div class="buttons">
53 <div class="buttons">
54 ## only owner should see that
54 ## only owner should see that
55 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
55 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
56 ${h.link_to(_('Edit'),h.url('edit_gist', gist_id=c.gist.gist_access_id),class_="btn btn-mini")}
56 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
57 %endif
57 %endif
58 ${h.link_to(_('Show as Raw'),h.url('formatted_gist', gist_id=c.gist.gist_access_id, format='raw'),class_="btn btn-mini")}
58 ${h.link_to(_('Show as Raw'), h.route_path('gist_show_formatted', gist_id=c.gist.gist_access_id, revision='tip', format='raw'), class_="btn btn-mini")}
59 </div>
59 </div>
60 <div class="left" >
60 <div class="left" >
61 %if c.gist.gist_type != 'public':
61 %if c.gist.gist_type != 'public':
@@ -78,19 +78,21 b''
78 </div>
78 </div>
79
79
80 </div>
80 </div>
81 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message,c.repo_name)}</div>
81 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
82 </div>
82 </div>
83
83
84 ## iterate over the files
84 ## iterate over the files
85 % for file in c.files:
85 % for file in c.files:
86 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
86 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
87 <!-- <div id="${h.FID('G', file.path)}" class="stats" >
87 <!--
88 <div id="${h.FID('G', file.path)}" class="stats" >
88 <a href="${c.gist.gist_url()}">¶</a>
89 <a href="${c.gist.gist_url()}">¶</a>
89 <b >${file.path}</b>
90 <b >${file.path}</b>
90 <div>
91 <div>
91 ${h.link_to(_('Show as raw'),h.url('formatted_gist_file', gist_id=c.gist.gist_access_id, format='raw', revision=file.commit.raw_id, f_path=file.path),class_="btn btn-mini")}
92 ${h.link_to(_('Show as raw'), h.route_path('gist_show_formatted_path', gist_id=c.gist.gist_access_id, revision=file.commit.raw_id, format='raw', f_path=file.path), class_="btn btn-mini")}
92 </div>
93 </div>
93 </div> -->
94 </div>
95 -->
94 <div class="code-body textarea text-area editor">
96 <div class="code-body textarea text-area editor">
95 %if renderer:
97 %if renderer:
96 ${h.render(file.content, renderer=renderer)}
98 ${h.render(file.content, renderer=renderer)}
@@ -397,7 +397,7 b''
397 </li>
397 </li>
398 %endif
398 %endif
399 <li class="${is_active('gists')}">
399 <li class="${is_active('gists')}">
400 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
400 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
401 <div class="menulabel">${_('Gists')}</div>
401 <div class="menulabel">${_('Gists')}</div>
402 </a>
402 </a>
403 </li>
403 </li>
@@ -241,7 +241,7 b''
241 <%def name="gist_access_id(gist_access_id, full_contact)">
241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 <div>
242 <div>
243 <b>
243 <b>
244 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
244 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 </b>
245 </b>
246 </div>
246 </div>
247 </%def>
247 </%def>
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