32 Commits

Author SHA1 Message Date
8475d83b25 .env
basic env file
2024-01-04 00:02:29 +03:00
297ef62fab start refactoring 2024-01-03 23:50:03 +03:00
f945dc30a1 start refactoring and testing 2024-01-03 08:15:35 +00:00
d3febb5a6a dev 2023-12-28 10:45:12 +00:00
4d3a309940 favicon url 2023-10-30 23:46:00 +03:00
c2e68881d9 удалил лишние импорты 2023-10-25 08:54:57 +03:00
cdf9bfe072 favicon 2023-10-23 10:36:27 +03:00
50e74ccf38 добавил tag description на страницы 2023-10-23 10:03:46 +03:00
52d1b84658 Todo в ридми2 2023-10-21 10:19:46 +03:00
9cecc960db Todo в ридми 2023-10-21 10:16:24 +03:00
985301d27f увеличил поле ввода в редакторе статьи 2023-10-18 08:57:52 +03:00
30f2dbde98 robots 2023-10-16 23:22:21 +03:00
54c40985cb локаль в штампе времени 2023-10-16 23:06:43 +03:00
d4722945e9 фиксация 2023-10-16 22:02:29 +03:00
e2743db969 поправил сортировку по обновлению 2023-10-16 19:36:02 +03:00
3bc178f2cc убрал навиг гет запросы, зафиксил навигацию пашинации 2023-10-16 19:24:14 +03:00
18724c455a убрал сессион 2023-10-16 17:01:15 +03:00
2c6c2ebbe9 fix 2023-10-16 15:41:31 +03:00
6af1d3c7cb исправил пагинацию на главной, центрирование футера, ссылки со взаимодействием 2023-10-15 23:22:15 +03:00
21a818d71e цвета в консоли 2023-10-12 15:05:34 +03:00
c126531e49 скрыл 2023-10-11 21:42:54 +03:00
3b63421f5f скрыл отображение неопубликованных 2023-10-11 21:34:45 +03:00
d8f8b36469 ошибки 2023-10-11 19:37:04 +03:00
009415d261 ошибки 2023-10-11 19:35:18 +03:00
9301f984c3 ошибку не заметил 2023-10-11 19:25:48 +03:00
40274136fe ошибку не заметил 2023-10-11 19:16:37 +03:00
eb5eb79383 gitignore 2023-10-11 17:50:41 +03:00
48be7e96d9 gitignore 2023-10-11 17:16:29 +03:00
911d3ef845 загрузка файлов при создании поста и вставка картинок динамически с загрузкой в папку uploads 2023-10-11 14:43:58 +03:00
3785ab37e6 fix 404#2 2023-10-11 09:55:26 +03:00
6103b1fd22 fix 404 2023-10-11 09:49:40 +03:00
199eb0f3ae refactoring 2023-10-10 22:44:32 +03:00
48 changed files with 896 additions and 425 deletions

21
.env Normal file
View File

@@ -0,0 +1,21 @@
# site basic constants
# this info will be shown on site header and footer
BRAND=pyproger_test
COPYRIGHT_YEAR=2024
COPYRIGHT_NAME=test user
COPYRIGHT_LINK=mailto:test@mail.com
COPYRIGHT_CITY=Some sity
# sqlalchemy connection section
DB_HOST=db_ip_adress
DB_PORT=db_port
DB_USER=db_user
DB_PASS=db_user_password
DB_NAME=db_name
# server run mode
MODE=DEV
# security requared values
SECRET_KEY=Some_secret_key
SECURITY_PASSWORD_SALT=Some_secret_salt

3
.gitignore vendored Executable file → Normal file
View File

