Blame Scripts/Python/centos-web/cgi-bin/Apps/page.py

8f6ee0
# Copyright (C) 2011 The CentOS Project
cc10e6
#
cc10e6
# This program is free software; you can redistribute it and/or modify
cc10e6
# it under the terms of the GNU General Public License as published by
22389d
# the Free Software Foundation; either version 2 of the License, or
22389d
# (at your option) any later version.
cc10e6
#
cc10e6
# This program is distributed in the hope that it will be useful, but
cc10e6
# WITHOUT ANY WARRANTY; without even the implied warranty of
cc10e6
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
cc10e6
# General Public License for more details.
cc10e6
#
cc10e6
# You should have received a copy of the GNU General Public License
cc10e6
# along with this program; if not, write to the Free Software
cc10e6
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
cc10e6
#
22389d
# ------------------------------------------------------------------
cc10e6
# $Id$
22389d
# ------------------------------------------------------------------
92991f
"""Support page construction.
22389d
92991f
The page construction is an XHTML document consisting of several
22389d
independent components that, when put together, provide organization
22389d
to content. Each of these components is set as a method of Layout
22389d
class that can be instantiated later from application specific modules.
22389d
22389d
When you create a new application package, you need to create a page
92991f
module for it and instantiate the Layout class provided here inside
92991f
it.  Later, the following functions must be created: page_content(),
22389d
page_navibar() and main(). These functions are used to define the
92991f
content and navigation bar of your application. Both application
92991f
content and application navigation are logically organized using
92991f
variables passed through the URL.
22389d
22389d
Application
22389d
===========
22389d
22389d
URL variable: app
22389d
22389d
This variable contains the application id. It is a unique numerical
92991f
value that starts at 0 and increments one for each new application
22389d
that might be added. The application identified by number 0 is the one
22389d
used as default when no other application is provided.  The
22389d
application identified by number 0 is added to database the first time
22389d
it is created as part of the initial configuration process.
22389d
22389d
Application is the highest level of organization inside
bd2a79
`webenv.cgi' script. Inside applications, there is content in form
92991f
of pages and entries. Content can be grouped by categories.
22389d
22389d
Pages
22389d
=====
22389d
22389d
URL variable: page
22389d
22389d
This variable contains the page id. It is a unique numerical value
92991f
that starts at 0 and increments in one for each new page added to the
22389d
application. In contrast to applications, the page identified by
22389d
number 0 is not used as default page when no other page is provided.
22389d
This configuration is specific to each application and can be
92991f
customized inside each application individually, using string values
92991f
instead of numerical values when passing values to page variable.
92991f
92991f
Generally, when a page variable isn't passed through the URL, the
92991f
application module uses the `content_list()' method from Layout class
92991f
to display a list of all available content entries while links to
92991f
content pages are displayed in the application navigation bar so users
92991f
can access them.  The unique numerical value of content pages is
92991f
specific to each application, so there is one page 0 for each
92991f
application available. No page is added to database the first time the
92991f
database is created as part of the initial configuration process.
22389d
22389d
Pages contain similar information to that described by contents with
92991f
few exceptions. Pages, in contrast to entries, can differentiate the
22389d
page title from the page name. The page title goes in the page content
22389d
itself and describes what the page is about with a phrase. On the
22389d
other hand, the page name is generaly one word describing the page
92991f
content and is used as link on the application navigation bar.  When
92991f
no page name is explicitly provided, the first word of page title is
92991f
used instead.
22389d
    
22389d
Pages are always accessible inside the same application while contents
22389d
aren't.  Pages are permanently visible and linkend from each
22389d
application specific navigation bar.  This kind of pages can be
22389d
managed by editors or administrators and can be marked as `draft' to
92991f
put it on a special state where it is possible for administrator,
92991f
editors and authors to work on it, but impossible for others to read
92991f
it until the page be marked as `published' by either the page author
92991f
or any members of editor's or administrator's groups.
22389d
92991f
Pages can be converted to entires and the oposite. When convertion
22389d
occurs, unused information looses its meaning and is kept for
22389d
informative purpose, specially in situations when it might be needed
22389d
to realize a convertion back into the former state. Notice that in
92991f
order to realize such a back and forth convertion it is required that
92991f
both pages and entires share the same definition structure.  In fact,
92991f
that they be the same thing, but able to differentiate themselves
92991f
either as page or entry (e.g., through a `type' field.).
22389d
92991f
Pages content is under version control. When a page (or entry) is
22389d
changed, a verification is performed to determine whether the
22389d
information entered in edition matches the last record in the page
22389d
history table. When both the information coming from edition and the
22389d
last record in the page history table are the same (e.g., no change
92991f
happened) the edition action is cancelled and a message is printed out
22389d
to notify the action.  Otherwise, when the information entered in
22389d
edition differs from the last record in the page history table, the
22389d
information comming from edition passes to be the last record in the
22389d
page history table.  In case, a page be reverted to a revision
22389d
different to that one being currently the active page, the reverted
22389d
revision becomes the active page (e.g., by changing a `status' field
22389d
from `false' to `true' in the history table).
22389d
22389d
Categories
22389d
==========
22389d
22389d
Categories exists to organize contents. When an entry is created it is
22389d
automatically linked to a category. Categories are managed by
22389d
administrators and editors only. Categories can be nested one another
22389d
and provide another way of finding information inside the web
22389d
environment.  Categories are specific to each web application, just as
22389d
contents and pages are. The `Unknown' category is created when the
22389d
categories table is created for first time, as part of the initial
22389d
configuration process so if no explicit category assignation is set by
22389d
the user, a default value (the `Unknown' category in this case) is
22389d
used to satisfy the connection between contents and categories.
22389d
22389d
Referential integrity
22389d
=====================
22389d
22389d
Referential integrity is not handle in the logic layer provided by
22389d
this module, but set inside the database system used to store the
22389d
information handled by this module. The most we do about it here, is
22389d
to display a confirmation message before committing such actions, so
22389d
you can be aware of them.
22389d
22389d
"""
cc10e6
cc10e6
import cgi
cc10e6
import cgitb; cgitb.enable()
7e82d6
import ConfigParser
8c93bb
from Apps import xhtml
8c93bb
7e82d6
config = ConfigParser()
147f1a
qs = cgi.parse()
147f1a
715121
def qs_args(names={}):
147f1a
    """Returns query string arguments.
147f1a
147f1a
    The query string arguments are used to build links dynamically
147f1a
    and, this way, to create a browsable and logically organized web
147f1a
    environment.  Such a construction generally needs to retrive some
147f1a
    of the values previously passed to the query string and add new
147f1a
    ones to it.
147f1a
147f1a
    names: A dictionary containing the variable name and value pair
147f1a
        used to build a new query string. 
147f1a
            
147f1a
    When a variable is provied without a value, then its value is
147f1a
    retrived from the current query string. If a value isn't found
147f1a
    there neither, then the variable is removed from the new query
147f1a
    string.
147f1a
147f1a
    When a variable is provided with its value, then its value is used
147f1a
    to build the new query string.
147f1a
147f1a
    """
