##// END OF EJS Templates
imports: Remove unused imports.
Martin Bornhold -
r850:775a48ff default
parent child Browse files
Show More
@@ -1,367 +1,366 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2016 RhodeCode GmbH
3 # Copyright (C) 2013-2016 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
21
22 """
22 """
23 gist controller for RhodeCode
23 gist controller for RhodeCode
24 """
24 """
25
25
26 import time
26 import time
27 import logging
27 import logging
28
28
29 import formencode
29 import formencode
30 import peppercorn
30 import peppercorn
31 from formencode import htmlfill
32
31
33 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import redirect
35 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
36 from webob.exc import HTTPNotFound, HTTPForbidden
35 from webob.exc import HTTPNotFound, HTTPForbidden
37 from sqlalchemy.sql.expression import or_
36 from sqlalchemy.sql.expression import or_
38
37
39
38
40 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
41 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
42 from rhodecode.model.db import Gist, User
41 from rhodecode.model.db import Gist, User
43 from rhodecode.lib import auth
42 from rhodecode.lib import auth
44 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
45 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
46 from rhodecode.lib.auth import LoginRequired, NotAnonymous
45 from rhodecode.lib.auth import LoginRequired, NotAnonymous
47 from rhodecode.lib.utils import jsonify
46 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
47 from rhodecode.lib.utils2 import time_to_datetime
49 from rhodecode.lib.ext_json import json
48 from rhodecode.lib.ext_json import json
50 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
49 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
51 from rhodecode.model import validation_schema
50 from rhodecode.model import validation_schema
52 from rhodecode.model.validation_schema.schemas import gist_schema
51 from rhodecode.model.validation_schema.schemas import gist_schema
53
52
54
53
55 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
56
55
57
56
58 class GistsController(BaseController):
57 class GistsController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
58 """REST Controller styled on the Atom Publishing Protocol"""
60
59
61 def __load_defaults(self, extra_values=None):
60 def __load_defaults(self, extra_values=None):
62 c.lifetime_values = [
61 c.lifetime_values = [
63 (-1, _('forever')),
62 (-1, _('forever')),
64 (5, _('5 minutes')),
63 (5, _('5 minutes')),
65 (60, _('1 hour')),
64 (60, _('1 hour')),
66 (60 * 24, _('1 day')),
65 (60 * 24, _('1 day')),
67 (60 * 24 * 30, _('1 month')),
66 (60 * 24 * 30, _('1 month')),
68 ]
67 ]
69 if extra_values:
68 if extra_values:
70 c.lifetime_values.append(extra_values)
69 c.lifetime_values.append(extra_values)
71 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
70 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
72 c.acl_options = [
71 c.acl_options = [
73 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
72 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
74 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
73 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
75 ]
74 ]
76
75
77 @LoginRequired()
76 @LoginRequired()
78 def index(self):
77 def index(self):
79 """GET /admin/gists: All items in the collection"""
78 """GET /admin/gists: All items in the collection"""
80 # url('gists')
79 # url('gists')
81 not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
80 not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
82 c.show_private = request.GET.get('private') and not_default_user
81 c.show_private = request.GET.get('private') and not_default_user
83 c.show_public = request.GET.get('public') and not_default_user
82 c.show_public = request.GET.get('public') and not_default_user
84 c.show_all = request.GET.get('all') and c.rhodecode_user.admin
83 c.show_all = request.GET.get('all') and c.rhodecode_user.admin
85
84
86 gists = _gists = Gist().query()\
85 gists = _gists = Gist().query()\
87 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
86 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
88 .order_by(Gist.created_on.desc())
87 .order_by(Gist.created_on.desc())
89
88
90 c.active = 'public'
89 c.active = 'public'
91 # MY private
90 # MY private
92 if c.show_private and not c.show_public:
91 if c.show_private and not c.show_public:
93 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
92 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
94 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
93 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
95 c.active = 'my_private'
94 c.active = 'my_private'
96 # MY public
95 # MY public
97 elif c.show_public and not c.show_private:
96 elif c.show_public and not c.show_private:
98 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
97 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
99 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
98 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
100 c.active = 'my_public'
99 c.active = 'my_public'
101 # MY public+private
100 # MY public+private
102 elif c.show_private and c.show_public:
101 elif c.show_private and c.show_public:
103 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
102 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
104 Gist.gist_type == Gist.GIST_PRIVATE))\
103 Gist.gist_type == Gist.GIST_PRIVATE))\
105 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
104 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
106 c.active = 'my_all'
105 c.active = 'my_all'
107 # Show all by super-admin
106 # Show all by super-admin
108 elif c.show_all:
107 elif c.show_all:
109 c.active = 'all'
108 c.active = 'all'
110 gists = _gists
109 gists = _gists
111
110
112 # default show ALL public gists
111 # default show ALL public gists
113 if not c.show_public and not c.show_private and not c.show_all:
112 if not c.show_public and not c.show_private and not c.show_all:
114 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
113 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
115 c.active = 'public'
114 c.active = 'public'
116
115
117 from rhodecode.lib.utils import PartialRenderer
116 from rhodecode.lib.utils import PartialRenderer
118 _render = PartialRenderer('data_table/_dt_elements.html')
117 _render = PartialRenderer('data_table/_dt_elements.html')
119
118
120 data = []
119 data = []
121
120
122 for gist in gists:
121 for gist in gists:
123 data.append({
122 data.append({
124 'created_on': _render('gist_created', gist.created_on),
123 'created_on': _render('gist_created', gist.created_on),
125 'created_on_raw': gist.created_on,
124 'created_on_raw': gist.created_on,
126 'type': _render('gist_type', gist.gist_type),
125 'type': _render('gist_type', gist.gist_type),
127 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
126 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
128 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
127 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
129 'author_raw': h.escape(gist.owner.full_contact),
128 'author_raw': h.escape(gist.owner.full_contact),
130 'expires': _render('gist_expires', gist.gist_expires),
129 'expires': _render('gist_expires', gist.gist_expires),
131 'description': _render('gist_description', gist.gist_description)
130 'description': _render('gist_description', gist.gist_description)
132 })
131 })
133 c.data = json.dumps(data)
132 c.data = json.dumps(data)
134 return render('admin/gists/index.html')
133 return render('admin/gists/index.html')
135
134
136 @LoginRequired()
135 @LoginRequired()
137 @NotAnonymous()
136 @NotAnonymous()
138 @auth.CSRFRequired()
137 @auth.CSRFRequired()
139 def create(self):
138 def create(self):
140 """POST /admin/gists: Create a new item"""
139 """POST /admin/gists: Create a new item"""
141 # url('gists')
140 # url('gists')
142 self.__load_defaults()
141 self.__load_defaults()
143
142
144 data = dict(request.POST)
143 data = dict(request.POST)
145 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
144 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
146 data['nodes'] = [{
145 data['nodes'] = [{
147 'filename': data['filename'],
146 'filename': data['filename'],
148 'content': data.get('content'),
147 'content': data.get('content'),
149 'mimetype': data.get('mimetype') # None is autodetect
148 'mimetype': data.get('mimetype') # None is autodetect
150 }]
149 }]
151
150
152 data['gist_type'] = (
151 data['gist_type'] = (
153 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
152 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
154 data['gist_acl_level'] = (
153 data['gist_acl_level'] = (
155 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
154 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
156
155
157 schema = gist_schema.GistSchema().bind(
156 schema = gist_schema.GistSchema().bind(
158 lifetime_options=[x[0] for x in c.lifetime_values])
157 lifetime_options=[x[0] for x in c.lifetime_values])
159
158
160 try:
159 try:
161
160
162 schema_data = schema.deserialize(data)
161 schema_data = schema.deserialize(data)
163 # convert to safer format with just KEYs so we sure no duplicates
162 # convert to safer format with just KEYs so we sure no duplicates
164 schema_data['nodes'] = gist_schema.sequence_to_nodes(
163 schema_data['nodes'] = gist_schema.sequence_to_nodes(
165 schema_data['nodes'])
164 schema_data['nodes'])
166
165
167 gist = GistModel().create(
166 gist = GistModel().create(
168 gist_id=schema_data['gistid'], # custom access id not real ID
167 gist_id=schema_data['gistid'], # custom access id not real ID
169 description=schema_data['description'],
168 description=schema_data['description'],
170 owner=c.rhodecode_user.user_id,
169 owner=c.rhodecode_user.user_id,
171 gist_mapping=schema_data['nodes'],
170 gist_mapping=schema_data['nodes'],
172 gist_type=schema_data['gist_type'],
171 gist_type=schema_data['gist_type'],
173 lifetime=schema_data['lifetime'],
172 lifetime=schema_data['lifetime'],
174 gist_acl_level=schema_data['gist_acl_level']
173 gist_acl_level=schema_data['gist_acl_level']
175 )
174 )
176 Session().commit()
175 Session().commit()
177 new_gist_id = gist.gist_access_id
176 new_gist_id = gist.gist_access_id
178 except validation_schema.Invalid as errors:
177 except validation_schema.Invalid as errors:
179 defaults = data
178 defaults = data
180 errors = errors.asdict()
179 errors = errors.asdict()
181
180
182 if 'nodes.0.content' in errors:
181 if 'nodes.0.content' in errors:
183 errors['content'] = errors['nodes.0.content']
182 errors['content'] = errors['nodes.0.content']
184 del errors['nodes.0.content']
183 del errors['nodes.0.content']
185 if 'nodes.0.filename' in errors:
184 if 'nodes.0.filename' in errors:
186 errors['filename'] = errors['nodes.0.filename']
185 errors['filename'] = errors['nodes.0.filename']
187 del errors['nodes.0.filename']
186 del errors['nodes.0.filename']
188
187
189 return formencode.htmlfill.render(
188 return formencode.htmlfill.render(
190 render('admin/gists/new.html'),
189 render('admin/gists/new.html'),
191 defaults=defaults,
190 defaults=defaults,
192 errors=errors,
191 errors=errors,
193 prefix_error=False,
192 prefix_error=False,
194 encoding="UTF-8",
193 encoding="UTF-8",
195 force_defaults=False
194 force_defaults=False
196 )
195 )
197
196
198 except Exception:
197 except Exception:
199 log.exception("Exception while trying to create a gist")
198 log.exception("Exception while trying to create a gist")
200 h.flash(_('Error occurred during gist creation'), category='error')
199 h.flash(_('Error occurred during gist creation'), category='error')
201 return redirect(url('new_gist'))
200 return redirect(url('new_gist'))
202 return redirect(url('gist', gist_id=new_gist_id))
201 return redirect(url('gist', gist_id=new_gist_id))
203
202
204 @LoginRequired()
203 @LoginRequired()
205 @NotAnonymous()
204 @NotAnonymous()
206 def new(self, format='html'):
205 def new(self, format='html'):
207 """GET /admin/gists/new: Form to create a new item"""
206 """GET /admin/gists/new: Form to create a new item"""
208 # url('new_gist')
207 # url('new_gist')
209 self.__load_defaults()
208 self.__load_defaults()
210 return render('admin/gists/new.html')
209 return render('admin/gists/new.html')
211
210
212 @LoginRequired()
211 @LoginRequired()
213 @NotAnonymous()
212 @NotAnonymous()
214 @auth.CSRFRequired()
213 @auth.CSRFRequired()
215 def delete(self, gist_id):
214 def delete(self, gist_id):
216 """DELETE /admin/gists/gist_id: Delete an existing item"""
215 """DELETE /admin/gists/gist_id: Delete an existing item"""
217 # Forms posted to this method should contain a hidden field:
216 # Forms posted to this method should contain a hidden field:
218 # <input type="hidden" name="_method" value="DELETE" />
217 # <input type="hidden" name="_method" value="DELETE" />
219 # Or using helpers:
218 # Or using helpers:
220 # h.form(url('gist', gist_id=ID),
219 # h.form(url('gist', gist_id=ID),
221 # method='delete')
220 # method='delete')
222 # url('gist', gist_id=ID)
221 # url('gist', gist_id=ID)
223 c.gist = Gist.get_or_404(gist_id)
222 c.gist = Gist.get_or_404(gist_id)
224
223
225 owner = c.gist.gist_owner == c.rhodecode_user.user_id
224 owner = c.gist.gist_owner == c.rhodecode_user.user_id
226 if not (h.HasPermissionAny('hg.admin')() or owner):
225 if not (h.HasPermissionAny('hg.admin')() or owner):
227 raise HTTPForbidden()
226 raise HTTPForbidden()
228
227
229 GistModel().delete(c.gist)
228 GistModel().delete(c.gist)
230 Session().commit()
229 Session().commit()
231 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
230 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
232
231
233 return redirect(url('gists'))
232 return redirect(url('gists'))
234
233
235 def _add_gist_to_context(self, gist_id):
234 def _add_gist_to_context(self, gist_id):
236 c.gist = Gist.get_or_404(gist_id)
235 c.gist = Gist.get_or_404(gist_id)
237
236
238 # Check if this gist is expired
237 # Check if this gist is expired
239 if c.gist.gist_expires != -1:
238 if c.gist.gist_expires != -1:
240 if time.time() > c.gist.gist_expires:
239 if time.time() > c.gist.gist_expires:
241 log.error(
240 log.error(
242 'Gist expired at %s', time_to_datetime(c.gist.gist_expires))
241 'Gist expired at %s', time_to_datetime(c.gist.gist_expires))
243 raise HTTPNotFound()
242 raise HTTPNotFound()
244
243
245 # check if this gist requires a login
244 # check if this gist requires a login
246 is_default_user = c.rhodecode_user.username == User.DEFAULT_USER
245 is_default_user = c.rhodecode_user.username == User.DEFAULT_USER
247 if c.gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
246 if c.gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
248 log.error("Anonymous user %s tried to access protected gist `%s`",
247 log.error("Anonymous user %s tried to access protected gist `%s`",
249 c.rhodecode_user, gist_id)
248 c.rhodecode_user, gist_id)
250 raise HTTPNotFound()
249 raise HTTPNotFound()
251
250
252 @LoginRequired()
251 @LoginRequired()
253 def show(self, gist_id, revision='tip', format='html', f_path=None):
252 def show(self, gist_id, revision='tip', format='html', f_path=None):
254 """GET /admin/gists/gist_id: Show a specific item"""
253 """GET /admin/gists/gist_id: Show a specific item"""
255 # url('gist', gist_id=ID)
254 # url('gist', gist_id=ID)
256 self._add_gist_to_context(gist_id)
255 self._add_gist_to_context(gist_id)
257 c.render = not request.GET.get('no-render', False)
256 c.render = not request.GET.get('no-render', False)
258
257
259 try:
258 try:
260 c.file_last_commit, c.files = GistModel().get_gist_files(
259 c.file_last_commit, c.files = GistModel().get_gist_files(
261 gist_id, revision=revision)
260 gist_id, revision=revision)
262 except VCSError:
261 except VCSError:
263 log.exception("Exception in gist show")
262 log.exception("Exception in gist show")
264 raise HTTPNotFound()
263 raise HTTPNotFound()
265 if format == 'raw':
264 if format == 'raw':
266 content = '\n\n'.join([f.content for f in c.files
265 content = '\n\n'.join([f.content for f in c.files
267 if (f_path is None or f.path == f_path)])
266 if (f_path is None or f.path == f_path)])
268 response.content_type = 'text/plain'
267 response.content_type = 'text/plain'
269 return content
268 return content
270 return render('admin/gists/show.html')
269 return render('admin/gists/show.html')
271
270
272 @LoginRequired()
271 @LoginRequired()
273 @NotAnonymous()
272 @NotAnonymous()
274 @auth.CSRFRequired()
273 @auth.CSRFRequired()
275 def edit(self, gist_id):
274 def edit(self, gist_id):
276 self.__load_defaults()
275 self.__load_defaults()
277 self._add_gist_to_context(gist_id)
276 self._add_gist_to_context(gist_id)
278
277
279 owner = c.gist.gist_owner == c.rhodecode_user.user_id
278 owner = c.gist.gist_owner == c.rhodecode_user.user_id
280 if not (h.HasPermissionAny('hg.admin')() or owner):
279 if not (h.HasPermissionAny('hg.admin')() or owner):
281 raise HTTPForbidden()
280 raise HTTPForbidden()
282
281
283 data = peppercorn.parse(request.POST.items())
282 data = peppercorn.parse(request.POST.items())
284
283
285 schema = gist_schema.GistSchema()
284 schema = gist_schema.GistSchema()
286 schema = schema.bind(
285 schema = schema.bind(
287 # '0' is special value to leave lifetime untouched
286 # '0' is special value to leave lifetime untouched
288 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
287 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
289 )
288 )
290
289
291 try:
290 try:
292 schema_data = schema.deserialize(data)
291 schema_data = schema.deserialize(data)
293 # convert to safer format with just KEYs so we sure no duplicates
292 # convert to safer format with just KEYs so we sure no duplicates
294 schema_data['nodes'] = gist_schema.sequence_to_nodes(
293 schema_data['nodes'] = gist_schema.sequence_to_nodes(
295 schema_data['nodes'])
294 schema_data['nodes'])
296
295
297 GistModel().update(
296 GistModel().update(
298 gist=c.gist,
297 gist=c.gist,
299 description=schema_data['description'],
298 description=schema_data['description'],
300 owner=c.gist.owner,
299 owner=c.gist.owner,
301 gist_mapping=schema_data['nodes'],
300 gist_mapping=schema_data['nodes'],
302 gist_type=schema_data['gist_type'],
301 gist_type=schema_data['gist_type'],
303 lifetime=schema_data['lifetime'],
302 lifetime=schema_data['lifetime'],
304 gist_acl_level=schema_data['gist_acl_level']
303 gist_acl_level=schema_data['gist_acl_level']
305 )
304 )
306
305
307 Session().commit()
306 Session().commit()
308 h.flash(_('Successfully updated gist content'), category='success')
307 h.flash(_('Successfully updated gist content'), category='success')
309 except NodeNotChangedError:
308 except NodeNotChangedError:
310 # raised if nothing was changed in repo itself. We anyway then
309 # raised if nothing was changed in repo itself. We anyway then
311 # store only DB stuff for gist
310 # store only DB stuff for gist
312 Session().commit()
311 Session().commit()
313 h.flash(_('Successfully updated gist data'), category='success')
312 h.flash(_('Successfully updated gist data'), category='success')
314 except validation_schema.Invalid as errors:
313 except validation_schema.Invalid as errors:
315 errors = errors.asdict()
314 errors = errors.asdict()
316 h.flash(_('Error occurred during update of gist {}: {}').format(
315 h.flash(_('Error occurred during update of gist {}: {}').format(
317 gist_id, errors), category='error')
316 gist_id, errors), category='error')
318 except Exception:
317 except Exception:
319 log.exception("Exception in gist edit")
318 log.exception("Exception in gist edit")
320 h.flash(_('Error occurred during update of gist %s') % gist_id,
319 h.flash(_('Error occurred during update of gist %s') % gist_id,
321 category='error')
320 category='error')
322
321
323 return redirect(url('gist', gist_id=gist_id))
322 return redirect(url('gist', gist_id=gist_id))
324
323
325 @LoginRequired()
324 @LoginRequired()
326 @NotAnonymous()
325 @NotAnonymous()
327 def edit_form(self, gist_id, format='html'):
326 def edit_form(self, gist_id, format='html'):
328 """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
327 """GET /admin/gists/gist_id/edit: Form to edit an existing item"""
329 # url('edit_gist', gist_id=ID)
328 # url('edit_gist', gist_id=ID)
330 self._add_gist_to_context(gist_id)
329 self._add_gist_to_context(gist_id)
331
330
332 owner = c.gist.gist_owner == c.rhodecode_user.user_id
331 owner = c.gist.gist_owner == c.rhodecode_user.user_id
333 if not (h.HasPermissionAny('hg.admin')() or owner):
332 if not (h.HasPermissionAny('hg.admin')() or owner):
334 raise HTTPForbidden()
333 raise HTTPForbidden()
335
334
336 try:
335 try:
337 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
336 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
338 except VCSError:
337 except VCSError:
339 log.exception("Exception in gist edit")
338 log.exception("Exception in gist edit")
340 raise HTTPNotFound()
339 raise HTTPNotFound()
341
340
342 if c.gist.gist_expires == -1:
341 if c.gist.gist_expires == -1:
343 expiry = _('never')
342 expiry = _('never')
344 else:
343 else:
345 # this cannot use timeago, since it's used in select2 as a value
344 # this cannot use timeago, since it's used in select2 as a value
346 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
345 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
347 self.__load_defaults(
346 self.__load_defaults(
348 extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry}))
347 extra_values=(0, _('%(expiry)s - current value') % {'expiry': expiry}))
349 return render('admin/gists/edit.html')
348 return render('admin/gists/edit.html')
350
349
351 @LoginRequired()
350 @LoginRequired()
352 @NotAnonymous()
351 @NotAnonymous()
353 @jsonify
352 @jsonify
354 def check_revision(self, gist_id):
353 def check_revision(self, gist_id):
355 c.gist = Gist.get_or_404(gist_id)
354 c.gist = Gist.get_or_404(gist_id)
356 last_rev = c.gist.scm_instance().get_commit()
355 last_rev = c.gist.scm_instance().get_commit()
357 success = True
356 success = True
358 revision = request.GET.get('revision')
357 revision = request.GET.get('revision')
359
358
360 ##TODO: maybe move this to model ?
359 ##TODO: maybe move this to model ?
361 if revision != last_rev.raw_id:
360 if revision != last_rev.raw_id:
362 log.error('Last revision %s is different then submitted %s'
361 log.error('Last revision %s is different then submitted %s'
363 % (revision, last_rev))
362 % (revision, last_rev))
364 # our gist has newer version than we
363 # our gist has newer version than we
365 success = False
364 success = False
366
365
367 return {'success': success}
366 return {'success': success}
@@ -1,185 +1,183 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2016 RhodeCode GmbH
3 # Copyright (C) 2016-2016 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 os
21 import os
22
22
23 import colander
23 import colander
24
24
25 from rhodecode.translation import _
25 from rhodecode.translation import _
26 from rhodecode.model.validation_schema import validators, preparers
26 from rhodecode.model.validation_schema import preparers
27
27
28
28
29 def nodes_to_sequence(nodes, colander_node=None):
29 def nodes_to_sequence(nodes, colander_node=None):
30 """
30 """
31 Converts old style dict nodes to new list of dicts
31 Converts old style dict nodes to new list of dicts
32
32
33 :param nodes: dict with key beeing name of the file
33 :param nodes: dict with key beeing name of the file
34
34
35 """
35 """
36 if not isinstance(nodes, dict):
36 if not isinstance(nodes, dict):
37 msg = 'Nodes needs to be a dict, got {}'.format(type(nodes))
37 msg = 'Nodes needs to be a dict, got {}'.format(type(nodes))
38 raise colander.Invalid(colander_node, msg)
38 raise colander.Invalid(colander_node, msg)
39 out = []
39 out = []
40
40
41 for key, val in nodes.items():
41 for key, val in nodes.items():
42 val = (isinstance(val, dict) and val) or {}
42 val = (isinstance(val, dict) and val) or {}
43 out.append(dict(
43 out.append(dict(
44 filename=key,
44 filename=key,
45 content=val.get('content'),
45 content=val.get('content'),
46 mimetype=val.get('mimetype')
46 mimetype=val.get('mimetype')
47 ))
47 ))
48
48
49 out = Nodes().deserialize(out)
49 out = Nodes().deserialize(out)
50 return out
50 return out
51
51
52
52
53 def sequence_to_nodes(nodes, colander_node=None):
53 def sequence_to_nodes(nodes, colander_node=None):
54 if not isinstance(nodes, list):
54 if not isinstance(nodes, list):
55 msg = 'Nodes needs to be a list, got {}'.format(type(nodes))
55 msg = 'Nodes needs to be a list, got {}'.format(type(nodes))
56 raise colander.Invalid(colander_node, msg)
56 raise colander.Invalid(colander_node, msg)
57 nodes = Nodes().deserialize(nodes)
57 nodes = Nodes().deserialize(nodes)
58
58
59 out = {}
59 out = {}
60 try:
60 try:
61 for file_data in nodes:
61 for file_data in nodes:
62 file_data_skip = file_data.copy()
62 file_data_skip = file_data.copy()
63 # if we got filename_org we use it as a key so we keep old
63 # if we got filename_org we use it as a key so we keep old
64 # name as input and rename is-reflected inside the values as
64 # name as input and rename is-reflected inside the values as
65 # filename and filename_org differences.
65 # filename and filename_org differences.
66 filename_org = file_data.get('filename_org')
66 filename_org = file_data.get('filename_org')
67 filename = filename_org or file_data['filename']
67 filename = filename_org or file_data['filename']
68 out[filename] = {}
68 out[filename] = {}
69 out[filename].update(file_data_skip)
69 out[filename].update(file_data_skip)
70
70
71 except Exception as e:
71 except Exception as e:
72 msg = 'Invalid data format org_exc:`{}`'.format(repr(e))
72 msg = 'Invalid data format org_exc:`{}`'.format(repr(e))
73 raise colander.Invalid(colander_node, msg)
73 raise colander.Invalid(colander_node, msg)
74 return out
74 return out
75
75
76
76
77 @colander.deferred
77 @colander.deferred
78 def deferred_lifetime_validator(node, kw):
78 def deferred_lifetime_validator(node, kw):
79 options = kw.get('lifetime_options', [])
79 options = kw.get('lifetime_options', [])
80 return colander.All(
80 return colander.All(
81 colander.Range(min=-1, max=60 * 24 * 30 * 12),
81 colander.Range(min=-1, max=60 * 24 * 30 * 12),
82 colander.OneOf([x for x in options]))
82 colander.OneOf([x for x in options]))
83
83
84
84
85 def unique_gist_validator(node, value):
85 def unique_gist_validator(node, value):
86 from rhodecode.model.db import Gist
86 from rhodecode.model.db import Gist
87 existing = Gist.get_by_access_id(value)
87 existing = Gist.get_by_access_id(value)
88 if existing:
88 if existing:
89 msg = _(u'Gist with name {} already exists').format(value)
89 msg = _(u'Gist with name {} already exists').format(value)
90 raise colander.Invalid(node, msg)
90 raise colander.Invalid(node, msg)
91
91
92
92
93 def filename_validator(node, value):
93 def filename_validator(node, value):
94 if value != os.path.basename(value):
94 if value != os.path.basename(value):
95 msg = _(u'Filename {} cannot be inside a directory').format(value)
95 msg = _(u'Filename {} cannot be inside a directory').format(value)
96 raise colander.Invalid(node, msg)
96 raise colander.Invalid(node, msg)
97
97
98
98
99 class NodeSchema(colander.MappingSchema):
99 class NodeSchema(colander.MappingSchema):
100 # if we perform rename this will be org filename
100 # if we perform rename this will be org filename
101 filename_org = colander.SchemaNode(
101 filename_org = colander.SchemaNode(
102 colander.String(),
102 colander.String(),
103 preparer=[preparers.strip_preparer,
103 preparer=[preparers.strip_preparer,
104 preparers.non_ascii_strip_preparer],
104 preparers.non_ascii_strip_preparer],
105 validator=filename_validator,
105 validator=filename_validator,
106 missing=None)
106 missing=None)
107
107
108 filename = colander.SchemaNode(
108 filename = colander.SchemaNode(
109 colander.String(),
109 colander.String(),
110 preparer=[preparers.strip_preparer,
110 preparer=[preparers.strip_preparer,
111 preparers.non_ascii_strip_preparer],
111 preparers.non_ascii_strip_preparer],
112 validator=filename_validator)
112 validator=filename_validator)
113
113
114 content = colander.SchemaNode(
114 content = colander.SchemaNode(
115 colander.String())
115 colander.String())
116 mimetype = colander.SchemaNode(
116 mimetype = colander.SchemaNode(
117 colander.String(),
117 colander.String(),
118 missing=None)
118 missing=None)
119
119
120
120
121 class Nodes(colander.SequenceSchema):
121 class Nodes(colander.SequenceSchema):
122 filenames = NodeSchema()
122 filenames = NodeSchema()
123
123
124 def validator(self, node, cstruct):
124 def validator(self, node, cstruct):
125 if not isinstance(cstruct, list):
125 if not isinstance(cstruct, list):
126 return
126 return
127
127
128 found_filenames = []
128 found_filenames = []
129 for data in cstruct:
129 for data in cstruct:
130 filename = data['filename']
130 filename = data['filename']
131 if filename in found_filenames:
131 if filename in found_filenames:
132 msg = _('Duplicated value for filename found: `{}`').format(
132 msg = _('Duplicated value for filename found: `{}`').format(
133 filename)
133 filename)
134 raise colander.Invalid(node, msg)
134 raise colander.Invalid(node, msg)
135 found_filenames.append(filename)
135 found_filenames.append(filename)
136
136
137
137
138 class GistSchema(colander.MappingSchema):
138 class GistSchema(colander.MappingSchema):
139 """
139 """
140 schema = GistSchema()
140 schema = GistSchema()
141 schema.bind(
141 schema.bind(
142 lifetime_options = [1,2,3]
142 lifetime_options = [1,2,3]
143 )
143 )
144 out = schema.deserialize(dict(
144 out = schema.deserialize(dict(
145 nodes=[
145 nodes=[
146 {'filename': 'x', 'content': 'xxx', },
146 {'filename': 'x', 'content': 'xxx', },
147 {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'},
147 {'filename': 'docs/Z', 'content': 'xxx', 'mimetype': 'x'},
148 ]
148 ]
149 ))
149 ))
150 """
150 """
151
151
152 from rhodecode.model.db import Gist
152 from rhodecode.model.db import Gist
153
153
154 gistid = colander.SchemaNode(
154 gistid = colander.SchemaNode(
155 colander.String(),
155 colander.String(),
156 missing=None,
156 missing=None,
157 preparer=[preparers.strip_preparer,
157 preparer=[preparers.strip_preparer,
158 preparers.non_ascii_strip_preparer,
158 preparers.non_ascii_strip_preparer,
159 preparers.slugify_preparer],
159 preparers.slugify_preparer],
160 validator=colander.All(
160 validator=colander.All(
161 colander.Length(min=3),
161 colander.Length(min=3),
162 unique_gist_validator
162 unique_gist_validator
163 ))
163 ))
164
164
165 description = colander.SchemaNode(
165 description = colander.SchemaNode(
166 colander.String(),
166 colander.String(),
167 missing=u'')
167 missing=u'')
168
168
169 lifetime = colander.SchemaNode(
169 lifetime = colander.SchemaNode(
170 colander.Integer(),
170 colander.Integer(),
171 validator=deferred_lifetime_validator)
171 validator=deferred_lifetime_validator)
172
172
173 gist_acl_level = colander.SchemaNode(
173 gist_acl_level = colander.SchemaNode(
174 colander.String(),
174 colander.String(),
175 validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC,
175 validator=colander.OneOf([Gist.ACL_LEVEL_PUBLIC,
176 Gist.ACL_LEVEL_PRIVATE]))
176 Gist.ACL_LEVEL_PRIVATE]))
177
177
178 gist_type = colander.SchemaNode(
178 gist_type = colander.SchemaNode(
179 colander.String(),
179 colander.String(),
180 missing=Gist.ACL_LEVEL_PUBLIC,
180 missing=Gist.ACL_LEVEL_PUBLIC,
181 validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC]))
181 validator=colander.OneOf([Gist.GIST_PRIVATE, Gist.GIST_PUBLIC]))
182
182
183 nodes = Nodes()
183 nodes = Nodes()
184
185
General Comments 0
You need to be logged in to leave comments. Login now