[Zope3-checkins] CVS: Products3/demo/messageboard/step12/browser/skin - __init__.py:1.1 board.css:1.1 board_intro.pt:1.1 board_overview.pt:1.1 board_posts.pt:1.1 board_review.pt:1.1 configure.zcml:1.1 dialog_macros.pt:1.1 logo.png:1.1 msg_details.pt:1.1 template.pt:1.1 views.py:1.1

Stephan Richter srichter@cosmos.phy.tufts.edu
Mon, 21 Jul 2003 17:34:11 -0400


Update of /cvs-repository/Products3/demo/messageboard/step12/browser/skin
In directory cvs.zope.org:/tmp/cvs-serv1069/browser/skin

Added Files:
	__init__.py board.css board_intro.pt board_overview.pt 
	board_posts.pt board_review.pt configure.zcml dialog_macros.pt 
	logo.png msg_details.pt template.pt views.py 
Log Message:
Final step of the Message Board Demo product. This step corresponds to the
recipe: http://dev.zope.org/Zope3/DevelopingSkins


=== Added File Products3/demo/messageboard/step12/browser/skin/__init__.py ===


=== Added File Products3/demo/messageboard/step12/browser/skin/board.css ===
/*******************************************************************************
*
* Copyright (c) 2001, 2002 Zope Corporation and Contributors.
* All Rights Reserved.
*
* This software is subject to the provisions of the Zope Public License,
* Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
* WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
* FOR A PARTICULAR PURPOSE.
*
*******************************************************************************/
/*
  CSS 2 compatible stylesheet for the board skin

  $Id: board.css,v 1.1 2003/07/21 21:33:59 srichter Exp $
*/

body {
    font-family: Verdana, Arial, Helvetica, sans-serif;
    background: white;
    color: black;
    margin: 0;
    padding: 0pt;
}

h1, h2, h3, h4, h5, h6 { 
    font-weight: bold; 
    color: black; 
}

/* Different headers are used for the same purpose, so make them all equal. */

h1, h2, h3 { 
    font-size: 20pt;
    margin-top: 0px; 
    margin-bottom: .8em;
    border-bottom: solid 1px #1E5ADB;
}

p {
    margin-top: 0.4em; 
    margin-bottom: 0.4em;  
}

a {
    color: #1E5ADB;
}

a:visited {
    color: #000066;
}

textarea {
  font-family: monospace;
}


/* Header Stuff */

#board_header {
    background: #EEEEEE;
    border: solid 1px #AAAAAA;
    padding: 3pt;
    clear: both;
}

#board_logo {
    float: left;
    height: 63px;
}

#board_greeting {
    font-size: 30pt;
    padding-top: 5pt;
    padding-bottom: 5pt;
    font-weight: bold;
    color: #1E5ADB;
}

/* Footer stuff */

#footer {
    background: #EEEEEE;
    border: solid 1px #AAAAAA;
    padding: 0.5em;
    font-size: 85%;
}

#actions {
    float: left;
    vertical-align: middle;
}

#credits {
    font-size: 10pt;
    text-align: right;
}

/* Generic Content */

#content {
    padding: 1em 2em;
}

/* Intro */

p.board_description {
    background: #EEEEEE;
    border: solid 1px #1E5ADB;
    padding: 0.5em;
}

#login_link {
    padding: 1em 0em;
    text-decoration: underline;
    font-weight: bold;
    text-align: center;
}

/* Posts */

#summary {
    padding-bottom: 1em;
}

#summary_title {
    font-weight: bold;
}

#summary_body {
    font-size: 80%;
}

#summary_metadata {
    font-size: 70%;
    color: #444444;
}

/* Review */

#message_line {
    padding: 0.2em 0.2em;
}

/* Details */

#details_title {
    margin-bottom: 2pt;
}

#details_body {
    background: #EEEEEE;
    padding: 0.5em;
    margin-bottom: 20pt;
}

#details_metadata {
    font-size: 70%;
    padding-bottom: 15pt;
}

#details_thread {
    font-size: 70%;
}

/* Structural Elements */

div.row {
    clear: both;
    padding-top: 10px;
}

div.row div.label {
    float: left;
    font-size: 80%;
    width: 100px;
    text-align: right;
    margin-right: 0.8em;
}

div.row div.field {
    float: left;
    font-size: 80%;
    text-align: left;
}


=== Added File Products3/demo/messageboard/step12/browser/skin/board_intro.pt ===
<html metal:use-macro="views/standard_macros/page">
  <body>
    <div id="content" metal:fill-slot="body" i18n:domain="messageboard">

      <h2 i18n:translate="">Welcome to the Message Board</h2>

      <p class="board_description" tal:content="context/description">
        Description of the Message Board goes here.
      </p>

      <div id="login_link">
        <a href="./posts.html">Click here to enter the board.</a>
      </div>

    </div>
  </body>
