15.2.139. camcops_server.cc_modules.cc_pyramid

camcops_server/cc_modules/cc_pyramid.py


Copyright (C) 2012, University of Cambridge, Department of Psychiatry. Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

This file is part of CamCOPS.

CamCOPS is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

CamCOPS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.


Functions for the Pyramid web framework.

class camcops_server.cc_modules.cc_pyramid.CamcopsAuthenticationPolicy[source]

CamCOPS authentication policy.

See

static authenticated_userid(request: CamcopsRequest) Optional[int][source]

Returns the user ID of the authenticated user.

static effective_principals(request: CamcopsRequest) List[str][source]

Returns a list of strings indicating permissions that the current user has.

static unauthenticated_userid(request: CamcopsRequest) Optional[int][source]

Returns the user ID of the unauthenticated user.

We don’t allow users to be identified but not authenticated, so we return None.

class camcops_server.cc_modules.cc_pyramid.CamcopsAuthorizationPolicy[source]

CamCOPS authorization policy.

class camcops_server.cc_modules.cc_pyramid.CamcopsMakoLookupTemplateRenderer(lookup, spec, defname, package)[source]

A Mako template renderer that, when called:

  1. loads the Mako template

  2. shoves any other keys we specify into its dictionary

Typical incoming parameters look like:

spec = 'some_template.mako'
value = {'comment': None}
system = {
    'context': <pyramid.traversal.DefaultRootFactory ...>,
    'get_csrf_token': functools.partial(<function get_csrf_token ... >, ...>),
    'renderer_info': <pyramid.renderers.RendererHelper ...>,
    'renderer_name': 'some_template.mako',
    'req': <CamcopsRequest ...>,
    'request': <CamcopsRequest ...>,
    'view': None
}

Showing the incoming call stack info (see commented-out code) indicates that req and request (etc.) join at, and are explicitly introduced by, pyramid.renderers.render(). That function includes this code:

if system_values is None:
    system_values = {
        'view':None,
        'renderer_name':self.name, # b/c
        'renderer_info':self,
        'context':getattr(request, 'context', None),
        'request':request,
        'req':request,
        'get_csrf_token':partial(get_csrf_token, request),
    }

So that means, for example, that req and request are both always present in Mako templates as long as the request parameter was passed to pyramid.renderers.render_to_response().

What about a view configured with @view_config(..., renderer="somefile.mako")? Yes, that too (and anything included via <%include file="otherfile.mako"/>).

However, note that req and request are only available in the Mako evaluation blocks, e.g. via ${req.someattr} or via Python blocks like <% %> – not via Python blocks like <%! %>, because the actual Python generated by a Mako template like this:

## db_user_info.mako
<%page args="offer_main_menu=False"/>

<%!
module_level_thing = context.kwargs  # module-level block; will crash
%>

<%
thing = context.kwargs["request"]  # normal Python block; works
%>

<div>
    Database: <b>${ request.database_title | h }</b>.
    %if request.camcops_session.username:
        Logged in as <b>${request.camcops_session.username | h}</b>.
    %endif
    %if offer_main_menu:
        <%include file="to_main_menu.mako"/>
    %endif
</div>

looks like this:

from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
STOP_RENDERING = runtime.STOP_RENDERING
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1557179054.2796485
_enable_loop = True
_template_filename = '...'  # edited
_template_uri = 'db_user_info.mako'
_source_encoding = 'utf-8'
_exports = []

module_level_thing = context.kwargs  # module-level block; will crash

def render_body(context,offer_main_menu=False,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(offer_main_menu=offer_main_menu,pageargs=pageargs)
        request = context.get('request', UNDEFINED)
        __M_writer = context.writer()
        __M_writer('\n\n')
        __M_writer('\n\n')

        thing = context.kwargs["request"]  # normal Python block; works

        __M_locals_builtin_stored = __M_locals_builtin()
        __M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['thing'] if __M_key in __M_locals_builtin_stored]))
        __M_writer('\n\n<div>\n    Database: <b>')
        __M_writer(filters.html_escape(str( request.database_title )))
        __M_writer('</b>.\n')
        if request.camcops_session.username:
            __M_writer('        Logged in as <b>')
            __M_writer(filters.html_escape(str(request.camcops_session.username )))
            __M_writer('</b>.\n')
        if offer_main_menu:
            __M_writer('        ')
            runtime._include_file(context, 'to_main_menu.mako', _template_uri)
            __M_writer('\n')
        __M_writer('</div>\n')
        return ''
    finally:
        context.caller_stack._pop_frame()

'''
__M_BEGIN_METADATA
{"filename": ...}  # edited
__M_END_METADATA
'''
class camcops_server.cc_modules.cc_pyramid.CamcopsMakoRendererFactory[source]

