The built-in web2py wiki

Now you can forget the code we have built-in the previous section (not what you have learned about web2py APIs, just the code of the specific example) as we are going to provide an example of the built-in web2py wiki.

In fact, web2py comes with wiki capabilities including media attachments, tags, tag cloud, page permissions, and support for oembed [oembed] and components (chapter 14). This wiki can be used with any web2py application.

Notice the API of the built-in wiki is still considered experimental and small changes are still possible.

Here we assume we are starting from scratch from a simple clone of the “welcome” application called “wikidemo”. If not, make sure that in db.py migrations are enabled else the new required wiki tables will not be automatically created.

Edit the controller and replace the “index” action with.

  1. def index(): return auth.wiki()

Done! You have a fully working wiki. Please note that wiki needs some tables to be defined, and they’ll only be defined when accessing the controller. If you want them to be readily available, use auth.wiki(resolve=False), and make sure table migrations are turned on: more on that on the Extending-the-auth-wiki-feature section following.

At this point no page has been created and in order to create pages you must be logged-in and you must be member of a group called “wiki_editor” or “wiki_author”. If you are logged-in as administrator the “wiki_editor” group is created automatically and you are made a member. The difference between editors and authors is that the editors can create pages, edit and delete any page, while the authors can create pages (with some optional restrictions) and can only edit/delete the pages they have created.

The auth.wiki() function returns in a dictionary with a key content which is understood by the scaffolding “views/default/index.html”. You can make your own view for this action:

  1. {{extend 'layout.html'}}
  2. {{=content}}

and add extra HTML or code as needed. You do not have to use the “index” action to expose the wiki. You can use an action with a different name.

To try the wiki, simply login into admin, visit the page

  1. http://127.0.0.1:8000/wikidemo/default/index

Then choose a slug (in the publishing business, a slug is a short name given to an article that is in production) and you will be redirected to an empty page where you can edit the content using MARKMIN wiki syntax. A new menu item called “[wiki]“ will allow you to create, search, and edit pages. Wiki pages have URLs like:

  1. http://127.0.0.1:8000/wikidemo/default/index/[slug]

Service pages have names which start by underscore:

  1. http://127.0.0.1:8000/wikidemo/default/index/_create
  2. http://127.0.0.1:8000/wikidemo/default/index/_search
  3. http://127.0.0.1:8000/wikidemo/default/index/_could
  4. http://127.0.0.1:8000/wikidemo/default/index/_recent
  5. http://127.0.0.1:8000/wikidemo/default/index/_edit/...
  6. http://127.0.0.1:8000/wikidemo/default/index/_editmedia/...
  7. http://127.0.0.1:8000/wikidemo/default/index/_preview/...

Try to create more pages such as “index”, “aboutus”, and “contactus”. Try to edit them.

The wiki method has the following signature:

  1. def wiki(self, slug=None, env=None, render='markmin',
  2. manage_permissions=False, force_prefix='',
  3. restrict_search=False, resolve=True,
  4. extra=None, menu_groups=None)

It takes the following arguments:

  • render which defaults to 'markmin' but can be set equal to 'html'. It determines the syntax of the wiki. We will discuss the markmin wiki markup later. If you change it to HTML you can use a wysiwyg javascript editor such as TinyMCE or NicEdit.
  • manage_permissions. This is set to False by default and only recognizes permissions for “wiki_editor” and “wiki_author”. If you change it to True the create/edit page will give the option to specify by name the group(s) whose members have permission to read and edit the page. There is a group “everybody” which includes all users.
  • force_prefix. If set to something like '%(id)s-' it will restrict authors (not editors) to creating pages with a prefix like “[user id]-[page name]“. The prefix can contain the id (“%(id)s”) or the username (“%(username)s”) or any other field from the auth_user table, as long as the corresponding column contains a valid string that would pass URL validation.
  • restrict_search. This defaults to False and any logged-in user can search all wiki pages (but not necessary read or edit them). If set to True, authors can search only their own pages, editors can search everything, other users cannot search anything.
  • menu_groups. This defaults to None and it indicates that wiki management menu (search, create, edit, etc.) is always displayed. You can set it to a list of group names whose members only can see this menu, for example ['wiki_editor','wiki_author']. Notice that even if the menu is exposed to everybody that does not mean everybody is allowed to perform actions listed in the menu since they are regulated by the access control system.

