Update application

This commit is contained in:
RemiZOffAlex
2019-10-27 19:40:03 +03:00
parent 1e9ac7eb97
commit 4a04efc355
28 changed files with 572 additions and 90 deletions

View File

@@ -51,6 +51,9 @@ from . import ns_login
# Профиль
from . import ns_profile
# Метки
from . import ns_tag
# Пользователи
from . import ns_user

View File

@@ -24,6 +24,12 @@ Base.query = db_session.query_property()
# Пользователи
from .users import User
# Метки
from .tag import Tag
# Статьи
from .page import Page, TagPage
Base.metadata.create_all(engine)
__all__ = []

74
myapp/models/page.py Normal file
View File

@@ -0,0 +1,74 @@
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'http://remizoffalex.ru'
import datetime
from sqlalchemy import Column, Boolean, Integer, ForeignKey, String, DateTime
from sqlalchemy.orm import relationship
from . import Base
class Page(Base):
"""
Страницы
"""
__tablename__ = "page"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
title = Column(String)
body = Column(String, default='')
created = Column(DateTime) # Дата создания
updated = Column(DateTime) # Дата обновления
# Связи
user = relationship(
"User",
primaryjoin="Page.user_id==User.id",
uselist=False
)
tags = relationship("TagPage", primaryjoin="Page.id==TagPage.page_id")
def __init__(self, user, title):
assert type(user).__name__=='User', app.logger.info('Не передан объект User')
self.user_id = user.id
self.title = title
self.created = datetime.datetime.now()
self.updated = datetime.datetime.now()
def __repr__(self):
return "<Page('{}')>".format(self.title)
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
class TagPage(Base):
"""Теги к страницам"""
__tablename__ = "tagpage"
id = Column(Integer, primary_key=True)
page_id = Column(Integer, ForeignKey('page.id'))
tag_id = Column(Integer, ForeignKey('tag.id'))
created = Column(DateTime) # Дата создания
# Связи
page = relationship("Page", primaryjoin="TagPage.page_id==Page.id", uselist=False)
tag = relationship("Tag", primaryjoin="TagPage.tag_id==Tag.id", uselist=False)
def __init__(self, page, tag):
assert type(page).__name__=='Page', app.logger.info('Не передан объект Page')
assert type(tag).__name__=='Tag', app.logger.info('Не передан объект Tag')
self.page_id = page.id
self.tag_id = tag.id
self.created = datetime.datetime.now()
def __repr__(self):
return "<TagPage('%s')>" % (self.id)
def as_dict(self):
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

43
myapp/models/tag.py Normal file
View File

@@ -0,0 +1,43 @@
"""
Таблицы:
Tag
TagLogo
"""
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'http://remizoffalex.ru'
import datetime
from sqlalchemy import Column, Integer, ForeignKey, String, DateTime
from sqlalchemy.orm import relationship
from . import Base
from .. import app
class Tag(Base):
"""
Теги, метки
"""
__tablename__ = "tag"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
# Связи
pages = relationship("TagPage", primaryjoin="Tag.id==TagPage.tag_id")
def __init__(self, name):
self.name = name
def __repr__(self):
return "<Tag('%s')>" % (self.name)
def as_dict(self):
"""
Возвращает словарь
"""
return {c.name: getattr(self, c.name) for c in self.__table__.columns}

View File

