From f3e77ffa2700b140268249b2e82355ad043a63f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=BE=D1=81=D0=BB=D0=B0=D0=B2=D1=86=D0=B5=D0=B2=20?= =?UTF-8?q?=D0=A1=D1=82=D0=B0=D0=BD=D0=B8=D1=81=D0=BB=D0=B0=D0=B2=20=D0=92?= =?UTF-8?q?=D0=B0=D1=81=D0=B8=D0=BB=D1=8C=D0=B5=D0=B2=D0=B8=D1=87?= Date: Fri, 22 Nov 2024 06:55:42 +0000 Subject: [PATCH 1/4] DB changes + start message link change --- controllerBD/db_loader.py | 3 +- controllerBD/models.py | 73 +++++++++++++++++----------------- data/config.py | 1 + handlers/admin/admin_report.py | 48 +++++++++++++++------- handlers/user/first_check.py | 4 +- loader.py | 4 ++ requirements.txt | 1 + 7 files changed, 80 insertions(+), 54 deletions(-) diff --git a/controllerBD/db_loader.py b/controllerBD/db_loader.py index 90c8705..d7d2504 100644 --- a/controllerBD/db_loader.py +++ b/controllerBD/db_loader.py @@ -1,7 +1,8 @@ from sqlalchemy import create_engine from sqlalchemy.orm import declarative_base, Session +from data.config import DB_DSN -engine = create_engine('sqlite:///db/coffee_database.db') +engine = create_engine(DB_DSN) engine.connect() db_session = Session(bind=engine) diff --git a/controllerBD/models.py b/controllerBD/models.py index 884f712..f40dc2d 100644 --- a/controllerBD/models.py +++ b/controllerBD/models.py @@ -1,77 +1,78 @@ -from sqlalchemy import Integer, Column, ForeignKey, Text +from sqlalchemy import Integer, String, ForeignKey +from sqlalchemy.orm import mapped_column, Mapped from .db_loader import engine, Base class Gender(Base): __tablename__ = 'genders' - id = Column(Integer, primary_key=True, unique=True) - gender_name = Column(Text(100), nullable=False, unique=True) + id: Mapped[int] = mapped_column(primary_key=True, unique=True) + gender_name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) class Users(Base): __tablename__ = 'user_info' - id = Column(Integer, primary_key=True, autoincrement=True) - teleg_id = Column(Integer, nullable=False, unique=True) - name = Column(Text(100), nullable=False) - birthday = Column(Text(), nullable=False) - about = Column(Text(500), nullable=False) - gender = Column(Integer, ForeignKey('genders.id')) + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + teleg_id: Mapped[int] = mapped_column(nullable=False, unique=True) + name: Mapped[str] = mapped_column(String(100), nullable=False) + birthday: Mapped[str] + about: Mapped[str] = mapped_column(String(500), nullable=False) + gender: Mapped[int] = mapped_column(Integer, ForeignKey('genders.id')) class BanList(Base): __tablename__ = 'ban_list' - id = Column(Integer, primary_key=True, autoincrement=True) - banned_user_id = Column(Integer, ForeignKey('user_info.id')) - ban_status = Column(Integer, nullable=False, default=1) - date_of_ban = Column(Text(), nullable=False, default='null') - comment_to_ban = Column(Text(500), nullable=False) - date_of_unban = Column(Text(), nullable=False, default='null') - comment_to_unban = Column(Text(500), nullable=False, default='null') + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + banned_user_id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id')) + ban_status: Mapped[int] = mapped_column(Integer, default=1) + date_of_ban: Mapped[str] = mapped_column(default='null') + comment_to_ban: Mapped[str] = mapped_column(String(500)) + date_of_unban: Mapped[str] = mapped_column(default='null') + comment_to_unban: Mapped[str] = mapped_column(String(500), default='null') class Holidays(Base): __tablename__ = 'holidays_status' - id = Column(Integer, ForeignKey('user_info.id'), primary_key=True) - status = Column(Integer, nullable=False, default=0) - till_date = Column(Text, nullable=False, default='null') + id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + status: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + till_date: Mapped[str] = mapped_column(String, nullable=False, default='null') class MetInfo(Base): __tablename__ = 'met_info' - id = Column(Integer, primary_key=True, autoincrement=True) - first_user_id = Column(Integer, ForeignKey('user_info.id')) - second_user_id = Column(Integer, ForeignKey('user_info.id')) - date = Column(Text()) + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + first_user_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + second_user_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + date: Mapped[str] class MetsReview(Base): __tablename__ = 'mets_reviews' - id = Column(Integer, primary_key=True, autoincrement=True) - met_id = Column(Integer, ForeignKey('met_info.id')) - who_id = Column(Integer, ForeignKey('user_info.id')) - about_whom_id = Column(Integer, ForeignKey('user_info.id')) - grade = Column(Integer, nullable=False) - comment = Column(Text(500), nullable=True) - date_of_comment = Column(Text()) + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + met_id: Mapped[int] = mapped_column(ForeignKey('met_info.id')) + who_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + about_whom_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + grade: Mapped[int] + comment: Mapped[str | None] = mapped_column(String(500), nullable=True) + date_of_comment: Mapped[str] class UserMets(Base): __tablename__ = 'user_mets' - id = Column(Integer, ForeignKey('user_info.id'), primary_key=True) - met_info = Column(Text(), nullable=False, default='{}') + id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + met_info: Mapped[str] = mapped_column(nullable=False, default='{}') class UserStatus(Base): __tablename__ = 'user_status' - id = Column(Integer, ForeignKey('user_info.id'), primary_key=True) - status = Column(Integer, nullable=False, default=1) + id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + status: Mapped[int] = mapped_column(default=1) class Username(Base): __tablename__ = 'tg_usernames' - id = Column(Integer, ForeignKey('user_info.id'), primary_key=True) - username = Column(Text(), nullable=True) + id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + username: Mapped[str] def create_tables(): diff --git a/data/config.py b/data/config.py index b477eeb..2b5744d 100644 --- a/data/config.py +++ b/data/config.py @@ -5,6 +5,7 @@ load_dotenv() TOKEN = os.getenv('TG_TOKEN') +DB_DSN = os.getenv('DB_DSN') ADMIN_TG_ID = os.getenv('ADMIN_TG_ID') DEFAULT_PARE_iD = os.getenv('DEFAULT_PARE_iD') DEFAULT_PARE_USERNAME = os.getenv('DEFAULT_PARE_USERNAME') diff --git a/handlers/admin/admin_report.py b/handlers/admin/admin_report.py index 428a4b1..4c4c255 100644 --- a/handlers/admin/admin_report.py +++ b/handlers/admin/admin_report.py @@ -5,26 +5,44 @@ def prepare_user_info(): """Формируем список пользователей со штрафными баллами и другой инф.""" - query = text("""SELECT mr.about_whom_id, ui.teleg_id, ui.name, un.username, + query = text("""SELECT + mr.about_whom_id, + ui.teleg_id, + ui.name, + un.username, COUNT( - CASE - WHEN mr.grade=0 THEN 1 - ELSE NULL - END - ) as cnt_fail, - MAX(mr.date_of_comment) as last_comment, mr.comment, + CASE + WHEN mr.grade = 0 THEN 1 + ELSE NULL + END + ) AS cnt_fail, + MAX(mr.date_of_comment) AS last_comment, + mr.comment, bl.ban_status - FROM mets_reviews as mr - LEFT JOIN user_info as ui + FROM mets_reviews AS mr + LEFT JOIN user_info AS ui ON mr.about_whom_id = ui.id - LEFT JOIN tg_usernames as un + LEFT JOIN tg_usernames AS un ON mr.about_whom_id = un.id - LEFT JOIN (SELECT *, MAX(id) FROM ban_list GROUP BY banned_user_id) - as bl + LEFT JOIN ( + SELECT + banned_user_id, + MAX(id) AS max_id, + ban_status + FROM ban_list + GROUP BY banned_user_id, ban_status + ) AS bl ON mr.about_whom_id = bl.banned_user_id - WHERE mr.grade = 0 - GROUP BY mr.about_whom_id - ORDER BY mr.date_of_comment""") + WHERE mr.grade = 0 + GROUP BY + mr.about_whom_id, + ui.teleg_id, + ui.name, + un.username, + mr.comment, + bl.ban_status + ORDER BY MAX(mr.date_of_comment) DESC; + """) users = db_session.execute(query) return users diff --git a/handlers/user/first_check.py b/handlers/user/first_check.py index 599c842..cd06f1b 100644 --- a/handlers/user/first_check.py +++ b/handlers/user/first_check.py @@ -22,8 +22,8 @@ async def check_and_add_registration_button(message: types.Message): " отвечать не хочется, то часть шагов можно пропустить.\n\n" "Нажми кнопку \"Регистрация\" ниже.\n\n" "Для общения, помощи и рассказов о том, как прошла " - "встреча присоединяйся к уютному сообществу бота в " - "телеграм https://t.me/+Ai1RweqsyjFhNmFi" + "встреча присоединяйся к нашему IT сообществу в " + "телеграм https://t.me/ViribusUnitisGroup" ), reply_markup=start_registr_markup() ) diff --git a/loader.py b/loader.py index fbad553..d874754 100644 --- a/loader.py +++ b/loader.py @@ -1,6 +1,7 @@ import logging from datetime import datetime from logging.handlers import RotatingFileHandler +import os import pytz from aiogram import Bot, Dispatcher @@ -28,6 +29,9 @@ def timetz(*args): aiogram_logger.setLevel(logging.INFO) schedule_logger.setLevel(level=logging.DEBUG) +if not(os.path.isdir("logs")): + os.mkdir('logs') + main_handler = RotatingFileHandler( 'logs/my_logger.log', maxBytes=30000000, diff --git a/requirements.txt b/requirements.txt index b4525fb..d2e10a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ pytz==2022.7 SQLAlchemy==2.0.4 typing_extensions==4.5.0 yarl==1.8.2 +psycopg2 From f921921b66e41f1cb1dddbd14fff008d39c4ea62 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Wed, 11 Dec 2024 10:40:05 +0300 Subject: [PATCH 2/4] Alembic + pair generation config + workflows --- .github/workflows/build_and_publish.yml | 129 +++++++++++++++++++++++ .github/workflows/checks.yml | 24 +++++ .github/workflows/deploy.yml | 53 ---------- .github/workflows/test_deploy.yml | 53 ---------- alembic.ini | 102 ++++++++++++++++++ docker-compose.yml | 32 ++++-- handlers/admin/handlers.py | 64 +++++++++-- keyboards/admin/admin_markups.py | 18 +++- migrations/env.py | 82 ++++++++++++++ migrations/script.py.mako | 24 +++++ migrations/versions/cbba5d89b3ac_init.py | 111 +++++++++++++++++++ requirements.txt | 1 + 12 files changed, 569 insertions(+), 124 deletions(-) create mode 100644 .github/workflows/build_and_publish.yml create mode 100644 .github/workflows/checks.yml delete mode 100644 .github/workflows/deploy.yml delete mode 100644 .github/workflows/test_deploy.yml create mode 100644 alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/cbba5d89b3ac_init.py diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml new file mode 100644 index 0000000..43eb61c --- /dev/null +++ b/.github/workflows/build_and_publish.yml @@ -0,0 +1,129 @@ +name: Build, publish and deploy docker + +on: + push: + branches: [ 'main' ] + tags: + - 'v*' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + name: Build and push + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=raw,value=test,enable=true + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + deploy-testing: + name: Deploy Testing + needs: build-and-push-image + runs-on: [ self-hosted, Linux, testing ] + environment: + name: Testing + env: + CONTAINER_NAME: com_profcomff_tgbot_print_test + permissions: + packages: read + steps: + - name: Pull new version + run: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test + - name: Migrate DB + run: | + docker run \ + --rm \ + --env DB_DSN=${{ secrets.DB_DSN }} \ + --env TG_TOKEN=${{ secrets.BOT_TOKEN }} \ + --env MARKETING_URL=${{ vars.MARKETING_URL }} \ + --env DEFAULT_PARE_iD=${{ vars.DEFAULT_PARE_iD }} \ + --env ADMIN_TG_ID=${{ vars.ADMIN_TG_ID }} \ + --env DEFAULT_PARE_USERNAME=${{ vars.DEFAULT_PARE_USERNAME }} \ + --name ${{ env.CONTAINER_NAME }}_migration \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test \ + alembic upgrade head + - name: Run new version + run: | + docker stop ${{ env.CONTAINER_NAME }} || true && docker rm ${{ env.CONTAINER_NAME }} || true + docker run \ + --detach \ + --restart on-failure:3 \ + --env DB_DSN='${{ secrets.DB_DSN }}' \ + --env BOT_TOKEN='${{ secrets.BOT_TOKEN }}' \ + --env MARKETING_URL='${{ vars.MARKETING_URL }}' \ + --env PRINT_URL='${{ vars.PRINT_URL }}' \ + --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ + --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --name ${{ env.CONTAINER_NAME }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test + + deploy-production: + name: Deploy Production + needs: build-and-push-image + if: startsWith(github.ref, 'refs/tags/v') + runs-on: [ self-hosted, Linux, production ] + environment: + name: Production + env: + CONTAINER_NAME: com_profcomff_tgbot_print + permissions: + packages: read + steps: + - name: Pull new version + run: docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + - name: Migrate DB + run: | + docker run \ + --rm \ + --env DB_DSN=${{ secrets.DB_DSN }} \ + --env BOT_TOKEN=${{ secrets.BOT_TOKEN }} \ + --env MARKETING_URL=${{ vars.MARKETING_URL }} \ + --env PRINT_URL=${{ vars.PRINT_URL }} \ + --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ + --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --name ${{ env.CONTAINER_NAME }}_migration \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ + alembic upgrade head + - name: Run new version + run: | + docker stop ${{ env.CONTAINER_NAME }} || true && docker rm ${{ env.CONTAINER_NAME }} || true + docker run \ + --detach \ + --restart always \ + --env DB_DSN='${{ secrets.DB_DSN }}' \ + --env BOT_TOKEN='${{ secrets.BOT_TOKEN }}' \ + --env MARKETING_URL='${{ vars.MARKETING_URL }}' \ + --env PRINT_URL='${{ vars.PRINT_URL }}' \ + --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ + --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --name ${{ env.CONTAINER_NAME }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..07479ca --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,24 @@ +name: Python package + +on: + pull_request: + + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: 3.11 + - uses: isort/isort-action@master + with: + requirementsFiles: "requirements.txt requirements.dev.txt" + - uses: psf/black@stable + - name: Comment if linting failed + if: ${{ failure() }} + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + :poop: Code linting failed, use `black` and `isort` to fix it. \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml deleted file mode 100644 index e28a2c1..0000000 --- a/.github/workflows/deploy.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: deploy_coffee_bot - -on: - push: - branches: [ "main" ] - -jobs: - build_and_push_to_docker_hub: - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Login to Docker - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Push to Docker Hub - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:latest - - deploy: - runs-on: ubuntu-latest - needs: build_and_push_to_docker_hub - steps: - - name: executing remote ssh commands to deploy - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USER }} - key: ${{ secrets.SSH_KEY }} - script: | - cd /home/random_coffee/ - sudo docker compose down - sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:latest - sudo docker pull ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:latest - sudo docker compose up -d - - send_message: - runs-on: ubuntu-latest - needs: deploy - steps: - - name: send message - uses: appleboy/telegram-action@master - with: - to: ${{ secrets.TELEGRAM_TO }} - token: ${{ secrets.TELEGRAM_TOKEN }} - message: ${{ github.workflow }} успешно выполнен! diff --git a/.github/workflows/test_deploy.yml b/.github/workflows/test_deploy.yml deleted file mode 100644 index 8f5728f..0000000 --- a/.github/workflows/test_deploy.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: deploy_coffee_bot_test - -on: - push: - branches: [ "test" ] - -jobs: - build_and_push_to_docker_hub: - name: Push Docker image to Docker Hub - runs-on: ubuntu-latest - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - name: Login to Docker - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Push to Docker Hub - uses: docker/build-push-action@v2 - with: - push: true - tags: ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:test - - deploy: - runs-on: ubuntu-latest - needs: build_and_push_to_docker_hub - steps: - - name: executing remote ssh commands to deploy - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USER }} - key: ${{ secrets.SSH_KEY }} - script: | - cd /home/test_random_coffee/ - sudo docker compose down - sudo docker rmi ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:test - sudo docker pull ${{ secrets.DOCKER_USERNAME }}/random_coffee_bot:test - sudo docker compose up -d - - send_message: - runs-on: ubuntu-latest - needs: deploy - steps: - - name: send message - uses: appleboy/telegram-action@master - with: - to: ${{ secrets.TELEGRAM_TO }} - token: ${{ secrets.TELEGRAM_TOKEN }} - message: ${{ github.workflow }} успешно выполнен! diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 0000000..317057e --- /dev/null +++ b/alembic.ini @@ -0,0 +1,102 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = migrations + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os # Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = driver://user:pass@localhost/dbname + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 481d5a2..2722b68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,29 @@ -version: '3.0' +version: '3.8' services: - bot: - image: tbf - working_dir: /bot + telegram-bot: + build: + context: . + dockerfile: Dockerfile + container_name: random-coffee-bot + env_file: + - .env + environment: + - DB_DSN=postgresql://bot_user:bot_password@db:5432/bot_database + depends_on: + - db + restart: always + + db: + image: postgres:latest + container_name: postgres-random-coffee + environment: + POSTGRES_USER: bot_user + POSTGRES_PASSWORD: bot_password + POSTGRES_DB: bot_database volumes: - - tbf:/bot/db - - tbf:/bot/logs + - postgres_data:/var/lib/postgresql/data restart: always - env_file: tbf/.env + +volumes: + postgres_data: \ No newline at end of file diff --git a/handlers/admin/handlers.py b/handlers/admin/handlers.py index ec7d1e8..c78f88f 100644 --- a/handlers/admin/handlers.py +++ b/handlers/admin/handlers.py @@ -8,8 +8,11 @@ from controllerBD.services import get_user_count_from_db from keyboards.admin import (admin_cancel_markup, admin_change_status_markup, admin_inform_markup, admin_menu_button, - admin_menu_markup, algo_start, cancel, - change_status, do_not_take_part_button, go_back, + admin_menu_markup, admin_pair_generation_markup, + force_pair_generation, cancel, pair_generation, + stop_pair_generation, renew_pair_generation, + change_pair_generation_date, change_status, + do_not_take_part_button, go_back, inform, inform_active_users, inform_bad_users, send_message_to_all_button, take_part_button) from loader import bot, logger @@ -87,6 +90,51 @@ async def change_status_message(message: types.Message): reply_markup=admin_change_status_markup() ) +@admin_handlers +async def pairs_config_message(message: types.Message): + """Вывод кнопок настроек генерации пар.""" + await bot.send_message( + message.from_user.id, + "Выберите вариант:", + reply_markup=admin_pair_generation_markup() + ) + +@admin_handlers +async def stop_generation(message: types.Message): + """Остановить генерацию пар.""" + await bot.send_message( + message.from_user.id, + "Данный функционал пока что не реализован :)", + reply_markup=admin_pair_generation_markup() + ) + +@admin_handlers +async def renew_generation(message: types.Message): + """Возобновить генерацию пар.""" + await bot.send_message( + message.from_user.id, + "Данный функционал пока что не реализован :)", + reply_markup=admin_pair_generation_markup() + ) + +@admin_handlers +async def change_generation_date(message: types.Message): + """Изменить день недели и время генерации пар.""" + await bot.send_message( + message.from_user.id, + "Данный функционал пока что не реализован :)", + reply_markup=admin_pair_generation_markup() + ) + +@admin_handlers +async def generate_pairs(message: types.Message): + """Сгенерировать пары вручную.""" + await start_algoritm() + await bot.send_message( + message.from_user.id, + "Пары успешно сгенерированы!", + reply_markup=admin_pair_generation_markup() + ) @admin_handlers async def take_part_yes(message: types.Message): @@ -108,12 +156,6 @@ async def take_part_no(message: types.Message): ) -@admin_handlers -async def handler_start_algoritm(message: types.Message): - """Ручной запуск алгоритма.""" - await start_algoritm() - - def change_admin_status(message: types.Message, status): user_id = get_id_from_user_info_table(message.from_user.id) db_session.query(UserStatus).filter(UserStatus.id == user_id). \ @@ -203,9 +245,13 @@ def register_admin_handlers(dp: Dispatcher): dp.register_message_handler(inform_message_1, text=inform_active_users) dp.register_message_handler(inform_message_2, text=inform_bad_users) dp.register_message_handler(change_status_message, text=change_status) + dp.register_message_handler(pairs_config_message, text=pair_generation) dp.register_message_handler(take_part_yes, text=take_part_button) dp.register_message_handler(take_part_no, text=do_not_take_part_button) - dp.register_message_handler(handler_start_algoritm, text=algo_start) + dp.register_message_handler(generate_pairs, text=force_pair_generation) + dp.register_message_handler(stop_generation, text=stop_pair_generation) + dp.register_message_handler(renew_generation, text=renew_pair_generation) + dp.register_message_handler(change_generation_date, text=change_pair_generation_date) dp.register_message_handler(request_message_to_all, text=send_message_to_all_button) dp.register_message_handler(get_message_and_send, diff --git a/keyboards/admin/admin_markups.py b/keyboards/admin/admin_markups.py index 1507f4c..bcd4b75 100644 --- a/keyboards/admin/admin_markups.py +++ b/keyboards/admin/admin_markups.py @@ -7,11 +7,12 @@ admin_menu_button = "Меню администратора" inform = "Отчет" ban_list = "Бан-лист" +pair_generation = "Настройки генерации пар" add_to_ban_list = "Добавить в бан-лист" remove_from_ban_list = "Убрать из бана" go_back = "Назад" -algo_start = "Алгоритм" +# algo_start = "Сгенерировать пары сейчас" review_messages = "Опрос" change_status = "Статус участия" cancel = "Отмена" @@ -19,6 +20,11 @@ do_not_take_part_button = "Не принимать участие" send_message_to_all_button = "Сообщение пользователям" +force_pair_generation = "Сгенерировать пары сейчас" +stop_pair_generation = "Остановить генерацию пар" +renew_pair_generation = "Начать генерацию пар" +change_pair_generation_date = "Изменить дату генерации пар" + inform_active_users = "Участники" inform_bad_users = "Нарушители" @@ -43,7 +49,7 @@ def admin_menu_markup(): """Меню админа.""" markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) markup.row(inform, send_message_to_all_button) - markup.row(ban_list, change_status) + markup.row(ban_list, change_status, pair_generation) markup.row(back_to_main,) return markup @@ -65,6 +71,14 @@ def admin_change_status_markup(): markup.add(go_back) return markup +def admin_pair_generation_markup(): + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.add(force_pair_generation) + markup.add(stop_pair_generation) + markup.add(renew_pair_generation) + markup.add(change_pair_generation_date) + markup.add(go_back) + return markup def admin_back_markup(): """Кнопка назад""" diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..bd87528 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,82 @@ +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +from controllerBD.db_loader import Base +from controllerBD.models import * # УРААА КОСТЫЫЫЫЛЬ +from data.config import DB_DSN + + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config +# settings = Settings() + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + configuration = config.get_section(config.config_ini_section) + configuration["sqlalchemy.url"] = DB_DSN + connectable = engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() \ No newline at end of file diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..1e4564e --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} \ No newline at end of file diff --git a/migrations/versions/cbba5d89b3ac_init.py b/migrations/versions/cbba5d89b3ac_init.py new file mode 100644 index 0000000..8aa7855 --- /dev/null +++ b/migrations/versions/cbba5d89b3ac_init.py @@ -0,0 +1,111 @@ +"""init + +Revision ID: cbba5d89b3ac +Revises: +Create Date: 2024-12-11 10:34:50.845505 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cbba5d89b3ac' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('genders', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('gender_name', sa.String(length=100), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('gender_name'), + sa.UniqueConstraint('id') + ) + op.create_table('user_info', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('teleg_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('birthday', sa.String(), nullable=False), + sa.Column('about', sa.String(length=500), nullable=False), + sa.Column('gender', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['gender'], ['genders.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('teleg_id') + ) + op.create_table('ban_list', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('banned_user_id', sa.Integer(), nullable=False), + sa.Column('ban_status', sa.Integer(), nullable=False), + sa.Column('date_of_ban', sa.String(), nullable=False), + sa.Column('comment_to_ban', sa.String(length=500), nullable=False), + sa.Column('date_of_unban', sa.String(), nullable=False), + sa.Column('comment_to_unban', sa.String(length=500), nullable=False), + sa.ForeignKeyConstraint(['banned_user_id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('holidays_status', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('status', sa.Integer(), nullable=False), + sa.Column('till_date', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('met_info', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('first_user_id', sa.Integer(), nullable=False), + sa.Column('second_user_id', sa.Integer(), nullable=False), + sa.Column('date', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['first_user_id'], ['user_info.id'], ), + sa.ForeignKeyConstraint(['second_user_id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('tg_usernames', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('username', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_mets', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('met_info', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_status', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('status', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('mets_reviews', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('met_id', sa.Integer(), nullable=False), + sa.Column('who_id', sa.Integer(), nullable=False), + sa.Column('about_whom_id', sa.Integer(), nullable=False), + sa.Column('grade', sa.Integer(), nullable=False), + sa.Column('comment', sa.String(length=500), nullable=True), + sa.Column('date_of_comment', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['about_whom_id'], ['user_info.id'], ), + sa.ForeignKeyConstraint(['met_id'], ['met_info.id'], ), + sa.ForeignKeyConstraint(['who_id'], ['user_info.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('mets_reviews') + op.drop_table('user_status') + op.drop_table('user_mets') + op.drop_table('tg_usernames') + op.drop_table('met_info') + op.drop_table('holidays_status') + op.drop_table('ban_list') + op.drop_table('user_info') + op.drop_table('genders') + # ### end Alembic commands ### \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d2e10a7..b2ccc01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,3 +20,4 @@ SQLAlchemy==2.0.4 typing_extensions==4.5.0 yarl==1.8.2 psycopg2 +alembic From 259d45f3f82e0fc88f14af702944e28ad5da1742 Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Wed, 11 Dec 2024 10:40:15 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=9B=D0=98=D0=9D=D0=A2=D0=98=D0=9D=D0=93?= =?UTF-8?q?=20=D0=90=D0=90=D0=90=D0=90=D0=90=D0=90=D0=90=D0=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_and_publish.yml | 32 +-- controllerBD/add_info_to_db.py | 6 +- controllerBD/db_loader.py | 3 +- controllerBD/models.py | 64 +++--- controllerBD/services.py | 89 +++++---- data/__init__.py | 2 +- data/config.py | 10 +- handlers/admin/admin_report.py | 29 +-- handlers/admin/ban_handlers.py | 101 +++++----- handlers/admin/handlers.py | 130 ++++++------ handlers/admin/validators/__init__.py | 2 +- handlers/admin/validators/validators.py | 26 +-- handlers/decorators.py | 9 +- handlers/user/add_username.py | 16 +- handlers/user/ban_check.py | 10 +- handlers/user/check_message.py | 23 ++- handlers/user/first_check.py | 35 ++-- handlers/user/get_info_from_table.py | 41 ++-- handlers/user/handlers.py | 120 ++++++------ handlers/user/help_texts.py | 35 ++-- handlers/user/holidays.py | 130 ++++++------ handlers/user/new_member.py | 140 +++++++------ handlers/user/review_history.py | 164 ++++++++-------- handlers/user/reviews.py | 168 +++++++++------- handlers/user/start_handler.py | 17 +- handlers/user/unknown_message.py | 4 +- handlers/user/validators/__init__.py | 2 +- handlers/user/validators/validators.py | 48 ++--- handlers/user/work_with_date.py | 8 +- keyboards/admin/admin_markups.py | 10 +- keyboards/user/defalt_markups.py | 16 +- loader.py | 27 +-- main.py | 16 +- match_algoritm/MatchingHelper.py | 47 +++-- match_algoritm/__init__.py | 2 +- match_algoritm/blossom.py | 228 ++++++++++++++-------- match_algoritm/test_blossom.py | 13 +- match_algoritm/tests/create_test_graph.py | 22 +-- migrations/env.py | 5 +- migrations/versions/cbba5d89b3ac_init.py | 202 +++++++++++-------- requirements.dev.txt | 2 + sendler/__init__.py | 2 +- sendler/match_messages.py | 108 ++++++---- states/__init__.py | 2 +- states/states.py | 2 + 45 files changed, 1220 insertions(+), 948 deletions(-) create mode 100644 requirements.dev.txt diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml index 43eb61c..06a7270 100644 --- a/.github/workflows/build_and_publish.yml +++ b/.github/workflows/build_and_publish.yml @@ -77,12 +77,12 @@ jobs: docker run \ --detach \ --restart on-failure:3 \ - --env DB_DSN='${{ secrets.DB_DSN }}' \ - --env BOT_TOKEN='${{ secrets.BOT_TOKEN }}' \ - --env MARKETING_URL='${{ vars.MARKETING_URL }}' \ - --env PRINT_URL='${{ vars.PRINT_URL }}' \ - --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ - --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --env DB_DSN=${{ secrets.DB_DSN }} \ + --env TG_TOKEN=${{ secrets.BOT_TOKEN }} \ + --env MARKETING_URL=${{ vars.MARKETING_URL }} \ + --env DEFAULT_PARE_iD=${{ vars.DEFAULT_PARE_iD }} \ + --env ADMIN_TG_ID=${{ vars.ADMIN_TG_ID }} \ + --env DEFAULT_PARE_USERNAME=${{ vars.DEFAULT_PARE_USERNAME }} \ --name ${{ env.CONTAINER_NAME }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:test @@ -105,11 +105,11 @@ jobs: docker run \ --rm \ --env DB_DSN=${{ secrets.DB_DSN }} \ - --env BOT_TOKEN=${{ secrets.BOT_TOKEN }} \ + --env TG_TOKEN=${{ secrets.BOT_TOKEN }} \ --env MARKETING_URL=${{ vars.MARKETING_URL }} \ - --env PRINT_URL=${{ vars.PRINT_URL }} \ - --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ - --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --env DEFAULT_PARE_iD=${{ vars.DEFAULT_PARE_iD }} \ + --env ADMIN_TG_ID=${{ vars.ADMIN_TG_ID }} \ + --env DEFAULT_PARE_USERNAME=${{ vars.DEFAULT_PARE_USERNAME }} \ --name ${{ env.CONTAINER_NAME }}_migration \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ alembic upgrade head @@ -119,11 +119,11 @@ jobs: docker run \ --detach \ --restart always \ - --env DB_DSN='${{ secrets.DB_DSN }}' \ - --env BOT_TOKEN='${{ secrets.BOT_TOKEN }}' \ - --env MARKETING_URL='${{ vars.MARKETING_URL }}' \ - --env PRINT_URL='${{ vars.PRINT_URL }}' \ - --env PRINT_URL_QR=${{ vars.PRINT_URL_QR }} \ - --env MAX_PDF_SIZE_MB=${{ vars.MAX_PDF_SIZE_MB }} \ + --env DB_DSN=${{ secrets.DB_DSN }} \ + --env TG_TOKEN=${{ secrets.BOT_TOKEN }} \ + --env MARKETING_URL=${{ vars.MARKETING_URL }} \ + --env DEFAULT_PARE_iD=${{ vars.DEFAULT_PARE_iD }} \ + --env ADMIN_TG_ID=${{ vars.ADMIN_TG_ID }} \ + --env DEFAULT_PARE_USERNAME=${{ vars.DEFAULT_PARE_USERNAME }} \ --name ${{ env.CONTAINER_NAME }} \ ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ No newline at end of file diff --git a/controllerBD/add_info_to_db.py b/controllerBD/add_info_to_db.py index 2a63217..c9e567a 100644 --- a/controllerBD/add_info_to_db.py +++ b/controllerBD/add_info_to_db.py @@ -9,9 +9,9 @@ def add_gender_info(): - if not db_session.query(exists().where( - Gender.gender_name == 'Не указано' - )).scalar(): + if not db_session.query( + exists().where(Gender.gender_name == "Не указано") + ).scalar(): db_session.add_all([el1, el2, el3]) db_session.commit() diff --git a/controllerBD/db_loader.py b/controllerBD/db_loader.py index d7d2504..4568b1a 100644 --- a/controllerBD/db_loader.py +++ b/controllerBD/db_loader.py @@ -1,5 +1,6 @@ from sqlalchemy import create_engine -from sqlalchemy.orm import declarative_base, Session +from sqlalchemy.orm import Session, declarative_base + from data.config import DB_DSN engine = create_engine(DB_DSN) diff --git a/controllerBD/models.py b/controllerBD/models.py index f40dc2d..eaa1e4e 100644 --- a/controllerBD/models.py +++ b/controllerBD/models.py @@ -1,77 +1,85 @@ -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import mapped_column, Mapped +from sqlalchemy import ForeignKey, Integer, String +from sqlalchemy.orm import Mapped, mapped_column -from .db_loader import engine, Base +from .db_loader import Base, engine class Gender(Base): - __tablename__ = 'genders' + __tablename__ = "genders" id: Mapped[int] = mapped_column(primary_key=True, unique=True) gender_name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) class Users(Base): - __tablename__ = 'user_info' + __tablename__ = "user_info" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) teleg_id: Mapped[int] = mapped_column(nullable=False, unique=True) name: Mapped[str] = mapped_column(String(100), nullable=False) birthday: Mapped[str] about: Mapped[str] = mapped_column(String(500), nullable=False) - gender: Mapped[int] = mapped_column(Integer, ForeignKey('genders.id')) + gender: Mapped[int] = mapped_column(Integer, ForeignKey("genders.id")) class BanList(Base): - __tablename__ = 'ban_list' + __tablename__ = "ban_list" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - banned_user_id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id')) + banned_user_id: Mapped[int] = mapped_column(Integer, ForeignKey("user_info.id")) ban_status: Mapped[int] = mapped_column(Integer, default=1) - date_of_ban: Mapped[str] = mapped_column(default='null') + date_of_ban: Mapped[str] = mapped_column(default="null") comment_to_ban: Mapped[str] = mapped_column(String(500)) - date_of_unban: Mapped[str] = mapped_column(default='null') - comment_to_unban: Mapped[str] = mapped_column(String(500), default='null') + date_of_unban: Mapped[str] = mapped_column(default="null") + comment_to_unban: Mapped[str] = mapped_column(String(500), default="null") class Holidays(Base): - __tablename__ = 'holidays_status' - id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + __tablename__ = "holidays_status" + id: Mapped[int] = mapped_column( + Integer, ForeignKey("user_info.id"), primary_key=True + ) status: Mapped[int] = mapped_column(Integer, nullable=False, default=0) - till_date: Mapped[str] = mapped_column(String, nullable=False, default='null') + till_date: Mapped[str] = mapped_column(String, nullable=False, default="null") class MetInfo(Base): - __tablename__ = 'met_info' + __tablename__ = "met_info" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - first_user_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) - second_user_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + first_user_id: Mapped[int] = mapped_column(ForeignKey("user_info.id")) + second_user_id: Mapped[int] = mapped_column(ForeignKey("user_info.id")) date: Mapped[str] class MetsReview(Base): - __tablename__ = 'mets_reviews' + __tablename__ = "mets_reviews" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - met_id: Mapped[int] = mapped_column(ForeignKey('met_info.id')) - who_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) - about_whom_id: Mapped[int] = mapped_column(ForeignKey('user_info.id')) + met_id: Mapped[int] = mapped_column(ForeignKey("met_info.id")) + who_id: Mapped[int] = mapped_column(ForeignKey("user_info.id")) + about_whom_id: Mapped[int] = mapped_column(ForeignKey("user_info.id")) grade: Mapped[int] comment: Mapped[str | None] = mapped_column(String(500), nullable=True) date_of_comment: Mapped[str] class UserMets(Base): - __tablename__ = 'user_mets' - id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) - met_info: Mapped[str] = mapped_column(nullable=False, default='{}') + __tablename__ = "user_mets" + id: Mapped[int] = mapped_column( + Integer, ForeignKey("user_info.id"), primary_key=True + ) + met_info: Mapped[str] = mapped_column(nullable=False, default="{}") class UserStatus(Base): - __tablename__ = 'user_status' - id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + __tablename__ = "user_status" + id: Mapped[int] = mapped_column( + Integer, ForeignKey("user_info.id"), primary_key=True + ) status: Mapped[int] = mapped_column(default=1) class Username(Base): - __tablename__ = 'tg_usernames' - id: Mapped[int] = mapped_column(Integer, ForeignKey('user_info.id'), primary_key=True) + __tablename__ = "tg_usernames" + id: Mapped[int] = mapped_column( + Integer, ForeignKey("user_info.id"), primary_key=True + ) username: Mapped[str] diff --git a/controllerBD/services.py b/controllerBD/services.py index 75c43a0..ad61f11 100644 --- a/controllerBD/services.py +++ b/controllerBD/services.py @@ -16,31 +16,44 @@ async def update_mets(match_info: dict): if all(match): first_user = match[0] second_user = match[1] - db_session.add(MetInfo(first_user_id=first_user, - second_user_id=second_user, - date=str(date.today()))) + db_session.add( + MetInfo( + first_user_id=first_user, + second_user_id=second_user, + date=str(date.today()), + ) + ) except Exception as error: - logger.error(f'Встреча для пользователей {match} ' - f'не записана. Ошибка - {error}') + logger.error( + f"Встреча для пользователей {match} " f"не записана. Ошибка - {error}" + ) continue def update_one_user_mets(first_user: int, second_user: int): """Записывает в user_mets информацию об одном пользователе.""" - first_user_mets = db_session.query(UserMets.met_info).filter( - UserMets.id == first_user - ).first() + first_user_mets = ( + db_session.query(UserMets.met_info).filter(UserMets.id == first_user).first() + ) user_mets = json.loads(first_user_mets[0]) - new_met_id = db_session.query(MetInfo.id).filter( - MetInfo.date == date.today(), - or_( - MetInfo.first_user_id == first_user, - MetInfo.second_user_id == first_user - )).order_by(desc(MetInfo.id)).limit(1).first()[0] + new_met_id = ( + db_session.query(MetInfo.id) + .filter( + MetInfo.date == date.today(), + or_( + MetInfo.first_user_id == first_user, + MetInfo.second_user_id == first_user, + ), + ) + .order_by(desc(MetInfo.id)) + .limit(1) + .first()[0] + ) user_mets[new_met_id] = second_user new_mets_value = json.dumps(user_mets) - db_session.query(UserMets).filter(UserMets.id == first_user). \ - update({'met_info': new_mets_value}) + db_session.query(UserMets).filter(UserMets.id == first_user).update( + {"met_info": new_mets_value} + ) db_session.commit() @@ -53,48 +66,48 @@ def update_all_user_mets(match_info: dict): try: update_one_user_mets(first_user, second_user) except Exception as error: - logger.error(f'Информация о встречах пользователя ' - f'{first_user} не обновлена. ' - f' Ошибка - {error}') + logger.error( + f"Информация о встречах пользователя " + f"{first_user} не обновлена. " + f" Ошибка - {error}" + ) first_user = match[1] second_user = match[0] try: update_one_user_mets(first_user, second_user) except Exception as error: - logger.error(f'Информация о встречах пользователя ' - f'{first_user} не обновлена. ' - f' Ошибка - {error}') - logger.info('Запись информации о новых встречах завершена') + logger.error( + f"Информация о встречах пользователя " + f"{first_user} не обновлена. " + f" Ошибка - {error}" + ) + logger.info("Запись информации о новых встречах завершена") def get_defaulf_pare_base_id(): """Получить id дефолтного юзера из базы.""" - return db_session.query(Users.id).filter( - Users.teleg_id == int(DEFAULT_PARE_iD) - ).first()[0] + return ( + db_session.query(Users.id) + .filter(Users.teleg_id == int(DEFAULT_PARE_iD)) + .first()[0] + ) def get_user_count_from_db(): all_users = db_session.query(Users).count() - active_users = db_session.query(UserStatus).filter( - UserStatus.status == 1 - ).count() + active_users = db_session.query(UserStatus).filter(UserStatus.status == 1).count() return {"all_users": all_users, "active_users": active_users} def get_user_id_from_db(teleg_id: int) -> int: """Получает id юзера в базе по телеграм id""" - return db_session.query(Users.id).filter( - Users.teleg_id == teleg_id - ).first()[0] + return db_session.query(Users.id).filter(Users.teleg_id == teleg_id).first()[0] def get_tg_username_from_db_by_teleg_id(teleg_id: int) -> int: """Получает телеграм-юзернейм по telegram id""" base_id = get_user_id_from_db(teleg_id) - answer = db_session.query(Username.username).filter( - Username.id == base_id - ).first() + answer = db_session.query(Username.username).filter(Username.id == base_id).first() if answer: return answer[0] return None @@ -102,9 +115,7 @@ def get_tg_username_from_db_by_teleg_id(teleg_id: int) -> int: def get_tg_username_from_db_by_base_id(base_id: int) -> int: """Получает телеграм-юзернейм по id в базе""" - answer = db_session.query(Username.username).filter( - Username.id == base_id - ).first() + answer = db_session.query(Username.username).filter(Username.id == base_id).first() if answer: return answer[0] return None @@ -116,4 +127,4 @@ async def send_message_to_admins(message): try: await bot.send_message(i, message) except Exception as error: - logger.error(f'Сообщение {message} не ушло админу {i}. {error}') + logger.error(f"Сообщение {message} не ушло админу {i}. {error}") diff --git a/data/__init__.py b/data/__init__.py index d085c3a..27c9ec6 100644 --- a/data/__init__.py +++ b/data/__init__.py @@ -1 +1 @@ -from .config import * \ No newline at end of file +from .config import * diff --git a/data/config.py b/data/config.py index 2b5744d..9376600 100644 --- a/data/config.py +++ b/data/config.py @@ -4,8 +4,8 @@ load_dotenv() -TOKEN = os.getenv('TG_TOKEN') -DB_DSN = os.getenv('DB_DSN') -ADMIN_TG_ID = os.getenv('ADMIN_TG_ID') -DEFAULT_PARE_iD = os.getenv('DEFAULT_PARE_iD') -DEFAULT_PARE_USERNAME = os.getenv('DEFAULT_PARE_USERNAME') +TOKEN = os.getenv("TG_TOKEN") +DB_DSN = os.getenv("DB_DSN") +ADMIN_TG_ID = os.getenv("ADMIN_TG_ID") +DEFAULT_PARE_iD = os.getenv("DEFAULT_PARE_iD") +DEFAULT_PARE_USERNAME = os.getenv("DEFAULT_PARE_USERNAME") diff --git a/handlers/admin/admin_report.py b/handlers/admin/admin_report.py index 4c4c255..1fcecd0 100644 --- a/handlers/admin/admin_report.py +++ b/handlers/admin/admin_report.py @@ -1,11 +1,13 @@ from sqlalchemy import text + from controllerBD.db_loader import db_session from handlers.user.work_with_date import date_from_db_to_message def prepare_user_info(): """Формируем список пользователей со штрафными баллами и другой инф.""" - query = text("""SELECT + query = text( + """SELECT mr.about_whom_id, ui.teleg_id, ui.name, @@ -42,7 +44,8 @@ def prepare_user_info(): mr.comment, bl.ban_status ORDER BY MAX(mr.date_of_comment) DESC; - """) + """ + ) users = db_session.execute(query) return users @@ -56,27 +59,29 @@ def prepare_report_message(users): if not user[3]: username = "" else: - username = f' (@{user[3]})' + username = f" (@{user[3]})" if user[7] == 0 or user[7] is None: status = "Не забанен" else: status = "Забанен" - if user[6] == 'null': + if user[6] == "null": comment = "Комментарий не был добавлен." else: comment = f"{user[6]}" date = date_from_db_to_message(user[5]) - user_message = f'ID пользователя: {user[0]};\n' \ - f'Ник пользователя: ' \ - f'{user[2]}{username}.\n' \ - f'Статус: {status}.\n' \ - f'Штрафных балов - {user[4]}\n' \ - f'{date} Последний комментарий: {comment}' - if len(message + '\n\n' + user_message) > 4095: + user_message = ( + f"ID пользователя: {user[0]};\n" + f'Ник пользователя: ' + f"{user[2]}{username}.\n" + f"Статус: {status}.\n" + f"Штрафных балов - {user[4]}\n" + f"{date} Последний комментарий: {comment}" + ) + if len(message + "\n\n" + user_message) > 4095: message_list.append(message) message = user_message else: - message = message + '\n\n' + user_message + message = message + "\n\n" + user_message message_list.append(message) return message_list diff --git a/handlers/admin/ban_handlers.py b/handlers/admin/ban_handlers.py index 8420400..c783894 100644 --- a/handlers/admin/ban_handlers.py +++ b/handlers/admin/ban_handlers.py @@ -1,20 +1,24 @@ import datetime -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext from controllerBD.db_loader import db_session from controllerBD.models import BanList, Holidays, UserStatus from handlers.admin.handlers import admin_menu -from handlers.admin.validators import ban_validator, comment_validator, \ - unban_validator +from handlers.admin.validators import ban_validator, comment_validator, unban_validator from handlers.decorators import admin_handlers -from keyboards.admin import cancel, ban_list, admin_ban_markup, \ - add_to_ban_list, admin_cancel_markup, remove_from_ban_list, \ - back_to_main_markup +from keyboards.admin import ( + add_to_ban_list, + admin_ban_markup, + admin_cancel_markup, + back_to_main_markup, + ban_list, + cancel, + remove_from_ban_list, +) from keyboards.user import back_to_main from loader import bot, logger - from states import AdminData @@ -30,9 +34,7 @@ async def cancel_message(message: types.Message, state: FSMContext): async def ban_list_message(message: types.Message): """Вывод сообщения с выбором действия по бану.""" await bot.send_message( - message.from_user.id, - "Что вы хотите сделать?", - reply_markup=admin_ban_markup() + message.from_user.id, "Что вы хотите сделать?", reply_markup=admin_ban_markup() ) @@ -44,7 +46,7 @@ async def ban_list_add(message: types.Message): await bot.send_message( message.from_user.id, "Введите id пользователя, которого необходимо забанить:", - reply_markup=admin_cancel_markup() + reply_markup=admin_cancel_markup(), ) await AdminData.user_ban.set() @@ -52,8 +54,7 @@ async def ban_list_add(message: types.Message): # @dp.message_handler(state=AdminData.user_ban) async def ban_list_add_answer(message: types.Message, state: FSMContext): """Получение ответа от админа и проверка введенного id.""" - logger.info(f"Для добавления в бан введен пользователь " - f"с if {message.text}.") + logger.info(f"Для добавления в бан введен пользователь " f"с if {message.text}.") user_id = message.text if not await ban_validator(message): return @@ -81,27 +82,32 @@ async def comment_to_ban_answer(message: types.Message, state: FSMContext): if not await comment_validator(comment): await bot.send_message( message.from_user.id, - "Комментарий должен быть не менее 10 и не более 500 символов" + "Комментарий должен быть не менее 10 и не более 500 символов", ) return data = await state.get_data() - banned_user_id = data.get('banned_user_id') + banned_user_id = data.get("banned_user_id") await save_to_ban(banned_user_id, comment) - await bot.send_message(message.from_user.id, - "Пользователь добавлен в бан-лист") + await bot.send_message(message.from_user.id, "Пользователь добавлен в бан-лист") await back_to_main_message(message, state) async def save_to_ban(banned_user_id, comment): """Запись в БД пользователя с баном.""" - db_session.add(BanList(banned_user_id=banned_user_id, - ban_status=1, - comment_to_ban=comment, - date_of_ban=str(datetime.date.today()))) - db_session.query(Holidays).filter(Holidays.id == banned_user_id). \ - update({'status': 0, 'till_date': 'null'}) - db_session.query(UserStatus).filter(UserStatus.id == banned_user_id). \ - update({'status': 0}) + db_session.add( + BanList( + banned_user_id=banned_user_id, + ban_status=1, + comment_to_ban=comment, + date_of_ban=str(datetime.date.today()), + ) + ) + db_session.query(Holidays).filter(Holidays.id == banned_user_id).update( + {"status": 0, "till_date": "null"} + ) + db_session.query(UserStatus).filter(UserStatus.id == banned_user_id).update( + {"status": 0} + ) db_session.commit() @@ -113,7 +119,7 @@ async def ban_list_remove(message: types.Message): await bot.send_message( message.from_user.id, "Введите id пользователя, которого необходимо убрать из бан листа:", - reply_markup=admin_cancel_markup() + reply_markup=admin_cancel_markup(), ) await AdminData.user_unban.set() @@ -121,8 +127,7 @@ async def ban_list_remove(message: types.Message): # @dp.message_handler(state=AdminData.user_unban) async def ban_list_remove_answer(message: types.Message, state: FSMContext): """Получение ответа с id пользователем для вывода из бана. Валидация.""" - logger.info(f"Для вывода из бана введен пользователь " - f"с if {message.text}.") + logger.info(f"Для вывода из бана введен пользователь " f"с if {message.text}.") user_id = message.text if not await unban_validator(message): return @@ -149,26 +154,28 @@ async def comment_to_unban_answer(message: types.Message, state: FSMContext): if not await comment_validator(comment): await bot.send_message( message.from_user.id, - "Комментарий должен быть не менее 10 и не более 500 символов" + "Комментарий должен быть не менее 10 и не более 500 символов", ) return data = await state.get_data() - unbanned_user_id = data.get('unbanned_user_id') + unbanned_user_id = data.get("unbanned_user_id") await save_to_unban(unbanned_user_id, comment) - await bot.send_message(message.from_user.id, - "Пользователь исключен из бан-листа") + await bot.send_message(message.from_user.id, "Пользователь исключен из бан-листа") await back_to_main_message(message, state) async def save_to_unban(unbanned_user_id, comment): """Сохранение в БД, что пользователь выведен из бана.""" - db_session.query(BanList).filter( - BanList.banned_user_id == unbanned_user_id - ).update({'ban_status': 0, - 'date_of_unban': datetime.date.today(), - 'comment_to_unban': comment}) - db_session.query(UserStatus).filter(UserStatus.id == unbanned_user_id). \ - update({'status': 1}) + db_session.query(BanList).filter(BanList.banned_user_id == unbanned_user_id).update( + { + "ban_status": 0, + "date_of_unban": datetime.date.today(), + "comment_to_unban": comment, + } + ) + db_session.query(UserStatus).filter(UserStatus.id == unbanned_user_id).update( + {"status": 1} + ) db_session.commit() @@ -179,7 +186,7 @@ async def back_to_main_message(message: types.Message, state: FSMContext): await bot.send_message( message.from_user.id, "Вы в главном меню", - reply_markup=back_to_main_markup(message) + reply_markup=back_to_main_markup(message), ) @@ -188,12 +195,10 @@ def register_admin_ban_handlers(dp: Dispatcher): dp.register_message_handler(ban_list_message, text=ban_list) dp.register_message_handler(ban_list_add, text=add_to_ban_list) dp.register_message_handler(ban_list_add_answer, state=AdminData.user_ban) - dp.register_message_handler(comment_to_ban_answer, - state=AdminData.comment_to_ban) + dp.register_message_handler(comment_to_ban_answer, state=AdminData.comment_to_ban) dp.register_message_handler(ban_list_remove, text=remove_from_ban_list) - dp.register_message_handler(ban_list_remove_answer, - state=AdminData.user_unban) - dp.register_message_handler(comment_to_unban_answer, - state=AdminData.comment_to_unban) - dp.register_message_handler(back_to_main_message, - text=back_to_main, state="*") + dp.register_message_handler(ban_list_remove_answer, state=AdminData.user_unban) + dp.register_message_handler( + comment_to_unban_answer, state=AdminData.comment_to_unban + ) + dp.register_message_handler(back_to_main_message, text=back_to_main, state="*") diff --git a/handlers/admin/handlers.py b/handlers/admin/handlers.py index c78f88f..ce77ea4 100644 --- a/handlers/admin/handlers.py +++ b/handlers/admin/handlers.py @@ -3,27 +3,39 @@ from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext from aiogram.utils.exceptions import BotBlocked + from controllerBD.db_loader import db_session from controllerBD.models import UserStatus from controllerBD.services import get_user_count_from_db -from keyboards.admin import (admin_cancel_markup, admin_change_status_markup, - admin_inform_markup, admin_menu_button, - admin_menu_markup, admin_pair_generation_markup, - force_pair_generation, cancel, pair_generation, - stop_pair_generation, renew_pair_generation, - change_pair_generation_date, change_status, - do_not_take_part_button, go_back, - inform, inform_active_users, inform_bad_users, - send_message_to_all_button, take_part_button) -from loader import bot, logger -from match_algoritm.MatchingHelper import start_algoritm -from states import AdminData - -from handlers.admin.admin_report import (prepare_report_message, - prepare_user_info) +from handlers.admin.admin_report import prepare_report_message, prepare_user_info from handlers.decorators import admin_handlers from handlers.user.check_message import prepare_user_list, send_message from handlers.user.get_info_from_table import get_id_from_user_info_table +from keyboards.admin import ( + admin_cancel_markup, + admin_change_status_markup, + admin_inform_markup, + admin_menu_button, + admin_menu_markup, + admin_pair_generation_markup, + cancel, + change_pair_generation_date, + change_status, + do_not_take_part_button, + force_pair_generation, + go_back, + inform, + inform_active_users, + inform_bad_users, + pair_generation, + renew_pair_generation, + send_message_to_all_button, + stop_pair_generation, + take_part_button, +) +from loader import bot, logger +from match_algoritm.MatchingHelper import start_algoritm +from states import AdminData @admin_handlers @@ -38,7 +50,7 @@ async def admin_menu(message: types.Message): await bot.send_message( message.from_user.id, text="Выберите из доступных вариантов:", - reply_markup=admin_menu_markup() + reply_markup=admin_menu_markup(), ) @@ -48,7 +60,7 @@ async def inform_message(message: types.Message): await bot.send_message( message.from_user.id, "Выберите из доступных вариантов:", - reply_markup=admin_inform_markup() + reply_markup=admin_inform_markup(), ) @@ -59,7 +71,7 @@ async def inform_message_1(message: types.Message): await bot.send_message( message.from_user.id, f"Всего пользователей - {users['all_users']};\n\n" - f"Активных пользователей - {users['active_users']}." + f"Активных пользователей - {users['active_users']}.", ) @@ -68,16 +80,12 @@ async def inform_message_2(message: types.Message): """Получение сообщения о пользователях со штрафными балами.""" bad_users = prepare_user_info() message_list = prepare_report_message(bad_users) - if message_list[0] == '': - await bot.send_message( - message.from_user.id, - "Отчёт пустой") + if message_list[0] == "": + await bot.send_message(message.from_user.id, "Отчёт пустой") else: for message_text in message_list: await bot.send_message( - message.from_user.id, - f"{message_text}", - parse_mode="HTML" + message.from_user.id, f"{message_text}", parse_mode="HTML" ) @@ -87,45 +95,50 @@ async def change_status_message(message: types.Message): await bot.send_message( message.from_user.id, "Выберите вариант:", - reply_markup=admin_change_status_markup() + reply_markup=admin_change_status_markup(), ) + @admin_handlers async def pairs_config_message(message: types.Message): """Вывод кнопок настроек генерации пар.""" await bot.send_message( message.from_user.id, "Выберите вариант:", - reply_markup=admin_pair_generation_markup() + reply_markup=admin_pair_generation_markup(), ) + @admin_handlers async def stop_generation(message: types.Message): """Остановить генерацию пар.""" await bot.send_message( message.from_user.id, "Данный функционал пока что не реализован :)", - reply_markup=admin_pair_generation_markup() + reply_markup=admin_pair_generation_markup(), ) + @admin_handlers async def renew_generation(message: types.Message): """Возобновить генерацию пар.""" await bot.send_message( message.from_user.id, "Данный функционал пока что не реализован :)", - reply_markup=admin_pair_generation_markup() + reply_markup=admin_pair_generation_markup(), ) + @admin_handlers async def change_generation_date(message: types.Message): """Изменить день недели и время генерации пар.""" await bot.send_message( message.from_user.id, "Данный функционал пока что не реализован :)", - reply_markup=admin_pair_generation_markup() + reply_markup=admin_pair_generation_markup(), ) + @admin_handlers async def generate_pairs(message: types.Message): """Сгенерировать пары вручную.""" @@ -133,16 +146,16 @@ async def generate_pairs(message: types.Message): await bot.send_message( message.from_user.id, "Пары успешно сгенерированы!", - reply_markup=admin_pair_generation_markup() + reply_markup=admin_pair_generation_markup(), ) + @admin_handlers async def take_part_yes(message: types.Message): """Изменение статуса на принимать участие.""" change_admin_status(message, 1) await bot.send_message( - message.from_user.id, - "Теперь вы участвуете в распределении." + message.from_user.id, "Теперь вы участвуете в распределении." ) @@ -152,14 +165,15 @@ async def take_part_no(message: types.Message): change_admin_status(message, 0) await bot.send_message( message.from_user.id, - "Вы изменили статус и теперь не участвуете в распределении." + "Вы изменили статус и теперь не участвуете в распределении.", ) def change_admin_status(message: types.Message, status): user_id = get_id_from_user_info_table(message.from_user.id) - db_session.query(UserStatus).filter(UserStatus.id == user_id). \ - update({'status': status}) + db_session.query(UserStatus).filter(UserStatus.id == user_id).update( + {"status": status} + ) @admin_handlers @@ -167,7 +181,7 @@ async def request_message_to_all(message: types.Message): await bot.send_message( message.from_user.id, "Введите сообщение которое будет отправлено всем пользователям", - reply_markup=admin_cancel_markup() + reply_markup=admin_cancel_markup(), ) await AdminData.message_send.set() @@ -181,9 +195,7 @@ async def get_message_and_send(message: types.Message, state=FSMContext): try: for user in user_list: await send_photo( - teleg_id=user, - photo=message_answer, - caption=message_caption + teleg_id=user, photo=message_answer, caption=message_caption ) await sleep(0.05) except TypeError: @@ -194,9 +206,9 @@ async def get_message_and_send(message: types.Message, state=FSMContext): await bot.send_message( message.from_user.id, "Сообщения отправлены", - reply_markup=admin_menu_markup() + reply_markup=admin_menu_markup(), ) - elif message.content_type == 'text': + elif message.content_type == "text": message_answer = message.text if message_answer == cancel: await admin_menu(message) @@ -216,12 +228,14 @@ async def get_message_and_send(message: types.Message, state=FSMContext): await bot.send_message( message.from_user.id, "Сообщения отправлены", - reply_markup=admin_menu_markup() + reply_markup=admin_menu_markup(), ) logger.info("Сообщения пользователям доставлены.") else: - await message.answer("Данный тип сообщения я обработать не могу", - reply_markup=admin_menu_markup()) + await message.answer( + "Данный тип сообщения я обработать не могу", + reply_markup=admin_menu_markup(), + ) await state.finish() @@ -230,12 +244,15 @@ async def send_photo(teleg_id, **kwargs): try: await bot.send_photo(teleg_id, **kwargs) except BotBlocked: - logger.error(f"Невозможно доставить сообщение пользователю {teleg_id}." - f"Бот заблокирован.") + logger.error( + f"Невозможно доставить сообщение пользователю {teleg_id}." + f"Бот заблокирован." + ) await change_status(teleg_id) except Exception as error: - logger.error(f"Невозможно доставить сообщение пользователю {teleg_id}." - f"{error}") + logger.error( + f"Невозможно доставить сообщение пользователю {teleg_id}." f"{error}" + ) def register_admin_handlers(dp: Dispatcher): @@ -251,9 +268,12 @@ def register_admin_handlers(dp: Dispatcher): dp.register_message_handler(generate_pairs, text=force_pair_generation) dp.register_message_handler(stop_generation, text=stop_pair_generation) dp.register_message_handler(renew_generation, text=renew_pair_generation) - dp.register_message_handler(change_generation_date, text=change_pair_generation_date) - dp.register_message_handler(request_message_to_all, - text=send_message_to_all_button) - dp.register_message_handler(get_message_and_send, - state=AdminData.message_send, - content_types=types.ContentTypes.ANY) + dp.register_message_handler( + change_generation_date, text=change_pair_generation_date + ) + dp.register_message_handler(request_message_to_all, text=send_message_to_all_button) + dp.register_message_handler( + get_message_and_send, + state=AdminData.message_send, + content_types=types.ContentTypes.ANY, + ) diff --git a/handlers/admin/validators/__init__.py b/handlers/admin/validators/__init__.py index ef5f67c..7946f53 100644 --- a/handlers/admin/validators/__init__.py +++ b/handlers/admin/validators/__init__.py @@ -1 +1 @@ -from .validators import * \ No newline at end of file +from .validators import * diff --git a/handlers/admin/validators/validators.py b/handlers/admin/validators/validators.py index 9507ade..e7e8890 100644 --- a/handlers/admin/validators/validators.py +++ b/handlers/admin/validators/validators.py @@ -23,20 +23,13 @@ async def ban_validator(message: types.Message): if not await check_id_in_ban_with_status(message.text, 1): logger.info("Валидация пройдена.") return True - await bot.send_message( - message.from_user.id, - "Пользователь уже забаннен." - ) + await bot.send_message(message.from_user.id, "Пользователь уже забаннен.") return False await bot.send_message( - message.from_user.id, - "Пользователя с таким id не существует." + message.from_user.id, "Пользователя с таким id не существует." ) return False - await bot.send_message( - message.from_user.id, - "Неверный ввод, введите число." - ) + await bot.send_message(message.from_user.id, "Неверный ввод, введите число.") return False @@ -47,20 +40,13 @@ async def unban_validator(message: types.Message): if await check_id_in_ban_with_status(message.text, 1): logger.info("Валидация пройдена.") return True - await bot.send_message( - message.from_user.id, - "Пользователь не забаннен." - ) + await bot.send_message(message.from_user.id, "Пользователь не забаннен.") return False await bot.send_message( - message.from_user.id, - "Пользователя с таким id не существует." + message.from_user.id, "Пользователя с таким id не существует." ) return False - await bot.send_message( - message.from_user.id, - "Неверный ввод, введите число." - ) + await bot.send_message(message.from_user.id, "Неверный ввод, введите число.") return False diff --git a/handlers/decorators.py b/handlers/decorators.py index cdac136..9dfd65b 100644 --- a/handlers/decorators.py +++ b/handlers/decorators.py @@ -13,6 +13,7 @@ async def wrapped(*args): message = list(args)[0] if message.from_user.id in list(map(int, ADMIN_TG_ID.split())): return await func(*args) + return wrapped @@ -25,9 +26,8 @@ async def wrapped(*args): else: await bot.send_message( message.from_user.id, - ("Ты заблокирован. Пожалуйста, " - "обратись к администратору."), - reply_markup=ReplyKeyboardRemove() + ("Ты заблокирован. Пожалуйста, " "обратись к администратору."), + reply_markup=ReplyKeyboardRemove(), ) else: await bot.send_message( @@ -35,6 +35,7 @@ async def wrapped(*args): "Ты не зарегистрирован. Введи 'Регистрация' без кавычек " "или нажми кнопку снизу.", reply_markup=start_registr_markup(), - ) + ) await UserData.start.set() + return wrapped diff --git a/handlers/user/add_username.py b/handlers/user/add_username.py index 9d78cae..c465903 100644 --- a/handlers/user/add_username.py +++ b/handlers/user/add_username.py @@ -1,5 +1,5 @@ from aiogram import types -from sqlalchemy import exists, and_ +from sqlalchemy import and_, exists from controllerBD.db_loader import db_session from controllerBD.models import Username @@ -10,19 +10,17 @@ async def check_username(message: types.Message): user_id = get_id_from_user_info_table(message.from_user.id) username = message.from_user.username - is_exist = db_session.query(exists().where( - Username.id == user_id - )).scalar() + is_exist = db_session.query(exists().where(Username.id == user_id)).scalar() if not is_exist: db_session.add(Username(id=user_id, username=username)) db_session.commit() logger.info(f"В базу добавлен Username пользователя {user_id}") - elif not db_session.query(exists().where(and_( - Username.id == user_id, - Username.username == username)) + elif not db_session.query( + exists().where(and_(Username.id == user_id, Username.username == username)) ).scalar(): - db_session.query(Username).filter(Username.id == user_id). \ - update({'username': username}) + db_session.query(Username).filter(Username.id == user_id).update( + {"username": username} + ) db_session.commit() logger.info(f"Обновлен Username пользователя {user_id}") else: diff --git a/handlers/user/ban_check.py b/handlers/user/ban_check.py index 76e6cb0..c5e3701 100644 --- a/handlers/user/ban_check.py +++ b/handlers/user/ban_check.py @@ -1,5 +1,5 @@ from aiogram import types -from sqlalchemy import exists, and_ +from sqlalchemy import and_, exists from controllerBD.db_loader import db_session from controllerBD.models import BanList @@ -16,9 +16,11 @@ async def check_user_in_ban(message: types.Message): async def check_id_in_ban_with_status(user_id, status): """Проверяем пользователя на наличие в бане с определенным статусом.""" - is_exist = db_session.query(exists().where( - and_(BanList.banned_user_id == user_id, BanList.ban_status == status) - )).scalar() + is_exist = db_session.query( + exists().where( + and_(BanList.banned_user_id == user_id, BanList.ban_status == status) + ) + ).scalar() if not is_exist: return False return True diff --git a/handlers/user/check_message.py b/handlers/user/check_message.py index 3e79477..c346a34 100644 --- a/handlers/user/check_message.py +++ b/handlers/user/check_message.py @@ -23,9 +23,12 @@ async def check_message(): def prepare_user_list(): """Подготовка списка id пользователей со статусом готов к встрече.""" logger.info("""Подготавливаем список пользователей из базы""") - data = db_session.query(Users.teleg_id).join(UserStatus).filter( - UserStatus.status == 1 - ).all() + data = ( + db_session.query(Users.teleg_id) + .join(UserStatus) + .filter(UserStatus.status == 1) + .all() + ) return [element[0] for element in data] @@ -34,16 +37,18 @@ async def send_message(teleg_id, **kwargs): try: await bot.send_message(teleg_id, **kwargs) except BotBlocked: - logger.error(f"Невозможно доставить сообщение пользователю {teleg_id}." - f"Бот заблокирован.") + logger.error( + f"Невозможно доставить сообщение пользователю {teleg_id}." + f"Бот заблокирован." + ) await change_status(teleg_id) except Exception as error: - logger.error(f"Невозможно доставить сообщение пользователю {teleg_id}." - f"{error}") + logger.error( + f"Невозможно доставить сообщение пользователю {teleg_id}." f"{error}" + ) async def change_status(teleg_id): """Смена статуса участия.""" user_id = get_id_from_user_info_table(teleg_id) - db_session.query(UserStatus).filter(UserStatus.id == user_id). \ - update({'status': 0}) + db_session.query(UserStatus).filter(UserStatus.id == user_id).update({"status": 0}) diff --git a/handlers/user/first_check.py b/handlers/user/first_check.py index cd06f1b..f6259b5 100644 --- a/handlers/user/first_check.py +++ b/handlers/user/first_check.py @@ -2,12 +2,12 @@ from aiogram.types import ReplyKeyboardRemove from data import ADMIN_TG_ID -from handlers.user.get_info_from_table import check_user_in_base from handlers.user.ban_check import check_user_in_ban +from handlers.user.get_info_from_table import check_user_in_base from keyboards.admin import admin_main_markup -from keyboards.user import start_registr_markup, menu_markup +from keyboards.user import menu_markup, start_registr_markup from loader import bot -from states import UserData, BannedState +from states import BannedState, UserData async def check_and_add_registration_button(message: types.Message): @@ -15,17 +15,18 @@ async def check_and_add_registration_button(message: types.Message): if not await check_user_in_base(message): await bot.send_message( message.from_user.id, - text=("Добро пожаловать в бот!\n\n" - "Для подбора пары нужно пройти небольшую регистрацию: " - "представиться и ответить на пару вопросов о себе, " - "чтобы собеседнику было проще начать с тобой разговор. Если" - " отвечать не хочется, то часть шагов можно пропустить.\n\n" - "Нажми кнопку \"Регистрация\" ниже.\n\n" - "Для общения, помощи и рассказов о том, как прошла " - "встреча присоединяйся к нашему IT сообществу в " - "телеграм https://t.me/ViribusUnitisGroup" - ), - reply_markup=start_registr_markup() + text=( + "Добро пожаловать в бот!\n\n" + "Для подбора пары нужно пройти небольшую регистрацию: " + "представиться и ответить на пару вопросов о себе, " + "чтобы собеседнику было проще начать с тобой разговор. Если" + " отвечать не хочется, то часть шагов можно пропустить.\n\n" + 'Нажми кнопку "Регистрация" ниже.\n\n' + "Для общения, помощи и рассказов о том, как прошла " + "встреча присоединяйся к нашему IT сообществу в " + "телеграм https://t.me/ViribusUnitisGroup" + ), + reply_markup=start_registr_markup(), ) await UserData.start.set() elif message.from_user.id in list(map(int, ADMIN_TG_ID.split())): @@ -45,8 +46,8 @@ async def check_and_add_registration_button(message: types.Message): await bot.send_message( message.from_user.id, text="К сожалению ты нарушил наши правила и попал в бан. " - "Для решения данного вопроса обратись к " - "администратору @Loravel", - reply_markup=ReplyKeyboardRemove() + "Для решения данного вопроса обратись к " + "администратору @Loravel", + reply_markup=ReplyKeyboardRemove(), ) await BannedState.start.set() diff --git a/handlers/user/get_info_from_table.py b/handlers/user/get_info_from_table.py index 1dd60c1..a40da15 100644 --- a/handlers/user/get_info_from_table.py +++ b/handlers/user/get_info_from_table.py @@ -1,15 +1,13 @@ from sqlalchemy import exists from controllerBD.db_loader import db_session -from controllerBD.models import Gender, Users, Holidays, UserStatus +from controllerBD.models import Gender, Holidays, Users, UserStatus from loader import logger def get_id_from_user_info_table(teleg_id): """Получение id пользователя по телеграм id.""" - id_obj = db_session.query(Users.id).filter( - Users.teleg_id == teleg_id - ).first() + id_obj = db_session.query(Users.id).filter(Users.teleg_id == teleg_id).first() return id_obj[0] @@ -21,9 +19,9 @@ def get_teleg_id_from_user_info_table(id): async def check_user_in_base(message): """Проверяем пользователя на наличие в БД.""" - is_exist = db_session.query(exists().where( - Users.teleg_id == message.from_user.id - )).scalar() + is_exist = db_session.query( + exists().where(Users.teleg_id == message.from_user.id) + ).scalar() if not is_exist: return False return True @@ -37,17 +35,13 @@ def get_user_data_from_db(teleg_id): def get_user_status_from_db(user_id): """Получение статуса участия пользователя из БД""" - user_status = db_session.query(UserStatus).filter( - UserStatus.id == user_id - ).first() + user_status = db_session.query(UserStatus).filter(UserStatus.id == user_id).first() return user_status.__dict__ def get_holidays_status_from_db(user_id): """Получение статуса каникул пользователя из БД""" - holidays = db_session.query(Holidays).filter( - Holidays.id == user_id - ).first() + holidays = db_session.query(Holidays).filter(Holidays.id == user_id).first() return holidays.__dict__ @@ -59,14 +53,19 @@ def get_user_info_by_id(user_id): def get_full_user_info_by_id(user_id): try: - result = db_session.query( - Users.id, - Users.teleg_id, - Users.name, - Users.birthday, - Users.about, - Gender.gender_name - ).join(Gender).filter(Users.id == user_id).first() + result = ( + db_session.query( + Users.id, + Users.teleg_id, + Users.name, + Users.birthday, + Users.about, + Gender.gender_name, + ) + .join(Gender) + .filter(Users.id == user_id) + .first() + ) except Exception as error: logger.error(f"{error}") result = None diff --git a/handlers/user/handlers.py b/handlers/user/handlers.py index 9118c3e..6e8adc5 100644 --- a/handlers/user/handlers.py +++ b/handlers/user/handlers.py @@ -1,31 +1,29 @@ -from aiogram import types, exceptions +from aiogram import exceptions, types from aiogram.dispatcher import Dispatcher from controllerBD.db_loader import db_session from controllerBD.models import MetInfo from handlers.decorators import user_handlers from handlers.user.add_username import check_username -from handlers.user.reviews import get_met_id_with_user_last_week from handlers.user.get_info_from_table import ( + get_full_user_info_by_id, + get_holidays_status_from_db, + get_id_from_user_info_table, get_user_data_from_db, get_user_status_from_db, - get_holidays_status_from_db, - get_id_from_user_info_table, get_full_user_info_by_id ) +from handlers.user.new_member import get_gender_from_db, start_registration +from handlers.user.reviews import get_met_id_with_user_last_week from handlers.user.work_with_date import date_from_db_to_message from keyboards.user import * from loader import bot, logger - -from handlers.user.new_member import get_gender_from_db, start_registration from sendler import make_message # @dp.errors_handler(exception=exceptions.RetryAfter) -async def exception_handler(update: types.Update, - exception: exceptions.RetryAfter): - await update.message.answer('Превышен лимит на данный запрос. ' - 'Подожди 5 минут') - logger.info(f'Пользователь {update.message.from_user.id} флудит') +async def exception_handler(update: types.Update, exception: exceptions.RetryAfter): + await update.message.answer("Превышен лимит на данный запрос. " "Подожди 5 минут") + logger.info(f"Пользователь {update.message.from_user.id} флудит") return True @@ -34,9 +32,7 @@ async def exception_handler(update: types.Update, async def main_menu(message: types.Message): """Вывод меню""" await bot.send_message( - message.from_user.id, - text="Меню:", - reply_markup=menu_markup(message) + message.from_user.id, text="Меню:", reply_markup=menu_markup(message) ) @@ -45,21 +41,22 @@ async def main_menu(message: types.Message): async def send_profile(message: types.Message): """Вывод данных о пользователе""" await check_username(message) - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"запросил информацию о себе") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " f"запросил информацию о себе" + ) data = dict(get_user_data_from_db(message.from_user.id)) - gender_id = data['gender'] + gender_id = data["gender"] gender_status = get_gender_from_db(gender_id) - data['gender'] = gender_status - if data['birthday'] != 'Не указано': - data['birthday'] = date_from_db_to_message(data['birthday']) + data["gender"] = gender_status + if data["birthday"] != "Не указано": + data["birthday"] = date_from_db_to_message(data["birthday"]) await bot.send_message( message.from_user.id, f"Имя: {data['name']}\n" f"Дата рождения: {data['birthday']}\n" f"О себе: {data['about']}\n" f"Пол: {data['gender']}", - reply_markup=edit_profile_markup() + reply_markup=edit_profile_markup(), ) @@ -67,16 +64,19 @@ async def send_profile(message: types.Message): @user_handlers async def edit_profile(message: types.Message): """Перенаправление на повторную регистрацию""" - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"отправлен на повторную регистрацию") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"отправлен на повторную регистрацию" + ) await start_registration(message) # @dp.message_handler(text=about_bot_message) async def about_bot(message: types.Message): """Вывод информации о боте""" - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"запросил информацию о боте") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " f"запросил информацию о боте" + ) await bot.send_message( message.from_user.id, """*Добро пожаловать в бот для нетворкинга\.*\n @@ -150,29 +150,34 @@ async def about_bot(message: types.Message): async def status_message(message: types.Message): """Вывод статуса участия в распределении""" await check_username(message) - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"запросил информацию о статусе участия") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"запросил информацию о статусе участия" + ) user_row = get_user_data_from_db(message.from_user.id) - status_row = get_user_status_from_db(user_row['id']) - if status_row['status'] == 1: + status_row = get_user_status_from_db(user_row["id"]) + if status_row["status"] == 1: status = "Вы участвуете в распределении пар на следующей неделе" else: - holidays_row = get_holidays_status_from_db(user_row['id']) - till_value = holidays_row['till_date'] - if till_value == 'null' or till_value == 'Неопределенный срок': - holidays_till = 'неопределенной даты' + holidays_row = get_holidays_status_from_db(user_row["id"]) + till_value = holidays_row["till_date"] + if till_value == "null" or till_value == "Неопределенный срок": + holidays_till = "неопределенной даты" else: holidays_till = date_from_db_to_message(till_value) - status = (f"Ты на каникулах до {holidays_till}. " - f"В это время пара для встречи тебе предложена не будет. " - f"После указанной даты статус 'Активен' " - f"будет восстановлен автоматически. Если дата не " - f"определена, то отключить каникулы необходимо " - f"вручную кнопкой 'Отключить' в меню 'Каникулы'" - ) + status = ( + f"Ты на каникулах до {holidays_till}. " + f"В это время пара для встречи тебе предложена не будет. " + f"После указанной даты статус 'Активен' " + f"будет восстановлен автоматически. Если дата не " + f"определена, то отключить каникулы необходимо " + f"вручную кнопкой 'Отключить' в меню 'Каникулы'" + ) await bot.send_message(message.from_user.id, text=status) - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"получил информацию о статусе участия") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"получил информацию о статусе участия" + ) # @dp.message_handler(text=my_pare_button) @@ -183,34 +188,37 @@ async def my_pare_check(message: types.Message): met_id = get_met_id_with_user_last_week(user_id) if met_id is None: await bot.send_message( - message.from_user.id, - "Ты не участвовал в последнем распределении." + message.from_user.id, "Ты не участвовал в последнем распределении." ) else: - users = db_session.query(MetInfo). \ - filter(MetInfo.id == met_id[0]).first().__dict__ - if users['first_user_id'] == user_id: - user_info = get_full_user_info_by_id(users['second_user_id']) + users = ( + db_session.query(MetInfo).filter(MetInfo.id == met_id[0]).first().__dict__ + ) + if users["first_user_id"] == user_id: + user_info = get_full_user_info_by_id(users["second_user_id"]) else: - user_info = get_full_user_info_by_id(users['first_user_id']) + user_info = get_full_user_info_by_id(users["first_user_id"]) message_text = make_message(user_info) try: await bot.send_message( message.from_user.id, message_text, parse_mode="HTML", - reply_markup=help_texts_markup() + reply_markup=help_texts_markup(), ) except Exception as error: - logger.error(f'Сообщение для пользователя {user_id} ' - f'не отправлено. Ошибка {error}') - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"получил информацию о своей паре") + logger.error( + f"Сообщение для пользователя {user_id} " + f"не отправлено. Ошибка {error}" + ) + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"получил информацию о своей паре" + ) def register_user_handlers(dp: Dispatcher): - dp.register_errors_handler(exception_handler, - exception=exceptions.RetryAfter) + dp.register_errors_handler(exception_handler, exception=exceptions.RetryAfter) dp.register_message_handler(main_menu, text=[menu_message, back_to_menu]) dp.register_message_handler(send_profile, text=my_profile_message) dp.register_message_handler(edit_profile, text=edit_profile_message) diff --git a/handlers/user/help_texts.py b/handlers/user/help_texts.py index 0809552..34417e9 100644 --- a/handlers/user/help_texts.py +++ b/handlers/user/help_texts.py @@ -8,16 +8,24 @@ from loader import bot help_texts_messages = [ - ("Привет! Я твой собеседник на этой неделе. " - "Как насчет сходить вместе выпить кофе на выходных?"), + ( + "Привет! Я твой собеседник на этой неделе. " + "Как насчет сходить вместе выпить кофе на выходных?" + ), "Здравствуйте! Когда у Вас есть время на этой неделе?", - ("Привет! Бот пишет, что ты мой собеседник на эту неделю. " - "Можем встретиться или созвониться. Тебе как удобно?"), - ("Добрый день! Ты мне выпал в боте. " - "Скажи, когда тебе удобно будет созвониться?"), - ("Приветствую! Буду рад/а встретиться, но у меня немного " - "загружена вторая половина недели. " - "Может, у нас получится во вторник или среду?") + ( + "Привет! Бот пишет, что ты мой собеседник на эту неделю. " + "Можем встретиться или созвониться. Тебе как удобно?" + ), + ( + "Добрый день! Ты мне выпал в боте. " + "Скажи, когда тебе удобно будет созвониться?" + ), + ( + "Приветствую! Буду рад/а встретиться, но у меня немного " + "загружена вторая половина недели. " + "Может, у нас получится во вторник или среду?" + ), ] @@ -26,14 +34,13 @@ async def send_help_texts(message: types.Message): """Отправка сообщений примеров.""" await bot.send_message( - message.from_user.id, - "Ты можешь скопировать текст нажав на него." + message.from_user.id, "Ты можешь скопировать текст нажав на него." ) for text in help_texts_messages: await sleep(0.05) - await bot.send_message(message.from_user.id, - f'{text}', - parse_mode='HTML') + await bot.send_message( + message.from_user.id, f"{text}", parse_mode="HTML" + ) def register_help_texts_handlers(dp: Dispatcher): diff --git a/handlers/user/holidays.py b/handlers/user/holidays.py index b7c9c0b..0320e0c 100644 --- a/handlers/user/holidays.py +++ b/handlers/user/holidays.py @@ -8,12 +8,19 @@ from controllerBD.models import Holidays, UserStatus from handlers.decorators import user_handlers from handlers.user.check_message import send_message -from handlers.user.get_info_from_table import get_id_from_user_info_table, \ - get_teleg_id_from_user_info_table +from handlers.user.get_info_from_table import ( + get_id_from_user_info_table, + get_teleg_id_from_user_info_table, +) from handlers.user.work_with_date import date_from_db_to_message -from keyboards.user import holidays_length, set_holiday_message, \ - one_week_holidays_message, two_week_holidays_message, \ - three_week_holidays_message, turn_off_holidays +from keyboards.user import ( + holidays_length, + one_week_holidays_message, + set_holiday_message, + three_week_holidays_message, + turn_off_holidays, + two_week_holidays_message, +) from loader import bot, logger @@ -21,22 +28,24 @@ @user_handlers async def check_and_choice_holidays(message: types.Message): """Проверка и выбор срока каникул""" - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"перешел к установке срока каникул") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"перешел к установке срока каникул" + ) await check_holidays_until(message.from_user.id) await bot.send_message( message.from_user.id, - text='Выбери на какой срок ты хочешь установить каникулы', - reply_markup=holidays_length() + text="Выбери на какой срок ты хочешь установить каникулы", + reply_markup=holidays_length(), ) def message_for_holidays(date_to_return): return ( - f'Ты установил каникулы до ' + f"Ты установил каникулы до " f'{date_to_return.strftime("%d.%m.%Y")} ' - f'и начнёшь участвовать в ' - f'распределении с ' + f"и начнёшь участвовать в " + f"распределении с " f'{(date_to_return + timedelta(days=1)).strftime("%d.%m.%Y")}.' ) @@ -48,8 +57,7 @@ async def get_one_week_holidays(message: types.Message): date_to_return = date.today() + timedelta(days=7) await get_holidays(message, date_to_return) await bot.send_message( - message.from_user.id, - text=message_for_holidays(date_to_return) + message.from_user.id, text=message_for_holidays(date_to_return) ) @@ -60,8 +68,7 @@ async def get_two_week_holidays(message: types.Message): date_to_return = date.today() + timedelta(days=14) await get_holidays(message, date_to_return) await bot.send_message( - message.from_user.id, - text=message_for_holidays(date_to_return) + message.from_user.id, text=message_for_holidays(date_to_return) ) @@ -72,8 +79,7 @@ async def get_three_week_holidays(message: types.Message): date_to_return = date.today() + timedelta(days=21) await get_holidays(message, date_to_return) await bot.send_message( - message.from_user.id, - text=message_for_holidays(date_to_return) + message.from_user.id, text=message_for_holidays(date_to_return) ) @@ -82,79 +88,75 @@ async def get_three_week_holidays(message: types.Message): async def cancel_holidays(message: types.Message): """Отключение режима каникул""" user_id = get_id_from_user_info_table(message.from_user.id) - db_session.query(Holidays).filter(Holidays.id == user_id). \ - update({'status': 0, 'till_date': 'null'}) - db_session.query(UserStatus).filter(UserStatus.id == user_id). \ - update({'status': 1}) + db_session.query(Holidays).filter(Holidays.id == user_id).update( + {"status": 0, "till_date": "null"} + ) + db_session.query(UserStatus).filter(UserStatus.id == user_id).update({"status": 1}) db_session.commit() - await bot.send_message( - message.from_user.id, - text='Режим каникул был отключен' + await bot.send_message(message.from_user.id, text="Режим каникул был отключен") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " f"отключил режим каникул" ) - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"отключил режим каникул") async def get_holidays(message: types.Message, date_to_return): """Запись данных о каникулах в БД""" user_id = get_id_from_user_info_table(message.from_user.id) - db_session.query(Holidays).filter(Holidays.id == user_id). \ - update({'status': 1, 'till_date': str(date_to_return)}) - db_session.query(UserStatus).filter(UserStatus.id == user_id). \ - update({'status': 0}) + db_session.query(Holidays).filter(Holidays.id == user_id).update( + {"status": 1, "till_date": str(date_to_return)} + ) + db_session.query(UserStatus).filter(UserStatus.id == user_id).update({"status": 0}) db_session.commit() - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"установил режим каникул до {date_to_return}") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " + f"установил режим каникул до {date_to_return}" + ) async def check_holidays_until(teleg_id): """Проверка даты окончания каникул пользователя в БД""" user_id = get_id_from_user_info_table(teleg_id) - row = db_session.query(Holidays).filter( - Holidays.id == user_id - ).first().__dict__ - if row['status'] == 0: + row = db_session.query(Holidays).filter(Holidays.id == user_id).first().__dict__ + if row["status"] == 0: pass else: await bot.send_message( teleg_id, - text=f'Каникулы установлены до ' - f'{date_from_db_to_message(row["till_date"])}.\n' - f'Ты начнешь участвовать в распределении пар ' - f'со следующего дня.' + text=f"Каникулы установлены до " + f'{date_from_db_to_message(row["till_date"])}.\n' + f"Ты начнешь участвовать в распределении пар " + f"со следующего дня.", ) async def sheduled_check_holidays(): """Отключение режима каникул при окончании срока. Проверка по расписанию""" - logger.info('Начало автопроверки статуса каникул') - data = db_session.query(Holidays.id).filter(and_( - Holidays.status == 1, - Holidays.till_date == str(date.today()) - )).all() + logger.info("Начало автопроверки статуса каникул") + data = ( + db_session.query(Holidays.id) + .filter(and_(Holidays.status == 1, Holidays.till_date == str(date.today()))) + .all() + ) if data: for row in data: user_id = get_teleg_id_from_user_info_table(row[0]) - db_session.query(Holidays).filter(Holidays.id == row[0]). \ - update({'status': 0, 'till_date': 'null'}) - db_session.query(UserStatus).filter(UserStatus.id == row[0]). \ - update({'status': 1}) - db_session.commit() - await send_message( - teleg_id=user_id, - text='Режим каникул был отключен' + db_session.query(Holidays).filter(Holidays.id == row[0]).update( + {"status": 0, "till_date": "null"} ) - logger.info(f'Каникулы юзера {user_id} отключены автопроверкой') - logger.info('Конец автопроверки статуса каникул') + db_session.query(UserStatus).filter(UserStatus.id == row[0]).update( + {"status": 1} + ) + db_session.commit() + await send_message(teleg_id=user_id, text="Режим каникул был отключен") + logger.info(f"Каникулы юзера {user_id} отключены автопроверкой") + logger.info("Конец автопроверки статуса каникул") def register_holidays_handlers(dp: Dispatcher): - dp.register_message_handler(check_and_choice_holidays, - text=set_holiday_message) - dp.register_message_handler(get_one_week_holidays, - text=one_week_holidays_message) - dp.register_message_handler(get_two_week_holidays, - text=two_week_holidays_message) - dp.register_message_handler(get_three_week_holidays, - text=three_week_holidays_message) + dp.register_message_handler(check_and_choice_holidays, text=set_holiday_message) + dp.register_message_handler(get_one_week_holidays, text=one_week_holidays_message) + dp.register_message_handler(get_two_week_holidays, text=two_week_holidays_message) + dp.register_message_handler( + get_three_week_holidays, text=three_week_holidays_message + ) dp.register_message_handler(cancel_holidays, text=turn_off_holidays) diff --git a/handlers/user/new_member.py b/handlers/user/new_member.py index 4321c00..a736246 100644 --- a/handlers/user/new_member.py +++ b/handlers/user/new_member.py @@ -1,30 +1,39 @@ -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext from aiogram.types import ReplyKeyboardRemove from controllerBD.db_loader import db_session -from controllerBD.models import Gender, Users, UserStatus, UserMets, Holidays +from controllerBD.models import Gender, Holidays, UserMets, Users, UserStatus from handlers.admin.ban_handlers import back_to_main_markup from handlers.user.add_username import check_username from handlers.user.first_check import check_and_add_registration_button from handlers.user.get_info_from_table import ( check_user_in_base, - get_id_from_user_info_table + get_id_from_user_info_table, +) +from handlers.user.validators import ( + validate_about, + validate_birthday, + validate_check_info, + validate_gender, + validate_name, ) from handlers.user.work_with_date import date_from_message_to_db -from keyboards.user import (back_message, confirm_markup, - man_message, register_can_skip_reply_markup, - register_man_or_woman_markup, - skip_message, woman_message, - return_to_begin_button, registr_message, - return_to_begin_markup) +from keyboards.user import ( + back_message, + confirm_markup, + man_message, + register_can_skip_reply_markup, + register_man_or_woman_markup, + registr_message, + return_to_begin_button, + return_to_begin_markup, + skip_message, + woman_message, +) from loader import bot, logger from states.states import UserData -from handlers.user.validators import (validate_about, validate_birthday, - validate_check_info, validate_gender, - validate_name) - # @dp.message_handler(text=return_to_begin_button, state="*") async def return_to_begin(message: types.Message, state: FSMContext): @@ -35,9 +44,7 @@ async def return_to_begin(message: types.Message, state: FSMContext): def get_gender_from_db(status): """Получаем пол пользователя по id пола""" - info = db_session.query(Gender.gender_name).filter( - Gender.id == status - ).first() + info = db_session.query(Gender.gender_name).filter(Gender.id == status).first() return info[0] @@ -52,10 +59,10 @@ async def confirmation_and_save(message: types.Message, state: FSMContext): else: await bot.send_message( message.from_user.id, - 'Ура! Теперь ты участвуешь в распределении на следующей неделе. ' - 'Бот напомнит: перед распределением придет сообщение, что ' - 'скоро тебе подберут пару.', - reply_markup=ReplyKeyboardRemove() + "Ура! Теперь ты участвуешь в распределении на следующей неделе. " + "Бот напомнит: перед распределением придет сообщение, что " + "скоро тебе подберут пару.", + reply_markup=ReplyKeyboardRemove(), ) await bot.send_message( message.from_user.id, @@ -64,17 +71,21 @@ async def confirmation_and_save(message: types.Message, state: FSMContext): ) data = await state.get_data() if await check_user_in_base(message): - update_profile_db(message.from_user.id, - data.get('name'), - data.get('birthday'), - data.get('about'), - data.get('gender')) + update_profile_db( + message.from_user.id, + data.get("name"), + data.get("birthday"), + data.get("about"), + data.get("gender"), + ) else: - add_to_db(message.from_user.id, - data.get('name'), - data.get('birthday'), - data.get('about'), - data.get('gender')) + add_to_db( + message.from_user.id, + data.get("name"), + data.get("birthday"), + data.get("about"), + data.get("gender"), + ) await add_new_user_in_status_table(message) await state.reset_state() @@ -91,11 +102,13 @@ def add_to_db(teleg_id, name, birthday, about, gender): pass else: birthday = date_from_message_to_db(birthday) - db_session.add(Users(teleg_id=teleg_id, name=name, - birthday=birthday, about=about, gender=gender)) + db_session.add( + Users( + teleg_id=teleg_id, name=name, birthday=birthday, about=about, gender=gender + ) + ) db_session.commit() - logger.info(f"Пользователь с TG_ID {teleg_id} " - f"добавлен в БД как новый участник") + logger.info(f"Пользователь с TG_ID {teleg_id} " f"добавлен в БД как новый участник") def update_profile_db(teleg_id, name, birthday, about, gender): @@ -104,12 +117,11 @@ def update_profile_db(teleg_id, name, birthday, about, gender): pass else: birthday = date_from_message_to_db(birthday) - db_session.query(Users).filter(Users.teleg_id == teleg_id). \ - update({'name': name, 'birthday': birthday, - 'about': about, 'gender': gender}) + db_session.query(Users).filter(Users.teleg_id == teleg_id).update( + {"name": name, "birthday": birthday, "about": about, "gender": gender} + ) db_session.commit() - logger.info(f"Пользователь с TG_ID {teleg_id} " - f"обновил информацию о себе") + logger.info(f"Пользователь с TG_ID {teleg_id} " f"обновил информацию о себе") async def add_new_user_in_status_table(message): @@ -127,12 +139,13 @@ async def add_new_user_in_status_table(message): # @dp.message_handler(text=registr_message, state=UserData.start) async def start_registration(message: types.Message): """Первое состояние. Старт регистрации.""" - logger.info(f"Пользователь с TG_ID {message.from_user.id} " - f"начал процесс регистрации") + logger.info( + f"Пользователь с TG_ID {message.from_user.id} " f"начал процесс регистрации" + ) await bot.send_message( message.from_user.id, - 'Как тебя представить собеседнику? (Введи только имя)', - reply_markup=return_to_begin_markup() + "Как тебя представить собеседнику? (Введи только имя)", + reply_markup=return_to_begin_markup(), ) await UserData.name.set() @@ -147,7 +160,7 @@ async def check_data(tg_id, name, birthday, about, gender): f"О себе: {about}\n" f"Пол: {gender}\n\n" f"Все верно?", - reply_markup=confirm_markup() + reply_markup=confirm_markup(), ) await UserData.check_info.set() @@ -155,10 +168,10 @@ async def check_data(tg_id, name, birthday, about, gender): async def end_registration(state, message): """Формирование данных пользователя для проверки""" data = await state.get_data() - name = data.get('name') - birthday = data.get('birthday') - about = data.get('about') - gender = get_gender_from_db(data.get('gender')) + name = data.get("name") + birthday = data.get("birthday") + about = data.get("about") + gender = get_gender_from_db(data.get("gender")) tg_id = message.from_user.id await check_data(tg_id, name, birthday, about, gender) @@ -169,9 +182,9 @@ async def answer_name(message: types.Message, state: FSMContext): name = message.text if not validate_name(name): await message.answer( - 'Что-то не так с введенным именем. ' - 'Имя должно состоять из букв русского или латинского алфавитов ' - 'и быть менее 100 символов.' + "Что-то не так с введенным именем. " + "Имя должно состоять из букв русского или латинского алфавитов " + "и быть менее 100 символов." ) return await state.update_data(name=name) @@ -182,8 +195,8 @@ async def question_birthday(message: types.Message): """Запрос даты рождения""" await bot.send_message( message.from_user.id, - 'Введи, пожалуйста, дату рождения в формате ДД.ММ.ГГГГ', - reply_markup=register_can_skip_reply_markup() + "Введи, пожалуйста, дату рождения в формате ДД.ММ.ГГГГ", + reply_markup=register_can_skip_reply_markup(), ) await UserData.birthday.set() @@ -196,7 +209,7 @@ async def answer_birthday(message: types.Message, state: FSMContext): await start_registration(message) else: if birthday == skip_message: - birthday = 'Не указано' + birthday = "Не указано" else: if not await validate_birthday(message): return @@ -209,7 +222,7 @@ async def question_about(message: types.Message): await bot.send_message( message.from_user.id, "Расскажи немного о себе?", - reply_markup=register_can_skip_reply_markup() + reply_markup=register_can_skip_reply_markup(), ) await UserData.about.set() @@ -223,7 +236,7 @@ async def answer_about(message: types.Message, state: FSMContext): await question_birthday(message) else: if about == skip_message: - about = 'Не указано' + about = "Не указано" else: if not await validate_about(message): return @@ -234,9 +247,7 @@ async def answer_about(message: types.Message, state: FSMContext): async def question_gender(message: types.Message): """Запрос пола пользователя""" await bot.send_message( - message.from_user.id, - "Выбери пол", - reply_markup=register_man_or_woman_markup() + message.from_user.id, "Выбери пол", reply_markup=register_man_or_woman_markup() ) await UserData.gender.set() @@ -261,12 +272,11 @@ async def answer_gender(message: types.Message, state: FSMContext): def register_new_member_handler(dp: Dispatcher): - dp.register_message_handler(return_to_begin, text=return_to_begin_button, - state="*") - dp.register_message_handler(confirmation_and_save, - state=UserData.check_info) - dp.register_message_handler(start_registration, text=registr_message, - state=UserData.start) + dp.register_message_handler(return_to_begin, text=return_to_begin_button, state="*") + dp.register_message_handler(confirmation_and_save, state=UserData.check_info) + dp.register_message_handler( + start_registration, text=registr_message, state=UserData.start + ) dp.register_message_handler(answer_name, state=UserData.name) dp.register_message_handler(answer_birthday, state=UserData.birthday) dp.register_message_handler(answer_about, state=UserData.about) diff --git a/handlers/user/review_history.py b/handlers/user/review_history.py index 9a7674e..3645e4c 100644 --- a/handlers/user/review_history.py +++ b/handlers/user/review_history.py @@ -1,8 +1,8 @@ -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.callback_data import CallbackData -from sqlalchemy import or_, desc, and_ +from sqlalchemy import and_, desc, or_ from sqlalchemy.exc import NoResultFound from controllerBD.db_loader import db_session @@ -10,10 +10,12 @@ from controllerBD.services import get_tg_username_from_db_by_base_id from handlers.decorators import user_handlers from handlers.user.add_username import check_username -from handlers.user.get_info_from_table import get_id_from_user_info_table, \ - get_user_info_by_id +from handlers.user.get_info_from_table import ( + get_id_from_user_info_table, + get_user_info_by_id, +) from handlers.user.work_with_date import date_from_db_to_message -from keyboards.user import review_yes_or_no, my_reviews +from keyboards.user import my_reviews, review_yes_or_no from loader import bot, logger from states import ReviewState @@ -22,10 +24,15 @@ def list_of_user_mets_id(user_id): """Получение id всех встреч пользователя.""" - met_ids = db_session.query(MetInfo.id).filter( - or_(MetInfo.first_user_id == user_id, - MetInfo.second_user_id == user_id) - ).order_by(desc(MetInfo.id)).limit(10).all() + met_ids = ( + db_session.query(MetInfo.id) + .filter( + or_(MetInfo.first_user_id == user_id, MetInfo.second_user_id == user_id) + ) + .order_by(desc(MetInfo.id)) + .limit(10) + .all() + ) return [met_id[0] for met_id in met_ids] @@ -38,8 +45,7 @@ async def get_my_reviews(message: types.Message): met_ids = list_of_user_mets_id(user_id) count_of_mets = len(met_ids) if count_of_mets == 0: - await bot.send_message(message.from_user.id, - "У тебя еще не было встреч.") + await bot.send_message(message.from_user.id, "У тебя еще не было встреч.") else: met_id = met_ids[0] review_info = get_sqliterow_review(met_id, user_id) @@ -48,19 +54,24 @@ async def get_my_reviews(message: types.Message): else: edit_button = "Доб. отзыв" message_text = prepare_message(user_id, met_id, review_info) - await bot.send_message(message.from_user.id, message_text, - parse_mode='HTML', - reply_markup=inline_markup(count_of_mets, 0, - edit_button)) - logger.info(f'Пользователь {message.from_user.id} получил информация ' - f'о последней встрече {met_id}') + await bot.send_message( + message.from_user.id, + message_text, + parse_mode="HTML", + reply_markup=inline_markup(count_of_mets, 0, edit_button), + ) + logger.info( + f"Пользователь {message.from_user.id} получил информация " + f"о последней встрече {met_id}" + ) # @dp.callback_query_handler(review_callbackdata.filter()) -async def button_press(call: types.CallbackQuery, callback_data: dict, - state: FSMContext): +async def button_press( + call: types.CallbackQuery, callback_data: dict, state: FSMContext +): """Выводим информацию о встрече в зависимости от позиции.""" - position = int(callback_data.get('position')) + position = int(callback_data.get("position")) user_id = get_id_from_user_info_table(call.from_user.id) met_ids = list_of_user_mets_id(user_id) count_of_mets = len(met_ids) @@ -70,58 +81,64 @@ async def button_press(call: types.CallbackQuery, callback_data: dict, edit_button = "Ред. отзыв" else: edit_button = "Доб. отзыв" - if int(callback_data.get('edit')) == 1: + if int(callback_data.get("edit")) == 1: await call.answer() await bot.edit_message_reply_markup( chat_id=call.from_user.id, message_id=call.message.message_id, - reply_markup=None + reply_markup=None, ) await bot.send_message( call.from_user.id, "Состоялась ли твоя встреча?", - reply_markup=review_yes_or_no() + reply_markup=review_yes_or_no(), + ) + logger.info( + f"Пользователь {call.from_user.id} начал редактировать " + f"отзыв о встрече {met_id}" ) - logger.info(f'Пользователь {call.from_user.id} начал редактировать ' - f'отзыв о встрече {met_id}') await ReviewState.start.set() user_id = get_id_from_user_info_table(call.from_user.id) await state.update_data(user_id=user_id) await state.update_data(met_id=met_id) else: message_text = prepare_message(user_id, met_id, review_info) - await bot.edit_message_text(message_text, call.from_user.id, - call.message.message_id, parse_mode='HTML', - reply_markup=inline_markup(count_of_mets, - position, - edit_button)) - logger.info(f'Пользователь {call.from_user.id} получил информация ' - f'о последней встрече {met_id}') + await bot.edit_message_text( + message_text, + call.from_user.id, + call.message.message_id, + parse_mode="HTML", + reply_markup=inline_markup(count_of_mets, position, edit_button), + ) + logger.info( + f"Пользователь {call.from_user.id} получил информация " + f"о последней встрече {met_id}" + ) def inline_markup(count_of_mets, position, edit_button): """Инлайн-кнопки для карточки встречи.""" markup = InlineKeyboardMarkup(row_width=3) if position < (count_of_mets - 1): - markup.insert(InlineKeyboardButton( - "Пред.", - callback_data=review_callbackdata.new( - position=position+1, - edit=0 - ))) - markup.insert(InlineKeyboardButton( - f"{edit_button}", - callback_data=review_callbackdata.new( - position=position, - edit=1 - ))) + markup.insert( + InlineKeyboardButton( + "Пред.", + callback_data=review_callbackdata.new(position=position + 1, edit=0), + ) + ) + markup.insert( + InlineKeyboardButton( + f"{edit_button}", + callback_data=review_callbackdata.new(position=position, edit=1), + ) + ) if position > 0: - markup.insert(InlineKeyboardButton( - "След.", - callback_data=review_callbackdata.new( - position=position-1, - edit=0 - ))) + markup.insert( + InlineKeyboardButton( + "След.", + callback_data=review_callbackdata.new(position=position - 1, edit=0), + ) + ) return markup @@ -129,30 +146,27 @@ def inline_markup(count_of_mets, position, edit_button): def prepare_message(user_id, met_id, review_info): """Подготавливаем сообщение о встрече с коментарием.""" met_info = get_sqliterow_about_met(met_id) - date = date_from_db_to_message(met_info['date']) - if met_info['first_user_id'] == user_id: - pare_id = met_info['second_user_id'] + date = date_from_db_to_message(met_info["date"]) + if met_info["first_user_id"] == user_id: + pare_id = met_info["second_user_id"] else: - pare_id = met_info['first_user_id'] + pare_id = met_info["first_user_id"] pare_info = get_user_info_by_id(pare_id) pare_tg_username = get_tg_username_from_db_by_base_id(pare_id) if pare_tg_username: - pare_username_for_message = f'(@{pare_tg_username})' + pare_username_for_message = f"(@{pare_tg_username})" else: - pare_username_for_message = '' + pare_username_for_message = "" if review_info: - grade = review_info['grade'] + grade = review_info["grade"] if grade == 0: - grade_text = 'Встреча не состоялась.' + grade_text = "Встреча не состоялась." else: grade_text = f"Оценка - {grade}." - comment = review_info['comment'] - if comment == 'null': - comment = 'Не указано' - review = ( - f" {grade_text}\n" - f" {comment}" - ) + comment = review_info["comment"] + if comment == "null": + comment = "Не указано" + review = f" {grade_text}\n" f" {comment}" else: review = "Комментарий на встречу не был добавлен." @@ -162,26 +176,25 @@ def prepare_message(user_id, met_id, review_info): f"{pare_info['name']} {pare_username_for_message}\n\n" f"Отзыв о встрече:\n" f"{review}" - ) return message def get_sqliterow_about_met(met_id): """Получаем словарь строки MetInfo.""" - met_info = db_session.query(MetInfo).filter( - MetInfo.id == met_id - ).first().__dict__ + met_info = db_session.query(MetInfo).filter(MetInfo.id == met_id).first().__dict__ return met_info def get_sqliterow_review(met_id, user_id): """Получаем словарь строки Review.""" try: - review_info = db_session.query(MetsReview).filter(and_( - MetsReview.met_id == met_id, - MetsReview.who_id == user_id - )).one().__dict__ + review_info = ( + db_session.query(MetsReview) + .filter(and_(MetsReview.met_id == met_id, MetsReview.who_id == user_id)) + .one() + .__dict__ + ) except NoResultFound: review_info = None return review_info @@ -189,5 +202,4 @@ def get_sqliterow_review(met_id, user_id): def register_review_history_handler(dp: Dispatcher): dp.register_message_handler(get_my_reviews, text=my_reviews) - dp.register_callback_query_handler(button_press, - review_callbackdata.filter()) + dp.register_callback_query_handler(button_press, review_callbackdata.filter()) diff --git a/handlers/user/reviews.py b/handlers/user/reviews.py index e18e378..e9c5868 100644 --- a/handlers/user/reviews.py +++ b/handlers/user/reviews.py @@ -1,20 +1,30 @@ import datetime -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext -from sqlalchemy import and_, or_, desc, exists +from sqlalchemy import and_, desc, exists, or_ from controllerBD.db_loader import db_session from controllerBD.models import MetInfo, MetsReview from handlers.decorators import admin_handlers -from handlers.user.get_info_from_table import \ - get_teleg_id_from_user_info_table, \ - get_id_from_user_info_table -from handlers.user.validators import validate_review_yes_or_no, \ - validate_about, validate_review_grade +from handlers.user.get_info_from_table import ( + get_id_from_user_info_table, + get_teleg_id_from_user_info_table, +) +from handlers.user.validators import ( + validate_about, + validate_review_grade, + validate_review_yes_or_no, +) from keyboards.admin import review_messages -from keyboards.user import skip_message, menu_markup, \ - review_yes_or_no, no_button, yes_button, review_skip +from keyboards.user import ( + menu_markup, + no_button, + review_skip, + review_yes_or_no, + skip_message, + yes_button, +) from loader import bot, logger from states import ReviewState @@ -28,8 +38,7 @@ async def start_review(message: types.Message): if len(users_id) > 0: await request_review(users_id) else: - await bot.send_message(message.from_user.id, - "На этой неделе не было встреч.") + await bot.send_message(message.from_user.id, "На этой неделе не было встреч.") async def request_review(users_id): @@ -41,21 +50,23 @@ async def request_review(users_id): await bot.send_message( user_teleg_id, "Состоялась ли твоя встреча?", - reply_markup=review_yes_or_no() + reply_markup=review_yes_or_no(), ) await ReviewState.start.set() - logger.info(f"Сообщение пользователю {user_teleg_id} " - f"для оценки встречи отправлено.") + logger.info( + f"Сообщение пользователю {user_teleg_id} " + f"для оценки встречи отправлено." + ) except Exception as error: - logger.error(f"Сообщение пользователю c {user_teleg_id} " - f"не доставлено. {error}") + logger.error( + f"Сообщение пользователю c {user_teleg_id} " f"не доставлено. {error}" + ) continue logger.info("Все сообщения разосланы") # @dp.message_handler(state=ReviewState.start) -async def review_answer_yes_or_now(message: types.Message, - state: FSMContext): +async def review_answer_yes_or_now(message: types.Message, state: FSMContext): """Вопрос состоялась ли встреча.""" answer = message.text if not await validate_review_yes_or_no(message): @@ -65,11 +76,12 @@ async def review_answer_yes_or_now(message: types.Message, message.from_user.id, "Ты можешь оставить отзыв позже. " "Для этого нажми в главном меню кнопку Мои встречи.", - reply_markup=menu_markup(message) + reply_markup=menu_markup(message), ) await state.reset_state() - logger.info(f'Пользователь {message.from_user.id} отклонил ' - f'предоставление отзыва') + logger.info( + f"Пользователь {message.from_user.id} отклонил " f"предоставление отзыва" + ) elif answer == no_button: await state.update_data(grade=0) await question_comment(message) @@ -82,7 +94,7 @@ async def question_comment(message: types.Message): await bot.send_message( message.from_user.id, "Пожалуйста, введи комментарий к оценке (не более 500 символов).", - reply_markup=review_skip() + reply_markup=review_skip(), ) await ReviewState.comment.set() @@ -97,7 +109,7 @@ async def answer_review_comment(message: types.Message, state: FSMContext): answer = "null" await state.update_data(comment=answer) data = await state.get_data() - if not data.get('user_id') or not data.get('met_id'): + if not data.get("user_id") or not data.get("met_id"): user_id = get_id_from_user_info_table(message.from_user.id) await state.update_data(user_id=user_id) met_id = get_met_id_with_user_last_week(user_id)[0] @@ -112,7 +124,7 @@ async def question_grade(message: types.Message): "Оцени встречу от 1 до 5, где \n" " - 1 - Совсем не понравилось,\n" " - 5 - Все было супер.", - reply_markup=review_skip() + reply_markup=review_skip(), ) await ReviewState.grade.set() @@ -126,11 +138,12 @@ async def answer_review_grade(message: types.Message, state: FSMContext): message.from_user.id, "Ты можешь оставить отзыв позже. " "Для этого нажми в главном меню кнопку Мои встречи.", - reply_markup=menu_markup(message) + reply_markup=menu_markup(message), ) await state.reset_state() - logger.info(f'Пользователь {message.from_user.id} отклонил ' - f'предоставление отзыва') + logger.info( + f"Пользователь {message.from_user.id} отклонил " f"предоставление отзыва" + ) elif not await validate_review_grade(grade): await bot.send_message(message.from_user.id, "Введи оценку от 1 до 5") return @@ -143,11 +156,11 @@ def preparing_list_of_users_id(): """Выгрузка списка ID пользователей из таблицы проведенных встреч за неделю.""" start_period = datetime.date.today() - datetime.timedelta(days=7) - data = db_session.query( - MetInfo.first_user_id, - MetInfo.second_user_id - ).filter( - MetInfo.date.between(str(start_period), str(datetime.date.today))).all() + data = ( + db_session.query(MetInfo.first_user_id, MetInfo.second_user_id) + .filter(MetInfo.date.between(str(start_period), str(datetime.date.today))) + .all() + ) logger.info("Список ID для рассылки на отзывы сформирован") return [element[0] for element in data] + [element[1] for element in data] @@ -155,39 +168,45 @@ def preparing_list_of_users_id(): async def save_or_update_review(message, state): """Сохранение комментария в БД.""" data = await state.get_data() - user_id = data.get('user_id') - met_id = data.get('met_id') - grade = data.get('grade') - comment = data.get('comment') + user_id = data.get("user_id") + met_id = data.get("met_id") + grade = data.get("grade") + comment = data.get("comment") if await check_comment_in_bd(user_id, met_id): update_review(user_id, met_id, grade, comment) else: add_review(user_id, met_id, grade, comment) await state.reset_state() await bot.send_message( - message.from_user.id, - "Спасибо за отзыв.", - reply_markup=menu_markup(message) + message.from_user.id, "Спасибо за отзыв.", reply_markup=menu_markup(message) ) def get_met_id_with_user_last_week(user_id): """Получение id встречи по пользователю за прошедшую неделю.""" start_period = datetime.date.today() - datetime.timedelta(days=7) - met_id = db_session.query(MetInfo.id).filter( - and_(MetInfo.date.between(str(start_period), str(datetime.date.today)), - or_( - MetInfo.first_user_id == user_id, - MetInfo.second_user_id == user_id - ))).order_by(desc(MetInfo.id)).limit(1).first() + met_id = ( + db_session.query(MetInfo.id) + .filter( + and_( + MetInfo.date.between(str(start_period), str(datetime.date.today)), + or_( + MetInfo.first_user_id == user_id, MetInfo.second_user_id == user_id + ), + ) + ) + .order_by(desc(MetInfo.id)) + .limit(1) + .first() + ) return met_id async def check_comment_in_bd(user_id, met_id): """Проверка наличия отзыва на встречу.""" - is_exist = db_session.query(exists().where( - and_(MetsReview.met_id == met_id, MetsReview.who_id == user_id) - )).scalar() + is_exist = db_session.query( + exists().where(and_(MetsReview.met_id == met_id, MetsReview.who_id == user_id)) + ).scalar() if not is_exist: return False return True @@ -197,32 +216,41 @@ def update_review(user_id, met_id, grade, comment): """Обновление комментария о встрече.""" db_session.query(MetsReview).filter( and_(MetsReview.met_id == met_id, MetsReview.who_id == user_id) - ).update({ - 'grade': grade, - 'comment': comment, - 'date_of_comment': str(datetime.date.today()) - }) + ).update( + { + "grade": grade, + "comment": comment, + "date_of_comment": str(datetime.date.today()), + } + ) db_session.commit() - logger.info(f"Пользователь с ID {user_id} " - f"обновил комментарий о встрече {met_id}") + logger.info( + f"Пользователь с ID {user_id} " f"обновил комментарий о встрече {met_id}" + ) def add_review(user_id, met_id, grade, comment): """Добавление комментария о встрече""" - users = db_session.query(MetInfo).filter( - MetInfo.id == met_id - ).first().__dict__ - if users['first_user_id'] == user_id: - about_whom_id = users['second_user_id'] + users = db_session.query(MetInfo).filter(MetInfo.id == met_id).first().__dict__ + if users["first_user_id"] == user_id: + about_whom_id = users["second_user_id"] else: - about_whom_id = users['first_user_id'] - db_session.add(MetsReview(met_id=met_id, who_id=user_id, - about_whom_id=about_whom_id, grade=grade, - comment=comment, - date_of_comment=datetime.date.today())) + about_whom_id = users["first_user_id"] + db_session.add( + MetsReview( + met_id=met_id, + who_id=user_id, + about_whom_id=about_whom_id, + grade=grade, + comment=comment, + date_of_comment=datetime.date.today(), + ) + ) db_session.commit() - logger.info(f"Пользователь с ID {user_id} " - f"добавил комментарий о встрече {met_id}") + logger.info( + f"Пользователь с ID {user_id} " f"добавил комментарий о встрече {met_id}" + ) + # def get_met_id_with_user_last_three(user_id): # """Получение id встречи по пользователю за прошедшую неделю.""" @@ -240,8 +268,6 @@ def add_review(user_id, met_id, grade, comment): def register_review_handlers(dp: Dispatcher): dp.register_message_handler(start_review, text=review_messages) - dp.register_message_handler(review_answer_yes_or_now, - state=ReviewState.start) - dp.register_message_handler(answer_review_comment, - state=ReviewState.comment) + dp.register_message_handler(review_answer_yes_or_now, state=ReviewState.start) + dp.register_message_handler(answer_review_comment, state=ReviewState.comment) dp.register_message_handler(answer_review_grade, state=ReviewState.grade) diff --git a/handlers/user/start_handler.py b/handlers/user/start_handler.py index 855585d..90dc13f 100644 --- a/handlers/user/start_handler.py +++ b/handlers/user/start_handler.py @@ -1,4 +1,4 @@ -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from aiogram.dispatcher import FSMContext from handlers.user.first_check import check_and_add_registration_button @@ -6,14 +6,17 @@ # @dp.message_handler(commands=['start', 'help'], state='*') -async def process_start_command(message: types.Message, - state: FSMContext): +async def process_start_command(message: types.Message, state: FSMContext): """Функция первого обращения к боту.""" await state.reset_state() - logger.info(f"user id-{message.from_user.id} " - f"tg-@{message.from_user.username} start a bot") + logger.info( + f"user id-{message.from_user.id} " + f"tg-@{message.from_user.username} start a bot" + ) await check_and_add_registration_button(message) + def register_start_handler(dp: Dispatcher): - dp.register_message_handler(process_start_command, - commands=['start', 'help'], state='*') + dp.register_message_handler( + process_start_command, commands=["start", "help"], state="*" + ) diff --git a/handlers/user/unknown_message.py b/handlers/user/unknown_message.py index 45d5701..b8dcf4a 100644 --- a/handlers/user/unknown_message.py +++ b/handlers/user/unknown_message.py @@ -1,4 +1,4 @@ -from aiogram import types, Dispatcher +from aiogram import Dispatcher, types from handlers.decorators import user_handlers from keyboards.user import menu_markup @@ -11,7 +11,7 @@ async def unknown_message(message: types.Message): await bot.send_message( message.from_user.id, "Я тебя не понимаю. Пожалуйста, воспользуйся меню.", - reply_markup=menu_markup(message) + reply_markup=menu_markup(message), ) diff --git a/handlers/user/validators/__init__.py b/handlers/user/validators/__init__.py index ef5f67c..7946f53 100644 --- a/handlers/user/validators/__init__.py +++ b/handlers/user/validators/__init__.py @@ -1 +1 @@ -from .validators import * \ No newline at end of file +from .validators import * diff --git a/handlers/user/validators/validators.py b/handlers/user/validators/validators.py index 7bcdcbe..5fc651c 100644 --- a/handlers/user/validators/validators.py +++ b/handlers/user/validators/validators.py @@ -2,17 +2,22 @@ from datetime import datetime from aiogram import types -from keyboards.user import (all_right_message, back_message, man_message, - skip_message, woman_message, yes_button, no_button) + +from keyboards.user import ( + all_right_message, + back_message, + man_message, + no_button, + skip_message, + woman_message, + yes_button, +) from loader import bot def validate_name(message): """Валидация введенных данных в поле Имя""" - return (re.fullmatch( - r"^[a-яА-ЯЁёa-zA-Z\s]{1,100}$", - message - ) and len(message) <= 100) + return re.fullmatch(r"^[a-яА-ЯЁёa-zA-Z\s]{1,100}$", message) and len(message) <= 100 async def validate_birthday(message: types.Message): @@ -20,36 +25,35 @@ async def validate_birthday(message: types.Message): if re.fullmatch( r"^((0[1-9]|[12]\d)\.(0[1-9]|1[012])|" r"30\.(0[13-9]|1[012])|31\.(0[13578]|1[02]))\.(19|20)\d\d$", - message.text + message.text, ): - date_obj = datetime.strptime(message.text, '%d.%m.%Y') + date_obj = datetime.strptime(message.text, "%d.%m.%Y") difference = datetime.now() - date_obj age = int(difference.days) / 365.2 if age < 0: await bot.send_message( message.from_user.id, - 'Дата из будущего?)) Введи правильную дату рождения' + "Дата из будущего?)) Введи правильную дату рождения", ) return False elif age <= 14: await bot.send_message( message.from_user.id, - 'Твой возраст должен быть больше 14 лет. ' - 'Введи правильную дату рождения' + "Твой возраст должен быть больше 14 лет. " + "Введи правильную дату рождения", ) return False elif age > 120: await bot.send_message( message.from_user.id, - 'Указанный возраст болеее 120 лет. ' - 'Введи правильную дату рождения' + "Указанный возраст болеее 120 лет. " "Введи правильную дату рождения", ) return False return True await bot.send_message( message.from_user.id, - 'Что-то не так с введенными данными. ' - 'Дата должна состоять из цифр и точек в формате ДД.ММ.ГГГГ' + "Что-то не так с введенными данными. " + "Дата должна состоять из цифр и точек в формате ДД.ММ.ГГГГ", ) return False @@ -58,14 +62,13 @@ async def validate_about(message): """Валидация введенных данных в поле О себе""" if len(message.text) > 500: await bot.send_message( - message.from_user.id, - 'Текст должен быть меньше 500 символов. Повтори ввод.' + message.from_user.id, "Текст должен быть меньше 500 символов. Повтори ввод." ) return False elif not re.fullmatch(r"^[^<>]+$", message.text): await bot.send_message( message.from_user.id, - 'В тексте запрещено использовать символы <>. Повтори ввод.' + "В тексте запрещено использовать символы <>. Повтори ввод.", ) return False return True @@ -77,8 +80,7 @@ async def validate_gender(message: types.Message): if message.text not in choice: await bot.send_message( message.from_user.id, - 'Пожалуйста, выбери из доступных вариантов или ' - 'нажми "Пропустить"' + "Пожалуйста, выбери из доступных вариантов или " 'нажми "Пропустить"', ) return False return True @@ -90,8 +92,7 @@ async def validate_check_info(message): choice = [all_right_message, back_message] if message.text not in choice: await bot.send_message( - message.from_user.id, - 'Пожалуйста, выбери из доступных вариантов.' + message.from_user.id, "Пожалуйста, выбери из доступных вариантов." ) return False return True @@ -101,8 +102,7 @@ async def validate_review_yes_or_no(message): choice = [yes_button, no_button, skip_message] if message.text not in choice: await bot.send_message( - message.from_user.id, - 'Пожалуйста, выбери из доступных вариантов.' + message.from_user.id, "Пожалуйста, выбери из доступных вариантов." ) return False return True diff --git a/handlers/user/work_with_date.py b/handlers/user/work_with_date.py index 12ba74b..33140b1 100644 --- a/handlers/user/work_with_date.py +++ b/handlers/user/work_with_date.py @@ -2,12 +2,12 @@ def date_from_db_to_message(date): - date_from_db = datetime.datetime.strptime(date, '%Y-%m-%d') - date_text = date_from_db.strftime('%d.%m.%Y') + date_from_db = datetime.datetime.strptime(date, "%Y-%m-%d") + date_text = date_from_db.strftime("%d.%m.%Y") return date_text def date_from_message_to_db(date): - date_from_message = datetime.datetime.strptime(date, '%d.%m.%Y') - date_text = date_from_message.strftime('%Y-%m-%d') + date_from_message = datetime.datetime.strptime(date, "%d.%m.%Y") + date_text = date_from_message.strftime("%Y-%m-%d") return date_text diff --git a/keyboards/admin/admin_markups.py b/keyboards/admin/admin_markups.py index bcd4b75..38f2a06 100644 --- a/keyboards/admin/admin_markups.py +++ b/keyboards/admin/admin_markups.py @@ -1,8 +1,8 @@ from aiogram import types from aiogram.types import ReplyKeyboardMarkup + from data import ADMIN_TG_ID -from keyboards.user.defalt_markups import (back_to_main, menu_markup, - menu_message) +from keyboards.user.defalt_markups import back_to_main, menu_markup, menu_message admin_menu_button = "Меню администратора" inform = "Отчет" @@ -50,7 +50,9 @@ def admin_menu_markup(): markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) markup.row(inform, send_message_to_all_button) markup.row(ban_list, change_status, pair_generation) - markup.row(back_to_main,) + markup.row( + back_to_main, + ) return markup @@ -71,6 +73,7 @@ def admin_change_status_markup(): markup.add(go_back) return markup + def admin_pair_generation_markup(): markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) markup.add(force_pair_generation) @@ -80,6 +83,7 @@ def admin_pair_generation_markup(): markup.add(go_back) return markup + def admin_back_markup(): """Кнопка назад""" markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) diff --git a/keyboards/user/defalt_markups.py b/keyboards/user/defalt_markups.py index cd8d1fb..658857c 100644 --- a/keyboards/user/defalt_markups.py +++ b/keyboards/user/defalt_markups.py @@ -2,13 +2,13 @@ from data import ADMIN_TG_ID -back_message = '👈 Назад' -skip_message = '👉 Пропустить' -all_right_message = '✅ Все верно' -cancel_message = '🚫 Отменить' -menu_message = '🏠 Меню' -confirm_message = '✅ Да' -reject_message = '❌ Нет' +back_message = "👈 Назад" +skip_message = "👉 Пропустить" +all_right_message = "✅ Все верно" +cancel_message = "🚫 Отменить" +menu_message = "🏠 Меню" +confirm_message = "✅ Да" +reject_message = "❌ Нет" edit_profile_message = "👩🏿‍🎨 Изменить Профиль" my_profile_message = "Мой профиль" my_status_message = "Мой статус" @@ -26,7 +26,7 @@ turn_off_holidays = "Отключить" back_to_menu = "Вернуться в меню" my_pare_button = "Моя пара" -back_to_main = 'Главное меню' +back_to_main = "Главное меню" def main_markup(): diff --git a/loader.py b/loader.py index d874754..0041ca9 100644 --- a/loader.py +++ b/loader.py @@ -1,7 +1,7 @@ import logging +import os from datetime import datetime from logging.handlers import RotatingFileHandler -import os import pytz from aiogram import Bot, Dispatcher @@ -12,38 +12,38 @@ from controllerBD.models import create_tables from data import config -timezone = pytz.timezone('Etc/GMT-3') +timezone = pytz.timezone("Etc/GMT-3") def timetz(*args): return datetime.now(timezone).timetuple() -logger = logging.getLogger('main_logger') +logger = logging.getLogger("main_logger") -aiogram_logger = logging.getLogger('aio_logger') +aiogram_logger = logging.getLogger("aio_logger") -schedule_logger = logging.getLogger('schedule') +schedule_logger = logging.getLogger("schedule") logger.setLevel(logging.INFO) aiogram_logger.setLevel(logging.INFO) schedule_logger.setLevel(level=logging.DEBUG) -if not(os.path.isdir("logs")): - os.mkdir('logs') +if not (os.path.isdir("logs")): + os.mkdir("logs") main_handler = RotatingFileHandler( - 'logs/my_logger.log', + "logs/my_logger.log", maxBytes=30000000, backupCount=5, ) aiogram_handler = RotatingFileHandler( - 'logs/aiogram_logger.log', + "logs/aiogram_logger.log", maxBytes=30000000, backupCount=2, ) schedule_handler = RotatingFileHandler( - 'logs/schedule_logger.log', + "logs/schedule_logger.log", maxBytes=30000000, backupCount=2, ) @@ -54,9 +54,10 @@ def timetz(*args): schedule_logger.addHandler(schedule_handler) formatter = logging.Formatter( - fmt=('%(asctime)s.%(msecs)d %(levelname)s ' - '%(filename)s %(funcName)s %(message)s'), - datefmt='%d-%m-%Y %H:%M:%S', + fmt=( + "%(asctime)s.%(msecs)d %(levelname)s " "%(filename)s %(funcName)s %(message)s" + ), + datefmt="%d-%m-%Y %H:%M:%S", ) formatter.converter = timetz diff --git a/main.py b/main.py index 5ead731..b29fcb6 100644 --- a/main.py +++ b/main.py @@ -8,8 +8,7 @@ from handlers.admin.handlers import register_admin_handlers from handlers.user.handlers import register_user_handlers from handlers.user.help_texts import register_help_texts_handlers -from handlers.user.holidays import (register_holidays_handlers, - sheduled_check_holidays) +from handlers.user.holidays import register_holidays_handlers, sheduled_check_holidays from handlers.user.new_member import register_new_member_handler from handlers.user.review_history import register_review_history_handler from handlers.user.reviews import register_review_handlers @@ -43,20 +42,19 @@ async def on_startup(_): """Выполняется во время запуска бота.""" loop = asyncio.get_event_loop() loop.create_task(scheduler()) - message = 'Бот запущен' + message = "Бот запущен" await send_message_to_admins(message) logger.info(message) async def on_shutdown(_): """Выполняется во время остановки бота.""" - message = 'Бот остановлен' + message = "Бот остановлен" await send_message_to_admins(message) logger.info(message) -if __name__ == '__main__': - executor.start_polling(dp, skip_updates=True, - on_startup=on_startup, - on_shutdown=on_shutdown - ) +if __name__ == "__main__": + executor.start_polling( + dp, skip_updates=True, on_startup=on_startup, on_shutdown=on_shutdown + ) diff --git a/match_algoritm/MatchingHelper.py b/match_algoritm/MatchingHelper.py index 80cf46f..253a7be 100644 --- a/match_algoritm/MatchingHelper.py +++ b/match_algoritm/MatchingHelper.py @@ -3,15 +3,19 @@ from controllerBD.db_loader import db_session from controllerBD.models import UserMets, UserStatus -from controllerBD.services import (send_message_to_admins, - update_all_user_mets, update_mets) +from controllerBD.services import ( + send_message_to_admins, + update_all_user_mets, + update_mets, +) from handlers.user.check_message import check_message from loader import bot, logger from sendler.match_messages import send_match_messages -class MachingHelper(): +class MachingHelper: """Класс - интерфейс алгоритма""" + vertex_conunt: int edges_count: int @@ -23,13 +27,16 @@ def prepare(self): """Подготовка алгоритма""" logger.info("Начало подготовки работы алгоритма") data_from_bd = {} - active_users = db_session.query(UserStatus.id).\ - filter(UserStatus.status == 1).all() + active_users = ( + db_session.query(UserStatus.id).filter(UserStatus.status == 1).all() + ) active_users = [i[0] for i in active_users] for now_user in active_users: - connected_user = db_session.query(UserMets.met_info).filter( - UserMets.id == now_user - ).first()[0] + connected_user = ( + db_session.query(UserMets.met_info) + .filter(UserMets.id == now_user) + .first()[0] + ) connected_user = list(json.loads(connected_user).values()) data_from_bd[now_user] = connected_user @@ -37,7 +44,8 @@ def prepare(self): self.all_active = list(data_from_bd.keys()) for v in self.all_active: adjacency_list[v] = [ - item for item in self.all_active if item not in data_from_bd[v]+[v]] + item for item in self.all_active if item not in data_from_bd[v] + [v] + ] edges = [] for v in self.all_active: for i in adjacency_list[v]: @@ -69,7 +77,12 @@ async def send_and_write(self, t: dict): def start(self): """Запуск алгоритма""" logger.info("Начало работы алгоритма") - subprocess.call(['./match_algoritm/matchingalogitm -f ./data/match_algoritm_data/input.txt --max'], shell=True) + subprocess.call( + [ + "./match_algoritm/matchingalogitm -f ./data/match_algoritm_data/input.txt --max" + ], + shell=True, + ) res = [] with open("./data/match_algoritm_data/output.txt", "r") as text: res = text.readlines() @@ -83,18 +96,20 @@ def start(self): matches[i] = None self.matchings = matches logger.info("Завершение работы алгоритма") - logger.info(f'пары {matches}') + logger.info(f"пары {matches}") return matches async def start_algoritm(): """Запуск алгоритма распределения""" - await send_message_to_admins('Начинаем распределение') + await send_message_to_admins("Начинаем распределение") await check_message() mc = MachingHelper() res = mc.start() - await send_message_to_admins(f'Количество пар: {len(res)}.\n' - f'Начинаем отправку сообщений.') + await send_message_to_admins( + f"Количество пар: {len(res)}.\n" f"Начинаем отправку сообщений." + ) await mc.send_and_write(res) - await send_message_to_admins('Сообщения пользователям отправлены.\n' - 'Распределение завершено.') + await send_message_to_admins( + "Сообщения пользователям отправлены.\n" "Распределение завершено." + ) diff --git a/match_algoritm/__init__.py b/match_algoritm/__init__.py index f7bae82..1f468f0 100644 --- a/match_algoritm/__init__.py +++ b/match_algoritm/__init__.py @@ -1 +1 @@ -from .MatchingHelper import * \ No newline at end of file +from .MatchingHelper import * diff --git a/match_algoritm/blossom.py b/match_algoritm/blossom.py index 28e3956..fcd7a6d 100644 --- a/match_algoritm/blossom.py +++ b/match_algoritm/blossom.py @@ -3,13 +3,15 @@ def get_maximum_matching(graph, matching, i): print(i) augmenting_path = get_augmenting_path(graph, matching) if len(augmenting_path) > 0: - return get_maximum_matching(graph, matching.augment(augmenting_path), i+1) + return get_maximum_matching(graph, matching.augment(augmenting_path), i + 1) else: return matching # https://en.wikipedia.org/wiki/Blossom_algorithm """Получить наибольший поток в графе""" + + def get_augmenting_path(graph, matching): forest = Forest() graph.unmark_all_edges() @@ -31,14 +33,14 @@ def get_augmenting_path(graph, matching): else: if forest.get_root(v) != forest.get_root(w): path = forest.get_path_from_root_to( - v) + forest.get_path_to_root_from(w) + v + ) + forest.get_path_to_root_from(w) return path else: blossom = forest.get_blossom(v, w) graph_prime = graph.contract(blossom) matching_prime = matching.contract(blossom) - path_prime = get_augmenting_path( - graph_prime, matching_prime) + path_prime = get_augmenting_path(graph_prime, matching_prime) path = graph.lift_path(path_prime, blossom) return path graph.mark_edge(e) @@ -61,13 +63,15 @@ def __init__(self): def __assert_representation(self): for t in self.adjacency: - assert len( - self.adjacency[t]) > 0, 'Если вершина существует в матрице смежности, она должна иметь хотя бы одного соседа.' + assert ( + len(self.adjacency[t]) > 0 + ), "Если вершина существует в матрице смежности, она должна иметь хотя бы одного соседа." for u in self.adjacency[t]: self.__assert_edge_exists((t, u)) for t in self.unmarked_adjacency: - assert len( - self.unmarked_adjacency[t]) > 0, 'Если вершина существует в непомеченной матрице смежности, она должна иметь хотя бы одного соседа.' + assert ( + len(self.unmarked_adjacency[t]) > 0 + ), "Если вершина существует в непомеченной матрице смежности, она должна иметь хотя бы одного соседа." for u in self.unmarked_adjacency[t]: self.__assert_edge_exists((t, u)) self.__assert_unmarked_edge_exists((t, u)) @@ -75,38 +79,52 @@ def __assert_representation(self): def __assert_edge_exists(self, edge): v, w = edge assert (v in self.adjacency) and ( - w in self.adjacency[v]), 'Край должен существовать в матрице смежности' + w in self.adjacency[v] + ), "Край должен существовать в матрице смежности" assert (w in self.adjacency) and ( - v in self.adjacency[w]), 'Взаимное ребро должно существовать в матрице смежности' + v in self.adjacency[w] + ), "Взаимное ребро должно существовать в матрице смежности" def __assert_edge_does_not_exist(self, edge): v, w = edge print(v, w) assert (v not in self.adjacency) or ( - w not in self.adjacency[v]), 'Ребро не должно существовать в матрице смежности' + w not in self.adjacency[v] + ), "Ребро не должно существовать в матрице смежности" assert (w not in self.adjacency) or ( - v not in self.adjacency[w]), 'Взаимное ребро не должно существовать в матрице смежности' + v not in self.adjacency[w] + ), "Взаимное ребро не должно существовать в матрице смежности" def __assert_unmarked_edge_exists(self, edge): v, w = edge assert (v in self.unmarked_adjacency) and ( - w in self.unmarked_adjacency[v]), 'Ребро должно существовать в непомеченной матрице смежности' + w in self.unmarked_adjacency[v] + ), "Ребро должно существовать в непомеченной матрице смежности" assert (w in self.unmarked_adjacency) and ( - v in self.unmarked_adjacency[w]), 'Взаимное ребро должно существовать в непомеченной матрице смежности' + v in self.unmarked_adjacency[w] + ), "Взаимное ребро должно существовать в непомеченной матрице смежности" def __assert_unmarked_edge_does_not_exist(self, edge): v, w = edge assert (v not in self.unmarked_adjacency) or ( - w not in self.unmarked_adjacency[v]), 'Ребро не должно существовать в непомеченной матрице смежности' + w not in self.unmarked_adjacency[v] + ), "Ребро не должно существовать в непомеченной матрице смежности" assert (w not in self.unmarked_adjacency) or ( - v not in self.unmarked_adjacency[w]), 'Обратное ребро не должно существовать в непомеченной матрице смежности' + v not in self.unmarked_adjacency[w] + ), "Обратное ребро не должно существовать в непомеченной матрице смежности" def __assert_vertice_exists(self, vertice): - assert vertice in self.adjacency, 'Вершина должна существовать в матрице смежности' + assert ( + vertice in self.adjacency + ), "Вершина должна существовать в матрице смежности" def __assert_vertice_does_not_exist(self, vertice): - assert vertice not in self.adjacency, 'Вершина не должна существовать в матрице смежности' - assert vertice not in self.unmarked_adjacency, 'Вершина не должна существовать в непомеченной матрице смежности' + assert ( + vertice not in self.adjacency + ), "Вершина не должна существовать в матрице смежности" + assert ( + vertice not in self.unmarked_adjacency + ), "Вершина не должна существовать в непомеченной матрице смежности" """Копировать граф""" @@ -219,7 +237,7 @@ def lift_path(self, path, blossom): if len(path) == 0: return path if len(path) == 1: - assert False, 'Путь не может содержать ровно одну вершину' + assert False, "Путь не может содержать ровно одну вершину" if path[0] == blossom.get_id(): ############################################################################################################ @@ -237,7 +255,9 @@ def lift_path(self, path, blossom): blossom_path.append(v) if (w in self.adjacency[v]) and (len(blossom_path) % 2 != 0): return blossom_path + path[1:] - assert False, 'At least one path with even edges must exist through the blossom' + assert ( + False + ), "At least one path with even edges must exist through the blossom" if path[-1] == blossom.get_id(): ############################################################################################################ @@ -254,10 +274,12 @@ def lift_path(self, path, blossom): blossom_path.append(v) if (u in self.adjacency[v]) and (len(blossom_path) % 2 != 0): return path[:-1] + list(reversed(blossom_path)) - assert False, 'At least one path with even edges must exist through the blossom' + assert ( + False + ), "At least one path with even edges must exist through the blossom" for i, v in enumerate(path): if v == blossom.get_id(): - u, w = path[i-1], path[i+1] + u, w = path[i - 1], path[i + 1] if u in self.adjacency[blossom.get_base()]: #################################################################################################### @@ -268,13 +290,15 @@ def lift_path(self, path, blossom): for v in blossom.traverse_left(): blossom_path.append(v) if (w in self.adjacency[v]) and (len(blossom_path) % 2 != 0): - return path[:i] + blossom_path + path[i+1:] + return path[:i] + blossom_path + path[i + 1 :] blossom_path = [] for v in blossom.traverse_right(): blossom_path.append(v) if (w in self.adjacency[v]) and (len(blossom_path) % 2 != 0): - return path[:i] + blossom_path + path[i+1:] - assert False, 'At least one path with even edges must exist through the blossom' + return path[:i] + blossom_path + path[i + 1 :] + assert ( + False + ), "At least one path with even edges must exist through the blossom" elif w in self.adjacency[blossom.get_base()]: #################################################################################################### @@ -285,15 +309,23 @@ def lift_path(self, path, blossom): for v in blossom.traverse_left(): blossom_path.append(v) if (u in self.adjacency[v]) and (len(blossom_path) % 2 != 0): - return path[:i] + list(reversed(blossom_path)) + path[i+1:] + return ( + path[:i] + list(reversed(blossom_path)) + path[i + 1 :] + ) blossom_path = [] for v in blossom.traverse_right(): blossom_path.append(v) if (u in self.adjacency[v]) and (len(blossom_path) % 2 != 0): - return path[:i] + list(reversed(blossom_path)) + path[i+1:] - assert False, 'Через цветок должен существовать хотя бы один путь с ровными краями.' + return ( + path[:i] + list(reversed(blossom_path)) + path[i + 1 :] + ) + assert ( + False + ), "Через цветок должен существовать хотя бы один путь с ровными краями." else: - assert False, 'Ровно одна сторона пути должна быть инцидентна основанию цветка.' + assert ( + False + ), "Ровно одна сторона пути должна быть инцидентна основанию цветка." return path @@ -319,43 +351,61 @@ def __assert_representation(self): def __assert_edge_exists(self, edge): v, w = edge assert (v in self.adjacency) and ( - w in self.adjacency[v]), 'Край должен существовать в матрице смежности' + w in self.adjacency[v] + ), "Край должен существовать в матрице смежности" assert (w in self.adjacency) and ( - v in self.adjacency[w]), 'Взаимное ребро должно существовать в матрице смежности' - assert edge in self.edges, 'Край должен существовать в наборе ребер' + v in self.adjacency[w] + ), "Взаимное ребро должно существовать в матрице смежности" + assert edge in self.edges, "Край должен существовать в наборе ребер" def __assert_edge_does_not_exist(self, edge): v, w = edge assert (v not in self.adjacency) or ( - w not in self.adjacency[v]), 'Ребро не должно существовать в матрице смежности' + w not in self.adjacency[v] + ), "Ребро не должно существовать в матрице смежности" assert (w not in self.adjacency) or ( - v not in self.adjacency[w]), 'Взаимное ребро не должно существовать в матрице смежности' - assert edge not in self.edges, 'Ребро не должно существовать в наборе ребер' + v not in self.adjacency[w] + ), "Взаимное ребро не должно существовать в матрице смежности" + assert edge not in self.edges, "Ребро не должно существовать в наборе ребер" def __assert_vertice_is_exposed(self, vertice): - assert vertice in self.adjacency, 'Вершина должна существовать в матрице смежности' - assert vertice in self.exposed_vertices, 'Вершина должна существовать в наборе вершин.' - assert len(self.adjacency[vertice] - ) == 0, 'Вершина не должна иметь соседей' + assert ( + vertice in self.adjacency + ), "Вершина должна существовать в матрице смежности" + assert ( + vertice in self.exposed_vertices + ), "Вершина должна существовать в наборе вершин." + assert len(self.adjacency[vertice]) == 0, "Вершина не должна иметь соседей" def __assert_vertice_is_not_exposed(self, vertice): - assert vertice in self.adjacency, 'Вершина должна существовать в матрице смежности' - assert vertice not in self.exposed_vertices, 'Вершина должна существовать в наборе вершин' - assert len( - self.adjacency[vertice]) == 1, 'Вершина должна иметь ровно одного соседа' + assert ( + vertice in self.adjacency + ), "Вершина должна существовать в матрице смежности" + assert ( + vertice not in self.exposed_vertices + ), "Вершина должна существовать в наборе вершин" + assert ( + len(self.adjacency[vertice]) == 1 + ), "Вершина должна иметь ровно одного соседа" def __assert_vertice_exists(self, vertice): - assert vertice in self.adjacency, 'Вершина должна существовать в матрице смежности' + assert ( + vertice in self.adjacency + ), "Вершина должна существовать в матрице смежности" if vertice in self.exposed_vertices: - assert len( - self.adjacency[vertice]) == 0, 'Если вершина открыта, у нее не должно быть соседей' + assert ( + len(self.adjacency[vertice]) == 0 + ), "Если вершина открыта, у нее не должно быть соседей" else: - assert len( - self.adjacency[vertice]) == 1, 'Если вершина не открыта, она должна иметь ровно одного соседа.' + assert ( + len(self.adjacency[vertice]) == 1 + ), "Если вершина не открыта, она должна иметь ровно одного соседа." def __assert_vertice_does_not_exist(self, vertice): - assert vertice not in self.adjacency, 'Вершина не должна существовать в матрице смежности' - assert vertice not in self.exposed_vertices, 'Вершина не должна быть открыта' + assert ( + vertice not in self.adjacency + ), "Вершина не должна существовать в матрице смежности" + assert vertice not in self.exposed_vertices, "Вершина не должна быть открыта" def copy(self): self.__assert_representation() @@ -377,8 +427,8 @@ def augment(self, path): matching.__assert_vertice_is_exposed(path[-1]) matching.exposed_vertices.remove(path[0]) matching.exposed_vertices.remove(path[-1]) - for i in range(len(path)-1): - v, w = path[i], path[i+1] + for i in range(len(path) - 1): + v, w = path[i], path[i + 1] edge = tuple(sorted((v, w))) if edge in matching.edges: matching.__assert_edge_exists(edge) @@ -456,24 +506,34 @@ def __init__(self): def __assert_representation(self): for vertice in self.roots: self.__assert_vertice_exists(vertice) - assert self.roots.keys() == self.distances_to_root.keys( - ), 'Корни и расстояния до корня должны иметь одинаковые ключи' - assert self.roots.keys() == self.parents.keys( - ), 'Корни и родители mut имеют одинаковые ключи' + assert ( + self.roots.keys() == self.distances_to_root.keys() + ), "Корни и расстояния до корня должны иметь одинаковые ключи" + assert ( + self.roots.keys() == self.parents.keys() + ), "Корни и родители mut имеют одинаковые ключи" for vertice in self.unmarked_even_vertices: self.__assert_vertice_exists(vertice) - assert self.distances_to_root[vertice] % 2 == 0, 'Неотмеченная четная вершина должна иметь четное расстояние до корня' + assert ( + self.distances_to_root[vertice] % 2 == 0 + ), "Неотмеченная четная вершина должна иметь четное расстояние до корня" def __assert_vertice_exists(self, vertice): - assert vertice in self.roots, 'Вершина должна иметь корень' - assert vertice in self.distances_to_root, 'Вершина должна иметь расстояние до корня' - assert vertice in self.parents, 'Вершина должна иметь родителя' + assert vertice in self.roots, "Вершина должна иметь корень" + assert ( + vertice in self.distances_to_root + ), "Вершина должна иметь расстояние до корня" + assert vertice in self.parents, "Вершина должна иметь родителя" def __assert_vertice_does_not_exist(self, vertice): - assert vertice not in self.roots, 'Вершина не должна иметь корня' - assert vertice not in self.distances_to_root, 'Вершина не должна иметь расстояние до корня' - assert vertice not in self.unmarked_even_vertices, 'Вершина не должна существовать в наборе непомеченных четных вершин.' - assert vertice not in self.parents, 'Вершина не должна иметь родителя' + assert vertice not in self.roots, "Вершина не должна иметь корня" + assert ( + vertice not in self.distances_to_root + ), "Вершина не должна иметь расстояние до корня" + assert ( + vertice not in self.unmarked_even_vertices + ), "Вершина не должна существовать в наборе непомеченных четных вершин." + assert vertice not in self.parents, "Вершина не должна иметь родителя" def add_singleton_tree(self, vertice): self.__assert_vertice_does_not_exist(vertice) @@ -493,7 +553,9 @@ def get_unmarked_even_vertice(self): def mark_vertice(self, vertice): self.__assert_vertice_exists(vertice) if self.distances_to_root[vertice] % 2 == 0: - assert vertice in self.unmarked_even_vertices, 'Если вершина имеет четное расстояние до корня, она должна существовать в наборе непомеченных четных вершин.' + assert ( + vertice in self.unmarked_even_vertices + ), "Если вершина имеет четное расстояние до корня, она должна существовать в наборе непомеченных четных вершин." self.unmarked_even_vertices.remove(vertice) self.__assert_representation() @@ -520,7 +582,9 @@ def add_edge(self, edge): self.unmarked_even_vertices.add(w) self.parents[w] = v else: - assert False, 'По крайней мере, одна инцидентная вершина не должна существовать' + assert ( + False + ), "По крайней мере, одна инцидентная вершина не должна существовать" self.__assert_representation() def get_distance_to_root(self, vertice): @@ -548,7 +612,8 @@ def get_path_to_root_from(self, vertice): parent = self.parents[parent] path.append(root) assert len(set(path)) == len( - path), 'Путь к корню не должен содержать повторяющихся вершин.' + path + ), "Путь к корню не должен содержать повторяющихся вершин." return path def get_blossom(self, v, w): @@ -564,19 +629,22 @@ def get_blossom(self, v, w): break else: v_blossom_vertices.append(u) - assert common_ancestor is not None, 'Должен существовать общий предок' + assert common_ancestor is not None, "Должен существовать общий предок" w_blossom_vertices = [] for u in w_path: if u == common_ancestor: break else: w_blossom_vertices.append(u) - blossom_vertices = [common_ancestor] + \ - list(reversed(v_blossom_vertices)) + w_blossom_vertices + blossom_vertices = ( + [common_ancestor] + list(reversed(v_blossom_vertices)) + w_blossom_vertices + ) assert len(set(blossom_vertices)) == len( - blossom_vertices), 'Цветок не должен содержать повторяющихся вершин.' - assert len( - blossom_vertices) % 2 != 0, 'Цветок должен содержать нечетное количество вершин' + blossom_vertices + ), "Цветок не должен содержать повторяющихся вершин." + assert ( + len(blossom_vertices) % 2 != 0 + ), "Цветок должен содержать нечетное количество вершин" blossom = Blossom(blossom_vertices, common_ancestor) return blossom @@ -593,11 +661,13 @@ def __init__(self, vertices, base): self.__assert_representation() def __assert_representation(self): - assert self.vertices[0] == self.base, 'Цветок должен начинаться с базовой вершины' - assert len( - self.vertices) % 2 != 0, 'Цветок должен иметь нечетное количество вершин' - assert len( - self.vertices) >= 3, 'Цветок должен иметь не менее трех вершин.' + assert ( + self.vertices[0] == self.base + ), "Цветок должен начинаться с базовой вершины" + assert ( + len(self.vertices) % 2 != 0 + ), "Цветок должен иметь нечетное количество вершин" + assert len(self.vertices) >= 3, "Цветок должен иметь не менее трех вершин." def get_id(self): self.__assert_representation() diff --git a/match_algoritm/test_blossom.py b/match_algoritm/test_blossom.py index da712bd..a752e93 100644 --- a/match_algoritm/test_blossom.py +++ b/match_algoritm/test_blossom.py @@ -1,5 +1,8 @@ -import blossom from datetime import datetime + +import blossom + + def test(): start_time = datetime.now() graph = blossom.Graph() @@ -9,11 +12,11 @@ def test(): print(datetime.now()) matching = blossom.Matching() matching.add_vertices(graph.get_vertices()) - actual = sorted(blossom.get_maximum_matching(graph, matching,0).edges) + actual = sorted(blossom.get_maximum_matching(graph, matching, 0).edges) print("end in", datetime.now() - start_time) - with open("./new.txt","w") as text: + with open("./new.txt", "w") as text: text.write(str(actual)) -if __name__ == '__main__': - test() +if __name__ == "__main__": + test() diff --git a/match_algoritm/tests/create_test_graph.py b/match_algoritm/tests/create_test_graph.py index 72bbe28..37ddf04 100644 --- a/match_algoritm/tests/create_test_graph.py +++ b/match_algoritm/tests/create_test_graph.py @@ -1,4 +1,5 @@ import random + res = {} res2 = [] n = 5000 @@ -6,27 +7,26 @@ for i in range(n): res[i] = [] for i in range(q): - first = random.randint(0,n-1) - second = random.randint(0,n-1) + first = random.randint(0, n - 1) + second = random.randint(0, n - 1) while first == second: - first = random.randint(0,n-1) - second = random.randint(0,n-1) - t = (max(first,second), min(first,second)) + first = random.randint(0, n - 1) + second = random.randint(0, n - 1) + t = (max(first, second), min(first, second)) res2.append(t) res2 = list(set(res2)) -with open("../input.txt","w") as text: +with open("../input.txt", "w") as text: res = list(sorted(res2)) st = "" - st+=f"{n}\n{q}\n" + st += f"{n}\n{q}\n" for i in res: - st += str(i[0])+ " "+ str(i[1])+" 0\n" + st += str(i[0]) + " " + str(i[1]) + " 0\n" text.write(st) # for https://programforyou.ru/graph-redactor -with open("./graph.txt","w") as text: +with open("./graph.txt", "w") as text: df = "" for i in res: - df += str(i[0])+ " -- "+ str(i[1])+"\n" + df += str(i[0]) + " -- " + str(i[1]) + "\n" text.write(df) - diff --git a/migrations/env.py b/migrations/env.py index bd87528..0e67d90 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -4,10 +4,9 @@ from sqlalchemy import engine_from_config, pool from controllerBD.db_loader import Base -from controllerBD.models import * # УРААА КОСТЫЫЫЫЛЬ +from controllerBD.models import * # УРААА КОСТЫЫЫЫЛЬ from data.config import DB_DSN - # this is the Alembic Config object, which provides # access to the values within the .ini file in use. config = context.config @@ -79,4 +78,4 @@ def run_migrations_online(): if context.is_offline_mode(): run_migrations_offline() else: - run_migrations_online() \ No newline at end of file + run_migrations_online() diff --git a/migrations/versions/cbba5d89b3ac_init.py b/migrations/versions/cbba5d89b3ac_init.py index 8aa7855..67902f7 100644 --- a/migrations/versions/cbba5d89b3ac_init.py +++ b/migrations/versions/cbba5d89b3ac_init.py @@ -5,12 +5,12 @@ Create Date: 2024-12-11 10:34:50.845505 """ -from alembic import op -import sqlalchemy as sa +import sqlalchemy as sa +from alembic import op # revision identifiers, used by Alembic. -revision = 'cbba5d89b3ac' +revision = "cbba5d89b3ac" down_revision = None branch_labels = None depends_on = None @@ -18,94 +18,136 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('genders', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('gender_name', sa.String(length=100), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('gender_name'), - sa.UniqueConstraint('id') + op.create_table( + "genders", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("gender_name", sa.String(length=100), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("gender_name"), + sa.UniqueConstraint("id"), ) - op.create_table('user_info', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('teleg_id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=100), nullable=False), - sa.Column('birthday', sa.String(), nullable=False), - sa.Column('about', sa.String(length=500), nullable=False), - sa.Column('gender', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['gender'], ['genders.id'], ), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('teleg_id') + op.create_table( + "user_info", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("teleg_id", sa.Integer(), nullable=False), + sa.Column("name", sa.String(length=100), nullable=False), + sa.Column("birthday", sa.String(), nullable=False), + sa.Column("about", sa.String(length=500), nullable=False), + sa.Column("gender", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["gender"], + ["genders.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("teleg_id"), ) - op.create_table('ban_list', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('banned_user_id', sa.Integer(), nullable=False), - sa.Column('ban_status', sa.Integer(), nullable=False), - sa.Column('date_of_ban', sa.String(), nullable=False), - sa.Column('comment_to_ban', sa.String(length=500), nullable=False), - sa.Column('date_of_unban', sa.String(), nullable=False), - sa.Column('comment_to_unban', sa.String(length=500), nullable=False), - sa.ForeignKeyConstraint(['banned_user_id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "ban_list", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("banned_user_id", sa.Integer(), nullable=False), + sa.Column("ban_status", sa.Integer(), nullable=False), + sa.Column("date_of_ban", sa.String(), nullable=False), + sa.Column("comment_to_ban", sa.String(length=500), nullable=False), + sa.Column("date_of_unban", sa.String(), nullable=False), + sa.Column("comment_to_unban", sa.String(length=500), nullable=False), + sa.ForeignKeyConstraint( + ["banned_user_id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('holidays_status', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('status', sa.Integer(), nullable=False), - sa.Column('till_date', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "holidays_status", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("status", sa.Integer(), nullable=False), + sa.Column("till_date", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('met_info', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('first_user_id', sa.Integer(), nullable=False), - sa.Column('second_user_id', sa.Integer(), nullable=False), - sa.Column('date', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['first_user_id'], ['user_info.id'], ), - sa.ForeignKeyConstraint(['second_user_id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "met_info", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("first_user_id", sa.Integer(), nullable=False), + sa.Column("second_user_id", sa.Integer(), nullable=False), + sa.Column("date", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["first_user_id"], + ["user_info.id"], + ), + sa.ForeignKeyConstraint( + ["second_user_id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('tg_usernames', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('username', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "tg_usernames", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('user_mets', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('met_info', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user_mets", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("met_info", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('user_status', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('status', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user_status", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("status", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_table('mets_reviews', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('met_id', sa.Integer(), nullable=False), - sa.Column('who_id', sa.Integer(), nullable=False), - sa.Column('about_whom_id', sa.Integer(), nullable=False), - sa.Column('grade', sa.Integer(), nullable=False), - sa.Column('comment', sa.String(length=500), nullable=True), - sa.Column('date_of_comment', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['about_whom_id'], ['user_info.id'], ), - sa.ForeignKeyConstraint(['met_id'], ['met_info.id'], ), - sa.ForeignKeyConstraint(['who_id'], ['user_info.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_table( + "mets_reviews", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("met_id", sa.Integer(), nullable=False), + sa.Column("who_id", sa.Integer(), nullable=False), + sa.Column("about_whom_id", sa.Integer(), nullable=False), + sa.Column("grade", sa.Integer(), nullable=False), + sa.Column("comment", sa.String(length=500), nullable=True), + sa.Column("date_of_comment", sa.String(), nullable=False), + sa.ForeignKeyConstraint( + ["about_whom_id"], + ["user_info.id"], + ), + sa.ForeignKeyConstraint( + ["met_id"], + ["met_info.id"], + ), + sa.ForeignKeyConstraint( + ["who_id"], + ["user_info.id"], + ), + sa.PrimaryKeyConstraint("id"), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('mets_reviews') - op.drop_table('user_status') - op.drop_table('user_mets') - op.drop_table('tg_usernames') - op.drop_table('met_info') - op.drop_table('holidays_status') - op.drop_table('ban_list') - op.drop_table('user_info') - op.drop_table('genders') - # ### end Alembic commands ### \ No newline at end of file + op.drop_table("mets_reviews") + op.drop_table("user_status") + op.drop_table("user_mets") + op.drop_table("tg_usernames") + op.drop_table("met_info") + op.drop_table("holidays_status") + op.drop_table("ban_list") + op.drop_table("user_info") + op.drop_table("genders") + # ### end Alembic commands ### diff --git a/requirements.dev.txt b/requirements.dev.txt new file mode 100644 index 0000000..3cfb3fc --- /dev/null +++ b/requirements.dev.txt @@ -0,0 +1,2 @@ +black +isort \ No newline at end of file diff --git a/sendler/__init__.py b/sendler/__init__.py index a33510d..0a47454 100644 --- a/sendler/__init__.py +++ b/sendler/__init__.py @@ -1 +1 @@ -from .match_messages import * \ No newline at end of file +from .match_messages import * diff --git a/sendler/match_messages.py b/sendler/match_messages.py index f82707a..c7a0f1c 100644 --- a/sendler/match_messages.py +++ b/sendler/match_messages.py @@ -3,9 +3,13 @@ from aiogram import Bot from controllerBD.db_loader import db_session -from controllerBD.models import Users, Gender -from controllerBD.services import get_defaulf_pare_base_id, update_mets, \ - update_all_user_mets, get_tg_username_from_db_by_teleg_id +from controllerBD.models import Gender, Users +from controllerBD.services import ( + get_defaulf_pare_base_id, + get_tg_username_from_db_by_teleg_id, + update_all_user_mets, + update_mets, +) from handlers.user.work_with_date import date_from_db_to_message from keyboards.user import help_texts_markup from loader import logger @@ -17,8 +21,7 @@ async def send_match_messages(match_info: dict, bot: Bot): await sleep(0.1) users_info = pare_users_query(match) if not users_info: - logger.error(f'Не удалось получить информацию ' - f'из БД для пары {match}') + logger.error(f"Не удалось получить информацию " f"из БД для пары {match}") continue elif len(users_info) == 2: first_user = users_info[0] @@ -28,31 +31,47 @@ async def send_match_messages(match_info: dict, bot: Bot): try: first_message = make_message(first_user) except Exception as error: - logger.error(f'Не удалось сформировать сообщение о ' - f'пользователе {first_user}. Ошибка {error}') + logger.error( + f"Не удалось сформировать сообщение о " + f"пользователе {first_user}. Ошибка {error}" + ) try: second_message = make_message(second_user) except Exception as error: - logger.error(f'Не удалось сформировать сообщение о ' - f'пользователе {second_user}. Ошибка {error}') + logger.error( + f"Не удалось сформировать сообщение о " + f"пользователе {second_user}. Ошибка {error}" + ) try: - await bot.send_message(second_user_id, first_message, - parse_mode="HTML", - reply_markup=help_texts_markup()) - logger.info(f'Сообщение для пользователя {second_user_id} ' - f'отправлено') + await bot.send_message( + second_user_id, + first_message, + parse_mode="HTML", + reply_markup=help_texts_markup(), + ) + logger.info( + f"Сообщение для пользователя {second_user_id} " f"отправлено" + ) except Exception as error: - logger.error(f'Сообщение для пользователя {second_user_id} ' - f'не отправлено. Ошибка {error}') + logger.error( + f"Сообщение для пользователя {second_user_id} " + f"не отправлено. Ошибка {error}" + ) try: - await bot.send_message(first_user_id, second_message, - parse_mode="HTML", - reply_markup=help_texts_markup()) - logger.info(f'Сообщение для пользователя {first_user_id} ' - f'отправлено') + await bot.send_message( + first_user_id, + second_message, + parse_mode="HTML", + reply_markup=help_texts_markup(), + ) + logger.info( + f"Сообщение для пользователя {first_user_id} " f"отправлено" + ) except Exception as error: - logger.error(f'Сообщение для пользователя {first_user_id} ' - f'не отправлено. Ошибка {error}') + logger.error( + f"Сообщение для пользователя {first_user_id} " + f"не отправлено. Ошибка {error}" + ) else: fail_user = users_info[0] fail_user_db_id = fail_user[0] @@ -78,23 +97,25 @@ def make_message(user_info: tuple) -> str: user_gender = user_info[5] user_tg_username = get_tg_username_from_db_by_teleg_id(user_id) - base_message = (f'На этой неделе твоя пара для кофе: ' - f'{user_name}') - tg_username_message = (f'@{user_tg_username}') - birth_day_message = f'Дата рождения: {user_birthday}' - about_message = f'Информация: {user_about}' - gender_message = f'Пол: {user_gender}' + base_message = ( + f"На этой неделе твоя пара для кофе: " + f'{user_name}' + ) + tg_username_message = f"@{user_tg_username}" + birth_day_message = f"Дата рождения: {user_birthday}" + about_message = f"Информация: {user_about}" + gender_message = f"Пол: {user_gender}" row_message_list = [base_message] if user_tg_username: row_message_list.append(tg_username_message) - if user_birthday != 'Не указано': + if user_birthday != "Не указано": row_message_list.append(birth_day_message) - if user_about != 'Не указано': + if user_about != "Не указано": row_message_list.append(about_message) - if user_gender != 'Не указано': + if user_gender != "Не указано": row_message_list.append(gender_message) - message = '\n'.join(row_message_list) + message = "\n".join(row_message_list) return message @@ -102,14 +123,19 @@ def make_message(user_info: tuple) -> str: def pare_users_query(pare: tuple): """Запрашивает информацию по паре юзеров из базы.""" try: - result = db_session.query( - Users.id, - Users.teleg_id, - Users.name, - Users.birthday, - Users.about, - Gender.gender_name - ).join(Gender).filter(Users.id.in_(pare)).all() + result = ( + db_session.query( + Users.id, + Users.teleg_id, + Users.name, + Users.birthday, + Users.about, + Gender.gender_name, + ) + .join(Gender) + .filter(Users.id.in_(pare)) + .all() + ) except Exception: result = None finally: diff --git a/states/__init__.py b/states/__init__.py index 8950754..38eb373 100644 --- a/states/__init__.py +++ b/states/__init__.py @@ -1 +1 @@ -from .states import * \ No newline at end of file +from .states import * diff --git a/states/states.py b/states/states.py index 48ad5d9..c0e4795 100644 --- a/states/states.py +++ b/states/states.py @@ -3,6 +3,7 @@ class UserData(StatesGroup): """Машина состояний пользователя""" + start = State() name = State() birthday = State() @@ -14,6 +15,7 @@ class UserData(StatesGroup): class AdminData(StatesGroup): """Машина состояний админа""" + start = State() user_ban = State() comment_to_ban = State() From cc92c0e645e7d83903cbaeca1fd9b1a3fededf3c Mon Sep 17 00:00:00 2001 From: Stanislav Roslavtsev Date: Wed, 11 Dec 2024 10:45:33 +0300 Subject: [PATCH 4/4] ?? --- migrations/env.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/env.py b/migrations/env.py index 0e67d90..4230bff 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -4,7 +4,7 @@ from sqlalchemy import engine_from_config, pool from controllerBD.db_loader import Base -from controllerBD.models import * # УРААА КОСТЫЫЫЫЛЬ +from controllerBD.models import * from data.config import DB_DSN # this is the Alembic Config object, which provides