Сообщить об ошибке.

Представления в веб-приложении на Flask Python

Содержание:


Функции-представления приложения Flask.

Функции-представления в приложении 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.

Фреймворк 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'))

HTTP-методы в качестве атрибута класса-представления.

Подключаемые классы-представления прикрепляются к приложению как обычная функция, с помощью метода 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'))

Диспетчеризация на основе HTTP-методов во Flask.

Для создания 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.

При использовании пользовательских декораторов при разработке приложения на 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, с точки зрения вызывающего, нельзя использовать обычные декораторы для отдельных методов-представлений.

Использование методов-представлений класса для создания RESTful API.

Веб-API часто очень тесно работают с HTTP-методами, следовательно имеет смысл реализовать такой API на основе MethodView (методов-представлений). Дополнительно для API потребуются разные URL-правила, которые могут применяться к одному и тому же методу-представлению. Например, HTTP-методы и URL-маршруты для объекта пользователя для веб-API:

URLHTTP-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')