147f1a
    output = ''
147f1a
147f1a
    names_keys = names.keys()
147f1a
    names_keys.sort()
715121
147f1a
    for key in names_keys:
147f1a
        if names[key] == '':
147f1a
            if key in qs:
147f1a
                names[key] = qs[key][0]
147f1a
            else:
147f1a
                continue
147f1a
        if output == '':
147f1a
            output = '?'
147f1a
        else:
147f1a
            output += '&'
147f1a
        output += key + '=' + str(names[key])
147f1a
7e82d6
    return config.get('webserver', 'baseurl') + output
147f1a
8c93bb
class Layout(xhtml.Strict):
147f1a
    """The Page Layout.
147f1a
    
147f1a
    The page layout is made by combining XHTML tags in specific ways.
147f1a
    These specific combinations make the page components which in turn
147f1a
    can be also combined. Some of these components can be reused and
147f1a
    others don't. The goal of this class is to define what such
147f1a
    components are and describe them well in order to understand how
147f1a
    to use them from application modules when building XHTML documents
147f1a
    dynamically.
147f1a
147f1a
    The page layout is initialized with a functional layout that can
147f1a
    be used as reference inside application modules, to create
147f1a
    variations of it. Generally, inside application packages, this
147f1a
    class is instantiated in a module named `page', variables are
147f1a
    reset and functions created in order to satisfy that application
147f1a
    needs. When you need to output one of the page components then you
147f1a
    use this class instantiated methods. When the method you need
147f1a
    doesn't exist in this class, then it is a good time for it to be
147f1a
    created, here ;). 
147f1a
147f1a
    Notice that most methods defined in this class make direct use of
147f1a
    methods defined by Strict class inside the `xhtml' module. The
147f1a
    Strict class inside xhtml module is inherited inside this class so
147f1a
    all the methods there are also available here. Methods which
147f1a
    doesn't make a direct use of Strict methods are dependencies of
147f1a
    those which do make direct use of Strict methods.
147f1a
147f1a
    """
8f6ee0
8f6ee0
    def __init__(self):
8f6ee0
        """Initialize page data."""
8f6ee0
        self.name = 'Home'
8f6ee0
        self.title = 'The CentOS Project'
8f6ee0
        self.description = 'Community Enterprise Operating System'
8f6ee0
        self.keywords = 'centos, project, community, enterprise, operating system'
8f6ee0
        self.copyright = '2009-2011 The CentOS Project. All rights reserved.'
8f6ee0
        self.language = 'en'
8f6ee0
f19343
        # Define page header. This is the information displayed
f19343
        # between the page top and the page content.
f19343
        self.header = self.logo()
147f1a
        self.header += self.google_ad()
e1d4e1
        self.header += self.navibar()
e1d4e1
        self.header += self.releases()
e1d4e1
        self.header += self.page_links()
e1d4e1
        self.header += self.page_navibar()
f19343
f19343
        # Define page body. This is the information displayed between
f19343
        # the page header and page footer.
e1d4e1
        self.body = 'None'
f19343
f19343
        # Define page footer. This is the information displayed
f19343
        # between the page bottom and the page content, the last
f19343
        # information displayed in the page.
f19343
        self.footer = self.credits()
8f6ee0
8c93bb
    def logo(self):
e1d4e1
        """Returns The CentOS Logo.
cc10e6
8f6ee0
        The page logo is displayed on the top-left corner of the page.
8f6ee0
        We use this area to show The CentOS Logo, the main visual
8f6ee0
        representation of The CentOS Project. In order to print the
8f6ee0
        page logo correctly, the image related must be 78 pixels of
8f6ee0
        height.
7e8dd3
8f6ee0
        """
8f6ee0
        attrs = []
8f6ee0
        attrs.append({'id': 'logo'})
bd2a79
        attrs.append({'title': 'Community Enterprise Operating System', 'href': '/webenv/'})
7e82d6
        attrs.append({'src': config.get('webserver','baseurl') + 'public/images/centos-logo.png', 'alt': 'CentOS'})
