diff --git a/bootstrap/data_static/Role.csv b/bootstrap/data_static/Role.csv index 934edc8..fa554c4 100644 --- a/bootstrap/data_static/Role.csv +++ b/bootstrap/data_static/Role.csv @@ -1 +1,2 @@ -name \ No newline at end of file +name +admin \ No newline at end of file diff --git a/bootstrap/data_static/User.csv b/bootstrap/data_static/User.csv index 4206147..0d9f71c 100644 --- a/bootstrap/data_static/User.csv +++ b/bootstrap/data_static/User.csv @@ -1,2 +1,2 @@ email,password,role_names -admin@blog.pi2.dev,__SENSITIVE__.ADMIN_PASSWORD,admin \ No newline at end of file +admin@app,__SENSITIVE__.ADMIN_PASSWORD,admin \ No newline at end of file diff --git a/bootstrap/webapp/view_models/Example.yaml_ b/bootstrap/webapp/view_models/Example.yaml_ index 18707da..d6a8e43 100644 --- a/bootstrap/webapp/view_models/Example.yaml_ +++ b/bootstrap/webapp/view_models/Example.yaml_ @@ -10,15 +10,21 @@ access: login_required: true roles_required: - admin - - verb: get - login_required: false - - verb: list - login_required: false - - verb: table - login_required: false - - verb: search + - verb: get|list|table|search|create|update|delete login_required: false +show_in_nav: yes columns: - - name: filename - - name: body - type: long_text \ No newline at end of file + - name: body|is_|_dt + type: boolean|integer|text|long_text|datetime|filename| + default: "{}" + - name: related + type: relationship # type relationship + multiple: true # could be many-to-many + - name: choisy + type: choice + choices: + INT_INFO: int_info # key:value +display: + primary: type + secondary: text + tertiary: alt_names \ No newline at end of file diff --git a/oshipka/webapp/views.py b/oshipka/webapp/views.py index 6d0d74e..206e381 100644 --- a/oshipka/webapp/views.py +++ b/oshipka/webapp/views.py @@ -1,3 +1,4 @@ +import os import importlib import json from collections import defaultdict @@ -11,6 +12,7 @@ from sqlalchemy_filters import apply_filters from oshipka.persistance import db, filter_m_n, update_m_ns from oshipka.util.strings import camel_case_to_snake_case +from config import MEDIA_DIR webapp_models = importlib.import_module("webapp.models") @@ -30,6 +32,14 @@ def default_get_form_func(vc): bool_values = request.form.getlist(k) bool_value = True if len(bool_values) == 2 else False vc.serialized_args[k[1:]] = bool_value + for k, f in request.files.items(): + if f.filename != '': + filedir = os.path.join(MEDIA_DIR, vc.model_view.model_name_pl) + if not os.path.exists(filedir): + os.makedirs(filedir) + vc.serialized_args[k] = os.path.join(vc.model_view.model_name_pl, f.filename) + filepath = os.path.join(filedir, f.filename) + f.save(filepath) vc.serialized_args.update(dict(filter(lambda k: not k[0].startswith("_"), dict(request.form).items()))) to_delete = [] for key, value in vc.serialized_args.items(): @@ -92,7 +102,14 @@ def default_update_func(vc): del vc.serialized_args[key] vc.instance = vc.instances[0] for k, v in vc.serialized_args.items(): - setattr(vc.instance, k, v) + if k.startswith("_file_"): + key = k.split('_file_')[1] + current_value = getattr(vc.instance, key) + if current_value and current_value != v: + os.remove(os.path.join(MEDIA_DIR, v)) + setattr(vc.instance, key, v) + else: + setattr(vc.instance, k, v) update_m_ns(vc.instance, m_ns) db.session.add(vc.instance) @@ -105,6 +122,13 @@ def default_create_func(vc): def default_delete_func(vc): instance = vc.instances[0] + for filecolumn in vc.model_view.model_file_columns: + filename = getattr(instance, filecolumn) + if filename: + try: + os.remove(os.path.join(MEDIA_DIR, filename)) + except Exception as e: + flash("Error deleting file: {}".format(e), "error") db.session.delete(instance) @@ -226,6 +250,7 @@ class ModelView(object): _model_name = model.name self.model_name = camel_case_to_snake_case(_model_name) self.model_name_pl = p.plural(self.model_name) + self.model_file_columns = model._file_columns MODEL_VIEWS[self.model_name] = self diff --git a/vm_gen/templates/_model_py b/vm_gen/templates/_model_py index 80ff757..aa8ab79 100644 --- a/vm_gen/templates/_model_py +++ b/vm_gen/templates/_model_py @@ -6,6 +6,8 @@ class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inhe [%- include "_model_choice_header_py" %] [%- include "_model_searchable_header_py" %] + _file_columns = [ [%- for column in columns %][%- if column.is_file %]"[[ column.name ]]"[%- endif %] [%- endfor %] ] + [%- for column in columns %] [%- if column._type == 'relationship' %] [%- include "_relationship_py" %] diff --git a/vm_gen/templates/html/_create.html b/vm_gen/templates/html/_create.html index c9475ad..828dd24 100644 --- a/vm_gen/templates/html/_create.html +++ b/vm_gen/templates/html/_create.html @@ -1,4 +1,4 @@ -
+ [%- for column in columns %] @@ -44,6 +44,8 @@ [%- elif column.type in ['long_text', ] %] + [%- elif column.type in ['file', 'audio', 'video', 'image', 'picture', 'img', ] %] + [%- else %] {{ _("[[ column.name ]]") }}: + [%- if column.type in ['picture', 'image', 'img'] %] + + [%- elif column.type in ['video'] %]
[%- for column in columns %] @@ -43,6 +43,9 @@ [%- elif column.type in ['long_text', ] %] + [%- elif column.type in ['file', 'audio', 'video', 'image', 'picture', 'img', ] %] +

