Python Element’s documentation!

Element is a python project created to build websites.

References

Install

Python Element has been tested with python 2.7 only and might not work with python 3.x.

Short story

sudo apt-get install mongodb-server
virtualenv --system-site-packages foobar
source foobar/bin/activate
pip install git+http://github.com/rande/python-element.git
python -m element my-project
cd my-project
python start.py element:demo:fixtures
python start.py tornado:start --verbose -d

Install services

The default skeleton required a mongodb server running, this is not mandatory for small website.

apt-get install mongodb-server

Install with virtual env

Virtual Env makes sure there are not conflict with any installed lib by creating its own environment.

virtualenv --system-site-packages element
source element/bin/activate

Install python element

You can install the package by using the main code on github

pip install git+http://github.com/rande/python-element.git

Setup your first project

The following command will help you creating your first project based on a skeleton website that you can tweak to match your needs.

python -m element my-project

The script will explain the next steps.

Architecture

Definitions

  • node: the smallest data available, it represents a content stored into the datastore. An node must contains
    • id: the internal identifier used by the datasource
    • path: the path to reach the node, the path is the external identifier to the node
    • data: a dictionnary of key-value representing the content
    • type: the node type, the node type will be used to handle the node
    • manager: the manager code which handle this node
    • created_at
    • published_at
    • enabled
    • content
    • title
    • tags
    • category
    • copyright
    • authors
  • node handler: it is a service used to render a node, there is one service per node type.

Components used

  • python 2.7: the main python version supported for now
  • IoC: it is a dependency container used to handle Element configuration and to instantiate all required services
  • Tornado: it is used to handle request and render response, Element also register custom routes to render nodes.
  • jinja: render templates.
  • unittest2: used to test the framework
  • mongodb: the main datastore for the content.

Application bootstrapping

The project used IoC to handle configuration, the skeleton application demonstrates some of its usage. The configuration files are stored in the config folder. The configuration is split into several files (this is not mandatory), each files have its own configuration:

  • config.yml: this file contains the main configuration: module to load and shared parameters.
  • parameters_*.yml: some parameters are only used on some environments, so depends on those parameters the application might behave differently (use different datastore, or webservice’s credentials)
  • services.yml: this file can contains your own custom services

Note

This configuration layout is not mandatory, you can organize those files as you want. Just alter the start.py file in order to match your wishes.

There are 2 ways to use the application:

  • command line: expose commands to produce or alter data
  • web: expose the data to the client.

The command line and the web does not use the same application instance. so make sure every thing is stateless.

Events

Most of the code is created using event to increase flexibility with how an user can interact with the Python Element. Some events are explained in the next section, other events are available on the dedicated documentation .

Request / Response workflow

  • rewrite this part to explain tornado usage
Plugins

Every things is a plugin, if you don’t like a feature just don’t enable the plugin and create your own plugin!

You can view current internal plugin in the plugins section

Bower

Elements relies on bower to install assets. All plugins use the base path resources/static/vendor to declare assets. So you should/must configure your .bowerrc like this.

{
    "directory": "resources/static/vendor"
}

Running Element

One Element’s core feature, the render helper, is used to generated sub request processes by a dedicated controller. The internal implementation of this feature rely on an ESI tag or SSI tag. So Element requires to be behind a reverse proxy like Nginx or Varnish.

To run an element instance, you need a process manager like supervisord:

[program:rabaix.net]
command=/home/rabaix/site/bin/python start.py tornado:start -np 8 -p 5001 --bind thomas.rabaix.net
autostart=true
autorestart=true
startsecs=3
user=rabaix
directory=/home/rabaix/site/src

This will spawn 8 independents processes to process http requests.

The Nginx configuration can be:

