Если Вы когда-нибудь задумывался о том, как создать своё собственное веб-приложение для управления задачами, надеюсь, эта статья вам поможет.
Мы пройдём весь путь — от установки необходимых инструментов и настройки окружения до разработки интерфейса и деплоя приложения на сервере. Каждый этап будет сопровождаться объяснениями и примерами кода, которые вы сможете найти в репозитории на GitHub.
Перед началом разработки необходимо убедиться, что на вашем компьютере установлены Python 3 и Git. Python будет использоваться для создания серверной части приложения, а Git — для управления версиями и размещения кода на GitHub.
Установка Python и виртуального окружения
Установка Python и виртуального окружения
Первым делом убедимся, что установлен Python 3. Если нет, скачайте его с официального сайта.
Теперь создадим виртуальное окружение, чтобы все зависимости проекта были изолированы:
-
Создайте папку для проекта:
mkdir task_manager cd task_manager
-
Создайте виртуальное окружение:
python3 -m venv venv
-
Активируйте виртуальное окружение:
-
На macOS/Linux:
source venv/bin/activate
-
На Windows:
venv\Scripts\activate
Теперь в начале командной строки должены увидеть (venv)
, что означает, что виртуальное окружение активировано.
Установка необходимых библиотек
С активированным виртуальным окружением установим необходимые пакеты:
pip install flask flask_sqlalchemy
-
Flask: наш основной веб-фреймворк.
-
Flask_SQLAlchemy: расширение для работы с базами данных.
Отлично! Теперь мы готовы перейти к созданию структуры проекта.
Организуем файлы и папки нашего приложения:
task_manager/ ├── app.py ├── models.py ├── extensions.py ├── templates/ │ ├── index.html │ └── update.html └── static/ ├── style.css └── timer.js
Что означает каждая часть:
-
app.py
: основной файл нашего приложения Flask. -
models.py
: файл, где мы опишем модели базы данных. -
extensions.py
: здесь мы инициализируем расширения для Flask. -
templates/
: папка для HTML-шаблонов. -
static/
: папка для статических файлов (CSS, JavaScript, изображения).
Создайте эти файлы и папки в своем проекте.
Инициализация базы данных
Начнем с файла extensions.py
, где мы инициализируем базу данных с помощью SQLAlchemy:
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()
Определение модели данных
В файле models.py
опишем модель Task
, которая представляет задачу в нашем приложении:
from extensions import db from datetime import datetime class Task(db.Model): id = db.Column(db.Integer, primary_key=True) content = db.Column(db.String(200), nullable=False) completed = db.Column(db.Boolean, default=False) deadline = db.Column(db.DateTime, nullable=True) def __repr__(self): return f''
Настройка основного приложения
Теперь перейдем к файлу app.py
, где мы настроим Flask-приложение и определим маршруты:
from flask import Flask, render_template, request, redirect, url_for from extensions import db from models import Task from datetime import datetime import os app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///tasks.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db.init_app(app) @app.route('/') @app.route('/<filter>') def index(filter='all'): if filter == 'completed': tasks = Task.query.filter_by(completed=True).all() elif filter == 'pending': tasks = Task.query.filter_by(completed=False).all() else: tasks = Task.query.all() return render_template('index.html', tasks=tasks) @app.route('/add', methods=['POST']) def add_task(): task_content = request.form['content'] date_str = request.form.get('date') time_str = request.form.get('time') deadline = None if date_str and time_str: deadline_str = f"{date_str} {time_str}" deadline = datetime.strptime(deadline_str, '%d.%m.%Y %H:%M') new_task = Task(content=task_content, deadline=deadline) db.session.add(new_task) db.session.commit() return redirect(url_for('index')) @app.route('/delete/<int:id>') def delete_task(id): task = Task.query.get_or_404(id) db.session.delete(task) db.session.commit() return redirect(url_for('index')) @app.route('/complete/<int:id>') def complete_task(id): task = Task.query.get_or_404(id) task.completed = not task.completed db.session.commit() return redirect(url_for('index')) @app.route('/update/<int:id>', methods=['GET', 'POST']) def update_task(id): task = Task.query.get_or_404(id) if request.method == 'POST': task.content = request.form['content'] date_str = request.form.get('date') time_str = request.form.get('time') if date_str and time_str: deadline_str = f"{date_str} {time_str}" task.deadline = datetime.strptime(deadline_str, '%d.%m.%Y %H:%M') else: task.deadline = None db.session.commit() return redirect(url_for('index')) return render_template('update.html', task=task) if __name__ == "__main__": with app.app_context(): if not os.path.exists('tasks.db'): db.create_all() app.run(debug=True)
Главная страница и фильтры (/
и /<filter>
):
@app.route('/') @app.route('/<filter>') def index(filter='all'): if filter == 'completed': tasks = Task.query.filter_by(completed=True).all() elif filter == 'pending': tasks = Task.query.filter_by(completed=False).all() else: tasks = Task.query.all() return render_template('index.html', tasks=tasks)
Добавление новой задачи (/add
):
@app.route('/add', methods=['POST']) def add_task(): task_content = request.form['content'] date_str = request.form.get('date') time_str = request.form.get('time') deadline = None if date_str and time_str: deadline_str = f"{date_str} {time_str}" deadline = datetime.strptime(deadline_str, '%d.%m.%Y %H:%M') new_task = Task(content=task_content, deadline=deadline) db.session.add(new_task) db.session.commit() return redirect(url_for('index'))
Удаление задачи (/delete/<int:id>
):
@app.route('/delete/<int:id>') def delete_task(id): task = Task.query.get_or_404(id) db.session.delete(task) db.session.commit() return redirect(url_for('index'))
Переключение статуса выполнения задачи (/complete/<int:id>
):
@app.route('/complete/<int:id>') def complete_task(id): task = Task.query.get_or_404(id) task.completed = not task.completed db.session.commit() return redirect(url_for('index'))
Обновление задачи (/update/<int:id>
):
@app.route('/update/<int:id>', methods=['GET', 'POST']) def update_task(id): task = Task.query.get_or_404(id) if request.method == 'POST': task.content = request.form['content'] date_str = request.form.get('date') time_str = request.form.get('time') if date_str and time_str: deadline_str = f"{date_str} {time_str}" task.deadline = datetime.strptime(deadline_str, '%d.%m.%Y %H:%M') else: task.deadline = None db.session.commit() return redirect(url_for('index')) return render_template('update.html', task=task)
Запуск приложения:
if __name__ == "__main__": with app.app_context(): if not os.path.exists('tasks.db'): db.create_all() app.run(debug=True)
Теперь создадим шаблоны для нашего приложения, чтобы отображать информацию пользователю.
Главная страница (index.html)
В файле templates/index.html
добавим следующий код:
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Task Manager</title> <!-- Подключаем стили и библиотеки --> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <!-- Библиотеки для анимаций --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.css"> </head> <body> <div class="container"> <!-- Левая боковая панель --> <aside class="sidebar-left"> <h2>О приложении</h2> <p>Управляйте своими задачами эффективно с помощью дедлайнов и напоминаний.</p> </aside> <!-- Основной контент --> <main class="content"> <div class="main-content" data-aos="fade-up"> <h1>Task Manager</h1> <!-- Форма добавления задачи --> <form action="/add" method="POST" class="task-form" data-aos="fade-up"> <input type="text" name="content" placeholder="Добавить новую задачу" required> <input type="text" name="date" placeholder="Дата дедлайна (дд.мм.гггг)" pattern="\d{2}\.\d{2}\.\d{4}"> <input type="text" name="time" placeholder="Время дедлайна (чч:мм)" pattern="\d{2}:\d{2}"> <button type="submit" class="add-btn">Добавить задачу</button> </form> <!-- Фильтры задач --> <div class="filters" data-aos="fade-up"> <a href="{{ url_for('index', filter='all') }}">Все</a> <a href="{{ url_for('index', filter='completed') }}">Выполненные</a> <a href="{{ url_for('index', filter='pending') }}">Невыполненные</a> </div> <!-- Список задач --> <ul class="task-list"> {% for task in tasks %} <li class="{% if task.completed %}completed{% endif %}" data-aos="fade-up"> {% if task.completed %} <span class="checkmark">✓</span> {% endif %} <span class="task-content">{{ task.content }}</span> {% if task.deadline and not task.completed %} <span class="deadline"> Дедлайн: {{ task.deadline.strftime('%d.%m.%Y %H:%M') }} <span class="timer" data-deadline="{{ task.deadline.isoformat() }}"></span> </span> {% endif %} <div class="actions"> <a href="/complete/{{ task.id }}">{{ "Отменить" if task.completed else "Выполнить" }}</a> <a href="/update/{{ task.id }}">Редактировать</a> <a href="/delete/{{ task.id }}">Удалить</a> </div> </li> {% endfor %} </ul> </div> </main> <!-- Правая боковая панель --> <aside class="sidebar-right"> <h2>Быстрые ссылки</h2> <ul> <li><a href="#">Настройки</a></li> <li><a href="#">Помощь</a></li> <li><a href="#">Контакты</a></li> </ul> </aside> </div> <!-- Подключаем скрипты --> <script src="https://cdnjs.cloudflare.com/ajax/libs/aos/2.3.4/aos.js"></script> <!-- GSAP для анимаций --> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.2/gsap.min.js"></script> <!-- Mo.js для эффектов --> <script src="https://cdnjs.cloudflare.com/ajax/libs/mo-js/0.288.0/mo.min.js"></script> <!-- Наш скрипт --> <script src="{{ url_for('static', filename='timer.js') }}"></script> <script> // Инициализация AOS AOS.init({ duration: 1000, // Продолжительность анимаций }); </script> </body> </html>
Страница обновления задачи (update.html)
Создадим файл templates/update.html
для редактирования задачи:
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Обновить задачу</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <div class="container"> <h1>Обновить задачу</h1> <!-- Форма обновления задачи --> <form action="{{ url_for('update_task', id=task.id) }}" method="POST" class="task-form"> <input type="text" name="content" value="{{ task.content }}" required> <input type="text" name="date" value="{{ task.deadline.strftime('%d.%m.%Y') if task.deadline else '' }}" placeholder="Дата дедлайна (дд.мм.гггг)"> <input type="text" name="time" value="{{ task.deadline.strftime('%H:%M') if task.deadline else '' }}" placeholder="Время дедлайна (чч:мм)"> <button type="submit" class="add-btn">Обновить задачу</button> </form> <a href="{{ url_for('index') }}" class="back-link">Вернуться к списку задач</a> </div> </body> </html>
В папке static
создаем файл style.css
и добавляем стили для нашего приложения:
/* Общие стили */ body { font-family: 'Arial', sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; display: flex; min-height: 100vh; } .container { display: flex; flex: 1; margin: 20px auto; max-width: 1200px; background: #fff; padding: 20px; border-radius: 8px; } /* Боковые панели */ .sidebar-left, .sidebar-right { width: 200px; background: #f0f0f0; padding: 15px; border-radius: 8px; } .sidebar-left h2, .sidebar-right h2 { font-size: 1.2em; margin-bottom: 10px; } .sidebar-left p, .sidebar-right ul { font-size: 0.9em; } .sidebar-right ul { list-style: none; padding: 0; } .sidebar-right ul li { margin-bottom: 10px; } .sidebar-right ul li a { color: #007bff; text-decoration: none; } .sidebar-right ul li a:hover { text-decoration: underline; } /* Основной контент */ .main-content { flex: 1; margin: 0 20px; } /* Форма задач */ .task-form { display: flex; flex-direction: column; margin-bottom: 20px; } .task-form input { margin-bottom: 10px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } .task-form .add-btn { padding: 10px; background-color: #5cb85c; color: white; border: none; border-radius: 4px; cursor: pointer; } .task-form .add-btn:hover { background-color: #4cae4c; } /* Фильтры */ .filters { margin-bottom: 20px; } .filters a { margin: 0 10px; text-decoration: none; color: #5cb85c; font-size: 16px; } .filters a:hover { color: #3e8e41; } /* Список задач */ .task-list { list-style: none; padding: 0; } .task-list li { background: #f9f9f9; padding: 15px; margin-bottom: 10px; border-radius: 4px; display: flex; align-items: center; } .task-list li.completed { background: #e6ffe6; } .task-list li.completed .task-content { text-decoration: line-through; color: #888; } .checkmark { color: #5cb85c; font-size: 1.5em; margin-right: 10px; } .task-content { flex: 1; } .deadline { font-size: 0.9em; color: #777; margin-left: 10px; } .timer { font-size: 0.9em; color: #ff5733; margin-left: 10px; } .timer.expired { color: #dc3545; } /* Действия с задачами */ .actions { margin-left: auto; display: flex; align-items: center; } .actions a { margin-left: 10px; text-decoration: none; color: #007bff; font-size: 16px; } .actions a:hover { color: #0056b3; } /* Ссылка возврата */ .back-link { display: block; margin-top: 20px; text-decoration: none; color: #007bff; } .back-link:hover { color: #0056b3; }
В файле static/timer.js
добавим код для обновления таймеров и анимаций:
document.addEventListener('DOMContentLoaded', () => { const timers = document.querySelectorAll('.timer'); const completeLinks = document.querySelectorAll('.actions a[href*="complete"]'); // Функция для анимации при выполнении задачи function createBurstAnimation(x, y) { new mojs.Burst({ radius: { 0: 100 }, angle: 45, count: 10, children: { shape: 'circle', radius: 10, fill: ['#FF5722', '#FFC107', '#8BC34A'], duration: 2000 }, x: x, y: y, opacity: { 1: 0 }, }).play(); } // Обновление таймеров function updateTimers() { timers.forEach(timer => { const deadline = new Date(timer.getAttribute('data-deadline')); const now = new Date(); const remainingTime = deadline - now; if (remainingTime <= 0) { timer.textContent = 'Дедлайн прошёл'; timer.classList.add('expired'); } else { const days = Math.floor(remainingTime / (1000 * 60 * 60 * 24)); const hours = Math.floor((remainingTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((remainingTime % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((remainingTime % (1000 * 60)) / 1000); timer.textContent = `${days}д ${hours}ч ${minutes}м ${seconds}с`; } }); } // Обработка клика по кнопке "Выполнить" function handleTaskComplete(event) { event.preventDefault(); const link = event.currentTarget; const taskItem = link.closest('li'); const { left, top } = taskItem.getBoundingClientRect(); createBurstAnimation(left + window.scrollX + taskItem.offsetWidth / 2, top + window.scrollY + taskItem.offsetHeight / 2); setTimeout(() => { window.location.href = link.href; }, 1000); } completeLinks.forEach(link => { link.addEventListener('click', handleTaskComplete); }); updateTimers(); setInterval(updateTimers, 1000); });
Запуск приложения
Теперь, когда всё готово, давай протестируем наше приложение.
В терминале, находясь в корневой директории проекта, введем:
python app.py
Получим сообщение о том, что сервер запущен на http://127.0.0.1:5000
.
Открываем браузер и переходим по адресу http://127.0.0.1:5000. Видим интерфейс нашего Task Manager, где сможешь добавлять, редактировать, выполнять и удалять задачи.
Тестирование с помощью Talend API Tester
Чтобы убедиться, что всё работает правильно, можно использовать инструмент Talend API Tester — расширение для браузера Google Chrome, которое позволяет тестировать API и HTTP-запросы.
Как его установить:
-
Открываем браузер Google Chrome.
-
Перейдем в Интернет-магазин Chrome и найдем Talend API Tester.
-
Нажмаем кнопку «Установить» и следуем инструкциям для добавления расширения в браузер.
После установки мы получим такую страницу:
Тестирование добавления новой задачи
-
Метод: POST
-
URL:
http://127.0.0.1:5000/add
-
Параметры формы (Form Data):
-
content
:"Тестовая задача"
-
date
:"25.12.2023"
-
time
:"12:00"
-
-
Откройте Talend API Tester в браузере.
-
Выберите метод POST.
-
Введите URL
http://127.0.0.1:5000/add
. -
Перейдите на вкладку Body и выберите Form.
-
Добавьте параметры формы:
-
Ключ:
content
, Значение:Тестовая задача
-
Ключ:
date
, Значение:25.12.2023
-
Ключ:
time
, Значение:12:00
-
-
Нажмите кнопку «Send» для отправки запроса.
-
Проверьте, что сервер отвечает статусом 302 Found, что означает перенаправление.
-
Откройте браузер и перейдите по адресу
http://127.0.0.1:5000/add
, чтобы убедиться, что задача добавлена
Откройте браузер и перейдите по адресу http://127.0.0.1:5000/
, чтобы убедиться, что задача добавлена.
Тестирование получения списка задач
-
Метод: GET
-
URL:
http://127.0.0.1:5000/
-
Выберите метод GET.
-
Введите URL
http://127.0.0.1:5000/
. -
Нажмите «Send».
-
Проверьте ответ сервера. Вы должны увидеть HTML-код страницы с добавленной задачей в теле ответа.
Деплой на сервера Amvera
После успешного тестирования нашего Task Manager на локальном сервере, давайте развернём его на удалённом сервере, чтобы он был доступен 24/7 и не зависел от твоего компьютера.
Сервис Amvera мы выбрали, так как он даст нам
-
Бесплатное доменное имя
-
Возможность доставлять обновления тремя командами через git push (что нмного проще настройки классической VPS)
-
Это блог нашей компании, странно было бы выбирать конкурентов)
Регистрация в сервисе Amvera
-
Создаем аккаунт:
-
Переходим на сайт Amvera
-
Нажимаем на кнопку «Регистрация».
-
Подтверждаем почту и телефон.
-
Создание проекта и размещение приложения
-
Создай новый проект:
-
После входа на платформу, на главной странице нажми кнопку «Создать» или «Создать первый!».
-
2.Настройка проекта:
-
Присвоим проекту название (лучше на английском).
-
Выберем тарифный план. Для развертывания бота достаточно самого простого тарифа.
-
Подготовка кода для развертывания:
-
Вам потребуется создать файл конфигурации
amvera.yml
, который подскажет облаку, как запускать ваш проект. -
Для упрощения создания этого файла воспользуйтесь графическим инструментом генерации.
-
Выбор окружения и зависимостей:
-
Укажите версию Python и путь до файла
requirements.txt
, который содержит все необходимые пакеты (можно использовать командуpip freeze для получения списка зависимостей, но она может сгенерировать лишнии зависимости, что замедлит сборку)
. -
Укажите путь до основного файла вашего проекта, например
main.py
.
-
-
Генерация и загрузка файла:
-
Нажмите «Generate YAML» для создания файла
amvera.yml
и загрузите его в корень вашего проекта.
-
Файл конфигурации amvera.yml
служит для того, чтобы платформа Amvera знала, как правильно собрать и запустить ваш проект. Этот файл содержит ключевую информацию об окружении, зависимостях, а также инструкциях для запуска приложения.
Структура файла amvera.yml
:
meta: environment: python toolchain: name: pip version: "3.8" build: requirementsPath: requirements.txt run: scriptName: app.py persistenceMount: /data containerPort: "5000"
Порт в коде и конфигурации должен совпадать, и если у вас используется БД SQLite, обязательно сохраняйте ее в постоянное хранилище /data.
Для того чтобы наш проект корректно работал в среде Amvera, важно указать все необходимые пакеты в файле requirements.txt
. Этот файл определяет все зависимости Python, которые нужны для выполнения кода.
Вот так выглядит наш файл requirements.txt
:
Flask==3.0.3 Flask_SQLAlchemy==3.0.5
Инициализация и отправка проекта в репозиторий:
-
Инициализируйте git репозиторий в корне вашего проекта, если это еще не сделано:
git init
-
Привяжите локальный репозиторий к удаленному на Amvera:
git remote add amvera
-
Добавьте и зафиксируйте изменения:
git add . git commit -m "Initial commit"
-
Отправьте проект в облако:
git push amvera master
Сборка и развертывание проекта:
-
После отправки проекта в систему, на странице проекта статус изменится на «Выполняется сборка». После завершения сборки проект перейдет в стадию «Выполняется развертывание», а затем в статус «Успешно развернуто».
-
Если проект не развернулся, проверьте логи сборки и логи приложения для отладки.
-
Если проект завис на этапе «Сборка», убедитесь в корректности файла
amvera.yml
Мы вместе прошли путь от установки Python и настройки окружения до разработки интерфейса и развёртывания приложения на сервере. Надеюсь, этот процесс был понятным и увлекательным.
Если вам требуется легко развернуть проект на сервере и доставлять в него обновления тремя командами в IDE, зарегистрируйтесь в облаке со встроенным CI/CD Amvera Cloud, и получите 111 руб. на тестирование функционала.
ссылка на оригинал статьи https://habr.com/ru/articles/857704/
Добавить комментарий