default.py 70 KB

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