server {
    listen   80;
    server_name thomas.rabaix.net;

    location / {
        ssi on;

        proxy_pass http://127.0.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

or the Varnish configuration (from the Symfony2 documentation):

sub vcl_fetch {
    /*
    Check for ESI acknowledgement
    and remove Surrogate-Control header
    */
    if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
        unset beresp.http.Surrogate-Control;

        // For Varnish >= 3.0
        set beresp.do_esi = true;
        // For Varnish < 3.0
        // esi;
    }
    /* By default Varnish ignores Cache-Control: nocache
    (https://www.varnish-cache.org/docs/3.0/tutorial/increasing_your_hitrate.html#cache-control),
    so in order avoid caching it has to be done explicitly */
    if (beresp.http.Pragma ~ "no-cache" ||
         beresp.http.Cache-Control ~ "no-cache" ||
         beresp.http.Cache-Control ~ "private") {
        return (hit_for_pass);
    }
}

Note

Depends on the use case, you can use varnish or nginx with specific caching rules, Nginx runs SSI in in parallel while Varnish runs ESI sequentially.

If you don’t have a local reverse proxy (ie, varnish) you can use the proxy.py script which able to render esi:include tag:

[program:rabaix.net]
command=/home/rabaix/site/bin/python proxy.py --bind thomas.rabaix.net -p 5000 -sp 5001
autostart=true
autorestart=true
startsecs=3
user=rabaix
directory=/home/rabaix/site/src

Note

This proxy is not save for production usage. And should only be used to your development environment.

Node

It is the smallest data available, it represents a content stored into the datastore. By default, a node is a instance of Node.

NodeContext

The NodeHandler is in charge of rendering a Node by using an intermediary object the NodeContext. The NodeContext hold altered the information from the Node, a NodeContext should never be persisted while the Node can. The Node is still available from the NodeContext by using the node attribute.

Change the default Node class

It is possible to change the default class Node per node type. (The underlying mechanism change the __class__ property).

In order to do that, you need to create a specific listener to register the class with the type

element.plugins.presentation.listener:
    class: element.plugins.presentation.listener.PresentationListener
    tags:
        event.listener:
            - { name: node.mapper.pre_initialize, method: register }

The related python is:

class PresentationListener(object):
    def register(self, event):
        collection = event.get('meta_collection')

        collection.add(Meta(PresentationNode, 'presentation.shower'))

Inside the PresentationNode, you can have your own logic and methods

class PresentationNode(Node):
    def __init__(self, uuid=None, data=None):
        super(PresentationNode, self).__init__(uuid=uuid, data=data)

    def count_slides():
        return len(self.data['slides'])

Events

handler.request

This event is generated by the ioc.extra.tornado.RouterHandler. Events registered:

  • element.plugins.security.firewall : this is the security firewall used to control resource access depends on user’s

    credentials and depends role required to access to the resource.

handler.response

  • element.plugins.security.handler.TornadoContextHandler: this is used to store security information into the user’s session

handler.terminate

  • todo

element.nodes.load.success

This event is used when a set of nodes is loaded. While a node is loaded, no element.node.load.success event is notified. This event must be used to alter the raw node data, ie the data altered can be persisted as it.

element.node.load.success

This event is used when a node is loaded. (see element.nodes.load.success event)

element.node.load.fail

This event is used when a node cannot be found

element.node.pre_delete

This event is used when a node will be deleted.

element.node.post_delete

This event is used when a node has been deleted.

element.node.pre_save

This event is used when a node will be saved.

element.node.post_save

This event is used when a node has been saved.

element.node.context.load

This event is used to alter the NodeContext object, you should not used this event to alter the node object. You can use this event to alter temporary data (data only required in the Request Scope). The SEO plugins used this event to add missing seo information from the node.

Managers

Manager interacts with the datasource to query and store a node. Each manager has its own options and way to query data.

Note

This documentation is under construction, more to come soon

Chain Manager

Features
  • Insert here the different feature available for this plugin
Configuration
  • Insert the yaml configuration for the DI
element.plugins.cache:
    cache_control:
        - { "path": "^.*\\.(txt|jpg|png|gif|xls|doc|docx)$",    "Cache-Control": ['public', 's-maxage=14212800']}
        - { "path": "^(blog|gallery).*",    "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^.*\\.rss",            "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^contact.*",           "Cache-Control": ['private', 'must-revalidate']}
        - { "path": "^/$",                  "Cache-Control": ['public', 's-maxage=3600']}
Events
  • List event or entry points for this plugin
Architecture
  • Provide information about how the feature is implemented

Filesystem

Features
  • Load contents from a yaml file
Configuration

There is no configuration option.

Usage

You can create a yaml file with the following structure:

title: Inline Content
type: blog.post
tags: ['red', 'yellow']

----
## my title
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean rutrum diam
lectus, eget ultricies purus. Suspendisse pellentesque enim ullamcorper libero
adipiscing vulputate.

## section 1
Curabitur velit ipsum, sagittis volutpat porta at, imperdiet at risus. Donec ipsum nunc,
commodo ut laoreet sed, mollis eu dolor. Praesent iaculis, nisl a laoreet elementum,
odio lacus aliquam risus, et aliquam turpis metus vestibulum dolor.

Maecenas venenatis nulla in metus egestas sollicitudin. Donec convallis sodales
massa, ac feugiat mauris tincidunt vel. Fusce eu leo vel nisi faucibus luctus.

Note

As you notice the file is not a valid yaml file, all the data after the ---- separator will be available in the content field of the node object

Note

This documentation is under construction, more to come soon

MongoDB

Features
  • Insert here the different feature available for this plugin
Configuration
  • Insert the yaml configuration for the DI
element.plugins.cache:
    cache_control:
        - { "path": "^.*\\.(txt|jpg|png|gif|xls|doc|docx)$",    "Cache-Control": ['public', 's-maxage=14212800']}
        - { "path": "^(blog|gallery).*",    "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^.*\\.rss",            "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^contact.*",           "Cache-Control": ['private', 'must-revalidate']}
        - { "path": "^/$",                  "Cache-Control": ['public', 's-maxage=3600']}
Events
  • List event or entry points for this plugin
Architecture
  • Provide information about how the feature is implemented

Plugins Index

Action

Features

This plugin provides a way to attach Tornado actions from the datasource.

Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.action:
Usage

An action is composed by:

  • name: the route name pointing to the controller, the name can also be used in a template to generate the path.
  • path: the relative path pointing to the controller. The final url will be the concatenation of the api.collection node’s path with the path value from the action definition.
  • methods: the accepted http method
  • default: the default value sent to the controller, the controller must be a service registered into the IOC. The _controller key represents the controller which handled the request object.
Actions Node

You can define an action route like this

# labs/pdf.yml
title: PDF generation
type: action.raw
name: wkhtmltopdf_index
methods: ['GET']
defaults:
    _controller: element.plugins.wkhtmltopdf.generate:execute
Actions collection

In order to attach a set of action, you need to create a node of type action.collection and defines an actions array.

# api.yml
title: API
type: action.collection
actions:
    element_api_list_index:
        path: /element/node.<_format>
        methods: ['GET']
        defaults:
            _controller: element.api.view.node.list:execute
            path: /

The action is a simple service like, where the execute method will be call as configured in the previous file

import json

class CrudView(object):
    def execute(self, request_handler, context, *args, **kwargs):
        request_handler.set_header('Content-Type', 'application/json')
        request_handler.write(json.dumps({"mydata": "myvalue"}))

Once the action is created, you can register it in the IOC like this:

services:
    element.api.view.node:
        class: element.plugins.api.views.NodeView

Note

For now, actions are registered once when the application is booted.

Redirect Action

The plugin also provides a redirect handler, to redirect a node to another one, just create a node like this:

type: action.redirect
redirect: en

Api

Features
  • Expose node from the datasource through a RESTful API.
Configuration

You need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.api:

Then, you must have a node api as defined in your datasource:

# api.yml
title: API
type: action.collection
actions:
    element_api_node:
        path: /element/node/<path:path>.<_format>
        methods: ['GET', 'PUT', 'POST', 'DELETE']
        defaults:
            _controller: element.api.view.node:execute

    element_api_list_index:
        path: /element/node.<_format>
        methods: ['GET']
        defaults:
            _controller: element.api.view.node.list:execute
            path: /

    element_api_list:
        path: /element/path/<path:path>.<_format>
        methods: ['GET']
        defaults:
            _controller: element.api.view.node.list:execute

    element_api_handler_list:
        path: /element/handlers.<_format>
        methods: ['GET']
        defaults:
            _controller: element.api.view.handler.list:execute

    element_api_handler:
        path: /element/handler/<code>.<_format>
        methods: ['GET']
        defaults:
            _controller: element.api.view.handler:execute

Note

The API is not stable.

Usage
  • GET /api/element/handlers.json : return the list of handlers
  • GET /api/element/node.json: return the different node available
  • GET /api/element/node/{ID}.json: get a node
  • POST /api/element/node/{ID}.json: update a node
  • PUT /api/element/node/{ID}.json: create a node

Blog

Features
  • Expose a blog.post node handler
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.blog:
Usage

You can create a blog index by creating a node.index node with the following value. The node will list all children of /blog with types = blog.post.

# /blog/_index.yml
title: Posts
type: node.index
filters:
    limit: 64
    path: /blog
    types: [blog.post]

A blog post is defined as:

# /blog/2009/sept/18/are-my-services-coool.yml
type: blog.post
title: Are my services coool ?
format: markdown
enabled: 1
published_at: Fri, 18 Sep 2009 19:19:16
comment_enabled:

----
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras gravida malesuada tellus,
at tincidunt lorem accumsan vel. Vestibulum varius sodales sagittis. Quisque tristique
tempus ligula blandit sodales. Nunc luctus, orci in interdum porttitor, urna massa scelerisque
felis, eget hendrerit sapien eros sed augue. Ut quam mauris, feugiat nec laoreet pellentesque,
molestie eget orci. Vivamus leo leo, convallis et sodales vel, fermentum sed leo. Cras sit
amet dui vel sapien consectetur adipiscing. Pellentesque lectus massa, aliquet et ultrices
sit amet, volutpat vel leo. Nulla aliquet sodales enim ac dictum. Proin mattis arcu a metus
aliquam pulvinar. Phasellus sed lectus elit. Donec vitae urna magna. Vestibulum id volutpat eros.

The format option defines how to handle the content field. You can provide a markdown content or a html content.

Events

The default template used is element.plugins.blog:post.html and declare two nodes events that can be used to extends the template.

  • node.comment.list: the listener should return the comment list related to the provided node
  • node.comment.form: the listener should return the comment form

The element.plugins.disqus plugin can be used to handle comments.

Cache

Features
  • The plugins add support for altering cache information on the response
Configuration

You need to enable the plugin by adding the element.plugins.cache module and defines a set of cache_control entries.

element.plugins.cache:
    cache_control:
        - { "path": "^.*\\.(txt|jpg|png|gif|xls|doc|docx)$",    "Cache-Control": ['public', 's-maxage=14212800']}
        - { "path": "^(blog|gallery).*",    "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^.*\\.rss",            "Cache-Control": ['public', 's-maxage=3600']}
        - { "path": "^contact.*",           "Cache-Control": ['private', 'must-revalidate']}
        - { "path": "^/$",                  "Cache-Control": ['public', 's-maxage=3600']}
A cache entry defines:
  • path: a regular expression to find the rule that should be applied.
  • Cache-Control: the data to append to the response

By default, if no match is found then the Cache-Control value will be private, must-revalidate

Architecture
  • The plugin listen to the element.node.render_response event.

Contact

Features
  • Add a simple contact form as a block
Configuration

You need to enable the module ioc.extra.mailer and configure the smtp settings.

ioc.extra.mailer:
    host:       smtp.localhost
    port:
    user:
    password:
Usage

Create a contact.form node:

# /contact.yml
title: Contact
type: contact.form
email:
    to:        an-email@localhost
    from:      'no-reply@localhost'
    subject:   'Contact Form localhost'

Disqus

Features
  • Add a custom block to include disqus comments
Configuration

You need to enabled the element.plugins.disqus module.

element.plugins.disqus:
    account: account_code

The account value is the name of your account on disqus.

Events
  • The plugin uses the node.comment.list event to add the comment list.

Errors

Features
  • Return a valid error node
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.errors:
Usage

Depends on the error type, the listener will look for - errors/40x node if a node does not exist - errors/50x node if there is an internal error

A node can be anything, here an example:

# /errors/40x.yml
title: Page Not Found
type: page.default
format: markdown

----
The requested URL was not found on the server.

If you entered the URL manually please check your spelling and try again.
Events
  • The plugin listen to two events: element.node.not_found and element.node.internal_error

Feed

Features
  • Add a handler to render atom/rss feed from query
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.feed:
Usage

To create an atom feed, just define a node element.feed.atom

# /feeds/python.atom.yml
title: Python Feeds
type: element.feed.atom
filters:
    types:      [blog.post]
    tags:       [python]

To create a RSS feed, just define a node element.feed.rss

# /feeds/python.rss.yml
title: Python Feeds
type: element.feed.rss
filters:
    types:      [blog.post]
    tags:       [python]

If you want to create an index of feed, just create a simple index:

# /feeds/_index.yml
title: Feeds List
type: node.index
template: element.plugins.node:index.html
filters:
    types: [element.feed.rss, element.feed.atom]
    path: /feeds

Livestats

Features
  • Expose information about the CPU and memory usage for the current host
  • It requires a websocket connection to work
Configuration

You need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.livestats:
    websocket:
        access:  ws://element.vagrant:5000/websocket/system
        handler: /websocket/system

Also, you need to expose the livestats template with a simple node.template node

# labs/livestats.yaml
title: System States
type: node.template
template: element.plugins.livestats:system.html
Preview
_images/livestats.png

The gauge values

Media

  • Display a gallery of media
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.media:
Usage

You can create a gallery with this node:

# /gallery/2010/australia/_index.yml
title: Australia
type: media.gallery
format: markdown
base_template: element:base_gallery.html
content: |
    Australia is one country where everything is possible, from dessert to the sea, from
    the walabee to the shark. This country is incredible.

Angular Admin

The plugin provides an AngularJS Admin to edit nodes.

Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.ngadmin:
Usage

You can access to the admin using the url /element/static/element.plugins.ngadmin/index.html. You can create a redirect node to make the url a bit shorter:

# /admin.yml
type: action.redirect
redirect: /element/static/element.plugins.ngadmin/index.html

Note

The ngadmin plugin will be refactored ;)

