Применение фреймворка Flask для создания веб-приложений на Python
Цель урока
Освоить основы создания веб-приложений с использованием микрофреймворка Flask и научиться создавать простые, но функциональные веб-решения.
Теоретическая часть
1. Что такое Flask?
Flask - это микрофреймворк для создания веб-приложений на Python. Он называется "микро", потому что его ядро простое, но расширяемое.
Преимущества Flask:
- Легковесность и простота
- Гибкость и расширяемость
- Встроенный сервер для разработки
- Поддержка модульного тестирования
- Хорошая документация и активное сообщество
2. Установка Flask
pip install flask
3. Базовая структура Flask-приложения
from flask import Flask
# Создание экземпляра приложения
app = Flask(__name__)
# Определение маршрута
@app.route('/')
def home():
return 'Привет, мир!'
# Запуск приложения
if __name__ == '__main__':
app.run(debug=True)
Практическая часть
Проект 1: Простое веб-приложение "Блог"
Шаг 1: Базовая структура
# app.py
from flask import Flask, render_template
app = Flask(__name__)
# Моковые данные (в реальном приложении будет база данных)
posts = [
{
'id': 1,
'title': 'Первая запись',
'content': 'Это содержимое первой записи в блоге',
'author': 'Иван Иванов',
'date': '2024-01-15'
},
{
'id': 2,
'title': 'Вторая запись',
'content': 'Продолжаем вести наш блог',
'author': 'Петр Петров',
'date': '2024-01-16'
}
]
@app.route('/')
def index():
return render_template('index.html', posts=posts)
@app.route('/post/<int:post_id>')
def show_post(post_id):
post = next((p for p in posts if p['id'] == post_id), None)
if post:
return render_template('post.html', post=post)
return 'Запись не найдена', 404
if __name__ == '__main__':
app.run(debug=True)
Шаг 2: Создаем шаблоны
templates/base.html (базовый шаблон):
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Мой Блог{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Главная</a>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
<footer>
<p>© 2024 Мой Блог</p>
</footer>
</body>
</html>
templates/index.html:
{% extends "base.html" %}
{% block title %}Главная страница{% endblock %}
{% block content %}
<h1>Последние записи</h1>
{% for post in posts %}
<article class="post">
<h2>
<a href="{{ url_for('show_post', post_id=post.id) }}">
{{ post.title }}
</a>
</h2>
<p class="meta">
Автор: {{ post.author }} | Дата: {{ post.date }}
</p>
<p>{{ post.content[:100] }}...</p>
</article>
{% endfor %}
{% endblock %}
templates/post.html:
{% extends "base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<article class="post-detail">
<h1>{{ post.title }}</h1>
<p class="meta">
Автор: {{ post.author }} | Дата: {{ post.date }}
</p>
<div class="content">
{{ post.content }}
</div>
<a href="{{ url_for('index') }}">← Назад к списку</a>
</article>
{% endblock %}
Шаг 3: Создаем статические файлы
static/style.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
nav {
background-color: #333;
padding: 1rem;
margin-bottom: 2rem;
}
nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
}
nav a:hover {
background-color: #555;
}
.container {
min-height: 70vh;
}
.post {
border: 1px solid #ddd;
padding: 1.5rem;
margin-bottom: 1.5rem;
border-radius: 5px;
}
.post h2 a {
color: #333;
text-decoration: none;
}
.post h2 a:hover {
color: #007bff;
}
.meta {
color: #666;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.post-detail {
background-color: #f9f9f9;
padding: 2rem;
border-radius: 5px;
}
footer {
text-align: center;
margin-top: 2rem;
padding: 1rem;
border-top: 1px solid #ddd;
}
Проект 2: REST API для управления задачами
# api.py
from flask import Flask, request, jsonify
app = Flask(__name__)
# Хранилище данных (в реальном приложении используйте БД)
tasks = []
next_id = 1
# Вспомогательная функция
def find_task(task_id):
for task in tasks:
if task['id'] == task_id:
return task, tasks.index(task)
return None, -1
# Получить все задачи
@app.route('/api/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
# Получить одну задачу
@app.route('/api/tasks/<int:task_id>', methods=['GET'])
def get_task(task_id):
task, _ = find_task(task_id)
if task:
return jsonify(task)
return jsonify({'error': 'Task not found'}), 404
# Создать новую задачу
@app.route('/api/tasks', methods=['POST'])
def create_task():
global next_id
if not request.json or not 'title' in request.json:
return jsonify({'error': 'Title is required'}), 400
task = {
'id': next_id,
'title': request.json['title'],
'description': request.json.get('description', ''),
'done': False
}
tasks.append(task)
next_id += 1
return jsonify(task), 201
# Обновить задачу
@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
task, index = find_task(task_id)
if not task:
return jsonify({'error': 'Task not found'}), 404
updated_task = {
'id': task_id,
'title': request.json.get('title', task['title']),
'description': request.json.get('description', task['description']),
'done': request.json.get('done', task['done'])
}
tasks[index] = updated_task
return jsonify(updated_task)
# Удалить задачу
@app.route('/api/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
task, index = find_task(task_id)
if not task:
return jsonify({'error': 'Task not found'}), 404
tasks.pop(index)
return jsonify({'result': 'Task deleted'})
if __name__ == '__main__':
app.run(debug=True, port=5001)
Проект 3: Форма с загрузкой файлов
# upload_app.py
import os
from flask import Flask, render_template, request, redirect, url_for
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
app.config['ALLOWED_EXTENSIONS'] = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
# Создаем папку для загрузок, если её нет
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']
@app.route('/')
def index():
# Получаем список загруженных файлов
files = os.listdir(app.config['UPLOAD_FOLDER'])
return render_template('upload_form.html', files=files)
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('index'))
return 'Файл не разрешен'
if __name__ == '__main__':
app.run(debug=True, port=5002)
templates/upload_form.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Загрузка файлов</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.upload-form {
background: #f5f5f5;
padding: 20px;
border-radius: 5px;
margin-bottom: 30px;
}
.file-list {
margin-top: 20px;
}
.file-item {
padding: 10px;
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<h1>Загрузка файлов</h1>
<div class="upload-form">
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file" required>
<button type="submit">Загрузить</button>
</form>
</div>
<div class="file-list">
<h2>Загруженные файлы:</h2>
{% for file in files %}
<div class="file-item">{{ file }}</div>
{% else %}
<p>Файлы не загружены</p>
{% endfor %}
</div>
</body>
</html>
Продвинутые концепции
1. Использование базы данных (SQLAlchemy)
from flask_sqlalchemy import SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
author = db.Column(db.String(100), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
def to_dict(self):
return {
'id': self.id,
'title': self.title,
'content': self.content,
'author': self.author,
'created_at': self.created_at.isoformat()
}
2. Аутентификация пользователей
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
login_manager = LoginManager(app)
login_manager.login_view = 'login'
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
password_hash = db.Column(db.String(128))
3. Обработка ошибок
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
Домашнее задание
Задание 1: Расширьте блог
Добавьте в блог следующие функции:
- Форму для создания новых записей
- Возможность редактирования и удаления записей
- Поиск по записям
- Пагинацию (по 5 записей на странице)
Задание 2: Создайте API для блога
Создайте REST API для блога со следующими endpoint'ами:
- GET /api/posts - получить все записи
- GET /api/posts/<id> - получить конкретную запись
- POST /api/posts - создать новую запись
- PUT /api/posts/<id> - обновить запись
- DELETE /api/posts/<id> - удалить запись
Задание 3: Добавьте аутентификацию
Реализуйте систему регистрации и входа пользователей для API, используя JWT-токены.
Полезные ресурсы
- Официальная документация Flask: https://flask.palletsprojects.com/
- Flask Mega-Tutorial (Miguel Grinberg): https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
- Flask на Habr: https://habr.com/ru/hub/flask/
Заключение
Flask - это мощный и гибкий инструмент для создания веб-приложений на Python. Он позволяет начать с простых проектов и постепенно добавлять сложность по мере необходимости. Ключевые преимущества Flask - его простота и расширяемость, что делает его отличным выбором как для начинающих, так и для опытных разработчиков.