Compare commits
28 Commits
Author | SHA1 | Date |
---|---|---|
|
4d3a309940 | |
|
c2e68881d9 | |
|
cdf9bfe072 | |
|
50e74ccf38 | |
|
52d1b84658 | |
|
9cecc960db | |
|
985301d27f | |
|
30f2dbde98 | |
|
54c40985cb | |
|
d4722945e9 | |
|
e2743db969 | |
|
3bc178f2cc | |
|
18724c455a | |
|
2c6c2ebbe9 | |
|
6af1d3c7cb | |
|
21a818d71e | |
|
c126531e49 | |
|
3b63421f5f | |
|
d8f8b36469 | |
|
009415d261 | |
|
9301f984c3 | |
|
40274136fe | |
|
eb5eb79383 | |
|
48be7e96d9 | |
|
911d3ef845 | |
|
3785ab37e6 | |
|
6103b1fd22 | |
|
199eb0f3ae |
|
@ -1,3 +1,6 @@
|
|||
pyproger/uploads/*
|
||||
!pyproger/uploads/.gitkeep
|
||||
|
||||
config.ini
|
||||
data/
|
||||
|
||||
|
|
|
@ -55,6 +55,11 @@
|
|||
|
||||
Перейдите в браузере по адресу 127.0.0.1:5000 для доступа к блогу или 127.0.0.1:5000/admin в админ панель
|
||||
|
||||
### TODO
|
||||
- Добавить на страницы постов "Оглавление" со ссылками на статьи
|
||||
- Добавить подписку на статьи
|
||||
- Добавить комментарии к постам
|
||||
|
||||
### Авторы
|
||||
- Сергей Ванюшкин <pi3c@yandex.ru>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import uuid
|
||||
from datetime import datetime as dt
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
@ -8,15 +9,54 @@ if os.path.exists(dotenv_path):
|
|||
load_dotenv(dotenv_path)
|
||||
|
||||
|
||||
def cls():
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
|
||||
|
||||
with open(dotenv_path, "a") as f:
|
||||
print("Генерирую SECRET_KEY...")
|
||||
if os.getenv("BRAND") is None:
|
||||
cls()
|
||||
print("\033[32m{}\033[0m ".format("Введите название проекта."))
|
||||
print(
|
||||
"Это название будет отображаться в строке",
|
||||
"меню и футере на страницах сайта",
|
||||
sep="\n",
|
||||
)
|
||||
br = input("-> ")
|
||||
f.writelines(f"BRAND={br}\n")
|
||||
|
||||
print("\033[32m{}\033[0m ".format("Настройка блока copyright в футере сайта"))
|
||||
if os.getenv("COPYRIGHT_YEAR") is None:
|
||||
start_date = dt.utcnow().strftime("%Y")
|
||||
f.writelines(f"COPYRIGHT_YEAR={start_date}\n")
|
||||
|
||||
if os.getenv("COPYRIGHT_NAME") is None:
|
||||
name = input("Введите имя для для футера:")
|
||||
f.writelines(f"COPYRIGHT_NAME={name}\n")
|
||||
|
||||
if os.getenv("COPYRIGHT_LINK") is None:
|
||||
print("Введите ссылку для футера:")
|
||||
print(
|
||||
"email('something@somthing.else) или веб адрес полностью('http://anysite.any')"
|
||||
)
|
||||
link = input(">:")
|
||||
if link.startswith("http"):
|
||||
f.writelines(f"COPYRIGHT_LINK={link}\n")
|
||||
else:
|
||||
f.writelines(f"COPYRIGHT_LINK=mailto:{link}\n")
|
||||
|
||||
if os.getenv("COPYRIGHT_CITY") is None:
|
||||
name = input("Введите свой город для для футера:")
|
||||
f.writelines(f"COPYRIGHT_CITY={name}\n")
|
||||
|
||||
print("\033[32m{}\033[0m ".format("Генерирую SECRET_KEY..."))
|
||||
if os.getenv("SECRET_KEY") is None:
|
||||
f.writelines(f"SECRET_KEY={uuid.uuid4().hex}\n")
|
||||
print("_Ok_")
|
||||
else:
|
||||
print("SECRET_KEY уже установлен, прорускаю")
|
||||
print("SECRET_KEY уже установлен, пропускаю")
|
||||
|
||||
print('Генерирую "Соль"...')
|
||||
print("\033[32m{}\033[0m ".format('Генерирую "Соль"...'))
|
||||
if os.getenv("SECURITY_PASSWORD_SALT") is None:
|
||||
f.writelines(f"SECURITY_PASSWORD_SALT={uuid.uuid4().hex}\n")
|
||||
print("_Ok_")
|
||||
|
@ -24,12 +64,14 @@ with open(dotenv_path, "a") as f:
|
|||
print("SECURITY_PASSWORD_SALT уже установлен, пропускаю")
|
||||
|
||||
if os.getenv("SQLALCHEMY_DATABASE_URI") is None:
|
||||
print("Настроки подключения к базе данных Posgresql:")
|
||||
print(
|
||||
"\033[32m{}\033[0m ".format("Настроки подключения к базе данных Posgresql:")
|
||||
)
|
||||
login = input("Введите логин пользователя бд: ")
|
||||
passwd = input("Пароль: ")
|
||||
db = input("Название бд (по умолчанию pyproger):") or "pyproger"
|
||||
db = input("Название бд (по умолчанию pyprogerdb):") or "pyprogerdb"
|
||||
ip = input("Адрес бд (по умолчанию localhost)") or "localhost"
|
||||
port = input("Порт подключения: (по умолчанию 5432)") or "5432"
|
||||
f.writelines(
|
||||
f"SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://{login}:{passwd}@{ip}:{port}/{db}"
|
||||
f"SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://{login}:{passwd}@{ip}:{port}/{db}\n"
|
||||
)
|
||||
|
|
|
@ -7,6 +7,7 @@ Create Date: ${create_date}
|
|||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import flask_security
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 5e6d181b4b74
|
||||
Revises: e24055e16b26
|
||||
Create Date: 2023-10-04 08:46:50.473590
|
||||
Revision ID: 055327bef08e
|
||||
Revises: 849ff22feb97
|
||||
Create Date: 2023-10-23 09:29:00.193470
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import flask_security
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '5e6d181b4b74'
|
||||
down_revision = 'e24055e16b26'
|
||||
revision = '055327bef08e'
|
||||
down_revision = '849ff22feb97'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
@ -19,8 +20,7 @@ depends_on = None
|
|||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('page', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('update_datetime', sa.DateTime(), nullable=True))
|
||||
batch_op.create_unique_constraint(None, ['id'])
|
||||
batch_op.add_column(sa.Column('header_description', sa.Text(), nullable=True))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
@ -28,7 +28,6 @@ def upgrade():
|
|||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('page', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
batch_op.drop_column('update_datetime')
|
||||
batch_op.drop_column('header_description')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,126 @@
|
|||
"""initial_migration
|
||||
|
||||
Revision ID: 68537cc1688c
|
||||
Revises:
|
||||
Create Date: 2023-10-10 22:17:40.476992
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import flask_security
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '68537cc1688c'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('footer_icons',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=30), nullable=True),
|
||||
sa.Column('bootstrap_ico', sa.String(length=20), nullable=True),
|
||||
sa.Column('link', sa.String(length=100), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
op.create_table('page',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=20), nullable=True),
|
||||
sa.Column('slug', sa.String(length=50), nullable=False),
|
||||
sa.Column('text', sa.Text(), nullable=True),
|
||||
sa.Column('update_datetime', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
op.create_table('role',
|
||||
sa.Column('name', sa.String(length=80), nullable=False),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('description', sa.String(length=255), nullable=True),
|
||||
sa.Column('permissions', flask_security.datastore.AsaList(), nullable=True),
|
||||
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
op.create_table('site_headers',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=20), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('content', sa.Text(), nullable=True),
|
||||
sa.Column('enabled', sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
op.create_table('tag',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('tag', sa.String(length=20), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('user',
|
||||
sa.Column('first_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('last_name', sa.String(length=255), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('email', sa.String(length=255), nullable=False),
|
||||
sa.Column('username', sa.String(length=255), nullable=True),
|
||||
sa.Column('password', sa.String(length=255), nullable=False),
|
||||
sa.Column('active', sa.Boolean(), nullable=False),
|
||||
sa.Column('fs_uniquifier', sa.String(length=64), nullable=False),
|
||||
sa.Column('confirmed_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('last_login_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('current_login_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('last_login_ip', sa.String(length=64), nullable=True),
|
||||
sa.Column('current_login_ip', sa.String(length=64), nullable=True),
|
||||
sa.Column('login_count', sa.Integer(), nullable=True),
|
||||
sa.Column('tf_primary_method', sa.String(length=64), nullable=True),
|
||||
sa.Column('tf_totp_secret', sa.String(length=255), nullable=True),
|
||||
sa.Column('tf_phone_number', sa.String(length=128), nullable=True),
|
||||
sa.Column('create_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.Column('update_datetime', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email'),
|
||||
sa.UniqueConstraint('fs_uniquifier')
|
||||
)
|
||||
op.create_table('post',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('author', sa.Integer(), nullable=True),
|
||||
sa.Column('slug', sa.String(length=100), nullable=False),
|
||||
sa.Column('title', sa.Text(), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=False),
|
||||
sa.Column('published', sa.Boolean(), nullable=True),
|
||||
sa.Column('create_datetime', sa.DateTime(), nullable=True),
|
||||
sa.Column('update_datetime', sa.DateTime(), nullable=True),
|
||||
sa.Column('text', sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['author'], ['user.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
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'], )
|
||||
)
|
||||
op.create_table('tag_post',
|
||||
sa.Column('tag_id', sa.Integer(), nullable=True),
|
||||
sa.Column('post_id', sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['post_id'], ['post.id'], ),
|
||||
sa.ForeignKeyConstraint(['tag_id'], ['tag.id'], )
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('tag_post')
|
||||
op.drop_table('roles_users')
|
||||
op.drop_table('post')
|
||||
op.drop_table('user')
|
||||
op.drop_table('tag')
|
||||
op.drop_table('site_headers')
|
||||
op.drop_table('role')
|
||||
op.drop_table('page')
|
||||
op.drop_table('footer_icons')
|
||||
# ### end Alembic commands ###
|
|
@ -0,0 +1,53 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 849ff22feb97
|
||||
Revises: 68537cc1688c
|
||||
Create Date: 2023-10-23 08:34:08.251453
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import flask_security
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '849ff22feb97'
|
||||
down_revision = '68537cc1688c'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('footer_icons', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint(None, ['id'])
|
||||
|
||||
with op.batch_alter_table('page', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint(None, ['id'])
|
||||
|
||||
with op.batch_alter_table('post', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('header_description', sa.Text(), nullable=True))
|
||||
batch_op.create_unique_constraint(None, ['id'])
|
||||
|
||||
with op.batch_alter_table('site_headers', schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint(None, ['id'])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('site_headers', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
|
||||
with op.batch_alter_table('post', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
batch_op.drop_column('header_description')
|
||||
|
||||
with op.batch_alter_table('page', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
|
||||
with op.batch_alter_table('footer_icons', schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_='unique')
|
||||
|
||||
# ### end Alembic commands ###
|
|
@ -1,132 +0,0 @@
|
|||
"""initial_migration
|
||||
|
||||
Revision ID: 8c415e462cb3
|
||||
Revises:
|
||||
Create Date: 2023-09-22 12:19:22.923813
|
||||
|
||||
"""
|
||||
import flask_security
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "8c415e462cb3"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"role",
|
||||
sa.Column("name", sa.String(length=80), nullable=False),
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("description", sa.String(length=255), nullable=True),
|
||||
sa.Column("permissions", flask_security.datastore.AsaList(), nullable=True),
|
||||
sa.Column(
|
||||
"update_datetime",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
op.create_table(
|
||||
"tag",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("tag", sa.String(length=20), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_table(
|
||||
"user",
|
||||
sa.Column("first_name", sa.String(length=255), nullable=True),
|
||||
sa.Column("last_name", sa.String(length=255), nullable=True),
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("email", sa.String(length=255), nullable=False),
|
||||
sa.Column("username", sa.String(length=255), nullable=True),
|
||||
sa.Column("password", sa.String(length=255), nullable=False),
|
||||
sa.Column("active", sa.Boolean(), nullable=False),
|
||||
sa.Column("fs_uniquifier", sa.String(length=64), nullable=False),
|
||||
sa.Column("confirmed_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_login_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("current_login_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("last_login_ip", sa.String(length=64), nullable=True),
|
||||
sa.Column("current_login_ip", sa.String(length=64), nullable=True),
|
||||
sa.Column("login_count", sa.Integer(), nullable=True),
|
||||
sa.Column("tf_primary_method", sa.String(length=64), nullable=True),
|
||||
sa.Column("tf_totp_secret", sa.String(length=255), nullable=True),
|
||||
sa.Column("tf_phone_number", sa.String(length=128), nullable=True),
|
||||
sa.Column(
|
||||
"create_datetime",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"update_datetime",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("email"),
|
||||
sa.UniqueConstraint("fs_uniquifier"),
|
||||
)
|
||||
op.create_table(
|
||||
"post",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("author", sa.Integer(), nullable=True),
|
||||
sa.Column("slug", sa.String(length=100), nullable=False),
|
||||
sa.Column("title", sa.Text(), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=False),
|
||||
sa.Column("published", sa.Boolean(), nullable=True),
|
||||
sa.Column("create_datetime", sa.DateTime(), nullable=True),
|
||||
sa.Column("update_datetime", sa.DateTime(), nullable=True),
|
||||
sa.Column("text", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["author"],
|
||||
["user.id"],
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("id"),
|
||||
)
|
||||
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"],
|
||||
),
|
||||
)
|
||||
op.create_table(
|
||||
"tag_post",
|
||||
sa.Column("tag_id", sa.Integer(), nullable=True),
|
||||
sa.Column("post_id", sa.Integer(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["post_id"],
|
||||
["post.id"],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["tag_id"],
|
||||
["tag.id"],
|
||||
),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("tag_post")
|
||||
op.drop_table("roles_users")
|
||||
op.drop_table("post")
|
||||
op.drop_table("user")
|
||||
op.drop_table("tag")
|
||||
op.drop_table("role")
|
||||
# ### end Alembic commands ###
|
|
@ -1,42 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: e24055e16b26
|
||||
Revises: 8c415e462cb3
|
||||
Create Date: 2023-10-02 09:30:10.566908
|
||||
|
||||
"""
|
||||
import flask_security
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "e24055e16b26"
|
||||
down_revision = "8c415e462cb3"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"page",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("name", sa.String(length=20), nullable=True),
|
||||
sa.Column("slug", sa.String(length=50), nullable=False),
|
||||
sa.Column("text", sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("id"),
|
||||
)
|
||||
with op.batch_alter_table("post", schema=None) as batch_op:
|
||||
batch_op.create_unique_constraint(None, ["id"])
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table("post", schema=None) as batch_op:
|
||||
batch_op.drop_constraint(None, type_="unique")
|
||||
|
||||
op.drop_table("page")
|
||||
# ### end Alembic commands ###
|
|
@ -1,36 +0,0 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: ea0fde3014d7
|
||||
Revises: 5e6d181b4b74
|
||||
Create Date: 2023-10-07 14:32:08.410786
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'ea0fde3014d7'
|
||||
down_revision = '5e6d181b4b74'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('site_headers',
|
||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column('name', sa.String(length=20), nullable=True),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('content', sa.Text(), nullable=True),
|
||||
sa.Column('enabled', sa.Boolean(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('site_headers')
|
||||
# ### end Alembic commands ###
|
|
@ -35,13 +35,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
|||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.6.2"
|
||||
version = "1.6.3"
|
||||
description = "Fast, simple object-to-object and broadcast signaling"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"},
|
||||
{file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"},
|
||||
{file = "blinker-1.6.3-py3-none-any.whl", hash = "sha256:296320d6c28b006eb5e32d4712202dbcdcbf5dc482da298c2f44881c43884aaa"},
|
||||
{file = "blinker-1.6.3.tar.gz", hash = "sha256:152090d27c1c5c722ee7e48504b02d76502811ce02e1523553b4cf8c8b3d3a8d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -222,22 +222,23 @@ Flask = "*"
|
|||
|
||||
[[package]]
|
||||
name = "flask-security-too"
|
||||
version = "5.3.0"
|
||||
version = "5.3.1"
|
||||
description = "Quickly add security features to your Flask application."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "Flask-Security-Too-5.3.0.tar.gz", hash = "sha256:9f5d830913eac66f18845795ae5f7d044bdd0d836aeabccfebadab6a29f79354"},
|
||||
{file = "Flask_Security_Too-5.3.0-py3-none-any.whl", hash = "sha256:c358893d9de3332e523288e6751b51d354973e25e70b0b4a7ef229eca9f910db"},
|
||||
{file = "Flask-Security-Too-5.3.1.tar.gz", hash = "sha256:1dafe00c611ce3811e7fe1686ecd7750938806a1ec3c5a278185f31958895d3c"},
|
||||
{file = "Flask_Security_Too-5.3.1-py3-none-any.whl", hash = "sha256:159ed080dce4a717c2852eac50443221f50b391f5af6f03c82febc3740d572d1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
email-validator = ">=1.1.1"
|
||||
Flask = ">=2.3.0"
|
||||
Flask = ">=2.3.2"
|
||||
Flask-Login = ">=0.6.2"
|
||||
Flask-Principal = ">=0.4.0"
|
||||
Flask-WTF = ">=1.1.1"
|
||||
Flask-WTF = ">=1.1.2"
|
||||
importlib-resources = ">=5.10.0"
|
||||
markupsafe = ">=2.1.0"
|
||||
passlib = ">=1.7.4"
|
||||
wtforms = ">=3.0.0"
|
||||
|
||||
|
@ -245,8 +246,8 @@ wtforms = ">=3.0.0"
|
|||
babel = ["babel (>=2.12.1)", "flask-babel (>=3.1.0)"]
|
||||
common = ["bcrypt (>=4.0.1)", "bleach (>=6.0.0)", "flask-mailman (>=0.3.0)"]
|
||||
fsqla = ["flask-sqlalchemy (>=3.0.3)", "sqlalchemy (>=2.0.12)", "sqlalchemy-utils (>=0.41.1)"]
|
||||
low = ["Flask (==2.3.2)", "Flask-Babel (==3.1.0)", "Flask-Login (==0.6.2)", "Flask-Mailman (==0.3.0)", "Flask-SQLAlchemy (==3.0.3)", "Flask-WTF (==1.1.1)", "argon2-cffi (==21.3.0)", "authlib (==1.2.0)", "babel (==2.12.1)", "bcrypt (==4.0.1)", "bleach (==6.0.0)", "itsdangerous (==2.1.2)", "jinja2 (==3.1.2)", "markupsafe (==2.1.2)", "mongoengine (==0.27.0)", "mongomock (==4.1.2)", "peewee (==3.16.2)", "phonenumberslite (==8.13.11)", "pony (==0.7.16)", "python-dateutil (==2.8.2)", "qrcode (==7.4.2)", "requests", "sqlalchemy (==2.0.12)", "sqlalchemy-utils (==0.41.1)", "webauthn (==1.9.0)", "werkzeug (==2.3.3)", "zxcvbn (==4.4.28)"]
|
||||
mfa = ["cryptography (>=40.0.2)", "phonenumberslite (>=8.13.11)", "qrcode (>=7.4.2)", "webauthn (>=1.9.0)"]
|
||||
low = ["Flask (==2.3.2)", "Flask-Babel (==3.1.0)", "Flask-Login (==0.6.2)", "Flask-Mailman (==0.3.0)", "Flask-SQLAlchemy (==3.0.3)", "Flask-WTF (==1.1.2)", "argon2-cffi (==21.3.0)", "authlib (==1.2.0)", "babel (==2.12.1)", "bcrypt (==4.0.1)", "bleach (==6.0.0)", "itsdangerous (==2.1.2)", "jinja2 (==3.1.2)", "markupsafe (==2.1.2)", "mongoengine (==0.27.0)", "mongomock (==4.1.2)", "peewee (==3.16.2)", "phonenumberslite (==8.13.11)", "pony (==0.7.16)", "pydantic (<2.0)", "python-dateutil (==2.8.2)", "qrcode (==7.4.2)", "requests", "sqlalchemy (==2.0.12)", "sqlalchemy-utils (==0.41.1)", "webauthn (==1.11.0)", "werkzeug (==2.3.3)", "zxcvbn (==4.4.28)"]
|
||||
mfa = ["cryptography (>=40.0.2)", "phonenumberslite (>=8.13.11)", "qrcode (>=7.4.2)", "webauthn (>=1.11.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "flask-sqlalchemy"
|
||||
|
@ -594,52 +595,60 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.21"
|
||||
version = "2.0.22"
|
||||
description = "Database Abstraction Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"},
|
||||
{file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"},
|
||||
{file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"},
|
||||
{file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"},
|
||||
{file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"},
|
||||
{file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"},
|
||||
{file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"},
|
||||
{file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f6ff392b27a743c1ad346d215655503cec64405d3b694228b3454878bf21590"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f776c2c30f0e5f4db45c3ee11a5f2a8d9de68e81eb73ec4237de1e32e04ae81c"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8f1792d20d2f4e875ce7a113f43c3561ad12b34ff796b84002a256f37ce9437"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80eeb5189d7d4b1af519fc3f148fe7521b9dfce8f4d6a0820e8f5769b005051"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:69fd9e41cf9368afa034e1c81f3570afb96f30fcd2eb1ef29cb4d9371c6eece2"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54bcceaf4eebef07dadfde424f5c26b491e4a64e61761dea9459103ecd6ccc95"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-win32.whl", hash = "sha256:7ee7ccf47aa503033b6afd57efbac6b9e05180f492aeed9fcf70752556f95624"},
|
||||
{file = "SQLAlchemy-2.0.22-cp311-cp311-win_amd64.whl", hash = "sha256:b560f075c151900587ade06706b0c51d04b3277c111151997ea0813455378ae0"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2c9bac865ee06d27a1533471405ad240a6f5d83195eca481f9fc4a71d8b87df8"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:625b72d77ac8ac23da3b1622e2da88c4aedaee14df47c8432bf8f6495e655de2"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b39a6e21110204a8c08d40ff56a73ba542ec60bab701c36ce721e7990df49fb9"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53a766cb0b468223cafdf63e2d37f14a4757476157927b09300c8c5832d88560"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0e1ce8ebd2e040357dde01a3fb7d30d9b5736b3e54a94002641dfd0aa12ae6ce"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:505f503763a767556fa4deae5194b2be056b64ecca72ac65224381a0acab7ebe"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"},
|
||||
{file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"},
|
||||
{file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"},
|
||||
{file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"},
|
||||
{file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"},
|
||||
{file = "SQLAlchemy-2.0.22-py3-none-any.whl", hash = "sha256:3076740335e4aaadd7deb3fe6dcb96b3015f1613bd190a4e1634e1b99b02ec86"},
|
||||
{file = "SQLAlchemy-2.0.22.tar.gz", hash = "sha256:5434cc601aa17570d79e5377f5fd45ff92f9379e2abed0be5e8c2fba8d353d2b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -700,17 +709,17 @@ watchdog = ["watchdog (>=2.3)"]
|
|||
|
||||
[[package]]
|
||||
name = "wtforms"
|
||||
version = "3.0.1"
|
||||
version = "3.1.0"
|
||||
description = "Form validation and rendering for Python web development."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "WTForms-3.0.1-py3-none-any.whl", hash = "sha256:837f2f0e0ca79481b92884962b914eba4e72b7a2daaf1f939c890ed0124b834b"},
|
||||
{file = "WTForms-3.0.1.tar.gz", hash = "sha256:6b351bbb12dd58af57ffef05bc78425d08d1914e0fd68ee14143b7ade023c5bc"},
|
||||
{file = "wtforms-3.1.0-py3-none-any.whl", hash = "sha256:addd7899004fdf9318eb711d33aae9c1973fe80378257b7383e06de2eff7c559"},
|
||||
{file = "wtforms-3.1.0.tar.gz", hash = "sha256:4edd15771630289a5fa343d58822f72749822ca5a39dd33f92ee917cf72b954b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = "*"
|
||||
markupsafe = "*"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator"]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from flask_admin import Admin
|
||||
from flask_ckeditor import CKEditor
|
||||
|
||||
admin = Admin(
|
||||
name="Админ панель",
|
||||
|
|
|
@ -72,6 +72,7 @@ class PostView(MyAdminView):
|
|||
tags="Тэги",
|
||||
slug="Слаг",
|
||||
title="Заголовок",
|
||||
header_description="description заголовок в head страницы",
|
||||
description="Краткое описание статьи",
|
||||
author="Автор",
|
||||
published="Опубликовано",
|
||||
|
@ -92,6 +93,7 @@ class PostView(MyAdminView):
|
|||
class PageView(MyAdminView):
|
||||
column_labels = dict(
|
||||
name="Название страницы",
|
||||
header_description="description заголовок в head страницы",
|
||||
slug="URL страницы",
|
||||
text="Содержимое страницы",
|
||||
)
|
||||
|
@ -112,3 +114,12 @@ class HeadersView(MyAdminView):
|
|||
"name",
|
||||
"description",
|
||||
)
|
||||
|
||||
|
||||
class FooterLinksView(MyAdminView):
|
||||
column_labels = dict(
|
||||
name="Название",
|
||||
bootstrap_ico="Bootstrap код иконки",
|
||||
link="Ссылка",
|
||||
)
|
||||
column_list = ("name",)
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from flask import Flask, render_template_string, url_for
|
||||
from flask import (Flask, render_template_string, request, send_from_directory,
|
||||
url_for)
|
||||
from flask_admin import helpers
|
||||
from flask_ckeditor import CKEditor
|
||||
from flask_ckeditor import CKEditor, upload_fail, upload_success
|
||||
from flask_migrate import Migrate
|
||||
from flask_security.core import Security
|
||||
|
||||
from pyproger.admin.views import FooterLinksView
|
||||
from pyproger.dbase import Role, User, db, user_datastore
|
||||
from pyproger.dbase.database import get_headers, get_menu_items
|
||||
from pyproger.dbase.models import Page, Post, SiteHeaders, Tag
|
||||
from pyproger.dbase.database import (get_footer_links, get_headers,
|
||||
get_menu_items)
|
||||
from pyproger.dbase.models import (FooterContactLinks, Page, Post, SiteHeaders,
|
||||
Tag)
|
||||
|
||||
|
||||
def create_app(test_config=None):
|
||||
|
@ -23,6 +27,13 @@ def create_app(test_config=None):
|
|||
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
|
||||
app.config["SECURITY_PASSWORD_SALT"] = os.getenv("SECURITY_PASSWORD_SALT")
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("SQLALCHEMY_DATABASE_URI")
|
||||
app.config["MYCOPYRIGHT"] = {
|
||||
"year": os.getenv("COPYRIGHT_YEAR"),
|
||||
"name": os.getenv("COPYRIGHT_NAME"),
|
||||
"link": os.getenv("COPYRIGHT_LINK"),
|
||||
"city": os.getenv("COPYRIGHT_CITY"),
|
||||
}
|
||||
app.config["BRAND"] = os.getenv("BRAND")
|
||||
else:
|
||||
app.config.from_mapping(test_config)
|
||||
|
||||
|
@ -98,6 +109,14 @@ def create_app(test_config=None):
|
|||
name="Включаемые в html заголовки",
|
||||
)
|
||||
)
|
||||
admin.add_view(
|
||||
FooterLinksView(
|
||||
FooterContactLinks,
|
||||
db.session,
|
||||
category="Страницы",
|
||||
name="Иконки-ссылки футера",
|
||||
)
|
||||
)
|
||||
|
||||
from pyproger.blog.blog import bp as bp_blog
|
||||
from pyproger.cli.commands import bp_cli
|
||||
|
@ -110,11 +129,27 @@ def create_app(test_config=None):
|
|||
app.register_blueprint(bp_robots)
|
||||
|
||||
with app.app_context():
|
||||
site_headers = get_headers()
|
||||
try:
|
||||
site_headers = get_headers()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
site_headers = None
|
||||
app.config["SITE_HEADERS"] = site_headers
|
||||
menu_items = get_menu_items()
|
||||
|
||||
try:
|
||||
menu_items = get_menu_items()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
menu_items = None
|
||||
app.config["MENU_ITEMS"] = menu_items
|
||||
|
||||
try:
|
||||
links = get_footer_links()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
links = None
|
||||
app.config["MYLINKS"] = links
|
||||
|
||||
@security.context_processor
|
||||
def security_context_processor():
|
||||
return dict(
|
||||
|
@ -134,4 +169,27 @@ def create_app(test_config=None):
|
|||
def hello():
|
||||
return render_template_string("pong")
|
||||
|
||||
@app.route("/files/<filename>")
|
||||
def uploaded_files(filename):
|
||||
path = app.config["UPLOADED_PATH"]
|
||||
return send_from_directory(path, filename)
|
||||
|
||||
@app.route("/upload", methods=["POST"])
|
||||
def upload():
|
||||
f = request.files.get("upload")
|
||||
extension = f.filename.split(".")[-1].lower()
|
||||
if extension not in ["jpg", "gif", "png", "jpeg"]:
|
||||
return upload_fail(message="Image only!")
|
||||
f.save(os.path.join(app.config["UPLOADED_PATH"], f.filename))
|
||||
url = url_for("uploaded_files", filename=f.filename)
|
||||
return upload_success(url=url)
|
||||
|
||||
@app.route("/favicon.ico")
|
||||
def favicon():
|
||||
return send_from_directory(
|
||||
os.path.join(app.root_path, "static"),
|
||||
"favicon.ico",
|
||||
mimetype="image/vnd.microsoft.icon",
|
||||
)
|
||||
|
||||
return app
|
||||
|
|
|
@ -1,29 +1,35 @@
|
|||
import locale
|
||||
import re
|
||||
|
||||
from flask import (abort, current_app, redirect, render_template, request,
|
||||
session, url_for)
|
||||
from flask import abort, current_app, render_template
|
||||
|
||||
from ..dbase.database import (get_all_posts_by_tag, get_page,
|
||||
get_paginated_posts, get_post, get_tags)
|
||||
from ..dbase.database import (
|
||||
get_all_posts_by_tag,
|
||||
get_page,
|
||||
get_paginated_posts,
|
||||
get_post,
|
||||
get_tags,
|
||||
)
|
||||
from .blog import bp
|
||||
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
locale.setlocale(locale.LC_ALL, ("ru", "utf-8"))
|
||||
|
||||
|
||||
@bp.route("/", methods=["GET"], defaults={"page": 1})
|
||||
@bp.route("/")
|
||||
@bp.route("/<int:page>")
|
||||
def index(page=1):
|
||||
session["back_url"] = request.url
|
||||
per_page = current_app.config.get("POSTS_ON_PAGE")
|
||||
|
||||
posts, total_pages = get_paginated_posts(page, per_page)
|
||||
posts, total = get_paginated_posts(page, per_page)
|
||||
total_pages = total // per_page + [0, 1][total % per_page != 0]
|
||||
|
||||
list_pages = [
|
||||
x for x in range(1, total_pages + 1) if x >= page - 2 and x <= page + 2
|
||||
]
|
||||
return render_template(
|
||||
"blog/index.html",
|
||||
title=f'{current_app.config.get("BRAND")} - разговоры про питон',
|
||||
header_description="Про изучение python, веб разработку и прочие вещи",
|
||||
headers=current_app.config.get("SITE_HEADERS"),
|
||||
menu_title=current_app.config.get("BRAND"),
|
||||
menu_items=current_app.config.get("MENU_ITEMS"),
|
||||
|
@ -72,6 +78,7 @@ def get_all_tags():
|
|||
return render_template(
|
||||
"blog/tags.html",
|
||||
title=f'{current_app.config.get("BRAND")} - поиск по тэгу',
|
||||
header_description="Выбор статей по тематике",
|
||||
headers=current_app.config.get("SITE_HEADERS"),
|
||||
menu_title=current_app.config.get("BRAND"),
|
||||
tags=tags,
|
||||
|
@ -81,32 +88,30 @@ def get_all_tags():
|
|||
)
|
||||
|
||||
|
||||
@bp.route("/tag/", methods=["GET"], defaults={"page": 1})
|
||||
@bp.route("/tag/<path:tag>")
|
||||
@bp.route("/tag/")
|
||||
@bp.route("/tag/<path:tag>/<int:page>")
|
||||
def get_posts_by_tag(page=1, tag=None):
|
||||
if tag is None:
|
||||
tag = request.args.get("tag")
|
||||
if tag is None:
|
||||
return redirect(url_for(".get_all_tags"))
|
||||
|
||||
per_page = current_app.config.get("POSTS_ON_PAGE")
|
||||
posts, total = get_all_posts_by_tag(tag, page, per_page)
|
||||
total_pages = total // per_page + [0, 1][total % per_page != 0]
|
||||
|
||||
if posts is None:
|
||||
abort(404)
|
||||
|
||||
total_pages = total // per_page + [0, 1][total % per_page != 0]
|
||||
|
||||
list_pages = [
|
||||
x for x in range(1, total_pages + 1) if x >= page - 2 and x <= page + 2
|
||||
]
|
||||
|
||||
return render_template(
|
||||
"blog/index.html",
|
||||
"blog/tagget_posts.html",
|
||||
title=f'{current_app.config.get("BRAND")} - посты по {tag}',
|
||||
header_description=f"Статьи по теме {tag}",
|
||||
headers=current_app.config.get("SITE_HEADERS"),
|
||||
menu_title=current_app.config.get("BRAND"),
|
||||
menu_items=current_app.config.get("MENU_ITEMS"),
|
||||
posts=posts,
|
||||
page=page,
|
||||
tag=tag,
|
||||
total_pages=total_pages,
|
||||
list_pages=list_pages,
|
||||
mylinks=current_app.config.get("MYLINKS"),
|
||||
|
@ -122,6 +127,7 @@ def page(slug=None):
|
|||
return render_template(
|
||||
"blog/page.html",
|
||||
title=f'{current_app.config.get("BRAND")} - {page.name}',
|
||||
header_description=page.header_description,
|
||||
headers=current_app.config.get("SITE_HEADERS"),
|
||||
menu_title=current_app.config.get("BRAND"),
|
||||
menu_items=current_app.config.get("MENU_ITEMS"),
|
||||
|
|
|
@ -1,19 +1,6 @@
|
|||
import os
|
||||
|
||||
# Настройки блога
|
||||
BRAND = "блог"
|
||||
MYCOPYRIGHT = {
|
||||
"year": "2023",
|
||||
"name": "Иванов Иван",
|
||||
"link": "http://yandex.ru",
|
||||
"city": "г.Москва",
|
||||
}
|
||||
MYLINKS = (
|
||||
{"icon": "fab fa-telegram", "link": "https://t.me"},
|
||||
{"icon": "fab fa-vk", "link": "https://m.vk.com"},
|
||||
{"icon": "fab fa-yandex", "link": "mailto:user@yandex.ru"},
|
||||
{"icon": "fab fa-github", "link": "https://github.com"},
|
||||
)
|
||||
POSTS_ON_PAGE = 6
|
||||
|
||||
# Тема оформления админ панели
|
||||
|
@ -53,7 +40,11 @@ SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL = False
|
|||
LANGUAGES = ["ru"]
|
||||
BABEL_TRANSLATION_DIRECTORIES = os.path.join(os.path.curdir, "translations")
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
CKEDITOR_PKG_TYPE = "full"
|
||||
CKEDITOR_SERVE_LOCAL = True
|
||||
CKEDITOR_ENABLE_CODESNIPPET = True
|
||||
CKEDITOR_CODE_THEME = "monokai_sublime"
|
||||
CKEDITOR_FILE_UPLOADER = "upload"
|
||||
# app.config['CKEDITOR_ENABLE_CSRF'] = True # if you want to enable CSRF protect, uncomment this line
|
||||
UPLOADED_PATH = os.path.join(basedir, "uploads")
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from . import db
|
||||
from .models import Page, Post, SiteHeaders, Tag, User
|
||||
from .models import FooterContactLinks, Page, Post, SiteHeaders, Tag, User
|
||||
|
||||
|
||||
def get_paginated_posts(page, per_page):
|
||||
all_post_query = (
|
||||
db.session.query(Post, User)
|
||||
.join(User, Post.author == User.id)
|
||||
.order_by(Post.create_datetime.desc())
|
||||
.filter(Post.published.is_(True))
|
||||
.order_by(Post.update_datetime.desc())
|
||||
.paginate(page=page, per_page=per_page, error_out=True)
|
||||
)
|
||||
return all_post_query, all_post_query.total
|
||||
|
@ -16,6 +17,7 @@ def get_post(slug):
|
|||
post_query = (
|
||||
db.session.query(Post, User)
|
||||
.join(User, Post.author == User.id)
|
||||
.filter(Post.published.is_(True))
|
||||
.filter(Post.slug == slug)
|
||||
.first()
|
||||
)
|
||||
|
@ -31,16 +33,14 @@ def get_all_posts_by_tag(tag, page, per_page):
|
|||
posts_query = (
|
||||
db.session.query(Post, User)
|
||||
.join(User, Post.author == User.id)
|
||||
.filter(Post.published.is_(True))
|
||||
.filter(Post.tags.any(Tag.tag == tag))
|
||||
.order_by(Post.create_datetime.desc())
|
||||
.order_by(Post.update_datetime.desc())
|
||||
.paginate(page=page, per_page=per_page, error_out=True)
|
||||
)
|
||||
total_pages = (
|
||||
posts_query.total // per_page + [0, 1][posts_query.total % per_page != 0]
|
||||
)
|
||||
if posts_query.total == 0:
|
||||
return None, None
|
||||
return posts_query, total_pages
|
||||
return None, False
|
||||
return posts_query, posts_query.total
|
||||
|
||||
|
||||
def get_page(slug):
|
||||
|
@ -72,3 +72,11 @@ def get_headers():
|
|||
.all()
|
||||
)
|
||||
return headers
|
||||
|
||||
|
||||
def get_footer_links():
|
||||
links = db.session.query(
|
||||
FooterContactLinks.link,
|
||||
FooterContactLinks.bootstrap_ico,
|
||||
).all()
|
||||
return links
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import datetime
|
||||
|
||||
from flask_security.models import fsqla
|
||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Text
|
||||
from sqlalchemy.sql import func
|
||||
|
@ -72,6 +70,7 @@ class Post(db.Model):
|
|||
author = Column(Integer, db.ForeignKey("user.id"))
|
||||
slug = Column(String(100), nullable=False)
|
||||
title = Column(Text, nullable=False)
|
||||
header_description = Column(Text)
|
||||
description = Column(Text, nullable=False)
|
||||
published = Column(Boolean, default=False)
|
||||
tags = db.relationship("Tag", secondary=tag_post)
|
||||
|
@ -99,6 +98,7 @@ class Page(db.Model):
|
|||
unique=True,
|
||||
autoincrement=True,
|
||||
)
|
||||
header_description = Column(Text)
|
||||
name = Column(String(20))
|
||||
slug = Column(String(50), nullable=False)
|
||||
text = Column(Text)
|
||||
|
@ -122,3 +122,17 @@ class SiteHeaders(db.Model):
|
|||
description = Column(Text)
|
||||
content = Column(Text)
|
||||
enabled = Column(Boolean, default=False)
|
||||
|
||||
|
||||
class FooterContactLinks(db.Model):
|
||||
__tablename__ = "footer_icons"
|
||||
id = Column(
|
||||
Integer,
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
unique=True,
|
||||
autoincrement=True,
|
||||
)
|
||||
name = Column(String(30))
|
||||
bootstrap_ico = Column(String(20))
|
||||
link = Column(String(100))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from flask import render_template, url_for
|
||||
from flask import current_app, render_template
|
||||
|
||||
from . import bp
|
||||
|
||||
|
@ -8,8 +8,12 @@ def handle_404(err):
|
|||
return (
|
||||
render_template(
|
||||
"errors/404.html",
|
||||
title="pyproger - Страница не найдена",
|
||||
menu_title="pyproger",
|
||||
title=f'{current_app.config.get("BRAND")} - поиск по тэгу',
|
||||
headers=current_app.config.get("SITE_HEADERS"),
|
||||
menu_title=current_app.config.get("BRAND"),
|
||||
menu_items=current_app.config.get("MENU_ITEMS"),
|
||||
mylinks=current_app.config.get("MYLINKS"),
|
||||
copyright=current_app.config.get("MYCOPYRIGHT"),
|
||||
),
|
||||
404,
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from flask import make_response, render_template
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from pyproger.dbase.database import get_posts_for_sitemap
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
|
@ -1,6 +1,14 @@
|
|||
{% extends 'admin/model/edit.html' %}
|
||||
|
||||
{% block head %}
|
||||
{{ super()}}
|
||||
{{ ckeditor.load_code_theme() }}
|
||||
{% endblock head %}
|
||||
|
||||
{% block tail %}
|
||||
{{ super() }}
|
||||
{{ ckeditor.load() }}
|
||||
{% endblock %}
|
||||
{{ ckeditor.config(name='title')}}
|
||||
{{ ckeditor.config(name='description')}}
|
||||
{{ ckeditor.config(name='text', height=700)}}
|
||||
{% endblock tail %}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
<title>{% block title %}{% endblock title %}</title>
|
||||
<meta name="description" content="{% block header_description %}{% endblock header_description %}" />
|
||||
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}" type="image/x-icon">
|
||||
{% for h in headers %}
|
||||
{{h.content | safe}}
|
||||
{% endfor %}
|
||||
|
@ -36,17 +38,17 @@
|
|||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="text-body navbar-nav me-auto mb-2 mb-lg-0 p-3">
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white p-2 link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="{{ url_for('bp_blog.index')}}">Главная
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white p-2 link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="{{ url_for('bp_blog.get_all_tags')}}">Статьи по темам</a>
|
||||
</li>
|
||||
{% for m in menu_items %}
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white p-2 link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="/{{m.slug}}">{{m.name}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -71,34 +73,34 @@
|
|||
<a class="btn btn-outline-light btn-floating m-2"
|
||||
href="{{ l.link }}"
|
||||
role="button"
|
||||
><i class="{{ l.icon }}"></i
|
||||
><i class="{{ l.bootstrap_ico }}"></i
|
||||
></a>
|
||||
{% endfor %}
|
||||
</section>
|
||||
<!-- Section: Social media -->
|
||||
|
||||
<!-- Section: Links -->
|
||||
<section class="">
|
||||
<section class="container-fluid justify-content-center">
|
||||
<!--Grid row-->
|
||||
<div class="row">
|
||||
<!--Grid column-->
|
||||
<div class="col-lg-3 col-md-6 mb-4 mb-md-0">
|
||||
<div class="col-lg-6 col-md-6 mb-4 mb-md-0">
|
||||
<h5 class="text-white">{{ menu_title }}.ru</h5>
|
||||
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="{{ url_for('bp_blog.index')}}">Главная
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="{{ url_for('bp_blog.get_all_tags')}}">Статьи по темам</a>
|
||||
</li>
|
||||
|
||||
{% for m in menu_items %}
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="/{{m.slug}}">{{m.name}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
@ -107,24 +109,24 @@
|
|||
<!--Grid column-->
|
||||
|
||||
<!--Grid column-->
|
||||
<div class="col-lg-3 col-md-6 mb-4 mb-md-0">
|
||||
<div class="col-lg-6 col-md-6 mb-4 mb-md-0">
|
||||
<h5 class="text-white">Вебхостинг</h5>
|
||||
|
||||
<ul class="list-unstyled mb-0">
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="https://www.reg.ru/?rlink=reflink-11750021">Российский регистратор reg.ru </a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="https://www.reg.ru/domain/new/?rlink=reflink-11750021">Регистрация доменов</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="https://www.reg.ru/hosting/?rlink=reflink-11750021">Веб хостинг сайтов</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white"
|
||||
<a class="link-offset-2 link-underline link-underline-opacity-0 text-white link-body-emphasis link-offset-2 link-underline-opacity-25 link-underline-opacity-75-hover"
|
||||
href="https://www.reg.ru/vps/cloud/?rlink=reflink-11750021">Облачные серверы</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -4,6 +4,10 @@
|
|||
{{title}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
{{ header_description }}
|
||||
{% endblock %}
|
||||
|
||||
{% block menu_title %}
|
||||
{{ menu_title }}
|
||||
{% endblock %}
|
||||
|
@ -18,7 +22,7 @@
|
|||
</a>
|
||||
<small>
|
||||
{% for t in p.Post.tags %}
|
||||
<a class="link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover" href="{{url_for(".get_posts_by_tag", tag=t.tag )}}">
|
||||
<a class="link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover" href="/tag/{{t.tag}}/1">
|
||||
#{{t.tag}}
|
||||
</a>
|
||||
{% endfor %}<br>
|
||||
|
@ -33,25 +37,27 @@
|
|||
|
||||
<nav class="container p-3">
|
||||
<ul class="pagination justify-content-center"
|
||||
{% block nav_pages %}
|
||||
{% if posts.has_prev %}
|
||||
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=posts.prev_num)}}"><<</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/1"><<</a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><a class="page-link" ><<</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for i in list_pages %}
|
||||
{% if i == page %}
|
||||
<li class="page-item active"><a class="page-link" href="{{ url_for(".index", page=i)}}">{{ i }}</a></li>
|
||||
<li class="page-item active"><a class="page-link" href="/{{i}}">{{ i }}</a></li>
|
||||
{% else %}
|
||||
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=i)}}">{{ i }}</a></li>
|
||||
<li class="page-item"><a class="page-link" href="/{{i}}">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if posts.has_next %}
|
||||
<li class="page-item"><a class="page-link" href="{{ url_for(".index", page=posts.next_num)}}">>></a></li>
|
||||
<li class="page-item"><a class="page-link" href="/{{total_pages}}">>></a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><a class="page-link" >>></a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endblock nav_pages %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
{{ menu_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
{{ header_description }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Main Content-->
|
||||
<div class="conteiner ">
|
||||
|
|
|
@ -4,12 +4,16 @@
|
|||
{{ title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description%}
|
||||
{{ post.Post.header_description }}
|
||||
{% endblock%}
|
||||
|
||||
{% block menu_title %}
|
||||
{{ menu_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="conteiner p-4 px-lg-5">
|
||||
<div class="container p-4 px-lg-5">
|
||||
<h4>{{ post.Post.title | safe }}</h4>
|
||||
{{ post.Post.text | safe }}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
{% extends 'blog/index.html' %}
|
||||
|
||||
{% block nav_pages %}
|
||||
{% if posts.has_prev %}
|
||||
<li class="page-item"><a class="page-link" href="/tag/{{tag}}/1"><<</a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><a class="page-link"><<</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% for i in list_pages %}
|
||||
{% if i == page %}
|
||||
<li class="page-item active"><a class="page-link" href="/tag/{{tag}}/{{i}}">{{ i }}</a></li>
|
||||
{% else %}
|
||||
<li class="page-item"><a class="page-link" href="/tag/{{tag}}/{{i}}">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if posts.has_next %}
|
||||
<li class="page-item"><a class="page-link" href="/tag/{{tag}}/{{total_pages}}">>></a></li>
|
||||
{% else %}
|
||||
<li class="page-item disabled"><a class="page-link">>></a></li>
|
||||
{% endif %}
|
||||
{% endblock nav_pages %}
|
|
@ -4,6 +4,10 @@
|
|||
{{title}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header_description %}
|
||||
{{ header_description }}
|
||||
{% endblock %}
|
||||
|
||||
{% block menu_title %}
|
||||
{{ menu_title }}
|
||||
{% endblock %}
|
||||
|
@ -15,7 +19,7 @@
|
|||
<h4>Тэги статей:</h4>
|
||||
{% for t in tags %}
|
||||
<li>
|
||||
<a class="link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover" href="{{url_for(".get_posts_by_tag", tag=t.tag )}}">
|
||||
<a class="link-offset-2 link-offset-3-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover" href="/tag/{{t.tag}}/1">
|
||||
#{{t.tag}}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -26,3 +26,18 @@
|
|||
</div>
|
||||
{% endblock content%}
|
||||
|
||||
{% block copyright_year %}
|
||||
{{ copyright.year }}
|
||||
{% endblock %}
|
||||
|
||||
{% block copyright_name %}
|
||||
{{ copyright.name }}
|
||||
{% endblock %}
|
||||
|
||||
{% block copyright_link %}
|
||||
{{ copyright.link }}
|
||||
{% endblock %}
|
||||
|
||||
{% block copyright_city %}
|
||||
{{ copyright.city }}
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,6 +5,7 @@ Disallow: /static/
|
|||
Disallow: /post/$
|
||||
Disallow: /tag/
|
||||
Disallow: /tags/
|
||||
Allow: /post/*
|
||||
Allow: /static/*.css
|
||||
Allow: /static/*.js
|
||||
Allow: /static/*.png
|
||||
|
@ -18,6 +19,7 @@ Disallow: /static/
|
|||
Disallow: /post/$
|
||||
Disallow: /tag/
|
||||
Disallow: /tags/
|
||||
Allow: /post/*
|
||||
Allow: /static/*.css
|
||||
Allow: /static/*.js
|
||||
Allow: /static/*.png
|
||||
|
@ -31,6 +33,7 @@ Disallow: /static/
|
|||
Disallow: /post/$
|
||||
Disallow: /tag/
|
||||
Disallow: /tags/
|
||||
Allow: /post/*
|
||||
Crawl-Delay: 5
|
||||
|
||||
Sitemap: https://pyproger.ru/sitemap.xml
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import os
|
||||
|
||||
from flask import Blueprint, current_app, request
|
||||
from flask_babel import Babel
|
||||
|
||||
|
|
Loading…
Reference in New Issue