Node

The node plugin is a core plugin as it provides main features to render a node.

Configuration
element.plugins.node:
    render_type: esi # or ssi
Usage

The plugin provide a node index handler to render a list of node

# /blog/_index.yml
title: Feeds List
type: node.index
template: element.plugins.node:index.html
filters:
    types: [element.feed.rss, element.feed.atom]
    path: /feeds

The filters option accept arguments: - types: filter nodes by types - category: filter by category - path: lookup from the specified path - tags: filter nodes the provided tags - limit: limit the number of result to return - offset: set the offset

Jinja Functions
render_node_event

This helper allows to create a place holder inside a template where listeners can generate some contents.

A good example is a blog post where comments are required. However, the comment mechanism might not be implemented as many solutions exist. The solution is to used the render_node_event helper to raise a specific event with proper option like the subject.

{{ render_node_event('node.comment.list', options={'subject': context.node}) }}
render_node

This helper renders a node instance.

{{ render_node(node) }}
render

This helper render an ESI tag or a SSI tag. This can be useful if you want to reuse a controller. It is a valid solution if you don’t have a node to render.

{{ render(path('element_profiler_wdt', token=token, position='normal')) }}
Jinja Filters
markup

This filter take a node and return a formatted string.

type: blog.post
title: Test
format: markdown

