From f68cc362eba9dae25a85f6e9025b9c50336c4a49 Mon Sep 17 00:00:00 2001 From: wking Date: Thu, 28 Aug 2008 19:17:32 -0400 Subject: [PATCH] Added valid_record option to door_warning() Also deleted chem_web_secure.py, since it is trivially similar to chem_web.py. Only the log/pid filenames and the value of the SSL variable were different. --- chem_db.py | 16 +- chem_web_secure.py | 560 --------------------------------------------- 2 files changed, 13 insertions(+), 563 deletions(-) delete mode 100755 chem_web_secure.py diff --git a/chem_db.py b/chem_db.py index 181d066..aabe348 100644 --- a/chem_db.py +++ b/chem_db.py @@ -172,6 +172,7 @@ class docgen (object) : os.system('cp ./docs/main.pdf %s' % path) return path def inventory(self, namewidth='a') : + "Create a pdf list of all currently owned chemicals." pp = db_pretty_printer(self.db) active_ids = [] for record in self.db.records() : @@ -215,7 +216,16 @@ class docgen (object) : ## run latex path = self._make_pdf('inventory.pdf') return path - def door_warning(self) : + def door_warning(self, + valid_record=lambda r: r['Disposed'] == '') : + """create a warning NFPA diamond and list of the most dangerous + chemicals for which valid_record(record) is true. For + example, to generate a door warning for the front door use + door_warning(lambda r: r['Disposed'] == '') + or to generate the warning for the fridge + door_warning(lambda r: r['Location'] == 'Refrigerator') + Note that valid_record defaults to the first example. + """ pp = db_pretty_printer(self.db) all_ids = range(self.db.len_records()) @@ -229,7 +239,7 @@ class docgen (object) : Reactivities = [] Others = [] for record in self.db.records() : - if record['Disposed'] == '' : # chemicals we still have + if valid_record(record) : for field in ['H', 'F', 'R', 'O'] : r = record[field] if r != '' and r != '?' : @@ -246,7 +256,7 @@ class docgen (object) : # now that we've found the max NFPAs, # find all the chemicals at those levels for record in self.db.records() : - if record['Disposed'] == '' : # chemicals we still have + if valid_record(record) : for field,array in zip(['H', 'F', 'R', 'O'], [Healths, Fires, Reactivities, Others]) : diff --git a/chem_web_secure.py b/chem_web_secure.py deleted file mode 100755 index d17395d..0000000 --- a/chem_web_secure.py +++ /dev/null @@ -1,560 +0,0 @@ -#!/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'] = '%s' \ - % (id, record['ID']) - if MSDSman.has_MSDS(id) : # link the id to the MSDS - record['Name'] = '%s' \ - % (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 = '
\n' % next_db_id - string += ' \n' - string += '

\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'}]), - - - - -
ABC
ab
def
- """ - # open the table - string = '\n' - # add the header - string += '' - for field in fields : - string += '' % self.htmlize(field) - string += '\n' - # add the records - for record in records : - string += '' - 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 += '' % rstring - string += '\n' - # close the table - string += '
%s
%s
\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'}), - - - - - - - -
:
:
:
- """ - # open the table - string = '\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 += ' \n' % (fstring, lfstring) - string += ' \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 += ' \n' \ - % (fstring, lfstring) - string += ' \n' \ - % (fstring, fstring) - - # file upload - fstring = 'MSDS' - lfstring = 'Upload MSDS' - string += ' \n' \ - % (fstring, lfstring) - string += ' \n' \ - % (entry_width, fstring, fstring) - - # file share menu - fstring = 'MSDS share' - lfstring = 'Use an already uploaded MSDS' - string += ' \n' \ - % (fstring, lfstring) - string += ' \n' - # close the table - string += '
:
:Upload\n' \ - % (fstring, fstring) - string += ' Share
:
:

\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=('\n' - '\n') - if not os.path.exists(self.dir+'view.html') : - view=('$def with (name, next_db_id, fields, records)\n' - +html+ - ' \n' - ' \n' - ' $:name2str(name) \n' - ' \n' - ' \n' - ' \n' - '

\n' - ' $:name2str(name)\n' - '

\n' - ' See \n' - ' the rules\n' - ' for more information.\n' - ' See the docs page to generate required documents.
\n' - '
\n' - ' $:make_add_button(next_db_id)\n' - ' $:make_table(fields, records)\n' - ' $:make_add_button(next_db_id)\n' - ' \n' - '\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+ - ' \n' - ' \n' - ' $:htmlize(record["Name"]) \n' - ' \n' - ' \n' - ' \n' - '

\n' - ' Editing: $:htmlize(record["Name"]) \n' - '

\n' - '
\n' - ' $:record_form(index, fields, lfields, record, MSDSs)
\n' - ' \n' - '
\n' - ' \n' - '\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+ - ' \n' - ' \n' - ' \n' - '

Error

\n' - ' There is currently no MSDS file for \n' - ' $:htmlize(id)\n' - ' \n' - '\n') - file(self.dir+'error.html', 'w').write(stat) - if not os.path.exists(self.dir+'docs.html') : - docs=(html + - 'Inventory\n' - ' in accordance with the \n' - ' \n' - ' Chemical Hygiene Plan Section E-7.
\n' - 'Door warning\n' - ' in accordance with the Chemical Hygiene Plan Sections\n' - ' E-7\n' - ' and \n' - ' E-10\n' - '.
\n' - ' \n' - '\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() -- 2.26.2