cc10e6
8c93bb
        return self.tag_div(attrs[0], [8,1], self.tag_a(attrs[1], [12,1], self.tag_img(attrs[2], [0,0]), 0), 1)
cc10e6
147f1a
    def google_ad_example(self):
147f1a
        """Returns Google advertisement for offline testings."""
92991f
        title = 'Google Advertisement'
7e82d6
        url = config.get('webserver','baseurl') + 'public/images/ads-sample-468x60.png'
92991f
        image = self.tag_img({'src': url, 'alt': title}, [0,0])
92991f
        link = self.tag_a({'href': url, 'title': title}, [12,1], image)
92991f
        output = self.tag_div({'class':'google-ad'}, [8,1], link, 1)
147f1a
        output += self.separator({'class':'page-line'}, [8,1])
147f1a
147f1a
        return output
147f1a
147f1a
    def google_ad(self):
147f1a
        """Returns Google advertisement for online using."""
147f1a
147f1a
        properties = {}
147f1a
        properties['google_ad_client']    = 'pub-6973128787810819'
147f1a
        properties['google_ad_width']     = '468'
147f1a
        properties['google_ad_height']    = '60'
147f1a
        properties['google_ad_format']    = '468x60_as'
147f1a
        properties['google_ad_type']      = 'text_image'
147f1a
        properties['google_ad_channel']   = ''
147f1a
        properties['google_color_border'] = '204c8d'
147f1a
        properties['google_color_bg']     = '345c97'
147f1a
        properties['google_color_link']   = '0000FF'
147f1a
        properties['google_color_text']   = 'FFFFFF'
147f1a
        properties['google_color_url']    = '008000'
147f1a
147f1a
        attrs = {}
147f1a
        attrs['type'] = "text/javascript"
147f1a
147f1a
        output = '
147f1a
        for key, value in properties.iteritems():
147f1a
            output += ' '*16 + key + '="' + value + '";\n'
147f1a
        output += ' '*16 + '//-->\n'
147f1a
147f1a
        properties = self.tag_script(attrs, [12,1], output, 1)
147f1a
147f1a
        attrs['src'] = "http://pagead2.googlesyndication.com/pagead/show_ads.js"
147f1a
147f1a
        source = self.tag_script(attrs, [12,1], ' ', 0)
147f1a
147f1a
        output = self.tag_div({'class':'google-ad'}, [8,1], properties + source, 1)
147f1a
        output += self.separator({'class':'page-line'}, [8,1])
147f1a
8f6ee0
        return output
cc10e6
e1d4e1
    def navibar(self):
92991f
        """Returns webenv navigation bar. 
cc10e6
    
92991f
        The webenv navigation bar organizes links to main web
e1d4e1
        applications The CentOS Project makes use of. Links to these
e1d4e1
        web applications stay always visible, no matter what web
92991f
        application the user be visiting (e.g., Wiki, Lists, Forums,
92991f
        Projects, Bugs, Docs, Downloads and Sponsors.).  Notice that
bd2a79
        some of these web applications are out of `webenv.cgi'
92991f
        scope and they need to code their own webenv navigation bars
bd2a79
        in a way that coincide the one set by `webenv.cgi'.
cc10e6
8f6ee0
        """
8f6ee0
        names = ['Home', 'Wiki', 'Lists', 'Forums', 'Projects', 'Bugs', 'Docs', 'Downloads', 'Sponsors']
8f6ee0
        attrs = []
8f6ee0
        focus = self.name
cc10e6
8f6ee0
        for i in range(len(names)):
8c93bb
            if names[i].lower() == 'home':
bd2a79
                attrs.append({'href': '/webenv/'})
8c93bb
            else:
bd2a79
                attrs.append({'href': '/webenv/?app=' + names[i].lower()})
8f6ee0
8c93bb
        tabs = self.navibar_tabs(names, attrs, focus)
8c93bb
        tabs += self.separator()
8f6ee0
8f6ee0
        return tabs
8f6ee0
8c93bb
    def navibar_tabs(self, names, attrs, focus=''):
8f6ee0
        """Returns navigation tabs.
8f6ee0
e1d4e1
        The navigation tabs are the smaller components a navigation
e1d4e1
        bar like "top-level navigation bar" and "application
e1d4e1
        navigation bar" are made of.
8f6ee0
e1d4e1
        names: List containing link names of tabs.
8f6ee0
e1d4e1
        attrs: List containing a dictionary for each tab link name
e1d4e1
            inside the `names' list. Dictionaries inside attrs
e1d4e1
            argument contain the link attributes (e.g., accesskey,
e1d4e1
            title, and href) used by link names so they can be
e1d4e1
            linkable once rendered.
cc10e6
    
e1d4e1
        focus: Name of the link marked as current.
cc10e6
    
8f6ee0
        """
8f6ee0
        navibar_tabs = ''
cc10e6
8f6ee0
        for i in range(len(names)):
22389d
            output = self.tag_span('', [0,0], str(names[i]))
22389d
            output = self.tag_a(attrs[i], [16,1], output)
8f6ee0
            if str(names[i]).lower() == focus.lower():
22389d
                output = self.tag_span({'class': 'current'}, [12,1], output, 1)
8f6ee0
            else:
22389d
                output = self.tag_span('', [12,1], output, 1)
22389d
            navibar_tabs += output
cc10e6
8c93bb
        return self.tag_div({'class': 'tabs'}, [8,1], navibar_tabs, 1)
cc10e6
147f1a
    def releases(self):
