Простое использование

Класс Action и его использование

Пример экшена, который читает даннные из POST’а. Проверяет значение C и сохраняет в БД значения A и B. Код:

from m3.ui.actions import Action
from m3.ui.actions.results import OperationResult
...

class MyFirstAction(Action):
    url = 'save'
    verbose_name = u'Сохранение данных'
    short_name = 'my-first-action-alias'

    def pre_run(self, request, context):
        """ pre_run удобно использовать для начальных проверок """
        value = request.POST.get('C')
        if value < 0:
            # Обработка запроса на этом прерывается
            return OperationResult(success=False, message=u'Недопустимое значение')

    def run(self, request, context):
        """ Основная работа экшена производится здесь """
        a = request.POST.get('A')
        b = request.POST.get('B')
        SomeModel.objects.create(a=a, b=b)
        return OperationResult(message=u'Данные сохранены')

OperationResult это один из возможных результатов работы экшена, который автоматически преобразуется в Django HttpResponse внутри контроллера. В нашем случае класс OperationResult указывает успешно ли завершилась операция success и несет соответствующее сообщение message.

Класс ActionPack и его использование

Экшены и паки входящие внутрь нашего пака задаются с помощью атрибутов actions и subpacks. Пример:

from m3.ui.actions import ActionPack

class MyFormActionPack(ActionPack):
    def __init__(self):
        super(MyFormActionPack, self).__init__()
        self.actions.extend([
            GetWindowAction, GetDataAction(), SaveAction, DeleteAction()
        ])
        self.subpacks.append( InnerActionPack )

class InnerActionPack(ActionPack):
    url = 'inner'
    def __init__(self):
        super(InnerActionPack, self).__init__()
        self.actions.extend([
            SubAction1, SubAction2, ...
        ])

Обратите внимание, что в список actions можно добавлять как классы, так и экземпляры экшенов. На самом деле это не имеет значения, т.к. контроллер при инициализации автоматически создает экземпляры экшенов и паков, а также дополняет их специальными атрибутами. Об этом в главе Класс ActionController и его использование

Часто бывает необходимо получить прямой доступ к экшенам внутри пака через атрибуты. Живой пример из М3:

class BaseDictionaryActions(ActionPack):
    def __init__(self):
        super(BaseDictionaryActions, self).__init__()
        self.list_window_action   = DictListWindowAction()
        self.select_window_action = DictSelectWindowAction()
        self.edit_window_action   = DictEditWindowAction()
        ...
        self.actions = [
            self.list_window_action,
            self.select_window_action,
            self.edit_window_action,
            ...
        ]

Класс ActionController и его использование

Определение контроллера состоит из нескольких этапов:

  1. Экземпляр контроллера как правило создается в файле app_meta.py внутри приложения.
  2. Чтобы передавать в него запросы Django нужно создать вьюшку контроллера dict_view и зарегистрировать url pattern для неё в методе register_urlpatterns. Он вызывается автоматически для всех приложений.
  3. Регистрация паков в контроллере производится методом register_actions.

Пример файла app_meta.py внутри приложения dicts:

# Именованный экземпляр контроллера
dict_controller = ActionController(url='/core-dicts', name=u'Справочники')

def register_urlpatterns():
    """ Регистрация вьюшки контроллера """
    return urls.defaults.patterns('',
            (r'^core-dicts/', 'mis.core.dicts.app_meta.dict_view'),
        )

    def dict_view(request):
        """ Вьюшка контроллера """
        return dict_controller.process_request(request)

    def register_actions():
        """ Регистрация паков в контроллере """
        dict_controller.packs.extend([
            MyFormActionPack,
            MyDictionaryActions
        ])

При добавлении пака в контроллер вызывается метод _build_pack_node, который создает экземпляр пака и всех вложенных в него экшенов и паков. Заполняются служебные атрибуты, с помощью которых можно обходить дерево:

  • parent - ссылка на родительский пак
  • controller - ссылка на родительский контроллер

Чаще всего бывает нужно найти экшен или пак в уже сформированной иерархии. Для этого есть несколько методов:

  • По известному адресу экшен можно найти методом get_action_by_url
  • Пак по имени или классу можно найти методом find_pack
  • Рекомендуется искать экшены и паки по короткому имени (псевдониму). Для него есть атрибут short_name.

