Page Source

from utils.controls import *
from utils.output import html
from utils import output
from mod_python import apache
from utils.display import Display, Database, FileCopy, Arguments
from utils.display import Modal
from utils.web_exc import WebError, SendLiteral
from utils import bounce
from config import docroot
import string, os
import debugging

class NoCommentsControl(SingleControl):
  """If there are no bodies (display.db_rows() == 0) then display a
  'nothing found' header."""
  def output_disp(self, display, container):
    if display.db_rows() == 0:
      container.append("<i>This path is empty.  Sorry.</i>")

class AddBodyFormControl(MultipleControl):
  """Makes an action form for adding a new body segment, with the
  specified PRIORITY, or one more than the current row's priority if
  no PRIORITY is specified."""
  def __init__(self, priority=None):
    self.priority = priority

  def output_disp(self, display, container):
    priority = self.priority
    if priority is None:
      priority = display.fields['priority'] + 1

    f = html.FORM(action="%(action:)s&add_priority=%(!prio)s"
                  % display.req.urls({'prio' : priority}))
    f.append("<center>Add a new body segment with identifier ")
    f.append(html.TEXT_INPUT(attrs={'size' : 10,
                                    'name' : 'new_body_name'}))
    f.append(" here. ")
    f.append(html.SUBMIT_INPUT(attrs={'value' : 'Add'}))
    f.append("</center>")
    container.append(f)

class DeleteBodyControl(SingleControl):
  def __init__(self, url=''):
    self.url = url

  def output_form(self, display, container):
    tr = output.TR()
    tr.append("""<b>Delete:</b>""")
    url = self.url % display.req.urls(display.fields)
    tr.append("""[&nbsp;<a href="%s">Delete&nbsp;This&nbsp;Body</a>&nbsp;]""" % (url,))
    tr.append("""Deleting is not reversible.""")
    container.append(tr)

    
###################
# The display class itself

