diff --git a/oshipka/persistance/__init__.py b/oshipka/persistance/__init__.py
index 9808895..5a2c618 100644
--- a/oshipka/persistance/__init__.py
+++ b/oshipka/persistance/__init__.py
@@ -23,7 +23,6 @@ from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy_utils import Choice
from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago
from whooshalchemy import IndexService
-from oshipka.search import add_to_index, remove_from_index, query_index
from oshipka.util.strings import camel_case_to_snake_case
db = SQLAlchemy()
@@ -221,11 +220,17 @@ def register_filters(app):
class Proxy(object):
def __init__(self, proxied):
self.proxied = proxied
+ self.searchables = []
index_service = Proxy(None)
+def register_index_svc():
+ for searchable in index_service.searchables:
+ index_service.proxied.register_class(searchable)
+
+
def init_db(app):
rv = False
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
@@ -253,6 +258,7 @@ def init_db(app):
rv = True
global index_service
index_service.proxied = IndexService(config=app.config, session=db.session)
+ register_index_svc()
return rv
@@ -280,46 +286,3 @@ def populate_static(app):
instance = model(**row)
db.session.add(instance)
db.session.commit()
-
-
-class SearchableMixin(object):
- @classmethod
- def search(cls, expression, page, per_page):
- ids, total = query_index(cls.__tablename__, expression, page, per_page)
- if total == 0:
- return cls.query.filter_by(id=0), 0
- when = []
- for i in range(len(ids)):
- when.append((ids[i], i))
- return cls.query.filter(cls.id.in_(ids)).order_by(
- db.case(when, value=cls.id)), total
-
- @classmethod
- def before_commit(cls, session):
- session._changes = {
- 'add': list(session.new),
- 'update': list(session.dirty),
- 'delete': list(session.deleted)
- }
-
- @classmethod
- def after_commit(cls, session):
- for obj in session._changes['add']:
- if isinstance(obj, SearchableMixin):
- add_to_index(obj.__tablename__, obj)
- for obj in session._changes['update']:
- if isinstance(obj, SearchableMixin):
- add_to_index(obj.__tablename__, obj)
- for obj in session._changes['delete']:
- if isinstance(obj, SearchableMixin):
- remove_from_index(obj.__tablename__, obj)
- session._changes = None
-
- @classmethod
- def reindex(cls):
- for obj in cls.query:
- add_to_index(cls.__tablename__, obj)
-
-
-db.event.listen(db.session, 'before_commit', SearchableMixin.before_commit)
-db.event.listen(db.session, 'after_commit', SearchableMixin.after_commit)
diff --git a/oshipka/search.py b/oshipka/search.py
deleted file mode 100644
index 9e564ce..0000000
--- a/oshipka/search.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from flask import current_app
-
-
-def add_to_index(index, model):
- if not hasattr(current_app, 'elasticsearch'):
- return
- payload = {}
- for field in model.__searchable__:
- payload[field] = getattr(model, field)
- current_app.elasticsearch.index(index=index, id=model.id, body=payload)
-
-
-def remove_from_index(index, model):
- if not hasattr(current_app, 'elasticsearch'):
- return
- current_app.elasticsearch.delete(index=index, id=model.id)
-
-
-def query_index(index, query, page, per_page):
- if not hasattr(current_app, 'elasticsearch'):
- return [], 0
- search = current_app.elasticsearch.search(
- index=index,
- body={'query': {'multi_match': {'query': query, 'fields': ['*']}},
- 'from': (page - 1) * per_page, 'size': per_page})
- ids = [int(hit['_id']) for hit in search['hits']['hits']]
- return ids, search['hits']['total']['value']
diff --git a/oshipka/webapp/templates/delete_instance.html b/oshipka/webapp/templates/delete_instance.html
index c29fb7b..8adee59 100644
--- a/oshipka/webapp/templates/delete_instance.html
+++ b/oshipka/webapp/templates/delete_instance.html
@@ -1,9 +1,9 @@
{% extends "layout.html" %}
{% block content %}
-
Delete {{ model_name }}:{{ instance.id }} ?
-
Back
diff --git a/oshipka/webapp/views.py b/oshipka/webapp/views.py
index 5248f70..551303a 100644
--- a/oshipka/webapp/views.py
+++ b/oshipka/webapp/views.py
@@ -1,185 +1,173 @@
from functools import wraps
-from uuid import uuid4
import inflect
-from flask import flash, render_template, redirect, request, url_for
+from flask import flash, render_template, redirect, request, url_for, jsonify
from oshipka.persistance import db
from oshipka.util.strings import camel_case_to_snake_case
-def get_instance(model_view, uuid):
- model = model_view.model
- if uuid.isdigit():
- instance = model.query.filter_by(id=uuid).first()
+def default_get_args_func(view_context):
+ view_context.serialized_args = request.args
+
+
+def default_get_form_func(vc):
+ vc.redirect_next = request.form.get('_next')
+ vc.serialized_args = dict(filter(lambda k: not k[0].startswith("_"), dict(request.form).items()))
+ to_delete = []
+ for key, value in vc.serialized_args.items():
+ if key.endswith('_id'):
+ if value in ['']:
+ to_delete.append(key)
+ else:
+ vc.serialized_args[key] = int(value)
+ for key in to_delete:
+ del vc.serialized_args[key]
+
+
+def default_jsonify_func(vc):
+ if type(vc.instances) is list:
+ return jsonify([instance.serialize() for instance in vc.instances])
+ return jsonify(vc.instances.serialize())
+
+
+def default_redirect_func(vc):
+ return redirect(vc.redirect_next or request.referrer or url_for('home'))
+
+
+def default_get_func(vc):
+ model = vc.model_view.model
+ uuid = vc.url_args.get('uuid')
+ if uuid and vc.url_args.get('uuid').isdigit():
+ vc.instances = model.query.filter_by(id=uuid).all()
else:
- instance = model.query.filter_by(uuid=uuid).first()
- if not instance:
- flash("No {}:{}".format(model_view.model_name, uuid))
- return instance
+ vc.instances = model.query.filter_by(uuid=uuid).all()
+ if not vc.instances:
+ flash("No {}:{}".format(vc.model_view.model_name, uuid))
-def list_view(model_view, template_func=None, template_ctx_func=None, args_process_func=None,
- list_func=None, post_list_func=None, **kwargs):
- def inner():
- serialized_args = request.args
- serialized_args = args_process_func(serialized_args) if args_process_func else serialized_args
- instances = None
- if list_func is not None:
- instances = list_func(model_view, serialized_args)
- if instances is None:
- instances = model_view.model.query.all()
- if post_list_func is not None:
- instances = post_list_func(instances)
- template = template_func(instances) if template_func else None
- if not template:
- template = "{}/list.html".format(model_view.model_name)
- template_ctx = template_ctx_func(instances) if template_ctx_func else {}
- return render_template(template, instances=instances, **template_ctx)
-
- return inner
+def default_list_args_get_func(vc):
+ vc.serialized_args = request.args
-def get_view(model_view, template_func=None, template_ctx_func=None, args_process_func=None,
- get_func=None, post_get_func=None, **kwargs):
- def inner(uuid):
- serialized_args = request.args
- serialized_args = args_process_func(serialized_args) if args_process_func else serialized_args
- instance = None
- if get_func is not None:
- instance = get_func(model_view, uuid, serialized_args)
- if instance is not None:
- instance = instance[0]
- if instance is None:
- instance = get_instance(model_view, uuid)
- if post_get_func is not None:
- instance = post_get_func(instance)
- template = template_func(instance) if template_func else None
- if not template:
- template = "{}/get.html".format(model_view.model_name)
- template_ctx = template_ctx_func(instance) if template_ctx_func else {}
- return render_template(template, instance=instance, **template_ctx)
-
- return inner
+def default_list_func(vc):
+ vc.instances = vc.model_view.model.query.all()
-def serialize_form():
- return dict(filter(lambda k: not k[0].startswith("__"), dict(request.form).items()))
+def default_search_func(vc):
+ q = vc.serialized_args.get('q')
+ if hasattr(vc.model_view.model, 'search_query'):
+ vc.instances = vc.model_view.model.search_query("{q}".format(q=q)).all()
-def update_view(model_view, template_func=None, template_ctx_func=None, args_process_func=None,
- should_update_func=None, post_add=None, post_commit=None, **kwargs):
- def inner(uuid):
- instance = get_instance(model_view, uuid)
- if not instance:
- return redirect(request.referrer or url_for('home'))
+def default_create_func(vc):
+ instance = vc.model_view.model(**vc.serialized_args)
+ db.session.add(instance)
+ vc.instances = [instance]
+
+
+def default_update_func(vc):
+ instance = vc.instances[0]
+ for k, v in vc.serialized_args.items():
+ setattr(instance, k, v)
+ db.session.add(instance)
+
+
+def default_delete_func(vc):
+ instance = vc.instances[0]
+ db.session.delete(instance)
+
+
+def default_render_func(vc):
+ if len(vc.instances) == 1:
+ vc.template_ctx['instance'] = vc.instances[0]
+ vc.template_ctx['instances'] = vc.instances
+ vc.template_ctx['model_view'] = vc.model_view
+ return render_template(vc.template, **vc.template_ctx)
+
+
+def default_commit_func(vc):
+ db.session.commit()
+
+
+def default_none_func(vc):
+ pass
+
+
+class ViewContext(object):
+ def __init__(self, args_get_func=None, args_process_func=None,
+ filter_func=None, redirect_func=None,
+ should_execute_func=None, execute_func=None, post_execute_func=None,
+ commit_func=None, post_commit_func=None,
+ jsonify_func=None, render_func=None, template_func=None, template_ctx_func=None,
+ should_redirect_no_instances_func=None,
+ should_redirect_at_end_func=None,
+ json=False, **kwargs):
+ self.args_get_func = args_get_func or default_get_args_func
+ self.args_process_func = args_process_func or default_none_func
+ self.filter_func = filter_func or default_none_func
+ self.should_redirect_no_instances_func = should_redirect_no_instances_func or default_none_func
+ self.redirect_func = redirect_func or default_redirect_func
+ self.should_execute_func = should_execute_func or default_none_func
+ self.execute_func = execute_func or default_none_func
+ self.post_execute_func = post_execute_func or default_none_func
+ self.commit_func = commit_func or default_commit_func
+ self.post_commit_func = post_commit_func or default_none_func
+ self.jsonify_func = jsonify_func or default_jsonify_func
+ self.render_func = render_func or default_render_func
+ self.template_func = template_func or default_none_func
+ self.template_ctx_func = template_ctx_func or default_none_func
+ self.should_redirect_at_end_func = should_redirect_at_end_func or default_none_func
+ self.json = json
+
+ self.serialized_args = {}
+ self.url_args = {}
+ self.instances = []
+ self.should_execute = True
+ self.should_redirect_at_end = True
+ self.template = None
+ self.template_ctx = {}
+ self.model_view = None
+ self.redirect_next = None
+
+
+def create_view(model_view, view_context, is_json=False, **kwargs):
+ view_context.model_view = model_view
+
+ def return_json_or_template():
+ if is_json:
+ return view_context.jsonify_func(view_context)
+ view_context.template_func(view_context)
+ view_context.template_ctx_func(view_context)
+ return view_context.render_func(view_context)
+
+ def inner(**kwargs):
+ view_context.url_args = kwargs
+ view_context.args_get_func(view_context)
+ view_context.args_process_func(view_context)
+
+ view_context.filter_func(view_context)
+ if not view_context.instances:
+ if view_context.should_redirect_no_instances_func(view_context):
+ return view_context.redirect_func(view_context)
+
if request.method == "GET":
- template = template_func(instance) if template_func else None
- if not template:
- template = "{}/edit.html".format(model_view.model_name)
- template_ctx = template_ctx_func(instance) if template_ctx_func else {}
- return render_template(template, instance=instance, **template_ctx)
- serialized_form = serialize_form()
+ return return_json_or_template()
- _next = serialized_form.pop('_next') if '_next' in serialized_form else None
- serialized_form = args_process_func(serialized_form) if args_process_func else serialized_form
- if should_update_func is not None:
- should_update, msg = should_update_func(instance, serialized_form)
- else:
- should_update, msg = True, ""
- if not should_update:
- if msg:
- flash(msg)
- return redirect(_next or request.referrer or url_for('home'))
- for k, v in serialized_form.items():
- setattr(instance, k, v)
+ view_context.should_execute_func(view_context)
+ if not view_context.should_execute:
+ return view_context.redirect_func(view_context)
- db.session.add(instance)
- if post_add is not None:
- post_add(instance)
+ view_context.execute_func(view_context)
+ view_context.post_execute_func(view_context)
+ view_context.commit_func(view_context)
+ view_context.post_commit_func(view_context)
- db.session.commit()
- if post_commit is not None:
- post_commit(instance)
- flash("Updated {}:{}".format(model_view.model_name, id))
- return redirect(_next or request.referrer or url_for('home'))
+ view_context.should_redirect_at_end_func(view_context)
+ if view_context.should_redirect_at_end:
+ return view_context.redirect_func(view_context)
- return inner
-
-
-def create_view(model_view, template_func=None, template_ctx_func=None, args_process_func=None,
- should_func=None, post_add=None, post_commit=None):
- def inner():
- if request.method == "GET":
- template = template_func() if template_func else None
- if not template:
- template = "{}/create.html".format(model_view.model_name)
- template_ctx = template_ctx_func() if template_ctx_func else {}
- return render_template(template, **template_ctx)
- serialized_form = serialize_form()
-
- _next = serialized_form.pop('_next') if '_next' in serialized_form else None
- serialized_form = args_process_func(serialized_form) if args_process_func else serialized_form
- serialized_form['uuid'] = str(uuid4())
-
- if should_func is not None:
- should_create, msg = should_func(serialized_form)
- else:
- should_create, msg = True, ""
- if not should_create:
- if msg:
- flash(msg)
- return redirect(_next or request.referrer or url_for('home'))
- instance = model_view.model(**serialized_form)
- db.session.add(instance)
- if post_add is not None:
- post_add(instance)
-
- db.session.commit()
- if post_commit is not None:
- post_commit(instance)
- flash("Created {}".format(model_view.model_name))
- return redirect(_next or request.referrer or url_for('home'))
-
- return inner
-
-
-def delete_view(model_view, template_func=None, template_ctx_func=None, args_process_func=None,
- should_func=None, post_delete=None, post_commit=None, **kwargs):
- def inner(uuid):
- instance = get_instance(model_view, uuid)
- if not instance:
- return redirect(request.referrer or url_for('home'))
- if request.method == "GET":
- template = template_func(instance) if template_func else None
- if not template:
- template = "delete_instance.html"
- template_ctx = template_ctx_func(instance) if template_ctx_func else {}
- return render_template(template,
- instance=instance,
- model_name=model_view.model_name,
- **template_ctx)
-
- serialized_form = serialize_form()
- serialized_form = args_process_func(serialized_form) if args_process_func else serialized_form
- _next = serialized_form.pop('_next') if '_next' in serialized_form else None
- if should_func is not None:
- should_delete, msg = should_func(instance, serialized_form)
- else:
- should_delete, msg = True, ""
- if not should_delete:
- if msg:
- flash(msg)
- return redirect(_next or request.referrer or url_for('home'))
- db.session.delete(instance)
- if post_delete is not None:
- post_delete(instance)
-
- db.session.commit()
- if post_commit is not None:
- post_commit(instance)
- flash("Deleted {}:{}".format(model_view.model_name, uuid))
- return redirect(_next or request.referrer or url_for('home'))
+ return return_json_or_template()
return inner
@@ -194,36 +182,71 @@ class ModelView(object):
self.model_name = camel_case_to_snake_case(model.__name__)
self.model_name_pl = p.plural(self.model_name)
- def register_create(self, **kwargs):
- url = '/{}/create'.format(self.model_name_pl)
- self.app.add_url_rule(url, methods=["GET", "POST"],
- endpoint='create_{}'.format(self.model_name),
- view_func=create_view(self, **kwargs))
-
- def register_list(self, **kwargs):
- url = '/{}'.format(self.model_name_pl)
- self.app.add_url_rule(url,
- 'list_{}'.format(self.model_name),
- list_view(self, **kwargs))
+ def _register_rule(self, url_args, **kwargs):
+ url = url_args.pop('rule')
+ api_url = '/api{}'.format(url)
+ endpoint = url_args.pop('endpoint')
+ api_endpoint = 'api_{}'.format(endpoint)
+ view_func = url_args.pop('view_func')
+ self.app.add_url_rule(rule=url, endpoint=endpoint,
+ view_func=view_func(self, **kwargs), **url_args)
+ kwargs['is_json'] = True
+ self.app.add_url_rule(rule=api_url, endpoint=api_endpoint,
+ view_func=view_func(self, **kwargs), **url_args)
def register_get(self, **kwargs):
- url = '/{}/'.format(self.model_name_pl)
+ url_args = dict(
+ rule='/{}//get'.format(self.model_name_pl),
+ methods=["GET"],
+ endpoint='get_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
- self.app.add_url_rule(url,
- 'get_{}'.format(self.model_name),
- get_view(self, **kwargs))
+ def register_list(self, **kwargs):
+ url_args = dict(
+ rule='/{}/list'.format(self.model_name_pl),
+ methods=["GET"],
+ endpoint='list_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
+
+ def register_create(self, **kwargs):
+ url_args = dict(
+ rule='/{}/create'.format(self.model_name_pl),
+ methods=["GET", "POST"],
+ endpoint='create_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
def register_update(self, **kwargs):
- 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, **kwargs))
+ url_args = dict(
+ rule='/{}//update'.format(self.model_name_pl),
+ methods=["GET", "POST"],
+ endpoint='update_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
def register_delete(self, **kwargs):
- 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, **kwargs))
+ url_args = dict(
+ rule='/{}//delete'.format(self.model_name_pl),
+ methods=["GET", "POST"],
+ endpoint='delete_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
+
+ def register_search(self, **kwargs):
+ url_args = dict(
+ rule='/{}/search'.format(self.model_name_pl),
+ methods=["GET"],
+ endpoint='search_{}'.format(self.model_name),
+ view_func=create_view,
+ )
+ self._register_rule(url_args, **kwargs)
def catch_flash(f):
diff --git a/vm_gen/templates/_hooks.py b/vm_gen/templates/_hooks.py
index a3e24fc..09a6e32 100644
--- a/vm_gen/templates/_hooks.py
+++ b/vm_gen/templates/_hooks.py
@@ -1,109 +1,62 @@
-def get_template_func(instance):
- return None
+from oshipka.webapp.views import ViewContext, default_get_args_func, default_get_func, default_list_func, \
+ default_get_form_func, default_create_func, default_update_func, default_delete_func, default_search_func
-def get_templ_ctx_func(instance):
- rv = dict()
- return rv
+def get_template(vc):
+ vc.template = "{}/get.html".format(vc.model_view.model_name)
-def get_func(model_view, uuid, serialized_args):
- """Should return a list of one element (or [None] if element is none) or None if you want default behaviour"""
- return None
+def search_template(vc):
+ vc.template = "{}/search.html".format(vc.model_view.model_name)
-def post_get_func(instance):
- return instance
+def list_template(vc):
+ vc.template = "{}/list.html".format(vc.model_view.model_name)
-def list_template_func(instances):
- return None
+def create_template(vc):
+ vc.template = "{}/create.html".format(vc.model_view.model_name)
-def list_templ_ctx_func(instances):
- rv = dict()
- return rv
+def update_template(vc):
+ vc.template = "{}/update.html".format(vc.model_view.model_name)
-def list_func(model_view, serialized_args):
- """Should return a list elements or None if you want default behaviour"""
- return None
+def delete_template(vc):
+ vc.template = "delete_instance.html".format(vc.model_view.model_name)
-def post_list_func(instances):
- return instances
+get_view_context = ViewContext(
+ filter_func=default_get_func,
+ template_func=get_template,
+)
+list_view_context = ViewContext(
+ filter_func=default_list_func,
+ template_func=list_template,
+)
-def create_template_func():
- return None
+search_view_context = ViewContext(
+ filter_func=default_search_func,
+ template_func=list_template,
+)
+create_view_context = ViewContext(
+ args_get_func=default_get_form_func,
+ template_func=create_template,
+ execute_func=default_create_func,
+)
-def create_templ_ctx_func():
- rv = dict()
- return rv
+update_view_context = ViewContext(
+ args_get_func=default_get_form_func,
+ filter_func=default_get_func,
+ template_func=update_template,
+ execute_func=default_update_func,
+)
-
-def create_args_process_func(serialized_form):
- return serialized_form
-
-
-def create_should_func(serialized_form):
- return True, ""
-
-
-def create_post_add_func(instance):
- pass
-
-
-def create_post_commit_func(instance):
- pass
-
-
-def update_template_func(instance):
- return None
-
-
-def update_templ_ctx_func(instance):
- rv = dict()
- return rv
-
-
-def update_args_process_func(serialized_form):
- return serialized_form
-
-
-def update_should_func(serialized_form):
- return True, ""
-
-
-def update_post_add_func(instance):
- pass
-
-
-def update_post_commit_func(instance):
- pass
-
-
-def delete_template_func(instance):
- return None
-
-
-def delete_templ_ctx_func(instance):
- rv = dict()
- return rv
-
-
-def delete_args_process_func(serialized_form):
- return serialized_form
-
-
-def delete_should_func(instance, serialized_form):
- return True, ""
-
-
-def delete_post_delete_func(instance):
- pass
-
-
-def delete_post_commit_func(instance):
- pass
+delete_view_context = ViewContext(
+ args_get_func=default_get_form_func,
+ filter_func=default_get_func,
+ template_func=delete_template,
+ execute_func=default_delete_func,
+)
diff --git a/vm_gen/templates/html/_create.html b/vm_gen/templates/html/_create.html
index 063300e..9df14f0 100644
--- a/vm_gen/templates/html/_create.html
+++ b/vm_gen/templates/html/_create.html
@@ -1,11 +1,16 @@
-Create [[ name ]]
\ No newline at end of file
diff --git a/vm_gen/templates/html/_get.html b/vm_gen/templates/html/_get.html
index 10edc07..e69de29 100644
--- a/vm_gen/templates/html/_get.html
+++ b/vm_gen/templates/html/_get.html
@@ -1,3 +0,0 @@
-{% include "[[ name|camel_to_snake ]]/_title.html" %}
-edit |
-delete
\ No newline at end of file
diff --git a/vm_gen/templates/html/_list.html b/vm_gen/templates/html/_list.html
index 7d0c018..70a5b4f 100644
--- a/vm_gen/templates/html/_list.html
+++ b/vm_gen/templates/html/_list.html
@@ -1,6 +1,3 @@
-[[ name|pluralize ]]
-Create
-
{% for instance in instances %}
diff --git a/vm_gen/templates/html/_search.html b/vm_gen/templates/html/_search.html
new file mode 100644
index 0000000..70a5b4f
--- /dev/null
+++ b/vm_gen/templates/html/_search.html
@@ -0,0 +1,11 @@
+{% for instance in instances %}
+
+
+ {% include "[[ name|camel_to_snake ]]/_title.html" %}
+ |
+ [
+ e |
+ x
+ ]
+
+{% endfor %}
\ No newline at end of file
diff --git a/vm_gen/templates/html/_edit.html b/vm_gen/templates/html/_update.html
similarity index 89%
rename from vm_gen/templates/html/_edit.html
rename to vm_gen/templates/html/_update.html
index d3eab1c..d167665 100644
--- a/vm_gen/templates/html/_edit.html
+++ b/vm_gen/templates/html/_update.html
@@ -1,4 +1,3 @@
-Edit {% include "[[ name|camel_to_snake ]]/_title.html" %}