----
## John Doe

Put a Resume here!!
{{ node|markup }}
Events

The plugin listen to different events:

  • element.node.load.success and element.nodes.load.success for normalizing a node. The normalization make sure that all required fields are set.

Page

Features
  • Expose a page.default node handler
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.page:
Usage

A page node is very similar to a blog node, however it should be used to render simple page.

title: Homepage
type: page.default
format: html

----
<div class="row demo-tiles">
  <div class="span3">
    <div class="tile">
      <img class="tile-image big-illustration" alt="" src="images/illustrations/colors.png" />
      <h3 class="tile-title">Blog</h3>
      <p>Some posts about <br/>technicals playground.</p>
      <a class="btn btn-primary btn-large btn-block" href="blog">Read It</a>
    </div>
  </div>
  <div class="span3">
    <div class="tile">
      <img class="tile-image" alt="" src="images/illustrations/infinity.png" />
      <h3 class="tile-title">Element</h3>
      <p>A python CMS based on Tornado with a bit of IOC. </p>
      <a class="btn btn-primary btn-large btn-block" href="https://github.com/rande/python-element">Play</a>
    </div>
  </div>
  <div class="span3">
    <div class="tile">
      <img class="tile-image" alt="" src="images/illustrations/bag.png" />
      <h3 class="tile-title">Stats</h3>
      <p>Some information about this instance.</p>
      <a class="btn btn-primary btn-large btn-block" href="stats/parameters">Get Them</a>
    </div>
  </div>
  <div class="span3">
    <div class="tile">
      <img class="tile-image" alt="" src="images/illustrations/compass.png" />
      <h3 class="tile-title">Resume</h3>
      <p>A "standard" <br />curriculum vitae.</p>
      <a class="btn btn-primary btn-large btn-block" href="resume">Discover It</a>
    </div>
  </div>
