gaetk2.handlers - WSGI Request Handlers¶
The gaetk2.handlers
package aims to be the working horse on which
you build your application. Instead of a monolytic approach like GAETK1 we
work with mixins here. All of this is based on webapp2 request handlers. Basically you overwrite get()
post()
. In there you do a self.response.write('foo')
.
gaetk2 provides you with the convinience functions return_text()
for simple replies and render()
for rendering jinja2 templates.
BasicHandler
provides basic functionality and template variables. JsonBasicHandler
is specialized to produce JSON.
Usually you would use DefaultHandler
for your public pages.
This includes BasicHandler
, MessagesMixin
, AuthenticationReaderMixin
. For JSON output you would use JsonBasicHandler
based on JsonBasicHandler
and AuthenticationReaderMixin
.
If you want to ensure Users are authenticated use AuthenticatedHandler
which extends DefaultHandler
with AuthenticationRequiredMixin
Mix-Ins¶
Mix-Ins provide specialized functionality to handlers. They are mostly
implemented using gaetk2.handlers.basic.BasicHandler
Hook Mechanism. Some Mix-Ins provide just methods manually called by your method handlers.
PaginateMixin
provides pagination of ndb-Queries.gaetk2.handlers.mixins.messages.MessagesMixin
provide short term feedback to a user displayed on the “next page”. The concept is similar to flask’s “Message flashing”.
gaetk2.handlers package¶
-
class
gaetk2.handlers.
DefaultHandler
(*args, **kwargs)[source]¶ Handle Requests and load self.credential if Authentication is provided.
-
class
gaetk2.handlers.
JsonHandler
(*args, **kwargs)[source]¶ Send JSON data to client and load self.credential if Authentication is provided.
-
class
gaetk2.handlers.base.
BasicHandler
(*args, **kwargs)[source]¶ Generic Handler functionality.
You usually overwrite
get()
orpost()
and callrender()
in there. See gaetk2.handlers for examples.For Returning Data to the user you can access the self.response object or use
return_text()
andrender()
. See_create_jinja2env()
to understand the jinja2 context being used.Helper functions are
abs_url()
andis_production()
.See also
is_sysadmin()
,is_staff()
andhas_permission()
are meant to work withAuthenticationReaderMixin
-
credential
¶ authenticated user, see
AuthenticationReaderMixin
-
session
¶ current session which is based on https://github.com/dound/gae-sessions.
-
default_cachingtime
¶ None or int – Class Variable. Which cache headers to generate, see
_set_cache_headers()
.
-
debug_hooks
¶ boolean – Class Variable. If set to
True
the order in which hooks are called is logged.
Note
gaetk2 adds various variables to the template context. Other mixins provide additional template variables. See the Index Index under “Template Context” to get an overview.
These Template Variables are provided:
- request
- credential
- is_staff
- is_sysadmin
- gaetk_production
- gaetk_development
- gaetk_app_name
- gaeth_version
- gaetk_logout_url
Warning
BasicHandler
implements a rather unusual way to implement Multi-Inherance/Mix-Ins. Instead of insisting that every parent class and everty Mix-In implements all possible methods and calls super on themBasicHandler
uses a custom dispatch mechanism which calls all methods in all parent and sibling classes.The following functions are called on all parent and sibling classes:
pre_authentication_hook()
.authentication_preflight_hook()
.authentication_hook()
.authorisation_hook()
.method_preperation_hook()
.finished_hook()
- called even if aexc.HTTPException
< 500 occurs.
build_context()
is special because the output is “chained”. So the rendering is done with something like the output ofChild.build_context(Parent.build_context(MixIn.build_context({})))
response_overwrite()
andfinished_overwrite()
can be overwritten to provide special functionality like inJsonBasicHandler
.You are encouraged to study the source code of
BasicHandler
!-
abs_url
(url)[source]¶ Converts an relative into an qualified absolute URL.
Parameters: url (str) – an path to a web resource. Returns: A fully qualified url. Return type: str Example
>>> BasicHandler().abs_url('/foo') 'http://server.example.com/foo'
-
is_sysadmin
()[source]¶ Checks if the current user is logged in as a SysOp/SystemAdministrator.
We use various souces to deterine the Status of the user. Returns True if:
- google.appengine.api.users.is_current_user_admin()
- the request came from 127.0.0.1 local address
- self.credential.sysadmin == True
Returns: the status of the currently logged in user. Return type: boolean
-
is_staff
()[source]¶ Returns if the current user is considered internal.
This means he has access to not only his own but to all settings pages, etc.
is_sysadmin()
- self.credential.staff == True
Returns: the status of the currently logged in user is considered internal. Return type: boolean
-
render
(values, template_name)[source]¶ Render a Jinja2 Template and write it to the client.
If rendering takes an unusual long time this is logged.
Parameters: See also
build_context()`also provides data to the template context and is often extended by plugins. See :class:`BasicHandler()
docsting for standard template variables.
-
return_text
(text, status=200, content_type='text/plain', encoding='utf-8')[source]¶ Quick and dirty sending of some plaintext to the client.
Parameters:
-
build_context
(values)[source]¶ Helper to provide additional request-specific values to HTML Templates.
Will be called on all Parents and MixIns, no super() needed.
- def build_context(self, values):
- myvalues = dict(navsection=’kunden’, …) myvalues.update(values) return myvalues
-
_add_jinja2env_globals
(env)[source]¶ Helper to provide additional Globals to Jinja2 Environment.
This should be considered one time initialisation.
- Example::
- env.globals[‘bottommenuurl’] = ‘/admin/’ env.globals[‘bottommenuaddon’] = ‘<i class=”fa fa-area-chart”></i> Admin’ env.globals[‘profiler_includes’] = gae_mini_profiler.templatetags.profiler_includes
-
debug
(message, *args)[source]¶ Detailed logging for development.
This logging will only happen, if
WSGIApplication
was initialized withdebug=True
. Is meant for local inspection of the stack during development. Messages are prefixed with the method name from where they are called.
-
pre_authentication_hook
(method_name, *args, **kwargs)[source]¶ Might do redirects before even authentication data is loaded.
Called on all parent and sibling classes.
-
authentication_preflight_hook
(method_name, *args, **kwargs)[source]¶ Might load Authentication data from Headers.
Called on all parent and sibling classes.
-
authentication_hook
(method_name, *args, **kwargs)[source]¶ Might verify Authentication data.
Called on all parent and sibling classes.
Might check if authenticated user is authorized.
Called on all parent and sibling classes.
-
method_preperation_hook
(method_name, *args, **kwargs)[source]¶ Is Called just before GEP, POST, PUT, DELETE etc. is called.
Used to provide common data in child classes. E.g. to set up variables, load Date etc.
-
response_overwrite
(response, method, *args, **kwargs)[source]¶ Function to transform response. To be overwritten.
-
finished_overwrite
(response, method, *args, **kwargs)[source]¶ Function to allow logging etc. To be overwritten.
-
_jinja2_exception_handler
(traceback)[source]¶ Is called during Jinja2 Exception processing to provide logging.
-
_render_to_fd
(values, template_name, fd)[source]¶ Sends the rendered content of a Jinja2 Template to Output.
Per default the template is provided with output of
build_context(values)
.
-
_set_cache_headers
(caching_time=None)[source]¶ Set Cache Headers.
Parameters: caching_time (None or int) – the number of seconds, the result should be cachetd at frontend caches. None
means no Caching-Headers. See alsodefault_cachingtime
. 0 or negative Values generate an comand to disable all caching.
-
_call_all_inherited
(funcname, *args, **kwargs)[source]¶ In all SuperClasses call funcname - if it exists.
-
_reduce_all_inherited
(funcname, initial)[source]¶ In all SuperClasses call funcname with the output of the previus call.
-
dispatch
()[source]¶ Dispatches the requested method fom the WSGI App.
Meant for internal use by the stack.
-
handle_exception
(exception, debug)[source]¶ Called if this handler throws an exception during execution.
The default behavior is to re-raise the exception to be handled by
WSGIApplication.handle_exception()
.Parameters: - exception – The exception that was thrown.
- debug_mode – True if the web application is running in debug mode.
Returns: response to be sent to the client.
-
-
class
gaetk2.handlers.base.
JsonBasicHandler
(*args, **kwargs)[source]¶ Handler which is specialized for returning JSON.
Excepts the method to return
- dict(), e.g. {‘foo’: bar}
Dict is converted to JSON. status is used as HTTP status code. cachingtime is used to generate a Cache-Control header. If cachingtime is None, no header is generated. cachingtime defaults to 60 seconds.
-
class
gaetk2.handlers.mixins.paginate.
PaginateMixin
[source]¶ Show data in a paginated fashion.
Call
paginate()
in your Request-Method handler.Example
Build a view function like this:
from ..handlers import AuthenticatedHandler from ..handlers.mixins import PaginateMixin class MyView(AuthenticatedHandler, PaginateMixin): def get(self): query = MyModel.query().order('-created_at) template_values = self.paginate(query) self.render(template_values, 'template.html')
Your
template.html
could look like this:<ul> {% for obj in object_list %} <li>{{ obj }}</li> {% endfor %} </ul> {{ paginator4 }}
The
{{ paginator4 }}
expression renders a Bootstrap 4 Paginator object. If you dont want that you can add your own links:{% if prev_objects %} <a href="?{{ prev_qs }}">← Prev</a> {% endif %} {% if more_objects %} <a href="?{{ next_qs }}">Next →</a> {% endif %}
-
paginate
(query, defaultcount=25, datanodename='objects', calctotal=False, formatter=None)[source]¶ Add NDB-based pagination to Views.
Provides a template environment by calling
self.paginate()
.Parameters: formatter is called for each object and can transform it into something suitable. If no formatter is given and objects have a as_dict() method, this is used for formating.
if calctotal == True then the total number of matching rows is given as an integer value. This is a ecpensive operation on the AppEngine and results might be capped at 1000.
datanodename is the key in the returned dict, where the Objects resulting form the query resides.
defaultcount is the default number of results returned. It can be overwritten with the HTTP-parameter limit.
We handle the additional query parameters
start
,cursor
,cursor_start
from the HTTP-Request to note what is currently displayed.limit
can be used to overwritedefaultcount
.The start HTTP-parameter can skip records at the beginning of the result set.
If the cursor HTTP-parameter is given we assume this is a cursor returned from an earlier query.
Returns: A dict like this to be used in the template: {more_objects=True, prev_objects=True, prev_start=10, next_start=30, limit=10, total=None, objects=[...], cursor='ABCDQWERY', prev_qs='?start=10', next_qs='?start=30&cursor=ABCDQWERY', paginator='<html ...>' }
paginator is generated by rendering gaetk_fragments/PaginateMixin.paginator.html with the other return values. You can overwrite the output by providing your own gaetk_fragments/PaginateMixin.paginator.html in your search path.
See also
http://mdornseif.github.com/2010/10/02/appengine-paginierung.html
http://blog.notdot.net/2010/02/New-features-in-1-3-1-prerelease-Cursors
http://code.google.com/appengine/docs/python/datastore/queryclass.html#Query_cursor
Note
Somewhat obsoleted by listviewer.
-
-
class
gaetk2.handlers.mixins.messages.
MessagesMixin
[source]¶ MessagesMixin provides the possibility to send messages to the user.
Like Push-Notifications without the pushing.
-
add_message
(typ, text, ttl=15)[source]¶ Sets a user specified message to be displayed to the currently logged in user.
typ can be error, success, info or warning text is the text do be displayed ttl is the number of seconds after we should stop serving the message.
If you want to pass in HTML, you need to use jinja2.Markup([string]).
-
-
class
gaetk2.handlers.mixins.multirender.
MultirenderMixin
[source]¶ Provide rendering for a variety of formats with minimal code.
For the three major formats HTML, JSON, CSV and XML und you can get away with virtually no code.
Still nowadays we discourage the habit of massaging a single view into providing different formats of the same data.
-
multirender
(fmt, data, mappers=None, contenttypes=None, filename='download', defaultfmt='html', html_template='data', html_addon=None, xml_root='data', xml_lists=None, tabular_datanodename='objects')[source]¶ Send Data formated in different ways to the client.
Some real-world view method might look like this:
# URL matches ‘/empfaenger/([A-Za-z0-9_-]+)/rechnungen.?(json|xml|html)?’, def get(self, kundennr, fmt):
query = models.Rechnung.all().filter(‘kundennr = ‘, kundennr) values = self.paginate(query, 25, datanodename=’rechnungen’) self.multirender(fmt, values,
filename=’rechnungen-%s’ % kundennr, html_template=’rechnungen.html’, tabular_datanodename=’rechnungen’)/empfaenger/12345/rechnungen and /empfaenger/12345/rechnungen.html will result in rechnungen.html beeing rendered. /empfaenger/12345/rechnungen.json results in JSON being returned with a Content-Disposition header sending it to the file rechnungen-12345.json. Likewise for /empfaenger/12345/rechnungen.xml. If you add the Parameter disposition=inline no Content-Desposition header is generated.
If you use fmt=json with a callback parameter, JSONP is generated. See http://en.wikipedia.org/wiki/JSONP#JSONP for details.
If you give a dict in html_addon this dict is additionaly passed the the HTML rendering function (but not to the rendering functions of other formats).
You can give the xml_root and xml_lists parameters to provide dict2xml() with defenitions on how to name elements. See the documentation of roottag and listnames in dict2xml documentation.
For tabular formats like XLS and CSV we assume that data[tabular_datanodename] contains a list of dicts to be rendered.
For more sophisticated layout you can give customized mappers. Using functools.partial is very helpfiull for thiss. E.g.
from functools import partial multirender(fmt, values,
- mappers=dict(xml=partial(dict2xml, roottag=’response’,
- listnames={‘rechnungen’: ‘rechnung’, ‘odlines’: ‘odline’},
- pretty=True),
html=lambda x: ‘<body><head><title>%s</title></head></body>’ % x))
-