</html>


=== Added File Products3/demo/messageboard/step12/browser/skin/board_overview.pt ===
<html metal:use-macro="views/standard_macros/page">
  <body>
    <div metal:fill-slot="body" i18n:domain="messageboard">

      <h1 i18n:translate="">Discussion Thread</h1>

      <div tal:replace="structure view/subthread" />

    </div>
  </body>
</html>


=== Added File Products3/demo/messageboard/step12/browser/skin/board_posts.pt ===
<html metal:use-macro="views/standard_macros/page">
  <body>
    <div metal:fill-slot="body" i18n:domain="messageboard">

      <h2 i18n:translate="">Posts</h2>

      <div id="summary" tal:repeat="post view/getPosts">
        <div id="summary_title" >
          <a href="" tal:attributes="href post/url"
             tal:content="post/title">Message Title</a>
        </div>

        <div id="summary_body" tal:content="post/body">
          Message Description Sneak Preview goes here...
        </div>

        <div id="summary_metadata">
          Posted by <b tal:content="post/creator">Creator</b> 
          on <b tal:replace="post/created">2003/01/01</b>
          - <b tal:replace="post/replies">3</b> replies
        </div>
      </div>

    </div>
    <div id="actions" metal:fill-slot="actions">
      <a href="" tal:attributes="href view/getAddURL"
        i18n:translate="">Create new Post</a>
      |
      <a href="./@@review.html"
        i18n:translate="">Review Messages</a>
    </div>
  </body>
</html>


=== Added File Products3/demo/messageboard/step12/browser/skin/board_review.pt ===
<html metal:use-macro="views/standard_macros/page">
  <body>
    <div metal:fill-slot="body" i18n:domain="messageboard">

      <h2 i18n:translate="">Review Pending Posts</h2>

      <form action="updateStatus.html" method="POST">

        <div id="message_line" tal:repeat="post view/getPendingMessagesInfo">
          <input type="checkbox" name="messages" value=""
            tal:attributes="value post/path" />
          <a href="" tal:attributes="href post/url"
            tal:content="post/title">Message Title</a>
          <div style="font-size: 70%">
            (Posted by <b tal:content="post/creator">Creator</b> 
            on <b tal:replace="post/created">2003/01/01</b>)
          </div>
        </div>
        <br />
        <input type="submit" value="Publish" i18n:attributes="value" />

      </form>

    </div>
    <div id="actions" metal:fill-slot="actions">
      <a href="posts.html" i18n:translate="">View Posts</a>
    </div>
  </body>
</html>


=== Added File Products3/demo/messageboard/step12/browser/skin/configure.zcml ===
<zopeConfigure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:browser="http://namespaces.zope.org/browser">

  <browser:skin name="board" layers="board rotterdam default" />

  <browser:resource 
      name="board.css" file="board.css" layer="board" />

  <browser:resource 
      name="logo.png" file="logo.png" layer="board" />

  <browser:page 
      for="*"
      name="skin_macros"
      permission="zope.View"
      layer="board"
      template="template.pt" />

  <browser:page
      for="*"
      name="dialog_macros"
      permission="zope.View"
      layer="board"
      template="dialog_macros.pt" />

  <browser:page 
      for="zopeproducts.messageboard.interfaces.IMessageBoard"
      name="intro.html"
      permission="zopeproducts.messageboard.View"
      layer="board"
      template="board_intro.pt" />

  <browser:page 
      for="zopeproducts.messageboard.interfaces.IMessageBoard"
      name="posts.html"
      permission="zopeproducts.messageboard.View"
      layer="board"
      class=".views.Posts"
      template="board_posts.pt" />

  <browser:pages
      for="zopeproducts.messageboard.interfaces.IMessageBoard"
      class=".views.Review"
      permission="zopeproducts.messageboard.PublishContent"
      layer="board">
      <browser:page name="review.html" template="board_review.pt"/>
      <browser:page name="updateStatus.html" attribute="updateStatus" />
  </browser:pages>

  <browser:addform
      label="Add Message"
      name="AddMessage"
      schema="zopeproducts.messageboard.interfaces.IMessage"
      content_factory="zopeproducts.messageboard.message.Message"
      permission="zopeproducts.messageboard.Add"
      class=".views.AddMessage"
      layer="board"/>

  <browser:addform
      label="Reply to Message"
      name="ReplyMessage"
      schema="zopeproducts.messageboard.interfaces.IMessage"
      content_factory="zopeproducts.messageboard.message.Message"
      permission="zopeproducts.messageboard.Add"
      class=".views.ReplyMessage"
      layer="board"/>

  <browser:page 
      for="zopeproducts.messageboard.interfaces.IMessage"
      name="details.html"
      permission="zopeproducts.messageboard.View"
      layer="board"
      class=".views.Details"
      template="msg_details.pt" />

