Сменные представления (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')

Оригинал этой страницы