Сменные представления (Pluggable Views)¶
Добавлено в версии 0.7.
В Flask 0.7 появились сменные представления, идея которых взята из обычных представлений (generic views) 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)
app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))
Как видите, вам необходимо создать подкласс класса flask.views.View
и реализовать dispatch_request()
. Далее нам надо
преобразовать этот класс в функцию актуального представления с использованием
метода класса as_view()
. Строка, которую вы передаёте
в эту функцию - это имя конечной точки, к которой придёт представление. Однако
само по себе это бесполезно, так что давайте немного переработаем код:
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'))
Подсказки метода¶
Сменные представления присоединяются к приложению подобно обычной функции
с помощью использования route()
или лучше
add_url_rule()
. Однако это будет также означать, что
при их присоединении вам придётся предоставить имена HTTP-методов,
поддерживаемых представлением. Чтобы переместить эту информацию в класс,
вам надо предоставить атрибут methods
, который
содержит следующую информацию:
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
.
Он будет автоматически установлен исходя из определённых в классе методов.
Декорирующие представления¶
Так как сам по себе класс предcтавления не является функцией представления,
которая добавляется в подсистему маршрутизации, не имеет особого смысла
декорировать сам класс. Вместо этого вы должны вручную декорировать
возвращаемое as_view()
значение:
def user_required(f):
"""Checks whether user is logged in or raises error 401."""
def decorator(*args, **kwargs):
if not g.user:
abort(401)
return f(*args, **kwargs)
return decorator
view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)
Начиная с Flask 0.8 есть альтернативный способ, при котором вы можете указать список декораторов, которые необходимо применить при декорировании класса:
class UserAPI(MethodView):
decorators = [user_required]
В связи с неявным self с точки зрения вызывающего объекта, вы не можете использовать для индивидуальных методов обычные декораторы представления, однако, имейте это ввиду.
Представления метода для различных API¶
Веб-API часто работают очень близко с методами HTTP, так что есть большой
смысл реализовывать такие API на базе MethodView
.
Тем не менее вы заметите, что API требуют различные URL-правила, которые
чаще всего передавались тому же методу представления. Рассмотрим для
примера, что вы делаете доступным через web объект пользователя (user):
URL | Method | Description |
/users/ |
GET |
Вывести список всех пользователей |
/users/ |
POST |
Создать нового пользователя |
/users/<id> |
GET |
Показать одного пользователя |
/users/<id> |
PUT |
Обновить одного пользователя |
/users/<id> |
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('%s<%s:%s>' % (url, pk_type, pk), view_func=view_func,
methods=['GET', 'PUT', 'DELETE'])
register_api(UserAPI, 'user_api', '/users/', pk='user_id')