generate model, html templates

This commit is contained in:
Daniel Tsvetkov 2020-06-03 16:00:51 +02:00
parent c665454045
commit 6c45fca97e
20 changed files with 220 additions and 26 deletions

View File

@ -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 "$@"

View File

@ -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.

View File

@ -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),

View File

@ -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
View 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
View File

View 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 %]

View 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 ]]"))

View File

View File

View 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 %}

View File

@ -0,0 +1,5 @@
{% extends "layout.html" %}
{% block content %}
{% include "[[ name|camel_to_snake ]]/_edit.html" %}
{% endblock %}

View File

@ -0,0 +1,5 @@
{% extends "layout.html" %}
{% block content %}
{% include "[[ name|camel_to_snake ]]/_get.html" %}
{% endblock %}

View 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
View 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
View 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)