Preface
The original text was written in German for a book about Python1. Later Nick Demou made an effort2 to provide a better translation than the one you get by trying a Google Translation of the original German page. Be warned however that most of this text is the result of machine translation cleaned up by someone who doesn't speak German.
If you do speak German we'd love to see you cleaning up bits of this text (an easy way is to enable comments and look for sentences marked with BAD_TRANSLATION). To make it easy to compare this text and the original the few additions have been marked with the comment NEW_TEXT
@copyright: 2007-2012 ReimarBauer
@license: GPL
Contents
1. MoinMoin (1.9) - extensions and adaptations (Reimar Bauer)
2. Introduction
MoinMoin is a wiki software that is written in Python. Currently being developed in version 2.0. - On the homepage (http://moinmo.in), you can find the current state of development. MoinMoin is Free Software (Open Source Software) under the GNU General Public License. This description requires that you download the latest version of the software from the website.
A wiki a World Wide Web pages available collection, read by users, not just in real time can be changed is online. As is usual in hypertext, the individual pages and articles of a wiki is by cross-references (links) connected together.
Since version 1.6 contains a preconfigured MoinMoin Wiki, which is related to the work contained in the Python standard library web server suitable for a personal wiki on your local desktop. This combination also allows, in a development environment (eg Eclipse / PyDev) to debug comfortable. Therefore, please follow the instructions in the installation instructions for the preconfigured Wiki. This wiki also contains the help pages.
Almost anything can be changed to MoinMoin through a sophisticated plug-in concept. You can mount the look of its own Theme transform (see theme). With a Parser (see parser plugins You can interpret and display this content then. A Macro (see macro plugins) serves as a command to insert a little on one side while having a Action ( see action plugins) determine what should be done to "push". A Formatter see formatter plugins) and chapter formatter ) allows an output in any format. Filter (filter plugins) are used to build the search index using the example of file attachments. With XMLRPC (see xmlrpc and xmlrpc plugins), you can communicate via a network connection with MoinMoin.
If you have for improvement, you can create a feature request 3 - possibly even a patch if you have already implemented the feature.
In this text, all examples refer to the standalone server and preconfigured wiki. To start the standalone server, go the directory moin-1.9 and call the program wikiserver.py. You can access your wiki localy by visiting the URL http://localhost:8080/. If you want to change settings, you'll find them the configuration file wikiconfig.py which can be found in the same directory (see HelpOnConfiguration).
To try the examples in the Python interpreter you should stay in the same directory and start python with a command like sudo -u www-data python. Note that you must ensure that the appropriate user rights are granted. If you're working with a test wiki you can include this line in wikiconfig.py: acl_rights_default = u'All:read,write,delete,revert,admin' But NOTE that this line lets every user do everything he likes on any page.
3. Classes and methods of MoinMoin
The following chart 4 shows the main classes that serve a wiki page to your browser. The individual classes represent steps of a complete operation. For example a page is requested by an Action request. Then goes through Page and then Theme adds additional content before finaly an HTML page is sent to your browser.
|
Basic construction of MoinMoin |
What all these classes have in common is that they communicate via a request object with each other. With its own request object (see request), you can use from another application MoinMoin to integrate a wiki into your application.
It's simple to work with MoinMoin code. I will present to you first some classes from the library, but I can not describe in detail all their methods and parameters. I'll limit myself to those that we will use in our examples. Since you have the source code available, you can still find many other interesting classes and methods. You are welcome to visit the developers in their chat room #moin at chat.freenode.net if something is unclear or you want to discuss further options.
The example below shows how you can use some functions to output a page.
First we import Page from class MoinMoin.Page and ScriptContext from class MoinMoin.web.contexts. The Page class (see Page), you need read access to a wiki page. The request Object (see request) you need for all access to the wiki, the place of the Python shell.
With request = ScriptContext() the variable request is assigned with a newly created request object.
The variable pagename is set to a unicode string (u'StartPage') which is the name of the page you want to output.
The following line text = Page(request, pagename).get_raw_body() calls method get_raw_body from class Page (see page.get_raw_body). This reads the content of the page in text.
With print text the content of text are displayed.
3.1. request
The request object is central and conects all the classes (see figure) together. This contains data and objects that are required in many parts of the code, including access to the configuration file.
Besides the request object (ScriptContext), you can use to work in the Python shell, MoinMoin also supports cgi, fcgi, standalone, twisted and wsgi (see HelpOnInstalling) .
MoinMoin since version 1.9 is a WSGI application and uses the werkzeug library. All other deployment methods are implemented by the middleware flup.
Very practical while learning is to use the request object in the Python shell, many of the methods for the plug-in development (see Plugins) are important, and know.
If you already have an application developed in Python, you can communicate with others with this request object with a MoinMoin wiki or use the wiki functionality.
The following example shows how you can make a request object from the Python Shell, and how you can access the wiki from the shell.
You can contact help(request) the docstrings of request. The docstring begins with the following text:
Help on ScriptContext in module MoinMoin.web.contexts object: class ScriptContext(AllContext) | Context to act in scripting environments (e.g. former request_cli). | | For input, sys.stdin is used as 'wsgi.input', output is written directly | to sys.stdout though. | | Method resolution order: | ScriptContext | AllContext | HTTPContext | BaseContext | Context | AuxilaryMixin | __builtin__.object
3.1.1. request.getText
The method request.getText enables the translation of text (eg the caption of the navigation elements) in the default language of the user.
Outputs:
E-Mail mit den Zugangsdaten senden
MoinMoin is developed in English and translates the text into the current user language. See moin-1.9/MoinMoin/i18n in the file de.MoinMoin.po the German translations. Possibly. found in this file there an appropriate output text for your extension. Then you can directly benefit from the existing translations. The translation work is jointly carried out by many users on the wiki MoinMaster 5. This wiki is also the development of the help pages, you'll find in your wiki. If you want to help with translations, you should just try out the action CheckTranslation. So you can get an overview of what needs to be translated.
3.1.2. request.dicts
The methods of the class wiki_dicts gives you access to dictionaries that corespond to Wiki pages. You can use a dictionary to process variables as they create will be used in dicts. They allow you to define each with a key to one or more values (see HelpOnDictionaries).
There are several ways to create dicts. Currently you can create dicts as a wiki page or in the wikiconfig. These are also vereinigbar (CompositeDicts).
The following is an excerpt from moin-1.9/wiki/config/more_samples/dicts_wikiconfig_snippet.
This is an example of how to define the wikiconfig in the dictionary. The default is to map dictionaries from wiki pages.Access to the dicts via the request.dicts.
In HelpOnConfiguration variable page_dict_regex is described. With the variable page_dict_regex you determine which pages are considered as wiki_dicts.
The default is:
page_dict_regex = ur'(?P<all>(?P<key>\S+)Dict)'
If for example you create a page with the name http://localhost:8080/BeispielDict and then create an example of a definition list
1 # ConfigGroups uses groups defined in the configuration file.
2 def groups(self, request):
3 from MoinMoin.datastruct import ConfigGroups
4 # Groups are defined here.
5 groups = {u'EditorGroup': [u'AdminGroup', u'John', u'JoeDoe', u'Editor1'],
6 u'AdminGroup': [u'Admin1', u'Admin2', u'John']}
7 return ConfigGroups(request, groups)
Using composite groups you can combine several methods.
Access to the groups by means of request.groups.
Make sure you have to test a page with the name `http://localhost:8080/FruitsGroup' on and there a bulleted list with the items, such as:
* Aple * Orange * Banana
Now you can easily see what's in the fruit group and what's not.
output:
True Apple Orange Banana
Please note that groups may contain other groups and quite a lot of items, so a list of items does not necessarily make sense.
3.1.4. request.user.may
Using the methods of request.user.may one can check what rights a user has on a specific page.
3.2. Page
The class Page is used for readonly access to a page. It also allows you to access older versions of a page. For operations on wiki pages or any of their attachments, the user's access rights are checked (see HelpOnAccessControlLists).
1 from MoinMoin.Page import Page
3.2.1. Page.exists()
The method Page.exists() is for determining if a page already exists, for example:
output:
True
3.2.2. Page.getRevList()
The method Page.getRevList() gives a list of older versions of a page. The current version is shown first.
output:
[1]
3.2.3. Page.current_rev()
The method Page.current_rev() shows the current revision of a page.
output:
1
3.2.4. Page.getPagePath()
If you want to know the file path to a wiki page, use the method Page.getPagePath().
output:
/home/workspace/moin-1.9/wiki/underlay/pages/StartPage
3.2.5. Page.get_raw_body()
To read the source of a page use Page.get_raw_body().
Output
## Please edit system and help pages ONLY in the master wiki! ## For more information, please see MoinMoin:MoinDev/Translation. ##master-page:FrontPage ##master-date:2007-17-12 21:30:15 #format wiki #language de #pragma section-numbers off = WikiName Wiki = ... ...
3.2.6. Page.send_page()
Using Page.send_page() send the formatted page to the output device (usually the browser). Optionally, you can print a message on the msg parameter. This message is then displayed in a box above the page. If this method is not used for a browser, an http_header should not be sent.
The output in the console starts with:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"> <meta name="robots" content="index,nofollow"> <title>StartPage - MoinMoin DesktopEdition</title> <script type="text/javascript" src="/moin_static190/common/js/common.js"></script> <script type="text/javascript"> <!-- var search_hint = "Search"; //--> </script>
3.3. auth
To authenticate to the wiki, use
user = MoinMoin.auth.handle_login(request, username='foo', password='bar' request.user = user
handle_login returns a user, which has to be set in the request to actually have a request for that user.
This is required e.g. for write access to pages when that needs authentication
3.4. search
Since moin-1.9 xapian the indexed search using regular expressions is available. Prior to 1.9 the slow search method moin search was used. And this is still the default.
To search with xapian, you must use a version> = 1.0.6 and configure wikiconfig like this:
xapian_search = True xapian_stemming = True
If the wiki has pages or even attachments, an index should be formed.
moin --config-dir=/where/your/configdir/is --wiki-url=wiki-url/ index build --mode=add
The xapian is an index of words in the wiki pages. Using filters text data from attachments can also be extracted making the attachments also searchable.
Unlike the default search, but only words can be found which are also included in the index. The Stemming it allows that stems are found. Words can be found part of a regex with security assistance.
3.4.1. search pages
The following section describes how pages of hand a category assignment can be found.
1 from MoinMoin.search import searchPages
A small example about how you can look for pages belonging to the category CategoryHomePage:
3.5. page editor
The class PageEditor is used for all write operations to a wiki page. For operations that are made to wiki pages or any attachments, the user's access rights are checked. (see HelpOnAccessControlLists)
1 from MoinMoin.PageEditor import PageEditor
3.5.1. PageEditor.saveText()
PageEditor.saveText() is used to save text on a page. In addition to the text you want to save, you must also specify the revision of the page as a parameter.
The result of a successful save is:
'Thank you for your changes. Your attention to detail is appreciated.'
This page is now in the Wiki, as you can easily check with a web browser. http://localhost:8080/TestPage
If you repeat the call PageEditor(request, pagename).saveText(text, 0) without a change to the text it will be denied and you'll get an error message triggered by an exception.
MoinMoin.PageEditor.Unchanged: You did not change the page content, not saved!
This error message should always be caught with a try ... except: block, see the following example:
3.5.2. PageEditor.deletePage()
The method PageEditor.deletePage is used to delete a page. If a page is deleted, a new revision is created without any data - the old versions of the page remain however so they can be recovered.
The confirmation of the successful deletion is:
(True, u'Page "TestPage" was successfully deleted!')
3.6. AttachFile
The module AttachFile contains the functions needed to store attachments to wiki pages (see HelpOnActions / AttachFile) or to load attachments. The plan for future versions of MoinMoin is that attachments become like wiki pages (and thus also be versionable). This will then be accounted by this class.
1 from MoinMoin.action import AttachFile
3.6.1. AttachFile.exists()
The method AttachFile.exists() determines whether a particular attachment already exists.
output:
True
3.6.2. AttachFile.getAttachDir()
The method AttachFile.getAttachDir() returns the path of the attachment.
output:
/home/workspace/moin-1.9/wiki/underlay/pages/WikiSandkasten/attachments
3.6.3. AttachFile.getAttachUrl()
The method AttachFile.getAttachUrl() returns a URL for the attachment.
output:
./WikiSandkasten?action=AttachFile&do=get&target=dateianhang.png
3.7. logfile
MoinMoin has two logging systems: eventlog is mainly for events like which browser is used or what pages were viewed in one day, recorded for statistical analysis. editlog stores events like when a page gets created, edited, or deleted, and this information will be displayed by clicking 'Info' on a page.
3.7.1. logfile.eventlog
The class eventlog managing access to wiki pages. You have the option to choose between two filters. 'VIEWPAGE' about the pages that are viewed and 'SAVEPAGE' about those that are saved. The following example counts the hits on StartPage. Before the page views are counted, it is checked whether the user has read access on the page. If this is not the case, the value 0 is issued.
1 from MoinMoin.logfile import eventlog
2 event_type = u'VIEWPAGE'
3 pagename = u'LanguageSetup'
4 event_log = eventlog.EventLog(request)
5 count = 0
6 if request.user.may.read(pagename):
7 test = filter(lambda line: line[1] in event_type and line[2]['pagename'] == pagename, event_log)
8 count = len(test)
9
10 print count
output:
8
3.7.2. logfile.editlog
Using the Class logfile.editlog we can check the editing history of page. Wiki pages can be created, renamed and deleted. This is noted in editlog. u'SAVENEW' is for newly created pages, u'SAVE/RENAME' is for pages that have been renamed, u'SAVE' is for deleted pages (see PageEditor.deletePage). The following example shows the newly created Wiki pages in your wiki, and creation date of the pages. Due to the condition line.action == 'SAVENEW' only pages that have been recreated in your wiki will be printed.
1 from MoinMoin import wikiutil
2 from MoinMoin.logfile import editlog
3 edit_log = editlog.EditLog(request)
4 for line in edit_log.reverse():
5 if (request.user.may.read(line.pagename) and line.action == 'SAVENEW'):
6 timestamp = request.user.getTime(wikiutil.version2timestamp(line.ed_time_usecs))
7 year, month, day = timestamp[0:3]
8 print "%s %s.%s.%s" % (line.pagename, day, month, year)
Sample output:
ExamplePage1 8.9.2009 ExamplePage2 8.9.2009 ExamplePage3 9.9.2009
3.8. wikiutil
The class wikiutil contains various utility functions.
1 from MoinMoin import wikiutil
3.8.1. wikiutil.escape()
The method wikiutil.escape() To mask any HTML special characters. Anything that contains HTML special characters must be escaped before output. This method applies also to the formatter (see page formatter ).
1 print wikiutil.escape(u'<html>')
output:
<html>
3.8.2. wikiutil.createTicket()
By applying the method wikiutil.createTicket() create a ticket that you can use for verification of forms. This allows you to check whether the incoming form data come from a form you created (see Example).
1 print wikiutil.createTicket(request)
output:
0046e8506e.None.show.8ea03fb9d6e50bf60721aa
3.8.3. wikiutil.checkTicket()
The method wikiutil.checkTicket() checks whether the ticket is known to the system (see Example ).
output:
True
3.8.4. wikiutil.invoke_extension_function()
The application of the method wikiutil.invoke_extension_function() allows you to comfortably evaluate parameters of your macros (see Plugins macro ). You can define a default value for the parameter. You can specify a list of allowed input entries. On different inputs an error message is produced.
3.8.5. wikiutil.version2timestamp()
The method wikiutil.version2timestamp() convertes the time information that is used in MoinMoin to the UNIX time format.
3.8.6. wikiutil.timestamp2version()
The method wikiutil.timestamp2version() is the UNIX time information into the equivalent of time spent in MoinMoin.
3.8.7. wikiutil.renderText()
With the method wikiutil.renderText() wiki text will be parsed and displayed in HTML.
1 from MoinMoin import wikiutil
2 from MoinMoin.Page import Page
3 from MoinMoin.web.contexts import ScriptContext
4 request = ScriptContext()
5 pagename = u'NeverExistingPage'
6 request.formatter.page = Page(request, pagename)
7 from MoinMoin.parser.text_moin_wiki import Parser as WikiParser
8 text = "WikiName"
9 wikiutil.renderText(request, WikiParser, text)
output:
u'<span class="anchor" id="line-1"></span><a href="/WikiName">WikiName</a> '
3.9. user
A set of useful functions for the User Object classes. The class User represents a user and provides access to his profile.
1 from MoinMoin import user
3.9.1. user.User(request, auth_method="new-user")
The following example creates a user named "ExampleUser".
1 import os
2 from MoinMoin.web.contexts import ScriptContext
3 request = ScriptContext()
4 from MoinMoin import user, wikiutil
5 the_user = user.User(request, auth_method="new-user")
6 the_user.name = "ExampleUser"
7 the_user.email = "ExampleUser@localhost"
8 the_user.enc_password = user.encodePassword("zoo0Eifi")
9 if (user.isValidName(request, the_user.name) and
10 not user.getUserId(request, the_user.name) and
11 not user.get_by_email_address(request, the_user.email)):
12 print "This user with the userid %s doesn't exist yet." % the_user.id
13 the_user.save()
14 filename = os.path.join(request.cfg.user_dir, the_user.id)
15 if os.path.exists(filename):
16 print "The user file %s was created" % filename
17 else:
18 print "user credential already used - nothing done"
output:
This user with the userid 1257617552.0.33542 doesn't exist yet. The user file /home/workspace/moin-1.9/wiki/data/user/1257617552.0.33542 was created
3.9.2. user.getUserList()
To get all numeric uids of users use user.getUserList.
output:
['1252446015.52.43538', '1252448264.85.4326', '1257617552.0.33542']
3.9.3. user.getUserId()
Use this method to convert user names to user uids. The uid of the user name is required if you want to access a user.
output:
1257617552.0.33542
3.9.4. user.User.name
the username is stored in the variable user.User.name. So if a user with the name ExampleUser you'll get this output:
output:
ExampleUser
3.9.5. user.User.exists()
The method user.User.exists() is used to determine whether a user exists.
output:
True
3.9.6. user.User.isSuperUser()
The method user.User.isSuperUser() Checks whether the user is a superuser. Super users have some special rights. (see HelpOnSuperUser)
output:
False
3.9.7. user.User.save()
With the method user.User.save() the user profile is saved.
3.9.8. user.User.getSubscriptionList()
The method user.User.getSubscriptionList() returns the pages a user has subscribed to.
output:
[]
3.9.9. user.User.subscribe()
The method user.User.subscribe() is used to subscribe to a page. With a return value of True the successful subscription to a page is confirmed.
output:
True
3.9.10. user.User.unsubscribe()
The method user.User.unsubscribe() is used to un-subscripe from of a page.
output:
True
3.9.11. user.User.getTime()
The method user.User.getTime() is used to convert the time in the time zone of the user. If you specify the value 0, the current time is used.
output:
time.struct_time(tm_year=2009, tm_mon=11, tm_mday=7, tm_hour=18, tm_min=21, tm_sec=56, tm_wday=5, tm_yday=311, tm_isdst=0)
3.10. formatter
A Formatter is responsible for output generation. For the HTML output formatter is an instance of formatter.text_html included request object.
The following example imports the HTML Formatter and stores an instance of the formatter in the variable html_formatter:
3.10.1. formatter.text()
If you print text with a Macro you should make sure that HTML control characters are masked. The text formatter method takes this into account, as you can see the second example.
1 print html_formatter.text('Hello World')
output:
Hello World
1 print html_formatter.text('<BR>Hello World<BR>')
output:
<BR>Hello World<BR>
3.10.2. formatter.img()
If you want to pass an image to the formatter use the method formatter.img().
output:
<img src="http://static.wikiwikiweb.de/logos/moinmoin.png" />
3.10.3. formatter.number_list()
The method formatter.number_list() allows you to output a number list (see Example).
3.10.4. formatter.bullet_list()
The method formatter.bullet_list() allows you to output a point list (see Example).
3.10.5. formatter.listitem()
The method formatter.listitem() allows you to spend in a numbered list items or points.
output:
<ul><li>first bulleted line</li></ul>
output:
<ol><li>first numbered line</li></ol>
3.11. xmlrpc
In the previous examples you have been introduced to a few classes and methods of MoinMoin. You have seen how you can interactively in the Python command line or from other programs work with wiki pages. These programs must be run on the same machine that hosts the wiki. This restriction can be lifted with the application of the xmlrpc protocol. This allows data from other computers to be added to a wiki. The Action xmlrpc is prohibited in the default configuration so you can not automatically have write and read access. This setting must be changed by you. To use xmlrpc you should find the necessary configuration in the configuration file wikiconfig.py to limit the rights for write access to wiki pages to a known group of users. Access management is done through the use of Access Control Lists (ACLs). If for example you have created a user TestUser in your wiki, you could have an entry in wikiconfig.py as follows:
actions_excluded = multiconfig.DefaultConfig.actions_excluded[:] actions_excluded.remove('xmlrpc') acl_rights_default = u"TestUser:read,write,delete All:read"
So that you can access with xmlrpc on your Wiki, the wiki must be started (see Example). Currently, the Wiki is operated stand alone. Is it installed on a server (see HelpOnInstalling), use the wikiurl the address of the wiki want to address you. The following example shows how the application is made by the user TestUser with username and password:
name = "TestUser" password = "topsecret" wikiurl = "http://localhost:8080" homewiki = xmlrpclib.ServerProxy(wikiurl + "?action=xmlrpc2", allow_none=True) auth_token = homewiki.getAuthToken(name, password)
Through the getAuthToken the User object is initialized.
3.11.1. xmlrpc.putPage()
Use the xmlrpc.putPage() method to transfer new content on a page. The following example creates the page! "Test page" in your wiki. Please use a user in your wiki through the configuration in wikiconfig.py write permission. In the example, the user TestUser is. With the xmlrpclib.MultiCall() method you accelerate the processing of xmlrpc commands by bundling multiple xmlrpc commands and sending them then as one xmlrpc command.
1 import xmlrpclib
2 name = "TestUser"
3 password = "topsecret"
4 wikiurl = "http://localhost:8080"
5 homewiki = xmlrpclib.ServerProxy(wikiurl + "?action=xmlrpc2", allow_none=True)
6 auth_token = homewiki.getAuthToken(name, password)
7 mc = xmlrpclib.MultiCall(homewiki)
8 mc.applyAuthToken(auth_token)
9 pagename = 'TestPage'
10 text = 'This is a line of text'
11 mc.putPage(pagename, text)
12 result = mc()
3.11.2. xmlrpc.getPage()
Just like you can write pages in the wiki, you can also read pages. Instead of xmlrpc.putPage() method, then use the xmlrpc.getPage() method.
or
1 import xmlrpclib
2 name = "TestUser"
3 password = "topsecret"
4 wikiurl = "http://localhost:8080/"
5 homewiki = xmlrpclib.ServerProxy(wikiurl + "?action=xmlrpc2", allow_none=True)
6 auth_token = homewiki.getAuthToken(name, password)
7 mc = xmlrpclib.MultiCall(homewiki)
8 mc.applyAuthToken(auth_token)
9 pagename = u'TestPage'
10 mc.getPage(pagename)
11 result = mc()
12 success, raw = tuple(result)
13 if success:
14 print raw
3.11.3. xmlrpc.putAttachment()
Using xmlrpc.putAttachment() You can load a file as an attachment to a wiki page.
1 import xmlrpclib
2 name = "TestUser"
3 password = "topsecret"
4 wikiurl = "http://localhost:8080"
5 homewiki = xmlrpclib.ServerProxy(wikiurl + "?action=xmlrpc2", allow_none=True)
6 mc = xmlrpclib.MultiCall(homewiki)
7 auth_token = homewiki.getAuthToken(name, password)
8 mc.applyAuthToken(auth_token)
9 attachname = 'example.txt'
10 text = file(attachname, 'rb+').read()
11 data = xmlrpclib.Binary(text)
12 mc.putAttachment(pagename, attachname, data)
13 result = mc()
3.11.4. xmlrpc.getAttachment()
Using xmlrpc.getAttachment() You can read an attachment of a wiki page as a file (e.g to save it to your computer). With the specified ACL definition an anonymous user must be allowed read of wiki pages and attachments. The example describes the reading of a file example.txt on the page TestPage.
4. Plugins (currently for 1.8, needs to be refactored for 1.9)
If a feature is missing from MoinMoin, it can be easily extended by a plugin. Wiki pages of MoinMoin are stored as HTML in a cache system for. As long as pages do not change this is fine. Complex pages can therefore be quickly presented after their first generation. However sometimes this is not desired. Thus for all the plugins dependencies can be defined that instruct the code that the output of the plugin depends on some change. The variable Dependencies = [] you can determine what dependencies exist (if there are none, the output is assumed to be static). In addition to a simple [], the following dependencies ["time"], ["user"], ["language"], ["pages"], ["namespace"] is used.
Depending on the type of the plugin you must save it under the coresponding directory bellow moin-1.9/wiki/data/plugin.
e.g. an Action plugin must be saved in the directory moin-1.9/wiki/data/plugin/action.
In the following section headers refer to the place where you put your plugins below moin-1.9/wiki/data/plugin so that they are recognized by MoinMoin.
Please make sure that you use the formatter to prevent XSS vulnerability (please pay attention on this issue) (see formatter).
In plugins, it may be necessary to query about permissions the request object (see Example).
4.1. macro
See HelpOnMacros for general information about macros
4.1.1. Hello World
The following example describes a typical hello world program to get to know a Macro call. <<HelloWorld>>
The above example does not use arguments. Since version 1.6 it is possible to evaluate arguments by ArgumentParser. This allows you to explicitly specify in the definition of macros which input is allowed (see wikiutil.invoke_extension_function). Since version 1.7 the execute functions disappears from the macro definition.
With <<HelloWorld>> the first value is applied to the definition of color and the text written in red. <<HelloWorld(color="blue")>> Hello World writes in blue. Whereas <<HelloWorld(color="green")>> prints the error message:
<<HelloWorld: Argument "color" must be one of "red", "blue" -- not "green">>
4.1.2. Handling Attachments
The following example processes CSV data that are stored as file attachments on a page and displays them as an HTML table within the body of the page.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - ShowCSV shows csv file as table
4 @copyright: 2007 MoinMoin:ReimarBauer
5 @license: GNU GPL, see COPYING for details.
6 """
7 Dependencies = ['time'] # do not cache
8
9 import codecs, os
10 from MoinMoin import config, wikiutil
11 from MoinMoin.action import AttachFile
12 from MoinMoin.parser.text_moin_wiki import Parser as wiki_Parser
13 from MoinMoin.parser.text_csv import Parser as CSV_Parser
14
15 def execute(macro, args):
16 request = macro.request
17 formatter = macro.formatter
18 extension = '.csv'
19 pagename = formatter.page.page_name
20 files = AttachFile._get_files(request, pagename)
21 attach_dir = AttachFile.getAttachDir(request, pagename)
22 for file in files:
23 if file.lower().endswith(extension):
24 file_id = codecs.open(os.path.join(attach_dir, file), 'rb', config.charset)
25 parser = CSV_Parser(file_id.read(), request, line_anchors=False)
26 parser.format(request.formatter)
27 result = '. \n[[attachment:%s]]' % file
28 result = wikiutil.escape(result)
29 wiki_Parser(result, request).format(request.formatter)
30 return ""
This program is now in use as <<ShowCSV>> on a wiki page all attachments that are stored on this page and end up with .csv . tables below the table is a link to the file.
|
output of the macro ShowCSV.py |
4.1.3. Use log entry on a page
Macros are well suited to process information from the event-log output. The following example shows the page hits. It displays the hits at the point that you write <<Hits>> in the page. If you use the filter variable, you can set it to either u'VIEWPAGE' or u'SAVEPAGE' (see eventlog. The setting all allows you to specify whether the output of the analysis is about the current page or about all pages of the . Wiki. You can only enter Boolean values eg: <<HITS(all=True)>>, <<HITS(filter='SAVEPAGE')>>.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Hits Macro shows hits of a page
4 @copyright: 2004-2007 MoinMoin:ReimarBauer,
5 2005 BenjaminVrolijk
6 @license: GNU GPL, see COPYING for details.
7 """
8 Dependencies = ['time'] # do not cache
9 from MoinMoin import wikiutil
10 from MoinMoin.logfile import eventlog
11
12 def macro_Hits(macro, all=False, event_type=(u'VIEWPAGE', u'SAVEPAGE')):
13 pagename = macro.formatter.page.page_name
14 event_log = eventlog.EventLog(macro.request)
15 count = 0
16 if not all:
17 test = filter(lambda line: line[1] in event_type and line[2]['pagename'] == pagename, event_log)
18 else:
19 test = filter(lambda line: line[1] in event_type, event_log)
20
21 return u'%d' % len(test)
4.2. action
An Action is offered on the "More Actions" menu if you start the file name with a capital letter. An action is not tied to a specific page as opposed to a Macro (see HelpOnActions). The following example illustrates a Save Action. If you select this action for a page, the source of the page is saved.
for example, http://localhost:8080/FrontPage?action=Save.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Action for saving a page
4 @copyright: 2007 MoinMoin:ReimarBauer
5 @license: GNU GPL, see COPYING for details.
6 """
7
8 from MoinMoin.Page import Page
9
10 def execute(pagename, request):
11 if not request.user.may.read(pagename):
12 Page(request, pagename).send_page()
13 else:
14 rev = request.rev or 0
15 Page(request, pagename,rev=rev).send_raw(content_disposition='attachment')
In this example, you see the code chcking if the user has read access to the page: if not request.user.may.read(pagename):
This query has the effect that you get a hint with an error message when you try to save a page that you can not read. An action must be completed with a message. If you wish to return your own value, use the msg parameter, eg:
Besides the read a user may have more ACL rights, such as: write, delete, revert and admin (see request.user.may).
4.2.1. Action with a POST or GET parameter
An Action can handle variables from GET or POST http methods. The following example shows how you can use an Action to add data to a page. E.g. may find the use, if you have a program in any programming language to write the status information in a wiki.
A secret information is transferred in order for the initiator to prove he is trusted. This is done in this example by a variable whose value is specified in the program. It is also editable, so this variable as on a wiki page is protected and is read out (see wikidicts).
The transmitted data may originate from another machine. The action can handle both GET and POST parameters. The URL to use for GET parameters is http://localhost:8080?action=log&date=200701129&comment=System on&ticket=Secret! You should only use POST when the data is sent over the internet. If your data are sensitive you should also encrypt them.
1 # -*- coding: iso-8859-1 -*-
2 """
3 log - add log entry
4 @license: GNU GPL, see COPYING for details.
5 @copyright: 2007-2008 MoinMoin:ReimarBauer
6 """
7 from MoinMoin import wikiutil
8 from MoinMoin.PageEditor import PageEditor
9 from MoinMoin.Page import Page
10
11 def execute(pagename, request):
12 _ = request.getText
13 ticket = 'Secret!' # you better keep this password in wikiconfig
14 date = wikiutil.escape(request.form.get('date', ['None'])[0])
15 comment = wikiutil.escape(request.form.get('comment', ['None'])[0])
16 secret = wikiutil.escape(request.form.get('ticket', ['wrong'])[0])
17 if (ticket != secret or date == 'None' or comment == 'None'):
18 msg = _("A severe error occured:")
19 request.theme.add_msg(msg, "error")
20 Page(request, pagename).send_page()
21 return
22
23 pagename = u'LogPage' # the name of the output page
24 page = Page(request, pagename)
25 raw = u''
26
27 if page.exists():
28 raw = page.get_raw_body()
29 result = "|| %s || %s ||" % (date, comment)
30 newtext = "%s%s\n" % (raw, result)
31 try:
32 PageEditor(request, pagename).saveText(newtext, 0)
33 info = (_(u"OK!"), "info")
34 except:
35 info = (_(u"A severe error occured:"), "error")
36 request.theme.add_msg(info[0], info[1])
37 Page(request, pagename).send_page()
The following example shows you how to transfer data with this action from another Python program. In result you get back the HTML of the page.
4.2.2. Process only forms that are from private servers
If data are used only by the wiki server, you should use wikiutil.createTicket() and wikiutil.checkTicket() (see wikiutil.createTicket). This allows you to store a secret text on the server that must be specified by the comunicating host.
It makes sense to use some of this data with the POST method. If you use a form, you can transfer in a hidden text box with the ticket. Once the form is completed and the data is sent to the server, your program then checks with wikiutil.checkTicket() whether the right ticket is included. If this is the case, the executed Action. Also in this example it makes sense to verify whether the user is authorized to execute the Action. The following example creates a list box in which the various revisions of the page are available. By selecting a revision of the corresponding page is displayed.
1 # -*- coding: iso-8859-1 -*-
2 """
3 ShowRev - action to select a page revision
4 @license: GNU GPL, see COPYING for details.
5 @copyright: 2007-2008 MoinMoin:ReimarBauer
6 """
7 from MoinMoin import wikiutil
8 from MoinMoin.Page import Page
9
10 def form_html(ticket, revlist):
11 html = []
12 for rev in revlist:
13 html.append("<OPTION>%d</OPTION>" % rev)
14 return '''
15 <form method="post" >
16 <p>select a revision</P>
17 <select name="revision" size="%(len)s">
18 %(option)s
19 </select>
20 <input type="hidden" name="action" value="ShowRev">
21 <input type="submit" name="button" value="Select">
22 <input type="hidden" name="ticket" value="%(ticket)s">
23 </form>''' % {
24 'ticket' : ticket,
25 'option': ''.join(html),
26 'len': min(len(revlist), 5)}
27
28 def execute(pagename, request):
29 _ = request.getText
30 page = Page(request, pagename)
31 if not request.user.may.read(pagename):
32 msg= _('''You are not allowed to read this page.''')
33 request.theme.add_msg(msg, "error")
34 page.send_page()
35 return
36
37 if (request.form.has_key('button')
38 and request.form.has_key('ticket')):
39 if not wikiutil.checkTicket(request, request.form['ticket'][0]):
40 msg = _('''Please use the interactive user interface!''')
41 request.theme.add_msg(msg, "error")
42 page.send_page()
43 return
44 rev = long((request.form.get('revision', ['-1'])[0]))
45 return Page(request, pagename, rev=rev).send_page()
46 ticket = wikiutil.createTicket(request)
47 revlist = page.getRevList()
48 msg = form_html(ticket, revlist)
49 request.theme.add_msg(msg)
50 page.send_page()
4.3. parser
A Parser analyzes the wiki text on its input and outputs it in a desired format (see HelpOnParsers). For example a Parser is used to format source code in color and. Also it's used to analyze the content, so it structures can then be issued There are two ways to instruct the wiki which parser you wish to use. One method (which affects the entire page) is to use a line ike #format plain at the top of a page. The other is to surround the text to be parsed inside three open and three closed curly brackets using hash-bang #! indicator like this:
{{{#!plain This is an example Line 2 }}}
The following example shows the use of #format command.
#format plain Alles was jetzt auf dieser Seite geschrieben wird, wird genauso ausgegeben.
The parser that accomplishes the plain text output is as follows.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - Plain Text Parser, fallback for text/*
4 @copyright: 2000-2002 Juergen Hermann <jh@web.de>
5 @license: GNU GPL, see COPYING for details.
6 """
7
8 Dependencies = []
9
10 class Parser:
11 """
12 Send plain text in a HTML <pre> element.
13 """
14 extensions = '*'
15 Dependencies = []
16
17 def __init__(self, raw, request, **kw):
18 self.raw = raw
19 self.request = request
20 self.form = request.form
21 self._ = request.getText
22
23 def format(self, formatter):
24 """ Send the text. """
25 self.request.write(formatter.preformatted(1))
26 self.request.write(formatter.text(self.raw.expandtabs()))
27 self.request.write(formatter.preformatted(0))
my own parser should start with the name text_x_, otherwise they are not detected. The name is based on the MIME type of the data to be parsed.
4.4. formatter
The contents of a page are output by a Formatter in a specific format. The wiki text can e.g. be output as HTML output (the most usual) or in DocBook format, or any other format, you want for which a formatter exists.
E.g. the url http://localhost:8080/FrontPage?mimetype=text/plain requests the plain text formater
You must define the costs of each method.
The following list is an extract of some possible definitions. Please consult the file MoinMoin/formatter/__init__.py for a complete list.
def startDocument(self, pagename): def startContent(self, content_id="content", **kw): def endContent(self): def text(self, text, **kw): def table(self, on, attrs={}, **kw): def table_row(self, on, attrs={}, **kw): def table_cell(self, on, attrs={}, **kw):
The following example shows some of the methods and the derivation of FormatterBase of the text_plain formatter. It shows a variation where output of tables is formated as a comma separated list. The file name of your formatter should therefore be text_csv.py and then stored in fromatter directory.
1 from MoinMoin.formatter.text_plain import Formatter as FBase
2
3 class Formatter(FBase):
4 def __init__(self, request, **kw):
5 FBase.__init__(self, request, **kw)
6 self._text = []
7 self._table_rows = []
8
9 def text(self, text, **kw):
10 if text:
11 self._text.append(text)
12 return ""
13
14 def table_row(self, on, attrs={}, **kw):
15 if len(self._text) > 0:
16 self._table_rows.append(u','.join(self._text))
17 self._text = []
18 return ""
19
20 def table(self, on, attrs={}, **kw):
21 if len(self._table_rows) > 0:
22 result = u'\n'.join(self._table_rows)
23 return "%s\n\n" % result
24 return ""
Possibly. You want to adapt other methods. The output of a wiki page is accessed by http://localhost:8080/FrontPage?mimetype=text/csv. If you have defined an application in your browser for this MIME type, then the output is redirected.
4.5. filter
Filters are used for indexing of file attachments, ie they extract text information from file attachments.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - PDF filter
4 Depends on: pdftotext command from
5 xpdf-utils package
6
7 @copyright: 2006 MoinMoin:ThomasWaldmann
8 @license: GNU GPL, see COPYING for details.
9 """
10
11 from MoinMoin.filter import execfilter
12
13 def execute(indexobj, filename):
14 return execfilter("pdftotext -enc UTF-8 '%s' -", filename)
4.6. xmlrpc
In the event that you need more xmlrpc methods, you can also extend it. The following example specifies who has run the query.
1 """
2 MoinMoin - Tells who you are
3 and whether the wiki trusts you.
4
5 @copyright: 2005 MoinMoin:ThomasWaldmann
6 @license: GNU GPL, see COPYING for details.
7 """
8
9 def execute(xmlrpcobj, *args):
10 request = xmlrpcobj.request
11 username = request.user.name
12 if not username:
13 username = "<unknown user>"
14 valid = request.user.valid
15 result = "You are %s. valid=%d." % (username.encode("utf-8"), valid)
16 return xmlrpcobj._outstr(result)
4.7. theme
A theme determines how the user interface is presented by MoinMoin. Currently MoinMoin includes 3 Themes (modern, classic and right sidebar). There are also a number of users developed Themes -- see http://moinmo.in/ThemeMarket.
A theme consists of a Python program, CSS and images. The basic structure of the theme modern.py is represented by the following example. Please refer to the actual program file for a complete listing. With the help of the variable name is announced, where appropriate to the theme are CSS files and images.
For the modern theme that can be found under moin-1.9/wiki/htdocs/modern there are 2 directories: css and img.
The css directory contains the files
common.css |
sets the default styles set |
msie.css |
MoinMoin MS Internet Explorer bug workarounds |
print.css |
sets the styles set for a print edition |
projection.css |
sets the styles for a slide-show |
screen.css |
sets the styles for the Darsellung in the browser |
The img directory contains all the images found in modern theme.
The theme modern is derived from the base theme. It has a different header, editor header and footer, using single items.
1 # -*- coding: iso-8859-1 -*-
2 """
3 MoinMoin - modern theme
4 @copyright: 2003-2005 Nir Soffer, Thomas Waldmann
5 @license: GNU GPL, see COPYING for details.
6 """
7 from MoinMoin.theme import ThemeBase
8
9 class Theme(ThemeBase):
10 name = "modern"
11
12 def header(self, d, **kw):
13 html = [
14 self.emit_custom_html(self.cfg.page_header1),]
15
16
17 def editorheader(self, d, **kw):
18 html = [
19 self.emit_custom_html(self.cfg.page_header1),]
20
21
22 def footer(self, d, **keywords):
23 page = d['page']
24 html = [
25 self.pageinfo(page),]
26
27
28 def execute(request):
29 """ Generate and return a theme object """
30 return Theme(request)
The following example is from the modern_cms Theme developed by Nir Soffer.
1 """
2 @copyright (c) 2005 Nir Soffer <nirs@freeshell.org>
3 @copyright (c) 2006 Thomas Waldmann
4 @license: GNU GPL, see COPYING for details
5 """
6 from MoinMoin.theme import modern
7
8 class Theme(modern.Theme):
9
10 name = "modern" # uses "modern" CSS and images
11
12 def shouldShowEditbar(self, page):
13 """ Hide the edit bar if you can't edit """
14 if self.request.user.may.write(page.page_name):
15 return modern.Theme.shouldShowEditbar(
16 self, page)
17 return False
This Theme hides the editbars for non-registered user when the following is mentioned in wikiconfig.py:
acl_rights_default = u"Known:read,write,delete,revert" theme_default = 'modern_cms' theme_force = True
If you want to create a new Theme, the best way is to copy an included theme and modify it according to your wishes. You can find the themes in moin-1.9/MoinMoin/theme/.
The following example is based on a copy of the modern Themes. It only changes the look of the headers.
1 # -*- coding: iso-8859-1 -*-
2 """
3 exampletheme
4 @license: GNU GPL, see COPYING for details.
5 """
6 from MoinMoin.theme import modern
7
8 class Theme(modern.Theme):
9
10 name = "modern"
11
12 def header(self, d, **kw):
13 html = [
14 u'<div id="header">',
15 self.searchform(d),
16 self.username(d),
17 self.navibar(d),
18 u'<div id="pageline">',
19 u'<hr style="display:none;"></div>',
20 u'</div>',
21 self.startPage(),
22 ]
23
24
25 def execute(request):
26 return Theme(request)
These changes give the Wiki a different look.
|
a modified theme based on modern themes |
The idea of the book was to provide an insight into python with examples of popular python projects. Two chapter of the book were about python learning and the last chapter describes first steps for three python projects. At last the publisher decided to drop the third chapter for several reasons. (1)
The idea and code behind Nick Demou's method might be of interest to people that want to translate other MoinMoin pages. For the curious the executive summary of the method is this: take the wiki-source and run a script to try and split the parts that need translation from those that don't. After translating the former use another script to combine the 2 parts back together -- if you want to see the, ugly, code drop an email to NickDemou (2)
by courtesy of Nir Soffer (4)
If you send everything you spend, by the formatter, ensuring that no user of your program is able to inject any HTML or Javascript commands in your application. If this is possible for a user, this is called cross-site scripting (XSS). Through the use of XSS can someone steal your user data.
By entering <script> alert ("XSS !")</ script> (in input fields) you can test any HTML application to XSS problems.