e1d4e1
        """Returns The CentOS Distribution last releases.
e1d4e1
e1d4e1
        This method introduces the `releases' method by providing
e1d4e1
        links to it.
e1d4e1
e1d4e1
        names: List containing release numbers in the form M.N, where M
e1d4e1
            means major release and N minor release.
e1d4e1
e1d4e1
        attrs: List containing a dictionary for each release number
e1d4e1
            provided in `names' argument. These dictionaries provide
e1d4e1
            the link attributes required by release numbers in order
e1d4e1
            for them to be transformed into valid links once the page
e1d4e1
            be rendered.
e1d4e1
        
e1d4e1
        """
8f6ee0
        releases = ''
7e8dd3
147f1a
        names = []
147f1a
        names.append('6.0')
147f1a
147f1a
        attrs = []
147f1a
        attrs.append({'href': qs_args({'p':'releases', 'id': 6.0})})
147f1a
147f1a
        
147f1a
        title = self.tag_a({'href': qs_args({'p':'releases'})}, [0,0], 'Last Releases') + ':'
8c93bb
        title = self.tag_span({'class': 'title'}, [16,1], title)
cc10e6
8f6ee0
        for i in range(len(names)):
8c93bb
            link = self.tag_a(attrs[i], [20,1], names[i])
8f6ee0
            if i == len(names) - 1:
8c93bb
                span = self.tag_span({'class': 'last release'}, [16,1], link, 1) 
8f6ee0
            else:
8c93bb
                span = self.tag_span({'class': 'release'}, [16,1], link, 1) 
8f6ee0
            releases += span
8c93bb
        releases = self.tag_div({'class': 'left'}, [12,1], title + releases, 1)
cc10e6
8c93bb
        rsslink = self.tag_span('', [0,0], 'RSS')
147f1a
        rsslink = self.tag_a({'href': qs_args({'rss':'releases'}), 'title': 'RSS'}, [20,1], rsslink)
8c93bb
        rsslink = self.tag_span({'class': 'rss'}, [16,1], rsslink, 1)
8c93bb
        rsslink = self.tag_div({'class': 'right'}, [12, 1], rsslink, 1)
7e8dd3
8c93bb
        return self.tag_div({'id': 'last-releases'}, [8,1], releases + rsslink, 1)
7e8dd3
e1d4e1
    def user_links_logs(self):
e1d4e1
        """Return links related to user's logs.
e1d4e1
        
e1d4e1
        This function introduces the `logs' module. The `logs' module
e1d4e1
        registers all user's activity, from login to logout. This link
e1d4e1
        must be display/accessible only after a user has successfully
e1d4e1
        login.
cc10e6
e1d4e1
        """
147f1a
        last_visit = self.tag_a({'href': qs_args({'app':'', 'p':'logs'})}, [0,0], 'Logs')
e1d4e1
        return self.tag_div({'class': 'logs'}, [12, 1], last_visit, 1)
cc10e6
e1d4e1
    def user_links_session(self):
e1d4e1
        """Returns links related to user's session.
e1d4e1
        
e1d4e1
        This function introduces the `session' module. The `session'
e1d4e1
        module provides state to user interactions so their action can
e1d4e1
        be registered individually.
cc10e6
e1d4e1
        """
8f6ee0
        names = []
8f6ee0
        attrs = []
8f6ee0
        session = ''
8f6ee0
8f6ee0
        names.append('Lost your password?')
147f1a
        attrs.append({'href': qs_args({'app':'', 'p':'lostpwd'})})
8f6ee0
        names.append('Register')
147f1a
        attrs.append({'href': qs_args({'app':'', 'p':'register'})})
8f6ee0
        names.append('Login')
147f1a
        attrs.append({'href': qs_args({'app':'', 'p':'login'})})
8f6ee0
8f6ee0
        for i in range(len(names)):
8c93bb
            output = self.tag_a(attrs[i], [20,1], str(names[i]), 0)
8f6ee0
            if i == len(names) - 1:
8c93bb
                output = self.tag_span({'class': 'last'}, [16,1], output, 1)
8f6ee0
            else:
8c93bb
                output = self.tag_span('', [16,1], output, 1)
8f6ee0
            session += output
8f6ee0
8c93bb
        return self.tag_div({'class': 'session'}, [12,1], session, 1)
cc10e6
bd2a79
    def user_links_trails(self, names=['None'], attrs=[{'href': '/webenv/'}]):
8f6ee0
        """Returns page trails (a.k.a. breadcrumbs).
cc10e6
    
e1d4e1
        The page breadcrumbs record the last pages the user visited
e1d4e1
        inside the current web application. Notice that page
e1d4e1
        breadcrumbs are user-specific information, so it isn't
e1d4e1
        possible to implement them until a way to manage user sessions
bd2a79
        be implemeneted inside `webenv.cgi' script. Until then,
e1d4e1
        keep the tag construction commented and return an empty value.
e1d4e1
e1d4e1
        names: List with trail link names.
e1d4e1
e1d4e1
        attrs: Dictionary with trail link attributes.
8f6ee0
8f6ee0
        """
8f6ee0
        links = ''
8f6ee0
8f6ee0
        for i in range(len(names)):
8f6ee0
            if i == len(names) - 1:
22389d
                output = self.tag_span({'class':'last'}, [16,1], self.tag_a(attrs[i], [20, 1], names[i]), 1)
8f6ee0
            else:
22389d
                output = self.tag_span('', [16,1], self.tag_a(attrs[i], [20, 1], names[i], 0), 1)
22389d
            links += output
cc10e6
8c93bb
        return self.tag_div({'class': 'trail'}, [12,1], links, 1)
