Source code for feretui.resources.resource

# This file is a part of the FeretUI project
#
#    Copyright (C) 2024 Jean-Sebastien SUZANNE <js.suzanne@gmail.com>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
"""Module feretui.resource.

The main class to create a resource.
"""

import inspect
from collections.abc import Callable
from copy import deepcopy
from typing import TYPE_CHECKING

from markupsafe import Markup
from polib import POFile

from feretui.exceptions import ResourceError
from feretui.helper import (
    action_for_authenticated_user,
    menu_for_authenticated_user,
    page_for_authenticated_user_or_goto,
)
from feretui.menus import Menu, ToolBarMenu
from feretui.pages import login, page_404
from feretui.request import Request
from feretui.resources.view import View
from feretui.response import Response
from feretui.session import Session

if TYPE_CHECKING:
    from feretui.feretui import FeretUI
    from feretui.translation import Translation


[docs] class Resource: """Resource class.""" code: str = None label: str = None menu: Menu = ToolBarMenu("", page="resource") menu_visibility: Callable = staticmethod(menu_for_authenticated_user) page_visibility: Callable = staticmethod( page_for_authenticated_user_or_goto(login), ) action_security: Callable = staticmethod(action_for_authenticated_user) default_view: str = None
[docs] class Form: """The Form class."""
def __init__(self: "Resource") -> None: """Resource class.""" self.views: dict[str, View] = {} def __str__(self: "Resource") -> str: """Return the resource as a string.""" return f"<{self.__class__.__name__} code={self.code}>"
[docs] def export_catalog( self: "Resource", translation: "Translation", po: POFile, ) -> None: """Export the translations in the catalog. :param translation: The translation instance to add also inside it. :type translation: :class:`.Translation` :param po: The catalog instance :type po: PoFile_ """ po.append(translation.define(f"{self.context}:label", self.label)) for view in self.views.values(): view.export_catalog(translation, po)
[docs] @classmethod def build(cls: "Resource") -> None: """Build the additional part of the resource.""" if not cls.code: raise ResourceError("No code defined") if not cls.label: raise ResourceError("No label defined") cls.menu = deepcopy(cls.menu) if not cls.menu.label: cls.menu.label = cls.label if cls.menu_visibility: cls.menu.visible_callback = cls.menu_visibility cls.menu.querystring["resource"] = cls.code cls.context = f"resource:{cls.code}" resource = cls() for attr in dir(cls): if attr.startswith("MetaView") and inspect.isclass( getattr(cls, attr), ): view = resource.build_view(attr) if view: resource.views[view.code] = view return resource
[docs] def get_meta_view_class(self: "Resource", view_cls_name: str) -> list: """Return all the meta view class. :param view_cls_name: The name of the meta class :type view_cls_name: Class :return: list of the class :rtype: list """ return [ getattr(cls, view_cls_name) for cls in self.__class__.__mro__ if view_cls_name in cls.__dict__ ]
[docs] def build_view( self: "Resource", view_cls_name: str, ) -> View: """Return the view instance in fonction of the MetaView attributes. :param view_cls_name: name of the meta attribute :type view_cls_name: str :return: An instance of the view :rtype: :class:`feretui.resources.view.View` """
[docs] def get_label( self: "Resource", feretui: "FeretUI", session: Session, ) -> str: """Return the translated label. :param feretui: The feretui client :type feretui: :class:`feretui.feretui.FeretUI` :param session: The Session :type session: :class:`feretui.session.Session` :rtype: str. """ return feretui.translation.get( session.lang, f"{self.context}:label", self.label, )
[docs] def render( self: "Resource", feretui: "FeretUI", session: Session, options: dict, # noqa: ARG002 ) -> str: """Render the resource. :param feretui: The feretui client :type feretui: :class:`feretui.feretui.FeretUI` :param session: The Session :type session: :class:`feretui.session.Session` :param options: The options come from the body or the query string :type options: dict :return: The html page in :rtype: str. """ viewcode = options.setdefault("view", self.default_view) if isinstance(viewcode, list): viewcode = viewcode[0] if not viewcode: func = page_404 else: view = self.views.get(viewcode) func = page_404 if not view else view.render if self.page_visibility: func = self.page_visibility(func) return Markup.unescape( feretui.render_template( session, "feretui-page-resource", view=Markup.unescape(func(feretui, session, options)), code=self.code, ), )
[docs] def router( self: "Resource", feretui: "FeretUI", request: Request, ) -> Response: """Resource entry point actions. :param feretui: The feretui client :type feretui: :class:`feretui.feretui.FeretUI` :param request: The request :type request: :class:`feretui.request.Request` :return: The page to display :rtype: :class:`feretui.response.Response` """ qs = request.get_query_string_from_current_url() options = ( request.query if request.method == Request.GET else request.params ) viewcode = qs.get("view") if isinstance(viewcode, list): viewcode = viewcode[0] if not viewcode: raise ResourceError("No view defined in the query string") view = self.views.get(viewcode) if not view: raise ResourceError(f"No view {viewcode} defined in {self}") action = options.get("action") if isinstance(action, list): action = action[0] if not action: raise ResourceError("No action defined in the query string") if not hasattr(view, action): raise ResourceError( f"{self.code} - {viewcode} has no method {action}", ) func = getattr(view, action) if self.action_security: func = self.action_security(func) return func(feretui, request)