"""
thekraf.flaskapp.baseview
=========================
The base for all flaskapp views
"""
import flask
from flask.views import MethodView
from thekraf import __version__, Multilogr
import thekraf.config as cfg
from thekraf.flaskapp.nav import top_nav_elems
logr = Multilogr.get_logr('web')
[docs]class ViewBase(MethodView):
"""MethodView with contexts for backend template and frontend
* Subclasses should subclass `TemplateContext` to add additional items to
the context.
* Update the frontend dict with ``ctx.update_frontend(d)``. This method is
equivalent to ``dict.update()``.
* ``ctx.frontend`` will become the json string, and this is done
automatically by the base view. There is no reason to access this attr
from a subclass.
The frontend data is collected in a python dict. One of the final stages of
rendering is to dump this dict as a string of json and save it to
``ctx.frontend``. Then, the base template creates a javascript script in
which ``ctx`` is set to the json data. Then, the frontend can access this
data via the ``ctx`` namespace.
Attributes:
ctx (ViewBase.TemplateContext): Context for project-specific data to be
available from the backend template
"""
[docs] class TemplateContext(object):
def __init__(self):
self.version = __version__
self._frontend = {
'endpoint': flask.request.endpoint
}
self.frontend = None
self.html = cfg.HTML
self.top_nav_elems = top_nav_elems()
[docs] def prep_frontend(self):
"""Prepare frontend data by converting dict to json"""
matches = cfg.load_config_item(
'tooltips',
select=lambda x: x['endpoint'] == self._frontend['endpoint']
)
if matches:
self._frontend['tooltips'] = matches[0]
self.frontend = flask.json.htmlsafe_dumps(self._frontend, indent=2)
[docs] def update_frontend(self, other=None, **kwargs):
"""Update the frontend dict"""
self._frontend.update(other=other, **kwargs)
@classmethod
[docs] def ctx_processor(cls):
"""Context processor for use with Flask-Security
When customizing the Flask-Security views, this context processor must
be added so the template context includes data available to all other
views.
Returns:
dict: Dict of keyword args included with flask.render
"""
ctx = cls.TemplateContext()
ctx.prep_frontend()
d = {
'ctx': ctx,
}
return d
def __init__(self):
super(ViewBase, self).__init__()
self.ctx = self.TemplateContext()
[docs] def get(self):
"""Override with specific code for GET action
Override should end by returning ``self.render()``
"""
return self.render()
[docs] def post(self):
"""Override with specific code for POST action
Override should end by returning ``self.render()`` or
``self.redirect_to_get()``
"""
return self.render()
@classmethod
[docs] def redirect_to_get(cls, endpoint=None):
"""Redirect a POST action to the equivalent GET
After a POST action, reloading the page results in the "Are you sure you
want to send a form again?" dialog. One way to avoid this often
annoying occurrence is to redirect to the equivalent GET.
Notes:
The POST data is lost upon redirect, so this only works if the POST
data has been processed before the redirect.
"""
if endpoint is None:
endpoint = flask.request.endpoint
return flask.redirect(flask.url_for(endpoint), code=303)
[docs] def before_render(self):
"""This method runs before any other logic in the render method"""
pass
[docs] def render(self):
"""Format data for the backend template and render the template"""
self.before_render()
# Create data to be available to the frontend
self.ctx.prep_frontend()
return flask.render_template(
'{}.hamlish'.format(flask.request.endpoint), ctx=self.ctx)