cc10e6
e1d4e1
    def user_links(self):
e1d4e1
        """Returns user related links.
cc10e6
8f6ee0
        The user links are specific to each web application. They are
e1d4e1
        shown over the application navigation bar.
cc10e6
8f6ee0
        """
e1d4e1
        userlinks = self.user_links_logs()
e1d4e1
        userlinks += self.user_links_session()
e1d4e1
        userlinks += self.user_links_trails()
cc10e6
8c93bb
        return self.tag_div({'class': 'userlinks'}, [8,1], userlinks, 1)
cc10e6
bd2a79
    def page_navibar(self, names=['Welcome'], attrs=[{'href':'/webenv/?p=welcome'}], focus='Welcome'):
e1d4e1
        """Returns navigation bar for application main pages.
e1d4e1
       
e1d4e1
        names: List containing link names.
e1d4e1
e1d4e1
        attrs: List containing one dictionary for each link name in
e1d4e1
            `names' argument. Dictionaries here contain the link
e1d4e1
            attributes needed to make linkable tabs once the page is
e1d4e1
            rendered.
8f6ee0
e1d4e1
        """
8c93bb
        navibar_app = self.navibar_tabs(names, attrs, focus)
8c93bb
        navibar_app += self.separator({'class': 'page-line white'}, [8,1])
8f6ee0
8f6ee0
        return navibar_app
cc10e6
8c93bb
    def separator(self, attrs={'class': 'page-line'}, indent=[16,1]):
e1d4e1
        """Returns separator.
e1d4e1
e1d4e1
        The separator construction is mainly used to clear both sides
e1d4e1
        inside the page, specially when floating elements are around.
e1d4e1
        
e1d4e1
        attrs: Dictionary containing hr's div attributes.
e1d4e1
        
e1d4e1
        indent: List containing hr's div indentation values.
e1d4e1
        
e1d4e1
        """
8c93bb
        line = self.tag_hr({'style': 'display:none;'}, [0,0])
8c93bb
        line = self.tag_div(attrs, indent, line)
cc10e6
8c93bb
        return line
cc10e6
8c93bb
    def license(self):
e1d4e1
        """Retruns license link."""
8f6ee0
        license = 'Creative Commons Attribution-Share Alike 3.0 Unported License'
8c93bb
        license = self.tag_a({'href': 'http://creativecommons.org/licenses/by-sa/3.0/'}, [0,0], license) + '.'
cffc47
8f6ee0
        return license
cc10e6
8c93bb
    def metadata(self):
e1d4e1
        """Returns metadata."""
8c93bb
        metadata = self.tag_meta({'http-equiv': 'content-type', 'content': 'text/html; charset=UTF-8'}, [4,1])
8c93bb
        metadata += self.tag_meta({'http-equiv': 'content-style-type', 'content': 'text/css'}, [4,0])
8c93bb
        metadata += self.tag_meta({'http-equiv': 'content-language', 'content': str(self.language)}, [4,1])
8c93bb
        metadata += self.tag_meta({'name': 'keywords', 'content': str(self.keywords)}, [4,0])
8c93bb
        metadata += self.tag_meta({'name': 'description', 'content': str(self.description)}, [4,1])
8c93bb
        metadata += self.tag_meta({'name': 'copyright', 'content': 'Copyright © ' + str(self.copyright)}, [4,0])
8c93bb
        metadata += self.tag_title('', [4,1], self.title)
7e82d6
        metadata += self.tag_link({'href': config.get('webserver','baseurl') + 'public/stylesheet.css','rel': 'stylesheet', 'type': 'text/css'}, [4,0])
7e82d6
        metadata += self.tag_link({'href': config.get('webserver','baseurl') + 'public/centos-fav.png', 'rel': 'shortcut icon', 'type': 'image/png'}, [4,1])
cc10e6
8c93bb
        return self.tag_head('', [0,1], metadata)
cc10e6
147f1a
    def searchform(self, size=15):
3694e5
        """Returns search form.
d59f66
147f1a
        The search form redirects user from the current page onto the
147f1a
        search page, where the keywords previously introduced in the
147f1a
        input field are processed then.
3694e5
        
147f1a
        size: A number discribing how large the search box is.
e1d4e1
3694e5
        """
147f1a
        input = self.tag_input({'type':'text', 'value':'', 'size':size}, [0,0])
e1d4e1
147f1a
        action = self.tag_dt({}, [20,1], 'Search')
d59f66
        action += self.tag_dd({}, [20,1], input)
22389d
        action = self.tag_dl({'class':'search'}, [16,1], action, 1)
22389d
147f1a
        return self.tag_form({'action': qs_args({'app':'', 'p':'search'}), 
22389d
                              'method':'post', 'title':'Search'},
22389d
                              [12,1], action, 1)
e1d4e1
22389d
    def content_resumen(self, attrs, id, title, user_id, commit_date,
22389d
                        update_date, category_id, comments, abstract):
