Initial commit

This commit is contained in:
Daniel Tsvetkov 2020-07-08 14:36:13 +02:00
commit 52c6bdeaba
49 changed files with 1250 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea
venv
*.pyc
data/
__pycache__
sensitive.py

4
INSTALL.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
virtualenv -p python3 venv
source venv/bin/activate
pip install -r requirements.txt

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Project
## Install
```
virtualenv -p python3 venv
source venv/bin/activate
pip install -r requirements.txt
```
## Run
```
source venv/bin/activate
python run.py
```
_Powered by [oshipka](https://gitlab.com/pisquared/oshipka)_

27
config.py Normal file
View File

@ -0,0 +1,27 @@
import os
basepath = os.path.dirname(os.path.realpath(__file__))
DATA_DIR = os.path.join(basepath, "data")
STATIC_DATA_DIR = os.path.join(basepath, "data_static")
MEDIA_DIR = os.path.join(DATA_DIR, "media")
TASKS_DIR = os.path.join(DATA_DIR, "tasks")
TASKS_IN_DIR = os.path.join(TASKS_DIR, "in")
TASKS_BUF_DIR = os.path.join(TASKS_DIR, "buf")
TASKS_PROC_DIR = os.path.join(TASKS_DIR, "proc")
DATABASE_FILE = os.path.join(DATA_DIR, "db.sqlite")
SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(DATABASE_FILE)
SEARCH_INDEX_PATH = os.path.join(DATA_DIR, "search_index")
TEMPLATES_FOLDER = os.path.join(basepath, "webapp", "templates")
STATIC_FOLDER = os.path.join(basepath, "webapp", "static")
MAKEDIRS = [
DATA_DIR, STATIC_DATA_DIR, MEDIA_DIR, TASKS_DIR, TASKS_IN_DIR, TASKS_PROC_DIR, TASKS_BUF_DIR,
]
TRANSLATION_LANGUAGES = ['en', 'bg']

2
data_static/Role.csv Normal file
View File

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

2
data_static/User.csv Normal file
View File

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

View File

@ -0,0 +1,2 @@
Role
User

8
init_populate.py Normal file
View File

@ -0,0 +1,8 @@
from oshipka.persistance import populate_static, init_db
from populate import populate_db
from webapp.app import app
init_db(app)
populate_static(app)
populate_db(app)

17
manager.py Normal file
View File

@ -0,0 +1,17 @@
from flask_migrate import MigrateCommand
from flask_script import Manager
from config import SQLALCHEMY_DATABASE_URI
from oshipka.persistance import db, migrate
from oshipka.webapp import app
app.config["SQLALCHEMY_DATABASE_URI"] = SQLALCHEMY_DATABASE_URI
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
migrate.init_app(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
from webapp.models import *
if __name__ == '__main__':
manager.run()

1
migrations/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

45
migrations/alembic.ini Normal file
View File

@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

96
migrations/env.py Normal file
View File

@ -0,0 +1,96 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option(
'sqlalchemy.url',
str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
**current_app.extensions['migrate'].configure_args
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,40 @@
"""002
Revision ID: 28f9ab4fc8b0
Revises: d08a068031c3
Create Date: 2020-07-08 14:27:11.649888
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '28f9ab4fc8b0'
down_revision = 'd08a068031c3'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('blog_post',
sa.Column('created_dt', sa.UnicodeText(), nullable=True),
sa.Column('updated_dt', sa.UnicodeText(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.Unicode(), nullable=True),
sa.Column('filename', sa.UnicodeText(), nullable=True),
sa.Column('body', sa.UnicodeText(), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_blog_post_uuid'), 'blog_post', ['uuid'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_blog_post_uuid'), table_name='blog_post')
op.drop_table('blog_post')
# ### end Alembic commands ###

View File

@ -0,0 +1,73 @@
"""001
Revision ID: d08a068031c3
Revises:
Create Date: 2020-07-08 11:48:15.369556
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'd08a068031c3'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.Unicode(), nullable=True),
sa.Column('name', sa.Unicode(), nullable=True),
sa.Column('description', sa.Unicode(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_index(op.f('ix_role_uuid'), 'role', ['uuid'], unique=False)
op.create_table('task',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Unicode(), nullable=True),
sa.Column('uuid', sa.Unicode(), nullable=True),
sa.Column('status', sa.Unicode(), nullable=True),
sa.Column('func_name', sa.Unicode(), nullable=True),
sa.Column('args', sa.Unicode(), nullable=True),
sa.Column('kwargs', sa.Unicode(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('uuid', sa.Unicode(), nullable=True),
sa.Column('email', sa.Unicode(), nullable=True),
sa.Column('password', sa.Unicode(), nullable=True),
sa.Column('active', sa.Boolean(), nullable=True),
sa.Column('confirmed_at', sa.DateTime(), nullable=True),
sa.Column('timezone', sa.String(), nullable=True),
sa.Column('tz_offset_seconds', sa.Integer(), nullable=True),
sa.Column('locale', sa.String(length=4), nullable=True),
sa.Column('name', sa.Unicode(), nullable=True),
sa.Column('profile_image_url', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
op.create_index(op.f('ix_user_uuid'), 'user', ['uuid'], unique=False)
op.create_table('roles_users',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], )
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('roles_users')
op.drop_index(op.f('ix_user_uuid'), table_name='user')
op.drop_table('user')
op.drop_table('task')
op.drop_index(op.f('ix_role_uuid'), table_name='role')
op.drop_table('role')
# ### end Alembic commands ###

6
populate.py Normal file
View File

@ -0,0 +1,6 @@
from oshipka.persistance import db
def populate_db(app):
with app.app_context():
db.session.commit()

0
requirements.txt Normal file
View File

15
run.py Normal file
View File

@ -0,0 +1,15 @@
from config import TEMPLATES_FOLDER, STATIC_FOLDER
from oshipka.persistance import populate_static, init_db
from populate import populate_db
from webapp.app import app
if init_db(app):
populate_static(app)
populate_db(app)
app.template_folder = TEMPLATES_FOLDER
app.static_folder = STATIC_FOLDER
if __name__ == "__main__":
app.run(debug=True)

5
translations/babel.cfg Normal file
View File

@ -0,0 +1,5 @@
[ignore: venv/**]
[ignore: migrations/**]
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

Binary file not shown.

View File

@ -0,0 +1,92 @@
# Bulgarian translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-07-08 14:25+0200\n"
"PO-Revision-Date: 2020-07-08 14:25+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: bg\n"
"Language-Team: bg <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: webapp/templates/layout.html:7
msgid "Project"
msgstr ""
#: webapp/templates/navigation.html:1
msgid "Home"
msgstr ""
#: webapp/templates/navigation.html:5
msgid "Logout"
msgstr ""
#: webapp/templates/navigation.html:7
msgid "Login"
msgstr ""
#: webapp/templates/blog_post/_create.html:5
#: webapp/templates/blog_post/_get.html:3
#: webapp/templates/blog_post/_table.html:5
#: webapp/templates/blog_post/_update.html:5
msgid "filename"
msgstr ""
#: webapp/templates/blog_post/_create.html:12
#: webapp/templates/blog_post/_get.html:6
#: webapp/templates/blog_post/_table.html:8
#: webapp/templates/blog_post/_update.html:13
msgid "body"
msgstr ""
#: webapp/templates/blog_post/_table.html:10
msgid "Actions"
msgstr ""
#: webapp/templates/blog_post/_title.html:2
#: webapp/templates/blog_post/create.html:4
msgid "BlogPost"
msgstr ""
#: webapp/templates/blog_post/create.html:4
#: webapp/templates/blog_post/list.html:5
#: webapp/templates/blog_post/search.html:5
#: webapp/templates/blog_post/table.html:5
msgid "Create"
msgstr ""
#: webapp/templates/blog_post/get.html:4
msgid "list"
msgstr ""
#: webapp/templates/blog_post/get.html:6
msgid "edit"
msgstr ""
#: webapp/templates/blog_post/get.html:7
msgid "delete"
msgstr ""
#: webapp/templates/blog_post/list.html:4
#: webapp/templates/blog_post/search.html:4
#: webapp/templates/blog_post/table.html:4
msgid "BlogPosts"
msgstr ""
#: webapp/templates/blog_post/search.html:4
msgid "Search results for"
msgstr ""
#: webapp/templates/blog_post/update.html:4
msgid "Edit"
msgstr ""

Binary file not shown.

View File

@ -0,0 +1,92 @@
# English translations for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-07-08 14:25+0200\n"
"PO-Revision-Date: 2020-07-08 14:25+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: en\n"
"Language-Team: en <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: webapp/templates/layout.html:7
msgid "Project"
msgstr ""
#: webapp/templates/navigation.html:1
msgid "Home"
msgstr ""
#: webapp/templates/navigation.html:5
msgid "Logout"
msgstr ""
#: webapp/templates/navigation.html:7
msgid "Login"
msgstr ""
#: webapp/templates/blog_post/_create.html:5
#: webapp/templates/blog_post/_get.html:3
#: webapp/templates/blog_post/_table.html:5
#: webapp/templates/blog_post/_update.html:5
msgid "filename"
msgstr ""
#: webapp/templates/blog_post/_create.html:12
#: webapp/templates/blog_post/_get.html:6
#: webapp/templates/blog_post/_table.html:8
#: webapp/templates/blog_post/_update.html:13
msgid "body"
msgstr ""
#: webapp/templates/blog_post/_table.html:10
msgid "Actions"
msgstr ""
#: webapp/templates/blog_post/_title.html:2
#: webapp/templates/blog_post/create.html:4
msgid "BlogPost"
msgstr ""
#: webapp/templates/blog_post/create.html:4
#: webapp/templates/blog_post/list.html:5
#: webapp/templates/blog_post/search.html:5
#: webapp/templates/blog_post/table.html:5
msgid "Create"
msgstr ""
#: webapp/templates/blog_post/get.html:4
msgid "list"
msgstr ""
#: webapp/templates/blog_post/get.html:6
msgid "edit"
msgstr ""
#: webapp/templates/blog_post/get.html:7
msgid "delete"
msgstr ""
#: webapp/templates/blog_post/list.html:4
#: webapp/templates/blog_post/search.html:4
#: webapp/templates/blog_post/table.html:4
msgid "BlogPosts"
msgstr ""
#: webapp/templates/blog_post/search.html:4
msgid "Search results for"
msgstr ""
#: webapp/templates/blog_post/update.html:4
msgid "Edit"
msgstr ""

91
translations/messages.pot Normal file
View File

@ -0,0 +1,91 @@
# Translations template for PROJECT.
# Copyright (C) 2020 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2020-07-08 14:25+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.8.0\n"
#: webapp/templates/layout.html:7
msgid "Project"
msgstr ""
#: webapp/templates/navigation.html:1
msgid "Home"
msgstr ""
#: webapp/templates/navigation.html:5
msgid "Logout"
msgstr ""
#: webapp/templates/navigation.html:7
msgid "Login"
msgstr ""
#: webapp/templates/blog_post/_create.html:5
#: webapp/templates/blog_post/_get.html:3
#: webapp/templates/blog_post/_table.html:5
#: webapp/templates/blog_post/_update.html:5
msgid "filename"
msgstr ""
#: webapp/templates/blog_post/_create.html:12
#: webapp/templates/blog_post/_get.html:6
#: webapp/templates/blog_post/_table.html:8
#: webapp/templates/blog_post/_update.html:13
msgid "body"
msgstr ""
#: webapp/templates/blog_post/_table.html:10
msgid "Actions"
msgstr ""
#: webapp/templates/blog_post/_title.html:2
#: webapp/templates/blog_post/create.html:4
msgid "BlogPost"
msgstr ""
#: webapp/templates/blog_post/create.html:4
#: webapp/templates/blog_post/list.html:5
#: webapp/templates/blog_post/search.html:5
#: webapp/templates/blog_post/table.html:5
msgid "Create"
msgstr ""
#: webapp/templates/blog_post/get.html:4
msgid "list"
msgstr ""
#: webapp/templates/blog_post/get.html:6
msgid "edit"
msgstr ""
#: webapp/templates/blog_post/get.html:7
msgid "delete"
msgstr ""
#: webapp/templates/blog_post/list.html:4
#: webapp/templates/blog_post/search.html:4
#: webapp/templates/blog_post/table.html:4
msgid "BlogPosts"
msgstr ""
#: webapp/templates/blog_post/search.html:4
msgid "Search results for"
msgstr ""
#: webapp/templates/blog_post/update.html:4
msgid "Edit"
msgstr ""

9
webapp/app.py Normal file
View File

@ -0,0 +1,9 @@
from flask import render_template
from webapp.routes import *
from oshipka.webapp import app
@app.route('/')
def home():
return render_template("home.html")

View File

@ -0,0 +1,2 @@
from oshipka.persistance import db
from webapp.models.blog_post import BlogPost

View File

@ -0,0 +1,11 @@
from oshipka.persistance import db, ModelController, index_service, LiberalBoolean, Ownable, Datable
class BlogPost(db.Model, ModelController, Ownable, Datable):
__searchable__ = ['body', ]
filename = db.Column(db.UnicodeText,)
body = db.Column(db.UnicodeText,)
index_service.searchables.append(BlogPost)

View File

@ -0,0 +1 @@
from webapp.models._blog_post import BlogPost

View File

@ -0,0 +1 @@
from webapp.routes.blog_post import *

View File

@ -0,0 +1,68 @@
"""
!!!AUTOGENERATED: DO NOT EDIT!!!
Edit the hooks in webapp/routes/blog_post_hooks.py instead
"""
from oshipka.webapp import app
from oshipka.webapp.views import ModelView
from webapp.models import BlogPost
from webapp.routes.blog_post_hooks import *
blog_post = ModelView(app, BlogPost)
blog_post.register_verb(view_context=get_view_context,
verb="get",
methods=['GET'],
per_item=True,
is_login_required=False,
the_roles_required=[],
)
blog_post.register_verb(view_context=list_view_context,
verb="list",
methods=['GET'],
per_item=False,
is_login_required=False,
the_roles_required=[],
)
blog_post.register_verb(view_context=table_view_context,
verb="table",
methods=['GET'],
per_item=False,
is_login_required=False,
the_roles_required=[],
)
blog_post.register_verb(view_context=search_view_context,
verb="search",
methods=['GET'],
per_item=False,
is_login_required=False,
the_roles_required=[],
)
blog_post.register_verb(view_context=create_view_context,
verb="create",
methods=['GET', 'POST'],
per_item=False,
is_login_required=True,
the_roles_required=['admin'],
)
blog_post.register_verb(view_context=update_view_context,
verb="update",
methods=['GET', 'POST'],
per_item=True,
is_login_required=True,
the_roles_required=['admin'],
)
blog_post.register_verb(view_context=delete_view_context,
verb="delete",
methods=['GET', 'POST'],
per_item=True,
is_login_required=True,
the_roles_required=['admin'],
)

View File

@ -0,0 +1,71 @@
from oshipka.webapp.views import ViewContext, default_get_args_func, default_get_func, default_list_func, \
default_get_form_func, default_create_func, default_update_func, default_delete_func, default_search_func
def get_template(vc):
vc.template = "{}/get.html".format(vc.model_view.model_name)
def list_template(vc):
vc.template = "{}/list.html".format(vc.model_view.model_name)
def table_template(vc):
vc.template = "{}/table.html".format(vc.model_view.model_name)
def search_template(vc):
vc.template = "{}/search.html".format(vc.model_view.model_name)
def create_template(vc):
vc.template = "{}/create.html".format(vc.model_view.model_name)
def update_template(vc):
vc.template = "{}/update.html".format(vc.model_view.model_name)
def delete_template(vc):
vc.template = "delete_instance.html".format(vc.model_view.model_name)
get_view_context = ViewContext(
filter_func=default_get_func,
template_func=get_template,
)
list_view_context = ViewContext(
filter_func=default_list_func,
template_func=list_template,
)
table_view_context = ViewContext(
filter_func=default_list_func,
template_func=table_template,
)
search_view_context = ViewContext(
filter_func=default_search_func,
template_func=list_template,
)
create_view_context = ViewContext(
args_get_func=default_get_form_func,
template_func=create_template,
execute_func=default_create_func,
)
update_view_context = ViewContext(
args_get_func=default_get_form_func,
filter_func=default_get_func,
template_func=update_template,
execute_func=default_update_func,
)
delete_view_context = ViewContext(
args_get_func=default_get_form_func,
filter_func=default_get_func,
template_func=delete_template,
execute_func=default_delete_func,
)

View File

@ -0,0 +1,19 @@
<form action="{{ url_for('create_blog_post') }}" method="post">
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('list_blog_post') }}"/>
<table>
<tr><td>
<label for="input-blog_post-filename">{{ _("filename") }}</label>:
</td><td>
<input id="input-blog_post-filename"
type="text" name="filename" autocomplete="off"
/>
</td></tr>
<tr><td>
<label for="input-blog_post-body">{{ _("body") }}</label>:
</td><td>
<textarea id="input-blog_post-body"
name="body"></textarea>
</td></tr>
</table>
<input type="submit">
</form>

View File

@ -0,0 +1,7 @@
{% if "filename" not in skip_list %}
<li id="display-blog_post-filename"><strong>{{ _("filename") }}</strong>: {{ instance.filename }}</li>
{% endif %}
{% if "body" not in skip_list %}
<li id="display-blog_post-body"><strong>{{ _("body") }}</strong>: {{ instance.body }}</li>
{% endif %}

View File

@ -0,0 +1,11 @@
{% for instance in instances %}
<li>
<a href="{{ url_for('get_blog_post', uuid=instance.id) }}">
{% include "blog_post/_title.html" %}</a>
|
[
<a href="{{ url_for('update_blog_post', uuid=instance.id, _next=request.path) }}">e</a> |
<a href="{{ url_for('delete_blog_post', uuid=instance.id, _next=request.path) }}">x</a>
]
</li>
{% endfor %}

View File

@ -0,0 +1,11 @@
{% for instance in instances %}
<li>
<a href="{{ url_for('get_blog_post', uuid=instance.id) }}">
{% include "blog_post/_title.html" %}</a>
|
[
<a href="{{ url_for('update_blog_post', uuid=instance.id) }}">e</a> |
<a href="{{ url_for('delete_blog_post', uuid=instance.id) }}">x</a>
]
</li>
{% endfor %}

View File

@ -0,0 +1,33 @@
<table class="full-width data-table">
<thead>
<tr>
{% if "filename" not in skip_columns %}
<th>{{ _("filename") }}</th>
{% endif %}
{% if "body" not in skip_columns %}
<th>{{ _("body") }}</th>
{% endif %}
<th>{{ _("Actions") }}</th>
</tr>
</thead>
<tbody>
{% for instance in instances %}
<tr>
{% if "filename" not in skip_columns %}
<td>
{{ instance.filename }}
</td>
{% endif %}
{% if "body" not in skip_columns %}
<td>
{{ instance.body }}
</td>
{% endif %}
<td>
<a href="{{ url_for('update_blog_post', uuid=instance.id, _next=request.path) }}">e</a> |
<a href="{{ url_for('delete_blog_post', uuid=instance.id, _next=request.path) }}">x</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,2 @@
{{ _("BlogPost") }} {{ instance.id }}

View File

@ -0,0 +1,20 @@
<form action="{{ url_for('update_blog_post', uuid=instance.id) }}" method="post">
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('get_blog_post', uuid=instance.id) }}"/>
<table>
<tr><td>
<label for="input-blog_post-filename">{{ _("filename") }}</label>:
</td><td>
<input id="input-blog_post-filename"
value="{{ instance.filename }}"
type="text" name="filename" autocomplete="off"
/>
</td></tr>
<tr><td>
<label for="input-blog_post-body">{{ _("body") }}</label>:
</td><td>
<textarea id="input-blog_post-body"
name="body">{{ instance.body }}</textarea>
</td></tr>
</table>
<input type="submit">
</form>

View File

@ -0,0 +1,6 @@
{% extends "layout.html" %}
{% block content %}
<h2>{{ _("Create") }} {{_("BlogPost") }}</h2>
{% include "blog_post/_create.html" %}
{% endblock %}

View File

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<a href="{{ url_for('list_blog_post', uuid=instance.id) }}">{{ _("list") }}</a> |
<h2>{% include "blog_post/_title.html" %}</h2>
<a href="{{ url_for('update_blog_post', uuid=instance.id, _next=request.path) }}">{{ _("edit") }}</a> |
<a href="{{ url_for('delete_blog_post', uuid=instance.id) }}">{{ _("delete") }}</a>
{% include "blog_post/_get.html" %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "layout.html" %}
{% block content %}
<h2>{{ _("BlogPosts") }}</h2>
<a href="{{ url_for('create_blog_post') }}">{{ _("Create") }}</a>
<br>
{% include "blog_post/_list.html" %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "layout.html" %}
{% block content %}
<h2>{{ _("Search results for") }} {{ _("BlogPosts") }}</h2>
<a href="{{ url_for('create_blog_post') }}">{{ _("Create") }}</a>
<br>
{% include "blog_post/_search.html" %}
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends "layout.html" %}
{% block content %}
<h2>{{ _("BlogPosts") }}</h2>
<a href="{{ url_for('create_blog_post') }}">{{ _("Create") }}</a>
<br>
{% include "blog_post/_table.html" %}
{% endblock %}

View File

@ -0,0 +1,6 @@
{% extends "layout.html" %}
{% block content %}
<h2>{{ _("Edit") }} {% include "blog_post/_title.html" %}</h2>
{% include "blog_post/_update.html" %}
{% endblock %}

View File

@ -0,0 +1,4 @@
{% extends "layout.html" %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,223 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{ _("Project") }}</title>
<style type="text/css">
body {
font-family: "Open Sans", Arial, sans-serif;
color: #444;
padding: 0 1em;
}
main {
max-width: 1200px;
margin: 0 auto;
}
nav {
position: fixed;
top: 0;
background: white;
width: 100%;
}
@media (min-width: 1400px) {
main {
margin: 0 150px;
}
aside {
position: fixed;
top: 0;
right: 0;
margin: 0;
padding: 1em;
}
#archive-container {
display: block !important;
}
}
input, textarea {
padding: 0.5em;
font-size: 1em;
box-sizing: border-box;
min-width: 300px;
}
input[type=number] {
min-width: 70px;
width: 70px;
}
input[type=checkbox] {
min-width: 50px;
width: 50px;
}
select {
min-width: 300px;
}
.full-width {
width: 100%;
}
textarea {
height: 5em;
}
.input-label {
font-weight: bold;
}
blockquote > h1 {
font-size: 2em;
}
blockquote > h2 {
font-size: 1.5em;
}
blockquote > h3 {
font-size: 1.25em;
}
cite:before {
content: "\2014 ";
}
cite {
font-size: 0.8em;
}
.search-form {
display: inline;
}
.flash {
padding: 4px;
margin: 4px;
top: 1em;
position: relative;
}
.flash.error {
border: 1px solid #f22;
background-color: #faa;
color: #f22;
}
.flash.message, .flash.info {
border: 1px solid #28f;
background-color: #cef;
color: #28f;
}
.flash.success {
border: 1px solid #2f8;
background-color: #cfe;
color: #094;
}
.danger {
color: #f22;
}
.pull-right {
float: right;
right: 50px;
position: relative;
}
pre {
white-space: pre-wrap;
}
table {
border-spacing: 0;
border-collapse: collapse;
}
table tr {
background-color: #fff;
border-top: 1px solid #aaa;
}
table tr:nth-child(2n) {
background-color: #f5f5f5;
}
table th, table td {
padding: 6px 12px;
border: 1px solid #ddd;
}
.vertical-padding {
height: 80px;
}
.video-inline {
width: 30%;
}
</style>
<link rel="stylesheet" type="text/css" href="{{ url_for('oshipka_bp.static', filename='css/chosen.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('oshipka_bp.static', filename='css/lightbox.css') }}">
<link rel="stylesheet" type="text/css" href="{{ url_for('oshipka_bp.static', filename='css/datatables.css') }}">
{% block style %}{% endblock %}
</head>
<body>
<nav>
{% include "navigation.html" %}
</nav>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="vertical-padding"></div>
<aside>
{% block aside %}{% endblock %}
</aside>
<main>
{% if ctx and ctx.messages %}
{% for message in ctx.messages %}
<div class="flash {{ message.type.value }}">
<a href="/messages/{{ message.id }}/dismiss"
style="float:right;">x</a>
{{ message.body|safe }}
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</main>
<script src="{{ url_for('oshipka_bp.static', filename='js/jquery.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/lightbox.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/chosen.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/datatables.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/fuse.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/draggable.js') }}"></script>
<script src="{{ url_for('oshipka_bp.static', filename='js/oshipka.js') }}"></script>
<script>
$("select").chosen({
inherit_select_classes: true,
});
lightbox.option({
'resizeDuration': 100,
'fadeDuration': 100,
'imageFadeDuration': 100,
});
$('.data-table').DataTable();
</script>
{% block script %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,9 @@
<a href="{{ url_for('home') }}">{{ _("Home") }}</a> |
<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

@ -0,0 +1,23 @@
name: BlogPost
searchable:
- body
interits:
- Ownable
- Datable
access:
- verb: all
login_required: true
roles_required:
- admin
- verb: get
login_required: false
- verb: list
login_required: false
- verb: table
login_required: false
- verb: search
login_required: false
columns:
- name: filename
- name: body
type: long_text

13
worker.py Normal file
View File

@ -0,0 +1,13 @@
from oshipka import worker
from oshipka.persistance import populate_static, init_db
from populate import populate_db
from webapp.app import app
if init_db(app):
populate_static(app)
populate_db(app)
if __name__ == "__main__":
worker.main()