Concept Tutorial

A concept is a way to organize code. Technically it’s a package that bundles the code needed to view, create, modify and delete (or CRUD: create, read, update, delete) a thing or entity in your application’s domain. Most applications will consist of multiple concepts. Typical concepts would be user, profile, comment, item, document or department.

The thing we want to implement here is a blog post. We use ekklesia-portal as target project here but this also works for other Python Ekklesia applications with a Web UI.

Building Blocks

Concepts use the following objects passed in from the outside:

  • Request: A HTTP request object, provided by WebOb and extended by Morepath’s Request. Gives access to a database session, POST data, a browser session and application settings, for example.

  • Model: Typically, this is an object from a database or a collection of them. But can be of any class that is used to carry around information belonging to a concept.

digraph concept {
  Path -> Model
  View -> Model
  View -> Cell
  Cell -> Model
  Cell -> Template[dir=both]
  Cell -> Form
  Form -> Schema
  Form -> Model[dir=both]
  Cell -> Request
  Path -> Request
  View -> Request
  Form -> Request
}

Building blocks of a concept

  • Cell: Renders a HTML view of a concept by using data from a model object.

  • Template: Cells usually produce HTML output by using a PyPugJS Template. Templates use code from their associated cell and model fields to display stuff.

  • Path: Maps URL pattern to a model. It’s easy to link to model instances from cells by using the request.link method. (-> Morepath: Paths and Linking)

  • View: Contains code to prepare and render a HTTP response. In most cases, HTML views should delegate the actual rendering to a cell. (-> Morepath: Views)

  • Form: Renders a HTML form based on a schema, captures the HTTP response and passes the input to the schema for validation. (-> Deform: Basic Usage)

  • Schema: Defines properties that are rendered by a form, validates incoming data that should be written to a model object.

Generate a Concept

The Command ekklesia-generate-concept generates a working concept implementation together with a test. It contains views for creating, editing and displaying instances of the concept.

Let’s generate our Comment concept:

$ ekklesia-generate-concept blog_post

generated concept /home/ts/git/ekklesia-portal/src/ekklesia_portal/concepts/blog_post,
tests are located at tests/concepts/blog_post

The generate source tree looks like this:

$ tree /home/ts/git/ekklesia-portal/src/ekklesia_portal/concepts/blog_post
/home/ts/git/ekklesia-portal/src/ekklesia_portal/concepts/blog_post
├── blog_post_cells.py
├── blog_post_contracts.py
├── blog_post_helper.py
├── blog_posts.py
├── blog_post_views.py
├── __init__.py
└── templates
    ├── blog_post.j2.jade
    ├── blog_posts.j2.jade
    ├── edit_blog_post.j2.jade
    └── new_blog_post.j2.jade
  • blog_post_views.py: path and view functions

    • path blog_posts: handles listing blog posts and creating new ones

    • path blog_post: handles viewing a blog post and editing it

    • view index: list blog posts

    • view new: show form for new blog post

    • view create: handle POST request from the new blog post form

    • view edit: show form for editing an existing blog post

    • view update: handle POST request from the edit blog post form

  • blog_post_contracts.py: bundles blog post schema and form

  • blog_posts.py: collection model used by the blog posts path

  • blog_post_helper.py: utilities that can be used in cells and from other concepts

digraph concept {
  "blog_post_views.py" -> "blog_post_cells.py"
  "blog_post_views.py" -> "blog_post_contracts.py"
  "blog_post_cells.py" -> "templates/*.j2.jade"
  "blog_post_cells.py" -> "blog_posts.py"
}

Generated files for the Comment concept

Tests live outside of the application package:

$ tree /home/ts/git/ekklesia-portal/tests/concepts/blog_post
/home/ts/git/ekklesia-portal/tests/concepts/blog_post
├── __init__.py
└── test_blog_posts.py

Test it

  • unit test a cell: given a model instance, when using an option or not, then produces the expected HTML output

  • functional testing of views: fill out forms, submit and check output with WebTest

Extend it

  • implement cell properties that can be used in a template

  • extend the schema and set custom widgets in blog_post_contracts

  • put more path and view functions in blog_post_views

  • (a bit more advanced) refactor templates with fragments