##// END OF EJS Templates
gists: Remove gist_type from update method in model. #4243...
Martin Bornhold -
r853:30d12223 stable
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
31 from formencode import htmlfill
32
32
33 from pylons import request, response, tmpl_context as c, url
33 from pylons import request, response, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36 from webob.exc import HTTPNotFound, HTTPForbidden
36 from webob.exc import HTTPNotFound, HTTPForbidden
37 from sqlalchemy.sql.expression import or_
37 from sqlalchemy.sql.expression import or_
38
38
39
39
40 from rhodecode.model.gist import GistModel
40 from rhodecode.model.gist import GistModel
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.model.db import Gist, User
42 from rhodecode.model.db import Gist, User
43 from rhodecode.lib import auth
43 from rhodecode.lib import auth
44 from rhodecode.lib import helpers as h
44 from rhodecode.lib import helpers as h
45 from rhodecode.lib.base import BaseController, render
45 from rhodecode.lib.base import BaseController, render
46 from rhodecode.lib.auth import LoginRequired, NotAnonymous
46 from rhodecode.lib.auth import LoginRequired, NotAnonymous
47 from rhodecode.lib.utils import jsonify
47 from rhodecode.lib.utils import jsonify
48 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
48 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
49 from rhodecode.lib.ext_json import json
49 from rhodecode.lib.ext_json import json
50 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
50 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
51 from rhodecode.model import validation_schema
51 from rhodecode.model import validation_schema
52 from rhodecode.model.validation_schema.schemas import gist_schema
52 from rhodecode.model.validation_schema.schemas import gist_schema
53
53
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class GistsController(BaseController):
58 class GistsController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60
60
61 def __load_defaults(self, extra_values=None):
61 def __load_defaults(self, extra_values=None):
62 c.lifetime_values = [
62 c.lifetime_values = [
63 (-1, _('forever')),
63 (-1, _('forever')),
64 (5, _('5 minutes')),
64 (5, _('5 minutes')),
65 (60, _('1 hour')),
65 (60, _('1 hour')),
66 (60 * 24, _('1 day')),
66 (60 * 24, _('1 day')),
67 (60 * 24 * 30, _('1 month')),
67 (60 * 24 * 30, _('1 month')),
68 ]
68 ]
69 if extra_values:
69 if extra_values:
70 c.lifetime_values.append(extra_values)
70 c.lifetime_values.append(extra_values)
71 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
71 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
72 c.acl_options = [
72 c.acl_options = [
73 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
73 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
74 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
74 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
75 ]
75 ]
76
76
77 @LoginRequired()
77 @LoginRequired()
78 def index(self):
78 def index(self):
79 """GET /admin/gists: All items in the collection"""
79 """GET /admin/gists: All items in the collection"""
80 # url('gists')
80 # url('gists')
81 not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
81 not_default_user = c.rhodecode_user.username != User.DEFAULT_USER
82 c.show_private = request.GET.get('private') and not_default_user
82 c.show_private = request.GET.get('private') and not_default_user
83 c.show_public = request.GET.get('public') and not_default_user
83 c.show_public = request.GET.get('public') and not_default_user
84 c.show_all = request.GET.get('all') and c.rhodecode_user.admin
84 c.show_all = request.GET.get('all') and c.rhodecode_user.admin
85
85
86 gists = _gists = Gist().query()\
86 gists = _gists = Gist().query()\
87 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
87 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
88 .order_by(Gist.created_on.desc())
88 .order_by(Gist.created_on.desc())
89
89
90 c.active = 'public'
90 c.active = 'public'
91 # MY private
91 # MY private
92 if c.show_private and not c.show_public:
92 if c.show_private and not c.show_public:
93 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
93 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
94 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
94 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
95 c.active = 'my_private'
95 c.active = 'my_private'
96 # MY public
96 # MY public
97 elif c.show_public and not c.show_private:
97 elif c.show_public and not c.show_private:
98 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
98 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
99 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
99 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
100 c.active = 'my_public'
100 c.active = 'my_public'
101 # MY public+private
101 # MY public+private
102 elif c.show_private and c.show_public:
102 elif c.show_private and c.show_public:
103 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
103 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
104 Gist.gist_type == Gist.GIST_PRIVATE))\
104 Gist.gist_type == Gist.GIST_PRIVATE))\
105 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
105 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
106 c.active = 'my_all'
106 c.active = 'my_all'
107 # Show all by super-admin
107 # Show all by super-admin
108 elif c.show_all:
108 elif c.show_all:
109 c.active = 'all'
109 c.active = 'all'
110 gists = _gists
110 gists = _gists
111
111
112 # default show ALL public gists
112 # default show ALL public gists
113 if not c.show_public and not c.show_private and not c.show_all:
113 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)
114 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
115 c.active = 'public'
115 c.active = 'public'
116
116
117 from rhodecode.lib.utils import PartialRenderer
117 from rhodecode.lib.utils import PartialRenderer
118 _render = PartialRenderer('data_table/_dt_elements.html')
118 _render = PartialRenderer('data_table/_dt_elements.html')
119
119
120 data = []
120 data = []
121
121
122 for gist in gists:
122 for gist in gists:
123 data.append({
123 data.append({
124 'created_on': _render('gist_created', gist.created_on),
124 'created_on': _render('gist_created', gist.created_on),
125 'created_on_raw': gist.created_on,
125 'created_on_raw': gist.created_on,
126 'type': _render('gist_type', gist.gist_type),
126 'type': _render('gist_type', gist.gist_type),
127 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
127 '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),
128 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
129 'author_raw': h.escape(gist.owner.full_contact),
129 'author_raw': h.escape(gist.owner.full_contact),
130 'expires': _render('gist_expires', gist.gist_expires),
130 'expires': _render('gist_expires', gist.gist_expires),
131 'description': _render('gist_description', gist.gist_description)
131 'description': _render('gist_description', gist.gist_description)
132 })
132 })
133 c.data = json.dumps(data)
133 c.data = json.dumps(data)
134 return render('admin/gists/index.html')
134 return render('admin/gists/index.html')
135
135
136 @LoginRequired()
136 @LoginRequired()
137 @NotAnonymous()
137 @NotAnonymous()
138 @auth.CSRFRequired()
138 @auth.CSRFRequired()
139 def create(self):
139 def create(self):
140 """POST /admin/gists: Create a new item"""
140 """POST /admin/gists: Create a new item"""
141 # url('gists')
141 # url('gists')
142 self.__load_defaults()
142 self.__load_defaults()
143
143
144 data = dict(request.POST)
144 data = dict(request.POST)
145 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
145 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
146 data['nodes'] = [{
146 data['nodes'] = [{
147 'filename': data['filename'],
147 'filename': data['filename'],
148 'content': data.get('content'),
148 'content': data.get('content'),
149 'mimetype': data.get('mimetype') # None is autodetect
149 'mimetype': data.get('mimetype') # None is autodetect
150 }]
150 }]
151
151
152 data['gist_type'] = (
152 data['gist_type'] = (
153 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
153 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
154 data['gist_acl_level'] = (
154 data['gist_acl_level'] = (
155 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
155 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
156
156
157 schema = gist_schema.GistSchema().bind(
157 schema = gist_schema.GistSchema().bind(
158 lifetime_options=[x[0] for x in c.lifetime_values])
158 lifetime_options=[x[0] for x in c.lifetime_values])
159
159
160 try:
160 try:
161
161
162 schema_data = schema.deserialize(data)
162 schema_data = schema.deserialize(data)
163 # convert to safer format with just KEYs so we sure no duplicates
163 # convert to safer format with just KEYs so we sure no duplicates
164 schema_data['nodes'] = gist_schema.sequence_to_nodes(
164 schema_data['nodes'] = gist_schema.sequence_to_nodes(
165 schema_data['nodes'])
165 schema_data['nodes'])
166
166
167 gist = GistModel().create(
167 gist = GistModel().create(
168 gist_id=schema_data['gistid'], # custom access id not real ID
168 gist_id=schema_data['gistid'], # custom access id not real ID
169 description=schema_data['description'],
169 description=schema_data['description'],
170 owner=c.rhodecode_user.user_id,
170 owner=c.rhodecode_user.user_id,
171 gist_mapping=schema_data['nodes'],
171 gist_mapping=schema_data['nodes'],
172 gist_type=schema_data['gist_type'],
172 gist_type=schema_data['gist_type'],
173 lifetime=schema_data['lifetime'],
173 lifetime=schema_data['lifetime'],
174 gist_acl_level=schema_data['gist_acl_level']
174 gist_acl_level=schema_data['gist_acl_level']
175 )
175 )
176 Session().commit()
176 Session().commit()
177 new_gist_id = gist.gist_access_id
177 new_gist_id = gist.gist_access_id
178 except validation_schema.Invalid as errors:
178 except validation_schema.Invalid as errors:
179 defaults = data
179 defaults = data
180 errors = errors.asdict()
180 errors = errors.asdict()
181
181
182 if 'nodes.0.content' in errors:
182 if 'nodes.0.content' in errors:
183 errors['content'] = errors['nodes.0.content']
183 errors['content'] = errors['nodes.0.content']
184 del errors['nodes.0.content']
184 del errors['nodes.0.content']
185 if 'nodes.0.filename' in errors:
185 if 'nodes.0.filename' in errors:
186 errors['filename'] = errors['nodes.0.filename']
186 errors['filename'] = errors['nodes.0.filename']
187 del errors['nodes.0.filename']
187 del errors['nodes.0.filename']
188
188
189 return formencode.htmlfill.render(
189 return formencode.htmlfill.render(
190 render('admin/gists/new.html'),
190 render('admin/gists/new.html'),
191 defaults=defaults,
191 defaults=defaults,
192 errors=errors,
192 errors=errors,
193 prefix_error=False,
193 prefix_error=False,
194 encoding="UTF-8",
194 encoding="UTF-8",
195 force_defaults=False
195 force_defaults=False
196 )
196 )
197
197
198 except Exception:
198 except Exception:
199 log.exception("Exception while trying to create a gist")
199 log.exception("Exception while trying to create a gist")
200 h.flash(_('Error occurred during gist creation'), category='error')
200 h.flash(_('Error occurred during gist creation'), category='error')
201 return redirect(url('new_gist'))
201 return redirect(url('new_gist'))
202 return redirect(url('gist', gist_id=new_gist_id))
202 return redirect(url('gist', gist_id=new_gist_id))
203
203
204 @LoginRequired()
204 @LoginRequired()
205 @NotAnonymous()
205 @NotAnonymous()
206 def new(self, format='html'):
206 def new(self, format='html'):
207 """GET /admin/gists/new: Form to create a new item"""
207 """GET /admin/gists/new: Form to create a new item"""
208 # url('new_gist')
208 # url('new_gist')
209 self.__load_defaults()
209 self.__load_defaults()
210 return render('admin/gists/new.html')
210 return render('admin/gists/new.html')
211
211
212 @LoginRequired()
212 @LoginRequired()
213 @NotAnonymous()
213 @NotAnonymous()
214 @auth.CSRFRequired()
214 @auth.CSRFRequired()
215 def delete(self, gist_id):
215 def delete(self, gist_id):
216 """DELETE /admin/gists/gist_id: Delete an existing item"""
216 """DELETE /admin/gists/gist_id: Delete an existing item"""
217 # Forms posted to this method should contain a hidden field:
217 # Forms posted to this method should contain a hidden field:
218 # <input type="hidden" name="_method" value="DELETE" />
218 # <input type="hidden" name="_method" value="DELETE" />
219 # Or using helpers:
219 # Or using helpers:
220 # h.form(url('gist', gist_id=ID),
220 # h.form(url('gist', gist_id=ID),
221 # method='delete')
221 # method='delete')
222 # url('gist', gist_id=ID)
222 # url('gist', gist_id=ID)
223 c.gist = Gist.get_or_404(gist_id)
223 c.gist = Gist.get_or_404(gist_id)
224
224
225 owner = c.gist.gist_owner == c.rhodecode_user.user_id
225 owner = c.gist.gist_owner == c.rhodecode_user.user_id
226 if not (h.HasPermissionAny('hg.admin')() or owner):
226 if not (h.HasPermissionAny('hg.admin')() or owner):
227 raise HTTPForbidden()
227 raise HTTPForbidden()
228
228
229 GistModel().delete(c.gist)
229 GistModel().delete(c.gist)
230 Session().commit()
230 Session().commit()
231 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
231 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
232
232
233 return redirect(url('gists'))
233 return redirect(url('gists'))
234
234
235 def _add_gist_to_context(self, gist_id):
235 def _add_gist_to_context(self, gist_id):
236 c.gist = Gist.get_or_404(gist_id)
236 c.gist = Gist.get_or_404(gist_id)
237
237
238 # Check if this gist is expired
238 # Check if this gist is expired
239 if c.gist.gist_expires != -1:
239 if c.gist.gist_expires != -1:
240 if time.time() > c.gist.gist_expires:
240 if time.time() > c.gist.gist_expires:
241 log.error(
241 log.error(
242 'Gist expired at %s', time_to_datetime(c.gist.gist_expires))
242 'Gist expired at %s', time_to_datetime(c.gist.gist_expires))
243 raise HTTPNotFound()
243 raise HTTPNotFound()
244
244
245 # check if this gist requires a login
245 # check if this gist requires a login
246 is_default_user = c.rhodecode_user.username == User.DEFAULT_USER
246 is_default_user = c.rhodecode_user.username == User.DEFAULT_USER
247 if c.gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
247 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`",
248 log.error("Anonymous user %s tried to access protected gist `%s`",
249 c.rhodecode_user, gist_id)
249 c.rhodecode_user, gist_id)
250 raise HTTPNotFound()
250 raise HTTPNotFound()
251
251
252 @LoginRequired()
252 @LoginRequired()
253 def show(self, gist_id, revision='tip', format='html', f_path=None):
253 def show(self, gist_id, revision='tip', format='html', f_path=None):
254 """GET /admin/gists/gist_id: Show a specific item"""
254 """GET /admin/gists/gist_id: Show a specific item"""
255 # url('gist', gist_id=ID)
255 # url('gist', gist_id=ID)
256 self._add_gist_to_context(gist_id)
256 self._add_gist_to_context(gist_id)
257 c.render = not request.GET.get('no-render', False)
257 c.render = not request.GET.get('no-render', False)
258
258
259 try:
259 try:
260 c.file_last_commit, c.files = GistModel().get_gist_files(
260 c.file_last_commit, c.files = GistModel().get_gist_files(
261 gist_id, revision=revision)
261 gist_id, revision=revision)
262 except VCSError:
262 except VCSError:
263 log.exception("Exception in gist show")
263 log.exception("Exception in gist show")
264 raise HTTPNotFound()
264 raise HTTPNotFound()
265 if format == 'raw':
265 if format == 'raw':
266 content = '\n\n'.join([f.content for f in c.files
266 content = '\n\n'.join([f.content for f in c.files
267 if (f_path is None or f.path == f_path)])
267 if (f_path is None or f.path == f_path)])
268 response.content_type = 'text/plain'
268 response.content_type = 'text/plain'
269 return content
269 return content
270 return render('admin/gists/show.html')
270 return render('admin/gists/show.html')
271
271
272 @LoginRequired()
272 @LoginRequired()
273 @NotAnonymous()
273 @NotAnonymous()
274 @auth.CSRFRequired()
274 @auth.CSRFRequired()
275 def edit(self, gist_id):
275 def edit(self, gist_id):
276 self.__load_defaults()
276 self.__load_defaults()
277 self._add_gist_to_context(gist_id)
277 self._add_gist_to_context(gist_id)
278
278
279 owner = c.gist.gist_owner == c.rhodecode_user.user_id
279 owner = c.gist.gist_owner == c.rhodecode_user.user_id
280 if not (h.HasPermissionAny('hg.admin')() or owner):
280 if not (h.HasPermissionAny('hg.admin')() or owner):
281 raise HTTPForbidden()
281 raise HTTPForbidden()
282
282
283 data = peppercorn.parse(request.POST.items())
283 data = peppercorn.parse(request.POST.items())
284
284
285 schema = gist_schema.GistSchema()
285 schema = gist_schema.GistSchema()
286 schema = schema.bind(
286 schema = schema.bind(
287 # '0' is special value to leave lifetime untouched
287 # '0' is special value to leave lifetime untouched
288 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
288 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
289 )
289 )
290
290
291 try:
291 try:
292 schema_data = schema.deserialize(data)
292 schema_data = schema.deserialize(data)
293 # convert to safer format with just KEYs so we sure no duplicates
293 # convert to safer format with just KEYs so we sure no duplicates
294 schema_data['nodes'] = gist_schema.sequence_to_nodes(
294 schema_data['nodes'] = gist_schema.sequence_to_nodes(
295 schema_data['nodes'])
295 schema_data['nodes'])
296
296
297 GistModel().update(
297 GistModel().update(
298 gist=c.gist,
298 gist=c.gist,
299 description=schema_data['description'],
299 description=schema_data['description'],
300 owner=c.gist.owner,
300 owner=c.gist.owner,
301 gist_mapping=schema_data['nodes'],
301 gist_mapping=schema_data['nodes'],
302 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,236 +1,235 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 gist model for RhodeCode
22 gist model for RhodeCode
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from rhodecode.lib.utils2 import (
31 from rhodecode.lib.utils2 import (
32 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
32 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
33 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.ext_json import json
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import Gist
35 from rhodecode.model.db import Gist
36 from rhodecode.model.repo import RepoModel
36 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41 GIST_STORE_LOC = '.rc_gist_store'
41 GIST_STORE_LOC = '.rc_gist_store'
42 GIST_METADATA_FILE = '.rc_gist_metadata'
42 GIST_METADATA_FILE = '.rc_gist_metadata'
43
43
44
44
45 class GistModel(BaseModel):
45 class GistModel(BaseModel):
46 cls = Gist
46 cls = Gist
47
47
48 def _get_gist(self, gist):
48 def _get_gist(self, gist):
49 """
49 """
50 Helper method to get gist by ID, or gist_access_id as a fallback
50 Helper method to get gist by ID, or gist_access_id as a fallback
51
51
52 :param gist: GistID, gist_access_id, or Gist instance
52 :param gist: GistID, gist_access_id, or Gist instance
53 """
53 """
54 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
54 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
55
55
56 def __delete_gist(self, gist):
56 def __delete_gist(self, gist):
57 """
57 """
58 removes gist from filesystem
58 removes gist from filesystem
59
59
60 :param gist: gist object
60 :param gist: gist object
61 """
61 """
62 root_path = RepoModel().repos_path
62 root_path = RepoModel().repos_path
63 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
63 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
64 log.info("Removing %s", rm_path)
64 log.info("Removing %s", rm_path)
65 shutil.rmtree(rm_path)
65 shutil.rmtree(rm_path)
66
66
67 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
67 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
68 gist_type, gist_expires, gist_acl_level):
68 gist_type, gist_expires, gist_acl_level):
69 """
69 """
70 store metadata inside the gist repo, this can be later used for imports
70 store metadata inside the gist repo, this can be later used for imports
71 or gist identification. Currently we use this inside RhodeCode tools
71 or gist identification. Currently we use this inside RhodeCode tools
72 to do cleanup of gists that are in storage but not in database.
72 to do cleanup of gists that are in storage but not in database.
73 """
73 """
74 metadata = {
74 metadata = {
75 'metadata_version': '2',
75 'metadata_version': '2',
76 'gist_db_id': gist_id,
76 'gist_db_id': gist_id,
77 'gist_access_id': gist_access_id,
77 'gist_access_id': gist_access_id,
78 'gist_owner_id': user_id,
78 'gist_owner_id': user_id,
79 'gist_owner_username': username,
79 'gist_owner_username': username,
80 'gist_type': gist_type,
80 'gist_type': gist_type,
81 'gist_expires': gist_expires,
81 'gist_expires': gist_expires,
82 'gist_updated': time.time(),
82 'gist_updated': time.time(),
83 'gist_acl_level': gist_acl_level,
83 'gist_acl_level': gist_acl_level,
84 }
84 }
85 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
85 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
86 with open(metadata_file, 'wb') as f:
86 with open(metadata_file, 'wb') as f:
87 f.write(json.dumps(metadata))
87 f.write(json.dumps(metadata))
88
88
89 def get_gist(self, gist):
89 def get_gist(self, gist):
90 return self._get_gist(gist)
90 return self._get_gist(gist)
91
91
92 def get_gist_files(self, gist_access_id, revision=None):
92 def get_gist_files(self, gist_access_id, revision=None):
93 """
93 """
94 Get files for given gist
94 Get files for given gist
95
95
96 :param gist_access_id:
96 :param gist_access_id:
97 """
97 """
98 repo = Gist.get_by_access_id(gist_access_id)
98 repo = Gist.get_by_access_id(gist_access_id)
99 commit = repo.scm_instance().get_commit(commit_id=revision)
99 commit = repo.scm_instance().get_commit(commit_id=revision)
100 return commit, [n for n in commit.get_node('/')]
100 return commit, [n for n in commit.get_node('/')]
101
101
102 def create(self, description, owner, gist_mapping,
102 def create(self, description, owner, gist_mapping,
103 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
103 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
104 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
104 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
105 """
105 """
106 Create a gist
106 Create a gist
107
107
108 :param description: description of the gist
108 :param description: description of the gist
109 :param owner: user who created this gist
109 :param owner: user who created this gist
110 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
110 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
111 :param gist_type: type of gist private/public
111 :param gist_type: type of gist private/public
112 :param lifetime: in minutes, -1 == forever
112 :param lifetime: in minutes, -1 == forever
113 :param gist_acl_level: acl level for this gist
113 :param gist_acl_level: acl level for this gist
114 """
114 """
115 owner = self._get_user(owner)
115 owner = self._get_user(owner)
116 gist_id = safe_unicode(gist_id or unique_id(20))
116 gist_id = safe_unicode(gist_id or unique_id(20))
117 lifetime = safe_int(lifetime, -1)
117 lifetime = safe_int(lifetime, -1)
118 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
118 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
119 expiration = (time_to_datetime(gist_expires)
119 expiration = (time_to_datetime(gist_expires)
120 if gist_expires != -1 else 'forever')
120 if gist_expires != -1 else 'forever')
121 log.debug('set GIST expiration date to: %s', expiration)
121 log.debug('set GIST expiration date to: %s', expiration)
122 # create the Database version
122 # create the Database version
123 gist = Gist()
123 gist = Gist()
124 gist.gist_description = description
124 gist.gist_description = description
125 gist.gist_access_id = gist_id
125 gist.gist_access_id = gist_id
126 gist.gist_owner = owner.user_id
126 gist.gist_owner = owner.user_id
127 gist.gist_expires = gist_expires
127 gist.gist_expires = gist_expires
128 gist.gist_type = safe_unicode(gist_type)
128 gist.gist_type = safe_unicode(gist_type)
129 gist.acl_level = gist_acl_level
129 gist.acl_level = gist_acl_level
130 self.sa.add(gist)
130 self.sa.add(gist)
131 self.sa.flush()
131 self.sa.flush()
132 if gist_type == Gist.GIST_PUBLIC:
132 if gist_type == Gist.GIST_PUBLIC:
133 # use DB ID for easy to use GIST ID
133 # use DB ID for easy to use GIST ID
134 gist_id = safe_unicode(gist.gist_id)
134 gist_id = safe_unicode(gist.gist_id)
135 gist.gist_access_id = gist_id
135 gist.gist_access_id = gist_id
136 self.sa.add(gist)
136 self.sa.add(gist)
137
137
138 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
138 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
139 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
139 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
140 repo = RepoModel()._create_filesystem_repo(
140 repo = RepoModel()._create_filesystem_repo(
141 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
141 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
142 use_global_config=True)
142 use_global_config=True)
143
143
144 # now create single multifile commit
144 # now create single multifile commit
145 message = 'added file'
145 message = 'added file'
146 message += 's: ' if len(gist_mapping) > 1 else ': '
146 message += 's: ' if len(gist_mapping) > 1 else ': '
147 message += ', '.join([x for x in gist_mapping])
147 message += ', '.join([x for x in gist_mapping])
148
148
149 # fake RhodeCode Repository object
149 # fake RhodeCode Repository object
150 fake_repo = AttributeDict({
150 fake_repo = AttributeDict({
151 'repo_name': gist_repo_path,
151 'repo_name': gist_repo_path,
152 'scm_instance': lambda *args, **kwargs: repo,
152 'scm_instance': lambda *args, **kwargs: repo,
153 })
153 })
154
154
155 ScmModel().create_nodes(
155 ScmModel().create_nodes(
156 user=owner.user_id, repo=fake_repo,
156 user=owner.user_id, repo=fake_repo,
157 message=message,
157 message=message,
158 nodes=gist_mapping,
158 nodes=gist_mapping,
159 trigger_push_hook=False
159 trigger_push_hook=False
160 )
160 )
161
161
162 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
162 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
163 owner.user_id, owner.username, gist.gist_type,
163 owner.user_id, owner.username, gist.gist_type,
164 gist.gist_expires, gist_acl_level)
164 gist.gist_expires, gist_acl_level)
165 return gist
165 return gist
166
166
167 def delete(self, gist, fs_remove=True):
167 def delete(self, gist, fs_remove=True):
168 gist = self._get_gist(gist)
168 gist = self._get_gist(gist)
169 try:
169 try:
170 self.sa.delete(gist)
170 self.sa.delete(gist)
171 if fs_remove:
171 if fs_remove:
172 self.__delete_gist(gist)
172 self.__delete_gist(gist)
173 else:
173 else:
174 log.debug('skipping removal from filesystem')
174 log.debug('skipping removal from filesystem')
175 except Exception:
175 except Exception:
176 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
177 raise
177 raise
178
178
179 def update(self, gist, description, owner, gist_mapping, gist_type,
179 def update(self, gist, description, owner, gist_mapping, lifetime,
180 lifetime, gist_acl_level):
180 gist_acl_level):
181 gist = self._get_gist(gist)
181 gist = self._get_gist(gist)
182 gist_repo = gist.scm_instance()
182 gist_repo = gist.scm_instance()
183
183
184 if lifetime == 0: # preserve old value
184 if lifetime == 0: # preserve old value
185 gist_expires = gist.gist_expires
185 gist_expires = gist.gist_expires
186 else:
186 else:
187 gist_expires = (
187 gist_expires = (
188 time.time() + (lifetime * 60) if lifetime != -1 else -1)
188 time.time() + (lifetime * 60) if lifetime != -1 else -1)
189
189
190 # calculate operation type based on given data
190 # calculate operation type based on given data
191 gist_mapping_op = {}
191 gist_mapping_op = {}
192 for k, v in gist_mapping.items():
192 for k, v in gist_mapping.items():
193 # add, mod, del
193 # add, mod, del
194 if not v['filename_org'] and v['filename']:
194 if not v['filename_org'] and v['filename']:
195 op = 'add'
195 op = 'add'
196 elif v['filename_org'] and not v['filename']:
196 elif v['filename_org'] and not v['filename']:
197 op = 'del'
197 op = 'del'
198 else:
198 else:
199 op = 'mod'
199 op = 'mod'
200
200
201 v['op'] = op
201 v['op'] = op
202 gist_mapping_op[k] = v
202 gist_mapping_op[k] = v
203
203
204 gist.gist_description = description
204 gist.gist_description = description
205 gist.gist_expires = gist_expires
205 gist.gist_expires = gist_expires
206 gist.owner = owner
206 gist.owner = owner
207 gist.gist_type = gist_type
208 gist.acl_level = gist_acl_level
207 gist.acl_level = gist_acl_level
209 self.sa.add(gist)
208 self.sa.add(gist)
210 self.sa.flush()
209 self.sa.flush()
211
210
212 message = 'updated file'
211 message = 'updated file'
213 message += 's: ' if len(gist_mapping) > 1 else ': '
212 message += 's: ' if len(gist_mapping) > 1 else ': '
214 message += ', '.join([x for x in gist_mapping])
213 message += ', '.join([x for x in gist_mapping])
215
214
216 # fake RhodeCode Repository object
215 # fake RhodeCode Repository object
217 fake_repo = AttributeDict({
216 fake_repo = AttributeDict({
218 'repo_name': gist_repo.path,
217 'repo_name': gist_repo.path,
219 'scm_instance': lambda *args, **kwargs: gist_repo,
218 'scm_instance': lambda *args, **kwargs: gist_repo,
220 })
219 })
221
220
222 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
221 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
223 owner.user_id, owner.username, gist.gist_type,
222 owner.user_id, owner.username, gist.gist_type,
224 gist.gist_expires, gist_acl_level)
223 gist.gist_expires, gist_acl_level)
225
224
226 # this can throw NodeNotChangedError, if changes we're trying to commit
225 # this can throw NodeNotChangedError, if changes we're trying to commit
227 # are not actually changes...
226 # are not actually changes...
228 ScmModel().update_nodes(
227 ScmModel().update_nodes(
229 user=owner.user_id,
228 user=owner.user_id,
230 repo=fake_repo,
229 repo=fake_repo,
231 message=message,
230 message=message,
232 nodes=gist_mapping_op,
231 nodes=gist_mapping_op,
233 trigger_push_hook=False
232 trigger_push_hook=False
234 )
233 )
235
234
236 return gist
235 return gist
General Comments 0
You need to be logged in to leave comments. Login now