A Mako renderer factory to use CamcopsMakoLookupTemplateRenderer.

renderer_factory

alias of camcops_server.cc_modules.cc_pyramid.CamcopsMakoLookupTemplateRenderer

class camcops_server.cc_modules.cc_pyramid.CamcopsPage(collection: Union[Sequence[Any], sqlalchemy.orm.query.Query, sqlalchemy.sql.selectable.Select], url_maker: Callable[[int], str], request: CamcopsRequest, page: int = 1, items_per_page: int = 20, item_count: int = None, wrapper_class: Type[Any] = None, ellipsis: str = '&hellip;', **kwargs)[source]

Pagination class, for HTML views that display, for example, items 1-20 and buttons like “page 2”, “next page”, “last page”.

  • Fixes a bug in paginate: it slices its collection BEFORE it realizes that the page number is out of range.

  • Also, it uses .. for an ellipsis, which is just wrong.

__init__(collection: Union[Sequence[Any], sqlalchemy.orm.query.Query, sqlalchemy.sql.selectable.Select], url_maker: Callable[[int], str], request: CamcopsRequest, page: int = 1, items_per_page: int = 20, item_count: int = None, wrapper_class: Type[Any] = None, ellipsis: str = '&hellip;', **kwargs) None[source]

See paginate.Page. Additional arguments:

Parameters

ellipsis – HTML text to use as the ellipsis marker

default_pager_pattern() str[source]

Allows internationalization of the pager pattern.

See equivalent in superclass.

Fixes bugs (e.g. mutable default arguments) and nasties (e.g. enforcing “..” for the ellipsis) in the original.

pager(format: Optional[str] = None, url: Optional[str] = None, show_if_single_page: bool = True, separator: str = ' ', symbol_first: str = '<i role="img" aria-label="Start" class="bi-skip-backward"></i>', symbol_last: str = '<i role="img" aria-label="End" class="bi-skip-forward"></i>', symbol_previous: str = '<i role="img" aria-label="Backward" class="bi-skip-start"></i>', symbol_next: str = '<i role="img" aria-label="Forward" class="bi-skip-end"></i>', link_attr: Optional[Dict[str, str]] = None, curpage_attr: Optional[Dict[str, str]] = None, dotdot_attr: Optional[Dict[str, str]] = None, link_tag: Optional[Callable[[Dict[str, str]], str]] = None)[source]

See paginate.Page.pager().

The reason for the default for show_if_single_page being True is that it’s possible otherwise to think you’ve lost your tasks. For example: (1) have 99 tasks; (2) view 50/page; (3) go to page 2; (4) set number per page to 100. Or simply use the URL to go beyond the end.

class camcops_server.cc_modules.cc_pyramid.CookieKey[source]

Keys for HTTP cookies. We keep this to the absolute minimum; cookies contain enough detail to look up a session on the server, and then everything else is looked up on the server side.

class camcops_server.cc_modules.cc_pyramid.FlashQueue[source]

Predefined flash (alert) message queues for Bootstrap; see https://getbootstrap.com/docs/3.3/components/#alerts.

class camcops_server.cc_modules.cc_pyramid.FormAction[source]

Action values for HTML forms. These values generally end up as the name attribute (and sometimes also the value attribute) of an HTML button.

exception camcops_server.cc_modules.cc_pyramid.HTTPFoundDebugVersion(location: str = '', **kwargs)[source]

A debugging version of HTTPFound, for debugging redirections.

__init__(location: str = '', **kwargs) None[source]
class camcops_server.cc_modules.cc_pyramid.Icons[source]

Constants for Bootstrap icons. See https://icons.getbootstrap.com/. See also include_bootstrap_icons.rst; must match.

class camcops_server.cc_modules.cc_pyramid.PageUrl(request: pyramid.request.Request, qualified: bool = False)[source]

A page URL generator for WebOb-compatible Request objects.

I derive new URLs based on the current URL but overriding the ‘page’ query parameter.

I’m suitable for Pyramid, Pylons, and TurboGears, as well as any other framework whose Request object has ‘application_url’, ‘path’, and ‘GET’ attributes that behave the same way as webob.Request’s.

__init__(request: pyramid.request.Request, qualified: bool = False)[source]

request is a WebOb-compatible Request object.

If qualified is false (default), generated URLs will have just the path and query string. If true, the “scheme://host” prefix will be included. The default is false to match traditional usage, and to avoid generating unuseable URLs behind reverse proxies (e.g., Apache’s mod_proxy).

class camcops_server.cc_modules.cc_pyramid.Permission[source]

Pyramid permission values.

  • Permissions are strings.

  • For “logged in”, use pyramid.security.Authenticated

