Skip to content

Latest commit

 

History

History
169 lines (110 loc) · 17.2 KB

File metadata and controls

169 lines (110 loc) · 17.2 KB

Документация по доработке

Все объекты определяются в директории models, где разбиваются на небольшие, связанные по смыслу файлы. В дальнейшем для включения файла в проект его нужно включить в файл _init_.py - в этом случае все вызовы и обращения к моделям в других модулях будут корректны.

Все маршруты разбиваются на небольшие, связанные по смыслу модули - blueprints в терминологии Flask'а, каждый из которых в дальнейшем вручную добавляется в _init_.py в качестве соответствующего модуля.

В директории helpers хранятся файлы, содержащие функции, в том или ином плане полезные для соответствующих модулей. Здесь в большинстве своём - хаос.

Каждый объект из models имеет атрибут - класс Meta, предназначение которого - хранить информацию, общую для всех объектов. Эта информация содержит обычно следующие поля:

  • verbose_name - наименование класса объекта в человеко-читаемом виде. Пример: Задача;

  • verbose_name_plural - наименование множества объектов данного класса в человеко-читаемом виде. Пример: Задачи;

  • description - текст, отображаемый в панели администрирования, и более подробно описывающий данный класс;

  • title_new - поле, используемое для объектов перечисления в панели администрирования при добавлении нового объекта;

  • icon - иконка данного объекта. Записывается в виде строки, которая будет подставлена в <i class="">;

  • icon_index - иконка объекта, отображаемая на странице перечисления множества объектов (обычно совпадает с icon);

  • column_index - это список имён столбцов, которые будут отображаться в панели администрирования на странице списка текущих объектов;

  • additional_template_variables - это словарь дополнительных переменных, передаваемых в шаблон Jinja2 при отображении страницы добавления/редактирования объекта в панели администрирования;

  • project_permission_actions - словарь разрешений для данного объекта, которые можно проверить и который представляет из себя словарь Ключ : Значение, где Ключ - это "Наименование" разрешения, сохраняемое в базе знаний (неизменяемый объект), а Значение - это "отображаемое" имя разрешения - именно оно будет отображаться в списке разрешений в панели администрирования.

  • bleached_field - список атрибутов данного объекта, для которых необходимо выполнять очистку вводимых пользователем значений от соответствующих тэгов, поскольку они не экранируются на уровне Jinja2 или других точках прослушивания;

  • fulltitle_lazy_string - атрибут, используемый в истории для отображения объектов типа "Многие ко многим", сохранения именно этой строки в json в базе данных, и создания истории именно через этот атрибут.

Панель администрирования

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

Объекты перечисления - это объекты, общие для всех объектов, создаваемых/изменяемых пользователями (проекты, задачи, вики-страницы и т.д.), выбираемые через поле select, и предназначенные для группировки признаков у данных объектов.

Объекты состояния - это объекты, общие для всех объектов, создаваемых/изменяемых пользователями (проекты, задачи, вики-страницы и т.д.), для которых определено условие изменения с одного значения на другое.

Для того, чтобы объект попал к объектам перечисления, его необходимо обернуть в декоратор project_enumerated_object из модуля app.helpers.admin_helpers.

Пример:

from app.helpers.admin_helpers import project_enumerated_object

@project_enumerated_object
class MyObject(db.Model):
    ...

Для того, чтобы для объекта можно было определять условия изменения значений, его необходимо обернуть в декоратор project_status_object из модуля app.helpers.admin_helpers, а также определить атрибут can_switch_to_state в качестве Set от списка объектов того же класса, что и он сам.

Пример:

from app import db
import sqlalchemy.orm as so
from app.helpers.admin_helpers import project_status_object

@project_status_object
class MyObject(db.Model):
    can_switch_to_state: so.Mapped[Set["MyObject"]]
    ...

Роли и права доступа

Для каждого объекта существуют роли - это функции, которые относительно данного объекта и данного пользователя возвращают True или False - обладает ли пользователь данной ролью или нет.

Считается, что администратор обладает всеми возможными ролями - ему доступны все действия. Администратор - это пользователь, для которого флаг is_administrator установлен в True.

Помимо глобальных ролей существуют также и роли на проекте - это просто список объектов класса ProjectRole. Для каждого проекта существует ассоциации между пользователем и ролью на текущем проекте. Для каждого объекта и для каждого действия существуют различные разрешения на текущий объект (create, show и т. д.) - для каждого объекта свой список.

Проверка на возможность роли выполнять определённые действия выполняется через функцию project_role_can_make_action из модуля app.models - эта функция возвращает True или False в зависимости от того, обладает ли текущий пользователь хоть одной ролью, которая может выполнять указанное действие. На вход функция принимает 3 обязательных параметра и один опциональный:

  • current_user - текущий пользователь (можно получить из current_user модуля flask_login)

  • obj - экземпляр текущего объекта, для которого производится проверка;

  • action - проверочное действие;

  • project - текущий проект, для которого выполняется проверка на наличие прав. Можно не передавать, если объект уже связан с проектом.

Пример:

from app.roles import project_role_can_make_action
from flask_login import current_user
import app.models as models
from app import db


@bp.route('/')
@login_required
def test_route()
    project = db.get_or_404(models.Project, 1)
    if project_role_can_make_action(current_user, models.MyModel(), 'create', project=project):
        ...

Также для эндпоинтов, доступ к которым может быть только у администратора, существует специальный декоратор administrator_only из модуля app.roles. Этот декоратор должен вызываться после login_required, иначе для неаутентифицированного пользователя вместо страницы аутентификации мы увидим ошибку 403.

Сессии

Всего у объекта db из модуля app существует 2 сессии: session и technical_session.

Важно помнить, что у обоих сессий отключён параметр autoflush - это сделано для того, чтобы создавать элементы истории перед коммитом базы данных. Причём коммит сессии technical_session вызывается сразу после коммита session. Эта сессия нужна для добавления каких-либо атрибутов после их добавления и коммита в основную сессию. Например, для создания объектов типа "Пользовательское уведомление" сразу после создания объекта (во время создания - ссылка на объект отсутствует, поскольку ID ещё не присвоен).

Объекты, соглашения об именовании

Все объекты, сохраняемые в базе данных, соответствуют декларативному стилю SQLAlchemy, и должны наследоваться от декларативной модели db.Model.

Каждая модель должна иметь атрибуты:

  • id - тип - integer, кроме того, имеет primary_key=True опцию;

  • title - объект обязателен не всегда, но почти всегда именно его выбирают в случае, если другая модель на него ссылается, или же происходит их поиск в базе данных через плагин bootstrap-table. Возможно создание title через декоратор hybrid_property в SQLAlchemy.

Дополнительно, если модель ссылается на другую модель (через связь Один-к-Одному или Один-ко-Многим), то имя первичного атрибута (column или mapped_column) на стороне "Многие" должен заканчиваться на _id, и совпадать с именем отношения (relationship). Это требуется, в том числе, для корректного отображения изменений в истории, поиска по БД, сохранения формы в объект, редактировании в панели администратора и т.д.

Также каждый атрибут, который так или иначе будет изменяться, и для которого будет сохраняться история, должен иметь атрибут info, задающийся словарём, с ключом label и значением - именем соответствующего атрибута.

Пример:

from app import db
import sqlalchemy as sa
import sqlalchemy.orm as so
from flask_babel import lazy_gettext as _l

class MyModel(db.Model):
    id: so.Mapped[int] = so.mapped_column(primary_key=True, info={'label': _l('ID')})
    title: so.Mapped[str] = so.mapped_column(sa.String(50), info={'label': _l("Title")})
    relationship_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey('relation.id', ondelete='CASCADE'), info={'label': _l("My relation")})
    relationship: so.Mapped["Relation"] = so.mapped_column(lazy='select', info={'label': _l("My relation")}, backref=so.backref("my backref", info={'label': _l("backref to relationship")}))

Объекты перечислениия, объекты состояния

Объекты, общие для всех проектов, и на которые ссылаются другие объекты (такие как, например, трекер задачи, приоритет задачи, и т.д.) называются объектами перечисления. Список этих объектов редактируется в панели администратора во вкладке "Объекты перечисления". Эти объекты обязаны иметь атрибуты id и title, что требуется для работы большинства плагинов, включая, например, историю событий.

Также такой объект должен иметь класс Meta в качестве объекта, в котором определяются следующие атрибуты:

  • verbose_name - собственно имя объекта перечисления (например, "Приоритет задачи");
  • verbose_name_plural - имя множества объектов перечисления (например, "Приоритеты задач");
  • title_new - заголовок добавления нового объекта (например, "Добавить новый приоритет задач");
  • column_index - список столбцов, по которым будет производиться поиск при запросе через плагин bootstrap-table. Можно добавлять все колонки.

Класс Meta может также иметь атрибут description, который будет отображаться в панели администратора напротив объекта перечисления.

Для добавления класса в качестве объекта перечисления необходимо обернуть его декоратором @project_enumerated_object:

@project_enumerated_object
class MyEnumeratedObject(db.Model):
    id: so.Mapped[ID]
    title: so.Mapped[str] = so.mapped_column(sa.String(50), info={'label': _l("Title")})

    class Meta:
        verbose_name = _l("My Enumerated Object")
        verbose_name_plural = _l("My Enumerated Objects")
        title_new = _l("Add new My Enumerated Objects")
        description = _l("My Awesome Enumerated Object")
        column_index = ['id', 'title']

Объекты состояния - это объекты, общие для всех проектов, для которых определены условия изменения состояния на другое значение. Хорошим примером объекта состояния являются статусы задачи: Задача со статусом "Новая" может перейти в статус "В работе", но не может перейти в статус "Разрешена". Задача со статусом "В работе" может перейти в статус "Разрешена", "Отменена", "Приостановлена", но не может перейти в статус "Новая".

Для каждого объекта состояния необходимо вручную определять таблицу связи Многие-ко-Многим, а также атрибуты, по которым будет проходить эта связь...

Формы, автозаполнение форм