22389d
        """Returns content resumen.
d59f66
147f1a
        The content resumen is used to build the list of contents,
147f1a
        output by `content_list()' method. The content resumen intends
147f1a
        to be concise and informative so the user can grab a general
147f1a
        idea about the related content and what it is about.
d59f66
147f1a
        attrs: A dictionary discribing the rows style.  This is useful
147f1a
            to alternate the row background colors.
e1d4e1
147f1a
        id: A unique numerical value referring the content
147f1a
            identification. This is the value used on administrative
147f1a
            tasks like updating and deleting.
e1d4e1
        
147f1a
        title: A few words phrase describing the content, up to 255
147f1a
            characters.
e1d4e1
            
147f1a
        author_id: A string referring the user email address, as
147f1a
            specified by RFC2822. The user email address is used as id
147f1a
            inside The CentOS User LDAP server, where user specific
147f1a
            information (e.g., surname, lastname, office, phone, etc.)
147f1a
            are stored in. This is the field that bonds the user with
147f1a
            the content he/she produces.
e1d4e1
            
147f1a
        commit_date: A string referring the timestamp the content
147f1a
            arrived to database for time.
e1d4e1
147f1a
        update_date: A string representing the timestamp the content
147f1a
            was updated/revised for last time.
e1d4e1
147f1a
        category_id: A number refering the category id the content is
147f1a
            attached to.
d59f66
147f1a
        abstract: One paragraphs describing the content.  This
147f1a
            information is used to build the page metadata
147f1a
            information. When this value is not provided no abstract
147f1a
            information is displayed in the page, but the 
147f1a
            name="description".../> is built using article's first 255
147f1a
            characters.
e1d4e1
147f1a
        comments: A number representing how many comments the content
147f1a
            has received since it is in the database.
e1d4e1
22389d
        The content itself is not displayed in the resumen, but in
147f1a
        `content_details()'.
e1d4e1
e1d4e1
        """
147f1a
        title = self.tag_a({'href': qs_args({'app':'', 'p':'entry', 'id':id})}, [0,0], title)
d59f66
        title = self.tag_h3({'class': 'title'}, [20,1], title, 0)
147f1a
        info = self.content_info(id, user_id, commit_date,
147f1a
                                 update_date, category_id, comments,
147f1a
                                 abstract)
22389d
        return self.tag_div(attrs, [16,1], title + info, 1)
22389d
22389d
    def pagination(self):
22389d
        """Return content pagination."""
147f1a
        previous = self.tag_a({'href':''}, [0,0], 'Previous')
147f1a
        previous = self.tag_span({'class':'previous'}, [20,1], previous)
147f1a
        next = self.tag_a({'href':''}, [0,0], 'Next')
147f1a
        next = self.tag_span({'class':'next'}, [20,1], next)
147f1a
        separator = self.separator({'class':'page-line'}, [20,1])
147f1a
        return self.tag_div({'class':'pagination'}, [16,1], previous +
147f1a
                            next + separator, 1)
22389d
147f1a
    def content_info(self, content_id, user_id, commit_date,
147f1a
                     update_date, category_id, comments, abstract):
22389d
        """Return content information.
22389d
22389d
        The content information provides a reduced view of content so
22389d
        people can make themselves an idea of what the content talks
22389d
        about. The content information displays content's title,
22389d
        author, timestamp, related category, number of comments and an
22389d
        abstract of the whole content.
22389d
22389d
        """
22389d
        categories = []
22389d
        categories.append('Unknown')
22389d
        categories.append('Erratas')
22389d
        categories.append('Articles')
22389d
        categories.append('Events')
22389d
22389d
        if category_id <= len(categories):
22389d
            category_name = categories[category_id].capitalize()
22389d
        else:
22389d
            category_id = 0
22389d
            category_name = categories[category_id].capitalize()
22389d
147f1a
        category_name = self.tag_a({'href': qs_args({'app':'', 'p':'categories', 'id':category_id})}, [0,0], category_name)
22389d
        category_name = self.tag_span({'class':'category'}, [24,1], category_name) 
22389d
22389d
        users = {}
22389d
        users['al@centos.org'] = 'Alain Reguera Delgado'
22389d
        users['ana@centos.org'] = 'Ana Tamara Reguera Gattorno'
22389d
        users['alina@centos.org'] = 'Alina Reguera Gattorno'
22389d
22389d
        if user_id in users.keys():
22389d
            user_name = self.tag_a({'href':'mailto:' + user_id}, [0,0], users[user_id])
22389d
            user_name = self.tag_span({'class':'author'}, [24,1], 'Written by ' + user_name)
22389d
d59f66
        if update_date != commit_date:
22389d
            date = self.tag_span({'class':'date'}, [24,1], update_date)
d59f66
        else:
22389d
            date = self.tag_span({'class':'date'}, [24,1], commit_date)
22389d
22389d
            
147f1a
        comments_attrs = {'href': qs_args({'app':'', 'p':'entry', 'id':content_id}) + '#comments'}
d59f66
        if comments == 1:
22389d
            comments = self.tag_a(comments_attrs, [0,0], str(comments) + ' comment')
d59f66
        elif comments > 1:
22389d
            comments = self.tag_a(comments_attrs, [0,0], str(comments) + ' comments')
d59f66
        else:
d59f66
            comments = 'No comments'
22389d
        comments = self.tag_span({'class':'comment'}, [24,1], comments)
22389d
22389d
        abstract = self.tag_p({'class':'abstract'}, [24,1], abstract)
d59f66
22389d
        return self.tag_div({'class': 'info'}, [20,1], user_name + date + category_name + comments + abstract, 1)
d59f66
22389d
    def content_list(self):
22389d
        """Return list of content.
d59f66
        
d59f66
        The list of content is used to explore the content available
d59f66
        inside specific pages of specific web applications. The
d59f66
        information is displayed through paginated rows of content
d59f66
        that can be filtered to reduce the search results based on
d59f66
        patterns.  By default, the list of content displays 15 rows,
d59f66
        but this value can be changed in user's preferences.
d59f66
d59f66
        """
d59f66
        output = ''
d59f66
        count = 0
d59f66
        rows = []
