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 |
|
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( |
|
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( |
|
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( |
|
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( |
|
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( |
|
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 |
|
|
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 |
|
|
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( |
|
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 |
|
|
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 |
|
|
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 |
|
|
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( |
|
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( |
|
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 |
|
|
275 | route_path('gist_delete', gist_id=gist.gist_id), | |
250 |
params={ |
|
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 |
|
|
284 | route_path('gist_delete', gist_id=gist.gist_id), | |
258 |
params={ |
|
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 |
|
|
293 | route_path('gist_delete', gist_id=gist.gist_id), | |
266 |
params={ |
|
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( |
|
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( |
|
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,13 +333,16 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( |
|
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( |
|
344 | response = self.app.get( | |
|
345 | route_path('gist_show_formatted_path', | |||
313 |
|
|
346 | gist_id=gist.gist_access_id, format='raw', | |
314 |
|
|
347 | revision='tip', f_path='gist-show-me-raw')) | |
315 | assert response.body == 'GIST BODY' |
|
348 | assert response.body == 'GIST BODY' | |
@@ -317,22 +350,25 b' class TestGistsController(TestController' | |||||
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( |
|
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( |
|
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( |
|
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( |
|
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( |
|
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( |
|
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(' |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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. |
|
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')} · ${c.gist.gist_access_id} |
|
20 | ${_('Gist')} · ${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. |
|
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( |
|
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. |
|
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. |
|
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, |
|
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. |
|
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. |
|
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. |
|
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