##// END OF EJS Templates
gists: updated template names and synced edit with create template.
marcink -
r3870:796d3ee3 default
parent child Browse files
Show More
@@ -1,414 +1,414 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2019 RhodeCode GmbH
3 # Copyright (C) 2013-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound, HTTPBadRequest
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound, HTTPBadRequest
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.utils2 import time_to_datetime
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model.db import Gist, User, or_
42 from rhodecode.model import validation_schema
42 from rhodecode.model import validation_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class GistView(BaseAppView):
49 class GistView(BaseAppView):
50
50
51 def load_default_context(self):
51 def load_default_context(self):
52 _ = self.request.translate
52 _ = self.request.translate
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54 c.user = c.auth_user.get_instance()
54 c.user = c.auth_user.get_instance()
55
55
56 c.lifetime_values = [
56 c.lifetime_values = [
57 (-1, _('forever')),
57 (-1, _('forever')),
58 (5, _('5 minutes')),
58 (5, _('5 minutes')),
59 (60, _('1 hour')),
59 (60, _('1 hour')),
60 (60 * 24, _('1 day')),
60 (60 * 24, _('1 day')),
61 (60 * 24 * 30, _('1 month')),
61 (60 * 24 * 30, _('1 month')),
62 ]
62 ]
63
63
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
65 c.acl_options = [
65 c.acl_options = [
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
68 ]
68 ]
69
69
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @view_config(
73 @view_config(
74 route_name='gists_show', request_method='GET',
74 route_name='gists_show', request_method='GET',
75 renderer='rhodecode:templates/admin/gists/index.mako')
75 renderer='rhodecode:templates/admin/gists/gist_index.mako')
76 def gist_show_all(self):
76 def gist_show_all(self):
77 c = self.load_default_context()
77 c = self.load_default_context()
78
78
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
80 c.show_private = self.request.GET.get('private') and not_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
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
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
83
83
84 gists = _gists = Gist().query()\
84 gists = _gists = Gist().query()\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .order_by(Gist.created_on.desc())
86 .order_by(Gist.created_on.desc())
87
87
88 c.active = 'public'
88 c.active = 'public'
89 # MY private
89 # MY private
90 if c.show_private and not c.show_public:
90 if c.show_private and not c.show_public:
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
93 c.active = 'my_private'
93 c.active = 'my_private'
94 # MY public
94 # MY public
95 elif c.show_public and not c.show_private:
95 elif c.show_public and not c.show_private:
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
98 c.active = 'my_public'
98 c.active = 'my_public'
99 # MY public+private
99 # MY public+private
100 elif c.show_private and c.show_public:
100 elif c.show_private and c.show_public:
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 Gist.gist_type == Gist.GIST_PRIVATE))\
102 Gist.gist_type == Gist.GIST_PRIVATE))\
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
104 c.active = 'my_all'
104 c.active = 'my_all'
105 # Show all by super-admin
105 # Show all by super-admin
106 elif c.show_all:
106 elif c.show_all:
107 c.active = 'all'
107 c.active = 'all'
108 gists = _gists
108 gists = _gists
109
109
110 # default show ALL public gists
110 # default show ALL public gists
111 if not c.show_public and not c.show_private and not c.show_all:
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)
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 c.active = 'public'
113 c.active = 'public'
114
114
115 _render = self.request.get_partial_renderer(
115 _render = self.request.get_partial_renderer(
116 'rhodecode:templates/data_table/_dt_elements.mako')
116 'rhodecode:templates/data_table/_dt_elements.mako')
117
117
118 data = []
118 data = []
119
119
120 for gist in gists:
120 for gist in gists:
121 data.append({
121 data.append({
122 'created_on': _render('gist_created', gist.created_on),
122 'created_on': _render('gist_created', gist.created_on),
123 'created_on_raw': gist.created_on,
123 'created_on_raw': gist.created_on,
124 'type': _render('gist_type', gist.gist_type),
124 'type': _render('gist_type', gist.gist_type),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
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),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author_raw': h.escape(gist.owner.full_contact),
127 'author_raw': h.escape(gist.owner.full_contact),
128 'expires': _render('gist_expires', gist.gist_expires),
128 'expires': _render('gist_expires', gist.gist_expires),
129 'description': _render('gist_description', gist.gist_description)
129 'description': _render('gist_description', gist.gist_description)
130 })
130 })
131 c.data = json.dumps(data)
131 c.data = json.dumps(data)
132
132
133 return self._get_template_context(c)
133 return self._get_template_context(c)
134
134
135 @LoginRequired()
135 @LoginRequired()
136 @NotAnonymous()
136 @NotAnonymous()
137 @view_config(
137 @view_config(
138 route_name='gists_new', request_method='GET',
138 route_name='gists_new', request_method='GET',
139 renderer='rhodecode:templates/admin/gists/new.mako')
139 renderer='rhodecode:templates/admin/gists/gist_new.mako')
140 def gist_new(self):
140 def gist_new(self):
141 c = self.load_default_context()
141 c = self.load_default_context()
142 return self._get_template_context(c)
142 return self._get_template_context(c)
143
143
144 @LoginRequired()
144 @LoginRequired()
145 @NotAnonymous()
145 @NotAnonymous()
146 @CSRFRequired()
146 @CSRFRequired()
147 @view_config(
147 @view_config(
148 route_name='gists_create', request_method='POST',
148 route_name='gists_create', request_method='POST',
149 renderer='rhodecode:templates/admin/gists/new.mako')
149 renderer='rhodecode:templates/admin/gists/gist_new.mako')
150 def gist_create(self):
150 def gist_create(self):
151 _ = self.request.translate
151 _ = self.request.translate
152 c = self.load_default_context()
152 c = self.load_default_context()
153
153
154 data = dict(self.request.POST)
154 data = dict(self.request.POST)
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
156 data['nodes'] = [{
156 data['nodes'] = [{
157 'filename': data['filename'],
157 'filename': data['filename'],
158 'content': data.get('content'),
158 'content': data.get('content'),
159 'mimetype': data.get('mimetype') # None is autodetect
159 'mimetype': data.get('mimetype') # None is autodetect
160 }]
160 }]
161
161
162 data['gist_type'] = (
162 data['gist_type'] = (
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
164 data['gist_acl_level'] = (
164 data['gist_acl_level'] = (
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
166
166
167 schema = gist_schema.GistSchema().bind(
167 schema = gist_schema.GistSchema().bind(
168 lifetime_options=[x[0] for x in c.lifetime_values])
168 lifetime_options=[x[0] for x in c.lifetime_values])
169
169
170 try:
170 try:
171
171
172 schema_data = schema.deserialize(data)
172 schema_data = schema.deserialize(data)
173 # convert to safer format with just KEYs so we sure no duplicates
173 # convert to safer format with just KEYs so we sure no duplicates
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
175 schema_data['nodes'])
175 schema_data['nodes'])
176
176
177 gist = GistModel().create(
177 gist = GistModel().create(
178 gist_id=schema_data['gistid'], # custom access id not real ID
178 gist_id=schema_data['gistid'], # custom access id not real ID
179 description=schema_data['description'],
179 description=schema_data['description'],
180 owner=self._rhodecode_user.user_id,
180 owner=self._rhodecode_user.user_id,
181 gist_mapping=schema_data['nodes'],
181 gist_mapping=schema_data['nodes'],
182 gist_type=schema_data['gist_type'],
182 gist_type=schema_data['gist_type'],
183 lifetime=schema_data['lifetime'],
183 lifetime=schema_data['lifetime'],
184 gist_acl_level=schema_data['gist_acl_level']
184 gist_acl_level=schema_data['gist_acl_level']
185 )
185 )
186 Session().commit()
186 Session().commit()
187 new_gist_id = gist.gist_access_id
187 new_gist_id = gist.gist_access_id
188 except validation_schema.Invalid as errors:
188 except validation_schema.Invalid as errors:
189 defaults = data
189 defaults = data
190 errors = errors.asdict()
190 errors = errors.asdict()
191
191
192 if 'nodes.0.content' in errors:
192 if 'nodes.0.content' in errors:
193 errors['content'] = errors['nodes.0.content']
193 errors['content'] = errors['nodes.0.content']
194 del errors['nodes.0.content']
194 del errors['nodes.0.content']
195 if 'nodes.0.filename' in errors:
195 if 'nodes.0.filename' in errors:
196 errors['filename'] = errors['nodes.0.filename']
196 errors['filename'] = errors['nodes.0.filename']
197 del errors['nodes.0.filename']
197 del errors['nodes.0.filename']
198
198
199 data = render('rhodecode:templates/admin/gists/new.mako',
199 data = render('rhodecode:templates/admin/gists/gist_new.mako',
200 self._get_template_context(c), self.request)
200 self._get_template_context(c), self.request)
201 html = formencode.htmlfill.render(
201 html = formencode.htmlfill.render(
202 data,
202 data,
203 defaults=defaults,
203 defaults=defaults,
204 errors=errors,
204 errors=errors,
205 prefix_error=False,
205 prefix_error=False,
206 encoding="UTF-8",
206 encoding="UTF-8",
207 force_defaults=False
207 force_defaults=False
208 )
208 )
209 return Response(html)
209 return Response(html)
210
210
211 except Exception:
211 except Exception:
212 log.exception("Exception while trying to create a gist")
212 log.exception("Exception while trying to create a gist")
213 h.flash(_('Error occurred during gist creation'), category='error')
213 h.flash(_('Error occurred during gist creation'), category='error')
214 raise HTTPFound(h.route_url('gists_new'))
214 raise HTTPFound(h.route_url('gists_new'))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
216
216
217 @LoginRequired()
217 @LoginRequired()
218 @NotAnonymous()
218 @NotAnonymous()
219 @CSRFRequired()
219 @CSRFRequired()
220 @view_config(
220 @view_config(
221 route_name='gist_delete', request_method='POST')
221 route_name='gist_delete', request_method='POST')
222 def gist_delete(self):
222 def gist_delete(self):
223 _ = self.request.translate
223 _ = self.request.translate
224 gist_id = self.request.matchdict['gist_id']
224 gist_id = self.request.matchdict['gist_id']
225
225
226 c = self.load_default_context()
226 c = self.load_default_context()
227 c.gist = Gist.get_or_404(gist_id)
227 c.gist = Gist.get_or_404(gist_id)
228
228
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
230 if not (h.HasPermissionAny('hg.admin')() or owner):
230 if not (h.HasPermissionAny('hg.admin')() or owner):
231 log.warning('Deletion of Gist was forbidden '
231 log.warning('Deletion of Gist was forbidden '
232 'by unauthorized user: `%s`', self._rhodecode_user)
232 'by unauthorized user: `%s`', self._rhodecode_user)
233 raise HTTPNotFound()
233 raise HTTPNotFound()
234
234
235 GistModel().delete(c.gist)
235 GistModel().delete(c.gist)
236 Session().commit()
236 Session().commit()
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
238
238
239 raise HTTPFound(h.route_url('gists_show'))
239 raise HTTPFound(h.route_url('gists_show'))
240
240
241 def _get_gist(self, gist_id):
241 def _get_gist(self, gist_id):
242
242
243 gist = Gist.get_or_404(gist_id)
243 gist = Gist.get_or_404(gist_id)
244
244
245 # Check if this gist is expired
245 # Check if this gist is expired
246 if gist.gist_expires != -1:
246 if gist.gist_expires != -1:
247 if time.time() > gist.gist_expires:
247 if time.time() > gist.gist_expires:
248 log.error(
248 log.error(
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
250 raise HTTPNotFound()
250 raise HTTPNotFound()
251
251
252 # check if this gist requires a login
252 # check if this gist requires a login
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_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`",
255 log.error("Anonymous user %s tried to access protected gist `%s`",
256 self._rhodecode_user, gist_id)
256 self._rhodecode_user, gist_id)
257 raise HTTPNotFound()
257 raise HTTPNotFound()
258 return gist
258 return gist
259
259
260 @LoginRequired()
260 @LoginRequired()
261 @view_config(
261 @view_config(
262 route_name='gist_show', request_method='GET',
262 route_name='gist_show', request_method='GET',
263 renderer='rhodecode:templates/admin/gists/show.mako')
263 renderer='rhodecode:templates/admin/gists/gist_show.mako')
264 @view_config(
264 @view_config(
265 route_name='gist_show_rev', request_method='GET',
265 route_name='gist_show_rev', request_method='GET',
266 renderer='rhodecode:templates/admin/gists/show.mako')
266 renderer='rhodecode:templates/admin/gists/gist_show.mako')
267 @view_config(
267 @view_config(
268 route_name='gist_show_formatted', request_method='GET',
268 route_name='gist_show_formatted', request_method='GET',
269 renderer=None)
269 renderer=None)
270 @view_config(
270 @view_config(
271 route_name='gist_show_formatted_path', request_method='GET',
271 route_name='gist_show_formatted_path', request_method='GET',
272 renderer=None)
272 renderer=None)
273 def gist_show(self):
273 def gist_show(self):
274 gist_id = self.request.matchdict['gist_id']
274 gist_id = self.request.matchdict['gist_id']
275
275
276 # TODO(marcink): expose those via matching dict
276 # TODO(marcink): expose those via matching dict
277 revision = self.request.matchdict.get('revision', 'tip')
277 revision = self.request.matchdict.get('revision', 'tip')
278 f_path = self.request.matchdict.get('f_path', None)
278 f_path = self.request.matchdict.get('f_path', None)
279 return_format = self.request.matchdict.get('format')
279 return_format = self.request.matchdict.get('format')
280
280
281 c = self.load_default_context()
281 c = self.load_default_context()
282 c.gist = self._get_gist(gist_id)
282 c.gist = self._get_gist(gist_id)
283 c.render = not self.request.GET.get('no-render', False)
283 c.render = not self.request.GET.get('no-render', False)
284
284
285 try:
285 try:
286 c.file_last_commit, c.files = GistModel().get_gist_files(
286 c.file_last_commit, c.files = GistModel().get_gist_files(
287 gist_id, revision=revision)
287 gist_id, revision=revision)
288 except VCSError:
288 except VCSError:
289 log.exception("Exception in gist show")
289 log.exception("Exception in gist show")
290 raise HTTPNotFound()
290 raise HTTPNotFound()
291
291
292 if return_format == 'raw':
292 if return_format == 'raw':
293 content = '\n\n'.join([f.content for f in c.files
293 content = '\n\n'.join([f.content for f in c.files
294 if (f_path is None or f.path == f_path)])
294 if (f_path is None or f.path == f_path)])
295 response = Response(content)
295 response = Response(content)
296 response.content_type = 'text/plain'
296 response.content_type = 'text/plain'
297 return response
297 return response
298 elif return_format:
298 elif return_format:
299 raise HTTPBadRequest()
299 raise HTTPBadRequest()
300
300
301 return self._get_template_context(c)
301 return self._get_template_context(c)
302
302
303 @LoginRequired()
303 @LoginRequired()
304 @NotAnonymous()
304 @NotAnonymous()
305 @view_config(
305 @view_config(
306 route_name='gist_edit', request_method='GET',
306 route_name='gist_edit', request_method='GET',
307 renderer='rhodecode:templates/admin/gists/edit.mako')
307 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
308 def gist_edit(self):
308 def gist_edit(self):
309 _ = self.request.translate
309 _ = self.request.translate
310 gist_id = self.request.matchdict['gist_id']
310 gist_id = self.request.matchdict['gist_id']
311 c = self.load_default_context()
311 c = self.load_default_context()
312 c.gist = self._get_gist(gist_id)
312 c.gist = self._get_gist(gist_id)
313
313
314 owner = c.gist.gist_owner == self._rhodecode_user.user_id
314 owner = c.gist.gist_owner == self._rhodecode_user.user_id
315 if not (h.HasPermissionAny('hg.admin')() or owner):
315 if not (h.HasPermissionAny('hg.admin')() or owner):
316 raise HTTPNotFound()
316 raise HTTPNotFound()
317
317
318 try:
318 try:
319 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
319 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
320 except VCSError:
320 except VCSError:
321 log.exception("Exception in gist edit")
321 log.exception("Exception in gist edit")
322 raise HTTPNotFound()
322 raise HTTPNotFound()
323
323
324 if c.gist.gist_expires == -1:
324 if c.gist.gist_expires == -1:
325 expiry = _('never')
325 expiry = _('never')
326 else:
326 else:
327 # this cannot use timeago, since it's used in select2 as a value
327 # this cannot use timeago, since it's used in select2 as a value
328 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
328 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
329
329
330 c.lifetime_values.append(
330 c.lifetime_values.append(
331 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
331 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
332 )
332 )
333
333
334 return self._get_template_context(c)
334 return self._get_template_context(c)
335
335
336 @LoginRequired()
336 @LoginRequired()
337 @NotAnonymous()
337 @NotAnonymous()
338 @CSRFRequired()
338 @CSRFRequired()
339 @view_config(
339 @view_config(
340 route_name='gist_update', request_method='POST',
340 route_name='gist_update', request_method='POST',
341 renderer='rhodecode:templates/admin/gists/edit.mako')
341 renderer='rhodecode:templates/admin/gists/gist_edit.mako')
342 def gist_update(self):
342 def gist_update(self):
343 _ = self.request.translate
343 _ = self.request.translate
344 gist_id = self.request.matchdict['gist_id']
344 gist_id = self.request.matchdict['gist_id']
345 c = self.load_default_context()
345 c = self.load_default_context()
346 c.gist = self._get_gist(gist_id)
346 c.gist = self._get_gist(gist_id)
347
347
348 owner = c.gist.gist_owner == self._rhodecode_user.user_id
348 owner = c.gist.gist_owner == self._rhodecode_user.user_id
349 if not (h.HasPermissionAny('hg.admin')() or owner):
349 if not (h.HasPermissionAny('hg.admin')() or owner):
350 raise HTTPNotFound()
350 raise HTTPNotFound()
351
351
352 data = peppercorn.parse(self.request.POST.items())
352 data = peppercorn.parse(self.request.POST.items())
353
353
354 schema = gist_schema.GistSchema()
354 schema = gist_schema.GistSchema()
355 schema = schema.bind(
355 schema = schema.bind(
356 # '0' is special value to leave lifetime untouched
356 # '0' is special value to leave lifetime untouched
357 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
357 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
358 )
358 )
359
359
360 try:
360 try:
361 schema_data = schema.deserialize(data)
361 schema_data = schema.deserialize(data)
362 # convert to safer format with just KEYs so we sure no duplicates
362 # convert to safer format with just KEYs so we sure no duplicates
363 schema_data['nodes'] = gist_schema.sequence_to_nodes(
363 schema_data['nodes'] = gist_schema.sequence_to_nodes(
364 schema_data['nodes'])
364 schema_data['nodes'])
365
365
366 GistModel().update(
366 GistModel().update(
367 gist=c.gist,
367 gist=c.gist,
368 description=schema_data['description'],
368 description=schema_data['description'],
369 owner=c.gist.owner,
369 owner=c.gist.owner,
370 gist_mapping=schema_data['nodes'],
370 gist_mapping=schema_data['nodes'],
371 lifetime=schema_data['lifetime'],
371 lifetime=schema_data['lifetime'],
372 gist_acl_level=schema_data['gist_acl_level']
372 gist_acl_level=schema_data['gist_acl_level']
373 )
373 )
374
374
375 Session().commit()
375 Session().commit()
376 h.flash(_('Successfully updated gist content'), category='success')
376 h.flash(_('Successfully updated gist content'), category='success')
377 except NodeNotChangedError:
377 except NodeNotChangedError:
378 # raised if nothing was changed in repo itself. We anyway then
378 # raised if nothing was changed in repo itself. We anyway then
379 # store only DB stuff for gist
379 # store only DB stuff for gist
380 Session().commit()
380 Session().commit()
381 h.flash(_('Successfully updated gist data'), category='success')
381 h.flash(_('Successfully updated gist data'), category='success')
382 except validation_schema.Invalid as errors:
382 except validation_schema.Invalid as errors:
383 errors = h.escape(errors.asdict())
383 errors = h.escape(errors.asdict())
384 h.flash(_('Error occurred during update of gist {}: {}').format(
384 h.flash(_('Error occurred during update of gist {}: {}').format(
385 gist_id, errors), category='error')
385 gist_id, errors), category='error')
386 except Exception:
386 except Exception:
387 log.exception("Exception in gist edit")
387 log.exception("Exception in gist edit")
388 h.flash(_('Error occurred during update of gist %s') % gist_id,
388 h.flash(_('Error occurred during update of gist %s') % gist_id,
389 category='error')
389 category='error')
390
390
391 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
391 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
392
392
393 @LoginRequired()
393 @LoginRequired()
394 @NotAnonymous()
394 @NotAnonymous()
395 @view_config(
395 @view_config(
396 route_name='gist_edit_check_revision', request_method='GET',
396 route_name='gist_edit_check_revision', request_method='GET',
397 renderer='json_ext')
397 renderer='json_ext')
398 def gist_edit_check_revision(self):
398 def gist_edit_check_revision(self):
399 _ = self.request.translate
399 _ = self.request.translate
400 gist_id = self.request.matchdict['gist_id']
400 gist_id = self.request.matchdict['gist_id']
401 c = self.load_default_context()
401 c = self.load_default_context()
402 c.gist = self._get_gist(gist_id)
402 c.gist = self._get_gist(gist_id)
403
403
404 last_rev = c.gist.scm_instance().get_commit()
404 last_rev = c.gist.scm_instance().get_commit()
405 success = True
405 success = True
406 revision = self.request.GET.get('revision')
406 revision = self.request.GET.get('revision')
407
407
408 if revision != last_rev.raw_id:
408 if revision != last_rev.raw_id:
409 log.error('Last revision %s is different then submitted %s',
409 log.error('Last revision %s is different then submitted %s',
410 revision, last_rev)
410 revision, last_rev)
411 # our gist has newer version than we
411 # our gist has newer version than we
412 success = False
412 success = False
413
413
414 return {'success': success}
414 return {'success': success}
@@ -1,139 +1,137 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
5 ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()"></%def>
12 ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
13 </%def>
14
12
15 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='gists')}
14 ${self.menu_items(active='gists')}
17 </%def>
15 </%def>
18
16
19 <%def name="main()">
17 <%def name="main()">
20 <div class="box">
18 <div class="box">
21 <!-- box / title -->
19 <!-- box / title -->
22 <div class="title">
20 <div class="title">
23 ${self.breadcrumbs()}
21
24 </div>
22 </div>
25
23
26 <div class="table">
24 <div class="table">
27
28 <div id="files_data">
25 <div id="files_data">
29 ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', request=request)}
26 ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', request=request)}
30 <div>
27 <div>
31 <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash">
28 <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash">
32 <textarea id="description" name="description"
33 placeholder="${_('Gist description ...')}">${c.gist.gist_description}</textarea>
34 <div>
29 <div>
35 <span class="gist-gravatar">
30 <span class="gist-gravatar">
36 ${self.gravatar(h.email_or_none(c.rhodecode_user.full_contact), 30)}
31 ${self.gravatar(h.email_or_none(c.rhodecode_user.full_contact), 30)}
37 </span>
32 </span>
38 <label for='lifetime'>${_('Gist lifetime')}</label>
33 <label for='lifetime'>${_('Gist lifetime')}</label>
39 ${h.dropdownmenu('lifetime', '0', c.lifetime_options)}
34 ${h.dropdownmenu('lifetime', '0', c.lifetime_options)}
40
35
41 <label for='gist_acl_level'>${_('Gist access level')}</label>
36 <label for='gist_acl_level'>${_('Gist access level')}</label>
42 ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)}
37 ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)}
38
39 <textarea style="margin-top: 5px; border-color: #dbd9da" id="description" name="description"
40 placeholder="${_('Gist description ...')}">${c.gist.gist_description}</textarea>
43 </div>
41 </div>
44 </div>
42 </div>
45
43
46 ## peppercorn schema
44 ## peppercorn schema
47 <input type="hidden" name="__start__" value="nodes:sequence"/>
45 <input type="hidden" name="__start__" value="nodes:sequence"/>
48 % for cnt, file in enumerate(c.files):
46 % for cnt, file in enumerate(c.files):
49 <input type="hidden" name="__start__" value="file:mapping"/>
47 <input type="hidden" name="__start__" value="file:mapping"/>
50 <div id="codeblock" class="codeblock" >
48 <div id="codeblock" class="codeblock" >
51 <div class="code-header">
49 <div class="code-header">
52 <div class="form">
50 <div class="form">
53 <div class="fields">
51 <div class="fields">
54 <input type="hidden" name="filename_org" value="${file.path}" >
52 <input type="hidden" name="filename_org" value="${file.path}" >
55 <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}">
53 <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}">
56 ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
54 ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
57 </div>
55 </div>
58 </div>
56 </div>
59 </div>
57 </div>
60 <div class="editor_container">
58 <div class="editor_container">
61 <pre id="editor_pre"></pre>
59 <pre id="editor_pre"></pre>
62 <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea>
60 <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea>
63 </div>
61 </div>
64 </div>
62 </div>
65 <input type="hidden" name="__end__" />
63 <input type="hidden" name="__end__" />
66
64
67 ## dynamic edit box.
65 ## dynamic edit box.
68 <script type="text/javascript">
66 <script type="text/javascript">
69 $(document).ready(function(){
67 $(document).ready(function(){
70 var myCodeMirror = initCodeMirror(
68 var myCodeMirror = initCodeMirror(
71 "editor_${h.FID('f',file.path)}", '');
69 "editor_${h.FID('f',file.path)}", '');
72
70
73 var modes_select = $("#mimetype_${h.FID('f',file.path)}");
71 var modes_select = $("#mimetype_${h.FID('f',file.path)}");
74 fillCodeMirrorOptions(modes_select);
72 fillCodeMirrorOptions(modes_select);
75
73
76 // try to detect the mode based on the file we edit
74 // try to detect the mode based on the file we edit
77 var mimetype = "${file.mimetype}";
75 var mimetype = "${file.mimetype}";
78 var detected_mode = detectCodeMirrorMode(
76 var detected_mode = detectCodeMirrorMode(
79 "${file.path}", mimetype);
77 "${file.path}", mimetype);
80
78
81 if(detected_mode){
79 if(detected_mode){
82 $(modes_select).select2("val", mimetype);
80 $(modes_select).select2("val", mimetype);
83 $(modes_select).change();
81 $(modes_select).change();
84 setCodeMirrorMode(myCodeMirror, detected_mode);
82 setCodeMirrorMode(myCodeMirror, detected_mode);
85 }
83 }
86
84
87 var filename_selector = "#filename_${h.FID('f',file.path)}";
85 var filename_selector = "#filename_${h.FID('f',file.path)}";
88 // on change of select field set mode
86 // on change of select field set mode
89 setCodeMirrorModeFromSelect(
87 setCodeMirrorModeFromSelect(
90 modes_select, filename_selector, myCodeMirror, null);
88 modes_select, filename_selector, myCodeMirror, null);
91
89
92 // on entering the new filename set mode, from given extension
90 // on entering the new filename set mode, from given extension
93 setCodeMirrorModeFromInput(
91 setCodeMirrorModeFromInput(
94 modes_select, filename_selector, myCodeMirror, null);
92 modes_select, filename_selector, myCodeMirror, null);
95 });
93 });
96 </script>
94 </script>
97 %endfor
95 %endfor
98 <input type="hidden" name="__end__" />
96 <input type="hidden" name="__end__" />
99
97
100 <div class="pull-right">
98 <div class="pull-right">
101 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
99 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
102 <a class="btn" href="${h.route_path('gist_show', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
100 <a class="btn" href="${h.route_path('gist_show', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
103 </div>
101 </div>
104 ${h.end_form()}
102 ${h.end_form()}
105 </div>
103 </div>
106 </div>
104 </div>
107
105
108 </div>
106 </div>
109 <script>
107 <script>
110 $('#update').on('click', function(e){
108 $('#update').on('click', function(e){
111 e.preventDefault();
109 e.preventDefault();
112
110
113 $(this).val('Updating...');
111 $(this).val('Updating...');
114 $(this).attr('disabled', 'disabled');
112 $(this).attr('disabled', 'disabled');
115 // check for newer version.
113 // check for newer version.
116 $.ajax({
114 $.ajax({
117 url: "${h.route_path('gist_edit_check_revision', gist_id=c.gist.gist_access_id)}",
115 url: "${h.route_path('gist_edit_check_revision', gist_id=c.gist.gist_access_id)}",
118 data: {
116 data: {
119 'revision': '${c.file_last_commit.raw_id}'
117 'revision': '${c.file_last_commit.raw_id}'
120 },
118 },
121 dataType: 'json',
119 dataType: 'json',
122 type: 'GET',
120 type: 'GET',
123 success: function(data) {
121 success: function(data) {
124 if(data.success === false){
122 if(data.success === false){
125 message = '${h.literal(_('Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version.')
123 message = '${h.literal(_('Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version.')
126 % {'here': h.link_to('here', h.route_path('gist_edit', gist_id=c.gist.gist_access_id))})}'
124 % {'here': h.link_to('here', h.route_path('gist_edit', gist_id=c.gist.gist_access_id))})}'
127 alertMessage = [{"message": {
125 alertMessage = [{"message": {
128 "message": message, "force": "true", "level": "warning"}}];
126 "message": message, "force": "true", "level": "warning"}}];
129 $.Topic('/notifications').publish(alertMessage[0]);
127 $.Topic('/notifications').publish(alertMessage[0]);
130 }
128 }
131 else{
129 else{
132 $('#eform').submit();
130 $('#eform').submit();
133 }
131 }
134 }
132 }
135 });
133 });
136 })
134 })
137
135
138 </script>
136 </script>
139 </%def>
137 </%def>
1 NO CONTENT: file renamed from rhodecode/templates/admin/gists/index.mako to rhodecode/templates/admin/gists/gist_index.mako
NO CONTENT: file renamed from rhodecode/templates/admin/gists/index.mako to rhodecode/templates/admin/gists/gist_index.mako
@@ -1,85 +1,85 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('New Gist')}
5 ${_('New Gist')}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()"></%def>
11 <%def name="breadcrumbs_links()"></%def>
12
12
13 <%def name="menu_bar_nav()">
13 <%def name="menu_bar_nav()">
14 ${self.menu_items(active='gists')}
14 ${self.menu_items(active='gists')}
15 </%def>
15 </%def>
16
16
17 <%def name="main()">
17 <%def name="main()">
18 <div class="box">
18 <div class="box">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21
21
22 </div>
22 </div>
23
23
24 <div class="table">
24 <div class="table">
25 <div id="files_data">
25 <div id="files_data">
26 ${h.secure_form(h.route_path('gists_create'), id='eform', request=request)}
26 ${h.secure_form(h.route_path('gists_create'), id='eform', request=request)}
27 <div>
27 <div>
28 <span class="gist-gravatar">
28 <span class="gist-gravatar">
29 ${self.gravatar(c.rhodecode_user.email, 30)}
29 ${self.gravatar(c.rhodecode_user.email, 30)}
30 </span>
30 </span>
31 <label for='gistid'>${_('Gist id')}</label>
31 <label for='gistid'>${_('Gist id')}</label>
32 ${h.text('gistid', placeholder=_('Auto generated'))}
32 ${h.text('gistid', placeholder=_('Auto generated'))}
33
33
34 <label for='lifetime'>${_('Gist lifetime')}</label>
34 <label for='lifetime'>${_('Gist lifetime')}</label>
35 ${h.dropdownmenu('lifetime', '', c.lifetime_options)}
35 ${h.dropdownmenu('lifetime', '', c.lifetime_options)}
36
36
37 <label for='acl_level'>${_('Gist access level')}</label>
37 <label for='acl_level'>${_('Gist access level')}</label>
38 ${h.dropdownmenu('gist_acl_level', '', c.acl_options)}
38 ${h.dropdownmenu('gist_acl_level', '', c.acl_options)}
39
39
40 <textarea style="margin-top: 5px" id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
40 <textarea style="margin-top: 5px; border-color: #dbd9da" id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
41 </div>
41 </div>
42
42
43 <div id="codeblock" class="codeblock">
43 <div id="codeblock" class="codeblock">
44 <div class="code-header">
44 <div class="code-header">
45 <div class="form">
45 <div class="form">
46 <div class="fields">
46 <div class="fields">
47 ${h.text('filename', size=30, placeholder=_('name gist file...'))}
47 ${h.text('filename', size=30, placeholder=_('name gist file...'))}
48 ${h.dropdownmenu('mimetype','plain',[('plain',_('plain'))],enable_filter=True)}
48 ${h.dropdownmenu('mimetype','plain',[('plain',_('plain'))],enable_filter=True)}
49 </div>
49 </div>
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div id="editor_container">
53 <div id="editor_container">
54 <div id="editor_pre"></div>
54 <div id="editor_pre"></div>
55 <textarea id="editor" name="content" ></textarea>
55 <textarea id="editor" name="content" ></textarea>
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 <div class="pull-right">
59 <div class="pull-right">
60 ${h.submit('private',_('Create Private Gist'),class_="btn")}
60 ${h.submit('private',_('Create Private Gist'),class_="btn")}
61 ${h.submit('public',_('Create Public Gist'),class_="btn")}
61 ${h.submit('public',_('Create Public Gist'),class_="btn")}
62 </div>
62 </div>
63 ${h.end_form()}
63 ${h.end_form()}
64 </div>
64 </div>
65 </div>
65 </div>
66
66
67 </div>
67 </div>
68
68
69 <script type="text/javascript">
69 <script type="text/javascript">
70 var myCodeMirror = initCodeMirror('editor', '');
70 var myCodeMirror = initCodeMirror('editor', '');
71
71
72 var modes_select = $('#mimetype');
72 var modes_select = $('#mimetype');
73 fillCodeMirrorOptions(modes_select);
73 fillCodeMirrorOptions(modes_select);
74
74
75 var filename_selector = '#filename';
75 var filename_selector = '#filename';
76 // on change of select field set mode
76 // on change of select field set mode
77 setCodeMirrorModeFromSelect(
77 setCodeMirrorModeFromSelect(
78 modes_select, filename_selector, myCodeMirror, null);
78 modes_select, filename_selector, myCodeMirror, null);
79
79
80 // on entering the new filename set mode, from given extension
80 // on entering the new filename set mode, from given extension
81 setCodeMirrorModeFromInput(
81 setCodeMirrorModeFromInput(
82 modes_select, filename_selector, myCodeMirror, null);
82 modes_select, filename_selector, myCodeMirror, null);
83
83
84 </script>
84 </script>
85 </%def>
85 </%def>
1 NO CONTENT: file renamed from rhodecode/templates/admin/gists/show.mako to rhodecode/templates/admin/gists/gist_show.mako
NO CONTENT: file renamed from rhodecode/templates/admin/gists/show.mako to rhodecode/templates/admin/gists/gist_show.mako
General Comments 0
You need to be logged in to leave comments. Login now