22389d
        rows.append([0, 'Introduction to CentOS Web Environment',
d59f66
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
d59f66
                    0,
22389d
                    0,
22389d
                    'This is the abstract paragrah of content. '*10])
22389d
        rows.append([1, 'Creating New Applications',
d59f66
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
22389d
                    2,
d59f66
                    1,
22389d
                    'This is the abstract paragrah of content. '*5])
22389d
        rows.append([2, 'Texinfo Documentation Backend',
d59f66
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
22389d
                    1,
d59f66
                    5,
22389d
                    'This is the abstract paragrah of content. '*8])
d59f66
d59f66
        for row in rows:
d59f66
            if count == 0:
d59f66
                attrs = {'class': 'dark row'}
d59f66
                count += 1
d59f66
            else:
d59f66
                attrs = {'class': 'light row'}
d59f66
                count = 0
22389d
            output += self.content_resumen(attrs, *row)
d59f66
22389d
        list = output + self.pagination() + self.separator()
22389d
        list = self.tag_div({'id':'content-list'}, [12,1], list, 1)
147f1a
        actions = self.searchform() + self.categories() + self.archives()
22389d
        actions = self.tag_div({'id':'content-actions'}, [8,1], actions, 1)
d59f66
22389d
        return actions + list
d59f66
22389d
    def content_details(self):
22389d
        """Return content details.
22389d
        
22389d
        The content detail is shown for contents and pages.
d59f66
        """
22389d
        output = ''
22389d
        rows = []
22389d
        rows.append([0, 'Introduction to CentOS Web Environment',
22389d
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
22389d
                    0,
22389d
                    0,
22389d
                    'This is the abstract paragrah of content. '*10,
22389d
                    'This is the first paragraph of content'*10 + "\n"
22389d
                    'This is the second paragraph of content'*20 +
22389d
                    "\n" + 'This is the third paragraph of content.'*10 + "\n"])
