files impl
This commit is contained in:
parent
1048122def
commit
b680ca8bbd
@ -1 +1,2 @@
|
|||||||
name
|
name
|
||||||
|
admin
|
|
@ -1,2 +1,2 @@
|
|||||||
email,password,role_names
|
email,password,role_names
|
||||||
admin@blog.pi2.dev,__SENSITIVE__.ADMIN_PASSWORD,admin
|
admin@app,__SENSITIVE__.ADMIN_PASSWORD,admin
|
|
@ -10,15 +10,21 @@ access:
|
|||||||
login_required: true
|
login_required: true
|
||||||
roles_required:
|
roles_required:
|
||||||
- admin
|
- admin
|
||||||
- verb: get
|
- verb: get|list|table|search|create|update|delete
|
||||||
login_required: false
|
|
||||||
- verb: list
|
|
||||||
login_required: false
|
|
||||||
- verb: table
|
|
||||||
login_required: false
|
|
||||||
- verb: search
|
|
||||||
login_required: false
|
login_required: false
|
||||||
|
show_in_nav: yes
|
||||||
columns:
|
columns:
|
||||||
- name: filename
|
- name: body|is_<bool>|<date>_dt
|
||||||
- name: body
|
type: boolean|integer|text|long_text|datetime|filename|
|
||||||
type: long_text
|
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
|
@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import importlib
|
import importlib
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
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.persistance import db, filter_m_n, update_m_ns
|
||||||
from oshipka.util.strings import camel_case_to_snake_case
|
from oshipka.util.strings import camel_case_to_snake_case
|
||||||
|
from config import MEDIA_DIR
|
||||||
|
|
||||||
webapp_models = importlib.import_module("webapp.models")
|
webapp_models = importlib.import_module("webapp.models")
|
||||||
|
|
||||||
@ -30,6 +32,14 @@ def default_get_form_func(vc):
|
|||||||
bool_values = request.form.getlist(k)
|
bool_values = request.form.getlist(k)
|
||||||
bool_value = True if len(bool_values) == 2 else False
|
bool_value = True if len(bool_values) == 2 else False
|
||||||
vc.serialized_args[k[1:]] = bool_value
|
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())))
|
vc.serialized_args.update(dict(filter(lambda k: not k[0].startswith("_"), dict(request.form).items())))
|
||||||
to_delete = []
|
to_delete = []
|
||||||
for key, value in vc.serialized_args.items():
|
for key, value in vc.serialized_args.items():
|
||||||
@ -92,6 +102,13 @@ def default_update_func(vc):
|
|||||||
del vc.serialized_args[key]
|
del vc.serialized_args[key]
|
||||||
vc.instance = vc.instances[0]
|
vc.instance = vc.instances[0]
|
||||||
for k, v in vc.serialized_args.items():
|
for k, v in vc.serialized_args.items():
|
||||||
|
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)
|
setattr(vc.instance, k, v)
|
||||||
update_m_ns(vc.instance, m_ns)
|
update_m_ns(vc.instance, m_ns)
|
||||||
db.session.add(vc.instance)
|
db.session.add(vc.instance)
|
||||||
@ -105,6 +122,13 @@ def default_create_func(vc):
|
|||||||
|
|
||||||
def default_delete_func(vc):
|
def default_delete_func(vc):
|
||||||
instance = vc.instances[0]
|
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)
|
db.session.delete(instance)
|
||||||
|
|
||||||
|
|
||||||
@ -226,6 +250,7 @@ class ModelView(object):
|
|||||||
_model_name = model.name
|
_model_name = model.name
|
||||||
self.model_name = camel_case_to_snake_case(_model_name)
|
self.model_name = camel_case_to_snake_case(_model_name)
|
||||||
self.model_name_pl = p.plural(self.model_name)
|
self.model_name_pl = p.plural(self.model_name)
|
||||||
|
self.model_file_columns = model._file_columns
|
||||||
|
|
||||||
MODEL_VIEWS[self.model_name] = self
|
MODEL_VIEWS[self.model_name] = self
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inhe
|
|||||||
[%- include "_model_choice_header_py" %]
|
[%- include "_model_choice_header_py" %]
|
||||||
[%- include "_model_searchable_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 %]
|
[%- for column in columns %]
|
||||||
[%- if column._type == 'relationship' %]
|
[%- if column._type == 'relationship' %]
|
||||||
[%- include "_relationship_py" %]
|
[%- include "_relationship_py" %]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<form action="{{ url_for('create_[[ name|camel_to_snake ]]') }}" method="post">
|
<form action="{{ url_for('create_[[ name|camel_to_snake ]]') }}" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('list_[[ name|camel_to_snake ]]') }}"/>
|
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('list_[[ name|camel_to_snake ]]') }}"/>
|
||||||
<table>
|
<table>
|
||||||
[%- for column in columns %]
|
[%- for column in columns %]
|
||||||
@ -44,6 +44,8 @@
|
|||||||
[%- elif column.type in ['long_text', ] %]
|
[%- elif column.type in ['long_text', ] %]
|
||||||
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
||||||
name="[[ column.name ]]"></textarea>
|
name="[[ column.name ]]"></textarea>
|
||||||
|
[%- elif column.type in ['file', 'audio', 'video', 'image', 'picture', 'img', ] %]
|
||||||
|
<input type="file" name="_file_[[ column.name ]]" accept="[[ column.accept ]]">
|
||||||
[%- else %]
|
[%- else %]
|
||||||
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
||||||
type="text" name="[[ column.name ]]" autocomplete="off"
|
type="text" name="[[ column.name ]]" autocomplete="off"
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
[%- for column in columns %]
|
[%- for column in columns %]
|
||||||
{% if "[[ column.name ]]" not in skip_list %}
|
{% if "[[ column.name ]]" not in skip_list %}
|
||||||
[%- if column.type in ['video'] %]
|
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>:
|
||||||
|
[%- if column.type in ['picture', 'image', 'img'] %]
|
||||||
|
<img src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" id="display-[[ name|camel_to_snake ]]-[[ column.name ]]" />
|
||||||
|
[%- elif column.type in ['video'] %]
|
||||||
<video src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" controls class="video-inline" id="display-[[ name|camel_to_snake ]]-[[ column.name ]]">
|
<video src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" controls class="video-inline" id="display-[[ name|camel_to_snake ]]-[[ column.name ]]">
|
||||||
<source src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" type="video/mp4">
|
<source src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" type="video/mp4">
|
||||||
<source src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" type="video/webm">
|
<source src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" type="video/webm">
|
||||||
@ -9,14 +12,15 @@
|
|||||||
<audio src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" controls id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"></audio>
|
<audio src="{{ url_for('oshipka_bp.get_media', filepath=instance.[[ column.name ]]) }}" controls id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"></audio>
|
||||||
[%- elif column.type in ['relationship'] %]
|
[%- elif column.type in ['relationship'] %]
|
||||||
[%- if column.multiple %]
|
[%- if column.multiple %]
|
||||||
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name|pluralize ]]") }}</strong>: {{ instance.[[ column.name|pluralize ]] }}</li>
|
{{ instance.[[ column.name|pluralize ]] }}
|
||||||
[%- else %]
|
[%- else %]
|
||||||
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{_("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
|
{{ instance.[[ column.name ]] }}
|
||||||
[%- endif %]
|
[%- endif %]
|
||||||
[%- elif column.type in ['bool', 'boolean', ] %]
|
[%- elif column.type in ['bool', 'boolean', ] %]
|
||||||
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]]|bool }}</li>
|
{{ instance.[[ column.name ]]|bool }}
|
||||||
[%- else %]
|
[%- else %]
|
||||||
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
|
{{ instance.[[ column.name ]] }}
|
||||||
[%- endif %]
|
[%- endif %]
|
||||||
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
[%- endfor %]
|
[%- endfor %]
|
@ -1,4 +1,4 @@
|
|||||||
<form action="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id) }}" method="post">
|
<form action="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id) }}" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('get_[[ name|camel_to_snake ]]', uuid=instance.id) }}"/>
|
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('get_[[ name|camel_to_snake ]]', uuid=instance.id) }}"/>
|
||||||
<table>
|
<table>
|
||||||
[%- for column in columns %]
|
[%- for column in columns %]
|
||||||
@ -43,6 +43,9 @@
|
|||||||
[%- elif column.type in ['long_text', ] %]
|
[%- elif column.type in ['long_text', ] %]
|
||||||
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
||||||
name="[[ column.name ]]">{{ instance.[[ column.name ]] }}</textarea>
|
name="[[ column.name ]]">{{ instance.[[ column.name ]] }}</textarea>
|
||||||
|
[%- elif column.type in ['file', 'audio', 'video', 'image', 'picture', 'img', ] %]
|
||||||
|
<p>{{ instance.[[ column.name ]] }}</p>
|
||||||
|
<input type="file" name="_file_[[ column.name ]]" accept="[[ column.accept ]]">
|
||||||
[%- else %]
|
[%- else %]
|
||||||
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
|
||||||
value="{{ instance.[[ column.name ]] }}"
|
value="{{ instance.[[ column.name ]] }}"
|
||||||
|
19
vm_gen/templates/html/navigation.html
Normal file
19
vm_gen/templates/html/navigation.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<a href="{{ url_for('home') }}">{{ _("Home") }}</a> |
|
||||||
|
[%- for nav in navigation_items %]
|
||||||
|
[%- if nav._verbs.list.is_login_required %]
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
[%- endif %]
|
||||||
|
<a href="{{ url_for('list_[[ nav.name|camel_to_snake ]]') }}">{{ _("[[ nav.name ]]") }}</a>[%- if not loop.last %]|[%- endif %]
|
||||||
|
[%- if nav._verbs.list.is_login_required %]
|
||||||
|
{% endif %}
|
||||||
|
[%- endif %]
|
||||||
|
[%- endfor %]
|
||||||
|
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<a href="#">{{ current_user.email }}</a> |
|
||||||
|
<a href="{{ url_for('security.logout') }}">{{ _("Logout") }}</a> |
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('security.login') }}">{{ _("Login") }}</a> |
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
@ -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 = {
|
VERBS = {
|
||||||
'get': {
|
'get': {
|
||||||
'per_item': 'True',
|
'per_item': 'True',
|
||||||
@ -97,6 +115,9 @@ def enrich_view_model(view_model):
|
|||||||
_choices = _process_choice(column)
|
_choices = _process_choice(column)
|
||||||
_column_type = 'ChoiceType({})'.format(_choices.get('name'))
|
_column_type = 'ChoiceType({})'.format(_choices.get('name'))
|
||||||
view_model['_choice_types'].append(_choices)
|
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:
|
else:
|
||||||
_column_type = 'db.UnicodeText'
|
_column_type = 'db.UnicodeText'
|
||||||
column.update({'_type': _column_type})
|
column.update({'_type': _column_type})
|
||||||
@ -124,6 +145,18 @@ def enrich_view_model(view_model):
|
|||||||
return 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):
|
def process_model(view_model):
|
||||||
template = env.get_template('model_py')
|
template = env.get_template('model_py')
|
||||||
model = autopep8.fix_code(template.render(**view_model), options=pep_options)
|
model = autopep8.fix_code(template.render(**view_model), options=pep_options)
|
||||||
@ -171,13 +204,14 @@ def process_html_templates(view_model):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
all_model_imports = ['from oshipka.persistance import db']
|
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)
|
view_model_names = order_from_process_order('yaml', VIEW_MODELS_PATH)
|
||||||
for view_model_name in view_model_names:
|
for view_model_name in view_model_names:
|
||||||
with open(os.path.join(VIEW_MODELS_PATH, "{}.yaml".format(view_model_name)), 'r') as stream:
|
with open(os.path.join(VIEW_MODELS_PATH, "{}.yaml".format(view_model_name)), 'r') as stream:
|
||||||
try:
|
try:
|
||||||
view_models = yaml.safe_load_all(stream)
|
view_models = yaml.safe_load_all(stream)
|
||||||
for view_model in view_models:
|
for view_model in view_models:
|
||||||
|
all_view_models.append(view_model)
|
||||||
view_model = enrich_view_model(view_model)
|
view_model = enrich_view_model(view_model)
|
||||||
process_model(view_model)
|
process_model(view_model)
|
||||||
process_routes(view_model)
|
process_routes(view_model)
|
||||||
@ -191,6 +225,7 @@ def main():
|
|||||||
))
|
))
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
breakpoint()
|
breakpoint()
|
||||||
|
process_navigation(all_view_models)
|
||||||
all_model_imports = autopep8.fix_code('\n'.join(all_model_imports), options=pep_options)
|
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)
|
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:
|
with open(os.path.join(MODELS_PATH, "__init__.py"), 'w+') as f:
|
||||||
|
Loading…
Reference in New Issue
Block a user