class camcops_server.cc_modules.cc_pyramid.RouteCollection[source]

All routes, with their paths, for CamCOPS. They will be auto-read by all_routes().

To make a URL on the fly, use Request.route_url() or CamcopsRequest.route_url_params().

To associate a view with a route, use the Pyramid @view_config decorator.

classmethod all_routes() List[camcops_server.cc_modules.cc_pyramid.RoutePath][source]

Fetch all routes for CamCOPS.

class camcops_server.cc_modules.cc_pyramid.RoutePath(route: str, path: str = '', ignore_in_all_routes: bool = False, pregenerator: Optional[Callable] = None)[source]

Class to hold a route/path pair.

  • Pyramid route names are just strings used internally for convenience.

  • Pyramid URL paths are URL fragments, like '/thing', and can contain placeholders, like '/thing/{bork_id}', which will result in the request.matchdict object containing a 'bork_id' key. Those can be further constrained by regular expressions, like '/thing/{bork_id:\d+}' to restrict to digits. See https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/urldispatch.html

__init__(route: str, path: str = '', ignore_in_all_routes: bool = False, pregenerator: Optional[Callable] = None) None[source]
class camcops_server.cc_modules.cc_pyramid.Routes[source]

Names of Pyramid routes.

  • Used by the @view_config(route_name=...) decorator.

  • Configured via RouteCollection / RoutePath to the Pyramid route configurator.

class camcops_server.cc_modules.cc_pyramid.SqlalchemyOrmPage(query: sqlalchemy.orm.query.Query, url_maker: Callable[[int], str], request: CamcopsRequest, page: int = 1, items_per_page: int = 25, item_count: int = None, **kwargs)[source]

A pagination page that paginates SQLAlchemy ORM queries efficiently.

__init__(query: sqlalchemy.orm.query.Query, url_maker: Callable[[int], str], request: CamcopsRequest, page: int = 1, items_per_page: int = 25, item_count: int = None, **kwargs) None[source]

See paginate.Page. Additional arguments:

Parameters

ellipsis – HTML text to use as the ellipsis marker

class camcops_server.cc_modules.cc_pyramid.SqlalchemyOrmQueryWrapper(query: sqlalchemy.orm.query.Query)[source]

Wrapper class to access elements of an SQLAlchemy ORM query in an efficient way for pagination. We only ask the database for what we need.

(But it will perform a COUNT(*) for the query before fetching it via LIMIT/OFFSET.)

See:

__init__(query: sqlalchemy.orm.query.Query) None[source]
class camcops_server.cc_modules.cc_pyramid.UrlParam(name: str, paramtype: False)[source]

Represents a parameter within a URL. For example:

from camcops_server.cc_modules.cc_pyramid import *
p = UrlParam("patient_id", UrlParamType.POSITIVE_INTEGER)
p.markerdef()  # '{patient_id:\d+}'

These fragments are suitable for building into a URL for use with Pyramid’s URL Dispatch system: https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/urldispatch.html

See also RoutePath.

__init__(name: str, paramtype: False) None[source]
Parameters
  • name – the name of the parameter

  • paramtype – the type (e.g. string? positive integer), defined via the UrlParamType enum.

markerdef() str[source]

Returns the string to use in building the URL.

regex() str[source]

Returns text for a regular expression to capture the parameter value.

class camcops_server.cc_modules.cc_pyramid.UrlParamType(value)[source]

Enum for building templatized URLs. See UrlParam.

class camcops_server.cc_modules.cc_pyramid.ViewArg[source]

String used as view arguments. For example, camcops_server.cc_modules.cc_forms.DumpTypeSelector represents its choices (inside an HTTP POST request) as values from this class.

class camcops_server.cc_modules.cc_pyramid.ViewParam[source]

View parameter constants.

Used in the following situations:

  • as parameter names for parameterized URLs (via RoutePath to Pyramid’s route configuration, then fetched from the matchdict);

  • as form parameter names (often with some duplication as the attribute names of deform Form objects, because to avoid duplication would involve metaclass mess).

camcops_server.cc_modules.cc_pyramid.camcops_add_mako_renderer(config: pyramid.config.Configurator, extension: str) None[source]

Registers a renderer factory for a given template file type.

Replacement for add_mako_renderer() from pyramid_mako, so we can use our own lookup.

The extension parameter is a filename extension (e.g. “.mako”).

camcops_server.cc_modules.cc_pyramid.get_body_from_request(req: pyramid.request.Request) bytes[source]