@@ -4,10 +4,35 @@ __license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from functools import wraps
from flask_jsonrpc import JSONRPC
from flask import session
from .. import app
from .. import app, models
def login_required(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if 'logged_in' in session and 'user_id' in session:
user = models.db_session.query(
models.User
).filter(
models.User.id==session['user_id']
).first()
if user:
return func(*args, **kwargs)
else:
session.pop('logged_in', None)
session.pop('user_id', None)
raise Exception('Необходима авторизация')
else:
raise Exception('Необходима авторизация')
return decorated_function
jsonrpc = JSONRPC(app, '/api')
from . import user
from . import (
page,
tag,
user
)

21
myapp/ns_api/page.py Normal file
View File

@@ -0,0 +1,21 @@
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from . import jsonrpc, login_required
from .. import models
@jsonrpc.method('page.update(title=str, text=str)')
@login_required
def page_update(title, text):
"""
Обновить статью
"""
result = {
"title": title,
"text": text
}
return result

28
myapp/ns_api/tag.py Normal file
View File

@@ -0,0 +1,28 @@
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from . import jsonrpc, login_required
from .. import models
@jsonrpc.method('tag.add(name=str)')
@login_required
def tag_add(name):
"""
Добавить новый тег
"""
exist = models.db_session.query(
models.Tag
).filter(
models.Tag.name == name
).first()
if exist:
raise ValueError
newTag = models.Tag(name)
models.db_session.add(newTag)
models.db_session.commit()
return newTag.as_dict()

View File

@@ -4,14 +4,12 @@ __license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from flask import abort, escape
from . import jsonrpc
from .. import models
@jsonrpc.method('users.getList')
def users_getList():
@jsonrpc.method('users')
def users_list():
"""
Показать список пользователей
"""

18
myapp/ns_tag/__init__.py Normal file
View File

@@ -0,0 +1,18 @@
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
import os
import jinja2
from . import views
from .. import app
my_loader = jinja2.ChoiceLoader([
app.jinja_loader,
jinja2.FileSystemLoader(os.path.dirname(os.path.abspath(__file__)) + "/templates"),
])
app.jinja_loader = my_loader

View File

@@ -0,0 +1,37 @@
{% extends "skeleton.html" %}
{% block body %}
{% raw %}
<h3>
<a class="btn btn-outline-secondary" :href="'/tag/' + tag.id"><i class="fa fa-chevron-left"></i></a>
Тег {{ tag.name }}</h3>
<hr />
{% endraw %}
{% endblock %}
{% block breadcrumb %}
{% raw %}
<ol class="breadcrumb mt-3">
<li class="breadcrumb-item"><a href="/"><i class="fa fa-home"></i></a></li>
<li class="breadcrumb-item"><a href="/tags">Список тегов</a></li>
<li class="breadcrumb-item">Тег {{ tag.id }}</li>
</ol>
{% endraw %}
{% endblock %}
{% block script %}
<script type="text/javascript" src="/static/components/backtotop.js"></script>
<link rel="stylesheet" href="/static/components/backtotop.css"></link>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
tag: {{ pagedata['tag']|tojson|safe }}
},
methods: {
}
})
</script>
{% endblock %}

View File

@@ -0,0 +1,80 @@
{% extends "skeleton.html" %}
{% block body %}
<h3>
<div class="btn btn-outline-success btn-sm float-right" v-on:click="showFormNewTag"><i class="fa fa-plus"></i></div>
Список тегов</h3>
<hr />
{% raw %}
<div class="form-group mt-3" v-if="formNewTag">
<div class="input-group mb-3">
<input class="form-control" type="text" v-model="newTag"/>
<div class="input-group-append">
<div class="btn btn-outline-success float-right" v-on:click="addTag"><i class="fa fa-save"></i></div>
</div>
</div>
</div>
<div class="row">
<div class="col py-2">
<a class="btn btn-outline-secondary mr-2 mb-3" v-for="(tag, tagIdx) in tags" :href="'/tag/' + tag.id">{{ tag.name }}</a>
</div>
</div>
{% endraw %}
<backtotop-component></backtotop-component>
{% endblock %}
{% block breadcrumb %}
<ol class="breadcrumb mt-3">
<li class="breadcrumb-item"><a href="/"><i class="fa fa-home"></i></a></li>
<li class="breadcrumb-item">Список тегов</li>
</ol>
{% endblock %}
{% block script %}
<script type="text/javascript" src="/static/components/backtotop.js"></script>
<link rel="stylesheet" href="/static/components/backtotop.css"></link>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
tags: {{ pagedata['tags']|tojson|safe }},
formNewTag: false,
newTag: ''
},
methods: {
addTag: function() {
/* Добавить тег */
var vm = this;
axios.post(
'/api',
{
"jsonrpc": "2.0",
"method": 'tag.add',
"params": {
"name": vm.newTag
},
"id": 1
}
).then(
function(response) {
if ('result' in response.data) {
vm.tags.push(response.data['result']);
vm.newTag = '';
}
}
);
},
showFormNewTag: function() {
/* Показать/скрыть форму добавления нового тега */
var vm = this;
vm.formNewTag = !vm.formNewTag;
}
}
})
</script>
{% endblock %}

54
myapp/ns_tag/views.py Normal file
View File

@@ -0,0 +1,54 @@
__author__ = 'RemiZOffAlex'
__copyright__ = '(c) RemiZOffAlex'
__license__ = 'MIT'
__email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from flask import render_template
from .. import app, models
@app.route('/tags')
def tags():
"""
Список меток
"""
pagedata = {}
pagedata['title'] = 'Список меток - ' + app.config['TITLE']
tags = models.db_session.query(
models.Tag
).order_by(
models.Tag.name.asc()
).all()
pagedata['tags'] = []
for tag in tags:
pagedata['tags'].append(tag.as_dict())
body = render_template('tags.html', pagedata=pagedata)
return body
@app.route('/tag/<int:id>')
def tag_id(id):
"""
Метка
"""
pagedata = {}
tag = models.db_session.query(
models.Tag
).filter(
models.Tag.id == id
).first()
if tag is None:
abort(404)
pagedata['title'] = 'Метка {} - {}'.format(
tag.name,
app.config['TITLE']
)
pagedata['tag'] = tag.as_dict()
body = render_template('tag.html', pagedata=pagedata)
return body

