Skip to content

Commit

Permalink
meged
Browse files Browse the repository at this point in the history
  • Loading branch information
mdipierro committed Nov 2, 2019
2 parents ed2e591 + 5fdc462 commit 582f42c
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 89 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ apps/*/uploads/*
apps/*/*.py[oc]
packages/
logs/
password.txt
117 changes: 67 additions & 50 deletions apps/_dashboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import base64
import os
import sys
import time
import shutil
import datetime
import zipfile
import subprocess
import io
Expand Down Expand Up @@ -30,7 +29,7 @@ class Logged(Fixture):
def __init__(self, session):
self.__prerequisites__ = [session]
self.session = session

def on_request(self):
user = self.session.get('user')
if not user or not user.get('id'):
Expand All @@ -42,7 +41,7 @@ def on_request(self):
@action('index')
@action.uses('index.html', session, T)
def index():
return dict(languages = dumps(T.local.language),
return dict(languages = dumps(T.local.language),
mode = MODE,
user_id=(session.get('user') or {}).get('id'))

Expand All @@ -62,7 +61,7 @@ def login():
if valid:
session['user'] = dict(id=1)
return dict(user=valid, mode=MODE)

@action('logout', method='POST')
@action.uses(session)
def logout():
Expand All @@ -74,7 +73,7 @@ def logout():
def dbadmin():
return dict(languages = dumps(T.local.language))

@action('info')
@action('info')
@session_secured
def info():
vars = [{'name':'python', 'version':sys.version}]
Expand All @@ -92,18 +91,18 @@ def info():
def routes():
"""Returns current registered routes"""
return {'payload':Reloader.ROUTES, 'status':'success'}


@action('apps')
@session_secured
def apps():
"""Returns a list of installed apps"""
apps = os.listdir(FOLDER)
apps = [{'name':app, 'error':Reloader.ERRORS.get(app)}
for app in apps
apps = [{'name':app, 'error':Reloader.ERRORS.get(app)}
for app in apps
if os.path.isdir(os.path.join(FOLDER, app)) and
not app.startswith('__') and
not app.startswith('.')]
not app.startswith('.')]
apps.sort(key=lambda item: item['name'])
return {'payload': apps, 'status':'success'}

Expand All @@ -117,8 +116,8 @@ def walk(path):
store = {}
for root, dirs, files in os.walk(top, topdown=False):
store[root] = {
'dirs':list(sorted([{'name':dir, 'content':store[os.path.join(root,dir)]}
for dir in dirs if dir[0]!='.' and dir[:2]!='__'],
'dirs':list(sorted([{'name':dir, 'content':store[os.path.join(root,dir)]}
for dir in dirs if dir[0]!='.' and dir[:2]!='__'],
key=lambda item: item['name'])),
'files':list(sorted([f for f in files if f[0]!='.' and f[-1]!='~' and f[-4:]!='.pyc']))
}
Expand All @@ -131,7 +130,7 @@ def load(path):
path = safe_join(FOLDER, path) or abort()
content = open(path,'rb').read().decode('utf8')
return {'payload':content, 'status':'success'}

@action('load_bytes/<path:path>')
@session_secured
def load_bytes(path):
Expand Down Expand Up @@ -180,29 +179,29 @@ def api(path):
args = path.split('/')
app_name = args[0]
from py4web.core import Reloader, DAL
from pydal.restapi import RestAPI, ALLOW_ALL_POLICY, DENY_ALL_POLICY
from pydal.restapi import RestAPI, ALLOW_ALL_POLICY, DENY_ALL_POLICY
policy = ALLOW_ALL_POLICY if MODE == 'full' else DENY_ALL_POLICY
module = Reloader.MODULES[app_name]
def url(*args): return request.url + '/' + '/'.join(args)
databases = [name for name in dir(module) if isinstance(getattr(module, name), DAL)]
if len(args) == 1:
def tables(name):
db = getattr(module, name)
return [{'name': t._tablename,
'fields': t.fields,
'link': url(name, t._tablename)+'?model=true'}
return [{'name': t._tablename,
'fields': t.fields,
'link': url(name, t._tablename)+'?model=true'}
for t in getattr(module, name)]
return {'databases': [{'name':name, 'tables': tables(name)} for name in databases]}
elif len(args) > 2 and args[1] in databases:
db = getattr(module, args[1])
id = args[3] if len(args) == 4 else None
id = args[3] if len(args) == 4 else None
data = RestAPI(db, policy)(request.method, args[2], id, request.query, request.json)
else:
data = {}
if 'code' in data:
response.status = data['code']
return data

if MODE == 'full':
@action('reload')
@session_secured
Expand All @@ -228,52 +227,70 @@ def delete(path):
recursive_unlink(fullpath)
return {'status':'success'}


def install_by_unzip_or_treecopy(source, source_dir, target_dir):
"""Installs an app by either unzipping it (if py4web installed from pip)
or by copying the directory tree (if installed from source)."""
if os.path.exists(source):
zfile = zipfile.ZipFile(source, 'r')
zfile.extractall(target_dir)
zfile.close()
else:
shutil.copytree(source_dir, target_dir)


def prepare_target_dir(form, target_dir):
"""Prepares the target directory for the new app.
If should_exist is False, leaves the directory blank."""
if form['mode'] == 'new':
if os.path.exists(target_dir):
abort(500) # already validated client side
elif form['mode'] == 'replace':
if os.path.exists(target_dir):
shutil.rmtree(target_dir)
else:
abort(500) # not a replacement


@action('new_app', method='POST')
@session_secured
def new_app():
form = request.json
target_dir = safe_join(FOLDER, form['name'])
if os.path.exists(target_dir):
if form['mode'] == 'new':
abort(500) # already validated client side
elif form['mode'] == 'replace':
shutil.rmtree(target_dir)
elif form['type'] != 'web' and not form['source'].endswith('.git'):
os.mkdir(target_dir)
# Directory for zipped assets
assets_dir = os.path.join(os.path.dirname(py4web.__file__), 'assets')
source = None
if form['type'] == 'minimal':
target_dir = safe_join(FOLDER, form['name'])
if form['type'] == 'minimal':
source = os.path.join(assets_dir,'py4web.app._minimal.zip')
source_dir = safe_join(FOLDER, '_minimal')
prepare_target_dir(form, target_dir)
install_by_unzip_or_treecopy(source, source_dir, target_dir)
elif form['type'] == 'scaffold':
source = os.path.join(assets_dir,'py4web.app._scaffold.zip')
source_dir = safe_join(FOLDER, '_scaffold')
prepare_target_dir(form, target_dir)
install_by_unzip_or_treecopy(source, source_dir, target_dir)
elif form['type'] == 'web':
prepare_target_dir(form, target_dir)
source = form['source']
if source.endswith('.zip'): # install from the web (zip file)
res = requests.get(source)
mem_zip = io.BytesIO(res.content)
zfile = zipfile.ZipFile(mem_zip, 'r')
zfile.extractall(target_dir)
zfile.close()
elif source.endswith('.git'): # clone from a git repo
if subprocess.call(['git', 'clone', source, form['name']]):
abort(500)
elif form['type'] == 'upload':
prepare_target_dir(form, target_dir)
source_stream = io.BytesIO(base64.b64decode(form['file']))
zfile = zipfile.ZipFile(source_stream, 'r')
zfile.extractall(target_dir)
zfile.close()
else:
abort(500)
# TODO catch and report better errors below
if form['type'] == 'upload':
zip = zipfile.ZipFile(source_stream, 'r')
zip.extractall(target_dir)
zip.close()
elif not '://' in source: # install from a local asset (zip) file
zip = zipfile.ZipFile(source, 'r')
zip.extractall(target_dir)
zip.close()
elif source.endswith('.zip'): # install from the web (zip file)
res = requests.get(source)
mem_zip = io.BytesIO(res.content)
zipfile.ZipFile(mem_zip, 'r')
zip.extractall(target_dir)
zip.close()
elif source.endswith('.git'): # clone from a git repo
if subprocess.call(['git', 'clone', source, form['name']]):
abort(500)
else:
abort(400)
return {'status':'success'}




13 changes: 6 additions & 7 deletions apps/_dashboard/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
<base href="[[=URL('static')]]/">
<link rel="shortcut icon" href="data:image/x-icon;base64,AAABAAEAAQEAAAEAIAAwAAAAFgAAACgAAAABAAAAAgAAAAEAIAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAA=="/>
<link rel="stylesheet" type="text/css" href="css/future.css"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css">
</head>
<body>
<body>
<a href="https://github.com/web2py/py4web"><img style="position: absolute; width:100px; top:0; right:0; border:0;" src="../static/images/forkme.png"/></a>
<div id="target" class="my-effects">
<div class="login" v-if="!user">
<h1>PY4WEB</h1>
<h2><i class="fa fa-lock fa-2x"></i></h2>
<form v-on:submit.prevent="login($event)">
<input v-model="password" type="password" placeholder="py4web password"/>
<input v-model="password" type="password" placeholder="py4web password"/>
</form>
[[if mode=='demo':]]<div style="margin-top:10px">Demo mode: any password will get you in</div>[[pass]]
</div>
Expand All @@ -39,7 +39,7 @@ <h2><i class="fa fa-lock fa-2x"></i></h2>
</div>
</div>
</div>
<div class="panel accordion" v-if="selected_app && selected_app.error">
<div class="panel accordion" v-if="selected_app && selected_app.error">
<input type="checkbox" id="traceback" checked>
<label for="traceback">Cause of failure for {{selected_app.name}}</label>
<div>
Expand Down Expand Up @@ -89,7 +89,7 @@ <h2><i class="fa fa-lock fa-2x"></i></h2>
<treefiles :f="walk" :p="selected_app.name"></treefiles>
</div>
</div>
<div v-show="selected_filename" class="panel accordion">
<div v-show="selected_filename" class="panel accordion">
<input type="checkbox" id="editor_container" checked>
<label for="editor_container">File: {{selected_filename}}</label>
<div>
Expand Down Expand Up @@ -174,7 +174,7 @@ <h2><i class="fa fa-lock fa-2x"></i></h2>
</div>
</div>
<div class="panel">
<button v-on:click="logout()">Logout</button>
<button v-on:click="logout()">Logout</button>
<div class="right">
Created by Massimo Di Pierro @ BSDv3 License
</div>
Expand Down Expand Up @@ -212,7 +212,6 @@ <h2>{{modal.title}}</h2>
<td style="padding-top: 20px">
<input type="radio" name="mode" v-model="modal.form.mode" value="new"/> New App
<input type="radio" name="mode" v-model="modal.form.mode" value="replace"/> Replace
<input type="radio" name="mode" v-model="modal.form.mode" value="overwite"/> Overwite
</td>
</tr>
</table>
Expand Down
36 changes: 18 additions & 18 deletions apps/_documentation/static/chapters/en/chapter-01.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

### Supported platforms and prerequisites

PY4WEB runs fine on Windows, MacOS and Linux. Its only prerequisite is Python 3, which must be installed in advance.
PY4WEB runs fine on Windows, MacOS and Linux. Its only prerequisite is Python 3, which must be installed in advance.

### Installing from pip

Expand All @@ -16,21 +16,21 @@
This will install py4web and all its dependencies. Once installed you can start it with:

``
py4web-start apps
py4web-start.py apps
``:bash

This should produce an output like:

``
██████╗ ██╗ ██╗██╗ ██╗██╗ ██╗███████╗██████╗
██████╗ ██╗ ██╗██╗ ██╗██╗ ██╗███████╗██████╗
██╔══██╗╚██╗ ██╔╝██║ ██║██║ ██║██╔════╝██╔══██╗
██████╔╝ ╚████╔╝ ███████║██║ █╗ ██║█████╗ ██████╔╝
██╔═══╝ ╚██╔╝ ╚════██║██║███╗██║██╔══╝ ██╔══██╗
██║ ██║ ██║╚███╔███╔╝███████╗██████╔╝
╚═╝ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝╚═════╝
╚═╝ ╚═╝ ╚═╝ ╚══╝╚══╝ ╚══════╝╚═════╝
Dashboard is at: http://127.0.0.1:8000/_dashboard
[X] loaded _dashboard
[X] loaded _default
[X] loaded _dashboard
[X] loaded _default
Bottle v0.12.16 server starting up (using TornadoServer())...
Listening on http://127.0.0.1:8000/
Hit Ctrl-C to quit.
Expand All @@ -48,7 +48,7 @@

``
http://localhost:8000
http://localhost:8000/_default
http://localhost:8000/_default
http://localhost:8000/_dashboard
http://localhost:8000/{yourappname}/index
``
Expand All @@ -69,7 +69,7 @@ Notice that ONLY the default app does not need a path prefix (``/_default`` is o
Once installed, you should start with

``
./py4web-start apps
./py4web-start.py apps
``:bash

Notice the ``./`` ; it forces the run of the local folder's py4web and not the installed one.
Expand All @@ -85,28 +85,28 @@ Notice that ONLY the default app does not need a path prefix (``/_default`` is o
and then ask py4web to re-use that password:
``
py4web-start -p password.txt apps
py4web-start.py -p password.txt apps
``:bash
### Command line options
py4web provides multiple command line options which can be listed with ``-h``.
``
usage: py4web-start [-h] [-a ADDRESS] [-n NUMBER_WORKERS]
[--ssl_cert_filename SSL_CERT_FILENAME]
[--ssl_key_filename SSL_KEY_FILENAME]
[--service_db_uri SERVICE_DB_URI] [-d DASHBOARD_MODE]
[-p PASSWORD_FILE] [-c]
apps_folder
usage: py4web-start.py [-h] [-a ADDRESS] [-n NUMBER_WORKERS]
[--ssl_cert_filename SSL_CERT_FILENAME]
[--ssl_key_filename SSL_KEY_FILENAME]
[--service_db_uri SERVICE_DB_URI] [-d DASHBOARD_MODE]
[-p PASSWORD_FILE] [-c]
apps_folder
positional arguments:
apps_folder path to the applications folder
optional arguments:
-h, --help show this help message and exit
-a ADDRESS, --address ADDRESS
serving address
--host HOST serving host
--port PORT serving port
-n NUMBER_WORKERS, --number_workers NUMBER_WORKERS
number of gunicorn workers
--ssl_cert_filename SSL_CERT_FILENAME
Expand Down Expand Up @@ -146,7 +146,7 @@ Copy the example files from py4web (assuming you have the source from github)
Makefile
apps
__init__.py
... your apps ...
... your apps ...
lib
app.yaml
main.py
Expand Down
Loading

0 comments on commit 582f42c

Please sign in to comment.