class PathDisplay(Display, FileCopy, Database, Arguments, Modal):
  def __init__(self, req):
    Display.__init__(self, req)

  def process_arguments(self):
    if not hasattr(self, 'args'):
      self.args = self.parse_arguments(['path_name', 'body_name'])
      if self.args['path_name'] and \
         not self.check_name_characters(self.args['path_name']):
        self.args['path_name'] = None
      if self.args['body_name'] and \
         not self.check_name_characters(self.args['body_name']):
        self.args['body_name'] = None
        
  def perform_action(self):
    self.process_arguments()
    self.prep_database()
    
    if self.permit_mode('edit'):
      if self.is_multiple():
        if self.req.url.internal.has_key('add_priority'):
          # Add a new body
          new_prio = int(self.req.url.internal['add_priority'][0])
          new_body_name = self.req.form_data['new_body_name']
          if new_body_name:
            self.db_add_body(new_prio, new_body_name)
            return "%(docpath!path_name)s/%(!body_name)s" \
                   % self.req.urls( { 'path_name' : self.args['path_name'],
                                      'body_name' : new_body_name } )
      else: 
        if self.req.url.internal.has_key('delete_body'):
          # Delete a body
          path_name = self.args['path_name']
          body_name = self.req.url.internal['delete_body'][0]
          self.db_delete_body(path_name, body_name)
          return "%(docpath!path_name)s" \
                 % self.req.urls( { 'path_name' : self.args['path_name'] } )
        else:
          # Edit a body
          self.perform_database_update()
          return "%(docpath!path_name)s" \
                 % self.req.urls( { 'path_name' : self.args['path_name'] } )
       
  def make_page(self, page):
    self.process_arguments()

    # bounce back to the top of the docs if we have no arguments
    if not self.args['path_name']:
      bounce.bounce(self.req, "%(documentation)s" % self.req.urls)
    
    self.prep_database()

    page.set_type("info")
    self.add_modes_to_page(page)

    # Check to see if we need to give the user *just* the body
    # in text format
    if not self.is_multiple() and self.req.url.internal.has_key('body'):
      raise SendLiteral('text/plain', self.fields['body'])
    if self.is_multiple():
      page.set_title(self.get_multiple_title())
      page.append("<h1>%s</h1>\n" % self.get_multiple_title())
      page.add_navigation("%(documentation)s" % self.req.urls(self.args),
                          "Site Documentation")
      page.append(self.multiple_database())
    else:
      page.set_title(self.get_single_title())
      page.add_navigation("%(docpath!path_name)s" % self.req.urls(self.args),
                          "View Path")
      if self.get_mode() == 'edit':
        self.shape = shapes.FORM
      page.append(self.single_database())

  def permit_mode(self, mode):
    return ((mode == 'display') or
            (mode == 'edit') and perms.may(self.req.login, 'edit', 'documentation'))

  _name_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."
  def check_name_characters(self, name):
    """Check a name to be sure it's URL-OK."""
    if len(name) > 20:
      return None                       # too long
    for char in name:
      if char not in self._name_characters:
        return None                     # not OK
    return 1                            # OK

  multiple_container_control = CoalesceContainerControl([
    NoCommentsControl(),
    IfModeControl("edit", AddBodyFormControl(0)),
    SimpleContainerControl(),
    ])

  multiple_controls = [ 
    LinkControl("path_name",
                "%(docpath!path_name)s/%(!body_name)s",
                [ "<h3>", FieldControl("title"), "</h3>"] ),
    "\n",
    DropCapControl(HTMLEditorControl("body",
                                     "%(docpath!path_name)s/%(!body_name)s?body=txt")),
    IfModeControl("edit", AddBodyFormControl()),
    ]

  def get_multiple_title(self):
    if not hasattr(self, '_multiple_title'):
      s = queries.Select(tables="doc_paths",
                         where="path_name = %s"
                         % queries.represent_value(self.args['path_name']),
                         columns=['title'])
      s.execute()
      row = s.fetch()
      if row:
        self._multiple_title = row['title']
      else:
        self._multiple_title = 'Path Display'
    return self._multiple_title
      

  single_controls = [
    IfShapeControl(formcontrols=[ NoEditControl("path_name", title="Path Identifier"),
                                  NoEditControl("body_name", title="Body Identifier"),
                                  BooleanControl("private", title="Private",
                                                 comment="""Private data can only be read by those with permission to <tt>read_private</tt> in <tt>documentation</tt>.""") ] ),
    "<h1>", TextFieldControl("title", title="Title"), "</h1>",
    DropCapControl(HTMLEditorControl("body",
                                     "%(docpath!path_name)s/%(!body_name)s?body=txt",
                                     title="Body", rows=25, cols=80)),
    DeleteBodyControl(url="%(action:)s&delete_body=%(!body_name)s")
    ]

  def get_single_title(self):
    return self.fields['title']

  def db_query(self):
    wc = [ "p.path_name = b.path_name",
           "b.path_name = %s" % queries.represent_value(self.args['path_name']) ]
    # if not logged in, don't show private paths
    if not perms.may(self.req.login, 'read_private', 'documentation'):
      wc.append("not p.private")
      wc.append("not b.private")
    if self.args['body_name']:
      wc.append("b.body_name = %s" % queries.represent_value(self.args['body_name']))

    s = queries.Select(tables=["doc_paths as p", "doc_bodies as b"],
                       order="b.priority",
                       where=string.join(wc, ' AND '),
                       columns = { 'title' : 'b.title',
                                   'path_name' : 'b.path_name',
                                   'body_name' : 'b.body_name',
                                   'body' : 'b.body',
                                   'priority' : 'b.priority',
                                   'private' : 'b.private'} )
    s.execute()
    if not self.is_multiple() and s.count() == 0:
      raise WebError("Not Found",
                     """The specified piece of documentation was not found.  Please
                     navigate to the documentation you would like to see starting
                     at the <a href="%(documentation)s">documentation</a> page.""" %
                     self.req.urls)
    self.query = s

  def is_multiple(self):
    return not self.args['body_name']

  def db_fetch(self):
    self.fields = self.query.fetch()
    return self.fields

  def db_update(self):
    u = queries.Update(table="doc_bodies",
                       where="path_name = %s and body_name = %s"
                       % (queries.represent_value(self.args['path_name']),
                          queries.represent_value(self.args['body_name'])))
    u['title'] = self.form_fields['title']
    u['body'] = self.form_fields['body']
    u['private'] = self.form_fields['private']
    u.execute()

  def db_add_body(self, priority, body_name):
    path_name = self.args['path_name']

    if not self.check_name_characters(body_name):
      raise WebError("Illegal Characters in identifier",
                     """Identifiers must be short and safe to include in a URL.
                     They cannot contain spaces.""")
    
    # Lock down the table while we do all of this
    lock = queries.Lock(tables="doc_bodies")
    lock.lock()
    try:
      # check to see if the identifier already exists
      s = queries.Select(tables="doc_bodies",
                         where="path_name = %s and body_name = %s"
                         % (queries.represent_value(path_name),
                            queries.represent_value(body_name)))
      s.execute()
      if s.count() > 0:
        raise WebError("Identifier Already In Use",
                       """The identifier you selected, '%s', is already in use within
                       this path.  Please go back and try another.""" % body_name)
      
      # now move all of the other bodies' priorities up by one to
      # make room for this one.
      u = queries.Update(table="doc_bodies",
                         where="priority >= %s" % queries.represent_value(priority),
                         sets={'priority' : queries.SQL('priority + 1')})
      u.execute()

      # finally, insert the new one
      i = queries.Insert(table="doc_bodies",
                         rows=[{'title' : 'Untitled',
                                'path_name' : path_name,
                                'body_name' : body_name,
                                'priority' : priority,
                                'private' : 0,
                                'body' : ''}])
      i.execute()
    finally:
      lock.unlock()

  def db_delete_body(self, path_name, body_name):
    d = queries.Delete(table="doc_bodies",
                       where="path_name = %s and body_name = %s"
                       % (queries.represent_value(path_name),
                          queries.represent_value(body_name)))
    d.execute()
    

  def db_rows(self):
    return self.query.count()

def new(req):
  return PathDisplay(req)