generate model, html templates
This commit is contained in:
parent
c665454045
commit
6c45fca97e
12
oshipka.sh
12
oshipka.sh
@ -8,9 +8,10 @@ echo "oshipka is at: $OSHIPKA_PATH"
|
||||
|
||||
#!/usr/bin/env bash
|
||||
HELP="
|
||||
Usage $0 [ bootstrap | worker | web | venv | install | link | cert ]
|
||||
Usage $0 [ bootstrap | model | init | worker | web | venv | install | link | cert ]
|
||||
|
||||
bootstrap [PROJECT_PATH] Create a new project in PROJECT_PATH
|
||||
model [MODEL_NAME] Create or update a model
|
||||
init Install dev env
|
||||
|
||||
worker Start worker
|
||||
@ -107,11 +108,20 @@ run_in_prod() {
|
||||
gunicorn -w 4 -b 0.0.0.0:${PORT} run:app
|
||||
}
|
||||
|
||||
model() {
|
||||
shift
|
||||
MODEL_NAME=$1
|
||||
source venv/bin/activate
|
||||
python "${OSHIPKA_PATH}/vm_gen/vm_gen.py" "${MODEL_NAME}" "`pwd`"
|
||||
}
|
||||
|
||||
command_main() {
|
||||
INITIAL_COMMAND=$1
|
||||
case "$INITIAL_COMMAND" in
|
||||
bootstrap) bootstrap "$@"
|
||||
;;
|
||||
model) model "$@"
|
||||
;;
|
||||
init) init "$@"
|
||||
;;
|
||||
worker) worker "$@"
|
||||
|
@ -20,6 +20,7 @@ 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 util.strings import camel_case_to_snake_case
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
@ -57,16 +58,6 @@ class LiberalBoolean(TypeDecorator):
|
||||
return value
|
||||
|
||||
|
||||
def camel_case_to_snake_case(name):
|
||||
"""
|
||||
Convertes a CamelCase name to snake_case
|
||||
:param name: the name to be converted
|
||||
:return:
|
||||
"""
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
|
||||
|
||||
class ModelController(ModelJsonEncoder):
|
||||
"""
|
||||
This interface is the parent of all models in our database.
|
||||
|
@ -5,9 +5,23 @@ import inflect
|
||||
from flask import flash, render_template, redirect, request, url_for
|
||||
|
||||
from oshipka.persistance import db
|
||||
from 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()
|
||||
else:
|
||||
instance = model.query.filter_by(uuid=uuid).first()
|
||||
if not instance:
|
||||
flash("No {}:{}".format(model_view.model_name, uuid))
|
||||
return instance
|
||||
|
||||
|
||||
def list_view(model_view, template, template_ctx_func=None):
|
||||
template = template if template else "{}/list.html".format(model_view.model_name)
|
||||
|
||||
def inner():
|
||||
instances = model_view.model.query.all()
|
||||
template_ctx = template_ctx_func(instances) if template_ctx_func else {}
|
||||
@ -17,11 +31,11 @@ def list_view(model_view, template, template_ctx_func=None):
|
||||
|
||||
|
||||
def get_view(model_view, template, template_ctx_func=None):
|
||||
template = template if template else "{}/get.html".format(model_view.model_name)
|
||||
|
||||
def inner(uuid):
|
||||
model = model_view.model
|
||||
instance = model.query.filter_by(uuid=uuid).first()
|
||||
instance = get_instance(model_view, uuid)
|
||||
if not instance:
|
||||
flash("No {}:{}".format(model_view.model_name, uuid))
|
||||
return redirect(request.referrer or url_for('home'))
|
||||
template_ctx = template_ctx_func(instance) if template_ctx_func else {}
|
||||
return render_template(template, instance=instance, **template_ctx)
|
||||
@ -34,11 +48,11 @@ def serialize_form():
|
||||
|
||||
|
||||
def update_view(model_view, template, pre_process_func=None, post_process_func=None, **kwargs):
|
||||
template = template if template else "{}/edit.html".format(model_view.model_name)
|
||||
|
||||
def inner(uuid):
|
||||
model = model_view.model
|
||||
instance = model.query.filter_by(uuid=uuid).first()
|
||||
instance = get_instance(model_view, uuid)
|
||||
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)
|
||||
@ -83,10 +97,8 @@ def create_view(model_view, template, template_ctx_func=None, post_add=None, pos
|
||||
|
||||
def delete_view(model_view):
|
||||
def inner(uuid):
|
||||
model = model_view.model
|
||||
instance = model.query.filter_by(uuid=uuid).first()
|
||||
instance = get_instance(model_view, uuid)
|
||||
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,
|
||||
@ -109,29 +121,29 @@ class ModelView(object):
|
||||
|
||||
p = inflect.engine()
|
||||
|
||||
self.model_name = model.__name__.lower()
|
||||
self.model_name = camel_case_to_snake_case(model.__name__)
|
||||
self.model_name_pl = p.plural(self.model_name)
|
||||
|
||||
def register_create(self, list_template, **kwargs):
|
||||
def register_create(self, create_template=None, **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, list_template, **kwargs))
|
||||
view_func=create_view(self, create_template, **kwargs))
|
||||
|
||||
def register_list(self, list_template, **kwargs):
|
||||
def register_list(self, list_template=None, **kwargs):
|
||||
url = '/{}'.format(self.model_name_pl)
|
||||
self.app.add_url_rule(url,
|
||||
'list_{}'.format(self.model_name),
|
||||
list_view(self, list_template, **kwargs))
|
||||
|
||||
def register_get(self, retrieve_template, **kwargs):
|
||||
def register_get(self, retrieve_template=None, **kwargs):
|
||||
url = '/{}/<uuid>'.format(self.model_name_pl)
|
||||
|
||||
self.app.add_url_rule(url,
|
||||
'get_{}'.format(self.model_name),
|
||||
get_view(self, retrieve_template, **kwargs))
|
||||
|
||||
def register_update(self, update_template, **kwargs):
|
||||
def register_update(self, update_template=None, **kwargs):
|
||||
url = '/{}/<uuid>/edit'.format(self.model_name_pl)
|
||||
self.app.add_url_rule(url, methods=["GET", "POST"],
|
||||
endpoint='update_{}'.format(self.model_name),
|
||||
|
@ -24,6 +24,7 @@ passlib==1.7.2
|
||||
pathtools==0.1.2
|
||||
pycparser==2.20
|
||||
pytz==2019.3
|
||||
pyyaml==5.3.1
|
||||
six==1.14.0
|
||||
speaklater==1.3
|
||||
SQLAlchemy==1.3.15
|
||||
|
20
util/strings.py
Normal file
20
util/strings.py
Normal file
@ -0,0 +1,20 @@
|
||||
import re
|
||||
|
||||
|
||||
def camel_case_to_snake_case(name):
|
||||
"""
|
||||
Convertes a CamelCase name to snake_case
|
||||
:param name: the name to be converted
|
||||
:return:
|
||||
"""
|
||||
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
||||
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
||||
|
||||
|
||||
def snake_case_to_camel_case(name):
|
||||
"""
|
||||
Convertes a snake_case name to CamelCase
|
||||
:param name: the name to be converted
|
||||
:return:
|
||||
"""
|
||||
return ''.join(x.title() for x in name.split('_'))
|
0
vm_gen/__init__.py
Normal file
0
vm_gen/__init__.py
Normal file
13
vm_gen/templates/_model_choice_header_py
Normal file
13
vm_gen/templates/_model_choice_header_py
Normal file
@ -0,0 +1,13 @@
|
||||
[%- if _choice_types %]
|
||||
[%- for choice_type in _choice_types %]
|
||||
[%- for key in choice_type.choices.keys() %]
|
||||
[[ choice_type.name|upper ]]_TYPE_[[ key ]] = "[[ key ]]"
|
||||
[%- endfor %]
|
||||
|
||||
[[ choice_type.name ]] = [
|
||||
[%- for key, value in choice_type.choices.items() %]
|
||||
([[ choice_type.name|upper ]]_TYPE_[[ key ]], u'[[ value ]]'),
|
||||
[%- endfor %]
|
||||
]
|
||||
[%- endfor %]
|
||||
[%- endif %]
|
4
vm_gen/templates/_relationship_py
Normal file
4
vm_gen/templates/_relationship_py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
[[ column.name ]]_id = db.Column(db.Integer, db.ForeignKey('[[ column.name ]].id'))
|
||||
[[ column.name ]] = db.relationship('[[ column.name|snake_to_camel ]]', backref=db.backref("[[ name|camel_to_snake|pluralize ]]"))
|
0
vm_gen/templates/html/_edit.html
Normal file
0
vm_gen/templates/html/_edit.html
Normal file
0
vm_gen/templates/html/_get.html
Normal file
0
vm_gen/templates/html/_get.html
Normal file
3
vm_gen/templates/html/_list.html
Normal file
3
vm_gen/templates/html/_list.html
Normal file
@ -0,0 +1,3 @@
|
||||
{% for instance in instances %}
|
||||
<li><a href="{{ url_for('get_[[ name|camel_to_snake ]]', uuid=instance.uuid) }}">{{ instance.number }}</a></li>
|
||||
{% endfor %}
|
5
vm_gen/templates/html/edit.html
Normal file
5
vm_gen/templates/html/edit.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include "[[ name|camel_to_snake ]]/_edit.html" %}
|
||||
{% endblock %}
|
5
vm_gen/templates/html/get.html
Normal file
5
vm_gen/templates/html/get.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include "[[ name|camel_to_snake ]]/_get.html" %}
|
||||
{% endblock %}
|
5
vm_gen/templates/html/list.html
Normal file
5
vm_gen/templates/html/list.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
{% include "[[ name|camel_to_snake ]]/_list.html" %}
|
||||
{% endblock %}
|
17
vm_gen/templates/model_py
Normal file
17
vm_gen/templates/model_py
Normal file
@ -0,0 +1,17 @@
|
||||
[%- if _choice_types %]
|
||||
from sqlalchemy_utils import ChoiceType
|
||||
[%- endif %]
|
||||
|
||||
from oshipka.persistance import db, ModelController
|
||||
|
||||
|
||||
class [[ name ]](db.Model, ModelController):
|
||||
[%- include "_model_choice_header_py" %]
|
||||
|
||||
[%- for column in columns %]
|
||||
[%- if column._type == 'relationship' %]
|
||||
[%- include "_relationship_py" %]
|
||||
[%- else %]
|
||||
[[ column.name ]] = db.Column([[ column._type ]][%- if column.default %], default="[[ column.default ]]"[%- endif %])
|
||||
[%- endif %]
|
||||
[%- endfor %]
|
108
vm_gen/vm_gen.py
Normal file
108
vm_gen/vm_gen.py
Normal file
@ -0,0 +1,108 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import inflect
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
from util.strings import snake_case_to_camel_case, camel_case_to_snake_case
|
||||
|
||||
|
||||
def _process_choice(column):
|
||||
column_name = column.get('name', '')
|
||||
column_upper = column_name.upper()
|
||||
types_name = "{}_TYPES".format(column_upper)
|
||||
choices = column.get('choices', {})
|
||||
return {
|
||||
'name': types_name,
|
||||
'choices': choices,
|
||||
}
|
||||
|
||||
|
||||
def enrich_view_model(view_model):
|
||||
columns = []
|
||||
for column in view_model.get('columns', {}):
|
||||
column_type = column.get('type')
|
||||
if column_type in ['text', 'long_text', ]:
|
||||
_column_type = 'db.UnicodeText'
|
||||
elif column_type in ['bool', ]:
|
||||
_column_type = 'db.Boolean'
|
||||
elif column_type in ['relationship', ]:
|
||||
_column_type = 'relationship'
|
||||
elif column_type in ['choice', ]:
|
||||
if '_choice_types' not in view_model:
|
||||
view_model['_choice_types'] = []
|
||||
_choices = _process_choice(column)
|
||||
_column_type = 'ChoiceType({})'.format(_choices.get('name'))
|
||||
view_model['_choice_types'].append(_choices)
|
||||
else:
|
||||
_column_type = 'db.UnicodeText'
|
||||
column.update({'_type': _column_type})
|
||||
columns.append(column)
|
||||
view_model['columns'] = columns
|
||||
return view_model
|
||||
|
||||
|
||||
def process_model(view_model):
|
||||
template = env.get_template('model_py')
|
||||
rv = template.render(**view_model)
|
||||
_model_name = view_model.get('name')
|
||||
filename = "{}.py".format(camel_case_to_snake_case(_model_name.split('.yaml')[0]))
|
||||
with open(os.path.join(MODELS_PATH, filename), 'w+') as f:
|
||||
f.write(rv)
|
||||
|
||||
|
||||
def process_html_templates(view_model):
|
||||
_model_name_snake = camel_case_to_snake_case(view_model.get('name'))
|
||||
model_dir = os.path.join(HTML_TEMPLATES_PATH, _model_name_snake)
|
||||
if not os.path.exists(model_dir):
|
||||
os.makedirs(model_dir)
|
||||
|
||||
for filename in os.listdir(os.path.join(VM_TEMPLATES_PATH, "html")):
|
||||
template = env.get_template(os.path.join('html', filename))
|
||||
rv = template.render(**view_model)
|
||||
with open(os.path.join(model_dir, filename), 'w+') as f:
|
||||
f.write(rv)
|
||||
|
||||
|
||||
def main(view_model_name):
|
||||
view_model_names = os.listdir(VIEW_MODELS_PATH) if not view_model_name else ["{}.yaml".format(view_model_name)]
|
||||
for view_model_name in view_model_names:
|
||||
with open(os.path.join(VIEW_MODELS_PATH, view_model_name), 'r') as stream:
|
||||
try:
|
||||
view_models = yaml.safe_load_all(stream)
|
||||
for view_model in view_models:
|
||||
view_model = enrich_view_model(view_model)
|
||||
process_model(view_model)
|
||||
process_html_templates(view_model)
|
||||
except yaml.YAMLError as e:
|
||||
breakpoint()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
model_name = sys.argv[1]
|
||||
basepath = sys.argv[2]
|
||||
oshipka_path = os.environ.get('OSHIPKA_PATH')
|
||||
|
||||
VM_TEMPLATES_PATH = os.path.join(oshipka_path, "vm_gen", "templates")
|
||||
|
||||
WEBAPP_PATH = os.path.join(basepath, "webapp")
|
||||
VIEW_MODELS_PATH = os.path.join(WEBAPP_PATH, "view_models")
|
||||
MODELS_PATH = os.path.join(WEBAPP_PATH, "models_gen")
|
||||
HTML_TEMPLATES_PATH = os.path.join(WEBAPP_PATH, "templates_gen")
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(searchpath=VM_TEMPLATES_PATH),
|
||||
block_start_string='[%',
|
||||
block_end_string='%]',
|
||||
variable_start_string='[[',
|
||||
variable_end_string=']]'
|
||||
)
|
||||
env.filters['snake_to_camel'] = snake_case_to_camel_case
|
||||
env.filters['camel_to_snake'] = camel_case_to_snake_case
|
||||
|
||||
p = inflect.engine()
|
||||
env.filters['pluralize'] = p.plural
|
||||
|
||||
main(model_name)
|
Loading…
Reference in New Issue
Block a user