diff --git a/bootstrap/config.py b/bootstrap/config.py
index a343a9b..c2f6fb7 100644
--- a/bootstrap/config.py
+++ b/bootstrap/config.py
@@ -13,6 +13,7 @@ DATABASE_FILE = os.path.join(DATA_DIR, "db.sqlite")
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(DATABASE_FILE)
TEMPLATES_FOLDER = os.path.join(basepath, "webapp", "templates")
+STATIC_FOLDER = os.path.join(basepath, "webapp", "static")
MAKEDIRS = [
DATA_DIR, TASKS_DIR, TASKS_IN_DIR, TASKS_PROC_DIR, TASKS_BUF_DIR,
diff --git a/bootstrap/run.py b/bootstrap/run.py
index 3a1b730..44799f3 100644
--- a/bootstrap/run.py
+++ b/bootstrap/run.py
@@ -1,4 +1,4 @@
-from config import TEMPLATES_FOLDER
+from config import TEMPLATES_FOLDER, STATIC_FOLDER
from oshipka import init_db
from populate import populate_db
@@ -8,6 +8,7 @@ if init_db(app):
populate_db(app)
app.template_folder = TEMPLATES_FOLDER
+app.static_folder = STATIC_FOLDER
if __name__ == "__main__":
app.run(debug=True)
diff --git a/oshipka.sh b/oshipka.sh
index d998a95..65e195c 100755
--- a/oshipka.sh
+++ b/oshipka.sh
@@ -16,13 +16,24 @@ Usage $0 [ bootstrap | worker | web ]
"
worker () {
+ source venv/bin/activate
python worker.py
}
web () {
+ source venv/bin/activate
python run.py
}
+init_venv() {
+ virtualenv -p python3 venv
+}
+
+install_reqs() {
+ source venv/bin/activate
+ pip install -r requirements.txt
+}
+
bootstrap() {
shift
PROJECT_PATH=$1
@@ -52,6 +63,8 @@ bootstrap() {
echo "INFO: Bootstrapping project $PROJECT_NAME..."
mkdir -p ${PROJECT_PATH}
cp -r ${OSHIPKA_PATH}/bootstrap/* ${PROJECT_PATH}/
+ cp ${OSHIPKA_PATH}/bootstrap/.gitignore ${PROJECT_PATH}/.gitignore
+ init_venv
}
command_main() {
diff --git a/oshipka/webapp/templates/delete_instance.html b/oshipka/webapp/templates/delete_instance.html
new file mode 100644
index 0000000..0a16e3c
--- /dev/null
+++ b/oshipka/webapp/templates/delete_instance.html
@@ -0,0 +1,10 @@
+{% extends "layout.html" %}
+
+{% block content %}
+
Delete {{ model_name }}:{{ instance.uuid }} ?
+
+ Back
+{% endblock %}
\ No newline at end of file
diff --git a/oshipka/webapp/templates/list_instances.html b/oshipka/webapp/templates/list_instances.html
new file mode 100644
index 0000000..011fd7b
--- /dev/null
+++ b/oshipka/webapp/templates/list_instances.html
@@ -0,0 +1,16 @@
+{% extends "layout.html" %}
+
+{% block content %}
+ {{ model_name_pl }}:
+
+{% endblock %}
\ No newline at end of file
diff --git a/oshipka/webapp/views.py b/oshipka/webapp/views.py
new file mode 100644
index 0000000..013b7fc
--- /dev/null
+++ b/oshipka/webapp/views.py
@@ -0,0 +1,106 @@
+import inflect
+from flask import flash, render_template, redirect, request, url_for
+
+from oshipka.persistance import db
+
+
+def list_view(model_view, template):
+ def inner():
+ instances = model_view.model.query.all()
+ return render_template(template, instances=instances)
+
+ return inner
+
+
+def get_view(model_view, template):
+ def inner(uuid):
+ model = model_view.model
+ instance = model.query.filter_by(uuid=uuid).first()
+ if not instance:
+ flash("No {}:{}".format(model_view.model_name, uuid))
+ return redirect(request.referrer or url_for('home'))
+ return render_template(template, instance=instance)
+
+ return inner
+
+
+def serialize_form():
+ return dict(request.form)
+
+
+def update_view(model_view, template):
+ def inner(uuid):
+ model = model_view.model
+ instance = model.query.filter_by(uuid=uuid).first()
+ if not instance:
+ flash("No {}:{}".format(model_view.model_name, uuid))
+ return redirect(request.referrer or url_for('home'))
+ if request.method == "GET":
+ return render_template(template, instance=instance)
+ serialized_form = serialize_form()
+
+ _next = serialized_form.pop('_next') if '_next' in serialized_form else None
+ for k, v in serialized_form.items():
+ setattr(instance, k, v)
+ db.session.add(instance)
+ db.session.commit()
+ flash("Updated {}:{}".format(model_view.model_name, uuid))
+ return redirect(_next or request.referrer or url_for('home'))
+
+ return inner
+
+
+def delete_view(model_view):
+ def inner(uuid):
+ model = model_view.model
+ instance = model.query.filter_by(uuid=uuid).first()
+ if not instance:
+ flash("No {}:{}".format(model_view.model_name, uuid))
+ return redirect(request.referrer or url_for('home'))
+ if request.method == "GET":
+ return render_template("delete_instance.html", instance=instance,
+ model_name=model_view.model_name)
+
+ serialized_form = serialize_form()
+ _next = serialized_form.pop('_next') if '_next' in serialized_form else None
+ db.session.delete(instance)
+ db.session.commit()
+ flash("Deleted {}:{}".format(model_view.model_name, uuid))
+ return redirect(_next or request.referrer or url_for('home'))
+
+ return inner
+
+
+class ModelView(object):
+ def __init__(self, app, model):
+ self.app = app
+ self.model = model
+
+ p = inflect.engine()
+
+ self.model_name = model.__name__.lower()
+ self.model_name_pl = p.plural(self.model_name)
+
+ def register_list(self, list_template):
+ url = '/{}'.format(self.model_name_pl)
+ self.app.add_url_rule(url,
+ 'list_{}'.format(self.model_name),
+ list_view(self, list_template))
+
+ def register_get(self, retrieve_template):
+ url = '/{}/'.format(self.model_name_pl)
+ self.app.add_url_rule(url,
+ 'get_{}'.format(self.model_name),
+ get_view(self, retrieve_template))
+
+ def register_update(self, update_template):
+ url = '/{}//edit'.format(self.model_name_pl)
+ self.app.add_url_rule(url, methods=["GET", "POST"],
+ endpoint='update_{}'.format(self.model_name),
+ view_func=update_view(self, update_template))
+
+ def register_delete(self):
+ url = '/{}//delete'.format(self.model_name_pl)
+ self.app.add_url_rule(url, methods=["GET", "POST"],
+ endpoint='delete_{}'.format(self.model_name),
+ view_func=delete_view(self))
diff --git a/requirements.txt b/requirements.txt
index f2ef029..fb12e80 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,6 +3,7 @@ filelock==3.0.12
Flask==1.1.1
Flask-SQLAlchemy==2.4.1
Flask-Table==0.5.0
+inflect==4.1.0
itsdangerous==1.1.0
Jinja2==2.11.1
MarkupSafe==1.1.1