22389d
        rows.append([1, 'Creating New Applications',
22389d
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
22389d
                    2,
22389d
                    1,
22389d
                    'This is the abstract paragrah of content. '*5,
22389d
                    "This is the first paragraph of content\n\
22389d
                    This is the second paragraph of content.\n\
22389d
                    This is the third paragraph of content."])
22389d
        rows.append([2, 'Texinfo Documentation Backend',
22389d
                    'al@centos.org',
22389d
                    '2011-8-30 12:33:11', 
22389d
                    '2011-8-30 12:33:11', 
22389d
                    1,
22389d
                    5,
22389d
                    'This is the abstract paragrah of content. '*8,
22389d
                    "This is the first paragraph of content.\n\
22389d
                    This is the second paragraph of content.\n\
22389d
                    This is the third paragraph of content."])
d59f66
147f1a
        if 'id' in qs:
147f1a
            id = int(qs['id'][0])
22389d
            title = rows[id][1]
22389d
            email = rows[id][2]
22389d
            commit_date = rows[id][3]
22389d
            update_date = rows[id][4]
22389d
            category = rows[id][5]
22389d
            comments = rows[id][6]
22389d
            abstract = self.tag_p({}, [0,0], rows[id][7])
22389d
22389d
            output = self.tag_h1({'class':'title'}, [12,1], title)
22389d
            output += self.content_info(id, email, commit_date, update_date, category, comments, abstract)
22389d
            output += self.tag_p({}, [20,1], rows[id][8])
22389d
            output += self.comments()
22389d
22389d
        return self.tag_div({'id':'content-details'}, [12,1], output, 1)
d59f66
22389d
    def comments(self):
22389d
        """Returns content specific list of comments.
22389d
22389d
        """
466e28
        output = self.tag_a({'name':'comments'}, [0,0], 'Comments')
22389d
        output = self.tag_h2({'class':'title comments'}, [12,1], output, 0) 
22389d
22389d
        return output
d59f66
d59f66
    def categories(self):
d59f66
        """Returns list of categories.
d59f66
        
d59f66
        """
22389d
        categories = ['Unknown', 'Articles', 'Erratas', 'Events']
147f1a
        dt = self.tag_dt({}, [16,1], 'Categories')
d59f66
        dd = ''
d59f66
        for id in range(len(categories)):
147f1a
            category_attrs = {'href': qs_args({'app':'', 'p':'categories', 'id':id})}
147f1a
            a = self.tag_a(category_attrs, [0,0], categories[id] + ' (0)') 
147f1a
            dd += self.tag_dd({}, [16,1], a)
d59f66
147f1a
        return self.tag_dl({},[12,1], dt + dd, 1)
d59f66
d59f66
    def archives(self):
d59f66
        """Returns archives."""
d59f66
        archives = {}
d59f66
        archives['2011'] = ['January', 'February', 'March', 'April', 'May']
d59f66
        archives['2010'] = ['January', 'February']
d59f66
147f1a
        dt = self.tag_dt({}, [16,1], 'Archives')
d59f66
        year_dl = ''
d59f66
        year_dd = ''
d59f66
d59f66
        for key in archives.keys():
147f1a
            year_dt = self.tag_dt({},[20,1], key)
d59f66
            for id in range(len(archives[key])):
147f1a
                a = self.tag_a({'href': qs_args({'app':'', 'p':'archives', 'year': key, 'month': id + 1})}, [0,0], archives[key][id] + ' (0)')
147f1a
                year_dd += self.tag_dd({}, [20,1], a)
147f1a
            year_dl += self.tag_dl({'class':'year'}, [16,1], year_dt + year_dd, 1)
d59f66
            year_dd = ''
d59f66
147f1a
        return self.tag_dl({},[12,1], dt + year_dl, 1)
e1d4e1
e1d4e1
    def page_top(self):
e1d4e1
        """Returns page top anchor."""
e1d4e1
        return self.tag_a({'name':'top'}, [0,1])
e1d4e1
e1d4e1
    def page_header(self):
d59f66
        """Returns page header.
d59f66
        
d59f66
        The page_header is common to all application modules and 
d59f66
        """
e1d4e1
        return self.tag_div({'id': 'page-header'}, [4,1], self.header, 1)
e1d4e1
e1d4e1
    def page_body(self):
d59f66
        """Returns page body.
d59f66
        
d59f66
        The page_body is specific to each application module and is
d59f66
        there where it must be constructed. The construction itself
22389d
        takes place through the `page_content()' function which does a
22389d
        return through an instantiated `content_' prefixed method.
d59f66
        The `content_' prefixed method used depends on the kind of
d59f66
        content you want to print out (e.g., `content_list()' for a
22389d
        content list, `detail()' for a detailed view of content,
22389d
        etc.). Later, the `body' variable instantiated from this class
22389d
        is reset in the `main()' function with the value returned from
22389d
        `page_content()' so the desired content layout can be printed
22389d
        out. 
d59f66
        
d59f66
        """
d59f66
        return self.tag_div({'id':'page-body'}, [4,1], self.body, 1)
e1d4e1
e1d4e1
    def page_links(self):
d59f66
        """Returns page links."""
e1d4e1
        page_links = self.user_links()
e1d4e1
        return self.tag_div({'id': 'pagelinks'}, [8,1], page_links, 1)
e1d4e1
    
e1d4e1
    def page_footer(self):
e1d4e1
        """Retruns page footer."""
e1d4e1
        return self.tag_div({'id': 'page-footer'}, [4,1], self.credits(), 1)
e1d4e1
e1d4e1
    def page_wrap(self):
e1d4e1
        """Returns page wrap."""
e1d4e1
        return self.tag_div({'id': 'wrap'}, [0,1], self.page_header() + self.page_body() + self.page_footer(), 1)
cc10e6
22389d
    def admonition(self, title='Note', subtitle="", body=""):
cffc47
        """Returns page admonition.
cffc47
        
cffc47
        title: Admonition's title.
cffc47
cffc47
        subtitle: Admonition's subtitle. The value of this argument is
cffc47
            concatenated on the right side of title using a colon (:)
cffc47
            as separator. Notice that this value is expanded inside
cffc47
            the 

tag and there is no need to introduce extra tags

cffc47
            here.
cffc47
22389d
        body: Admonition's body. The values passed through this
cffc47
            arguments needs to be XHTML code returned from
cffc47
            `self.tag()'. Preferably, paragraphs (p), tables (table),
cffc47
            lists (ul, ol, dl) and pre-formatted texts (pre).
cffc47
cffc47
        """
cffc47
        if title == '':
cffc47
            return ''
cffc47
        else:
cffc47
            title = str(title.capitalize())
cffc47
cffc47
        if subtitle != '':
cffc47
            subtitle = ': ' + str(subtitle.capitalize())
cffc47
22389d
        if body != '':
22389d
            body = str(body)
cffc47
cffc47
        admonitions = ['Note', 'Tip', 'Important', 'Caution', 'Warning', 'Redirected', 'Success', 'Error']
cffc47
        
cffc47
        if title in admonitions:
cffc47
            attrs = {'class': 'admonition ' + title.lower()}
7e82d6
            image = self.tag_img({'src': config.get('webserver','baseurl') + 'public/images/' + title.lower() + '.png', 'alt': title}, [16,1])
8c93bb
            title = self.tag_h3({'class': 'title'}, [16,1], title + subtitle, 0)
22389d
            output = image + title + body + self.separator()
cffc47
        else:
cffc47
            attrs = {'class': 'admonition unknown'}
8c93bb
            title = self.tag_h3({'class': 'title'}, [16,1], title + subtitle, 1)
22389d
            output = title + body
cffc47
        
8c93bb
        return self.tag_div(attrs, [12,1], output, 1)
cffc47
8c93bb
    def credits(self):
8f6ee0
        """Returns page credits."""
8c93bb
        copyright = self.tag_p({'class': 'copyright'}, [12,1], 'Copyright © ' + str(self.copyright))
8c93bb
        license = self.tag_p({'class': 'license'}, [12,1], 'This website is licensed under a ' + str(self.license()))
7e82d6
        credits = self.tag_img({'src': config.get('webserver','baseurl') + 'public/images/top.png', 'alt': 'Top'}, [0,0])
8c93bb
        credits = self.tag_a({'title': 'Top', 'href': '#top'}, [16,1], credits)
8c93bb
        credits = self.tag_div({'class': 'top'}, [12,1], credits, 1)
8f6ee0
        credits = str(credits) + str(copyright) + str(license) 
8c93bb
        credits = self.tag_div({'class': 'credits'}, [8,1], credits, 1)
7e8dd3
8f6ee0
        return credits
cc10e6
8f6ee0
    def page(self):
8f6ee0
        """Returns page final output."""
f19343
        html = self.doctype()
8c93bb
        html += self.tag_html({'xmlns': 'http://www.w3.org/1999/xhtml', 'dir': 'ltr', 
e1d4e1
                         'lang': str(self.language), 'xml:lang': str(self.language)}, [0,1], 
e1d4e1
                         self.metadata() + self.page_top() + self.page_wrap())
7e8dd3
8f6ee0
        return html