Source code for flask_datatables.utils

# -*- coding: utf-8 -*-
# pylint: disable=unsubscriptable-object
"""Utilities & Auxiliaries

We provided some auxiliary functions for ``Flask-DataTables``.

* :func:`~flask_datatables.utils.render_macro` renders a given
  ``macro`` from the Jinja templates
* :func:`~flask_datatables.utils.prepare_response` is the default
  built-in method for ``factory`` parameter of
  :meth:` <>`
* :func:`~flask_datatables.utils.parse_request` is the utility
  function to parse `DataTables`_ client-side query parameters
  from the URL

.. _DataTables:


import contextlib
import urllib.parse
from typing import TYPE_CHECKING, cast

import flask
import peewee
import werkzeug.datastructures
import werkzeug.exceptions

    from typing import Any, Callable, List, Optional, Union

    from jinja2 import Environment
    from peewee import Model
    from werkzeug.datastructures import ImmutableMultiDict

    from .typing import ObjectData, Query

__all__ = [

[docs]def render_macro(template_name_or_list: 'Union[str, List[str]]', macro: str, **context: 'Any') -> str: """Evaluates and renders a **macro** from the template. Args: template_name_or_list: The name of the template to be rendered, or an iterable with template names the first one existing will be rendered. macro: The name of macro to be called. Keyword Args: **context: The variables that should be available in the context of the template. Returns: The rendered macro. """ template = cast('Environment', flask.current_app.jinja_env).get_or_select_template(template_name_or_list) # type: ignore[arg-type] # pylint: disable=line-too-long macro_func = getattr(template.module, macro) return macro_func(**context)
[docs]def prepare_response(template: 'Union[str, List[str]]') -> 'Callable[[Model], ObjectData]': """Prepare response object data. The function returns a wrapper function to use the ``template`` as a factory to render HTML response blocks. The Jinja templates should have **macro** blocks for each target field named after ``render_{field_name}`` and takes only one argument ``record`` as the selected data model record. Args: template: Path to the macro template. Returns: Prepared response object data. See Also: See :func:`flask_datatables.utils.render_macro` for more information. """ def wrapper(record: peewee.Model) -> 'ObjectData': data = {} # type: ObjectData for field in record.__data__.keys(): try: data[field] = render_macro(template, f'render_{field}', record=record) # type: ignore[misc] except Exception: data[field] = getattr(record, field) # type: ignore[misc] return data return wrapper
def _parse_int(arg: 'Optional[str]') -> int: """Parse argument as :obj:`int`. Args: arg: Original request argument. Returns: Parsed query argument. """ if arg is not None: with contextlib.suppress(Exception): return int(arg) return -1 def _parse_bool(arg: 'Optional[str]') -> bool: """Parse argument as :obj:`bool`. Args: arg: Original request argument. Returns: Parsed query argument. """ if isinstance(arg, str): arg = arg.casefold() if arg == 'true': return True if arg == 'false': return False return False def _parse_str(arg: 'Optional[str]') -> str: """Parse argument as :obj:`str`. Args: arg: Original request argument. Returns: Parsed query argument. """ if arg is None: return '' return arg
[docs]def parse_request(args: 'Optional[ImmutableMultiDict]' = None) -> 'Query': """Parse :attr:`flask.request.args <flask.Request.args>` as :class:`~tekid.ext.datatables.Query`. Args: args: Original request arguments. The default value is inferred from :attr:`request.args <flask.Request.args>`. Returns: Parsed query dictionary. """ if args is None: args = flask.request.args query = { 'draw': _parse_int(args.get('draw')), 'columns': [], 'order': [], 'start': _parse_int(args.get('start')), 'length': _parse_int(args.get('length')), 'search': { 'value': _parse_str(args.get('search[value]')), 'regex': _parse_bool(args.get('search[regex]')), }, '_': _parse_int(args.get('_')), } # type: Query index = 0 while True: try: data = args[f'columns[{index}][data]'] except werkzeug.exceptions.BadRequestKeyError: break query['columns'].append({ 'data': _parse_str(data), 'name': _parse_str(args.get(f'columns[{index}][data]')), 'searchable': _parse_bool(args.get(f'columns[{index}][searchable]')), 'orderable': _parse_bool(args.get(f'columns[{index}][orderable]')), 'search': { 'value': _parse_str(args.get(f'columns[{index}][search][value]')), 'regex': _parse_bool(args.get(f'columns[{index}][search][regex]')), }, }) index += 1 index = 0 while True: try: column = args[f'order[{index}][column]'] except werkzeug.exceptions.BadRequestKeyError: break query['order'].append({ 'column': _parse_int(column), 'dir': _parse_str(args.get(f'order[{index}][dir]')), # type: ignore[typeddict-item] }) index += 1 return query
def build_cache(query_string: 'Optional[str]' = None) -> str: """Build a key to cache the query parameters. Args: query_string: Query parameters in string form. The default value is inferred from :attr:`request.query_string <flask.Request.query_string>`. Returns: A string literal representing the query parameters. """ if query_string is None: query_string = flask.request.query_string.decode() query_parsed = urllib.parse.parse_qsl(query_string) query = werkzeug.datastructures.MultiDict(query_parsed).to_dict() if 'draw' in query: del query['draw'] query_sorted = sorted(query.items(), key=lambda kv: kv[0]) return urllib.parse.urlencode(query_sorted)