示例#1
0
class Module(metaclass=ModuleMeta):
    id: str = None
    endpoint: str = None
    label: str = None
    managed_class: type = None
    list_view = None
    list_view_columns: List[Dict[str, Any]] = []
    single_view = None
    components: Tuple = ()

    # class based views. If not provided will be automaticaly created from
    # EntityView etc defined below
    base_template = "base.html"
    view_cls = EntityView
    edit_cls = EntityEdit
    create_cls = EntityCreate
    delete_cls = EntityDelete
    json_search_cls = JSONWhooshSearch
    JSON2_SEARCH_LENGTH = 50

    # form_class. Used when view_cls/edit_cls are not provided
    edit_form_class = None
    view_form_class = None  # by default, same as edit_form_class

    url = None
    name = None
    view_new_save_and_add = False  # show 'save and add new' button in /new form
    static_folder = None
    view_template = None
    view_options = None
    related_views: List["RelatedView"] = []
    blueprint = None
    search_criterions = (
        search.TextSearchCriterion("name", attributes=("name", "nom")),
    )
    # used mostly to change datatable search_label
    tableview_options = {}  # type: ignore
    _urls: List[Tuple] = []

    def __init__(self) -> None:
        # If endpoint name is not provided, get it from the class name
        if self.endpoint is None:
            class_name = self.__class__.__name__
            if class_name.endswith("Module"):
                class_name = class_name[0 : -len("Module")]
            self.endpoint = class_name.lower()

        if self.label is None:
            self.label = labelize(self.endpoint)

        if self.id is None:
            self.id = self.managed_class.__name__.lower()

        # If name is not provided, use capitalized endpoint name
        if self.name is None:
            self.name = self._prettify_name(self.__class__.__name__)

        if self.view_options is None:
            self.view_options = {}

        # self.single_view = make_single_view(self.edit_form_class,
        #                                     view_template=self.view_template,
        #                                     **self.view_options)
        if self.view_form_class is None:
            self.view_form_class = self.edit_form_class

        # init class based views
        kw = {
            "Model": self.managed_class,
            "pk": "entity_id",
            "module": self,
            "base_template": self.base_template,
        }
        self._setup_view(
            "/<int:entity_id>",
            "entity_view",
            self.view_cls,
            Form=self.view_form_class,
            **kw,
        )
        view_endpoint = self.endpoint + ".entity_view"

        self._setup_view(
            "/<int:entity_id>/edit",
            "entity_edit",
            self.edit_cls,
            Form=self.edit_form_class,
            view_endpoint=view_endpoint,
            **kw,
        )

        self._setup_view(
            "/new",
            "entity_new",
            self.create_cls,
            Form=self.edit_form_class,
            chain_create_allowed=self.view_new_save_and_add,
            view_endpoint=view_endpoint,
            **kw,
        )

        self._setup_view(
            "/<int:entity_id>/delete",
            "entity_delete",
            self.delete_cls,
            Form=self.edit_form_class,
            view_endpoint=view_endpoint,
            **kw,
        )

        self._setup_view("/json", "list_json", ListJson, module=self)

        self._setup_view(
            "/json_search",
            "json_search",
            self.json_search_cls,
            Model=self.managed_class,
        )

        self.init_related_views()

        # copy criterions instances; without that they may be shared by
        # subclasses
        self.search_criterions = copy.deepcopy(self.__class__.search_criterions)

        for sc in self.search_criterions:
            sc.model = self.managed_class

        self.__components = {}
        for component in self.components:
            component.init_module(self)
            self.__components[component.name] = component

    def get_component(self, name):
        return self.__components.get(name)

    def _setup_view(self, url: str, attr: str, cls: Any, *args, **kwargs) -> None:
        """Register class based views."""
        view = cls.as_view(attr, *args, **kwargs)
        setattr(self, attr, view)
        self._urls.append((url, attr, view.methods))

    def init_related_views(self) -> None:
        related_views = []
        for view in self.related_views:
            if not isinstance(view, RelatedView):
                view = DefaultRelatedView(*view)
            related_views.append(view)
        self.related_views = related_views

    @property
    def action_category(self) -> str:
        return f"module:{self.endpoint}"

    def get_grouped_actions(self) -> OrderedDict:
        items = actions.for_category(self.action_category)
        groups = OrderedDict()
        for action in items:
            groups.setdefault(action.group, []).append(action)

        return groups

    def register_actions(self) -> None:
        ACTIONS = [
            ModuleAction(
                self,
                "entity",
                "create",
                title=_l("Create New"),
                icon=FAIcon("plus"),
                endpoint=Endpoint(self.endpoint + ".entity_new"),
                button="default",
            )
        ]
        for component in self.components:
            ACTIONS.extend(component.get_actions())

        actions.register(*ACTIONS)

    def create_blueprint(self, crud_app: "CRUDApp") -> Blueprint:
        """Create a Flask blueprint for this module."""
        # Store admin instance
        self.crud_app = crud_app
        self.app = crud_app.app

        # If url is not provided, generate it from endpoint name
        if self.url is None:
            self.url = f"{self.crud_app.url}/{self.endpoint}"
        else:
            if not self.url.startswith("/"):
                self.url = f"{self.crud_app.url}/{self.url}"

        # Create blueprint and register rules
        self.blueprint = Blueprint(self.endpoint, __name__, url_prefix=self.url)

        for url, name, methods in self._urls:
            self.blueprint.add_url_rule(url, name, getattr(self, name), methods=methods)

        # run default_view decorator
        default_view(self.blueprint, self.managed_class, id_attr="entity_id")(
            self.entity_view
        )

        # delay registration of our breadcrumbs to when registered on app; thus
        # 'parents' blueprint can register theirs befores ours
        self.blueprint.record_once(self._setup_breadcrumb_preprocessors)

        return self.blueprint

    def _setup_breadcrumb_preprocessors(self, state: BlueprintSetupState) -> None:
        self.blueprint.url_value_preprocessor(self._add_breadcrumb)

    def _add_breadcrumb(self, endpoint: str, values: Dict[Any, Any]) -> None:
        g.breadcrumb.append(
            BreadcrumbItem(label=self.label, url=Endpoint(".list_view"))
        )

    @property
    def base_query(self) -> EntityQuery:
        """Return a query instance for :attr:`managed_class`."""
        return self.managed_class.query

    @property
    def read_query(self):
        """Return a query instance for :attr:`managed_class` filtering on
        `READ` permission."""
        return self.base_query.with_permission(READ)

    @property
    def listing_query(self) -> EntityQuery:
        """Like `read_query`, but can be made lightweight with only columns and
        joins of interest.

        `read_query` can be used with exports for example, with lot more
        columns (generallly it means more joins).
        """
        return self.base_query.with_permission(READ)

    def query(self, request: Request):
        """Return filtered query based on request args."""
        args = request.args
        search = args.get("sSearch", "").replace("%", "").lower()
        query = self.read_query.distinct()

        for crit in self.search_criterions:
            query = crit.filter(query, self, request, search)

        return query

    def list_query(self, request: Request) -> EntityQuery:
        """Return a filtered query based on request args, for listings.

        Like `query`, but subclasses can modify it to remove costly
        joined loads for example.
        """
        args = request.args
        search = args.get("sSearch", "").replace("%", "").lower()
        query = self.listing_query
        query = query.distinct()

        for crit in self.search_criterions:
            query = crit.filter(query, self, request, search)

        return query

    def ordered_query(
        self, request: Request, query: Optional[EntityQuery] = None
    ) -> EntityQuery:
        """Order query according to request args.

        If query is None, the query is generated according to request
        args with self.query(request)
        """
        if query is None:
            query = self.query(request)

        engine = query.session.get_bind(self.managed_class.__mapper__)
        args = request.args
        sort_col = int(args.get("iSortCol_0", 1))
        sort_dir = args.get("sSortDir_0", "asc")
        sort_col_def = self.list_view_columns[sort_col]
        sort_col_name = sort_col_def["name"]
        rel_sort_names = sort_col_def.get("sort_on", (sort_col_name,))
        sort_cols = []

        for rel_col in rel_sort_names:
            sort_col = getattr(self.managed_class, rel_col)
            if hasattr(sort_col, "property") and isinstance(
                sort_col.property, orm.properties.RelationshipProperty
            ):
                # this is a related model: find attribute to filter on
                query = query.outerjoin(sort_col_name, aliased=True)

                rel_model = sort_col.property.mapper.class_
                default_sort_name = "name"
                if issubclass(rel_model, BaseVocabulary):
                    default_sort_name = "label"

                rel_sort_name = sort_col_def.get("relationship_sort_on", None)
                if rel_sort_name is None:
                    rel_sort_name = sort_col_def.get("sort_on", default_sort_name)
                sort_col = getattr(rel_model, rel_sort_name, None)

            # XXX: Big hack, date are sorted in reverse order by default
            if isinstance(sort_col, (Date, DateTime)):
                sort_dir = "asc" if sort_dir == "desc" else "desc"

            elif (
                isinstance(sort_col, sa.types.String)
                or hasattr(sort_col, "property")
                and isinstance(sort_col.property.columns[0].type, sa.types.String)
            ):
                sort_col = func.lower(sort_col)

            if sort_col is not None:
                try:
                    direction = desc if sort_dir == "desc" else asc
                    sort_col = direction(sort_col)
                except Exception:
                    # FIXME
                    pass

                # sqlite does not support 'NULLS FIRST|LAST' in ORDER BY
                # clauses
                if engine.name != "sqlite":
                    nullsorder = nullslast if sort_dir == "desc" else nullsfirst
                    try:
                        sort_col = nullsorder(sort_col)
                    except Exception:
                        # FIXME
                        pass

                sort_cols.append(sort_col)

        if sort_cols:
            try:
                query = query.order_by(*sort_cols)
            except Exception:
                # FIXME
                pass
        query.reset_joinpoint()
        return query

    #
    # Exposed views
    #
    @expose("/")
    def list_view(self) -> str:
        actions.context["module"] = self
        table_view = AjaxMainTableView(
            name=self.managed_class.__name__.lower(),
            columns=self.list_view_columns,
            ajax_source=url_for(".list_json"),
            search_criterions=self.search_criterions,
            options=self.tableview_options,
        )
        rendered_table = table_view.render()

        ctx = {
            "rendered_table": rendered_table,
            "module": self,
            "base_template": self.base_template,
        }
        return render_template("default/list_view.html", **ctx)

    def list_json2_query_all(self, q):
        """Implements the search query for the list_json2 endpoint.

        May be re-defined by a Module subclass in order to customize
        the search results.

        - Return: a list of results (not json) with an 'id' and a
          'text' (that will be displayed in the select2).
        """
        cls = self.managed_class
        query = db.session.query(cls.id, cls.name)
        query = (
            query.filter(cls.name.ilike("%" + q + "%"))
            .distinct()
            .order_by(cls.name)
            .limit(self.JSON2_SEARCH_LENGTH)
        )
        results = query.all()
        results = [{"id": r[0], "text": r[1]} for r in results]
        return results

    @expose("/json2")
    def list_json2(self):
        """Other JSON endpoint, this time used for filling select boxes
        dynamically.

        You can write your own search method in list_json2_query_all,
        that returns a list of results (not json).
        """
        args = request.args

        q = args.get("q", "").replace("%", " ")
        if not q or len(q) < 2:
            raise BadRequest()

        results = self.list_json2_query_all(q)
        return {"results": results}

    #
    # Utils
    #
    def is_current(self):
        return request.path.startswith(self.url)

    @staticmethod
    def _prettify_name(name: str) -> str:
        """Prettify class name by splitting name by capital characters.

        So, 'MySuperClass' will look like 'My Super Class'

        `name`
          String to prettify
        """
        return re.sub(r"(?<=.)([A-Z])", r" \1", name)
示例#2
0
import os
import itertools
from flask import jsonify
from flask.blueprints import Blueprint
from ...model import Trial
from .._utils import convert_date, allow_origin, inject_model, answer_options
from .._utils import register_invalid_error
from ..errors import UnknownElement

blueprint = Blueprint('block', os.path.splitext(__name__)[0])

blueprint.url_value_preprocessor(inject_model)
register_invalid_error(blueprint, UnknownElement)
allow_origin(blueprint)
answer_options(blueprint)


@blueprint.route('/<experiment>/<run>/<int:block>')
def block_props(experiment, run, block):
    props = {
        'number': block.number,
        'measuredBlockNumber': block.measured_block_number(),
        'factorValues': dict((value.factor.id, value.id) for value in block.factor_values),
        'trialCount': block.trials.count(),
        'practice': block.practice
    }
    return jsonify(props)


def generate_block_trials_info(block,
                               completed_only=False,