{{ instance.[[ column.name ]] }}

+ [%- else %] {{ _("Home") }} | +[%- for nav in navigation_items %] +[%- if nav._verbs.list.is_login_required %] +{% if current_user.is_authenticated %} +[%- endif %] + {{ _("[[ nav.name ]]") }}[%- if not loop.last %]|[%- endif %] +[%- if nav._verbs.list.is_login_required %] +{% endif %} +[%- endif %] +[%- endfor %] + +
+ {% if current_user.is_authenticated %} + {{ current_user.email }} | + {{ _("Logout") }} | + {% else %} + {{ _("Login") }} | + {% endif %} +
\ No newline at end of file diff --git a/vm_gen/vm_gen.py b/vm_gen/vm_gen.py index 0231181..3279c64 100644 --- a/vm_gen/vm_gen.py +++ b/vm_gen/vm_gen.py @@ -37,6 +37,24 @@ def process_secondary(view_model, column_name): } +def _process_file(column): + column_type = column.get('type', '') + column_accept = column.get('accept', '') + if column_accept: + accept = column_accept + elif column_type in ['audio']: + accept = 'audio/*' + elif column_type in ['video']: + accept = 'video/*' + elif column_type in ['video']: + accept = 'video/*' + else: + accept = '*' + column["accept"] = accept + column["is_file"] = True + return column + + VERBS = { 'get': { 'per_item': 'True', @@ -97,6 +115,9 @@ def enrich_view_model(view_model): _choices = _process_choice(column) _column_type = 'ChoiceType({})'.format(_choices.get('name')) view_model['_choice_types'].append(_choices) + elif column_type in ['file', 'audio', 'video', 'image', 'img', 'picture', ]: + column = _process_file(column) + _column_type = 'db.UnicodeText'.format() else: _column_type = 'db.UnicodeText' column.update({'_type': _column_type}) @@ -124,6 +145,18 @@ def enrich_view_model(view_model): return view_model +def process_navigation(view_models): + navigation_items = [] + for view_model in view_models: + if view_model.get('show_in_nav'): + navigation_items.append(view_model) + template = env.get_template(os.path.join('html', 'navigation.html')) + rv = template.render(**{"navigation_items": navigation_items}) + filepath = os.path.join(HTML_TEMPLATES_PATH, "navigation.html") + with open(filepath, 'w+') as f: + f.write(rv) + + def process_model(view_model): template = env.get_template('model_py') model = autopep8.fix_code(template.render(**view_model), options=pep_options) @@ -171,13 +204,14 @@ def process_html_templates(view_model): def main(): all_model_imports = ['from oshipka.persistance import db'] - all_route_imports = [] + all_route_imports, all_view_models = [], [] view_model_names = order_from_process_order('yaml', VIEW_MODELS_PATH) for view_model_name in view_model_names: with open(os.path.join(VIEW_MODELS_PATH, "{}.yaml".format(view_model_name)), 'r') as stream: try: view_models = yaml.safe_load_all(stream) for view_model in view_models: + all_view_models.append(view_model) view_model = enrich_view_model(view_model) process_model(view_model) process_routes(view_model) @@ -191,6 +225,7 @@ def main(): )) except yaml.YAMLError as e: breakpoint() + process_navigation(all_view_models) all_model_imports = autopep8.fix_code('\n'.join(all_model_imports), options=pep_options) all_route_imports = autopep8.fix_code('\n'.join(all_route_imports), options=pep_options) with open(os.path.join(MODELS_PATH, "__init__.py"), 'w+') as f: