# -*- test-case-name: mv3d.test.test_editor -*- # Copyright (C) 2008-2012 Mortal Coil Games # See LICENSE for details. """ Code related to world editing """ from mv3d.util.conductor import findParent from mv3d.util.iservice import IConductor import os from datetime import datetime from twisted.internet import defer from twisted.internet.defer import inlineCallbacks, returnValue from zope.interface import implements #@UnresolvedImport from nevow import rend, loaders, inevow, tags as T from mv3d.util.modifier import IModifiable, IModifier from mv3d.util.classgen import ClassGenerator from mv3d.server.asset import AssetGroup from mv3d.server.account import Account from mv3d.server.directory import IDirectoryService from mv3d.resource.asset import Asset from mv3d.net.client import ServiceLoc def makeFriendlyURL(text): """ Turn text into a friendly URL """ def fixChar(c): if c == "/": return c if c == " ": return "_" if not c.isalnum(): return "+" return c return "".join(map(fixChar, text)).replace("+", "") def getEditableSections(parent, client): """ Returns the list of services that are IModifiable and that client has permission to access. """ sections = [] if (IModifiable.providedBy(parent) and parent.checkPermissions( client, "modify")): sections.append(parent) for svc in parent.services: if IModifiable.providedBy(svc) and svc.checkPermissions( client, "modify"): sections.append(svc) return sections def getPageOffsetLimit(pageSize, page): """ Returns an offset and limit suitable for getting the given page assuming pageSize entries per page """ return (page - 1) * pageSize, pageSize def shorten(string, length=50): """ Shorten the given string to be max of length characters. Add ... to abbreviate """ if string is None: return "none" if len(string) < length: return string return string[:50] + "..." class FragmentWithTemplateDir(rend.Fragment): """ Handles some basic functions of the fragments """ def __init__(self, root, section=None): self.root = root self.client = root.client self.parent = root.parent self.templateDir = root.templateDir self.section = section self.docFactory = loaders.xmlfile(self.templateFile(self.template)) def templateFile(self, name): """ Returns the full path of a template given the filename """ return os.path.join(self.templateDir, name) class MainNav(FragmentWithTemplateDir): """ This fragment renders the main navagation bar """ sections = None template = "main_nav.xml" def generateSectionLink(self, section): """ Returns a URL to the section """ url = makeFriendlyURL( "/".join(["", self.root.location, section])) return T.a(href=url)[section] def data_sections(self, ctx, data): """ Returns tags for each section of the editor """ if self.sections is not None: return self.sections self.sections = [self.generateSectionLink(s.name) for s in getEditableSections(self.parent.parent, self.client)] return self.sections def render_section(self, ctx, data): """ Render a single section link """ if data.children[0] == self.section: return ctx.tag(class_="navSelected")[T.b[data]] return ctx.tag[T.b[data]] class SubNav(FragmentWithTemplateDir): """ This fragment renders the section navagation bar """ template = "sub_nav.xml" modifiers = None def generateModifierLink(self, modifier): """ Returns a URL to the modifier """ url = makeFriendlyURL("/".join(["", self.root.location, self.section, modifier.shortName])) return T.a(href=url)[modifier.name] def data_modifiers(self, ctx, data): """ Returns a list of modifiers """ if self.modifiers is not None: return self.modifiers d = self.root.getAllTopModifiers() def gotEm(mods): self.modifiers = [self.generateModifierLink(m) for m in mods[self.section]] return self.modifiers return d.addCallback(gotEm) def render_modifier(self, ctx, data): """ Return a single modifier link """ return ctx.tag[data] class Footer(FragmentWithTemplateDir): """ This fragment renders the footer """ template = "footer.xml" def render_username(self, ctx, data): """ Just render the logged in user """ return ctx.tag[self.root.client["username"]] class WebEditMixin(rend.Page): """ Used to render all editing pages """ def setupFragments(self): """ Set up the navagation and footer fragments """ self.docFactory = loaders.xmlfile(self.templateFile("index.xml")) self.mainNav = MainNav(self.root, self.section) if self.section is not None: self.subNav = SubNav(self.root, self.section) self.footer = Footer(self.root) def templateFile(self, name): """ Returns the full path of a template given the filename """ return os.path.join(self.root.templateDir, name) def render_title(self, ctx, data): """ Returns the title for the page """ if self.section is None: return T.title["MV3D Editor"] return T.title["%s - MV3D Editor" % self.section] def render_mainNav(self, ctx, data): """ Renders the main navagation bar """ return ctx.tag[self.mainNav] def render_subNav(self, ctx, data): """ Renders the section specific navagation bar """ if self.subNav is None: return "No sub navagation." return ctx.tag[self.subNav] def render_editPane(self, ctx, data): """ Render the main edit pane """ if self.editPane is None: d = self.getEditPane() return d.addCallback(lambda p: ctx.tag[p]) return ctx.tag[self.editPane] def render_footer(self, ctx, data): """ Render the footer """ return ctx.tag[self.footer] def getEditPane(self): """ Normally used to grab the edit pane. This implementation will return nothing """ return defer.succeed("") def readyMod(self, modifier): """ Get a modifier ready to be used with the web editor """ modifier.root = self.root modifier.client = self.root.client modifier.section = self.section modifier.setDocFactory(self.root.templateDir) self.editPane = modifier def locateChild(self, ctx, segments): """ A deferrable locateChild implementation """ d, segment = rend.Page.locateChild(self, ctx, segments) if isinstance(d, defer.Deferred): return d.addCallback(lambda r: (r, segment)) return d, segment class WebEditSection(WebEditMixin): """ A section in the web editor """ addSlash = True editPane = None subNav = None modifierName = None def __init__(self, root, section): self.section = section self.root = root try: self.target = self.root.parent.parent.getServiceNamed(self.section) except KeyError: if self.root.parent.parent.name == self.section: self.target = self.root.parent.parent else: raise self.setupFragments() @inlineCallbacks def getEditPane(self): """ Get the modifier needed """ mods = yield self.root.getAllTopModifiers() if mods.has_key(self.section) and len(mods[self.section]): for mod in mods[self.section]: for perm in mod.getMinimumPermissions(): if self.target.checkPermissions(self.root.client, perm): self.readyMod(mod) returnValue(mod) returnValue("") @inlineCallbacks def childFactory(self, ctx, name): """ Check if name is the name of a modifier """ topMods = yield self.root.getAllTopModifiers() if not topMods.get(self.section): return mods = topMods[self.section] for m in mods: if m.shortName == name: for mod in topMods[self.section]: for perm in mod.getMinimumPermissions(): if self.target.checkPermissions(self.root.client, perm): returnValue(WebEditModifier(self.root, self.section, self.target, m.name)) class AccessDenied(rend.Fragment): """ A fragment that just displays access denied """ def __init__(self, username, object, access): self.docFactory = loaders.stan(T.b["Access Denied for ", T.i[username], " to ", access, " ", str(object.__class__)]) class WebEditModifier(WebEditMixin): """ This page is for a modifier """ addSlash = True editPane = None def __init__(self, root, section, target, modifierName): self.root = root self.section = section self.target = target self.modifierName = modifierName self.setupFragments() @inlineCallbacks def getEditPane(self): """ Get the modifier needed """ # if (hasattr(self.target, "check") and # not self.target.checkPermissions(self.root.client, "modify")): # returnValue(AccessDenied(self.root.client["username"], # self.target, "modify")) conductor = findParent(self.root, IConductor) mods = yield self.target.getModifiers("web", conductor=conductor) for modifier in mods: if modifier.name == self.modifierName: for perm in modifier.getMinimumPermissions(): if self.target.checkPermissions(self.root.client, perm): self.readyMod(modifier) returnValue(modifier) else: returnValue(AccessDenied(self.root.client["username"], self.target, "modify")) modlis = [T.li[T.a(href=mod.shortName)[mod.name]] for mod in mods] returnValue(["Available modifiers for ", T.b[ str(self.target.__class__)], ":", T.ul[modlis]]) @inlineCallbacks def childFactory(self, ctx, name): """ Pass this off to our edit pane unless we have no modifier """ if self.modifierName is None: # if (hasattr(self.target, "check") and # not self.target.checkPermissions(self.root.client, # "modify")): # w = WebEditModifier(self.root, self.section, None, None) # w.editPane = AccessDenied(self.root.client["username"], # self.target, "modify") # returnValue(w) conductor = findParent(self.root, IConductor) mods = yield self.target.getModifiers("web", conductor=conductor) for mod in mods: if mod.shortName == name: for perm in mod.getMinimumPermissions(): if self.target.checkPermissions(self.root.client, perm): returnValue(WebEditModifier( self.root, self.section, self.target, mod.name)) else: w = WebEditModifier(self.root, self.section, None, None) w.editPane = AccessDenied(self.root.client["username"], self.target, "modify") returnValue(w) returnValue(None) if self.editPane is None: yield self.getEditPane() if self.editPane is None: returnValue(None) ep = yield defer.maybeDeferred(self.editPane.childFactory, ctx, name) returnValue(ep) class WebRoot(WebEditMixin): """ The root page for the web based editor """ addSlash = True topMods = None editPane = None location = "webedit" # TODO: HARDCODE = BAD section = None def __init__(self, client, parent, templatedir=None): default = None self.client = client self.parent = parent self.templateDir = templatedir self.root = self self.sections = getEditableSections(parent.parent, client) if len(self.sections): self.section = self.sections[0].name self.setupFragments() def childFactory(self, ctx, name): """ Check if name is the name of a modifier """ for s in self.sections: if makeFriendlyURL(s.name) == name: return WebEditSection(self, s.name) def getAllTopModifiers(self): """ Returns all the top level modifiers """ if self.topMods is not None: return defer.succeed(self.topMods) d = [] self.topMods = {} def addEm(r, sect): self.topMods[sect] = r conductor = findParent(self.root, IConductor) for s in self.sections: d.append(s.getModifiers("web", conductor=conductor).addCallback( addEm, s.name)) return defer.gatherResults(d).addCallback(lambda _: self.topMods) class Stats(rend.Page): """ A generalized Stats viewer """ implements(IModifier) root = None client = None target = None section = None shortName = "stats" name = "Stats" def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["read"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ self.docFactory = loaders.xmlfile(os.path.join(templateDir, "stats.xml")) def data_stats(self, ctx, data): """ Returns the statistics for this server properly formatted for the page, which means [[name1, stat1, name2, stat2], [name3, stat3...] """ stats = self.target.getStatistics() data = [] sub = [] for name, stat in stats.items(): sub.append(name) if isinstance(stat, float): stat = "%.2f" % stat sub.append(stat) if len(sub) == 4: data.append(sub) sub = [] if sub: data.append(sub) return data class List(rend.Fragment): """ Base class for an editor that relies on displaying a list of items. """ implements(IModifier) root = None client = None section = None target = None shortName = "list" name = "List" perPage = 20 attribs = None classes = None title = None def __init__(self, target): self.target = target def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ self.docFactory = loaders.xmlfile(os.path.join(templateDir, "list.xml")) def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["read", "modify"] def render_addForm(self, ctx, data): """ Render the add asset form tag """ return ctx.tag(method="POST", action="#post") def render_checkPost(self, ctx, data): """ Check the post args """ if ctx.arg("new"): if not self.target.checkPermissions(self.root.client, "modify"): return "Access denied!" return self.createNew(ctx) return ctx.tag def data_items(self, ctx, data): """ Grab the list of items """ request = inevow.IRequest(ctx) page = int(request.args.get("page", [1])[0]) offset, limit = getPageOffsetLimit(self.perPage, page) return self.getItems(offset, limit) @inlineCallbacks def render_item(self, ctx, item): """ Render an item in the list """ data = [] for attrib in self.attribs: name = attrib.get("name") if name is not None: if name == "self": value = item elif "." in name: value = item for segment in name.split("."): value = getattr(value, segment) else: value = getattr(item, name) else: value = attrib.get("text") display = attrib.get("display") if display is not None: value = yield display(value) if value is None: value = "none" link = attrib.get("link") if link is not None: url = self.getLink(item) value = T.a(href=url + link)[value] data.append(T.td[value]) returnValue(ctx.tag[data]) def data_classes(self, ctx, data): """ Return a list of classes """ return self.classes def render_title(self, ctx, data): """ Return the title """ return ctx.tag[T.th(colspan=len(self.attribs))[self.title]] def render_columns(self, ctx, data): """ Render the column list """ return ctx.tag[[T.th[attrib["title"]] for attrib in self.attribs]] def render_customCreate(self, ctx, data): """ Render any extra create related items """ return ctx.tag @inlineCallbacks def linkModifiers(self, target): """ Returns a link to each of the web modifiers """ links = [] conductor = findParent(self.root, IConductor) mods = yield target.getModifiers("web", conductor=conductor) for modifier in mods: links.append([T.li()[T.a(href="/".join([self.getLink(target), modifier.shortName]))[modifier.name]]]) returnValue(links) def callIt(result): return result() def callItWithLen(result): return len(result()) class ViewAssetGroups(List): """ Show asset groups available on this server """ shortName = "group" name = "List Groups" attribs = [dict(name="agid", title="ID", link="/edit"), dict(name="name", title="Name", link="/edit"), dict(name="description", title="Description", display=shorten), #dict(name="getAssets", title="Assets", display=callItWithLen), dict(text="delete", title="Delete", link="/delete") ] classes = ["mv3d.server.asset.AssetGroup"] title = "Asset Groups" def getLink(self, group): """ Return the link for the group """ return "/".join(["", self.root.location, self.section, self.shortName, str(group.agid)]) def getItems(self, offset, limit): """ Returns items in the list """ return AssetGroup.query(self.target.store, offset=offset, limit=limit) def childFactory(self, ctx, name): """ Name here is assumed to be the asset group id """ agid = int(name) ag = self.target.getAssetGroup(agid).group return WebEditModifier(self.root, self.section, ag, None) @inlineCallbacks def createNew(self, ctx): """ create a new asset group... """ agid = yield self.target.newMasterAssetGroup(ClassGenerator( ctx.arg("classText") or ctx.arg("classSelect"))) group = self.target.getAssetGroup(agid) group.grantPermission("all", self.root.client["username"]) returnValue(T.a(href="%s/edit" % self.getLink(group.group))[ "Created Asset Group %s" % repr(group.uid)]) class GroupAsset(rend.Fragment): """ Link to an asset from a group """ root = None client = None section = None target = None shortName = "asset" name = "Asset" def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["read", "modify"] def setDocFactory(self, templateDir): """ There is no doc factory """ @inlineCallbacks def childFactory(self, ctx, name): """ Name here is assumed to be the asset id """ aid = (self.target.agid, int(name)) conductor = findParent(self.root, IConductor) asset = yield self.target.getAsset(conductor, aid) asset.parent = self.target.parent returnValue(WebEditModifier(self.root, self.section, asset, None)) def notEditable(name, data): """ Used for things that can not be edited """ return str(data) def notEditableDate(name, data): """ Format a date based on a timestamp """ if data is None: return "none" return datetime.fromtimestamp(data).strftime("%c") def textBox(name, data): """ Edit via a plain old text box """ return T.input(type="text", name=name, value=data or "") def textBoxWithURL(name, data): """ This text box also has a link after it to visit the url it contains """ link = "visit" if data: for ext in [".jpg", ".gif", ".png", ".tif"]: if data.endswith(ext): link = T.img(src=data, width=30, height=30) return T.div[T.input(type="text", name=name, value=data or ""), T.a(href=data, target="_blank")[link]] def splitPath(name, data): """ Display a path that was split """ if data is None: return T.input(type="text", name=name, value="") return T.input(type="text", name=name, value="/".join(data)) def setSplitPath(setter): def f(target, value): """ Sets a split path """ getattr(target, setter)(value.split("/")) return f def unfoldList(name, data): """ Display a list """ if data is None: return "" return T.input(type="text", name=name, value=", ".join( [str(l) for l in data])) def setIdList(setter): def f(target, value): """ Sets based on a list of tuples """ tuples = value.split("(") result = [] for tple in tuples: tple = tple.strip(", )") if len(tple): result.append(tuple([int(x) for x in tple.split(",")])) getattr(target, setter)(result) return f def foldList(setter): def f(target, value): """ Sets based on a list """ getattr(target, setter)([d.strip() for d in value.split(",")]) return f def textArea(name, data): return T.textarea(name=name, size=5, cols=60)[data or ""] class WebEditTarget(rend.Fragment): """ Basic framework for editing an object """ implements(IModifier) root = None client = None section = None target = None perPage = 20 template = None attribs = None def __init__(self, target): self.target = target def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ self.docFactory = loaders.xmlfile(os.path.join(templateDir, self.template)) def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["read", "modify"] def data_target(self, ctx, data): """ Return the list of editable elements in the format of: [ (name1, value1), (name2, value2), ... ] """ data = [] for attrib in self.attribs: name, label, formatter, setter = attrib data.append([label, formatter(name, getattr(self.target, name))]) return data def render_editField(self, ctx, data): """ Render the edit field for this """ return ctx.tag[data] def setAttribs(self, request): """ Set the attributes of the target according to the post args """ for attrib in self.attribs: name, label, formatter, setter = attrib if formatter is not notEditable: value = request.args.get(name) if value is not None: value = value[0] if setter is not None and not callable(setter): getattr(self.target, setter)(value) elif setter is not None: setter(self.target, value) else: setattr(self.target, name, value) def render_checkPost(self, ctx, _data): """ Check the post args """ request = inevow.IRequest(ctx) if request.method == "POST": if not self.target.checkPermissions(self.root.client, "modify"): return ctx.tag[T.i["Permission denied for %s." % self.target.__class__.__name__]] self.setAttribs(request) return ctx.tag[T.i["Updated %s." % self.target.__class__.__name__]] return ctx.tag class AssetGroupEditor(WebEditTarget): """ This is the asset group edit modifier """ shortName = "edit" name = "Edit Group" attribs = [("agid", "ID:", notEditable, "setID"), ("name", "Name:", textBox, "setName"), ("description", "Description:", textArea, "setDescription"), ] template = "assetgroupedit.xml" def getAssetLink(self, asset): """ Returns a link to an asset """ return "/".join(["", self.root.location, self.section, "group", str(asset.aid[0]), "asset", str(asset.aid[1])]) def render_groupName(self, ctx, data): """ Just render the group name """ return ctx.tag["%s (%d)" % (self.target.name, self.target.agid)] def data_assets(self, ctx, data): """ Grab the list of assets, sort it, and return an offset/limited result. """ request = inevow.IRequest(ctx) page = int(request.args.get("page", [1])[0]) offset, limit = getPageOffsetLimit(self.perPage, page) r = Asset.query(self.target.store, (Asset.aid >= (self.target.agid, 0)) & (Asset.aid < (self.target.agid + 1, 0)), offset=offset, limit=limit) return r def render_asset(self, ctx, asset): """ Render an asset entry in the list """ lnk = self.getAssetLink(asset) if asset.description: desc = asset.description[:50] if len(asset.description) > 50: desc += "..." else: desc = "none" return ctx.tag[ T.td[T.a(href="%s/edit" % lnk)[str(asset.aid)]], T.td[T.a(href="%s/edit" % lnk)[asset.name or "unnamed"]], T.td[asset.__class__.__name__], T.td[asset.author or "none"], T.td[desc], T.td[", ".join([str(d) for d in asset.dependencies]) or "none"], T.td[T.a(href="%s/delete" % lnk)["delete"]] ] def render_pageTurn(self, ctx, asset): """ Render page turning logic """ request = inevow.IRequest(ctx) page = int(request.args.get("page", [1])[0]) assetCount = self.target.cluster.iddisp.nextID pages = int(assetCount / self.perPage) if assetCount % self.perPage != 0: pages += 1 if page > 1: prev = T.a(href="?page=%d" % (page - 1))["previous"] else: prev = "" pageForm = T.form(method="POST", target="#page", name="pageturn")[ "page ", T.input(class_="text", size=3, name="page", value=str(page)), "of %d" % pages] if page < pages: next = T.a(href="?page=%d" % (page + 1))["next"] else: next = "" return ctx.tag[T.td[prev], T.td(colspan=5)[pageForm], T.td[next]] def render_addForm(self, ctx, data): """ Render the add asset form tag """ return ctx.tag(method="POST", action="#post") def data_assetClasses(self, ctx, data): """ Return a list of asset classes """ return ["mv3d.resource.ogre3d.URLOgreMaterialAsset", "mv3d.resource.ogre3d.URLOgreObjectAsset", "mv3d.resource.url.URLImageAsset", "mv3d.resource.url.URLClassGeneratorAsset", "mv3d.resource.local.LocalClassGeneratorAsset", ] def render_checkPost(self, ctx, data): """ Check the post args """ if ctx.arg("edit"): return WebEditTarget.render_checkPost(self, ctx, data) if ctx.arg("new"): if not self.target.checkPermissions(self.root.client, "modify"): return "Access denied!" # create a new asset... d = self.target.newAsset(findParent(self.root, IConductor), ClassGenerator(ctx.arg("classText") or ctx.arg("classSelect"))) def gotAsset(asset): asset.grantPermission("all", self.root.client["username"]) return T.a(href="%s/edit" % self.getAssetLink(asset))[ "Created Asset %s" % repr(asset.aid)] return d.addCallback(gotAsset) return ctx.tag class AssetEditor(WebEditTarget): """ This is the base asset edit modifier """ shortName = "edit" name = "Edit Asset" attribs = [("aid", "ID:", notEditable, None), ("__class__", "Class:", notEditable, None), ("name", "Name:", textBox, "setName"), ("dependencies", "Dependencies:", unfoldList, setIdList( "setDependencies")), ("author", "Author:", textBox, "setAuthor"), ("copyright", "Copyright:", textBox, "setCopyright"), ("license", "License:", textBox, "setLicense"), ("description", "Description:", textArea, "setDescription"), ("revisor", "Revisor:", textBox, "setRevisor"), ("comment", "Comment:", textArea, "setComment"), ] template = "edit.xml" def render_name(self, ctx, data): """ Just render the asset name """ return ctx.tag["%s %r" % (self.target.name, self.target.aid)] class URLAssetEditor(AssetEditor): """ Asset edit modifier for URL assets """ attribs = AssetEditor.attribs + [ ("url", "URL:", textBoxWithURL, "setURL"), # ("basedir", "Base Directory:", textBox, "setBaseDir"), ("localfile", "Local File Name:", splitPath, setSplitPath("setLocalFile")), ] class URLClassGeneratorAssetEditor(URLAssetEditor): """ Asset edit modifier for URL assets """ attribs = URLAssetEditor.attribs + [ ("classname", "Class Name:", textBox, "setClass"), ] class LocalClassGeneratorAssetEditor(AssetEditor): """ Asset edit modifier for URL assets """ attribs = AssetEditor.attribs + [ ("modulename", "Module Name:", textBox, "setModule"), ("classname", "Class Name:", textBox, "setClass"), ] class LocalOgreMaterialAssetEditor(AssetEditor): """ Asset edit modifier for URL assets """ attribs = AssetEditor.attribs + [ ("filename", "File Name:", textBox, "setFilename"), ] class URLOgreMaterialAssetEditor(URLAssetEditor): """ Asset edit modifier for URL assets """ attribs = URLAssetEditor.attribs + [ ("materialname", "Material Name:", textBox, "setMaterialName"), ] class DeleteAsset(rend.Fragment): """ Used for deleting an asset """ shortName = "delete" name = "Delete Asset" implements(IModifier) root = None client = None section = None target = None perPage = 20 attribs = None docFactory = loaders.stan(T.p(render=T.directive("checkPost"))[ "Are you sure you want to delete ", T.div(render=T.directive("asset")), "?", T.form(action="#post", method="POST")[ T.input(type="submit", name="yes", value="Yes"), T.input(type="submit", name="no", value="No", onclick="history.go(-1)") ]]) def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["modify"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ def render_asset(self, ctx, data): """ Render the asset's name and ID """ return "%s %r" % (self.target.name, self.target.aid) def render_checkPost(self, ctx, data): """ Check for posting and results """ if ctx.arg("yes"): # delete away group = self.target.parent.getAssetGroup(self.target.aid[0]) if not hasattr(group, "group"): return "Asset group is not local!" group.group.remAsset(self.target.aid) return "Deleted" return ctx.tag class DeleteAssetGroup(rend.Fragment): """ Used for deleting an asset group """ shortName = "delete" name = "Delete Asset" implements(IModifier) root = None client = None section = None target = None perPage = 20 attribs = None docFactory = loaders.stan(T.p(render=T.directive("checkPost"))[ "Are you sure you want to delete ", T.div(render=T.directive("group")), "?", T.form(action="#post", method="POST")[ T.input(type="submit", name="yes", value="Yes"), T.input(type="submit", name="no", value="No", onclick="history.go(-1)") ]]) def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["modify"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ def render_group(self, ctx, data): """ Render the assetgroup's name and ID """ return "%s (%r)" % (self.target.name, self.target.agid) def render_checkPost(self, ctx, data): """ Check for posting and results """ if ctx.arg("yes"): # delete away d = self.target.parent.remAssetGroup(self.target.agid) return d.addCallback(lambda _: "Deleted") return ctx.tag def mailLink(address): if address is None: return "none" return T.a(href="mailto:%s" % address)[address] class ViewAccounts(List): """ Show accounts available on this server """ shortName = "account" name = "List Accounts" classes = ["mv3d.server.account.Account"] title = "Accounts" def __init__(self, target): List.__init__(self, target) self.attribs = [ dict(name="username", title="Name", link="/edit"), dict(name="email", title="Email", display=mailLink), dict(name="groups", title="Groups", display=", ".join), dict(name="status", title="Status"), dict(name="self", title="Modify", display=self.linkModifiers) ] def getLink(self, account): """ Return the link for the account """ return "/".join(["", self.root.location, self.section, self.shortName, account.username]) def getItems(self, offset, limit): """ Returns items in the list """ query = Account.query(self.target.store, offset=offset, limit=limit, order=Account.username.asc) for item in query: item.parent = self.target return query def childFactory(self, ctx, name): """ Name here is assumed to be the asset group id """ acct = Account.get(self.target.store, Account.username == name) acct.parent = self.target return WebEditModifier(self.root, self.section, acct, None) @inlineCallbacks def createNew(self, ctx): """ create a new account. """ self.target.createAccount(ctx.arg("name"), None, None) account = yield self.target.getAccount(ctx.arg("name")) account.grantPermission("all", self.root.client["username"]) returnValue(T.a(href="%s/edit" % self.getLink(account))[ "Created Account %s" % repr(account.username)]) def render_customCreate(self, ctx, data): """ Render any extra create related items """ return ["Name:", T.input(name="name", type="text"), T.input(type="submit", name="new", value="New")] def lastLog(name, value): """ Return the last few log entries """ entries = value(sortDir="desc", limit=5) out = [] for entry in entries: out.append(T.tr[T.td[entry.toString()]]) return T.table[out] def accountBalance(name, value): """ Return the account balance """ return "$%s" % value() def emptyTextArea(name, value): """ Place to enter a comment """ return T.textarea(name=name, size=5, cols=60)[""] def setComment(target, value): """ Sets a comment on the account """ if value == "": return target.log(None, "comment", value) class AccountEditor(WebEditTarget): """ This is the base asset edit modifier """ shortName = "edit" name = "Edit Account" attribs = [ ("username", "Name:", notEditable, None), ("creationDate", "Account Created:", notEditableDate, None), ("lastlogin", "Last Login:", notEditableDate, None), ("failedLogins", "Failed Logins:", notEditable, None), ("getAccountBalance", "Balance:", accountBalance, None), ("getLogEntries", "Log Entries:", lastLog, None), ("status", "Status:", textBox, "setStatus"), ("email", "Email:", textBox, "setEmail"), ("groups", "Groups:", unfoldList, foldList("setGroups")), ("password", "Password:", textBox, "setPassword"), ("pcs", "PCs:", unfoldList, setIdList( "setPCs")), ("log", "Comment:", emptyTextArea, setComment), ] template = "edit.xml" def render_name(self, ctx, data): """ Just render the name """ return ctx.tag[self.target.username] class DeleteAccount(rend.Fragment): """ Used for deleting an account """ shortName = "delete" name = "Delete Account" implements(IModifier) root = None client = None section = None target = None perPage = 20 attribs = None docFactory = loaders.stan(T.p(render=T.directive("checkPost"))[ "Are you sure you want to delete ", T.div(render=T.directive("account")), "?", T.form(action="#post", method="POST")[ T.input(type="submit", name="yes", value="Yes"), T.input(type="submit", name="no", value="No", onclick="history.go(-2)") ]]) def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["modify"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ def render_account(self, ctx, data): """ Render the account's name """ return "%s" % self.target.username def render_checkPost(self, ctx, data): """ Check for posting and results """ if ctx.arg("yes"): # delete away self.target.parent.remAccount(self.target) return "Deleted" return ctx.tag class Permission: """ Defines a single permission """ def __init__(self, action, permission, entity): self.action = action self.permission = permission self.entity = entity def __eq__(self, other): """ Test that all items are equal """ if self.action != other.action: return False if self.permission != other.permission: return False if self.entity != other.entity: return False return True class EditPermissions(List): """ Permission editor """ implements(IModifier) shortName = "permissions" name = "Edit " attribs = [ dict(name="action", title="Action"), dict(name="permission", title="Permission"), dict(name="entity", title="Account / Group"), dict(text="delete", title="Delete", link="/delete") ] title = "" def getLink(self, permission): """ Return the link for the permission (not allowed """ return "/".join([".", str( self.generatePermissions().index(permission))]) def generatePermissions(self): """ Generate the list of permissions """ perms = [] for permission, grantees in self.target.allowed.items(): for grantee in grantees: perms.append(Permission("grant", permission, grantee)) for permission, grantees in self.target.denied.items(): for grantee in grantees: perms.append(Permission("deny", permission, grantee)) return perms def getItems(self, offset, limit): """ Generates Permission objects for each permission """ return self.generatePermissions()[offset:offset + limit] def createNew(self, ctx): """ create a new account. """ if not self.target.checkPermissions(self.root.client, "modify"): return "Access denied!" if ctx.arg("action") == "grant": self.target.grantPermission(ctx.arg("permission"), ctx.arg("entity")) else: self.target.denyPermission(ctx.arg("permission"), ctx.arg("entity")) return "Created" def render_customCreate(self, ctx, data): """ Render any extra create related items """ permissions = [T.option(value=perm)[perm] for perm in self.target.availablepermissions] return ["Action:", T.select(name="action")[T.option(selected=True, value="grant")["grant"], T.option(value="deny")["deny"]], "Permission:", T.select(name="permission")[permissions], "Entity:", T.input(name="entity", type="text"), T.input(type="submit", name="new", value="New")] def childFactory(self, ctx, permid): """ Return a PermissionModifier """ perms = self.generatePermissions() return PermissionModifier(self.root, perms[int(permid)], self.target) class PermissionModifier(rend.Page): """ We only have one modifier-- delete, so this will just do that action. No confirmation is necessary. """ def __init__(self, root, permission, target): rend.Page.__init__(self) self.root = root self.permission = permission self.target = target def renderHTTP(self, ctx): """ Delete away! """ if not self.target.checkPermissions(self.root.client, "modify"): return "Access denied!" req = inevow.IRequest(ctx) if self.permission.action == "grant": self.target.revokePermission(self.permission.permission, self.permission.entity) else: self.target.remDeniedPermission(self.permission.permission, self.permission.entity) req.redirect("../") return "" def childFactory(self, ctx, child): """ Just return ourself or raise an error """ if child != "delete": raise ValueError("Unknown permission modifier") return self class ViewDirectories(List): """ Show asset groups available on this server """ shortName = "directory" name = "List Directories" classes = ["mv3d.server.directory.Directory"] title = "Directories" def __init__(self, target): List.__init__(self, target) self.attribs = [ dict(name="uid", title="Name"), #dict(name="interface", title="Interface"), dict(name="getMembers", title="Members", display=callItWithLen), # dict(name="iddisp.count", title="Available IDs", display=callIt), #dict(name="self", title="Modify", display=self.linkModifiers) ] def getLink(self, directory): """ Return the link for the directory """ return "/".join(["", self.root.location, self.section, self.shortName, str(directory.uid)]) def getItems(self, offset, limit): """ Returns items in the list """ dirs = self.target.getDirectories() return dirs.values()[offset:offset + limit] def childFactory(self, ctx, name): """ Name here is assumed to be the directory name """ directory = self.target.getDirectory(name) return WebEditModifier(self.root, self.section, directory, None) @inlineCallbacks def createNew(self, ctx): """ create a new asset group... """ yield self.target.newMasterDirectory(ctx.arg("name")) directory = self.target.pools[ctx.arg("name")] directory.grantPermission("all", self.root.client["username"]) returnValue(["Created Directory %s" % directory.uid]) def render_customCreate(self, ctx, data): """ Render any extra create related items """ return ["Name:", T.input(name="name", type="text"), T.input(type="submit", name="new", value="New")] class DirectoryLanding(rend.Fragment): """ Link to an asset from a group """ root = None client = None section = None target = None shortName = "item" name = "Item" def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["read", "modify"] def setDocFactory(self, templateDir): """ There is no doc factory """ @inlineCallbacks def childFactory(self, ctx, name): """ Name here is assumed to be the item id """ item = yield self.target.getItem(int(name)) item.parent = self.target.parent returnValue(WebEditModifier(self.root, self.section, item, None)) class DirectoryEditor(WebEditTarget): """ This is the directory edit modifier """ shortName = "edit" name = "Edit Directory" attribs = [("name", "Name:", notEditable, None), ("interface", "Interface:", textBox, "setInterface"), ] template = "directoryedit.xml" def getItemLink(self, item): """ Returns a link to an item """ return "/".join(["", self.root.location, self.section, "directory", str(self.target.name), "item", str(item.iid)]) def render_directoryName(self, ctx, data): """ Just render the group name """ return ctx.tag[self.target.name] def data_items(self, ctx, data): """ Grab the list of assets, sort it, and return an offset/limited result. """ request = inevow.IRequest(ctx) page = int(request.args.get("page", [1])[0]) offset, limit = getPageOffsetLimit(self.perPage, page) return r def render_item(self, ctx, item): """ Render an item entry in the list """ lnk = self.getItemLink(item) masters = [T.li()[service.toString()] for service in item.masterservers] slaves = [T.li()[service.toString()] for service in item.slaveservers] return ctx.tag[ T.td[T.a(href="%s/edit" % lnk)[str(item.iid)]], T.td[masters], T.td[slaves], T.td[T.a(href="%s/delete" % lnk)["delete"]] ] def render_pageTurn(self, ctx, asset): """ Render page turning logic """ request = inevow.IRequest(ctx) page = int(request.args.get("page", [1])[0]) items = self.target.getItems() pages = int(len(items) / self.perPage) if len(items) % self.perPage != 0: pages += 1 if page > 1: prev = T.a(href="?page=%d" % (page - 1))["previous"] else: prev = "" pageForm = T.form(method="POST", target="#page", name="pageturn")[ "page ", T.input(class_="text", size=3, name="page", value=str(page)), "of %d" % pages] if page < pages: next = T.a(href="?page=%d" % (page + 1))["next"] else: next = "" return ctx.tag[T.td[prev], T.td(colspan=5)[pageForm], T.td[next]] def unfoldServiceList(name, data): """ Display a list of services """ if data is None: return "" return T.input(type="text", name=name, value=", ".join( [str(l.toString()) for l in data])) def setServiceList(setter): def f(target, value): """ Sets based on a list of services """ services = value.split(",") result = [] for service in value.split(","): if len(service.strip(" ")): result.append(ServiceLoc(service.strip(" "))) getattr(target, setter)(result) return f class DirectoryItemEditor(WebEditTarget): """ This is the base asset edit modifier """ shortName = "edit" name = "Edit Asset" attribs = [("iid", "ID:", notEditable, None), ("masterservers", "Masters:", unfoldServiceList, setServiceList( "setMasterServers")), ("slaveservers", "Slaves:", unfoldServiceList, setServiceList( "setSlaveServers")), ] template = "edit.xml" def render_name(self, ctx, data): """ Just render the asset name """ return ctx.tag["%r" % (self.target.iid,)] class DeleteDirectory(rend.Fragment): """ Used for deleting a directory """ shortName = "delete" name = "Delete Directory" implements(IModifier) root = None client = None section = None target = None perPage = 20 attribs = None def __init__(self, target): self.target = target self.docFactory = loaders.stan(T.p(render=T.directive("checkPost"))[ "Are you sure you want to delete ", T.div(render=T.directive("directory")), "? ", "You won't be able to get it back and will lose all %d items." % ( len(self.target.getItems())), T.form(action="#post", method="POST")[ T.input(type="submit", name="yes", value="Yes"), T.input(type="submit", name="no", value="No", onclick="history.go(-2)") ]]) def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["modify"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ def render_directory(self, ctx, data): """ Render the directory's name """ return "%s" % self.target.name def render_checkPost(self, ctx, data): """ Check for posting and results """ if ctx.arg("yes"): # delete away self.target.parent.getLocalService( IDirectoryService).remDirectory(self.target.name) return "Deleted" return ctx.tag class DeleteDirectoryItem(rend.Fragment): """ Used for deleting a directory item It's not too clear that this would be useful and would do anything other than f-up your simulation. """ shortName = "delete" name = "Delete Directory Item" implements(IModifier) root = None client = None section = None target = None perPage = 20 attribs = None docFactory = loaders.stan(T.p(render=T.directive("checkPost"))[ "Are you sure you want to delete ", T.div(render=T.directive("item")), "?", T.form(action="#post", method="POST")[ T.input(type="submit", name="yes", value="Yes"), T.input(type="submit", name="no", value="No", onclick="history.go(-2)") ]]) def __init__(self, target): self.target = target def getMinimumPermissions(self): """ Return the set of minimum permissions """ return ["modify"] def setDocFactory(self, templateDir): """ Sets the document factory using the given template dir """ def render_item(self, ctx, data): """ Render the account's name """ return repr(self.target.iid) def render_checkPost(self, ctx, data): """ Check for posting and results """ if ctx.arg("yes"): # delete away self.target.directory.remItem(self.target.iid) return "Deleted" return ctx.tag