files impl

This commit is contained in:
Daniel Tsvetkov 2021-05-08 12:24:13 +02:00
parent 1048122def
commit b680ca8bbd
10 changed files with 118 additions and 21 deletions

View File

@ -1 +1,2 @@
name
admin
1 name
2 admin

View File

@ -1,2 +1,2 @@
email,password,role_names
admin@blog.pi2.dev,__SENSITIVE__.ADMIN_PASSWORD,admin
admin@app,__SENSITIVE__.ADMIN_PASSWORD,admin
1 email password role_names
2 admin@blog.pi2.dev admin@app __SENSITIVE__.ADMIN_PASSWORD admin

View File

@ -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
- name: body|is_<bool>|<date>_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

View File

@ -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,6 +102,13 @@ def default_update_func(vc):
del vc.serialized_args[key]
vc.instance = vc.instances[0]
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)
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

View File

@ -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" %]

View File

@ -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 ]]') }}"/>
<table>
[%- for column in columns %]
@ -44,6 +44,8 @@
[%- elif column.type in ['long_text', ] %]
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
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 %]
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
type="text" name="[[ column.name ]]" autocomplete="off"

View File

@ -1,6 +1,9 @@
[%- for column in columns %]
{% 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 ]]">
<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">
@ -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>
[%- elif column.type in ['relationship'] %]
[%- 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 %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{_("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
{{ instance.[[ column.name ]] }}
[%- endif %]
[%- 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 %]
<li id="display-[[ name|camel_to_snake ]]-[[ column.name ]]"><strong>{{ _("[[ column.name ]]") }}</strong>: {{ instance.[[ column.name ]] }}</li>
{{ instance.[[ column.name ]] }}
[%- endif %]
</li>
{% endif %}
[%- endfor %]

View File

@ -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) }}"/>
<table>
[%- for column in columns %]
@ -43,6 +43,9 @@
[%- elif column.type in ['long_text', ] %]
<textarea id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
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 %]
<input id="input-[[ name|camel_to_snake ]]-[[ column.name ]]"
value="{{ instance.[[ column.name ]] }}"

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

View File

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