+++ /dev/null
-#!/usr/bin/python
-#
-# Web application for posting additions/modifications to the chem-inventory
-# derived from simplewiki and wiki_py, by Adam Bachman
-# ( http://bachman.infogami.com/ )
-# Authentication following Snakelets
-# http://snakelets.sourceforge.net/manual/authorization.html
-
-# Remember to delete the templates directory after making alterations,
-# because this program only generates templates if they are missing.
-
-
-## Bring in some useful goodies:
-# web, because we're running a web server, and generating html;
-# time, for timestamping backups;
-# os, for creating the template directories; and
-# re, for replacing internal node links with proper markup
-import web, os, re
-# md5, for hashing passwords
-#import md5
-
-# database must support the following methods :
-# field_list() : return an ordered list of available fields (fields unique)
-# long_field() : return an dict of long field names (keyed by field names)
-# record(id) : return a record dict (keyed by field names)
-# records() : return an ordered list of available records.
-# backup() : save a copy of the current database somehow.
-# set_record(i, newvals) : set a record by overwriting any preexisting data
-# : with data from the field-name-keyed dict NEWVALS
-import text_db
-database = text_db.text_db
-from chem_db import valid_CASno, MSDS_manager, docgen
-DB_FILE = 'inventory.db'
-import sys, daemon, logging
-PID_FILE = './chem_web_secure.pid'
-LOG_FILE = './chem_web_secure.log'
-
-## Also import a markup language, and associated regexp functions
-from markdown import markdown
-## for converting the first character in record names, and stripping |
-from string import upper, rstrip
-
-## for SSL
-SSL = True
-if SSL :
- from mk_simple_certs import get_cert_filenames
- server_name = 'chem_web'
-
-__version__ = "0.2"
-
-## For web.py, when a URL matches '/view', handle with class 'view'
-# when it matches '/edit', handle with class 'edit',
-# and when it matches '/login', handle with class 'login',
-# Note, there are no .html extension.
-#
-# link to edit pages using db_id
-# link to MSDSs with ID
-urls = ('/(view)?', 'view',
- '/edit/([0-9]+)', 'edit',
- '/MSDS/([0-9]+)', 'MSDS',
- '/docs/([a-z._]*)', 'docs',
- '/login', 'login')
-
-# limit the display width of selected fields
-WIDTH_MAX = {'CAS#':12}
-
-# set the display width of entry fields
-ENTRY_WIDTH = 50
-
-class view (object) :
- "Print the database"
- # when the server recieves the command "GET '/view', call view.get('view')
- def GET(self, name):
- """
- Render the view.html template using name and the previous text.
- If the file doesn't exist, use default text '%s doesn't exist'.
- """
- if name == None :
- name = 'view'
- db._refresh()
- recs = []
- for record in db.records() :
- id = int(record['ID'])
- record['ID'] = '<a href="/edit/%d">%s</a>' \
- % (id, record['ID'])
- if MSDSman.has_MSDS(id) : # link the id to the MSDS
- record['Name'] = '<a href="/MSDS/%d">%s</a>' \
- % (record['db_id'], record['Name'])
- recs.append(record)
- print render.view('Chemical inventory',
- db.len_records(), db.field_list(), recs)
-
-class edit (object) :
- "Provide methods for handling requests related to edit pages"
- def GET(self, name):
- """
- Render the edit.html template using the specified record number.
- """
- db._refresh()
- db_id = int(name)
- if db_id >= db.len_records() :
- logging.info("new record")
- assert db_id == db.len_records(), \
- 'invalid id: %d (max %d)' % (db_id, db.len_records())
- record = db.new_record()
- record['ID'] = str(record['db_id'])
- db.set_record(db_id, record)
- if MSDSman.has_MSDS(db_id) :
- MSDSs = None
- else :
- # only ask for an MSDS if we still need one
- MSDSs = MSDSman.get_all(simlinks=False)
- print render.edit(int(name), db.field_list(),
- db.long_fields(), db.record(int(name)), MSDSs)
- def POST(self, name):
- """
- Read the form input and update the database accordingly.
- Then redirect to /view.
- """
- db._refresh()
- record = db.record(int(name))
- ## Generate a new record from the form input
- # 'MSDS={}' to storify MSDS as a FieldStorage (dict-like) instance,
- # otherwise it's saved as a string, and we loose info about it.
- # e.g. newvals['MSDS'].type = 'text/html'
- # The contents of the file are saved to newvals['MSDS'].value
- newvals = web.input(MSDS={})
- update = False
- for field in db.field_list() :
- if newvals[field] != record[field] :
- update=True
- record[field] = newvals[field]
- if 'MSDS source' in newvals :
- # Handle any MSDS file actions
- if newvals['MSDS source'] == 'upload' :
- if len(newvals['MSDS'].filename) > 0 and len(newvals['MSDS'].value) > 0:
- # only save if there is a file there to save ;)
- #print >> stderr, web.ctx.env['CONTENT_TYPE']
- #print >> stderr, web.ctx.keys()
- #print >> stderr, dir(newvals['MSDS'])
- #print >> stderr, type(newvals['MSDS'])
- #print >> stderr, newvals['MSDS'].filename
- #print >> stderr, newvals['MSDS'].type
- MSDSman.save(int(name), newvals['MSDS'].value,
- newvals['MSDS'].type)
- else :
- logging.info('linking MSDS %d to %d' \
- % (int(record['ID']),
- int(newvals['MSDS share'])) )
- MSDSman.link(int(record['ID']),
- int(newvals['MSDS share']))
- if update :
- db.set_record(int(name), record, backup=True)
- # redirect to view all
- web.seeother('/view')
-
-class MSDS (object) :
- "Serve MSDS files by ID"
- def GET(self, name):
- "Serve MSDS files by ID"
- id = int(name)
- if MSDSman.has_MSDS(id) :
- mime = MSDSman.get_MSDS_MIME(id)
- web.header("Content-Type", mime)
- print file(MSDSman.get_MSDS_path(id), 'rb').read() ,
- else :
- print render.error(id)
-
-class docs (object) :
- "Generate and serve assorted official documents"
- def GET(self, name):
- """
- List the available documents.
- """
- db._refresh()
- if name == '' :
- print render.docs()
- if name == 'inventory.pdf' :
- path = dgen.inventory(namewidth=40)
- print file(path, 'rb').read() ,
- if name == 'door_warning.pdf' :
- path = dgen.door_warning()
- print file(path, 'rb').read() ,
-
-
-class login (object) :
- "Print an alphabetized index of all existing pages"
- def GET(self):
- print "Not yet implemented"
- #print render.stat('login')
-
-class markup (object) :
- "Convert text to html, using Markdown with a bit of preformatting"
- def __init__(self) :
- # [[optional display text|name with or_without_spaces]]
- linkregexp = ('\[\['
- +'([^\|\[\]]*\|)?'
- +'([a-zA-Z0-9-_ ]*)'
- +'\]\]')
- #print linkregexp
- self.linkexp = re.compile(linkregexp)
- self.linebreak = ' \n'
- self.uscore = re.compile('_')
- self.space = re.compile(' ')
- def htmlize(self, text) :
- return text
- pre_markup = self._preformat(text)
- #print "Preformatted '"+pre_markup+"'"
- return self._markup( pre_markup )
- def _markup(self, text) :
- # markdown() implements the text->html Markdown semantics
- # the str() builtin ensures a nice, printable string
- return str(markdown(text))
- def _preformat(self, text) :
- #print "Preformatting '"+text+"'"
- return self._autolink(text)
- def _autolink(self, text) :
- "Replace linkexps in text with Markdown syntax versions"
- # sub passes a match object to link2markup for each match
- # see http://docs.python.org/lib/match-objects.html
- return self.linkexp.sub(self.matchlink2markup, text)
- def matchlink2markup(self, match) :
- # The name is the first (and only) matched region
- #print match.groups()
- name = match.groups()[1]
- text = match.groups()[0]
- return self.link2markup(text, name)
- def linklist(self, text) :
- linklist = [] # empty list
- for m in self.linkexp.finditer(text) :
- linklist.append( self.str2name(m.groups()[1]) )
- #print linklist
- return linklist
- def link2markup(self, text, name) :
- """
- usage: string = link2markup(text, target)
- takes the string name of a wiki page,
- and returns a string that will be markuped into a link to that page
- displaying the text text.
- """
- # convert to Markdown pretty link syntax
- if text : # remove trailing |
- text = text.rstrip('|')
- else :
- text = self.name2str(name)
- name = self.str2name(name)
- return str('['+text+'](/'+name+')')
- def name2str(self, name) :
- """
- usage: string = name2str(name)
- takes the string name of a wiki page,
- and converts it to display format
- See str2name()
- """
- w_spaces = self.uscore.sub(' ', name)
- return w_spaces
- def str2name(self, string) :
- """
- usage: string = name2str(name)
- Converts strings to the relevent wiki page name.
- See str2name()
- """
- wo_spaces = self.space.sub('_', string)
- cap_first = upper(wo_spaces[0]) + wo_spaces[1:]
- return cap_first
- def make_add_button(self, next_db_id) :
- string = '<form action="/edit/%d" method="get">\n' % next_db_id
- string += ' <input type="submit" value="Add entry">\n'
- string += '</form><br>\n'
- return string
- def make_table(self, fields, records, width_max=WIDTH_MAX,
- raw_fields=()):
- """
- >>> print make_table(['A','B','C'],[{'A':'a','B':'b'},{'A':'d','B':'e','C':'f'}]),
- <table>
- <tr><td><b>A</b></td><td><b>B</b></td><td><b>C</b></td></tr>
- <tr><td>a</td><td>b</td><td></td></tr>
- <tr><td>d</td><td>e</td><td>f</td></tr>
- </table>
- """
- # open the table
- string = '<table>\n'
- # add the header
- string += '<tr>'
- for field in fields :
- string += '<td><b>%s<b></td>' % self.htmlize(field)
- string += '</tr>\n'
- # add the records
- for record in records :
- string += '<tr>'
- for field in fields :
- rstring = record[field]
- # truncate if the width is regulated...
- if field in width_max :
- w = width_max[field]
- # ... and the full string is too long
- if len(rstring) > w :
- rstring = "%s..." % rstring[:w]
- # if the field isn't raw, protect special chars
- if not field in raw_fields :
- rstring = self.htmlize(rstring)
- string += '<td>%s</td>' % rstring
- string += '</tr>\n'
- # close the table
- string += '</table>\n'
- return string
- def record_form_entries(self, index, fields, lfields, record, MSDSs=None, entry_width=ENTRY_WIDTH):
- """
- >>> print record_form_entries(4,['A','B','MSDS'],{'A':'AA','B':'BB','MSDS'},{'A':'a','B':'b'}),
- <table>
- <tr><td><label for="A">AA</label></td><td>:</td>
- <td><input type="text" size="50" id="A" name="A" value="a"></td></tr>
- <tr><td><label for="B">BB</label></td><td>:</td>
- <td><input type="text" size="50" id="B" name="B" value="b"></td></tr>
- <tr><td><label for="MSDS">MSDS</label></td><td>:</td>
- <td><input type="file" size="50" id="MSDS" name="MSDS" value=""></td></tr>
- </table>
- """
- # open the table
- string = '<table>\n'
- # add the record fields
- for field in fields :
- # get the record string
- rstring = self.htmlize(record[field])
- fstring = self.htmlize(field)
- lfstring = self.htmlize(lfields[field])
- string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' % (fstring, lfstring)
- string += ' <td><input type="text" size="%d" id="%s" name="%s" value="%s"></td></tr>\n' \
- % (entry_width, fstring, fstring, rstring)
- if MSDSs != None :
- ## add an MSDS radio, share menu, and file upload fields
- # radio
- lfstring = fstring = 'MSDS source'
- string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
- % (fstring, lfstring)
- string += ' <td><input type="radio" id="%s" name="%s" value="upload" checked>Upload\n' \
- % (fstring, fstring)
- string += ' <input type="radio" id="%s" name="%s" value="share">Share</td></tr>\n' \
- % (fstring, fstring)
-
- # file upload
- fstring = 'MSDS'
- lfstring = 'Upload MSDS'
- string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
- % (fstring, lfstring)
- string += ' <td><input type="file" size="%d" id="%s" name="%s" accept="text/plain,text/html,application/pdf" value=""></td></tr>\n' \
- % (entry_width, fstring, fstring)
-
- # file share menu
- fstring = 'MSDS share'
- lfstring = 'Use an already uploaded MSDS'
- string += ' <tr><td><label for="%s">%s</label></td><td>:</td>\n' \
- % (fstring, lfstring)
- string += ' <td><select size="1" id="%s" name="%s">\n' \
- % (fstring, fstring)
- for MSDS in MSDSs :
- lfstring = self.htmlize(MSDS['Name'])
- id = int(MSDS['ID'])
- string += ' <option value="%d">%d: %s</option>\n' \
- % (id, id, lfstring)
- string += ' </select></td></tr>\n'
- # close the table
- string += '</table><br>\n'
- return string
-
-class templt (object) :
- "Handle some template wrapping"
- def __init__(self) :
- self.dir = 'templates/'
- if not os.path.exists(self.dir): os.mkdir(self.dir)
- ## Write simple template html pages if they don't already exist.
- # see ( http://webpy.org/templetor ) for the syntax.
- html=('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"\n'
- ' "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n'
- '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n')
- if not os.path.exists(self.dir+'view.html') :
- view=('$def with (name, next_db_id, fields, records)\n'
- +html+
- ' <head>\n'
- ' <title>\n'
- ' $:name2str(name) \n'
- ' </title>\n'
- ' </head>\n'
- ' <body>\n'
- ' <h1>\n'
- ' $:name2str(name)\n'
- ' </h1>\n'
- ' See \n'
- ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm">the rules</a>\n'
- ' for more information.\n'
- ' See the <a href="/docs/">docs</a> page to generate required documents.<br>\n'
- ' <br>\n'
- ' $:make_add_button(next_db_id)\n'
- ' $:make_table(fields, records)\n'
- ' $:make_add_button(next_db_id)\n'
- ' </body>\n'
- '</html>\n')
- file(self.dir+'view.html', 'w').write(view)
- if not os.path.exists(self.dir+'edit.html') :
- # note:
- # the form encoding type 'enctype="multipart/form-data">\n'
- # is only required because of the MSDS file-upload field.
- edit=('$def with (index, fields, lfields, record, MSDSs)\n'
- +html+
- ' <head>\n'
- ' <title>\n'
- ' $:htmlize(record["Name"]) \n'
- ' </title>\n'
- ' </head>\n'
- ' <body>\n'
- ' <a style="cursor:pointer;" onclick="history.back()"><h1>\n'
- ' Editing: $:htmlize(record["Name"]) \n'
- ' </h1></a>\n'
- ' <form action="" method="post" enctype="multipart/form-data">\n'
- ' $:record_form(index, fields, lfields, record, MSDSs)<br />\n'
- ' <input type="submit" value="Submit" />\n'
- ' </form>\n'
- ' </body>\n'
- '</html>\n')
- file(self.dir+'edit.html', 'w').write(edit)
- if not os.path.exists(self.dir+'error.html') :
- # like view, but no edit ability
- # since the content is generated,
- # or backlink list, since there would be so many.
- stat=('$def with (id)\n'
- +html+
- ' <head>\n'
- ' </head>\n'
- ' <body>\n'
- ' <h1>Error</h1>\n'
- ' There is currently no MSDS file for \n'
- ' $:htmlize(id)\n'
- ' </body>\n'
- '</html>\n')
- file(self.dir+'error.html', 'w').write(stat)
- if not os.path.exists(self.dir+'docs.html') :
- docs=(html +
- '<a href="/docs/inventory.pdf">Inventory</a>\n'
- ' in accordance with the \n'
- ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe7">\n'
- ' Chemical Hygiene Plan Section E-7</a>.<br>\n'
- '<a href="/docs/door_warning.pdf">Door warning</a>\n'
- ' in accordance with the Chemical Hygiene Plan Sections\n'
- ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe7">E-7</a>\n'
- ' and \n'
- ' <a href="http://www.drexelsafetyandhealth.com/lab-chem.htm#chpe10">E-10</a>\n'
- '.<br>\n'
- ' </body>\n'
- '</html>\n')
- file(self.dir+'docs.html', 'w').write(docs)
-
-
-class chem_web_daemon (daemon.Daemon) :
- gid = None
- uid = None
- pidfile = PID_FILE
- logfile = LOG_FILE
- loglevel = 'INFO'
- def run(self) :
- if self.pkey_file == None or self.cert_file == None :
- logging.info("http://%s:%d/" % web.validip(self.options.ip))
- else :
- logging.info("https://%s:%d/" % web.validip(self.options.ip))
-
- ## web.py should give detailed error messages, not just `internal error'
- web.internalerror = web.debugerror
-
- ## How we'd start webpy server if we didn't need command line args or SSL
- # Pass web my URL regexps, and the globals from this script
- # You can also pass it web.profiler to help optimize for speed
- #web.run(urls, globals())
- ## How we have to start it now
- webpy_func = web.webpyfunc(urls, globals(), False)
- wsgi_func = web.wsgifunc(webpy_func)
- web.httpserver.runsecure(wsgi_func,
- web.validip(self.options.ip),
- ssl_private_key_filename=self.pkey_file,
- ssl_certificate_filename=self.cert_file)
-
- def read_basic_config(self) :
- pass
- def parse_options(self):
- from optparse import OptionParser
-
- usage_string = ('%prog [options]\n'
- '\n'
- '2008, W. Trevor King.\n')
- version_string = '%%prog %s' % __version__
- parser = OptionParser(usage=usage_string, version=version_string)
-
- # Daemon options
- parser.add_option('--start', dest='action',
- action='store_const', const='start', default='start',
- help='Start the daemon (the default action)')
- parser.add_option('--stop', dest='action',
- action='store_const', const='stop', default='start',
- help='Stop the daemon')
- parser.add_option('-n', '--nodaemon', dest='daemonize',
- action='store_false', default=True,
- help='Run in the foreground')
-
- # Server options
- parser.add_option('-a', '--ip-address', dest="ip",
- help="IP address (default '%default')",
- type='string', metavar="IP", default='0.0.0.0:8080')
- parser.add_option('-s', '--secure', dest="secure",
- help="Run in secure (HTTPS) mode.",
- type='string', metavar="PKEY_FILE:CERT_FILE")
- parser.add_option('-v', '--verbose', dest="verbose", action="store_true",
- help="Print lots of debugging information",
- default=False)
-
- self.options, self.args = parser.parse_args()
- parser.destroy()
-
- if self.options.verbose :
- self.loglevel = 'DEBUG'
- # get options for httpserver
- if self.options.secure != None :
- split = self.options.secure.split(':')
- assert len(split) == 2, "Invalid secure argument '%s'"
- self.pkey_file = split[0]
- self.cert_file = split[1]
- else :
- self.pkey_file = None
- self.cert_file = None
-
-### the following instances and definitions must have
-## global scope. because they are called from inside web
-# create the database
-db = database(filename=DB_FILE)
-MSDSman = MSDS_manager(db)
-dgen = docgen(db)
-
-## set up the templates
-tmplt = templt()
-# Tell templator where to look for templates
-# to provide a framework for the generated webpages
-render = web.template.render(tmplt.dir)
-
-## Define markup functions
-mkup = markup()
-htmlize = lambda t : mkup.htmlize(t)
-name2str = lambda n : mkup.name2str(n)
-mktable = lambda h,r : mkup.make_table(h,r,WIDTH_MAX,raw_fields=('ID','Name'))
-mkadd = lambda ni : mkup.make_add_button(ni)
-record_form = lambda i,f,l,r,M : mkup.record_form_entries(i,f,l,r,M)
-## Give templates access to the htmlize, make_table, and name2str functions
-# ( see http://webpy.org/templetor and our view.html )
-web.template.Template.globals['htmlize'] = htmlize
-web.template.Template.globals['make_table'] = mktable
-web.template.Template.globals['make_add_button'] = mkadd
-web.template.Template.globals['name2str'] = name2str
-web.template.Template.globals['record_form'] = record_form
-
-
-## If this script is run from the command line,
-# use the tools we've just defined to host the chemical inventory
-if __name__=="__main__":
- chem_web_daemon().main()