</zopeConfigure>


=== Added File Products3/demo/messageboard/step12/browser/skin/dialog_macros.pt ===
<metal:block define-macro="dialog">
  <metal:block define-slot="doctype">
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  </metal:block>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

  <head>
    <title metal:define-slot="title">Message Board for Zope 3</title>


    <style type="text/css" media="all"
        tal:content="string: @import url(${context/++resource++board.css});">
      @import url(board.css);
    </style>
        
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

    <link rel="icon" type="image/png"
          tal:attributes="href context/++resource++favicon.png" />
  </head>

  <body>

    <div id="board_header" i18n:domain="messageboard">
      <img id="board_logo" tal:attributes="src context/++resource++logo.png" />
      <div id="board_greeting">&nbsp;
        <span i18n:translate="">Zope 3 Message Board</span>
      </div>
    </div>

    <div id="workspace">

      <div metal:define-slot="message" id="message"></div>
      
      <div id="content">
        <metal:block define-slot="body">
	  This is the content.
        </metal:block>
      </div>

    </div>

    <div id="footer">

      <div id="actions">
        <metal:block define-slot="actions">
        </metal:block>
      </div>
      <div id="credits" i18n:domain="messageboard">
        Prowered by Zope 3.<br />
        Stephan Richter in 2003
      </div>
    </div>

  </body>

</html>

</metal:block>



=== Added File Products3/demo/messageboard/step12/browser/skin/logo.png ===
  <Binary-ish file>

=== Added File Products3/demo/messageboard/step12/browser/skin/msg_details.pt ===
<html metal:use-macro="views/standard_macros/page">
  <body>
    <div metal:fill-slot="body" i18n:domain="messageboard">

      <h2 id="details_title" tal:content="context/title" 
        i18n:translate="">Post Title</h2>

      <div id="details_metadata" tal:define="md view/metadata">
        Posted by <b tal:content="md/creator">Creator</b> 
        on <b tal:replace="md/created">2003/01/01</b>
      </div>

      <div id="details_body" tal:content="structure view/htmlBody">
        Message Body goes here
      </div>

      <b>Replies:</b>
      <div id="details_thread">
        <div tal:replace="structure view/subthread" />
      </div>

    </div>
    <div id="actions" metal:fill-slot="actions">
      <a href="" tal:attributes="href view/getParentURL"
        i18n:translate="">Back to Parent</a>
      |
      <a href="" tal:attributes="href view/getReplyURL"
        i18n:translate="">Reply to Post</a>
    </div>
  </body>
</html>


=== Added File Products3/demo/messageboard/step12/browser/skin/template.pt ===
<metal:block define-macro="page">
  <metal:block define-slot="doctype">
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  </metal:block>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

  <head>
    <title metal:define-slot="title">Message Board for Zope 3</title>

    <style type="text/css" media="all"
       tal:content="string: @import url(${context/++resource++board.css});">
      @import url(board.css);
    </style>
        
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

    <link rel="icon" type="image/png"
          tal:attributes="href context/++resource++favicon.png" />
  </head>

  <body>

    <div id="board_header" i18n:domain="messageboard">
      <img id="board_logo" tal:attributes="src context/++resource++logo.png" />
      <div id="board_greeting">&nbsp;
         <span i18n:translate="">Zope 3 Message Board</span>
      </div>
    </div>

    <div id="workspace">

      <div metal:define-slot="message" id="message"></div>
      
      <div id="content">
        <metal:block define-slot="body">
	  This is the content.
        </metal:block>
      </div>

    </div>

    <div id="footer">

      <div id="actions">
        <metal:block define-slot="actions" />
      </div>
      <div id="credits" i18n:domain="messageboard">
        Prowered by Zope 3.<br>
        Stephan Richter in 2003
      </div>
    </div>

  </body>

</html>

</metal:block>



=== Added File Products3/demo/messageboard/step12/browser/skin/views.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Various View classes supporting the pages.

