Update application
This commit is contained in:
@@ -51,6 +51,9 @@ from . import ns_login
|
|||||||
# Профиль
|
# Профиль
|
||||||
from . import ns_profile
|
from . import ns_profile
|
||||||
|
|
||||||
|
# Метки
|
||||||
|
from . import ns_tag
|
||||||
|
|
||||||
# Пользователи
|
# Пользователи
|
||||||
from . import ns_user
|
from . import ns_user
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ Base.query = db_session.query_property()
|
|||||||
# Пользователи
|
# Пользователи
|
||||||
from .users import User
|
from .users import User
|
||||||
|
|
||||||
|
# Метки
|
||||||
|
from .tag import Tag
|
||||||
|
|
||||||
|
# Статьи
|
||||||
|
from .page import Page, TagPage
|
||||||
|
|
||||||
Base.metadata.create_all(engine)
|
Base.metadata.create_all(engine)
|
||||||
|
|
||||||
__all__ = []
|
__all__ = []
|
||||||
|
|||||||
74
myapp/models/page.py
Normal file
74
myapp/models/page.py
Normal 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
43
myapp/models/tag.py
Normal 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}
|
||||||
@@ -4,10 +4,35 @@ __license__ = 'MIT'
|
|||||||
__email__ = 'remizoffalex@mail.ru'
|
__email__ = 'remizoffalex@mail.ru'
|
||||||
__url__ = 'https://remizoffalex.ru'
|
__url__ = 'https://remizoffalex.ru'
|
||||||
|
|
||||||
|
from functools import wraps
|
||||||
from flask_jsonrpc import JSONRPC
|
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')
|
jsonrpc = JSONRPC(app, '/api')
|
||||||
|
|
||||||
from . import user
|
from . import (
|
||||||
|
page,
|
||||||
|
tag,
|
||||||
|
user
|
||||||
|
)
|
||||||
|
|||||||
21
myapp/ns_api/page.py
Normal file
21
myapp/ns_api/page.py
Normal 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
28
myapp/ns_api/tag.py
Normal 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()
|
||||||
@@ -4,14 +4,12 @@ __license__ = 'MIT'
|
|||||||
__email__ = 'remizoffalex@mail.ru'
|
__email__ = 'remizoffalex@mail.ru'
|
||||||
__url__ = 'https://remizoffalex.ru'
|
__url__ = 'https://remizoffalex.ru'
|
||||||
|
|
||||||
from flask import abort, escape
|
|
||||||
|
|
||||||
from . import jsonrpc
|
from . import jsonrpc
|
||||||
from .. import models
|
from .. import models
|
||||||
|
|
||||||
|
|
||||||
@jsonrpc.method('users.getList')
|
@jsonrpc.method('users')
|
||||||
def users_getList():
|
def users_list():
|
||||||
"""
|
"""
|
||||||
Показать список пользователей
|
Показать список пользователей
|
||||||
"""
|
"""
|
||||||
|
|||||||
18
myapp/ns_tag/__init__.py
Normal file
18
myapp/ns_tag/__init__.py
Normal 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
myapp/ns_tag/templates/tag.html
Normal file
37
myapp/ns_tag/templates/tag.html
Normal 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
myapp/ns_tag/templates/tags.html
Normal file
80
myapp/ns_tag/templates/tags.html
Normal 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
54
myapp/ns_tag/views.py
Normal 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
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
<h3>Список пользователей</h3>
|
<h3>Список пользователей</h3>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div class="btn btn-outline-primary" v-on:click="getUserList()">Получить/обновить список пользователей</div>
|
|
||||||
|
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<div class="row" v-for="(user, userIdx) in users">
|
<div class="row" v-for="(user, userIdx) in users">
|
||||||
<div class="col py-2">
|
<div class="col py-2">
|
||||||
@@ -48,7 +46,7 @@ var app = new Vue({
|
|||||||
'/api',
|
'/api',
|
||||||
{
|
{
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
"method": 'users.getList',
|
"method": 'users',
|
||||||
"params": {
|
"params": {
|
||||||
},
|
},
|
||||||
"id": 1
|
"id": 1
|
||||||
@@ -61,6 +59,10 @@ var app = new Vue({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
created: function() {
|
||||||
|
var vm = this;
|
||||||
|
vm.getUserList();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
BIN
myapp/static/403.jpg
Normal file
BIN
myapp/static/403.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
myapp/static/404.jpg
Normal file
BIN
myapp/static/404.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
myapp/static/500.jpg
Normal file
BIN
myapp/static/500.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
7
myapp/static/js/bootstrap.min.js
vendored
7
myapp/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
2
myapp/static/js/jquery-3.3.1.slim.min.js
vendored
2
myapp/static/js/jquery-3.3.1.slim.min.js
vendored
File diff suppressed because one or more lines are too long
5
myapp/static/js/popper.min.js
vendored
5
myapp/static/js/popper.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -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
myapp/templates/error.html
Normal file
10
myapp/templates/error.html
Normal 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
myapp/templates/footer.html
Normal file
7
myapp/templates/footer.html
Normal 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">
|
||||||
|
© <a href="https://remizoffalex.ru/" target="_blank">RemiZOffAlex</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1"></div>
|
||||||
|
</div>
|
||||||
@@ -5,9 +5,6 @@
|
|||||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||||
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="/static/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="/static/css/font-awesome.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/vue.min.js"></script>
|
||||||
<script type="text/javascript" src="/static/js/axios.min.js"></script>
|
<script type="text/javascript" src="/static/js/axios.min.js"></script>
|
||||||
<title>{{ pagedata['title'] }}</title>
|
<title>{{ pagedata['title'] }}</title>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<div class="container-fluid py-2">
|
<div class="row">
|
||||||
|
<div class="col py-2">
|
||||||
|
|
||||||
{% if session.logged_in %}
|
{% if session.logged_in %}
|
||||||
<div class="btn-group float-right">
|
<div class="btn-group float-right">
|
||||||
@@ -10,7 +11,9 @@
|
|||||||
{% endif %}
|
{% 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="/"><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="/page">Статья</a>
|
||||||
<a class="btn btn-outline-secondary border-0" href="/users">Vue & axios</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>
|
<a class="btn btn-outline-secondary border-0" href="/api/browse">API JSON-RPC</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,112 @@
|
|||||||
{% extends "skeleton.html" %}
|
{% extends "skeleton.html" %}
|
||||||
{% block body %}
|
{% 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 %}
|
||||||
|
|||||||
0
myapp/templates/robots.txt
Normal file
0
myapp/templates/robots.txt
Normal file
@@ -4,11 +4,16 @@
|
|||||||
<body>
|
<body>
|
||||||
<section id="app">
|
<section id="app">
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
{% include 'navbar.html' %}
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
||||||
|
{% block breadcrumb %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% include 'footer.html' %}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ __email__ = 'remizoffalex@mail.ru'
|
|||||||
__url__ = 'https://remizoffalex.ru'
|
__url__ = 'https://remizoffalex.ru'
|
||||||
|
|
||||||
from myapp import app
|
from myapp import app
|
||||||
from flask import Flask, render_template, request
|
from flask import Flask, render_template, request, Response
|
||||||
|
|
||||||
from . import forms, models
|
from . import forms, models
|
||||||
|
|
||||||
@@ -19,23 +19,44 @@ def index():
|
|||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
||||||
@app.route('/edit', methods=['GET', 'POST'])
|
@app.route('/page')
|
||||||
def edit():
|
def page():
|
||||||
pagedata = {}
|
pagedata = {}
|
||||||
pagedata['title'] = app.config['TITLE']
|
pagedata['title'] = app.config['TITLE']
|
||||||
pagedata['form'] = forms.PageEdit(
|
pagedata['page'] = {
|
||||||
request.form,
|
'title': 'Заголовок страницы',
|
||||||
data={
|
'text': '''<p>Текст</p>
|
||||||
'title': 'Заголовок страницы',
|
|
||||||
'text': '''<p>Текст</p>
|
|
||||||
<p class="alert alert-danger">Внимание!</p>'''
|
<p class="alert alert-danger">Внимание!</p>'''
|
||||||
}
|
}
|
||||||
)
|
body = render_template('page.html', pagedata=pagedata)
|
||||||
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)
|
|
||||||
return body
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user