1 | # -*- coding: utf-8 -*- |
---|
2 | |
---|
3 | EXPERIMENTAL_STUFF = True |
---|
4 | MAXNFILES = 1000 |
---|
5 | |
---|
6 | if EXPERIMENTAL_STUFF: |
---|
7 | if is_mobile: |
---|
8 | response.view = response.view.replace('default/', 'default.mobile/') |
---|
9 | response.menu = [] |
---|
10 | |
---|
11 | import re |
---|
12 | from gluon.admin import * |
---|
13 | from gluon.fileutils import abspath, read_file, write_file |
---|
14 | from gluon.utils import web2py_uuid |
---|
15 | from gluon.tools import Config |
---|
16 | from gluon.compileapp import find_exposed_functions |
---|
17 | from glob import glob |
---|
18 | from gluon._compat import iteritems, PY2, pickle, xrange, urlopen, to_bytes, StringIO, to_native |
---|
19 | import gluon.rewrite |
---|
20 | import shutil |
---|
21 | import platform |
---|
22 | |
---|
23 | try: |
---|
24 | import git |
---|
25 | if git.__version__ < '0.3.1': |
---|
26 | raise ImportError("Your version of git is %s. Upgrade to 0.3.1 or better." % git.__version__) |
---|
27 | have_git = True |
---|
28 | except ImportError as e: |
---|
29 | have_git = False |
---|
30 | GIT_MISSING = 'Requires gitpython module, but not installed or incompatible version: %s' % e |
---|
31 | |
---|
32 | from gluon.languages import (read_possible_languages, read_dict, write_dict, |
---|
33 | read_plural_dict, write_plural_dict) |
---|
34 | |
---|
35 | |
---|
36 | if DEMO_MODE and request.function in ['change_password', 'pack', |
---|
37 | 'pack_custom', 'pack_plugin', 'upgrade_web2py', 'uninstall', |
---|
38 | 'cleanup', 'compile_app', 'remove_compiled_app', 'delete', |
---|
39 | 'delete_plugin', 'create_file', 'upload_file', 'update_languages', |
---|
40 | 'reload_routes', 'git_push', 'git_pull', 'install_plugin']: |
---|
41 | session.flash = T('disabled in demo mode') |
---|
42 | redirect(URL('site')) |
---|
43 | |
---|
44 | if is_gae and request.function in ('edit', 'edit_language', |
---|
45 | 'edit_plurals', 'update_languages', 'create_file', 'install_plugin'): |
---|
46 | session.flash = T('disabled in GAE mode') |
---|
47 | redirect(URL('site')) |
---|
48 | |
---|
49 | if not is_manager() and request.function in ['change_password', 'upgrade_web2py']: |
---|
50 | session.flash = T('disabled in multi user mode') |
---|
51 | redirect(URL('site')) |
---|
52 | |
---|
53 | if FILTER_APPS and request.args(0) and not request.args(0) in FILTER_APPS: |
---|
54 | session.flash = T('disabled in demo mode') |
---|
55 | redirect(URL('site')) |
---|
56 | |
---|
57 | |
---|
58 | if not session.token: |
---|
59 | session.token = web2py_uuid() |
---|
60 | |
---|
61 | |
---|
62 | def count_lines(data): |
---|
63 | return len([line for line in data.split('\n') if line.strip() and not line.startswith('#')]) |
---|
64 | |
---|
65 | |
---|
66 | def log_progress(app, mode='EDIT', filename=None, progress=0): |
---|
67 | progress_file = os.path.join(apath(app, r=request), 'progress.log') |
---|
68 | now = str(request.now)[:19] |
---|
69 | if not os.path.exists(progress_file): |
---|
70 | safe_open(progress_file, 'w').write('[%s] START\n' % now) |
---|
71 | if filename: |
---|
72 | safe_open(progress_file, 'a').write( |
---|
73 | '[%s] %s %s: %s\n' % (now, mode, filename, progress)) |
---|
74 | |
---|
75 | |
---|
76 | def safe_open(a, b): |
---|
77 | if (DEMO_MODE or is_gae) and ('w' in b or 'a' in b): |
---|
78 | class tmp: |
---|
79 | |
---|
80 | def write(self, data): |
---|
81 | pass |
---|
82 | |
---|
83 | def close(self): |
---|
84 | pass |
---|
85 | return tmp() |
---|
86 | if PY2 or 'b' in b: |
---|
87 | return open(a, b) |
---|
88 | else: |
---|
89 | return open(a, b, encoding="utf8") |
---|
90 | |
---|
91 | |
---|
92 | def safe_read(a, b='r'): |
---|
93 | safe_file = safe_open(a, b) |
---|
94 | try: |
---|
95 | return safe_file.read() |
---|
96 | finally: |
---|
97 | safe_file.close() |
---|
98 | |
---|
99 | |
---|
100 | def safe_write(a, value, b='w'): |
---|
101 | safe_file = safe_open(a, b) |
---|
102 | try: |
---|
103 | safe_file.write(value) |
---|
104 | finally: |
---|
105 | safe_file.close() |
---|
106 | |
---|
107 | |
---|
108 | def get_app(name=None): |
---|
109 | app = name or request.args(0) |
---|
110 | if (app and os.path.exists(apath(app, r=request)) and |
---|
111 | (not MULTI_USER_MODE or is_manager() or |
---|
112 | db(db.app.name == app)(db.app.owner == auth.user.id).count())): |
---|
113 | return app |
---|
114 | session.flash = T('App does not exist or you are not authorized') |
---|
115 | redirect(URL('site')) |
---|
116 | |
---|
117 | |
---|
118 | def index(): |
---|
119 | """ Index handler """ |
---|
120 | |
---|
121 | send = request.vars.send |
---|
122 | if DEMO_MODE: |
---|
123 | session.authorized = True |
---|
124 | session.last_time = t0 |
---|
125 | if not send: |
---|
126 | send = URL('site') |
---|
127 | if session.authorized: |
---|
128 | redirect(send) |
---|
129 | elif failed_login_count() >= allowed_number_of_attempts: |
---|
130 | time.sleep(2 ** allowed_number_of_attempts) |
---|
131 | raise HTTP(403) |
---|
132 | elif request.vars.password: |
---|
133 | if verify_password(request.vars.password[:1024]): |
---|
134 | session.authorized = True |
---|
135 | login_record(True) |
---|
136 | |
---|
137 | if CHECK_VERSION: |
---|
138 | session.check_version = True |
---|
139 | else: |
---|
140 | session.check_version = False |
---|
141 | |
---|
142 | session.last_time = t0 |
---|
143 | if isinstance(send, list): # ## why does this happen? |
---|
144 | send = str(send[0]) |
---|
145 | |
---|
146 | redirect(send) |
---|
147 | else: |
---|
148 | times_denied = login_record(False) |
---|
149 | if times_denied >= allowed_number_of_attempts: |
---|
150 | response.flash = \ |
---|
151 | T('admin disabled because too many invalid login attempts') |
---|
152 | elif times_denied == allowed_number_of_attempts - 1: |
---|
153 | response.flash = \ |
---|
154 | T('You have one more login attempt before you are locked out') |
---|
155 | else: |
---|
156 | response.flash = T('invalid password.') |
---|
157 | return dict(send=send) |
---|
158 | |
---|
159 | |
---|
160 | def check_version(): |
---|
161 | """ Checks if web2py is up to date """ |
---|
162 | |
---|
163 | session.forget() |
---|
164 | session._unlock(response) |
---|
165 | |
---|
166 | new_version, version = check_new_version(request.env.web2py_version, |
---|
167 | WEB2PY_VERSION_URL) |
---|
168 | |
---|
169 | if new_version in (-1, -2): |
---|
170 | return A(T('Unable to check for upgrades'), _href=WEB2PY_URL) |
---|
171 | elif not new_version: |
---|
172 | return A(T('web2py is up to date'), _href=WEB2PY_URL) |
---|
173 | elif platform.system().lower() in ('windows', 'win32', 'win64') and os.path.exists("web2py.exe"): |
---|
174 | return SPAN('You should upgrade to %s' % version.split('(')[0]) |
---|
175 | else: |
---|
176 | return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version.split('(')[0]) |
---|
177 | |
---|
178 | |
---|
179 | def logout(): |
---|
180 | """ Logout handler """ |
---|
181 | session.authorized = None |
---|
182 | if MULTI_USER_MODE: |
---|
183 | redirect(URL('user/logout')) |
---|
184 | redirect(URL('index')) |
---|
185 | |
---|
186 | |
---|
187 | def change_password(): |
---|
188 | |
---|
189 | if session.pam_user: |
---|
190 | session.flash = T( |
---|
191 | 'PAM authenticated user, cannot change password here') |
---|
192 | redirect(URL('site')) |
---|
193 | form = SQLFORM.factory(Field('current_admin_password', 'password'), |
---|
194 | Field('new_admin_password', |
---|
195 | 'password', requires=IS_STRONG()), |
---|
196 | Field('new_admin_password_again', 'password'), |
---|
197 | _class="span4 well") |
---|
198 | if form.accepts(request.vars): |
---|
199 | if not verify_password(request.vars.current_admin_password): |
---|
200 | form.errors.current_admin_password = T('invalid password') |
---|
201 | elif form.vars.new_admin_password != form.vars.new_admin_password_again: |
---|
202 | form.errors.new_admin_password_again = T('no match') |
---|
203 | else: |
---|
204 | path = abspath('parameters_%s.py' % request.env.server_port) |
---|
205 | safe_write(path, 'password="%s"' % CRYPT()( |
---|
206 | request.vars.new_admin_password)[0]) |
---|
207 | session.flash = T('password changed') |
---|
208 | redirect(URL('site')) |
---|
209 | return dict(form=form) |
---|
210 | |
---|
211 | |
---|
212 | def site(): |
---|
213 | """ Site handler """ |
---|
214 | |
---|
215 | myversion = request.env.web2py_version |
---|
216 | |
---|
217 | # Shortcut to make the elif statements more legible |
---|
218 | file_or_appurl = 'file' in request.vars or 'appurl' in request.vars |
---|
219 | |
---|
220 | class IS_VALID_APPNAME(object): |
---|
221 | |
---|
222 | def __call__(self, value): |
---|
223 | if not re.compile('^\w+$').match(value): |
---|
224 | return (value, T('Invalid application name')) |
---|
225 | if not request.vars.overwrite and \ |
---|
226 | os.path.exists(os.path.join(apath(r=request), value)): |
---|
227 | return (value, T('Application exists already')) |
---|
228 | return (value, None) |
---|
229 | |
---|
230 | is_appname = IS_VALID_APPNAME() |
---|
231 | form_create = SQLFORM.factory(Field('name', requires=is_appname), |
---|
232 | table_name='appcreate') |
---|
233 | form_update = SQLFORM.factory(Field('name', requires=is_appname), |
---|
234 | Field('file', 'upload', uploadfield=False), |
---|
235 | Field('url'), |
---|
236 | Field('overwrite', 'boolean'), |
---|
237 | table_name='appupdate') |
---|
238 | form_create.process() |
---|
239 | form_update.process() |
---|
240 | |
---|
241 | if DEMO_MODE: |
---|
242 | pass |
---|
243 | |
---|
244 | elif form_create.accepted: |
---|
245 | # create a new application |
---|
246 | appname = cleanpath(form_create.vars.name) |
---|
247 | created, error = app_create(appname, request, info=True) |
---|
248 | if created: |
---|
249 | if MULTI_USER_MODE: |
---|
250 | db.app.insert(name=appname, owner=auth.user.id) |
---|
251 | log_progress(appname) |
---|
252 | session.flash = T('new application "%s" created', appname) |
---|
253 | gluon.rewrite.load() |
---|
254 | redirect(URL('design', args=appname)) |
---|
255 | else: |
---|
256 | session.flash = \ |
---|
257 | DIV(T('unable to create application "%s"', appname), |
---|
258 | PRE(error)) |
---|
259 | redirect(URL(r=request)) |
---|
260 | |
---|
261 | elif form_update.accepted: |
---|
262 | if (form_update.vars.url or '').endswith('.git'): |
---|
263 | if not have_git: |
---|
264 | session.flash = GIT_MISSING |
---|
265 | redirect(URL(r=request)) |
---|
266 | target = os.path.join(apath(r=request), form_update.vars.name) |
---|
267 | try: |
---|
268 | new_repo = git.Repo.clone_from(form_update.vars.url, target) |
---|
269 | session.flash = T('new application "%s" imported', |
---|
270 | form_update.vars.name) |
---|
271 | gluon.rewrite.load() |
---|
272 | except git.GitCommandError as err: |
---|
273 | session.flash = T('Invalid git repository specified.') |
---|
274 | redirect(URL(r=request)) |
---|
275 | |
---|
276 | elif form_update.vars.url: |
---|
277 | # fetch an application via URL or file upload |
---|
278 | try: |
---|
279 | f = urlopen(form_update.vars.url) |
---|
280 | if f.code == 404: |
---|
281 | raise Exception("404 file not found") |
---|
282 | except Exception as e: |
---|
283 | session.flash = \ |
---|
284 | DIV(T('Unable to download app because:'), PRE(repr(e))) |
---|
285 | redirect(URL(r=request)) |
---|
286 | fname = form_update.vars.url |
---|
287 | |
---|
288 | elif form_update.accepted and form_update.vars.file: |
---|
289 | fname = request.vars.file.filename |
---|
290 | f = request.vars.file.file |
---|
291 | |
---|
292 | else: |
---|
293 | session.flash = 'No file uploaded and no URL specified' |
---|
294 | redirect(URL(r=request)) |
---|
295 | |
---|
296 | if f: |
---|
297 | appname = cleanpath(form_update.vars.name) |
---|
298 | installed = app_install(appname, f, |
---|
299 | request, fname, |
---|
300 | overwrite=form_update.vars.overwrite) |
---|
301 | if f and installed: |
---|
302 | msg = 'application %(appname)s installed with md5sum: %(digest)s' |
---|
303 | if MULTI_USER_MODE: |
---|
304 | db.app.insert(name=appname, owner=auth.user.id) |
---|
305 | log_progress(appname) |
---|
306 | session.flash = T(msg, dict(appname=appname, |
---|
307 | digest=md5_hash(installed))) |
---|
308 | gluon.rewrite.load() |
---|
309 | else: |
---|
310 | msg = 'unable to install application "%(appname)s"' |
---|
311 | session.flash = T(msg, dict(appname=form_update.vars.name)) |
---|
312 | redirect(URL(r=request)) |
---|
313 | |
---|
314 | regex = re.compile('^\w+$') |
---|
315 | |
---|
316 | if is_manager(): |
---|
317 | apps = [a for a in os.listdir(apath(r=request)) if regex.match(a) and |
---|
318 | a != '__pycache__'] |
---|
319 | else: |
---|
320 | apps = [a.name for a in db(db.app.owner == auth.user_id).select()] |
---|
321 | |
---|
322 | if FILTER_APPS: |
---|
323 | apps = [a for a in apps if a in FILTER_APPS] |
---|
324 | |
---|
325 | apps = sorted(apps, key=lambda a: a.upper()) |
---|
326 | myplatform = platform.python_version() |
---|
327 | return dict(app=None, apps=apps, myversion=myversion, myplatform=myplatform, |
---|
328 | form_create=form_create, form_update=form_update) |
---|
329 | |
---|
330 | |
---|
331 | def report_progress(app): |
---|
332 | import datetime |
---|
333 | progress_file = os.path.join(apath(app, r=request), 'progress.log') |
---|
334 | regex = re.compile('\[(.*?)\][^\:]+\:\s+(\-?\d+)') |
---|
335 | if not os.path.exists(progress_file): |
---|
336 | return [] |
---|
337 | matches = regex.findall(open(progress_file, 'r').read()) |
---|
338 | events, counter = [], 0 |
---|
339 | for m in matches: |
---|
340 | if not m: |
---|
341 | continue |
---|
342 | days = -(request.now - datetime.datetime.strptime(m[0], |
---|
343 | '%Y-%m-%d %H:%M:%S')).days |
---|
344 | counter += int(m[1]) |
---|
345 | events.append([days, counter]) |
---|
346 | return events |
---|
347 | |
---|
348 | |
---|
349 | def pack(): |
---|
350 | app = get_app() |
---|
351 | |
---|
352 | try: |
---|
353 | if len(request.args) == 1: |
---|
354 | fname = 'web2py.app.%s.w2p' % app |
---|
355 | filename = app_pack(app, request, raise_ex=True) |
---|
356 | else: |
---|
357 | fname = 'web2py.app.%s.compiled.w2p' % app |
---|
358 | filename = app_pack_compiled(app, request, raise_ex=True) |
---|
359 | except Exception as e: |
---|
360 | filename = None |
---|
361 | |
---|
362 | if filename: |
---|
363 | response.headers['Content-Type'] = 'application/w2p' |
---|
364 | disposition = 'attachment; filename=%s' % fname |
---|
365 | response.headers['Content-Disposition'] = disposition |
---|
366 | return safe_read(filename, 'rb') |
---|
367 | else: |
---|
368 | session.flash = T('internal error: %s', e) |
---|
369 | redirect(URL('site')) |
---|
370 | |
---|
371 | |
---|
372 | def pack_plugin(): |
---|
373 | app = get_app() |
---|
374 | if len(request.args) == 2: |
---|
375 | fname = 'web2py.plugin.%s.w2p' % request.args[1] |
---|
376 | filename = plugin_pack(app, request.args[1], request) |
---|
377 | if filename: |
---|
378 | response.headers['Content-Type'] = 'application/w2p' |
---|
379 | disposition = 'attachment; filename=%s' % fname |
---|
380 | response.headers['Content-Disposition'] = disposition |
---|
381 | return safe_read(filename, 'rb') |
---|
382 | else: |
---|
383 | session.flash = T('internal error') |
---|
384 | redirect(URL('plugin', args=request.args)) |
---|
385 | |
---|
386 | |
---|
387 | def pack_exe(app, base, filenames=None): |
---|
388 | import urllib |
---|
389 | import zipfile |
---|
390 | # Download latest web2py_win and open it with zipfile |
---|
391 | download_url = 'http://www.web2py.com/examples/static/web2py_win.zip' |
---|
392 | out = StringIO() |
---|
393 | out.write(urlopen(download_url).read()) |
---|
394 | web2py_win = zipfile.ZipFile(out, mode='a') |
---|
395 | # Write routes.py with the application as default |
---|
396 | routes = u'# -*- coding: utf-8 -*-\nrouters = dict(BASE=dict(default_application="%s"))' % app |
---|
397 | web2py_win.writestr('web2py/routes.py', routes.encode('utf-8')) |
---|
398 | # Copy the application into the zipfile |
---|
399 | common_root = os.path.dirname(base) |
---|
400 | for filename in filenames: |
---|
401 | fname = os.path.join(base, filename) |
---|
402 | arcname = os.path.join('web2py/applications', app, filename) |
---|
403 | web2py_win.write(fname, arcname) |
---|
404 | web2py_win.close() |
---|
405 | response.headers['Content-Type'] = 'application/zip' |
---|
406 | response.headers['Content-Disposition'] = 'attachment; filename=web2py.app.%s.zip' % app |
---|
407 | out.seek(0) |
---|
408 | return response.stream(out) |
---|
409 | |
---|
410 | |
---|
411 | def pack_custom(): |
---|
412 | app = get_app() |
---|
413 | base = apath(app, r=request) |
---|
414 | |
---|
415 | def ignore(fs): |
---|
416 | return [f for f in fs if not ( |
---|
417 | f[:1] in '#' or f.endswith('~') or f.endswith('.bak'))] |
---|
418 | files = {} |
---|
419 | for (r, d, f) in os.walk(base): |
---|
420 | files[r] = {'folders': ignore(d), 'files': ignore(f)} |
---|
421 | |
---|
422 | if request.post_vars.file: |
---|
423 | valid_set = set(os.path.relpath(os.path.join(r, f), base) for r in files for f in files[r]['files']) |
---|
424 | files = request.post_vars.file |
---|
425 | files = [files] if not isinstance(files, list) else files |
---|
426 | files = [file for file in files if file in valid_set] |
---|
427 | |
---|
428 | if request.post_vars.doexe is None: |
---|
429 | fname = 'web2py.app.%s.w2p' % app |
---|
430 | try: |
---|
431 | filename = app_pack(app, request, raise_ex=True, filenames=files) |
---|
432 | except Exception as e: |
---|
433 | filename = None |
---|
434 | if filename: |
---|
435 | response.headers['Content-Type'] = 'application/w2p' |
---|
436 | disposition = 'attachment; filename=%s' % fname |
---|
437 | response.headers['Content-Disposition'] = disposition |
---|
438 | return safe_read(filename, 'rb') |
---|
439 | else: |
---|
440 | session.flash = T('internal error: %s', e) |
---|
441 | redirect(URL(args=request.args)) |
---|
442 | else: |
---|
443 | return pack_exe(app, base, files) |
---|
444 | |
---|
445 | return locals() |
---|
446 | |
---|
447 | |
---|
448 | def upgrade_web2py(): |
---|
449 | dialog = FORM.confirm(T('Upgrade'), |
---|
450 | {T('Cancel'): URL('site')}) |
---|
451 | if dialog.accepted: |
---|
452 | (success, error) = upgrade(request) |
---|
453 | if success: |
---|
454 | session.flash = T('web2py upgraded; please restart it') |
---|
455 | else: |
---|
456 | session.flash = T('unable to upgrade because "%s"', error) |
---|
457 | redirect(URL('site')) |
---|
458 | return dict(dialog=dialog) |
---|
459 | |
---|
460 | |
---|
461 | def uninstall(): |
---|
462 | app = get_app() |
---|
463 | |
---|
464 | dialog = FORM.confirm(T('Uninstall'), |
---|
465 | {T('Cancel'): URL('site')}) |
---|
466 | dialog['_id'] = 'confirm_form' |
---|
467 | dialog['_class'] = 'well' |
---|
468 | for component in dialog.components: |
---|
469 | component['_class'] = 'btn' |
---|
470 | |
---|
471 | if dialog.accepted: |
---|
472 | if MULTI_USER_MODE: |
---|
473 | if is_manager() and db(db.app.name == app).delete(): |
---|
474 | pass |
---|
475 | elif db(db.app.name == app)(db.app.owner == auth.user.id).delete(): |
---|
476 | pass |
---|
477 | else: |
---|
478 | session.flash = T('no permission to uninstall "%s"', app) |
---|
479 | redirect(URL('site')) |
---|
480 | try: |
---|
481 | filename = app_pack(app, request, raise_ex=True) |
---|
482 | except: |
---|
483 | session.flash = T('unable to uninstall "%s"', app) |
---|
484 | else: |
---|
485 | if app_uninstall(app, request): |
---|
486 | session.flash = T('application "%s" uninstalled', app) |
---|
487 | else: |
---|
488 | session.flash = T('unable to uninstall "%s"', app) |
---|
489 | redirect(URL('site')) |
---|
490 | return dict(app=app, dialog=dialog) |
---|
491 | |
---|
492 | |
---|
493 | def cleanup(): |
---|
494 | app = get_app() |
---|
495 | clean = app_cleanup(app, request) |
---|
496 | if not clean: |
---|
497 | session.flash = T("some files could not be removed") |
---|
498 | else: |
---|
499 | session.flash = T('cache, errors and sessions cleaned') |
---|
500 | |
---|
501 | redirect(URL('site')) |
---|
502 | |
---|
503 | |
---|
504 | def compile_app(): |
---|
505 | app = get_app() |
---|
506 | c = app_compile(app, request, |
---|
507 | skip_failed_views=(request.args(1) == 'skip_failed_views')) |
---|
508 | if not c: |
---|
509 | session.flash = T('application compiled') |
---|
510 | elif isinstance(c, list): |
---|
511 | session.flash = DIV(*[T('application compiled'), BR(), BR(), |
---|
512 | T('WARNING: The following views could not be compiled:'), BR()] + |
---|
513 | [CAT(BR(), view) for view in c] + |
---|
514 | [BR(), BR(), T('DO NOT use the "Pack compiled" feature.')]) |
---|
515 | else: |
---|
516 | session.flash = DIV(T('Cannot compile: there are errors in your app:'), |
---|
517 | CODE(c)) |
---|
518 | redirect(URL('site')) |
---|
519 | |
---|
520 | |
---|
521 | def remove_compiled_app(): |
---|
522 | """ Remove the compiled application """ |
---|
523 | app = get_app() |
---|
524 | remove_compiled_application(apath(app, r=request)) |
---|
525 | session.flash = T('compiled application removed') |
---|
526 | redirect(URL('site')) |
---|
527 | |
---|
528 | |
---|
529 | def delete(): |
---|
530 | """ Object delete handler """ |
---|
531 | app = get_app() |
---|
532 | filename = '/'.join(request.args) |
---|
533 | sender = request.vars.sender |
---|
534 | |
---|
535 | if isinstance(sender, list): # ## fix a problem with Vista |
---|
536 | sender = sender[0] |
---|
537 | |
---|
538 | dialog = FORM.confirm(T('Delete'), |
---|
539 | {T('Cancel'): URL(sender, anchor=request.vars.id)}) |
---|
540 | |
---|
541 | if dialog.accepted: |
---|
542 | try: |
---|
543 | full_path = apath(filename, r=request) |
---|
544 | lineno = count_lines(open(full_path, 'r').read()) |
---|
545 | os.unlink(full_path) |
---|
546 | log_progress(app, 'DELETE', filename, progress=-lineno) |
---|
547 | session.flash = T('file "%(filename)s" deleted', |
---|
548 | dict(filename=filename)) |
---|
549 | except Exception: |
---|
550 | session.flash = T('unable to delete file "%(filename)s"', |
---|
551 | dict(filename=filename)) |
---|
552 | redirect(URL(sender, anchor=request.vars.id2)) |
---|
553 | return dict(dialog=dialog, filename=filename) |
---|
554 | |
---|
555 | def enable(): |
---|
556 | if not URL.verify(request, hmac_key=session.hmac_key): raise HTTP(401) |
---|
557 | app = get_app() |
---|
558 | filename = os.path.join(apath(app, r=request), 'DISABLED') |
---|
559 | if is_gae: |
---|
560 | return SPAN(T('Not supported'), _style='color:yellow') |
---|
561 | elif os.path.exists(filename): |
---|
562 | os.unlink(filename) |
---|
563 | return SPAN(T('Disable'), _style='color:green') |
---|
564 | else: |
---|
565 | if PY2: |
---|
566 | safe_open(filename, 'wb').write('disabled: True\ntime-disabled: %s' % request.now) |
---|
567 | else: |
---|
568 | str_ = 'disabled: True\ntime-disabled: %s' % request.now |
---|
569 | safe_open(filename, 'wb').write(str_.encode('utf-8')) |
---|
570 | return SPAN(T('Enable'), _style='color:red') |
---|
571 | |
---|
572 | |
---|
573 | def peek(): |
---|
574 | """ Visualize object code """ |
---|
575 | app = get_app(request.vars.app) |
---|
576 | filename = '/'.join(request.args) |
---|
577 | if request.vars.app: |
---|
578 | path = abspath(filename) |
---|
579 | else: |
---|
580 | path = apath(filename, r=request) |
---|
581 | try: |
---|
582 | data = safe_read(path).replace('\r', '') |
---|
583 | except IOError: |
---|
584 | session.flash = T('file does not exist') |
---|
585 | redirect(URL('site')) |
---|
586 | |
---|
587 | extension = filename[filename.rfind('.') + 1:].lower() |
---|
588 | |
---|
589 | return dict(app=app, |
---|
590 | filename=filename, |
---|
591 | data=data, |
---|
592 | extension=extension) |
---|
593 | |
---|
594 | |
---|
595 | def test(): |
---|
596 | """ Execute controller tests """ |
---|
597 | app = get_app() |
---|
598 | if len(request.args) > 1: |
---|
599 | file = request.args[1] |
---|
600 | else: |
---|
601 | file = '.*\.py' |
---|
602 | |
---|
603 | controllers = listdir( |
---|
604 | apath('%s/controllers/' % app, r=request), file + '$') |
---|
605 | |
---|
606 | return dict(app=app, controllers=controllers) |
---|
607 | |
---|
608 | |
---|
609 | def keepalive(): |
---|
610 | return '' |
---|
611 | |
---|
612 | |
---|
613 | def search(): |
---|
614 | keywords = request.vars.keywords or '' |
---|
615 | app = get_app() |
---|
616 | |
---|
617 | def match(filename, keywords): |
---|
618 | filename = os.path.join(apath(app, r=request), filename) |
---|
619 | if keywords in read_file(filename, 'rb'): |
---|
620 | return True |
---|
621 | return False |
---|
622 | path = apath(request.args[0], r=request) |
---|
623 | files1 = glob(os.path.join(path, '*/*.py')) |
---|
624 | files2 = glob(os.path.join(path, '*/*.html')) |
---|
625 | files3 = glob(os.path.join(path, '*/*/*.html')) |
---|
626 | files = [x[len(path) + 1:].replace( |
---|
627 | '\\', '/') for x in files1 + files2 + files3 if match(x, keywords)] |
---|
628 | return response.json(dict(files=files, message=T.M('Searching: **%s** %%{file}', len(files)))) |
---|
629 | |
---|
630 | |
---|
631 | def edit(): |
---|
632 | """ File edit handler """ |
---|
633 | # Load json only if it is ajax edited... |
---|
634 | app = get_app(request.vars.app) |
---|
635 | app_path = apath(app, r=request) |
---|
636 | preferences = {'theme': 'web2py', 'editor': 'default', 'closetag': 'true', 'codefolding': 'false', 'tabwidth': '4', 'indentwithtabs': 'false', 'linenumbers': 'true', 'highlightline': 'true'} |
---|
637 | config = Config(os.path.join(request.folder, 'settings.cfg'), |
---|
638 | section='editor', default_values={}) |
---|
639 | preferences.update(config.read()) |
---|
640 | |
---|
641 | if not(request.ajax) and not(is_mobile): |
---|
642 | # return the scaffolding, the rest will be through ajax requests |
---|
643 | response.title = T('Editing %s') % app |
---|
644 | return response.render('default/edit.html', dict(app=app, editor_settings=preferences)) |
---|
645 | |
---|
646 | # show settings tab and save prefernces |
---|
647 | if 'settings' in request.vars: |
---|
648 | if request.post_vars: # save new preferences |
---|
649 | if PY2: |
---|
650 | post_vars = request.post_vars.items() |
---|
651 | else: |
---|
652 | post_vars = list(request.post_vars.items()) |
---|
653 | # Since unchecked checkbox are not serialized, we must set them as false by hand to store the correct preference in the settings |
---|
654 | post_vars += [(opt, 'false') for opt in preferences if opt not in request.post_vars] |
---|
655 | if config.save(post_vars): |
---|
656 | response.headers["web2py-component-flash"] = T('Preferences saved correctly') |
---|
657 | else: |
---|
658 | response.headers["web2py-component-flash"] = T('Preferences saved on session only') |
---|
659 | response.headers["web2py-component-command"] = "update_editor(%s);$('a[href=#editor_settings] button.close').click();" % response.json(config.read()) |
---|
660 | return |
---|
661 | else: |
---|
662 | details = {'realfilename': 'settings', 'filename': 'settings', 'id': 'editor_settings', 'force': False} |
---|
663 | details['plain_html'] = response.render('default/editor_settings.html', {'editor_settings': preferences}) |
---|
664 | return response.json(details) |
---|
665 | |
---|
666 | """ File edit handler """ |
---|
667 | # Load json only if it is ajax edited... |
---|
668 | app = get_app(request.vars.app) |
---|
669 | filename = '/'.join(request.args) |
---|
670 | realfilename = request.args[-1] |
---|
671 | if request.vars.app: |
---|
672 | path = abspath(filename) |
---|
673 | else: |
---|
674 | path = apath(filename, r=request) |
---|
675 | # Try to discover the file type |
---|
676 | if filename[-3:] == '.py': |
---|
677 | filetype = 'python' |
---|
678 | elif filename[-5:] == '.html': |
---|
679 | filetype = 'html' |
---|
680 | elif filename[-5:] == '.load': |
---|
681 | filetype = 'html' |
---|
682 | elif filename[-4:] == '.css': |
---|
683 | filetype = 'css' |
---|
684 | elif filename[-3:] == '.js': |
---|
685 | filetype = 'javascript' |
---|
686 | else: |
---|
687 | filetype = 'html' |
---|
688 | |
---|
689 | # ## check if file is not there |
---|
690 | if ('revert' in request.vars) and os.path.exists(path + '.bak'): |
---|
691 | try: |
---|
692 | data = safe_read(path + '.bak') |
---|
693 | data1 = safe_read(path) |
---|
694 | except IOError: |
---|
695 | session.flash = T('Invalid action') |
---|
696 | if 'from_ajax' in request.vars: |
---|
697 | return response.json({'error': str(T('Invalid action'))}) |
---|
698 | else: |
---|
699 | redirect(URL('site')) |
---|
700 | |
---|
701 | safe_write(path, data) |
---|
702 | file_hash = md5_hash(data) |
---|
703 | saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) |
---|
704 | safe_write(path + '.bak', data1) |
---|
705 | response.flash = T('file "%s" of %s restored', (filename, saved_on)) |
---|
706 | else: |
---|
707 | try: |
---|
708 | data = safe_read(path) |
---|
709 | except IOError: |
---|
710 | session.flash = T('Invalid action') |
---|
711 | if 'from_ajax' in request.vars: |
---|
712 | return response.json({'error': str(T('Invalid action'))}) |
---|
713 | else: |
---|
714 | redirect(URL('site')) |
---|
715 | |
---|
716 | lineno_old = count_lines(data) |
---|
717 | file_hash = md5_hash(data) |
---|
718 | saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) |
---|
719 | |
---|
720 | if request.vars.file_hash and request.vars.file_hash != file_hash: |
---|
721 | session.flash = T('file changed on disk') |
---|
722 | data = request.vars.data.replace('\r\n', '\n').strip() + '\n' |
---|
723 | safe_write(path + '.1', data) |
---|
724 | if 'from_ajax' in request.vars: |
---|
725 | return response.json({'error': str(T('file changed on disk')), |
---|
726 | 'redirect': URL('resolve', |
---|
727 | args=request.args)}) |
---|
728 | else: |
---|
729 | redirect(URL('resolve', args=request.args)) |
---|
730 | elif request.vars.data: |
---|
731 | safe_write(path + '.bak', data) |
---|
732 | data = request.vars.data.replace('\r\n', '\n').strip() + '\n' |
---|
733 | safe_write(path, data) |
---|
734 | lineno_new = count_lines(data) |
---|
735 | log_progress( |
---|
736 | app, 'EDIT', filename, progress=lineno_new - lineno_old) |
---|
737 | file_hash = md5_hash(data) |
---|
738 | saved_on = time.ctime(os.stat(path)[stat.ST_MTIME]) |
---|
739 | response.flash = T('file saved on %s', saved_on) |
---|
740 | |
---|
741 | data_or_revert = (request.vars.data or request.vars.revert) |
---|
742 | |
---|
743 | # Check compile errors |
---|
744 | highlight = None |
---|
745 | if filetype == 'python' and request.vars.data: |
---|
746 | import _ast |
---|
747 | try: |
---|
748 | code = request.vars.data.rstrip().replace('\r\n', '\n') + '\n' |
---|
749 | compile(code, path, "exec", _ast.PyCF_ONLY_AST) |
---|
750 | except Exception as e: |
---|
751 | # offset calculation is only used for textarea (start/stop) |
---|
752 | start = sum([len(line) + 1 for l, line |
---|
753 | in enumerate(request.vars.data.split("\n")) |
---|
754 | if l < e.lineno - 1]) |
---|
755 | if e.text and e.offset: |
---|
756 | offset = e.offset - (len(e.text) - len( |
---|
757 | e.text.splitlines()[-1])) |
---|
758 | else: |
---|
759 | offset = 0 |
---|
760 | highlight = {'start': start, 'end': start + |
---|
761 | offset + 1, 'lineno': e.lineno, 'offset': offset} |
---|
762 | try: |
---|
763 | ex_name = e.__class__.__name__ |
---|
764 | except: |
---|
765 | ex_name = 'unknown exception!' |
---|
766 | response.flash = DIV(T('failed to compile file because:'), BR(), |
---|
767 | B(ex_name), ' ' + T('at line %s', e.lineno), |
---|
768 | offset and ' ' + |
---|
769 | T('at char %s', offset) or '', |
---|
770 | PRE(repr(e))) |
---|
771 | if data_or_revert and request.args[1] == 'modules': |
---|
772 | # Lets try to reload the modules |
---|
773 | try: |
---|
774 | mopath = '.'.join(request.args[2:])[:-3] |
---|
775 | exec('import applications.%s.modules.%s' % ( |
---|
776 | request.args[0], mopath)) |
---|
777 | reload(sys.modules['applications.%s.modules.%s' |
---|
778 | % (request.args[0], mopath)]) |
---|
779 | except Exception as e: |
---|
780 | response.flash = DIV( |
---|
781 | T('failed to reload module because:'), PRE(repr(e))) |
---|
782 | |
---|
783 | edit_controller = None |
---|
784 | editviewlinks = None |
---|
785 | view_link = None |
---|
786 | if filetype == 'html' and len(request.args) >= 3: |
---|
787 | cfilename = os.path.join(request.args[0], 'controllers', |
---|
788 | request.args[2] + '.py') |
---|
789 | if os.path.exists(apath(cfilename, r=request)): |
---|
790 | edit_controller = URL('edit', args=[cfilename.replace(os.sep, "/")]) |
---|
791 | view = request.args[3].replace('.html', '') |
---|
792 | view_link = URL(request.args[0], request.args[2], view) |
---|
793 | elif filetype == 'python' and request.args[1] == 'controllers': |
---|
794 | # it's a controller file. |
---|
795 | # Create links to all of the associated view files. |
---|
796 | app = get_app() |
---|
797 | viewname = os.path.splitext(request.args[2])[0] |
---|
798 | viewpath = os.path.join(app, 'views', viewname) |
---|
799 | aviewpath = apath(viewpath, r=request) |
---|
800 | viewlist = [] |
---|
801 | if os.path.exists(aviewpath): |
---|
802 | if os.path.isdir(aviewpath): |
---|
803 | viewlist = glob(os.path.join(aviewpath, '*.html')) |
---|
804 | elif os.path.exists(aviewpath + '.html'): |
---|
805 | viewlist.append(aviewpath + '.html') |
---|
806 | if len(viewlist): |
---|
807 | editviewlinks = [] |
---|
808 | for v in sorted(viewlist): |
---|
809 | vf = os.path.split(v)[-1] |
---|
810 | vargs = "/".join([viewpath.replace(os.sep, "/"), vf]) |
---|
811 | editviewlinks.append(A(vf.split(".")[0], |
---|
812 | _class="editor_filelink", |
---|
813 | _href=URL('edit', args=[vargs]))) |
---|
814 | |
---|
815 | if len(request.args) > 2 and request.args[1] == 'controllers': |
---|
816 | controller = (request.args[2])[:-3] |
---|
817 | try: |
---|
818 | functions = find_exposed_functions(data) |
---|
819 | functions = functions and sorted(functions) or [] |
---|
820 | except SyntaxError as err: |
---|
821 | functions = ['SyntaxError:Line:%d' % err.lineno] |
---|
822 | else: |
---|
823 | (controller, functions) = (None, None) |
---|
824 | |
---|
825 | if 'from_ajax' in request.vars: |
---|
826 | return response.json({'file_hash': file_hash, 'saved_on': saved_on, 'functions': functions, 'controller': controller, 'application': request.args[0], 'highlight': highlight}) |
---|
827 | else: |
---|
828 | file_details = dict(app=request.args[0], |
---|
829 | lineno=request.vars.lineno or 1, |
---|
830 | editor_settings=preferences, |
---|
831 | filename=filename, |
---|
832 | realfilename=realfilename, |
---|
833 | filetype=filetype, |
---|
834 | data=data, |
---|
835 | edit_controller=edit_controller, |
---|
836 | file_hash=file_hash, |
---|
837 | saved_on=saved_on, |
---|
838 | controller=controller, |
---|
839 | functions=functions, |
---|
840 | view_link=view_link, |
---|
841 | editviewlinks=editviewlinks, |
---|
842 | id=IS_SLUG()(filename)[0], |
---|
843 | force=True if (request.vars.restore or |
---|
844 | request.vars.revert) else False) |
---|
845 | plain_html = response.render('default/edit_js.html', file_details) |
---|
846 | file_details['plain_html'] = plain_html |
---|
847 | if is_mobile: |
---|
848 | return response.render('default.mobile/edit.html', |
---|
849 | file_details, editor_settings=preferences) |
---|
850 | else: |
---|
851 | return response.json(file_details) |
---|
852 | |
---|
853 | |
---|
854 | def todolist(): |
---|
855 | """ Returns all TODO of the requested app |
---|
856 | """ |
---|
857 | app = request.vars.app or '' |
---|
858 | app_path = apath('%(app)s' % {'app': app}, r=request) |
---|
859 | dirs = ['models', 'controllers', 'modules', 'private'] |
---|
860 | |
---|
861 | def listfiles(app, dir, regexp='.*\.py$'): |
---|
862 | files = sorted(listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp)) |
---|
863 | files = [x.replace(os.path.sep, '/') for x in files if not x.endswith('.bak')] |
---|
864 | return files |
---|
865 | |
---|
866 | pattern = '#\s*(todo)+\s+(.*)' |
---|
867 | regex = re.compile(pattern, re.IGNORECASE) |
---|
868 | |
---|
869 | output = [] |
---|
870 | for d in dirs: |
---|
871 | for f in listfiles(app, d): |
---|
872 | matches = [] |
---|
873 | filename = apath(os.path.join(app, d, f), r=request) |
---|
874 | with safe_open(filename, 'r') as f_s: |
---|
875 | src = f_s.read() |
---|
876 | for m in regex.finditer(src): |
---|
877 | start = m.start() |
---|
878 | lineno = src.count('\n', 0, start) + 1 |
---|
879 | matches.append({'text': m.group(0), 'lineno': lineno}) |
---|
880 | if len(matches) != 0: |
---|
881 | output.append({'filename': f, 'matches': matches, 'dir': d}) |
---|
882 | |
---|
883 | return {'todo': output, 'app': app} |
---|
884 | |
---|
885 | |
---|
886 | def editor_sessions(): |
---|
887 | config = Config(os.path.join(request.folder, 'settings.cfg'), |
---|
888 | section='editor_sessions', default_values={}) |
---|
889 | preferences = config.read() |
---|
890 | |
---|
891 | if request.vars.session_name and request.vars.files: |
---|
892 | session_name = request.vars.session_name |
---|
893 | files = request.vars.files |
---|
894 | preferences.update({session_name: ','.join(files)}) |
---|
895 | if config.save(preferences.items()): |
---|
896 | response.headers["web2py-component-flash"] = T('Session saved correctly') |
---|
897 | else: |
---|
898 | response.headers["web2py-component-flash"] = T('Session saved on session only') |
---|
899 | |
---|
900 | return response.render('default/editor_sessions.html', {'editor_sessions': preferences}) |
---|
901 | |
---|
902 | |
---|
903 | def resolve(): |
---|
904 | """ |
---|
905 | """ |
---|
906 | |
---|
907 | filename = '/'.join(request.args) |
---|
908 | # ## check if file is not there |
---|
909 | path = apath(filename, r=request) |
---|
910 | a = safe_read(path).split('\n') |
---|
911 | try: |
---|
912 | b = safe_read(path + '.1').split('\n') |
---|
913 | except IOError: |
---|
914 | session.flash = 'Other file, no longer there' |
---|
915 | redirect(URL('edit', args=request.args)) |
---|
916 | |
---|
917 | d = difflib.ndiff(a, b) |
---|
918 | |
---|
919 | def leading(line): |
---|
920 | """ """ |
---|
921 | |
---|
922 | # TODO: we really need to comment this |
---|
923 | z = '' |
---|
924 | for (k, c) in enumerate(line): |
---|
925 | if c == ' ': |
---|
926 | z += ' ' |
---|
927 | elif c == ' \t': |
---|
928 | z += ' ' |
---|
929 | elif k == 0 and c == '?': |
---|
930 | pass |
---|
931 | else: |
---|
932 | break |
---|
933 | |
---|
934 | return XML(z) |
---|
935 | |
---|
936 | def getclass(item): |
---|
937 | """ Determine item class """ |
---|
938 | operators = {' ': 'normal', '+': 'plus', '-': 'minus'} |
---|
939 | |
---|
940 | return operators[item[0]] |
---|
941 | |
---|
942 | if request.vars: |
---|
943 | c = '\n'.join([item[2:].rstrip() for (i, item) in enumerate(d) if item[0] |
---|
944 | == ' ' or 'line%i' % i in request.vars]) |
---|
945 | safe_write(path, c) |
---|
946 | session.flash = 'files merged' |
---|
947 | redirect(URL('edit', args=request.args)) |
---|
948 | else: |
---|
949 | # Making the short circuit compatible with <= python2.4 |
---|
950 | gen_data = lambda index, item: not item[:1] in ['+', '-'] and "" \ |
---|
951 | or INPUT(_type='checkbox', |
---|
952 | _name='line%i' % index, |
---|
953 | value=item[0] == '+') |
---|
954 | |
---|
955 | diff = TABLE(*[TR(TD(gen_data(i, item)), |
---|
956 | TD(item[0]), |
---|
957 | TD(leading(item[2:]), |
---|
958 | TT(item[2:].rstrip())), |
---|
959 | _class=getclass(item)) |
---|
960 | for (i, item) in enumerate(d) if item[0] != '?']) |
---|
961 | |
---|
962 | return dict(diff=diff, filename=filename) |
---|
963 | |
---|
964 | |
---|
965 | def edit_language(): |
---|
966 | """ Edit language file """ |
---|
967 | app = get_app() |
---|
968 | filename = '/'.join(request.args) |
---|
969 | response.title = request.args[-1] |
---|
970 | strings = read_dict(apath(filename, r=request)) |
---|
971 | |
---|
972 | if '__corrupted__' in strings: |
---|
973 | form = SPAN(strings['__corrupted__'], _class='error') |
---|
974 | return dict(filename=filename, form=form) |
---|
975 | |
---|
976 | keys = sorted(strings.keys(), key=lambda x: to_native(x).lower()) |
---|
977 | rows = [] |
---|
978 | rows.append(H2(T('Original/Translation'))) |
---|
979 | |
---|
980 | for key in keys: |
---|
981 | name = md5_hash(key) |
---|
982 | s = strings[key] |
---|
983 | (prefix, sep, key) = key.partition('\x01') |
---|
984 | if sep: |
---|
985 | prefix = SPAN(prefix + ': ', _class='tm_ftag') |
---|
986 | k = key |
---|
987 | else: |
---|
988 | (k, prefix) = (prefix, '') |
---|
989 | |
---|
990 | _class = 'untranslated' if k == s else 'translated' |
---|
991 | |
---|
992 | if len(s) <= 40: |
---|
993 | elem = INPUT(_type='text', _name=name, value=s, |
---|
994 | _size=70, _class=_class) |
---|
995 | else: |
---|
996 | elem = TEXTAREA(_name=name, value=s, _cols=70, |
---|
997 | _rows=5, _class=_class) |
---|
998 | |
---|
999 | # Making the short circuit compatible with <= python2.4 |
---|
1000 | k = (s != k) and k or B(k) |
---|
1001 | |
---|
1002 | new_row = DIV(LABEL(prefix, k, _style="font-weight:normal;"), |
---|
1003 | CAT(elem, '\n', TAG.BUTTON( |
---|
1004 | T('delete'), |
---|
1005 | _onclick='return delkey("%s")' % name, |
---|
1006 | _class='btn')), _id=name, _class='span6 well well-small') |
---|
1007 | |
---|
1008 | rows.append(DIV(new_row, _class="row-fluid")) |
---|
1009 | rows.append(DIV(INPUT(_type='submit', _value=T('update'), _class="btn btn-primary"), _class='controls')) |
---|
1010 | form = FORM(*rows) |
---|
1011 | if form.accepts(request.vars, keepvalues=True): |
---|
1012 | strs = dict() |
---|
1013 | for key in keys: |
---|
1014 | name = md5_hash(key) |
---|
1015 | if form.vars[name] == chr(127): |
---|
1016 | continue |
---|
1017 | strs[key] = form.vars[name] |
---|
1018 | write_dict(apath(filename, r=request), strs) |
---|
1019 | session.flash = T('file saved on %(time)s', dict(time=time.ctime())) |
---|
1020 | redirect(URL(r=request, args=request.args)) |
---|
1021 | return dict(app=request.args[0], filename=filename, form=form) |
---|
1022 | |
---|
1023 | |
---|
1024 | def edit_plurals(): |
---|
1025 | """ Edit plurals file """ |
---|
1026 | app = get_app() |
---|
1027 | filename = '/'.join(request.args) |
---|
1028 | plurals = read_plural_dict( |
---|
1029 | apath(filename, r=request)) # plural forms dictionary |
---|
1030 | nplurals = int(request.vars.nplurals) - 1 # plural forms quantity |
---|
1031 | xnplurals = xrange(nplurals) |
---|
1032 | |
---|
1033 | if '__corrupted__' in plurals: |
---|
1034 | # show error message and exit |
---|
1035 | form = SPAN(plurals['__corrupted__'], _class='error') |
---|
1036 | return dict(filename=filename, form=form) |
---|
1037 | |
---|
1038 | keys = sorted(plurals.keys(), lambda x, y: cmp( |
---|
1039 | unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())) |
---|
1040 | tab_rows = [] |
---|
1041 | for key in keys: |
---|
1042 | name = md5_hash(key) |
---|
1043 | forms = plurals[key] |
---|
1044 | |
---|
1045 | if len(forms) < nplurals: |
---|
1046 | forms.extend(None for i in xrange(nplurals - len(forms))) |
---|
1047 | tab_col1 = DIV(CAT(LABEL(T("Singular Form")), B(key, |
---|
1048 | _class='fake-input'))) |
---|
1049 | tab_inputs = [SPAN(LABEL(T("Plural Form #%s", n + 1)), INPUT(_type='text', _name=name + '_' + str(n), value=forms[n], _size=20), _class='span6') for n in xnplurals] |
---|
1050 | tab_col2 = DIV(CAT(*tab_inputs)) |
---|
1051 | tab_col3 = DIV(CAT(LABEL(XML(' ')), TAG.BUTTON(T('delete'), _onclick='return delkey("%s")' % name, _class='btn'), _class='span6')) |
---|
1052 | tab_row = DIV(DIV(tab_col1, '\n', tab_col2, '\n', tab_col3, _class='well well-small'), _id=name, _class='row-fluid tab_row') |
---|
1053 | tab_rows.append(tab_row) |
---|
1054 | |
---|
1055 | tab_rows.append(DIV(TAG['button'](T('update'), _type='submit', |
---|
1056 | _class='btn btn-primary'), |
---|
1057 | _class='controls')) |
---|
1058 | tab_container = DIV(*tab_rows, **dict(_class="row-fluid")) |
---|
1059 | |
---|
1060 | form = FORM(tab_container) |
---|
1061 | if form.accepts(request.vars, keepvalues=True): |
---|
1062 | new_plurals = dict() |
---|
1063 | for key in keys: |
---|
1064 | name = md5_hash(key) |
---|
1065 | if form.vars[name + '_0'] == chr(127): |
---|
1066 | continue |
---|
1067 | new_plurals[key] = [form.vars[name + '_' + str(n)] |
---|
1068 | for n in xnplurals] |
---|
1069 | write_plural_dict(apath(filename, r=request), new_plurals) |
---|
1070 | session.flash = T('file saved on %(time)s', dict(time=time.ctime())) |
---|
1071 | redirect(URL(r=request, args=request.args, vars=dict( |
---|
1072 | nplurals=request.vars.nplurals))) |
---|
1073 | return dict(app=request.args[0], filename=filename, form=form) |
---|
1074 | |
---|
1075 | |
---|
1076 | def about(): |
---|
1077 | """ Read about info """ |
---|
1078 | app = get_app() |
---|
1079 | # ## check if file is not there |
---|
1080 | about = safe_read(apath('%s/ABOUT' % app, r=request)) |
---|
1081 | license = safe_read(apath('%s/LICENSE' % app, r=request)) |
---|
1082 | return dict(app=app, about=MARKMIN(about), license=MARKMIN(license), progress=report_progress(app)) |
---|
1083 | |
---|
1084 | |
---|
1085 | def design(): |
---|
1086 | """ Application design handler """ |
---|
1087 | app = get_app() |
---|
1088 | |
---|
1089 | if not response.flash and app == request.application: |
---|
1090 | msg = T('ATTENTION: you cannot edit the running application!') |
---|
1091 | response.flash = msg |
---|
1092 | |
---|
1093 | if request.vars and not request.vars.token == session.token: |
---|
1094 | redirect(URL('logout')) |
---|
1095 | |
---|
1096 | if request.vars.pluginfile is not None and not isinstance(request.vars.pluginfile, str): |
---|
1097 | filename = os.path.basename(request.vars.pluginfile.filename) |
---|
1098 | if plugin_install(app, request.vars.pluginfile.file, |
---|
1099 | request, filename): |
---|
1100 | session.flash = T('new plugin installed') |
---|
1101 | redirect(URL('design', args=app)) |
---|
1102 | else: |
---|
1103 | session.flash = \ |
---|
1104 | T('unable to install plugin "%s"', filename) |
---|
1105 | redirect(URL(r=request, args=app)) |
---|
1106 | elif isinstance(request.vars.pluginfile, str): |
---|
1107 | session.flash = T('plugin not specified') |
---|
1108 | redirect(URL(r=request, args=app)) |
---|
1109 | |
---|
1110 | # If we have only pyc files it means that |
---|
1111 | # we cannot design |
---|
1112 | if os.path.exists(apath('%s/compiled' % app, r=request)): |
---|
1113 | session.flash = \ |
---|
1114 | T('application is compiled and cannot be designed') |
---|
1115 | redirect(URL('site')) |
---|
1116 | |
---|
1117 | # Get all models |
---|
1118 | models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') |
---|
1119 | models = [x.replace('\\', '/') for x in models] |
---|
1120 | defines = {} |
---|
1121 | for m in models: |
---|
1122 | data = safe_read(apath('%s/models/%s' % (app, m), r=request)) |
---|
1123 | defines[m] = re.findall(REGEX_DEFINE_TABLE, data, re.MULTILINE) |
---|
1124 | defines[m].sort() |
---|
1125 | |
---|
1126 | # Get all controllers |
---|
1127 | controllers = sorted( |
---|
1128 | listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) |
---|
1129 | controllers = [x.replace('\\', '/') for x in controllers] |
---|
1130 | functions = {} |
---|
1131 | for c in controllers: |
---|
1132 | data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) |
---|
1133 | try: |
---|
1134 | items = find_exposed_functions(data) |
---|
1135 | functions[c] = items and sorted(items) or [] |
---|
1136 | except SyntaxError as err: |
---|
1137 | functions[c] = ['SyntaxError:Line:%d' % err.lineno] |
---|
1138 | |
---|
1139 | # Get all views |
---|
1140 | views = sorted( |
---|
1141 | listdir(apath('%s/views/' % app, r=request), '[\w/\-]+(\.\w+)+$')) |
---|
1142 | views = [x.replace('\\', '/') for x in views if not x.endswith('.bak')] |
---|
1143 | extend = {} |
---|
1144 | include = {} |
---|
1145 | for c in views: |
---|
1146 | data = safe_read(apath('%s/views/%s' % (app, c), r=request)) |
---|
1147 | items = re.findall(REGEX_EXTEND, data, re.MULTILINE) |
---|
1148 | |
---|
1149 | if items: |
---|
1150 | extend[c] = items[0][1] |
---|
1151 | |
---|
1152 | items = re.findall(REGEX_INCLUDE, data) |
---|
1153 | include[c] = [i[1] for i in items] |
---|
1154 | |
---|
1155 | # Get all modules |
---|
1156 | modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') |
---|
1157 | modules = modules = [x.replace('\\', '/') for x in modules] |
---|
1158 | modules.sort() |
---|
1159 | |
---|
1160 | # Get all private files |
---|
1161 | privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*') |
---|
1162 | privates = [x.replace('\\', '/') for x in privates] |
---|
1163 | privates.sort() |
---|
1164 | |
---|
1165 | # Get all static files |
---|
1166 | statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*', |
---|
1167 | maxnum=MAXNFILES) |
---|
1168 | statics = [x.replace(os.path.sep, '/') for x in statics] |
---|
1169 | statics.sort() |
---|
1170 | |
---|
1171 | # Get all languages |
---|
1172 | langpath = os.path.join(apath(app, r=request), 'languages') |
---|
1173 | languages = dict([(lang, info) for lang, info |
---|
1174 | in iteritems(read_possible_languages(langpath)) |
---|
1175 | if info[2] != 0]) # info[2] is langfile_mtime: |
---|
1176 | # get only existed files |
---|
1177 | |
---|
1178 | # Get crontab |
---|
1179 | cronfolder = apath('%s/cron' % app, r=request) |
---|
1180 | crontab = apath('%s/cron/crontab' % app, r=request) |
---|
1181 | if not is_gae: |
---|
1182 | if not os.path.exists(cronfolder): |
---|
1183 | os.mkdir(cronfolder) |
---|
1184 | if not os.path.exists(crontab): |
---|
1185 | safe_write(crontab, '#crontab') |
---|
1186 | |
---|
1187 | plugins = [] |
---|
1188 | |
---|
1189 | def filter_plugins(items, plugins): |
---|
1190 | plugins += [item[7:].split('/')[0].split( |
---|
1191 | '.')[0] for item in items if item.startswith('plugin_')] |
---|
1192 | plugins[:] = list(set(plugins)) |
---|
1193 | plugins.sort() |
---|
1194 | return [item for item in items if not item.startswith('plugin_')] |
---|
1195 | |
---|
1196 | return dict(app=app, |
---|
1197 | models=filter_plugins(models, plugins), |
---|
1198 | defines=defines, |
---|
1199 | controllers=filter_plugins(controllers, plugins), |
---|
1200 | functions=functions, |
---|
1201 | views=filter_plugins(views, plugins), |
---|
1202 | modules=filter_plugins(modules, plugins), |
---|
1203 | extend=extend, |
---|
1204 | include=include, |
---|
1205 | privates=filter_plugins(privates, plugins), |
---|
1206 | statics=filter_plugins(statics, plugins), |
---|
1207 | languages=languages, |
---|
1208 | crontab=crontab, |
---|
1209 | plugins=plugins) |
---|
1210 | |
---|
1211 | |
---|
1212 | def delete_plugin(): |
---|
1213 | """ Object delete handler """ |
---|
1214 | app = request.args(0) |
---|
1215 | plugin = request.args(1) |
---|
1216 | plugin_name = 'plugin_' + plugin |
---|
1217 | |
---|
1218 | dialog = FORM.confirm( |
---|
1219 | T('Delete'), |
---|
1220 | {T('Cancel'): URL('design', args=app)}) |
---|
1221 | |
---|
1222 | if dialog.accepted: |
---|
1223 | try: |
---|
1224 | for folder in ['models', 'views', 'controllers', 'static', 'modules', 'private']: |
---|
1225 | path = os.path.join(apath(app, r=request), folder) |
---|
1226 | for item in os.listdir(path): |
---|
1227 | if item.rsplit('.', 1)[0] == plugin_name: |
---|
1228 | filename = os.path.join(path, item) |
---|
1229 | if os.path.isdir(filename): |
---|
1230 | shutil.rmtree(filename) |
---|
1231 | else: |
---|
1232 | os.unlink(filename) |
---|
1233 | session.flash = T('plugin "%(plugin)s" deleted', |
---|
1234 | dict(plugin=plugin)) |
---|
1235 | except Exception: |
---|
1236 | session.flash = T('unable to delete file plugin "%(plugin)s"', |
---|
1237 | dict(plugin=plugin)) |
---|
1238 | redirect(URL('design', args=request.args(0), anchor=request.vars.id2)) |
---|
1239 | return dict(dialog=dialog, plugin=plugin) |
---|
1240 | |
---|
1241 | |
---|
1242 | def plugin(): |
---|
1243 | """ Application design handler """ |
---|
1244 | app = get_app() |
---|
1245 | plugin = request.args(1) |
---|
1246 | |
---|
1247 | if not response.flash and app == request.application: |
---|
1248 | msg = T('ATTENTION: you cannot edit the running application!') |
---|
1249 | response.flash = msg |
---|
1250 | |
---|
1251 | # If we have only pyc files it means that |
---|
1252 | # we cannot design |
---|
1253 | if os.path.exists(apath('%s/compiled' % app, r=request)): |
---|
1254 | session.flash = \ |
---|
1255 | T('application is compiled and cannot be designed') |
---|
1256 | redirect(URL('site')) |
---|
1257 | |
---|
1258 | # Get all models |
---|
1259 | models = listdir(apath('%s/models/' % app, r=request), '.*\.py$') |
---|
1260 | models = [x.replace('\\', '/') for x in models] |
---|
1261 | defines = {} |
---|
1262 | for m in models: |
---|
1263 | data = safe_read(apath('%s/models/%s' % (app, m), r=request)) |
---|
1264 | defines[m] = regex_tables.findall(data) |
---|
1265 | defines[m].sort() |
---|
1266 | |
---|
1267 | # Get all controllers |
---|
1268 | controllers = sorted( |
---|
1269 | listdir(apath('%s/controllers/' % app, r=request), '.*\.py$')) |
---|
1270 | controllers = [x.replace('\\', '/') for x in controllers] |
---|
1271 | functions = {} |
---|
1272 | for c in controllers: |
---|
1273 | data = safe_read(apath('%s/controllers/%s' % (app, c), r=request)) |
---|
1274 | try: |
---|
1275 | items = find_exposed_functions(data) |
---|
1276 | functions[c] = items and sorted(items) or [] |
---|
1277 | except SyntaxError as err: |
---|
1278 | functions[c] = ['SyntaxError:Line:%d' % err.lineno] |
---|
1279 | |
---|
1280 | # Get all views |
---|
1281 | views = sorted( |
---|
1282 | listdir(apath('%s/views/' % app, r=request), '[\w/\-]+\.\w+$')) |
---|
1283 | views = [x.replace('\\', '/') for x in views] |
---|
1284 | extend = {} |
---|
1285 | include = {} |
---|
1286 | for c in views: |
---|
1287 | data = safe_read(apath('%s/views/%s' % (app, c), r=request)) |
---|
1288 | items = re.findall(REGEX_EXTEND, data, re.MULTILINE) |
---|
1289 | if items: |
---|
1290 | extend[c] = items[0][1] |
---|
1291 | |
---|
1292 | items = re.findall(REGEX_INCLUDE, data) |
---|
1293 | include[c] = [i[1] for i in items] |
---|
1294 | |
---|
1295 | # Get all modules |
---|
1296 | modules = listdir(apath('%s/modules/' % app, r=request), '.*\.py$') |
---|
1297 | modules = modules = [x.replace('\\', '/') for x in modules] |
---|
1298 | modules.sort() |
---|
1299 | |
---|
1300 | # Get all private files |
---|
1301 | privates = listdir(apath('%s/private/' % app, r=request), '[^\.#].*') |
---|
1302 | privates = [x.replace('\\', '/') for x in privates] |
---|
1303 | privates.sort() |
---|
1304 | |
---|
1305 | # Get all static files |
---|
1306 | statics = listdir(apath('%s/static/' % app, r=request), '[^\.#].*', |
---|
1307 | maxnum=MAXNFILES) |
---|
1308 | statics = [x.replace(os.path.sep, '/') for x in statics] |
---|
1309 | statics.sort() |
---|
1310 | |
---|
1311 | # Get all languages |
---|
1312 | languages = sorted([lang + '.py' for lang, info in |
---|
1313 | iteritems(T.get_possible_languages_info()) |
---|
1314 | if info[2] != 0]) # info[2] is langfile_mtime: |
---|
1315 | # get only existed files |
---|
1316 | |
---|
1317 | # Get crontab |
---|
1318 | crontab = apath('%s/cron/crontab' % app, r=request) |
---|
1319 | if not os.path.exists(crontab): |
---|
1320 | safe_write(crontab, '#crontab') |
---|
1321 | |
---|
1322 | def filter_plugins(items): |
---|
1323 | regex = re.compile('^plugin_' + plugin + '(/.*|\..*)?$') |
---|
1324 | return [item for item in items if item and regex.match(item)] |
---|
1325 | |
---|
1326 | return dict(app=app, |
---|
1327 | models=filter_plugins(models), |
---|
1328 | defines=defines, |
---|
1329 | controllers=filter_plugins(controllers), |
---|
1330 | functions=functions, |
---|
1331 | views=filter_plugins(views), |
---|
1332 | modules=filter_plugins(modules), |
---|
1333 | extend=extend, |
---|
1334 | include=include, |
---|
1335 | privates=filter_plugins(privates), |
---|
1336 | statics=filter_plugins(statics), |
---|
1337 | languages=languages, |
---|
1338 | crontab=crontab) |
---|
1339 | |
---|
1340 | |
---|
1341 | def create_file(): |
---|
1342 | """ Create files handler """ |
---|
1343 | if request.vars and not request.vars.token == session.token: |
---|
1344 | redirect(URL('logout')) |
---|
1345 | try: |
---|
1346 | anchor = '#' + request.vars.id if request.vars.id else '' |
---|
1347 | if request.vars.app: |
---|
1348 | app = get_app(request.vars.app) |
---|
1349 | path = abspath(request.vars.location) |
---|
1350 | else: |
---|
1351 | if request.vars.dir: |
---|
1352 | request.vars.location += request.vars.dir + '/' |
---|
1353 | app = get_app(name=request.vars.location.split('/')[0]) |
---|
1354 | path = apath(request.vars.location, r=request) |
---|
1355 | filename = re.sub('[^\w./-]+', '_', request.vars.filename) |
---|
1356 | if path[-7:] == '/rules/': |
---|
1357 | # Handle plural rules files |
---|
1358 | if len(filename) == 0: |
---|
1359 | raise SyntaxError |
---|
1360 | if not filename[-3:] == '.py': |
---|
1361 | filename += '.py' |
---|
1362 | lang = re.match('^plural_rules-(.*)\.py$', filename).group(1) |
---|
1363 | langinfo = read_possible_languages(apath(app, r=request))[lang] |
---|
1364 | text = dedent(""" |
---|
1365 | #!/usr/bin/env python |
---|
1366 | # -*- coding: utf-8 -*- |
---|
1367 | # Plural-Forms for %(lang)s (%(langname)s) |
---|
1368 | |
---|
1369 | nplurals=2 # for example, English language has 2 forms: |
---|
1370 | # 1 singular and 1 plural |
---|
1371 | |
---|
1372 | # Determine plural_id for number *n* as sequence of positive |
---|
1373 | # integers: 0,1,... |
---|
1374 | # NOTE! For singular form ALWAYS return plural_id = 0 |
---|
1375 | get_plural_id = lambda n: int(n != 1) |
---|
1376 | |
---|
1377 | # Construct and return plural form of *word* using |
---|
1378 | # *plural_id* (which ALWAYS>0). This function will be executed |
---|
1379 | # for words (or phrases) not found in plural_dict dictionary. |
---|
1380 | # By default this function simply returns word in singular: |
---|
1381 | construct_plural_form = lambda word, plural_id: word |
---|
1382 | """)[1:] % dict(lang=langinfo[0], langname=langinfo[1]) |
---|
1383 | |
---|
1384 | elif path[-11:] == '/languages/': |
---|
1385 | # Handle language files |
---|
1386 | if len(filename) == 0: |
---|
1387 | raise SyntaxError |
---|
1388 | if not filename[-3:] == '.py': |
---|
1389 | filename += '.py' |
---|
1390 | path = os.path.join(apath(app, r=request), 'languages', filename) |
---|
1391 | if not os.path.exists(path): |
---|
1392 | safe_write(path, '') |
---|
1393 | # create language xx[-yy].py file: |
---|
1394 | findT(apath(app, r=request), filename[:-3]) |
---|
1395 | session.flash = T('language file "%(filename)s" created/updated', |
---|
1396 | dict(filename=filename)) |
---|
1397 | redirect(request.vars.sender + anchor) |
---|
1398 | |
---|
1399 | elif path[-8:] == '/models/': |
---|
1400 | # Handle python models |
---|
1401 | if not filename[-3:] == '.py': |
---|
1402 | filename += '.py' |
---|
1403 | |
---|
1404 | if len(filename) == 3: |
---|
1405 | raise SyntaxError |
---|
1406 | |
---|
1407 | text = '# -*- coding: utf-8 -*-\n' |
---|
1408 | |
---|
1409 | elif path[-13:] == '/controllers/': |
---|
1410 | # Handle python controllers |
---|
1411 | if not filename[-3:] == '.py': |
---|
1412 | filename += '.py' |
---|
1413 | |
---|
1414 | if len(filename) == 3: |
---|
1415 | raise SyntaxError |
---|
1416 | |
---|
1417 | text = '# -*- coding: utf-8 -*-\n# %s\ndef index(): return dict(message="hello from %s")' |
---|
1418 | text = text % (T('try something like'), filename) |
---|
1419 | |
---|
1420 | elif path[-7:] == '/views/': |
---|
1421 | if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): |
---|
1422 | filename = 'plugin_%s/%s' % (request.vars.plugin, filename) |
---|
1423 | # Handle template (html) views |
---|
1424 | if filename.find('.') < 0: |
---|
1425 | filename += '.html' |
---|
1426 | extension = filename.split('.')[-1].lower() |
---|
1427 | |
---|
1428 | if len(filename) == 5: |
---|
1429 | raise SyntaxError |
---|
1430 | |
---|
1431 | msg = T( |
---|
1432 | 'This is the %(filename)s template', dict(filename=filename)) |
---|
1433 | if extension == 'html': |
---|
1434 | text = dedent(""" |
---|
1435 | {{extend 'layout.html'}} |
---|
1436 | <h1>%s</h1> |
---|
1437 | {{=BEAUTIFY(response._vars)}}""" % msg)[1:] |
---|
1438 | else: |
---|
1439 | generic = os.path.join(path, 'generic.' + extension) |
---|
1440 | if os.path.exists(generic): |
---|
1441 | text = read_file(generic) |
---|
1442 | else: |
---|
1443 | text = '' |
---|
1444 | |
---|
1445 | elif path[-9:] == '/modules/': |
---|
1446 | if request.vars.plugin and not filename.startswith('plugin_%s/' % request.vars.plugin): |
---|
1447 | filename = 'plugin_%s/%s' % (request.vars.plugin, filename) |
---|
1448 | # Handle python module files |
---|
1449 | if not filename[-3:] == '.py': |
---|
1450 | filename += '.py' |
---|
1451 | |
---|
1452 | if len(filename) == 3: |
---|
1453 | raise SyntaxError |
---|
1454 | |
---|
1455 | text = dedent(""" |
---|
1456 | #!/usr/bin/env python |
---|
1457 | # -*- coding: utf-8 -*- |
---|
1458 | from gluon import *\n""")[1:] |
---|
1459 | |
---|
1460 | elif (path[-8:] == '/static/') or (path[-9:] == '/private/'): |
---|
1461 | if (request.vars.plugin and |
---|
1462 | not filename.startswith('plugin_%s/' % request.vars.plugin)): |
---|
1463 | filename = 'plugin_%s/%s' % (request.vars.plugin, filename) |
---|
1464 | text = '' |
---|
1465 | |
---|
1466 | else: |
---|
1467 | redirect(request.vars.sender + anchor) |
---|
1468 | |
---|
1469 | full_filename = os.path.join(path, filename) |
---|
1470 | dirpath = os.path.dirname(full_filename) |
---|
1471 | |
---|
1472 | if not os.path.exists(dirpath): |
---|
1473 | os.makedirs(dirpath) |
---|
1474 | |
---|
1475 | if os.path.exists(full_filename): |
---|
1476 | raise SyntaxError |
---|
1477 | |
---|
1478 | safe_write(full_filename, text) |
---|
1479 | log_progress(app, 'CREATE', filename) |
---|
1480 | if request.vars.dir: |
---|
1481 | result = T('file "%(filename)s" created', |
---|
1482 | dict(filename=full_filename[len(path):])) |
---|
1483 | else: |
---|
1484 | session.flash = T('file "%(filename)s" created', |
---|
1485 | dict(filename=full_filename[len(path):])) |
---|
1486 | vars = {} |
---|
1487 | if request.vars.id: |
---|
1488 | vars['id'] = request.vars.id |
---|
1489 | if request.vars.app: |
---|
1490 | vars['app'] = request.vars.app |
---|
1491 | redirect(URL('edit', |
---|
1492 | args=[os.path.join(request.vars.location, filename)], vars=vars)) |
---|
1493 | |
---|
1494 | except Exception as e: |
---|
1495 | if not isinstance(e, HTTP): |
---|
1496 | session.flash = T('cannot create file') |
---|
1497 | |
---|
1498 | if request.vars.dir: |
---|
1499 | response.flash = result |
---|
1500 | response.headers['web2py-component-content'] = 'append' |
---|
1501 | response.headers['web2py-component-command'] = "%s %s %s" % ( |
---|
1502 | "$.web2py.invalidate('#files_menu');", |
---|
1503 | "load_file('%s');" % URL('edit', args=[app, request.vars.dir, filename]), |
---|
1504 | "$.web2py.enableElement($('#form form').find($.web2py.formInputClickSelector));") |
---|
1505 | return '' |
---|
1506 | else: |
---|
1507 | redirect(request.vars.sender + anchor) |
---|
1508 | |
---|
1509 | |
---|
1510 | def listfiles(app, dir, regexp='.*\.py$'): |
---|
1511 | files = sorted( |
---|
1512 | listdir(apath('%(app)s/%(dir)s/' % {'app': app, 'dir': dir}, r=request), regexp)) |
---|
1513 | files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')] |
---|
1514 | return files |
---|
1515 | |
---|
1516 | |
---|
1517 | def editfile(path, file, vars={}, app=None): |
---|
1518 | args = (path, file) if 'app' in vars else (app, path, file) |
---|
1519 | url = URL('edit', args=args, vars=vars) |
---|
1520 | return A(file, _class='editor_filelink', _href=url, _style='word-wrap: nowrap;') |
---|
1521 | |
---|
1522 | |
---|
1523 | def files_menu(): |
---|
1524 | app = request.vars.app or 'welcome' |
---|
1525 | dirs = [{'name': 'models', 'reg': '.*\.py$'}, |
---|
1526 | {'name': 'controllers', 'reg': '.*\.py$'}, |
---|
1527 | {'name': 'views', 'reg': '[\w/\-]+(\.\w+)+$'}, |
---|
1528 | {'name': 'modules', 'reg': '.*\.py$'}, |
---|
1529 | {'name': 'static', 'reg': '[^\.#].*'}, |
---|
1530 | {'name': 'private', 'reg': '.*\.py$'}] |
---|
1531 | result_files = [] |
---|
1532 | for dir in dirs: |
---|
1533 | result_files.append(TAG[''](LI(dir['name'], _class="nav-header component", _onclick="collapse('" + dir['name'] + "_files');"), |
---|
1534 | LI(UL(*[LI(editfile(dir['name'], f, dict(id=dir['name'] + f.replace('.', '__')), app), _style="overflow:hidden", _id=dir['name'] + "__" + f.replace('.', '__')) |
---|
1535 | for f in listfiles(app, dir['name'], regexp=dir['reg'])], |
---|
1536 | _class="nav nav-list small-font"), |
---|
1537 | _id=dir['name'] + '_files', _style="display: none;"))) |
---|
1538 | return dict(result_files=result_files) |
---|
1539 | |
---|
1540 | |
---|
1541 | def upload_file(): |
---|
1542 | """ File uploading handler """ |
---|
1543 | if request.vars and not request.vars.token == session.token: |
---|
1544 | redirect(URL('logout')) |
---|
1545 | try: |
---|
1546 | filename = None |
---|
1547 | app = get_app(name=request.vars.location.split('/')[0]) |
---|
1548 | path = apath(request.vars.location, r=request) |
---|
1549 | |
---|
1550 | if request.vars.filename: |
---|
1551 | filename = re.sub('[^\w\./]+', '_', request.vars.filename) |
---|
1552 | else: |
---|
1553 | filename = os.path.split(request.vars.file.filename)[-1] |
---|
1554 | |
---|
1555 | if path[-8:] == '/models/' and not filename[-3:] == '.py': |
---|
1556 | filename += '.py' |
---|
1557 | |
---|
1558 | if path[-9:] == '/modules/' and not filename[-3:] == '.py': |
---|
1559 | filename += '.py' |
---|
1560 | |
---|
1561 | if path[-13:] == '/controllers/' and not filename[-3:] == '.py': |
---|
1562 | filename += '.py' |
---|
1563 | |
---|
1564 | if path[-7:] == '/views/' and not filename[-5:] == '.html': |
---|
1565 | filename += '.html' |
---|
1566 | |
---|
1567 | if path[-11:] == '/languages/' and not filename[-3:] == '.py': |
---|
1568 | filename += '.py' |
---|
1569 | |
---|
1570 | filename = os.path.join(path, filename) |
---|
1571 | dirpath = os.path.dirname(filename) |
---|
1572 | |
---|
1573 | if not os.path.exists(dirpath): |
---|
1574 | os.makedirs(dirpath) |
---|
1575 | |
---|
1576 | data = request.vars.file.file.read() |
---|
1577 | lineno = count_lines(data) |
---|
1578 | safe_write(filename, data, 'wb') |
---|
1579 | log_progress(app, 'UPLOAD', filename, lineno) |
---|
1580 | session.flash = T('file "%(filename)s" uploaded', |
---|
1581 | dict(filename=filename[len(path):])) |
---|
1582 | except Exception: |
---|
1583 | if filename: |
---|
1584 | d = dict(filename=filename[len(path):]) |
---|
1585 | else: |
---|
1586 | d = dict(filename='unknown') |
---|
1587 | session.flash = T('cannot upload file "%(filename)s"', d) |
---|
1588 | |
---|
1589 | redirect(request.vars.sender) |
---|
1590 | |
---|
1591 | |
---|
1592 | def errors(): |
---|
1593 | """ Error handler """ |
---|
1594 | import operator |
---|
1595 | import os |
---|
1596 | import hashlib |
---|
1597 | |
---|
1598 | app = get_app() |
---|
1599 | if is_gae: |
---|
1600 | method = 'dbold' if ('old' in |
---|
1601 | (request.args(1) or '')) else 'dbnew' |
---|
1602 | else: |
---|
1603 | method = request.args(1) or 'new' |
---|
1604 | db_ready = {} |
---|
1605 | db_ready['status'] = get_ticket_storage(app) |
---|
1606 | db_ready['errmessage'] = T( |
---|
1607 | "No ticket_storage.txt found under /private folder") |
---|
1608 | db_ready['errlink'] = "http://web2py.com/books/default/chapter/29/13#Collecting-tickets" |
---|
1609 | |
---|
1610 | if method == 'new': |
---|
1611 | errors_path = apath('%s/errors' % app, r=request) |
---|
1612 | |
---|
1613 | delete_hashes = [] |
---|
1614 | for item in request.vars: |
---|
1615 | if item[:7] == 'delete_': |
---|
1616 | delete_hashes.append(item[7:]) |
---|
1617 | |
---|
1618 | hash2error = dict() |
---|
1619 | |
---|
1620 | for fn in listdir(errors_path, '^[a-fA-F0-9.\-]+$'): |
---|
1621 | fullpath = os.path.join(errors_path, fn) |
---|
1622 | if not os.path.isfile(fullpath): |
---|
1623 | continue |
---|
1624 | try: |
---|
1625 | fullpath_file = safe_open(fullpath, 'rb') |
---|
1626 | try: |
---|
1627 | error = pickle.load(fullpath_file) |
---|
1628 | finally: |
---|
1629 | fullpath_file.close() |
---|
1630 | except IOError: |
---|
1631 | continue |
---|
1632 | except EOFError: |
---|
1633 | continue |
---|
1634 | |
---|
1635 | hash = hashlib.md5(to_bytes(error['traceback'])).hexdigest() |
---|
1636 | |
---|
1637 | if hash in delete_hashes: |
---|
1638 | os.unlink(fullpath) |
---|
1639 | else: |
---|
1640 | try: |
---|
1641 | hash2error[hash]['count'] += 1 |
---|
1642 | except KeyError: |
---|
1643 | error_lines = error['traceback'].split("\n") |
---|
1644 | last_line = error_lines[-2] if len(error_lines) > 1 else 'unknown' |
---|
1645 | error_causer = os.path.split(error['layer'])[1] |
---|
1646 | hash2error[hash] = dict(count=1, pickel=error, |
---|
1647 | causer=error_causer, |
---|
1648 | last_line=last_line, |
---|
1649 | hash=hash, ticket=fn) |
---|
1650 | |
---|
1651 | decorated = [(x['count'], x) for x in hash2error.values()] |
---|
1652 | decorated.sort(key=operator.itemgetter(0), reverse=True) |
---|
1653 | |
---|
1654 | return dict(errors=[x[1] for x in decorated], app=app, method=method, db_ready=db_ready) |
---|
1655 | |
---|
1656 | elif method == 'dbnew': |
---|
1657 | errors_path = apath('%s/errors' % app, r=request) |
---|
1658 | tk_db, tk_table = get_ticket_storage(app) |
---|
1659 | |
---|
1660 | delete_hashes = [] |
---|
1661 | for item in request.vars: |
---|
1662 | if item[:7] == 'delete_': |
---|
1663 | delete_hashes.append(item[7:]) |
---|
1664 | |
---|
1665 | hash2error = dict() |
---|
1666 | |
---|
1667 | for fn in tk_db(tk_table.id > 0).select(): |
---|
1668 | try: |
---|
1669 | error = pickle.loads(fn.ticket_data) |
---|
1670 | hash = hashlib.md5(error['traceback']).hexdigest() |
---|
1671 | |
---|
1672 | if hash in delete_hashes: |
---|
1673 | tk_db(tk_table.id == fn.id).delete() |
---|
1674 | tk_db.commit() |
---|
1675 | else: |
---|
1676 | try: |
---|
1677 | hash2error[hash]['count'] += 1 |
---|
1678 | except KeyError: |
---|
1679 | error_lines = error['traceback'].split("\n") |
---|
1680 | last_line = error_lines[-2] |
---|
1681 | error_causer = os.path.split(error['layer'])[1] |
---|
1682 | hash2error[hash] = dict(count=1, |
---|
1683 | pickel=error, causer=error_causer, |
---|
1684 | last_line=last_line, hash=hash, |
---|
1685 | ticket=fn.ticket_id) |
---|
1686 | except AttributeError as e: |
---|
1687 | tk_db(tk_table.id == fn.id).delete() |
---|
1688 | tk_db.commit() |
---|
1689 | |
---|
1690 | decorated = [(x['count'], x) for x in hash2error.values()] |
---|
1691 | decorated.sort(key=operator.itemgetter(0), reverse=True) |
---|
1692 | return dict(errors=[x[1] for x in decorated], app=app, |
---|
1693 | method=method, db_ready=db_ready) |
---|
1694 | |
---|
1695 | elif method == 'dbold': |
---|
1696 | tk_db, tk_table = get_ticket_storage(app) |
---|
1697 | for item in request.vars: |
---|
1698 | if item[:7] == 'delete_': |
---|
1699 | tk_db(tk_table.ticket_id == item[7:]).delete() |
---|
1700 | tk_db.commit() |
---|
1701 | tickets_ = tk_db(tk_table.id > 0).select(tk_table.ticket_id, |
---|
1702 | tk_table.created_datetime, |
---|
1703 | orderby=~tk_table.created_datetime) |
---|
1704 | tickets = [row.ticket_id for row in tickets_] |
---|
1705 | times = dict([(row.ticket_id, row.created_datetime) for |
---|
1706 | row in tickets_]) |
---|
1707 | return dict(app=app, tickets=tickets, method=method, |
---|
1708 | times=times, db_ready=db_ready) |
---|
1709 | |
---|
1710 | else: |
---|
1711 | for item in request.vars: |
---|
1712 | # delete_all rows doesn't contain any ticket |
---|
1713 | # Remove anything else as requested |
---|
1714 | if item[:7] == 'delete_' and (not item == "delete_all}"): |
---|
1715 | os.unlink(apath('%s/errors/%s' % (app, item[7:]), r=request)) |
---|
1716 | func = lambda p: os.stat(apath('%s/errors/%s' % |
---|
1717 | (app, p), r=request)).st_mtime |
---|
1718 | tickets = sorted( |
---|
1719 | listdir(apath('%s/errors/' % app, r=request), '^\w.*'), |
---|
1720 | key=func, |
---|
1721 | reverse=True) |
---|
1722 | |
---|
1723 | return dict(app=app, tickets=tickets, method=method, db_ready=db_ready) |
---|
1724 | |
---|
1725 | |
---|
1726 | def get_ticket_storage(app): |
---|
1727 | private_folder = apath('%s/private' % app, r=request) |
---|
1728 | ticket_file = os.path.join(private_folder, 'ticket_storage.txt') |
---|
1729 | if os.path.exists(ticket_file): |
---|
1730 | db_string = safe_open(ticket_file).read() |
---|
1731 | db_string = db_string.strip().replace('\r', '').replace('\n', '') |
---|
1732 | elif is_gae: |
---|
1733 | # use Datastore as fallback if there is no ticket_file |
---|
1734 | db_string = "google:datastore" |
---|
1735 | else: |
---|
1736 | return False |
---|
1737 | tickets_table = 'web2py_ticket' |
---|
1738 | tablename = tickets_table + '_' + app |
---|
1739 | db_path = apath('%s/databases' % app, r=request) |
---|
1740 | ticketsdb = DAL(db_string, folder=db_path, auto_import=True) |
---|
1741 | if not ticketsdb.get(tablename): |
---|
1742 | table = ticketsdb.define_table( |
---|
1743 | tablename, |
---|
1744 | Field('ticket_id', length=100), |
---|
1745 | Field('ticket_data', 'text'), |
---|
1746 | Field('created_datetime', 'datetime'), |
---|
1747 | ) |
---|
1748 | return ticketsdb, ticketsdb.get(tablename) |
---|
1749 | |
---|
1750 | |
---|
1751 | def make_link(path): |
---|
1752 | """ Create a link from a path """ |
---|
1753 | tryFile = path.replace('\\', '/') |
---|
1754 | |
---|
1755 | if os.path.isabs(tryFile) and os.path.isfile(tryFile): |
---|
1756 | (folder, filename) = os.path.split(tryFile) |
---|
1757 | (base, ext) = os.path.splitext(filename) |
---|
1758 | app = get_app() |
---|
1759 | |
---|
1760 | editable = {'controllers': '.py', 'models': '.py', 'views': '.html'} |
---|
1761 | for key in editable.keys(): |
---|
1762 | check_extension = folder.endswith("%s/%s" % (app, key)) |
---|
1763 | if ext.lower() == editable[key] and check_extension: |
---|
1764 | return to_native(A('"' + tryFile + '"', |
---|
1765 | _href=URL(r=request, |
---|
1766 | f='edit/%s/%s/%s' % (app, key, filename))).xml()) |
---|
1767 | return '' |
---|
1768 | |
---|
1769 | |
---|
1770 | def make_links(traceback): |
---|
1771 | """ Make links using the given traceback """ |
---|
1772 | |
---|
1773 | lwords = traceback.split('"') |
---|
1774 | |
---|
1775 | # Making the short circuit compatible with <= python2.4 |
---|
1776 | result = (len(lwords) != 0) and lwords[0] or '' |
---|
1777 | |
---|
1778 | i = 1 |
---|
1779 | |
---|
1780 | while i < len(lwords): |
---|
1781 | link = make_link(lwords[i]) |
---|
1782 | |
---|
1783 | if link == '': |
---|
1784 | result += '"' + lwords[i] |
---|
1785 | else: |
---|
1786 | result += link |
---|
1787 | |
---|
1788 | if i + 1 < len(lwords): |
---|
1789 | result += lwords[i + 1] |
---|
1790 | i = i + 1 |
---|
1791 | |
---|
1792 | i = i + 1 |
---|
1793 | |
---|
1794 | return result |
---|
1795 | |
---|
1796 | |
---|
1797 | class TRACEBACK(object): |
---|
1798 | """ Generate the traceback """ |
---|
1799 | |
---|
1800 | def __init__(self, text): |
---|
1801 | """ TRACEBACK constructor """ |
---|
1802 | |
---|
1803 | self.s = make_links(CODE(text).xml()) |
---|
1804 | |
---|
1805 | def xml(self): |
---|
1806 | """ Returns the xml """ |
---|
1807 | |
---|
1808 | return self.s |
---|
1809 | |
---|
1810 | |
---|
1811 | def ticket(): |
---|
1812 | """ Ticket handler """ |
---|
1813 | |
---|
1814 | if len(request.args) != 2: |
---|
1815 | session.flash = T('invalid ticket') |
---|
1816 | redirect(URL('site')) |
---|
1817 | |
---|
1818 | app = get_app() |
---|
1819 | myversion = request.env.web2py_version |
---|
1820 | ticket = request.args[1] |
---|
1821 | e = RestrictedError() |
---|
1822 | e.load(request, app, ticket) |
---|
1823 | |
---|
1824 | return dict(app=app, |
---|
1825 | ticket=ticket, |
---|
1826 | output=e.output, |
---|
1827 | traceback=(e.traceback and TRACEBACK(e.traceback)), |
---|
1828 | snapshot=e.snapshot, |
---|
1829 | code=e.code, |
---|
1830 | layer=e.layer, |
---|
1831 | myversion=myversion) |
---|
1832 | |
---|
1833 | |
---|
1834 | def ticketdb(): |
---|
1835 | """ Ticket handler """ |
---|
1836 | |
---|
1837 | if len(request.args) != 2: |
---|
1838 | session.flash = T('invalid ticket') |
---|
1839 | redirect(URL('site')) |
---|
1840 | |
---|
1841 | app = get_app() |
---|
1842 | myversion = request.env.web2py_version |
---|
1843 | ticket = request.args[1] |
---|
1844 | e = RestrictedError() |
---|
1845 | request.tickets_db = get_ticket_storage(app)[0] |
---|
1846 | e.load(request, app, ticket) |
---|
1847 | response.view = 'default/ticket.html' |
---|
1848 | return dict(app=app, |
---|
1849 | ticket=ticket, |
---|
1850 | output=e.output, |
---|
1851 | traceback=(e.traceback and TRACEBACK(e.traceback)), |
---|
1852 | snapshot=e.snapshot, |
---|
1853 | code=e.code, |
---|
1854 | layer=e.layer, |
---|
1855 | myversion=myversion) |
---|
1856 | |
---|
1857 | |
---|
1858 | def error(): |
---|
1859 | """ Generate a ticket (for testing) """ |
---|
1860 | raise RuntimeError('admin ticket generator at your service') |
---|
1861 | |
---|
1862 | |
---|
1863 | def update_languages(): |
---|
1864 | """ Update available languages """ |
---|
1865 | |
---|
1866 | app = get_app() |
---|
1867 | update_all_languages(apath(app, r=request)) |
---|
1868 | session.flash = T('Language files (static strings) updated') |
---|
1869 | redirect(URL('design', args=app, anchor='languages')) |
---|
1870 | |
---|
1871 | |
---|
1872 | def user(): |
---|
1873 | if MULTI_USER_MODE: |
---|
1874 | if not db(db.auth_user).count(): |
---|
1875 | auth.settings.registration_requires_approval = False |
---|
1876 | return dict(form=auth()) |
---|
1877 | else: |
---|
1878 | return dict(form=T("Disabled")) |
---|
1879 | |
---|
1880 | |
---|
1881 | def reload_routes(): |
---|
1882 | """ Reload routes.py """ |
---|
1883 | gluon.rewrite.load() |
---|
1884 | redirect(URL('site')) |
---|
1885 | |
---|
1886 | |
---|
1887 | def manage_students(): |
---|
1888 | if not (MULTI_USER_MODE and is_manager()): |
---|
1889 | session.flash = T('Not Authorized') |
---|
1890 | redirect(URL('site')) |
---|
1891 | db.auth_user.registration_key.writable = True |
---|
1892 | grid = SQLFORM.grid(db.auth_user) |
---|
1893 | return locals() |
---|
1894 | |
---|
1895 | |
---|
1896 | def bulk_register(): |
---|
1897 | if not (MULTI_USER_MODE and is_manager()): |
---|
1898 | session.flash = T('Not Authorized') |
---|
1899 | redirect(URL('site')) |
---|
1900 | form = SQLFORM.factory(Field('emails', 'text')) |
---|
1901 | if form.process().accepted: |
---|
1902 | emails = [x.strip() for x in form.vars.emails.split('\n') if x.strip()] |
---|
1903 | n = 0 |
---|
1904 | for email in emails: |
---|
1905 | if not db.auth_user(email=email): |
---|
1906 | n += db.auth_user.insert(email=email) and 1 or 0 |
---|
1907 | session.flash = T('%s students registered', n) |
---|
1908 | redirect(URL('site')) |
---|
1909 | return locals() |
---|
1910 | |
---|
1911 | # Begin experimental stuff need fixes: |
---|
1912 | # 1) should run in its own process - cannot os.chdir |
---|
1913 | # 2) should not prompt user at console |
---|
1914 | # 3) should give option to force commit and not reuqire manual merge |
---|
1915 | |
---|
1916 | |
---|
1917 | def git_pull(): |
---|
1918 | """ Git Pull handler """ |
---|
1919 | app = get_app() |
---|
1920 | if not have_git: |
---|
1921 | session.flash = GIT_MISSING |
---|
1922 | redirect(URL('site')) |
---|
1923 | dialog = FORM.confirm(T('Pull'), |
---|
1924 | {T('Cancel'): URL('site')}) |
---|
1925 | if dialog.accepted: |
---|
1926 | try: |
---|
1927 | repo = git.Repo(os.path.join(apath(r=request), app)) |
---|
1928 | origin = repo.remotes.origin |
---|
1929 | origin.fetch() |
---|
1930 | origin.pull() |
---|
1931 | session.flash = T("Application updated via git pull") |
---|
1932 | redirect(URL('site')) |
---|
1933 | |
---|
1934 | except git.CheckoutError: |
---|
1935 | session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.") |
---|
1936 | redirect(URL('site')) |
---|
1937 | except git.UnmergedEntriesError: |
---|
1938 | session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.") |
---|
1939 | redirect(URL('site')) |
---|
1940 | except git.GitCommandError: |
---|
1941 | session.flash = T( |
---|
1942 | "Pull failed, git exited abnormally. See logs for details.") |
---|
1943 | redirect(URL('site')) |
---|
1944 | except AssertionError: |
---|
1945 | session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.") |
---|
1946 | redirect(URL('site')) |
---|
1947 | elif 'cancel' in request.vars: |
---|
1948 | redirect(URL('site')) |
---|
1949 | return dict(app=app, dialog=dialog) |
---|
1950 | |
---|
1951 | |
---|
1952 | def git_push(): |
---|
1953 | """ Git Push handler """ |
---|
1954 | app = get_app() |
---|
1955 | if not have_git: |
---|
1956 | session.flash = GIT_MISSING |
---|
1957 | redirect(URL('site')) |
---|
1958 | form = SQLFORM.factory(Field('changelog', requires=IS_NOT_EMPTY())) |
---|
1959 | form.element('input[type=submit]')['_value'] = T('Push') |
---|
1960 | form.add_button(T('Cancel'), URL('site')) |
---|
1961 | form.process() |
---|
1962 | if form.accepted: |
---|
1963 | try: |
---|
1964 | repo = git.Repo(os.path.join(apath(r=request), app)) |
---|
1965 | index = repo.index |
---|
1966 | index.add([apath(r=request) + app + '/*']) |
---|
1967 | new_commit = index.commit(form.vars.changelog) |
---|
1968 | origin = repo.remotes.origin |
---|
1969 | origin.push() |
---|
1970 | session.flash = T( |
---|
1971 | "Git repo updated with latest application changes.") |
---|
1972 | redirect(URL('site')) |
---|
1973 | except git.UnmergedEntriesError: |
---|
1974 | session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.") |
---|
1975 | redirect(URL('site')) |
---|
1976 | return dict(app=app, form=form) |
---|
1977 | |
---|
1978 | |
---|
1979 | def plugins(): |
---|
1980 | app = request.args(0) |
---|
1981 | from gluon.serializers import loads_json |
---|
1982 | if not session.plugins: |
---|
1983 | try: |
---|
1984 | rawlist = urlopen("http://www.web2pyslices.com/" + |
---|
1985 | "public/api.json/action/list/content/Package?package" + |
---|
1986 | "_type=plugin&search_index=false").read() |
---|
1987 | session.plugins = loads_json(rawlist) |
---|
1988 | except: |
---|
1989 | response.flash = T('Unable to download the list of plugins') |
---|
1990 | session.plugins = [] |
---|
1991 | return dict(plugins=session.plugins["results"], app=request.args(0)) |
---|
1992 | |
---|
1993 | |
---|
1994 | def install_plugin(): |
---|
1995 | app = request.args(0) |
---|
1996 | source = request.vars.source |
---|
1997 | plugin = request.vars.plugin |
---|
1998 | if not (source and app): |
---|
1999 | raise HTTP(500, T("Invalid request")) |
---|
2000 | # make sure no XSS attacks in source |
---|
2001 | if not source.lower().split('://')[0] in ('http','https'): |
---|
2002 | raise HTTP(500, T("Invalid request")) |
---|
2003 | form = SQLFORM.factory() |
---|
2004 | result = None |
---|
2005 | if form.process().accepted: |
---|
2006 | # get w2p plugin |
---|
2007 | if "web2py.plugin." in source: |
---|
2008 | filename = "web2py.plugin.%s.w2p" % \ |
---|
2009 | source.split("web2py.plugin.")[-1].split(".w2p")[0] |
---|
2010 | else: |
---|
2011 | filename = "web2py.plugin.%s.w2p" % cleanpath(plugin) |
---|
2012 | if plugin_install(app, urlopen(source), |
---|
2013 | request, filename): |
---|
2014 | session.flash = T('New plugin installed: %s', filename) |
---|
2015 | else: |
---|
2016 | session.flash = \ |
---|
2017 | T('unable to install plugin "%s"', filename) |
---|
2018 | redirect(URL(f="plugins", args=[app, ])) |
---|
2019 | return dict(form=form, app=app, plugin=plugin, source=source) |
---|