Функции-представления в приложении Flask представляют собой обычные функции Python, которые обязательно должны возвращать либо строку, либо кортежи следующих видов:
(response, status, headers)
;(response, headers)
;(response, status)
.Где переменные кортежей представляют следующие значения:
response
- это строка, представляющая собой тело ответа (обычно создается с помощью функции flask.render_template()
).status
- это код ответа/состояния HTTP, который может быть указан в виде целого числа или строки.headers
- это словарь со значениями заголовков ответа сервера клиенту на отправленный запрос.Например:
@app.route('/') def index(): text = 'Index Page' code = 500 return (f'<h1>{text}</h1>', code, {'Content-Type': 'text/plain'}) # или @app.route('/hello/<name>') def hello(name): return render_template('index.html', context=name) # или @app.route('/error') def error_500(): error = 'Server error' code = 500 return (f'<h1>{error}</h1>', code)
Обратите внимание, что функции-представления не умеют возвращать готовый объект ответа Response
. Объект ответа автоматически генерируется методом объекта приложения app.make_response()
из возвращаемого значения функции-представления.
Обычно, сверху, у представлений, присутствует декоратор @app.route()
, который определяет URL-адрес, для которого эта функция возвращает контент/тело ответа.
Функции-представления могут не иметь декоратора @app.route()
, в этом случае URL-адрес для нее определяет метод экземпляра приложения app.add_url_rule()
, где имя функции-представления передаётся в качестве аргумента view_func
.
def index(): return render_template('index.html', context='Привет Мир') app.add_url_rule('/', view_func=index)
Необходимо отметить, что декоратор @app.route()
представляет собой ссылку на вызов метода app.add_url_rule()
, следовательно аргументы, передаваемые в декоратор и метод совпадают.
Функция-представление, передаваемая в метод app.add_url_rule()
может иметь атрибуты, используемые методом для настройки своего поведения.
def index(): if request.method == 'OPTIONS': # обработка пользовательских параметров ... return render_template('index.html', context='Привет Мир') index.provide_automatic_options = False index.methods = ['GET', 'OPTIONS'] app.add_url_rule('/', view_func=index)
Дополнительно смотрите материал "Декоратор @route()
и метод add_url_rule()
приложения Flask".
Функция-представление может иметь атрибуты, которые автоматически будут передаваться в качестве аргументов методу app.add_url_rule()
:
__name__
: имя функции, по умолчанию используется как конечная точка endpoint
. Если конечная точка указана явно, то используется это значение. Кроме того, по умолчанию перед ним будет стоять префикс имени схемы blueprint
, который не может быть изменен из самой функции.methods
: если аргумент methods
не указан, то Flask будет искать атрибут methods
у функции-представления и если атрибут существует, то автоматически добавит указанные в нем HTTP-методы. provide_automatic_options
: если функция-представление имеет этот атрибут, то Flask принудительно включит или отключит автоматическую реализацию ответа HTTP OPTIONS
. Это может быть полезно при работе с декораторами, которые настраивают ответ OPTIONS
для каждого представления.required_methods
: если функция-представление имеет этот атрибут, то Flask всегда будет добавлять указанные методы при регистрации правила URL, даже если методы явно переопределены в декораторе @app.route()
.Фреймворк Flask представляет подключаемые представления, вдохновленные представлениями в Django, которые основаны на классах, а не на функциях. Основная цель состоит в том, чтобы можно было менять части реализаций и, таким образом, иметь настраиваемые подключаемые представления.
Предположим, есть функция, которая загружает список объектов из базы данных и отображает их в шаблоне:
@app.route('/users/') def show_users(page): # загрузка пользователей из базы users = User.query.all() # вывод пользователей return render_template('users.html', users=users)
Код выше прост и гибок, но если предоставить эту функцию-представление в общем виде, которое можно адаптировать к другим моделям и шаблонам, то гибкости обычной функции недостаточно. Здесь и появляются подключаемые представления на основе классов. В качестве первого шага для преобразования функции-представления в представление на основе классов необходимо сделать следующее:
from flask.views import View class ShowUsers(View): def dispatch_request(self): # загрузка пользователей из базы users = User.query.all() # вывод пользователей return render_template('users.html', objects=users) # прикрепляем класс-представление к URL-адресу app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))
Из кода видно, что необходимо создать подкласс flask.views.View
и реализовать метод dispatch_request()
. Затем нужно преобразовать этот класс в функцию-представление с помощью метода класса ShowUsers.as_view()
. Строка, которая передается этому методу - это имя конечной точки endpoint
, которую будет иметь это представление.
Немного реорганизуем код:
from flask.views import View class ListView(View): def get_template_name(self): raise NotImplementedError() def render_template(self, context): return render_template(self.get_template_name(), **context) def dispatch_request(self): context = {'objects': self.get_objects()} return self.render_template(context) class UserView(ListView): def get_template_name(self): return 'users.html' def get_objects(self): return User.query.all()
Приведенный код избыточен для такого небольшого примера, но он достаточен, чтобы объяснить основной принцип. Когда имеется класс-представление, то возникает вопрос, на что указывает self
. Код работает так: всякий раз, когда отправляется запрос, создается новый экземпляр класса и вызывается метод dispatch_request()
с параметрами из URL-адреса. Сам класс создается с параметрами, переданными в метод .as_view()
. Теперь, можно написать такой класс:
class RenderTemplateView(View): def __init__(self, template_name): self.template_name = template_name def dispatch_request(self): return render_template(self.template_name)
И тогда зарегистрировать это представление можно так:
app.add_url_rule('/about', view_func=RenderTemplateView.as_view( 'about_page', template_name='about.html'))
Подключаемые классы-представления прикрепляются к приложению как обычная функция, с помощью метода app.add_url_rule()
. Это означает, что нужно указать поддерживаемые представлением HTTP-методы. Чтобы поместить информацию о HTTP-методах в класс, можно указать их в качестве атрибута класса:
class MyView(View): methods = ['GET', 'POST'] def dispatch_request(self): if request.method == 'POST': ... ... app.add_url_rule('/myview', view_func=MyView.as_view('myview'))
Для создания RESTful API полезно выполнять разные методы в каком то классе-представлении для каждого HTTP-метода. Это легко сделать с помощью класса flask.views.MethodView
. Например, каждый HTTP-метод будет сопоставляется с методом класса-представления с тем же именем (только в нижнем регистре):
from flask.views import MethodView class UserAPI(MethodView): def get(self): users = User.query.all() ... def post(self): user = User.from_form_data(request.form) ... app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))
Таким образом, нет необходимости указывать атрибут methods = ['GET', 'POST']
в классе-представлении. Он автоматически устанавливается на основе методов, определенных в классе.
При использовании пользовательских декораторов при разработке приложения на Flask, нет смысла декорировать сам класс, так как класс-представление не является функцией-представлением. Следовательно нужно вручную передать возвращаемое значение UserAPI.as_view()
в пользовательский декоратор:
def user_required(f): """Декоратор проверки входа пользователя в систему.""" def decorator(*args, **kwargs): if not g.user: abort(401) return f(*args, **kwargs) return decorator # применяем декоратор `user_required()` user_api = user_required(UserAPI.as_view('users')) # регистрируем в системе маршрутизации app.add_url_rule('/users/', view_func=user_api)
Начиная с Flask 0.8 существует альтернативный способ, в котором можно указать список декораторов для применения в объявлении класса:
class UserAPI(MethodView): decorators = [user_required]
Имейте ввиду, что из-за неявного self
, с точки зрения вызывающего, нельзя использовать обычные декораторы для отдельных методов-представлений.
Веб-API часто очень тесно работают с HTTP-методами, следовательно имеет смысл реализовать такой API на основе MethodView
(методов-представлений). Дополнительно для API потребуются разные URL-правила, которые могут применяться к одному и тому же методу-представлению. Например, HTTP-методы и URL-маршруты для объекта пользователя для веб-API:
URL | HTTP-method | Что делает |
/users/ | GET | Дает список всех пользователей |
/users/ | POST | Создает нового пользователя |
/users/ | GET | Показывает одного пользователя |
/users/ | PUT | Обновляет одного пользователя |
/users/ | DELETE | Удаляет одного пользователя |
Как это можно сделать с помощью MethodView
? Решение состоит в том, что бы предоставить несколько правил для одного и того же метода-представления.
Предположим, что методы-представления в классе будут выглядеть так:
class UserAPI(MethodView): def get(self, user_id): if user_id is None: # список пользователей pass else: # один пользователь pass def post(self): # создать одного пользователя pass def delete(self, user_id): # удалить одного пользователя pass def put(self, user_id): # изменить одного пользователя pass
Теперь, при регистрации маршрутов добавим два правила и явно укажем методы для каждого:
user_view = UserAPI.as_view('user_api') app.add_url_rule('/users/', defaults={'user_id': None}, view_func=user_view, methods=['GET',]) app.add_url_rule('/users/', view_func=user_view, methods=['POST',]) app.add_url_rule('/users/<int:user_id>', view_func=user_view, methods=['GET', 'PUT', 'DELETE'])
Если имеется много похожих API, то можно реорганизовать этот код следующим образом:
def register_api(view, endpoint, url, pk='id', pk_type='int'): view_func = view.as_view(endpoint) app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) app.add_url_rule(f'{url}<{pk_type}:{pk}>', view_func=view_func, methods=['GET', 'PUT', 'DELETE']) register_api(UserAPI, 'user_api', '/users/', pk='user_id')