</div>

The format option defines how to handle the content field. You can provide a markdown content or a html content.

Presentation

Features
  • Expose some presentation nodes (shower, slideshare, raw mode)
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.presentation:
Bower configuration

You need to install shower or/and reveal.js, open your bower.json file and add

"dependencies": {
    "shower": "~1",
    "reveal.js": "2.6.2"
}
Shower Presentation

A shower presentation is defined by presentation.shower node type:

# /blog/2009/sept/18/are-my-services-coool.yml
type: presentation.shower
title: How to create a good presentation ?
abstract: Let's try to show off with shower!
published_at: 20 May 2014 12:37:48
theme: ribbon # or bright

----
class: shout

## The presentation
----
## Slide 1

- My bullet point 1
- My bullet point 2

----
## Slide 2

- My bullet point 1
- My bullet point 2

----
### etc ..

...

You can use some metadata to get better support from the theme

  • class:
    • shout: one big title
    • shout grow: a growing big title
    • shout shrink: a shrinking bit title
    • cover: a title + a picture
  • id: an html id that you can used to define custom slide

  • data-timing: a timer before switching to the next slide (value format: 00:03, ie 3s)

You can also include some stylesheet to tweak some slides

class: cover
id: cover

<style>
#cover h2 {
    margin: 30px 0px 0px;
    color: #FFF;
    text-align: center;
    font-size: 70px;
}

