# Twisted, the Framework of Your Internet # Copyright (C) 2001 Matthew W. Lefkowitz # # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """A web frontend to coil.""" # System Imports import types import sys import string # Twisted Imports from twisted.web import widgets from twisted.python import roots, components, reflect from twisted.python.plugin import getPlugIns from twisted.web import widgets, html from twisted.protocols import http from twisted.internet import protocol # Sibling Imports import coil, app class ConfigRoot(widgets.Gadget, widgets.Widget): """The root of the coil web interface.""" def __init__(self, application): widgets.Gadget.__init__(self) self.putWidget("config", AppConfiguratorPage(application)) self.addFile("images") def display(self, request): request.setResponseCode(http.FOUND) request.setHeader("location", request.prePathURL() + 'config') return ['no content'] class PluginLoader(widgets.Form): """Form for loading plugins.""" def __init__(self, appConfig): self.appConfig = appConfig def getFormFields(self, request): plugins = getPlugIns("coil") mnuList = [] for plugin in plugins: if not plugin.isLoaded(): mnuList.append([plugin.module, plugin.name]) else: mnuList.append([plugin.module, plugin.name + ' (already loaded)']) return [['menu', 'Plugin to load?', 'pluginToLoad', mnuList]] def process(self, write, request, submit, pluginToLoad): plugins = getPlugIns('coil') for plugin in plugins: print plugin.module if plugin.module == pluginToLoad: write( 'loaded ' + plugin.module + '('+pluginToLoad+')' ) plugin.load() self.appConfig.reloadDispensers() break else: write( 'could not load' + plugin.module ) class AppConfiguratorPage(widgets.Presentation): """A web configuration interface for Twisted. This configures the toplevel application. """ template = '''
%%%%self.streamCall(self.displayTree, request)%%%% %%%%self.configd%%%%
%%%%self.pluginLoader()%%%%
''' isLeaf = 1 def __init__(self, application): widgets.Presentation.__init__(self) self.app = app.ApplicationConfig(application) self.reloadDispensers() def reloadDispensers(self): self.dispensers = coil.DispenserStorage(self.app) def pluginLoader(self): return PluginLoader(self) def displayTree(self, write, request): self.displayTreeElement(write, str(self.app), "config", self.app) def displayTreeElement(self, write, inName, inPath, collection, indentLevel=0): subIndent = indentLevel + 1 for name, entity in collection.listStaticEntities(): collection = coil.getCollection(entity) configurator = coil.getConfigurator(entity) if collection or configurator: write('%s + %s
' % (indentLevel * ' ', inPath, name, name)) if collection: self.displayTreeElement(write, name, '%s/%s' % (inPath, name), collection, subIndent) else: write("%s. %s
" % (subIndent * ' ', name)) def prePresent(self, request): self.configd = self.configWidget(request) def configWidget(self, request): """Render the config part of the widget.""" path = request.postpath if path: obj = self.app for elem in path: if elem: # '' doesn't count collection = coil.getCollection(obj) if collection is None: obj = None else: obj = collection.getStaticEntity(elem) if obj is None: # no such subobject request.redirect(request.prePathURL()) return ['Redirecting...'] else: obj = self.app ret = [] linkfrom = string.join(['config']+request.postpath, '/') + '/' cfg = coil.getConfigurator(obj) # add a form for configuration if available if cfg and cfg.configTypes: ret.extend(widgets.TitleBox("Configuration", ConfigForm(self, cfg, linkfrom)).display(request)) # add a form for a collection of objects coll = coil.getCollection(obj) if components.implements(coll, coil.IConfigCollection): if coll.entityType in (types.StringType, types.IntType, types.FloatType): ret.extend(widgets.TitleBox("Delete Items", ImmutableCollectionDeleteForm(self, coll, linkfrom)).display(request)) colClass = ImmutableCollectionForm else: colClass = CollectionForm ret.extend(widgets.TitleBox("Listing", colClass(self, coll, linkfrom)).display(request)) ret.append(html.PRE(str(obj))) return ret def makeConfigMenu(self, interface): """Make a menu for adding a new object to a collection.""" l = [] if 1: for realClass in coil.getImplementors(interface): cfgClass = coil.getConfiguratorClass(realClass) nm = getattr(cfgClass, 'configName', None) or reflect.qual(realClass) l.append(['new '+reflect.qual(realClass), 'new '+nm]) for t in self.dispensers.getDispensers(interface): obj, methodName, desc = t l.append(['dis %d' % hash(t), desc]) return l def makeConfigurable(self, cfgInfo, container, name): """Create a new configurable to a container, based on input from web form.""" cmd, args = string.split(cfgInfo, ' ', 1) if cmd == "new": # create obj = coil.createConfigurable(reflect.namedClass(args), container, name) elif cmd == "dis": # dispense methodHash = int(args) if components.implements(container, coil.IConfigurator) and container.getType(name): interface = container.getType(name) elif components.implements(container, coil.IConfigCollection): interface = container.entityType else: interface = None for t in self.dispensers.getDispensers(interface): obj, methodName, desc = t if hash(t) == methodHash: cfg = coil.getConfigurator(obj) obj = getattr(cfg, methodName)() print "created %s from dispenser" % obj break else: raise ValueError, "Unrecognized command %r in cfgInfo %r" % (cmd, cfgInfo) self.dispensers.addObject(obj) return obj class ConfigForm(widgets.Form): """A form for configuring an object.""" def __init__(self, configurator, cfgr, linkfrom): if not components.implements(cfgr, coil.IConfigurator): raise TypeError self.configurator = configurator # this is actually a AppConfiguratorPage self.cfgr = cfgr self.linkfrom = linkfrom submitNames = ['Configure'] def getFormFields(self, request): existing = self.cfgr.getConfiguration() allowed = self.cfgr.configTypes myFields = [] for name, (cfgType, prompt, description) in allowed.items(): current = existing.get(name) if isinstance(cfgType, types.ClassType) and issubclass(cfgType, components.Interface): inputType = 'menu' inputValue = self.configurator.makeConfigMenu(cfgType) if current: inputValue.insert(0, ['current', "Current Object"]) elif cfgType == types.StringType: inputType = 'string' inputValue = current or '' elif cfgType == types.IntType: inputType = 'int' inputValue = str(current) or '0' elif cfgType == 'boolean': inputType = 'checkbox' inputValue = current else: inputType = 'string' inputValue = "" myFields.append([inputType, prompt, name, inputValue, description]) return myFields def process(self, write, request, submit, **values): existing = self.cfgr.getConfiguration() allowed = self.cfgr.configTypes created = {} for name, cfgInfo in values.items(): write(str((name, cfgInfo)) + "
") if isinstance(allowed[name][0], types.ClassType): if cfgInfo == 'current': continue created[name] = self.configurator.makeConfigurable(cfgInfo, self.cfgr, name) print 'instantiated', created[name] else: created[name] = cfgInfo try: self.cfgr.configure(created) self.format(self.getFormFields(request), write, request) except coil.InvalidConfiguration, ic: raise widgets.FormInputError(ic) class CollectionForm(widgets.Form): """Form for a collection of objects - adding, deleting, etc.""" def __init__(self, configurator, coll, linkfrom): self.configurator = configurator # this is actually a AppConfiguratorPage self.coll = coll self.linkfrom = linkfrom submitNames = ['Insert', 'Delete'] def getFormFields(self, request): itemlst = [] for name, val in self.coll.listStaticEntities(): itemlst.append([name, '%s: %s' % (name, self.linkfrom+name, html.escape(repr(val))), 0]) result = [] if itemlst: result.append(['checkgroup', 'Items in Set
(Select to Delete)', 'items', itemlst]) result.append(['string', "%s to Insert" % self.coll.getNameType(), "name", ""]) result.append(['menu', "%s to Insert" % self.coll.getEntityType(), "type", self.configurator.makeConfigMenu(self.coll.entityType)]) return widgets.Form.getFormFields(self, request, result) def process(self, write, request, submit, name="", type=None, items=()): # write(str(('YAY', name, type))) # TODO: validation on the name? if submit == 'Delete': for item in items: obj = self.coll.getStaticEntity(item) if components.implements(obj, coil.IConfigurator): obj = obj.getInstance() self.configurator.dispensers.removeObject(obj) self.coll.delEntity(item) write("Items Deleted.
(%s)
" % html.escape(repr(items))) elif submit == "Insert": obj = self.configurator.makeConfigurable(type, self.coll, name) self.coll.putEntity(name, obj) write("%s created!" % type) else: raise widgets.FormInputError("Don't know how to %s" % repr(submit)) self.format(self.getFormFields(request), write, request) class ImmutableCollectionForm(widgets.Form): """A collection of immutable objects such as strings or integers.""" typeMap = {types.StringType : 'string', types.IntType : 'integer', types.FloatType : 'float' } def __init__(self, appcpage, coll, linkfrom): self.appcpage = appcpage self.collection = coll self.linkfrom = linkfrom def getFormFields(self, request): result = [] for name, val in self.collection.listStaticEntities(): result.append(['string', name, 'val_%s' % name, val]) result.append(['string', "New %s to Insert" % self.collection.getNameType(), "name", ""]) kind = self.typeMap[self.collection.entityType] result.append([kind, "%s to Insert" % self.collection.getEntityType(), "value", ""]) return widgets.Form.getFormFields(self, request, result) def process(self, write, request, submit, name, value, **newitems): if name: self.collection.putEntity(name, value) write("%s created!" % name) for key, value in newitems.items(): if len(key) <= 4 or key[:4] != "val_": continue key = key[4:] self.collection.putEntity(key, value) write("%s changed!" % key) self.format(self.getFormFields(request), write, request) class ImmutableCollectionDeleteForm(widgets.Form): """A collection of immutable objects such as strings or integers. This form allows you to delete entries. """ submitNames = ["Delete"] def __init__(self, appcpage, coll, linkfrom): self.appcpage = appcpage self.collection = coll self.linkfrom = linkfrom def getFormFields(self, request): itemlst = [] for name, val in self.collection.listStaticEntities(): itemlst.append([name, '%s: %s' % (name, html.escape(repr(val))), 0]) result = [] if itemlst: result.append(['checkgroup', 'Items in Set
(Select to Delete)', 'items', itemlst]) return widgets.Form.getFormFields(self, request, result) def process(self, write, request, submit, items=()): for item in items: self.collection.delEntity(item) write("Items Deleted.
(%s)
" % html.escape(repr(items))) self.format(self.getFormFields(request), write, request)