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 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 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 | 32 | gist = gist_util.create_gist() |
|
33 | 33 | gist_id = gist.gist_access_id |
|
34 | 34 | gist_created_on = gist.created_on |
@@ -45,14 +45,14 b' class TestApiGetGist(object):' | |||
|
45 | 45 | 'expires': -1.0, |
|
46 | 46 | 'gist_id': int(gist_id), |
|
47 | 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 | 49 | 'acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
50 | 50 | 'content': None, |
|
51 | 51 | } |
|
52 | 52 | |
|
53 | 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 | 56 | mapping = { |
|
57 | 57 | u'filename1.txt': {'content': u'hello world'}, |
|
58 | 58 | u'filename1ą.txt': {'content': u'hello worldę'} |
@@ -73,7 +73,7 b' class TestApiGetGist(object):' | |||
|
73 | 73 | 'expires': -1.0, |
|
74 | 74 | 'gist_id': int(gist_id), |
|
75 | 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 | 77 | 'acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
78 | 78 | 'content': { |
|
79 | 79 | u'filename1.txt': u'hello world', |
@@ -27,7 +27,31 b' from rhodecode.model.gist import GistMod' | |||
|
27 | 27 | from rhodecode.model.meta import Session |
|
28 | 28 | from rhodecode.tests import ( |
|
29 | 29 | TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, |
|
30 |
TestController, assert_session_flash |
|
|
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 | 57 | class GistUtility(object): |
@@ -70,7 +94,7 b' class TestGistsController(TestController' | |||
|
70 | 94 | |
|
71 | 95 | def test_index_empty(self, create_gist): |
|
72 | 96 | self.log_user() |
|
73 |
response = self.app.get( |
|
|
97 | response = self.app.get(route_path('gists_show')) | |
|
74 | 98 | response.mustcontain('data: [],') |
|
75 | 99 | |
|
76 | 100 | def test_index(self, create_gist): |
@@ -79,7 +103,7 b' class TestGistsController(TestController' | |||
|
79 | 103 | g2 = create_gist('gist2', lifetime=1400) |
|
80 | 104 | g3 = create_gist('gist3', description='gist3-desc') |
|
81 | 105 | g4 = create_gist('gist4', gist_type='private').gist_access_id |
|
82 |
response = self.app.get( |
|
|
106 | response = self.app.get(route_path('gists_show')) | |
|
83 | 107 | |
|
84 | 108 | response.mustcontain('gist: %s' % g1.gist_access_id) |
|
85 | 109 | response.mustcontain('gist: %s' % g2.gist_access_id) |
@@ -95,7 +119,7 b' class TestGistsController(TestController' | |||
|
95 | 119 | def test_index_private_gists(self, create_gist): |
|
96 | 120 | self.log_user() |
|
97 | 121 | gist = create_gist('gist5', gist_type='private') |
|
98 |
response = self.app.get( |
|
|
122 | response = self.app.get(route_path('gists_show', params=dict(private=1))) | |
|
99 | 123 | |
|
100 | 124 | # and privates |
|
101 | 125 | response.mustcontain('gist: %s' % gist.gist_access_id) |
@@ -107,7 +131,7 b' class TestGistsController(TestController' | |||
|
107 | 131 | create_gist('gist3', description='gist3-desc') |
|
108 | 132 | create_gist('gist4', gist_type='private') |
|
109 | 133 | |
|
110 |
response = self.app.get( |
|
|
134 | response = self.app.get(route_path('gists_show', params=dict(all=1))) | |
|
111 | 135 | |
|
112 | 136 | assert len(GistModel.get_all()) == 4 |
|
113 | 137 | # and privates |
@@ -120,7 +144,7 b' class TestGistsController(TestController' | |||
|
120 | 144 | create_gist('gist3', gist_type='private') |
|
121 | 145 | create_gist('gist4', gist_type='private') |
|
122 | 146 | |
|
123 |
response = self.app.get( |
|
|
147 | response = self.app.get(route_path('gists_show', params=dict(all=1))) | |
|
124 | 148 | |
|
125 | 149 | assert len(GistModel.get_all()) == 3 |
|
126 | 150 | # since we don't have access to private in this view, we |
@@ -131,7 +155,7 b' class TestGistsController(TestController' | |||
|
131 | 155 | def test_create(self): |
|
132 | 156 | self.log_user() |
|
133 | 157 | response = self.app.post( |
|
134 |
|
|
|
158 | route_path('gists_create'), | |
|
135 | 159 | params={'lifetime': -1, |
|
136 | 160 | 'content': 'gist test', |
|
137 | 161 | 'filename': 'foo', |
@@ -146,7 +170,7 b' class TestGistsController(TestController' | |||
|
146 | 170 | def test_create_with_path_with_dirs(self): |
|
147 | 171 | self.log_user() |
|
148 | 172 | response = self.app.post( |
|
149 |
|
|
|
173 | route_path('gists_create'), | |
|
150 | 174 | params={'lifetime': -1, |
|
151 | 175 | 'content': 'gist test', |
|
152 | 176 | 'filename': '/home/foo', |
@@ -163,12 +187,13 b' class TestGistsController(TestController' | |||
|
163 | 187 | Session().add(gist) |
|
164 | 188 | Session().commit() |
|
165 | 189 | |
|
166 |
self.app.get( |
|
|
190 | self.app.get(route_path('gist_show', gist_id=gist.gist_access_id), | |
|
191 | status=404) | |
|
167 | 192 | |
|
168 | 193 | def test_create_private(self): |
|
169 | 194 | self.log_user() |
|
170 | 195 | response = self.app.post( |
|
171 |
|
|
|
196 | route_path('gists_create'), | |
|
172 | 197 | params={'lifetime': -1, |
|
173 | 198 | 'content': 'private gist test', |
|
174 | 199 | 'filename': 'private-foo', |
@@ -187,7 +212,7 b' class TestGistsController(TestController' | |||
|
187 | 212 | def test_create_private_acl_private(self): |
|
188 | 213 | self.log_user() |
|
189 | 214 | response = self.app.post( |
|
190 |
|
|
|
215 | route_path('gists_create'), | |
|
191 | 216 | params={'lifetime': -1, |
|
192 | 217 | 'content': 'private gist test', |
|
193 | 218 | 'filename': 'private-foo', |
@@ -206,7 +231,7 b' class TestGistsController(TestController' | |||
|
206 | 231 | def test_create_with_description(self): |
|
207 | 232 | self.log_user() |
|
208 | 233 | response = self.app.post( |
|
209 |
|
|
|
234 | route_path('gists_create'), | |
|
210 | 235 | params={'lifetime': -1, |
|
211 | 236 | 'content': 'gist test', |
|
212 | 237 | 'filename': 'foo-desc', |
@@ -231,7 +256,8 b' class TestGistsController(TestController' | |||
|
231 | 256 | 'gist_acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
232 | 257 | 'csrf_token': self.csrf_token |
|
233 | 258 | } |
|
234 |
response = self.app.post( |
|
|
259 | response = self.app.post( | |
|
260 | route_path('gists_create'), params=params, status=302) | |
|
235 | 261 | self.logout_user() |
|
236 | 262 | response = response.follow() |
|
237 | 263 | response.mustcontain('added file: foo-desc') |
@@ -240,35 +266,36 b' class TestGistsController(TestController' | |||
|
240 | 266 | |
|
241 | 267 | def test_new(self): |
|
242 | 268 | self.log_user() |
|
243 |
self.app.get( |
|
|
269 | self.app.get(route_path('gists_new')) | |
|
244 | 270 | |
|
245 | 271 | def test_delete(self, create_gist): |
|
246 | 272 | self.log_user() |
|
247 | 273 | gist = create_gist('delete-me') |
|
248 | 274 | response = self.app.post( |
|
249 |
|
|
|
250 |
params={ |
|
|
275 | route_path('gist_delete', gist_id=gist.gist_id), | |
|
276 | params={'csrf_token': self.csrf_token}) | |
|
251 | 277 | assert_session_flash(response, 'Deleted gist %s' % gist.gist_id) |
|
252 | 278 | |
|
253 | 279 | def test_delete_normal_user_his_gist(self, create_gist): |
|
254 | 280 | self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) |
|
255 | 281 | gist = create_gist('delete-me', owner=TEST_USER_REGULAR_LOGIN) |
|
282 | ||
|
256 | 283 | response = self.app.post( |
|
257 |
|
|
|
258 |
params={ |
|
|
284 | route_path('gist_delete', gist_id=gist.gist_id), | |
|
285 | params={'csrf_token': self.csrf_token}) | |
|
259 | 286 | assert_session_flash(response, 'Deleted gist %s' % gist.gist_id) |
|
260 | 287 | |
|
261 | 288 | def test_delete_normal_user_not_his_own_gist(self, create_gist): |
|
262 | 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 | 292 | self.app.post( |
|
265 |
|
|
|
266 |
params={ |
|
|
267 | status=403) | |
|
293 | route_path('gist_delete', gist_id=gist.gist_id), | |
|
294 | params={'csrf_token': self.csrf_token}, status=404) | |
|
268 | 295 | |
|
269 | 296 | def test_show(self, create_gist): |
|
270 | 297 | gist = create_gist('gist-show-me') |
|
271 |
response = self.app.get( |
|
|
298 | response = self.app.get(route_path('gist_show', gist_id=gist.gist_access_id)) | |
|
272 | 299 | |
|
273 | 300 | response.mustcontain('added file: gist-show-me<') |
|
274 | 301 | |
@@ -283,16 +310,19 b' class TestGistsController(TestController' | |||
|
283 | 310 | with mock.patch( |
|
284 | 311 | 'rhodecode.lib.vcs.settings.ALIASES', ['git']): |
|
285 | 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 | 316 | def test_show_acl_private(self, create_gist): |
|
289 | 317 | gist = create_gist('gist-show-me-only-when-im-logged-in', |
|
290 | 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 | 322 | # now we log-in we should see thi gist |
|
294 | 323 | self.log_user() |
|
295 |
response = self.app.get( |
|
|
324 | response = self.app.get( | |
|
325 | route_path('gist_show', gist_id=gist.gist_access_id)) | |
|
296 | 326 | response.mustcontain('added file: gist-show-me-only-when-im-logged-in') |
|
297 | 327 | |
|
298 | 328 | assert_response = response.assert_response() |
@@ -303,13 +333,16 b' class TestGistsController(TestController' | |||
|
303 | 333 | |
|
304 | 334 | def test_show_as_raw(self, create_gist): |
|
305 | 335 | gist = create_gist('gist-show-me', content='GIST CONTENT') |
|
306 |
response = self.app.get( |
|
|
307 | gist_id=gist.gist_access_id, format='raw')) | |
|
336 | response = self.app.get( | |
|
337 | route_path('gist_show_formatted', | |
|
338 | gist_id=gist.gist_access_id, revision='tip', | |
|
339 | format='raw')) | |
|
308 | 340 | assert response.body == 'GIST CONTENT' |
|
309 | 341 | |
|
310 | 342 | def test_show_as_raw_individual_file(self, create_gist): |
|
311 | 343 | gist = create_gist('gist-show-me-raw', content='GIST BODY') |
|
312 |
response = self.app.get( |
|
|
344 | response = self.app.get( | |
|
345 | route_path('gist_show_formatted_path', | |
|
313 | 346 |
|
|
314 | 347 |
|
|
315 | 348 | assert response.body == 'GIST BODY' |
@@ -317,22 +350,25 b' class TestGistsController(TestController' | |||
|
317 | 350 | def test_edit_page(self, create_gist): |
|
318 | 351 | self.log_user() |
|
319 | 352 | gist = create_gist('gist-for-edit', content='GIST EDIT BODY') |
|
320 |
response = self.app.get( |
|
|
353 | response = self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id)) | |
|
321 | 354 | response.mustcontain('GIST EDIT BODY') |
|
322 | 355 | |
|
323 | 356 | def test_edit_page_non_logged_user(self, create_gist): |
|
324 | 357 | gist = create_gist('gist-for-edit', content='GIST EDIT BODY') |
|
325 |
self.app.get( |
|
|
358 | self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id), | |
|
359 | status=302) | |
|
326 | 360 | |
|
327 | 361 | def test_edit_normal_user_his_gist(self, create_gist): |
|
328 | 362 | self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) |
|
329 | 363 | gist = create_gist('gist-for-edit', owner=TEST_USER_REGULAR_LOGIN) |
|
330 |
self.app.get( |
|
|
364 | self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id, | |
|
365 | status=200)) | |
|
331 | 366 | |
|
332 | 367 | def test_edit_normal_user_not_his_own_gist(self, create_gist): |
|
333 | 368 | self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS) |
|
334 | 369 | gist = create_gist('delete-me') |
|
335 |
self.app.get( |
|
|
370 | self.app.get(route_path('gist_edit', gist_id=gist.gist_access_id), | |
|
371 | status=404) | |
|
336 | 372 | |
|
337 | 373 | def test_user_first_name_is_escaped(self, user_util, create_gist): |
|
338 | 374 | xss_atack_string = '"><script>alert(\'First Name\')</script>' |
@@ -341,7 +377,7 b' class TestGistsController(TestController' | |||
|
341 | 377 | user = user_util.create_user( |
|
342 | 378 | firstname=xss_atack_string, password=password) |
|
343 | 379 | create_gist('gist', gist_type='public', owner=user.username) |
|
344 |
response = self.app.get( |
|
|
380 | response = self.app.get(route_path('gists_show')) | |
|
345 | 381 | response.mustcontain(xss_escaped_string) |
|
346 | 382 | |
|
347 | 383 | def test_user_last_name_is_escaped(self, user_util, create_gist): |
@@ -351,5 +387,5 b' class TestGistsController(TestController' | |||
|
351 | 387 | user = user_util.create_user( |
|
352 | 388 | lastname=xss_atack_string, password=password) |
|
353 | 389 | create_gist('gist', gist_type='public', owner=user.username) |
|
354 |
response = self.app.get( |
|
|
390 | response = self.app.get(route_path('gists_show')) | |
|
355 | 391 | response.mustcontain(xss_escaped_string) |
@@ -303,6 +303,7 b' def includeme(config):' | |||
|
303 | 303 | config.include('rhodecode.apps.user_profile') |
|
304 | 304 | config.include('rhodecode.apps.my_account') |
|
305 | 305 | config.include('rhodecode.apps.svn_support') |
|
306 | config.include('rhodecode.apps.gist') | |
|
306 | 307 | |
|
307 | 308 | config.include('rhodecode.tweens') |
|
308 | 309 | config.include('rhodecode.api') |
@@ -489,39 +489,6 b' def make_map(config):' | |||
|
489 | 489 | m.connect('notification', '/notifications/{notification_id}', |
|
490 | 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 | 492 | # USER JOURNAL |
|
526 | 493 | rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,), |
|
527 | 494 | controller='journal', action='index') |
@@ -67,15 +67,25 b' class CSRFDetector(object):' | |||
|
67 | 67 | '/error/document', |
|
68 | 68 | )) |
|
69 | 69 | |
|
70 | _SKIP_PATTERN = frozenset(( | |
|
71 | '/_admin/gists/', | |
|
72 | )) | |
|
73 | ||
|
70 | 74 | def __init__(self, app): |
|
71 | 75 | self._app = app |
|
72 | 76 | |
|
73 | 77 | def __call__(self, environ, start_response): |
|
74 | 78 | if environ['REQUEST_METHOD'].upper() not in ('GET', 'POST'): |
|
75 | 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 | 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 | 89 | routes.middleware.is_form_post(environ)): |
|
80 | 90 | body = environ['wsgi.input'] |
|
81 | 91 | if body.seekable(): |
@@ -3788,14 +3788,8 b' class Gist(Base, BaseModel):' | |||
|
3788 | 3788 | return cls.query().filter(cls.gist_access_id == gist_access_id).scalar() |
|
3789 | 3789 | |
|
3790 | 3790 | def gist_url(self): |
|
3791 | import rhodecode | |
|
3792 | from pylons import url | |
|
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) | |
|
3791 | from rhodecode.model.gist import GistModel | |
|
3792 | return GistModel().get_url(self) | |
|
3799 | 3793 | |
|
3800 | 3794 | @classmethod |
|
3801 | 3795 | def base_path(cls): |
@@ -28,6 +28,8 b' import logging' | |||
|
28 | 28 | import traceback |
|
29 | 29 | import shutil |
|
30 | 30 | |
|
31 | from pyramid.threadlocal import get_current_request | |
|
32 | ||
|
31 | 33 | from rhodecode.lib.utils2 import ( |
|
32 | 34 | safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict) |
|
33 | 35 | from rhodecode.lib.ext_json import json |
@@ -233,3 +235,16 b' class GistModel(BaseModel):' | |||
|
233 | 235 | ) |
|
234 | 236 | |
|
235 | 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 | 46 | window.location = pyroutes.url('home'); |
|
47 | 47 | }); |
|
48 | 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 | 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 | 54 | Mousetrap.bind(['n g'], function(e) { |
|
55 |
window.location = pyroutes.url(' |
|
|
55 | window.location = pyroutes.url('gists_new'); | |
|
56 | 56 | }); |
|
57 | 57 | Mousetrap.bind(['n r'], function(e) { |
|
58 | 58 | window.location = pyroutes.url('new_repo'); |
@@ -15,8 +15,6 b' function registerRCRoutes() {' | |||
|
15 | 15 | pyroutes.register('new_repo', '/_admin/create_repository', []); |
|
16 | 16 | pyroutes.register('edit_user', '/_admin/users/%(user_id)s/edit', ['user_id']); |
|
17 | 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 | 18 | pyroutes.register('toggle_following', '/_admin/toggle_following', []); |
|
21 | 19 | pyroutes.register('changeset_home', '/%(repo_name)s/changeset/%(revision)s', ['repo_name', 'revision']); |
|
22 | 20 | pyroutes.register('changeset_comment', '/%(repo_name)s/changeset/%(revision)s/comment', ['repo_name', 'revision']); |
@@ -152,5 +150,16 b' function registerRCRoutes() {' | |||
|
152 | 150 | pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []); |
|
153 | 151 | pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []); |
|
154 | 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 | 164 | pyroutes.register('apiv2', '/_admin/api', []); |
|
156 | 165 | } |
@@ -26,7 +26,7 b'' | |||
|
26 | 26 | <div class="table"> |
|
27 | 27 | |
|
28 | 28 | <div id="files_data"> |
|
29 |
${h.secure_form(h. |
|
|
29 | ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', method='POST')} | |
|
30 | 30 | <div> |
|
31 | 31 | <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash"> |
|
32 | 32 | <textarea id="description" name="description" |
@@ -99,7 +99,7 b'' | |||
|
99 | 99 | |
|
100 | 100 | <div class="pull-right"> |
|
101 | 101 | ${h.submit('update',_('Update Gist'),class_="btn btn-success")} |
|
102 |
<a class="btn" href="${h. |
|
|
102 | <a class="btn" href="${h.route_path('gist_show', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a> | |
|
103 | 103 | </div> |
|
104 | 104 | ${h.end_form()} |
|
105 | 105 | </div> |
@@ -109,9 +109,12 b'' | |||
|
109 | 109 | <script> |
|
110 | 110 | $('#update').on('click', function(e){ |
|
111 | 111 | e.preventDefault(); |
|
112 | ||
|
113 | $(this).val('Updating...'); | |
|
114 | $(this).attr('disabled', 'disabled'); | |
|
112 | 115 | // check for newer version. |
|
113 | 116 | $.ajax({ |
|
114 |
url: "${h. |
|
|
117 | url: "${h.route_path('gist_edit_check_revision', gist_id=c.gist.gist_access_id)}", | |
|
115 | 118 | data: { |
|
116 | 119 | 'revision': '${c.file_last_commit.raw_id}' |
|
117 | 120 | }, |
@@ -120,7 +123,7 b'' | |||
|
120 | 123 | success: function(data) { |
|
121 | 124 | if(data.success === false){ |
|
122 | 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. |
|
|
126 | % {'here': h.link_to('here', h.route_path('gist_edit', gist_id=c.gist.gist_access_id))})}' | |
|
124 | 127 | alertMessage = [{"message": { |
|
125 | 128 | "message": message, "force": "true", "level": "warning"}}]; |
|
126 | 129 | $.Topic('/notifications').publish(alertMessage[0]); |
@@ -41,7 +41,7 b'' | |||
|
41 | 41 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
42 | 42 | <ul class="links block-right"> |
|
43 | 43 | <li> |
|
44 |
<a href="${h. |
|
|
44 | <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a> | |
|
45 | 45 | </li> |
|
46 | 46 | </ul> |
|
47 | 47 | %endif |
@@ -53,13 +53,13 b'' | |||
|
53 | 53 | <div class="sidebar"> |
|
54 | 54 | <ul class="nav nav-pills nav-stacked"> |
|
55 | 55 | % if h.HasPermissionAll('hg.admin')('access admin gists page'): |
|
56 |
<li class="${'active' if c.active=='all' else ''}"><a href="${h. |
|
|
56 | <li class="${'active' if c.active=='all' else ''}"><a href="${h.route_path('gists_show', _query={'all': 1})}">${_('All gists')}</a></li> | |
|
57 | 57 | %endif |
|
58 |
<li class="${'active' if c.active=='public' else ''}"><a href="${h. |
|
|
58 | <li class="${'active' if c.active=='public' else ''}"><a href="${h.route_path('gists_show')}">${_('All public')}</a></li> | |
|
59 | 59 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
60 |
<li class="${'active' if c.active=='my_all' else ''}"><a href="${h. |
|
|
61 |
<li class="${'active' if c.active=='my_private' else ''}"><a href="${h. |
|
|
62 |
<li class="${'active' if c.active=='my_public' else ''}"><a href="${h. |
|
|
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.route_path('gists_show', _query={'private': 1})}">${_('My private')}</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 | 63 | %endif |
|
64 | 64 | </ul> |
|
65 | 65 | </div> |
@@ -25,7 +25,7 b'' | |||
|
25 | 25 | |
|
26 | 26 | <div class="table"> |
|
27 | 27 | <div id="files_data"> |
|
28 |
${h.secure_form(h. |
|
|
28 | ${h.secure_form(h.route_path('gists_create'), id='eform', method='POST')} | |
|
29 | 29 | <div> |
|
30 | 30 | <textarea id="description" name="description" placeholder="${_('Gist description ...')}"></textarea> |
|
31 | 31 |
@@ -18,7 +18,6 b'' | |||
|
18 | 18 | |
|
19 | 19 | <%def name="breadcrumbs_links()"> |
|
20 | 20 | ${_('Gist')} · ${c.gist.gist_access_id} |
|
21 | / ${_('URL')}: ${c.gist.gist_url()} | |
|
22 | 21 | </%def> |
|
23 | 22 | |
|
24 | 23 | <%def name="menu_bar_nav()"> |
@@ -33,11 +32,12 b'' | |||
|
33 | 32 | %if c.rhodecode_user.username != h.DEFAULT_USER: |
|
34 | 33 | <ul class="links"> |
|
35 | 34 | <li> |
|
36 |
<a href="${h. |
|
|
35 | <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a> | |
|
37 | 36 | </li> |
|
38 | 37 | </ul> |
|
39 | 38 | %endif |
|
40 | 39 | </div> |
|
40 | <code>${c.gist.gist_url()}</code> | |
|
41 | 41 | <div class="table"> |
|
42 | 42 | <div id="files_data"> |
|
43 | 43 | <div id="codeblock" class="codeblock"> |
@@ -45,7 +45,7 b'' | |||
|
45 | 45 | <div class="stats"> |
|
46 | 46 | %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id: |
|
47 | 47 | <div class="remove_gist"> |
|
48 |
${h.secure_form( |
|
|
48 | ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), method='POST')} | |
|
49 | 49 | ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")} |
|
50 | 50 | ${h.end_form()} |
|
51 | 51 | </div> |
@@ -53,9 +53,9 b'' | |||
|
53 | 53 | <div class="buttons"> |
|
54 | 54 | ## only owner should see that |
|
55 | 55 | %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id: |
|
56 |
${h.link_to(_('Edit'),h. |
|
|
56 | ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")} | |
|
57 | 57 | %endif |
|
58 |
${h.link_to(_('Show as Raw'),h. |
|
|
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 | 59 | </div> |
|
60 | 60 | <div class="left" > |
|
61 | 61 | %if c.gist.gist_type != 'public': |
@@ -78,19 +78,21 b'' | |||
|
78 | 78 | </div> |
|
79 | 79 | |
|
80 | 80 | </div> |
|
81 |
<div class="commit">${h.urlify_commit_message(c.file_last_commit.message, |
|
|
81 | <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div> | |
|
82 | 82 | </div> |
|
83 | 83 | |
|
84 | 84 | ## iterate over the files |
|
85 | 85 | % for file in c.files: |
|
86 | 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 | 89 | <a href="${c.gist.gist_url()}">¶</a> |
|
89 | 90 | <b >${file.path}</b> |
|
90 | 91 | <div> |
|
91 |
${h.link_to(_('Show as raw'),h. |
|
|
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 | 93 | </div> |
|
93 |
</div> |
|
|
94 | </div> | |
|
95 | --> | |
|
94 | 96 | <div class="code-body textarea text-area editor"> |
|
95 | 97 | %if renderer: |
|
96 | 98 | ${h.render(file.content, renderer=renderer)} |
@@ -397,7 +397,7 b'' | |||
|
397 | 397 | </li> |
|
398 | 398 | %endif |
|
399 | 399 | <li class="${is_active('gists')}"> |
|
400 |
<a class="menulink childs" title="${_('Show Gists')}" href="${h. |
|
|
400 | <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}"> | |
|
401 | 401 | <div class="menulabel">${_('Gists')}</div> |
|
402 | 402 | </a> |
|
403 | 403 | </li> |
@@ -241,7 +241,7 b'' | |||
|
241 | 241 | <%def name="gist_access_id(gist_access_id, full_contact)"> |
|
242 | 242 | <div> |
|
243 | 243 | <b> |
|
244 |
<a href="${h. |
|
|
244 | <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a> | |
|
245 | 245 | </b> |
|
246 | 246 | </div> |
|
247 | 247 | </%def> |
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now