The wiki method has some additional parameters which will be explained later: slug, env, and extra.

MARKMIN basics

The MARKMIN syntax allows you to markup bold text using **bold**, italic text with ''italic'', and code text should be delimited by double inverted quotes. Titles must be prefixed by a #, sections by ##, and sub-sections by ###. Use a minus (-) to prefix an un-ordered item and plus (+) to prefix an ordered item. URLs are automatically converted into links. Here is an example of markmin text:

  1. # This is a title
  2. ## this is a section title
  3. ### this is a subsection title
  4. Text can be **bold**, ''italic'', ``code`` etc.
  5. Learn more at:
  6. http://web2py.com

You can use the extra parameter of auth.wiki to pass extra rendering rules to the MARKMIN helper.

You can find more information about the MARKMIN syntax in chapter 5.

auth.wiki is more powerful than the barebones MARKMIN helpers, supporting oembed and components.

You can use the env parameter of auth.wiki to expose functions to your wiki. For example:

  1. auth.wiki(env=dict(join=lambda a:"-".join(a.split(","))))

allows you to use the markup syntax:

  1. @{join:1,2,3}

This calls the join function passed to env with argument "1,2,3" and will be rendered as 1-2-3.

Oembed protocol

You can type in (or cut-and-paste) any URL into a wiki page and it is rendered as a link to the URL. There are exceptions:

  • If the URL has an image extension, the link is embedded as an image, <img/>.
  • If the URL has an audio extension, the link is embedded as HTML5 audio <audio/>.
  • If the URL has a video extension, the link is embedded as HTML5 video <video/>.
  • If the URL has a MS Office or PDF extension, Google Doc Viewer is embedded, showing the content of the document (only works for public documents).
  • If the URL points to a YouTube page, a Vimeo page, or a Flickr page, web2py contacts the corresponding web service and queries it about the proper way to embed the content. This is done using the oembed protocol.

Here is a complete list of supported formats:

  1. Image (.PNG, .GIF, .JPG, .JPEG)
  2. Audio (.WAV, .OGG, .MP3)
  3. Video (.MOV, .MPE, .MP4, .MPG, .MPG2, .MPEG, .MPEG4, .MOVIE)

Supported via Google Doc Viewer:

  1. Microsoft Excel (.XLS and .XLSX)
  2. Microsoft PowerPoint 2007 / 2010 (.PPTX)
  3. Apple Pages (.PAGES)
  4. Adobe PDF (.PDF)
  5. Adobe Illustrator (.AI)
  6. Adobe Photoshop (.PSD)
  7. Autodesk AutoCad (.DXF)
  8. Scalable Vector Graphics (.SVG)
  9. PostScript (.EPS, .PS)
  10. TrueType (.TTF)
  11. xml Paper Specification (.XPS)

Supported by oembed:

  1. flickr.com
  2. youtube.com
  3. hulu.com
  4. vimeo.com
  5. slideshare.net
  6. qik.com
  7. polleverywhere.com
  8. wordpress.com
  9. revision3.com
  10. viddler.com

This is implemented in the web2py file gluon.contrib.autolinks and specifically in the function expand_one. You can extend oembed support by registering more services. This is done by appending an entry to the EMBED_MAPS list:

  1. from gluon.contrib.autolinks import EMBED_MAPS
  2. EMBED_MAPS.append((re.compile('http://vimeo.com/\S*'),
  3. 'http://vimeo.com/api/oembed.json'))

Referencing wiki content

If you create a wiki page with slug “contactus” you can refer to this page as

  1. @////contactus

Here @//// stands for

  1. @/app/controller/function/

but “app”, “controller”, and “function” are omitted thus assuming default.

Similarly you can use the wiki menu to upload a media file (for example an image) linked to the page. The “manage media” page will show all the files you have uploaded and will also show the proper expression to link the media file. If, for example you upload a file named “test.jpg” with title “beach”, the link expression will be something like:

  1. @////15/beach.jpg