View File

@@ -4,8 +4,6 @@
<h3>Список пользователей</h3>
<hr />
<div class="btn btn-outline-primary" v-on:click="getUserList()">Получить/обновить список пользователей</div>
{% raw %}
<div class="row" v-for="(user, userIdx) in users">
<div class="col py-2">
@@ -48,7 +46,7 @@ var app = new Vue({
'/api',
{
"jsonrpc": "2.0",
"method": 'users.getList',
"method": 'users',
"params": {
},
"id": 1
@@ -61,6 +59,10 @@ var app = new Vue({
}
);
}
},
created: function() {
var vm = this;
vm.getUserList();
}
})
</script>

BIN
myapp/static/403.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
myapp/static/404.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
myapp/static/500.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,40 +0,0 @@
{% extends "skeleton.html" %}
{% block body %}
<script type="text/javascript" src="/static/ckeditor/ckeditor.js"></script>
<form method="post" action="/edit">
<div class="form-group">
{{ pagedata['form'].title.label }}
{{ pagedata['form'].title(class="form-control") }}
</div>
<div class="form-group">
{{ pagedata['form'].text.label }}
{{ pagedata['form'].text(class="form-control") }}
</div>
<button class="btn btn-outline-success float-right"><i class="fa fa-save-o"></i> Сохранить</button>
</form>
{% endblock %}
{% block script %}
<script type="text/javascript" src="/static/ckeditor/ckeditor.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
},
methods: {
},
mounted: function () {
CKEDITOR.replace( 'text', {
customConfig: '/static/js/ckeditor-conf.js'
} );
}
})
</script>
{% endblock %}

View File

@@ -0,0 +1,10 @@
{% extends "skeleton.html" %}
{% block body %}
<h3 class="text-danger">{{ error_code }}: {{ error_message }}</h3>
<hr />
<h4 class="text-center">Это фиаско, братан!</h4>
<p class="text-center"><img class="img-thumbnail" src="/static/{{ error_code }}.jpg" /></p>
{% endblock %}

View File

@@ -0,0 +1,7 @@
<div class="row mt-3 py-3 bg-light">
<div class="col-md-1"></div>
<div class="col-md-10">
&copy; <a href="https://remizoffalex.ru/" target="_blank">RemiZOffAlex</a>
</div>
<div class="col-md-1"></div>
</div>

View File

@@ -5,9 +5,6 @@
<link rel="shortcut icon" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
<link rel="stylesheet" href="/static/css/font-awesome.min.css" />
<script type="text/javascript" src="/static/js/jquery-3.3.1.slim.min.js"></script>
<script type="text/javascript" src="/static/js/popper.min.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/static/js/vue.min.js"></script>
<script type="text/javascript" src="/static/js/axios.min.js"></script>
<title>{{ pagedata['title'] }}</title>

View File

@@ -1,4 +1,5 @@
<div class="container-fluid py-2">
<div class="row">
<div class="col py-2">
{% if session.logged_in %}
<div class="btn-group float-right">
@@ -10,7 +11,9 @@
{% endif %}
<a class="btn btn-outline-secondary border-0" href="/"><i class="fa fa-home"></i></a>
<a class="btn btn-outline-secondary border-0" href="/edit"><i class="fa fa-edit"></i> Редактор</a>
<a class="btn btn-outline-secondary border-0" href="/users">Vue & axios</a>
<a class="btn btn-outline-secondary border-0" href="/page">Статья</a>
<a class="btn btn-outline-secondary border-0" href="/tags">Метки</a>
<a class="btn btn-outline-secondary border-0" href="/users">Пользователи</a>
<a class="btn btn-outline-secondary border-0" href="/api/browse">API JSON-RPC</a>
</div>
</div>

View File