Debugging function to read the body from an HTTP request. May not work and will warn accordingly. Use Wireshark to be sure (https://www.wireshark.org/).

camcops_server.cc_modules.cc_pyramid.get_session_factory() Callable[[CamcopsRequest], <InterfaceClass pyramid.interfaces.ISession>][source]

We have to give a Pyramid request a way of making an HTTP session. We must return a session factory.

  • An example is in pyramid.session.SignedCookieSessionFactory.

  • A session factory has the signature [1]:

    sessionfactory(req: CamcopsRequest) -> session_object
    
    • … where session “is a namespace” [2]

    • … but more concretely, “implements the pyramid.interfaces.ISession interface”

  • We want to be able to make the session by reading the camcops_server.cc_modules.cc_config.CamcopsConfig from the request.

[1] https://docs.pylonsproject.org/projects/pyramid/en/latest/glossary.html#term-session-factory

[2] https://docs.pylonsproject.org/projects/pyramid/en/latest/glossary.html#term-session

camcops_server.cc_modules.cc_pyramid.icon_html(icon: str, alt: str, url: Optional[str] = None, extra_classes: Optional[List[str]] = None, extra_styles: Optional[List[str]] = None, escape_alt: bool = True) str[source]

Instantiates a Bootstrap icon, usually with a hyperlink. Returns rendered HTML.

Parameters
  • icon – Icon name, without “.svg” extension (or “bi-” prefix!).

  • alt – Alternative text for image.

  • url – Optional URL of hyperlink.

  • extra_classes – Optional extra CSS classes for the icon.

  • extra_styles – Optional extra CSS styles for the icon (each looks like: “color: blue”).

  • escape_alt – HTML-escape the alt text? Default is True.

camcops_server.cc_modules.cc_pyramid.icon_text(icon: str, text: str, url: Optional[str] = None, alt: Optional[str] = None, extra_icon_classes: Optional[List[str]] = None, extra_icon_styles: Optional[List[str]] = None, extra_a_classes: Optional[List[str]] = None, extra_a_styles: Optional[List[str]] = None, escape_alt: bool = True, escape_text: bool = True, hyperlink_together: bool = False) str[source]

Provide an icon and accompanying text. Usually, both are hyperlinked (to the same destination URL). Returns rendered HTML.

Parameters
  • icon – Icon name, without “.svg” extension.

  • url – Optional URL of hyperlink.

  • alt – Alternative text for image. Will default to the main text.

  • text – Main text to display.

  • extra_icon_classes – Optional extra CSS classes for the icon.

  • extra_icon_styles – Optional extra CSS styles for the icon (each looks like: “color: blue”).

  • extra_a_classes – Optional extra CSS classes for the <a> element.

  • extra_a_styles – Optional extra CSS styles for the <a> element.

  • escape_alt – HTML-escape the alt text?

  • escape_text – HTML-escape the main text?

  • hyperlink_together – Hyperlink the image and text as one (rather than separately and adjacent to each other)?

camcops_server.cc_modules.cc_pyramid.icons_text(icons: List[str], text: str, url: Optional[str] = None, alt: Optional[str] = None, extra_icon_classes: Optional[List[str]] = None, extra_icon_styles: Optional[List[str]] = None, extra_a_classes: Optional[List[str]] = None, extra_a_styles: Optional[List[str]] = None, escape_alt: bool = True, escape_text: bool = True, hyperlink_together: bool = False) str[source]

Multiple-icon version of :func:icon_text.

camcops_server.cc_modules.cc_pyramid.make_page_url(path: str, params: Dict[str, str], page: int, partial: bool = False, sort: bool = True) str[source]

A helper function for URL generators.

I assemble a URL from its parts. I assume that a link to a certain page is done by overriding the ‘page’ query parameter.

path is the current URL path, with or without a “scheme://host” prefix.

params is the current query parameters as a dict or dict-like object.

page is the target page number.

If partial is true, set query param ‘partial=1’. This is to for AJAX calls requesting a partial page.

If sort is true (default), the parameters will be sorted. Otherwise they’ll be in whatever order the dict iterates them.

camcops_server.cc_modules.cc_pyramid.make_url_path(base: str, *args: camcops_server.cc_modules.cc_pyramid.UrlParam) str[source]

Makes a URL path for use with the Pyramid URL dispatch system. See UrlParam.

Parameters
  • base – the base path, to which we will append parameter templates

  • *args – a number of UrlParam objects.

Returns

the URL path, beginning with /

camcops_server.cc_modules.cc_pyramid.pregen_for_fhir(request: pyramid.request.Request, elements: Tuple, kw: Dict) Tuple[source]

Pyramid pregenerator, to pre-populate an optional URL keyword (with an empty string, as it happens). See

camcops_server.cc_modules.cc_pyramid.valid_replacement_marker(marker: str) bool[source]

Is a string suitable for use as a parameter name in a templatized URL?

(That is: is it free of odd characters?)

See UrlParam.