$Id: views.py,v 1.1 2003/07/21 21:33:59 srichter Exp $
"""
import random, time, cgi
from zope.app.interfaces.dublincore import ICMFDublinCore
from zope.app.interfaces.workflow import IProcessInstanceContainer
from zopeproducts.messageboard.interfaces import IMessage

from zope.component import getAdapter, getView
from zope.app.form.utility import setUpWidgets
from zope.app.traversing import getPath, traverse, getParent
from zopeproducts.messageboard.browser.messageboard import hasMessageStatus
from zopeproducts.messageboard.browser.messageboard import ReviewMessages
from zopeproducts.messageboard.browser.thread import Thread

class Posts:
    """Displays all first-level posts of the messageboard."""

    def getPosts(self):
        """Get first level post infos as ZPT-friendly dict."""
        posts = []
        for name, post in self.context.items():
            if hasMessageStatus(post, 'published'):
                dc = getAdapter(post, ICMFDublinCore)
                info = {}
                info['title'] = post.title
                if len(post.body) > 100:
                    body = post.body[:100] + '...'
                else:
                    body = post.body
                info['body'] = body
                info['replies'] = len(post)
                info['creator'] = dc.creators[0]
                formatter = self.request.locale.getDateTimeFormatter('medium')
                info['created'] = formatter.format(dc.created)
                info['url'] = './' + name + '/@@details.html'
                posts.append(info)
        return posts


    def getAddURL(self):
        """Get the URL for adding a message."""
        # Use a random generator to create an id
        # While this is not a nice way of doing this,
        # it is sufficient for now.
        random.seed(time.time())
        id = str(random.randint(0, 100000))
        while id in self.context.keys():
            id = str(random.randint(0, 100000))

        return "@@+/AddMessage=" + id


class Review(ReviewMessages):
    """Review messages for publication."""

    def getPendingMessagesInfo(self):
        """Get all the display info for pending messages"""
        msg_infos = []
        for msg in self.getPendingMessages(self.context):
            dc = getAdapter(msg, ICMFDublinCore)
            info = {}
            info['path'] = getPath(msg)
            info['title'] = msg.title
            info['creator'] = dc.creators[0]
            formatter = self.request.locale.getDateTimeFormatter('medium')
            info['created'] = formatter.format(dc.created)
            info['url'] = getView(
                msg, 'absolute_url', self.request)() + '/@@details.html'
            msg_infos.append(info)
        return msg_infos

    def updateStatus(self, messages):
        """Upgrade the stati from 'pending' to 'published'."""
        if not isinstance(messages, (list, tuple)):
            messages = [messages]

        for path in messages:
            msg = traverse(self.context, path)

            adapter = getAdapter(msg, IProcessInstanceContainer)
            adapter['default'].fireTransition('pending_published')

        return self.request.response.redirect('@@review.html')



class AddMessage:
    """Add-Form supporting class."""

    def nextURL(self):
        return '../@@posts.html'


class ReplyMessage:
    """Add-Form supporting class."""

    def nextURL(self):
        return '../@@details.html'

    def _setUpWidgets(self):
        """Alllow addforms to also have default values."""
        parent = self.context.context
        title = parent.title
        if not title.startswith('Re:'):
            title = 'Re: ' + parent.title

        dc = getAdapter(parent, ICMFDublinCore)
        formatter = self.request.locale.getDateTimeFormatter('medium')
        body = '%s on %swrote:\n' %(dc.creators[0], formatter.format(dc.created))
        body += '> ' + parent.body.replace('\n', '\n> ')
        
        setUpWidgets(self, self.schema, initial={'title': title, 'body': body},
                     names=self.fieldNames)


class Details(Thread, object):
    """Message Details view class."""

    def metadata(self):
        """Get the relevant metadata from the message."""
        dc = getAdapter(self.context, ICMFDublinCore)
        info = {}
        info['creator'] = dc.creators[0]
        formatter = self.request.locale.getDateTimeFormatter('medium')
        info['created'] = formatter.format(dc.created)
        return info

    def htmlBody(self):
        """Make the body HTML-savy."""
        body = cgi.escape(self.context.body)
        return body.replace('\n', '<br/>\n')

    def getReplyURL(self):
        """Get the URL for replying to a message."""
        # Use a random generator to create an id
        # While this is not a nice way of doing this,
        # it is sufficient for now.
        random.seed(time.time())
        id = str(random.randint(0, 100000))
        while id in self.context.keys():
            id = str(random.randint(0, 100000))

        return "@@+/ReplyMessage=" + id

    def getParentURL(self):
        """Get the best view URL for the parent.

        Since messages can be contained by message boards and messages, we
        have different URLs based on the parent type.
        """
        parent = getParent(self.context)
        if IMessage.isImplementedBy(parent):
            return '../@@details.html'
        else:
            return '../@@posts.html'

    def listContentInfo(self):
        """Override the thread listContentInfo() to get an appropriate URL."""
        info = super(Details, self).listContentInfo()
        for item in info:
            item['url'] = item['url'].replace('@@thread.html', '@@details.html')
        return info