@//// is the same prefix described before. 15 is the id of the record storing the media file. beach is the title. .jpg is the extension of the original file.

If you cut and paste @////15/beach.jpg into wiki pages you embed the image.

Mind that media files are linked to pages and inherit access permission from the pages.

Wiki menus

If you create a page with slug “wiki-menu” page it will be interpreted as a description of the menu. Here is an example:

  1. - Home > @////index
  2. - Info > @////info
  3. - web2py > http://www.web2py.com
  4. - - About us > @////aboutus
  5. - - Contact us > @////contactus

Each line a menu item. We used double dash for nested menu items. The > symbols separates the menu item title from the menu item link.

Mind that the menu is appended to response.menu. It does not replace it. The [wiki] menu item with service functions is added automatically.

Service functions

If, for example, you want to use the wiki to create an editable sidebar you could create a page with slug="sidebar" and then embed it in your layout.html with

  1. {{=auth.wiki(slug='sidebar')}}

Notice that there is nothing special with the word “sidebar”. Any wiki page can be retrieved and embedded at any point in your code. This allows you mix and match wiki functionalities with regular web2py functionalities.

Also note that

  1. auth.wiki('sidebar')

is the same as

  1. auth.wiki(slug='sidebar')

, since the slug kwarg is the first in the method signature. The former gives a slightly simpler syntax.

You can also embed special wiki functions such as the search by tags:

  1. {{=auth.wiki('_search')}}

or the tag cloud:

  1. {{=auth.wiki('_cloud')}}

Extending the auth.wiki feature

When your wiki-enabled app gets more complicated, perhaps you might need to customize the wiki db records managed by the Auth interface or expose customized forms for wiki CRUD tasks. For example, you might want to customize a wiki table record representation or add a new field validator. This is not allowed by default, since the wiki model is defined only after the wiki interface is requested with the auth.wiki() method. To allow access to the wiki specific db setup within the model of your app you must add the following sentence to your model file (i.e. db.py)

  1. # Make sure this is called after the auth instance is created
  2. # and before any change to the wiki tables
  3. auth.wiki(resolve=False)

By using the line above in your model, the wiki tables will be accessible (i.e. wiki_page) for custom CRUD or other db tasks.

Note that you still have to call auth.wiki() in the controller or view in order to expose the wiki interface, since the resolve=False parameter instructs the auth object to just build the wiki model without any other interface setup.

Also, by setting resolve to False in the method call, the wiki tables will be now accessible through the app’s default db interface at <app>/appadmin for managing wiki records.

Another customization possible is adding extra fields to the standard wiki tables (in the same way as with the auth_user table, as described in Chapter 9). Here is how:

  1. # Place this after auth object initialization
  2. auth.settings.extra_fields["wiki_page"] = [Field("ablob", "blob"), ]

The line above adds a blob field to the wiki_page table. There is no need to call

  1. auth.wiki(resolve=False)

for this option, unless you need access to the wiki model for other customizations.

Components

One of the most powerful functions of the new web2py consists in the ability of embedding an action inside another action. We call this a component.

Consider the following model:

  1. db.define_table('thing', Field('name', requires=IS_NOT_EMPTY()))

and the following action:

  1. @auth.requires_login()
  2. def manage_things():
  3. return SQLFORM.grid(db.thing)

This action is special because it returns a widget/helper not a dict of objects. Now we can embed this manage_things action into any view, with

  1. {{=LOAD('default', 'manage_things', ajax=True)}}

This allows the visitor interact with the component via Ajax without reloading the host page that embeds the widget. The action is called via Ajax, inherits the style of the host page, and captures all form submissions and flash messages so that they are handled within the current page. On top of this the SQLFORM.grid widget uses digitally signed URLs to restrict access. More information about components can be found in chapter 13.

Components like the one above can be embedded into wiki pages using the MARKMIN syntax:

  1. ValueError: malformed string

This simply tells web2py that we want to include the “manage_things” action defined in the “default” controller as an Ajax “component”.

Most users will be able to build relatively complex applications simply by using auth.wiki to create pages and menus and embedded custom components into wiki pages. Wikis can be thought of as a mechanism to allow members of the group to create pages, but they can also be thought of as a way to develop applications in a modular way.