#cover p {
    margin: 10px 0px 0px;
    text-align: center;
    color: #FFF;
    font-style: italic;
    font-size: 20px;
}

</style>

## Shower Presentation Engine

Integrated into Python Element

<img src="http://shwr.me/pictures/cover.jpg" />
Reveal.js Presentation

A shower presentation is defined by presentation.reveal node type:

# /blog/2009/sept/18/are-my-services-coool.yml
type: presentation.reveal
title: How to create a good presentation ?
published_at: 20 May 2014 12:37:48

----
## The presentation
----
## Slide 1

- My bullet point 1
- My bullet point 2

----
data-state: blackout

## Slide 2

- My bullet point 1
- My bullet point 2

----
### etc ..

...

You can use some metadata to get better support from the theme

  • ``data-state`: used to change the background color
Slideshare Presentation

A shower presentation is defined by presentation.slideshare node type:

type: presentation.slideshare
title: SFPot March 2014 - Sonata Block Bundle
published_at: 19 March 2014 12:37:48
embed_code: 32480268 # the embed code
width: 800           # the width of the presentation (default=597)

Profiler

Features
  • Expose information about the current request
  • Memory Usage / Profiling ...
Configuration

You need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.profiler:

Also, you need to expose the profiler’s controllers

# profiler.yaml
title: Profiler
type: action.collection
actions:
    element_profiler_home:
        type: action.raw
        path: /<token>
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:home

    element_profiler_view:
        type: action.raw
        path: /<token>
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:view

    element_profiler_wdt:
        type: action.raw
        path: /wdt/<token>
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:wdt

    element_profiler_pyinfo:
        type: action.raw
        path: /pyinfo
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:pyinfo

    element_profiler_import:
        type: action.raw
        path: /import
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:import_run

    element_profiler_export:
        type: action.raw
        path: /<token>/export
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:export_run

    element_profiler_purge:
        type: action.raw
        path: /<token>/purge
        methods: ['GET']
        defaults:
            _controller: element.plugins.profiler.view:purge_run
Usage

The feature is enabled on dev mode, it does not work for threaded environment.

Preview
_images/profiler_wdt.png

The wdt displays memory usage, processing time, controller and python version

_images/profiler_view_config.png

Display more detailed information profiling panel: config, request, etc ..

Credits

The WDT is a python port of the Symfony2 Profiler. Icons created by Sensio are shared under a Creative Commons Attribution license.

Security

Features
  • Add security and access control to your application
  • The current implementation is based on the Security Component from the Symfony2 framework.

Note

For now, there is only one authentication implemented: the http basic.

Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.security:
    role_hierarchy:
        ROLE_PUBLIC:      [IS_AUTHENTICATED_ANONYMOUSLY]
        ROLE_ADMIN:       [ROLE_PUBLIC, ROLE_USER]

    providers:
        in_memory:
            users:
                - {'username': 'admin', 'password': 'admin', roles: ['ROLE_ADMIN']}

    firewalls:
        private:
            pattern:            ^/(admin|api)(.*)
            http_basic:
                provider:       element.plugins.security.provider.in_memory
                # login_path:     /admin/login
                # use_forward:    false
                # check_path:     /admin/login_check
                # failure_path:   null
            # logout:
                # path:           /admin/logout
            anonymous:          false  # allow anonymous connection

        public:
            pattern:            "^/.*"
            anonymous:          true    # allow anonymous connection

    access_control:
        - { path: ^/admin/login$,       role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$,      role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/login-check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/(admin|api),        role: ROLE_ADMIN }
        - { path: ^/.*,                 role: ['IS_AUTHENTICATED_ANONYMOUSLY'] }

Seo

Features
  • Add basic support for SEO information (title and metas)
Configuration

You need to enable the module element.plugins.seo and configure the settings.

element.plugins.seo:
    title_pattern: "%seo.title_pattern%"
    metas:
        name:
            keywords:             python, element, cms, markdown
            description:          Python Element by Thomas Rabaix ~ A CMS based on Tornado with a bit of "ioc"
            robots:               index, follow
            viewport:             width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no

        property:
            # Facebook application settings
            #'fb:app_id':          XXXXXX
            #'fb:admins':          admin1, admin2

            # Open Graph information
            # see http://developers.facebook.com/docs/opengraphprotocol/#types or http://ogp.me/
            'og:site_name':       Python Element by Thomas Rabaix
            'og:description':     A CMS based on Tornado

        http-equiv:
            'Content-Type':         text/html; charset=utf-8
            #'X-Ua-Compatible':      IE=EmulateIE7
Usage

You can add seo information on a node, by using a seo fields:

title: Homepage
seo:
    title: The Python Element Homepage
    metas:
        name:
            description: Python Element by Thomas Rabaix ~ A CMS based on Tornado with a bit of "ioc" ;)

        property:
            'og:description':     A CMS based on Tornado ;)

type: page.default
format: markdown

----
The content here

The seo information will be rendered by using the special render_node_event helper with the element.seo.headers event.

{% if context %}
    {{ render_node_event('element.seo.headers', options={'subject': context}) }}
{% else %}
    {{ render_node_event('element.seo.headers') }}
{% endif %}

Note

The seo information from the node will be merged with the one from the default configuration.

Static

Features
  • Add a handler to render static file
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.static:
Usage

Just place a store a file in the datasource and access it with a browser. That’s it.

If the url contains the ?mode=preview then the static will be rendered into a preview mode.

Jinja Helpers

The plugin provides 2 jinja plugins:

  • url_media_resize : take the width as parameter and generates a valid url to a resized version of the targetted media.
  • url_media_crop : take the size and the optional crop tuples to generates a valid url to a cropped version of the targetted media.
{% for media in medias %}
    <a href="{{ url_media_resize(media, 1440) }}" class="swipebox" title="{{ media.name }}">
        <img src="{{ url_media_crop(media, size=(234, 234), crop=(0.5, 0.5)) }}" alt="image" width="250px">
    </a>
{% endfor %}
Architecture

The plugin provides a StaticNodeLoader to create a node object from a path, the created node type is element.static.

Wkhtmltopdf

Features
  • Add a form to generate a PDF from a provided url using wkhtmltopdf (the software must be available in your path)
Configuration

There is no configuration option. You only need to enable the plugin by adding this line into the IoC configuration file.

element.plugins.wkhtmltopdf:
    # where the generated pdf will be stored
    data_path: %project.root_folder%/data/wkhtmltopdf
Usage

Just place a store a file in the datasource and access it with a browser. That’s it.

# pdf.yml
title: PDF generation
type: action.raw
name: wkhtmltopdf_index
methods: ['GET']
defaults:
    _controller: element.plugins.wkhtmltopdf.generate:execute

Contributing

Thanks for going into this section! This page tries to summaries all information required to contribute to this project. Any contribution must have its own unit tests to ensure good quality and avoid regressions.

Retrieving the code

git clone https://github.com/rande/python-element.git
pip install -e python-element
cd python-element

Loading fixtures

The repository comes with a set of fixtures that you can load to test the solution, or improve it! To load them just run the following commands:

make fixtures

Unit Tests

Unit tests are important to avoid bugs. The current test suite used nosetests, to run tests:

make test

Running code

Once fixtures are loaded, you can start the http webserver with the command:

make dev

Documentations

English is not the first language of programmers, some typos or huge grammatical errors might occur in this project, please feel free to fix them by sending some pull requests.

Of course you are welcome to contributing to the documentation too!

Indices and tables