@@ -1,3 +1,6 @@
pyproger/uploads/*
!pyproger/uploads/.gitkeep
config.ini
data/

9
.test.env Normal file
View File

@@ -0,0 +1,9 @@
BRAND=pyproger_test
COPYRIGHT_YEAR=2023
COPYRIGHT_NAME=TestUser
COPYRIGHT_LINK=mailto:test@mail.com
COPYRIGHT_CITY=Mysity
SECRET_KEY=somesecretkey
SECURITY_PASSWORD_SALT=somesecretsolt
SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://pi3c:@localhost:5432/pyproger_test

View File

@@ -55,6 +55,11 @@
Перейдите в браузере по адресу 127.0.0.1:5000 для доступа к блогу или 127.0.0.1:5000/admin в админ панель
### TODO
- Добавить на страницы постов "Оглавление" со ссылками на статьи
- Добавить подписку на статьи
- Добавить комментарии к постам
### Авторы
- Сергей Ванюшкин <pi3c@yandex.ru>

View File

@@ -1,22 +1,62 @@
import os
import uuid
from datetime import datetime as dt
from dotenv import load_dotenv
dotenv_path = os.path.join(os.path.dirname(__file__), "pyproger", ".env")
dotenv_path = os.path.join(os.path.dirname(__file__), ".env")
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"
)

View File

@@ -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.

View File

@@ -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 ###

View File

@@ -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 ###

View File

@@ -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 ###

View File

@@ -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 ###

View File

@@ -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 ###

View File

@@ -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 ###

376
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "alembic"
@@ -19,6 +19,17 @@ typing-extensions = ">=4"
[package.extras]
tz = ["python-dateutil"]
[[package]]
name = "annotated-types"
version = "0.6.0"
description = "Reusable constraint types to use with typing.Annotated"
optional = false
python-versions = ">=3.8"
files = [
{file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"},
{file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"},
]
[[package]]
name = "babel"
version = "2.13.0"
@@ -35,13 +46,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 +233,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 +257,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"
@@ -548,25 +560,237 @@ build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fullt
totp = ["cryptography"]
[[package]]
name = "psycopg2"
name = "psycopg2-binary"
version = "2.9.9"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
optional = false
python-versions = ">=3.7"
files = [
{file = "psycopg2-2.9.9-cp310-cp310-win32.whl", hash = "sha256:38a8dcc6856f569068b47de286b472b7c473ac7977243593a288ebce0dc89516"},
{file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"},
{file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"},
{file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"},
{file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"},
{file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"},
{file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"},
{file = "psycopg2-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:bac58c024c9922c23550af2a581998624d6e02350f4ae9c5f0bc642c633a2d5e"},
{file = "psycopg2-2.9.9-cp39-cp39-win32.whl", hash = "sha256:c92811b2d4c9b6ea0285942b2e7cac98a59e166d59c588fe5cfe1eda58e72d59"},
{file = "psycopg2-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:de80739447af31525feddeb8effd640782cf5998e1a4e9192ebdf829717e3913"},
{file = "psycopg2-2.9.9.tar.gz", hash = "sha256:d1454bde93fb1e224166811694d600e746430c006fbb031ea06ecc2ea41bf156"},
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
]
[[package]]
name = "pydantic"
version = "2.5.3"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic-2.5.3-py3-none-any.whl", hash = "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4"},
{file = "pydantic-2.5.3.tar.gz", hash = "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a"},
]
[package.dependencies]
annotated-types = ">=0.4.0"
pydantic-core = "2.14.6"
typing-extensions = ">=4.6.1"
[package.extras]
email = ["email-validator (>=2.0.0)"]
[[package]]
name = "pydantic-core"
version = "2.14.6"
description = ""
optional = false
python-versions = ">=3.7"
files = [
{file = "pydantic_core-2.14.6-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9"},
{file = "pydantic_core-2.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245"},
{file = "pydantic_core-2.14.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c"},
{file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66"},
{file = "pydantic_core-2.14.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590"},
{file = "pydantic_core-2.14.6-cp310-none-win32.whl", hash = "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7"},
{file = "pydantic_core-2.14.6-cp310-none-win_amd64.whl", hash = "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87"},
{file = "pydantic_core-2.14.6-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4"},
{file = "pydantic_core-2.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1"},
{file = "pydantic_core-2.14.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937"},
{file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622"},
{file = "pydantic_core-2.14.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2"},
{file = "pydantic_core-2.14.6-cp311-none-win32.whl", hash = "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2"},
{file = "pydantic_core-2.14.6-cp311-none-win_amd64.whl", hash = "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23"},
{file = "pydantic_core-2.14.6-cp311-none-win_arm64.whl", hash = "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6"},
{file = "pydantic_core-2.14.6-cp312-cp312-macosx_10_7_x86_64.whl", hash = "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec"},
{file = "pydantic_core-2.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b"},
{file = "pydantic_core-2.14.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd"},
{file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91"},
{file = "pydantic_core-2.14.6-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c"},
{file = "pydantic_core-2.14.6-cp312-none-win32.whl", hash = "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786"},
{file = "pydantic_core-2.14.6-cp312-none-win_amd64.whl", hash = "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40"},
{file = "pydantic_core-2.14.6-cp312-none-win_arm64.whl", hash = "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e"},
{file = "pydantic_core-2.14.6-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0"},
{file = "pydantic_core-2.14.6-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06"},
{file = "pydantic_core-2.14.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8"},
{file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8"},
{file = "pydantic_core-2.14.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e"},
{file = "pydantic_core-2.14.6-cp37-none-win32.whl", hash = "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6"},
{file = "pydantic_core-2.14.6-cp37-none-win_amd64.whl", hash = "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391"},
{file = "pydantic_core-2.14.6-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149"},
{file = "pydantic_core-2.14.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80"},
{file = "pydantic_core-2.14.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d"},
{file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1"},
{file = "pydantic_core-2.14.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60"},
{file = "pydantic_core-2.14.6-cp38-none-win32.whl", hash = "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe"},
{file = "pydantic_core-2.14.6-cp38-none-win_amd64.whl", hash = "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8"},
{file = "pydantic_core-2.14.6-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab"},
{file = "pydantic_core-2.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab"},
{file = "pydantic_core-2.14.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0"},
{file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9"},
{file = "pydantic_core-2.14.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411"},
{file = "pydantic_core-2.14.6-cp39-none-win32.whl", hash = "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975"},
{file = "pydantic_core-2.14.6-cp39-none-win_amd64.whl", hash = "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_10_7_x86_64.whl", hash = "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94"},
{file = "pydantic_core-2.14.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl", hash = "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f"},
{file = "pydantic_core-2.14.6-pp37-pypy37_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4"},
{file = "pydantic_core-2.14.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341"},
{file = "pydantic_core-2.14.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e"},
{file = "pydantic_core-2.14.6.tar.gz", hash = "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948"},
]
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-settings"
version = "2.1.0"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_settings-2.1.0-py3-none-any.whl", hash = "sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"},
{file = "pydantic_settings-2.1.0.tar.gz", hash = "sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c"},
]
[package.dependencies]
pydantic = ">=2.3.0"
python-dotenv = ">=0.21.0"
[[package]]
name = "python-dotenv"
version = "1.0.0"
@@ -594,52 +818,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,22 +932,22 @@ 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"]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "8e8742eb5d336c47334b669f8ef8115f21dc6ce2d555b6941da918fe054bb9f8"
python-versions = "^3.10"
content-hash = "5fd8b7e353d903e43f875ec2416e7d71cef5f62a6455a22fde3f875fb9e795b9"

View File

@@ -1,5 +1,4 @@
from flask_admin import Admin
from flask_ckeditor import CKEditor
admin = Admin(
name="Админ панель",

View File

@@ -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",)

View File

@@ -1,30 +1,30 @@
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
from pyproger.config import settings
def create_app(test_config=None):
def create_app():
app = Flask(__name__)
if test_config is None:
app.config.from_pyfile("config.py", silent=True)
dotenv_path = os.path.join(os.path.dirname(__file__), ".env")
if os.path.exists(dotenv_path):
load_dotenv(dotenv_path)
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")
else:
app.config.from_mapping(test_config)
app.config["SQLALCHEMY_DATABASE_URI"] = settings.DB_URL
app.config["MYCOPYRIGHT"] = {
"year": settings.COPYRIGHT_YEAR,
"name": settings.COPYRIGHT_NAME,
"link": settings.COPYRIGHT_LINK,
"city": settings.COPYRIGHT_CITY,
}
for key, val in settings.__dict__.items():
app.config[key] = val
from .translations import babel
from .translations import bp as bp_translate
@@ -47,8 +47,14 @@ def create_app(test_config=None):
admin.init_app(app)
from pyproger.admin.views import (HeadersView, PageView, PostView,
RoleView, TagView, UserView)
from pyproger.admin.views import (
HeadersView,
PageView,
PostView,
RoleView,
TagView,
UserView,
)
admin.add_view(
RoleView(
@@ -98,6 +104,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 +124,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 +164,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

View File

@@ -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"),

116
pyproger/config.py Executable file → Normal file
View File

@@ -1,59 +1,81 @@
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
from pydantic_settings import BaseSettings, SettingsConfigDict
# Тема оформления админ панели
FLASK_ADMIN_SWATCH = "slate"
# Тестовый ключ
SECRET_KEY = "Test_secret_key"
class Settings(BaseSettings):
# app settings
MODE: str
BRAND: str
COPYRIGHT_YEAR: int
COPYRIGHT_NAME: str
COPYRIGHT_LINK: str
COPYRIGHT_CITY: str
POSTS_ON_PAGE: int = 6
SQLALCHEMY_ECHO = False
# sqlalchemy cofiguration
DB_HOST: str
DB_PORT: int
DB_USER: str
DB_PASS: str
DB_NAME: str
SQLALCHEMY_ECHO: bool = False
SQLALCHEMY_TRACK_MODIFICATIONS: bool = False
# Настройки Flask-Security
SECURITY_URL_PREFIX = "/admin"
SECURITY_PASSWORD_HASH = "pbkdf2_sha512"
SECURITY_PASSWORD_SALT = "Password_salt"
SECURITY_TRACKABLE = True
# Flask config
SECRET_KEY: str
# Flask-Admin config
FLASK_ADMIN_SWATCH: str = "slate"
# Flask-Security-two config
SECURITY_PASSWORD_SALT: str
SECURITY_URL_PREFIX: str = "/admin"
SECURITY_PASSWORD_HASH: str = "pbkdf2_sha512"
SECURITY_TRACKABLE: bool = True
SECURITY_LOGIN_URL = "/login/"
SECURITY_LOGOUT_URL = "/logout/"
SECURITY_REGISTER_URL = "/register/"
SECURITY_LOGIN_URL: str = "/login/"
SECURITY_LOGOUT_URL: str = "/logout/"
SECURITY_REGISTER_URL: str = "/register/"
SECURITY_POST_LOGIN_VIEW = "/admin/"
SECURITY_POST_LOGOUT_VIEW = "/admin/"
SECURITY_POST_REGISTER_VIEW = "/admin/"
SECURITY_POST_RESET_VIEW = "/admin/"
SECURITY_POST_LOGIN_VIEW: str = "/admin/"
SECURITY_POST_LOGOUT_VIEW: str = "/admin/"
SECURITY_POST_REGISTER_VIEW: str = "/admin/"
SECURITY_POST_RESET_VIEW: str = "/admin/"
SECURITY_REGISTERABLE = False
SECURITY_CHANGEABLE = True
SECURITY_RECOVERABLE = False
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECURITY_REGISTERABLE: bool = False
SECURITY_CHANGEABLE: bool = True
SECURITY_RECOVERABLE: bool = False
SECURITY_SEND_REGISTER_EMAIL: bool = False
SECURITY_SEND_PASSWORD_CHANGE_EMAIL: bool = False
SECURITY_SEND_PASSWORD_RESET_EMAIL: bool = False
SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL: bool = False
# Настройки Flask-Babel необходимые для русификации админки
LANGUAGES: list = ["ru"]
BABEL_TRANSLATION_DIRECTORIES: str = os.path.join(os.path.curdir, "translations")
# Flask-ckeditor config
basedir: str = os.path.abspath(os.path.dirname(__file__))
CKEDITOR_PKG_TYPE: str = "full"
CKEDITOR_SERVE_LOCAL: bool = True
CKEDITOR_ENABLE_CODESNIPPET: bool = True
CKEDITOR_CODE_THEME: str = "monokai_sublime"
CKEDITOR_FILE_UPLOADER: str = "upload"
# app.config['CKEDITOR_ENABLE_CSRF'] = True # if you want to enable CSRF protect, uncomment this line
UPLOADED_PATH: str = os.path.join(basedir, "uploads")
@property
def DB_URL(self):
return f"postgresql+psycopg2://{self.DB_USER}:{self.DB_PASS}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
model_config = SettingsConfigDict(env_file=".env")
settings = Settings()
SECURITY_SEND_REGISTER_EMAIL = False
SECURITY_SEND_PASSWORD_CHANGE_EMAIL = False
SECURITY_SEND_PASSWORD_RESET_EMAIL = False
SECURITY_SEND_PASSWORD_RESET_NOTICE_EMAIL = False
# Настройки Flask-Babel необходимые для русификации админки
LANGUAGES = ["ru"]
BABEL_TRANSLATION_DIRECTORIES = os.path.join(os.path.curdir, "translations")
CKEDITOR_PKG_TYPE = "full"
CKEDITOR_SERVE_LOCAL = True
CKEDITOR_ENABLE_CODESNIPPET = True
CKEDITOR_CODE_THEME = "monokai_sublime"

View File

@@ -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

View File

@@ -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))

0
pyproger/errors/__init__.py Executable file → Normal file
View File

0
pyproger/errors/errors.py Executable file → Normal file
View File

10
pyproger/errors/routes.py Executable file → Normal file
View File

@@ -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,
)

View File

@@ -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

BIN
pyproger/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -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 %}

0
pyproger/templates/admin/index.html Executable file → Normal file
View File

View File

@@ -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>

View File

@@ -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)}}">&lt;&lt;</a></li>
<li class="page-item"><a class="page-link" href="/1">&lt;&lt;</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link" >&lt;&lt;</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)}}">&gt;&gt;</a></li>
<li class="page-item"><a class="page-link" href="/{{total_pages}}">&gt;&gt;</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link" >&gt;&gt;</a></li>
{% endif %}
{% endif %}
{% endblock nav_pages %}
</ul>
</nav>
{% endblock %}

View File

@@ -8,6 +8,10 @@
{{ menu_title }}
{% endblock %}
{% block header_description %}
{{ header_description }}
{% endblock %}
{% block content %}
<!-- Main Content-->
<div class="conteiner ">

View File

@@ -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>

View File

@@ -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">&lt;&lt;</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link">&lt;&lt;</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}}">&gt;&gt;</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link">&gt;&gt;</a></li>
{% endif %}
{% endblock nav_pages %}

View File

@@ -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>

15
pyproger/templates/errors/404.html Executable file → Normal file
View File

@@ -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 %}

0
pyproger/templates/my_master.html Executable file → Normal file
View File

View File

@@ -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

0
pyproger/templates/security/_macros.html Executable file → Normal file
View File

0
pyproger/templates/security/change_password.html Executable file → Normal file
View File

0
pyproger/templates/security/forgot_password.html Executable file → Normal file
View File

0
pyproger/templates/security/login_user.html Executable file → Normal file
View File

0
pyproger/templates/security/register_user.html Executable file → Normal file
View File

0
pyproger/templates/security/reset_password.html Executable file → Normal file
View File

View File

@@ -1,5 +1,3 @@
import os
from flask import Blueprint, current_app, request
from flask_babel import Babel

View File

View File

@@ -6,7 +6,7 @@ authors = ["Sergey Vanyushkin <pi3c@yandex.ru>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.11"
python = "^3.10"
flask = "^2.3.3"
gunicorn = "^21.2.0"
sqlalchemy = "^2.0.20"
@@ -14,12 +14,14 @@ flask-sqlalchemy = "^3.1.1"
flask-security-too = "^5.3.0"
flask-migrate = "^4.0.5"
flask-admin = "^1.6.1"
psycopg2 = "^2.9.7"
click = "^8.1.7"
flask-ckeditor = "^0.4.6"
flask-babel = "^3.1.0"
python-dotenv = "^1.0.0"
werkzeug = "2.3.7"
psycopg2-binary = "^2.9.9"
pydantic = "^2.5.3"
pydantic-settings = "^2.1.0"
[build-system]

5
pytest.ini Normal file
View File

@@ -0,0 +1,5 @@
[pytest]
# env_override_existing_values = 1
pythonpath = . pyproger
env_files =
.test.env

0
tests/conftest.py Normal file
View File

0
wsgi.py Executable file → Normal file
View File