@@ -1,8 +1,112 @@
{% extends "skeleton.html" %}
{% block body %}
<h3>{{ pagedata['title'] }}</h3>
{% raw %}
<h3>
<div class="btn btn-outline-secondary float-right" v-on:click="showPanel(panels.edit)"><i class="fa fa-edit"></i></div>
{{ page.title }}</h3>
<hr />
{{ pagedata['text']|safe }}
<div class="row" v-if="panels.edit.visible">
<div class="col">
{% endblock body %}
<div class="form-group">
<label for="title">Заголовок</label>
<input id="title" name="title" type="text" value="Заголовок страницы" class="form-control" v-model="newPage.title">
</div>
<div class="form-group">
<label for="text">Текст</label>
<textarea class="form-control" id="text" name="text" v-model="newPage.text"></textarea>
</div>
<div class="row">
<div class="col">
<button class="btn btn-outline-success float-right" v-on:click="send"><i class="fa fa-save-o"></i> Сохранить</button>
</div>
</div>
<div class="row mt-3" v-if="error">
<div class="col bg-danger text-white" v-html="error">
</div>
</div>
</div>
</div>
<span v-html="page.text" v-else></span>
{% endraw %}
{% endblock %}
{% block breadcrumb %}
<ol class="breadcrumb mt-3">
<li class="breadcrumb-item"><a href="/"><i class="fa fa-home"></i></a></li>
<li class="breadcrumb-item active">Редактирование страницы</li>
</ol>
{% endblock %}
{% block script %}
<script type="text/javascript" src="/static/ckeditor/ckeditor.js"></script>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
page: {{ pagedata['page']|tojson|safe }},
newPage: {{ pagedata['page']|tojson|safe }},
panels: {
edit: {
visible: false
}
},
error: null
},
methods: {
showPanel: function(panel) {
/* Показать/скрыть панель */
panel.visible = !panel.visible;
},
send: function() {
var vm = this;
var value = CKEDITOR.instances["text"].getData();
if (value != vm.newPage.text) {
vm.newPage.text = value;
}
axios.post(
'/api',
{
"jsonrpc": "2.0",
"method": 'page.update',
"params": {
"title": vm.newPage.title,
"text": vm.newPage.text
},
"id": 1
}
).then(
function(response) {
if ('result' in response.data) {
vm.page = response.data['result'];
vm.newPage = vm.page;
vm.error = null;
} else if ('error' in response.data) {
vm.error = response.data['error'].message;
}
}
);
}
},
updated: function() {
var vm = this;
for(var instanceName in CKEDITOR.instances) {
CKEDITOR.instances[instanceName].destroy(true);
}
if (vm.panels.edit.visible) {
CKEDITOR.replace( "text", {
customConfig: '/static/js/ckeditor-conf.js'
} );
}
}
})
</script>
{% endblock %}

View File

View File

@@ -4,11 +4,16 @@
<body>
<section id="app">
<div class="container">
{% include 'navbar.html' %}
<div class="container">
{% block body %}
{% endblock body %}
{% block breadcrumb %}
{% endblock %}
{% include 'footer.html' %}
</div>
</section>

View File

@@ -5,7 +5,7 @@ __email__ = 'remizoffalex@mail.ru'
__url__ = 'https://remizoffalex.ru'
from myapp import app
from flask import Flask, render_template, request
from flask import Flask, render_template, request, Response
from . import forms, models
@@ -19,23 +19,44 @@ def index():
return body
@app.route('/edit', methods=['GET', 'POST'])
def edit():
@app.route('/page')
def page():
pagedata = {}
pagedata['title'] = app.config['TITLE']
pagedata['form'] = forms.PageEdit(
request.form,
data={
'title': 'Заголовок страницы',
'text': '''<p>Текст</p>
pagedata['page'] = {
'title': 'Заголовок страницы',
'text': '''<p>Текст</p>
<p class="alert alert-danger">Внимание!</p>'''
}
)
if request.method == 'POST':
if pagedata['form'].validate():
pagedata['title'] = pagedata['form'].title.data
pagedata['text'] = pagedata['form'].text.data
body = render_template('page.html', pagedata=pagedata)
return body
body = render_template('edit.html', pagedata=pagedata)
}
body = render_template('page.html', pagedata=pagedata)
return body
@app.route("/robots.txt")
def robots_txt():
body = render_template("robots.txt")
return Response(body, mimetype='text/plain')
# noinspection PyUnusedLocal
@app.errorhandler(404)
def error_missing(exception):
pagedata = {}
error_message = "Не судьба..."
return render_template("error.html", error_code=404, error_message=error_message, pagedata=pagedata), 404
# noinspection PyUnusedLocal
@app.errorhandler(403)
def error_unauthorized(exception):
pagedata = {}
error_message = "У вас нет прав"
return render_template("error.html", error_code=403, error_message=error_message, pagedata=pagedata), 403
# noinspection PyUnusedLocal
@app.errorhandler(500)
def error_crash(exception):
pagedata = {}
error_message = "Вот незадача..."
return render_template("error.html", error_code=500, error_message=error_message, pagedata=pagedata), 500