Пример:

from m3.helpers import urls

my_action = urls.get_action('my-action-alias')
my_pack = urls.get_pack('my-pack-alias')

Контекст запросов в М3 (m3.actions.context)

В Django параметры запросов передаются с помощью класса HttpRequest в трех словарях POST, GET и REQUEST. Извлекаемые из них значения не проверяются и не конвертируются в сложные типы Python. Ещё один минус, что извлечение, как правило, делают внутри кода вьюшки, таким образом загромождается место под бизнес логику.

В М3 был разработан механизм контекста, который позволяет:

  • Автоматически извлекать значения из запроса по заранее заданным правилам.
  • Преобразовывать сырое значение в указанный в правилах тип.
  • Передавать контекст в ответе к визуальным компонентам.

Определение контекста в экшенах

Правила извлечения контекста задаются внутри метода context_declaration экшена. Представляют собой список экземпляров класса ActionContextDeclaration или его упрощенное написание ACD. Так же можно использовать декларативное описание по определенной схеме

Action.context_declaration()

Метод объявления необходимости наличия определенных параметров в контексте.

Должен возвращать список из экземпляров ActionContextDeclaration либо словарь описания контекста для DeclarativeActionContext

Результат:описание необходимости наличия определенных параметров в запросе
Тип результата:list of m3_core.actions.context.ActionContextDeclaration либо m3_core.actions.context.DeclarativeActionContext
class m3.actions.context.ActionContextDeclaration(name='', default=None, type=None, required=False, verbose_name='', *args, **kwargs)

Класс, который определяет правило извлечения параметра из запроса и необходимость его наличия в объекте контекста ActionContext.

Параметры:
  • name (str) – имя параметра
  • type – тип извлекаемого значения
  • required (bool) – указывает что параметр обязательный
  • default – значение параметра по умолчанию, используется если его нет в запросе, но наличие обязательно
  • verbose_name (unicode) – человеческое имя параметра, необходимо для сообщений об ошибках
human_name()

Возвращает человеческое название параметра verbose_name

Пример определения правил:

from m3.actions.context import ActionContextDeclaration, ACD
...

class GetRowsAction(Action):
    def context_declaration(self):
            return [
                ActionContextDeclaration(name='id', type=int, required=True, verbose_name=u'Идентификатор модели'),
                ActionContextDeclaration(name='start', type=int, required=True),
                ActionContextDeclaration(name='limit', type=int, required=True)
            ]
        ...

class GetRowsAction(Action):
    url = 'save'

    def context_declaration(self):
        # декларативное опимание контекста
        return {
            # параметр запроса -> параметры разбора
            'ids': {
                # значение по умолчанию,
                # используется при отсутствии параметра в запросе
                # если не указано - параметр считается обязательным
                'default': '',

                # тип парсера, может быть:
                # - строкой - именем одного из предопределенных парсеров
                # - callable-объектом, выполняющим парсинг. Такой объект
                #   может возбуждать ValueError/TypeError/KeyError/IndexError
                #   в случае неправильного формата данных, что позволяет
                #   использовать в качестве парсера что-то вроде:
                #     'type': ['on', 'yes'].__contains__
                #     'type': {1: 'Male', 2: 'Female'}.get
                #     'type': float
                #     'type': json.loads
                'type': int

                # наименование параметра, понятное пользователю
                # используется в сообщениях об ошибках
                'verbose_name': u'Идентификатор объекта'
            },
            #
            'date': {
                'type': datetime.date,
                'verbose_name': u'Дата начала действия изменений'
            },
            'comment': {
                'type': str
            }
        }

    @transaction.commit_on_success
    def run(self, request, context):
        if not hasattr(context, 'comment'):
            context.comment = u'Без комментариев'

        for id in context.ids:
            SomeModel.objects.create(pid=id, comment=context.comment)

        msg = u'Изменения внесены на дату %s' % context.date
        return OperationResult(message=msg)

Разберем правило:

ACD(name='date', required=True, type=datetime.date, verbose_name=u'Дата начала действия изменений')

Из словаря REQUEST запроса HttpRequest будет извлечено значение с именем “date” и приведено к типу datetime.date. Если его нет в REQUEST, то до run() управление не дойдет и будет сгенерировано исключение RequiredFailed.

Разберем правило:

ACD(name='comment', type=str)

Из словаря REQUEST запроса HttpRequest будет извлечено значение с именем “comment”. Если его нет в REQUEST, то соответствующий атрибут не будет добавлен в context, т.к. required=False по умолчанию. Управление будет передано в run().

Возможные результаты работы экшенов (m3.actions.results)

В Django в качестве конечного ответа вьюшек используется класс HttpResponse. В М3, при работе с экшенами и паками, используются более высокие абстракции - класс производные от ActionResult.

Главные отличия ActionResult от HttpResponse:

  • ActionResult используется для хранения и трансформации ответа приложения на запрос. HttpResponse же напротив является готовым ответом и содержит в себе данные специфичные для протокола http, например status_code и cookie. Которые не нужны при написании бизнес-логики.
  • Тип ответа в ActionResult определяется классом и интерфейсом им предоставляемым, а в HttpResponse только mimetype.
  • ActionResult поддерживает передачу контекста
  • ActionResult преобразуется в HttpResponse после обработки запроса в контроллере с помощью метода get_http_response.

Благодаря этим особенностям в процессе обработки запроса можно изменять и даже подменять ответы. Например, подключенный плагин может модифицировать форму, передаваемую через ExtUIScriptResult, добавив в нее новые контролы.

class m3.actions.results.ActionResult(data=None, http_params={})
get_http_response()
Результат:соответствующий данному результату выполнения действия ответ
Тип результата:django.http.HttpResponse
process_http_params(response)

Добавляет параметры http в ответ

Параметры:response (наследник m3_core.actions.results.ActionResult) – ответ, в который добавляются параметры
Результат:http-ответ с добавленными параметрами
Тип результата:наследник m3_core.actions.results.ActionResult

От него наследуется более сложный базовый класс BaseContextedResult, который может передавать контекст в визуальные компоненты и формы.

class m3.actions.results.BaseContextedResult(data=None, context=None, http_params={})

Абстрактный базовый класс, который оперирует понятием результата выполнения операции, ‘отягощенного некоторым контектом’

Простые обёртки над HttpResponse

class m3.actions.results.HttpReadyResult(data=None, http_params={})

Результат выполнения операции в виде готового HttpResponse. Для данного класса в data храниться объект класса HttpResponse.

class m3.actions.results.TextResult(data=None, http_params={})

Результат, данные data которого напрямую передаются в HttpResponse

class m3.actions.results.XMLResult(data=None, http_params={})

Результат в формате xml, данные которого напрямую передаются в HttpResponse

Ответы передающие JSON

Предназначены для работы с JSON и готовыми к JSON-сериализации данными

class m3.actions.results.JsonResult(data=None, http_params={})

Результат выполнения операции в виде готового JSON объекта для возврата в response. Для данного класса в data храниться строка с данными JSON объекта.

class m3.actions.results.PreJsonResult(data=None, secret_values=False, dict_list=None)

Результат выполнения операции в виде, например, списка объектов, готовых к сериализации в JSON формат и отправке в HttpResponse. В data передается объект для сериализации. В dict_list указывается список объектов и/или атрибутов вложенных объектов для более глубокой сериализации. Смотри класс т3.core.json.M3JSONEncoder. Параметр специфичный для проекта secret_values - используется чтобы указать, что передаются персональные обезличенные данные и их расшифровать перед отправкой клиенту.

Результат выполнения операции

class m3.actions.results.OperationResult(success=True, code='', message='', *args, **kwargs)

Результат выполнения операции, описанный в виде Ajax результата ExtJS: success или failure. В случае если операция выполнена успешно, параметр success должен быть True, иначе False.

Параметры:
  • success (boolean) – флаг успеха операции
  • message (unicode) – сообщение, поясняющее результат выполнения операции.
  • code (unicode) – текст javascript, который будет выполнен на клиенте в результате обработки результата операции.
static by_message(message)

Возвращает экземпляр OperationResult построенный исходя из сообщения message. Если сообщение не пустое, то операция считается проваленной и success=False, иначе операция считается успешной success=True.

Параметры:message (unicode) – текст сообщения об ошибке, или не указан
get_http_response()

Возвращает объект HttpResponse, соответствующий данному результату выполнения операции

Результат:http-ответ, соответствующий данному результату
Тип результата:django.http.HttpResponse