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