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
+3
View File
@@ -51,6 +51,9 @@ from . import ns_login
# Профиль
from . import ns_profile
# Метки
from . import ns_tag
# Пользователи
from . import ns_user
+6
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
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
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}
+27 -2
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
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
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()
+2 -4
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
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
+37
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 %}
+80
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
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
+5 -3
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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

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
-5
View File
File diff suppressed because one or more lines are too long
-40
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 %}
+10
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 %}
+7
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>
-3
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>
+6 -3
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>
+107 -3
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
+6 -1
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>
+38 -17
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