From 1e9e4df5a60668a79662d4b5312c7929240b9d71 Mon Sep 17 00:00:00 2001 From: Gaetan Delannay Date: Mon, 26 Mar 2012 19:09:45 +0200 Subject: [PATCH] appy.gen: added the possibility to create and manage web pages into an app; every class can now define a method showPortlet. If the class does not define it or if it returns False, the portlet won't be shown when showing/editing instances of this class. --- gen/descriptors.py | 27 ++++++++++++++++++++++++++- gen/generator.py | 24 ++++++++++++++++-------- gen/mixins/ToolMixin.py | 18 ++++++++++++------ gen/mixins/__init__.py | 3 +++ gen/model.py | 37 +++++++++++++++++++++++++++++++------ gen/templates/wrappers.pyt | 6 ++++++ gen/ui/appy.css | 10 ++++++---- gen/ui/template.pt | 6 +++++- gen/utils.py | 10 ++++++++-- 9 files changed, 113 insertions(+), 28 deletions(-) diff --git a/gen/descriptors.py b/gen/descriptors.py index 1010f3d..2e7db10 100644 --- a/gen/descriptors.py +++ b/gen/descriptors.py @@ -637,7 +637,7 @@ class TranslationClassDescriptor(ClassDescriptor): self.customized = False def getParents(self, allClasses=()): return ('Translation',) - + def isFolder(self, klass=None): return False def generateSchema(self): ClassDescriptor.generateSchema(self, configClass=True) @@ -681,4 +681,29 @@ class TranslationClassDescriptor(ClassDescriptor): params['format'] = gen.String.TEXT params['height'] = height self.addField(messageId, gen.String(**params)) + +class PageClassDescriptor(ClassDescriptor): + '''Represents the class that corresponds to a Page.''' + def __init__(self, klass, generator): + ClassDescriptor.__init__(self,klass,klass._appy_attributes[:],generator) + self.modelClass = self.klass + self.predefined = True + self.customized = False + def getParents(self, allClasses=()): + res = ['Page'] + if self.customized: + res.append('%s.%s' % (self.klass.__module__, self.klass.__name__)) + return res + def update(self, klass, attributes): + '''This method is called by the generator when he finds a custom page + definition. We must then add the custom page elements in this + default Page descriptor. + + NOTE: currently, it is not possible to define a custom Page class.''' + self.orderedAttributes += attributes + self.klass = klass + self.customized = True + def isFolder(self, klass=None): return True + def generateSchema(self): + ClassDescriptor.generateSchema(self, configClass=True) # ------------------------------------------------------------------------------ diff --git a/gen/generator.py b/gen/generator.py index 04568ee..e310029 100644 --- a/gen/generator.py +++ b/gen/generator.py @@ -6,7 +6,7 @@ import appy.gen as gen from po import PoMessage, PoFile, PoParser from descriptors import * from utils import produceNiceMessage, getClassName -from model import ModelClass, User, Group, Tool, Translation +from model import ModelClass, User, Group, Tool, Translation, Page # ------------------------------------------------------------------------------ class GeneratorError(Exception): pass @@ -353,11 +353,12 @@ class ZopeGenerator(Generator): Generator.__init__(self, *args, **kwargs) # Set our own Descriptor classes self.descriptorClasses['class'] = ClassDescriptor - # Create our own Tool, User, Group and Translation instances + # Create Tool, User, Group, Translation and Page instances. self.tool = ToolClassDescriptor(Tool, self) self.user = UserClassDescriptor(User, self) self.group = GroupClassDescriptor(Group, self) self.translation = TranslationClassDescriptor(Translation, self) + self.page = PageClassDescriptor(Page, self) # i18n labels to generate self.labels = [] # i18n labels self.referers = {} @@ -603,13 +604,14 @@ class ZopeGenerator(Generator): '''Returns the descriptors for all the classes in the generated gen-application. If p_include is: * "all" it includes the descriptors for the config-related - classes (tool, user, group, translation) + classes (tool, user, group, translation, page) * "allButTool" it includes the same descriptors, the tool excepted * "custom" it includes descriptors for the config-related classes for which the user has created a sub-class.''' if not include: return self.classes res = self.classes[:] - configClasses = [self.tool, self.user, self.group, self.translation] + configClasses = [self.tool, self.user, self.group, self.translation, + self.page] if include == 'all': res += configClasses elif include == 'allButTool': @@ -789,16 +791,22 @@ class ZopeGenerator(Generator): Tool.users.klass = self.user.klass Group.users.klass = self.user.klass - # Generate the Tool-related classes (User, Group, Translation) - for klass in (self.user, self.group, self.translation): + # Generate the Tool-related classes (User, Group, Translation, Page) + for klass in (self.user, self.group, self.translation, self.page): klassType = klass.name[len(self.applicationName):] klass.generateSchema() self.labels += [ Msg(klass.name, '', klassType), Msg('%s_plural' % klass.name,'', klass.name+'s')] repls = self.repls.copy() + if klass.isFolder(): + parents = 'BaseMixin, Folder' + icon = 'folder.gif' + else: + parents = 'BaseMixin, SimpleItem' + icon = 'object.gif' repls.update({'methods': klass.methods, 'genClassName': klass.name, - 'baseMixin':'BaseMixin', 'parents': 'BaseMixin, SimpleItem', - 'classDoc': 'Standard Appy class', 'icon':'object.gif'}) + 'baseMixin':'BaseMixin', 'parents': parents, + 'classDoc': 'Standard Appy class', 'icon': icon}) self.copyFile('Class.pyt', repls, destName='%s.py' % klass.name) # Before generating the Tool class, finalize it with query result diff --git a/gen/mixins/ToolMixin.py b/gen/mixins/ToolMixin.py index bf06c23..91843d9 100644 --- a/gen/mixins/ToolMixin.py +++ b/gen/mixins/ToolMixin.py @@ -223,14 +223,14 @@ class ToolMixin(BaseMixin): return [importParams['headers'], elems] def showPortlet(self, context): - if self.userIsAnon(): return False if context.id == 'ui': context = context.getParentNode() res = True - if not self.getRootClasses(): - res = False - # If there is no root class, show the portlet only if we are within - # the configuration. - if (self.id in context.absolute_url()): res = True + if hasattr(context.aq_base, 'appy'): + appyObj = context.appy() + try: + res = appyObj.showPortlet() + except AttributeError: + res = True return res def getObject(self, uid, appy=False, brain=False): @@ -1000,4 +1000,10 @@ class ToolMixin(BaseMixin): return '' \ '
An error occurred. %s' \ '
' % '\n'.join(htmlMessage) + + def getMainPages(self): + '''Returns the main pages.''' + if hasattr(self.o.aq_base, 'pages') and self.o.pages: + return [self.getObject(uid) for uid in self.o.pages ] + return () # ------------------------------------------------------------------------------ diff --git a/gen/mixins/__init__.py b/gen/mixins/__init__.py index 2371654..04d63b6 100644 --- a/gen/mixins/__init__.py +++ b/gen/mixins/__init__.py @@ -1312,6 +1312,9 @@ class BaseMixin: Else (if the object is stored directly within the tool or the root data folder) it returns None.''' parent = self.getParentNode() + # Not-Managers can't navigate back to the tool + if (parent.id == 'config') and not self.getUser().has_role('Manager'): + return False if parent.meta_type != 'Folder': return parent def index_html(self): diff --git a/gen/model.py b/gen/model.py index 97b1a40..0ac4c3b 100644 --- a/gen/model.py +++ b/gen/model.py @@ -42,7 +42,7 @@ class ModelClass: in order to avoid name conflicts with user-defined parts of the application model.''' _appy_attributes = [] # We need to keep track of attributes order. - + folder = False @classmethod def _appy_getTypeBody(klass, appyType, wrapperName): '''This method returns the code declaration for p_appyType.''' @@ -67,6 +67,12 @@ class ModelClass: value = 'tfw' else: value = appyType.getInputLayouts() + elif (name == 'klass') and value and (value == klass): + # This is a auto-Ref (a Ref that references the klass itself). + # At this time, we can't reference the class that is still being + # defined. So we initialize it to None. The post-init of the + # field must be done manually in wrappers.py. + value = 'None' elif isinstance(value, basestring): value = '"%s"' % value elif isinstance(value, gen.Ref): @@ -83,7 +89,7 @@ class ModelClass: elif isinstance(value, gen.Group): value = 'Grp("%s")' % value.name elif isinstance(value, gen.Page): - value = 'pages["%s"]' % value.name + value = 'pges["%s"]' % value.name elif callable(value): className = wrapperName if (appyType.type == 'Ref') and appyType.isBack: @@ -104,7 +110,7 @@ class ModelClass: else: wrapperName = 'W%s' % className res = 'class %s(%s):\n' % (className, wrapperName) # Tool must be folderish - if className == 'Tool': res += ' folder=True\n' + if klass.folder: res += ' folder=True\n' # First, scan all attributes, determine all used pages and create a # dict with it. It will prevent us from creating a new Page instance # for every field. @@ -114,12 +120,12 @@ class ModelClass: exec 'appyType = klass.%s' % name if appyType.page.name not in pages: pages[appyType.page.name] = appyType.page - res += ' pages = {' + res += ' pges = {' for page in pages.itervalues(): # Determine page show pageShow = page.show if isinstance(pageShow, basestring): pageShow='"%s"' % pageShow - res += '"%s":Page("%s", show=%s),'% (page.name, page.name, pageShow) + res += '"%s":Pge("%s", show=%s),'% (page.name, page.name, pageShow) res += '}\n' # Secondly, dump every attribute for name in klass._appy_attributes: @@ -183,13 +189,27 @@ class Translation(ModelClass): def label(self): pass def show(self, name): pass +# The Page class --------------------------------------------------------------- +class Page(ModelClass): + _appy_attributes = ['title', 'content', 'pages'] + folder = True + title = gen.String(show='edit', indexed=True) + content = gen.String(format=gen.String.XHTML, layouts='f') + # Pages can contain other pages. + def showSubPages(self): pass + pages = gen.Ref(None, multiplicity=(0,None), add=True, link=False, + back=gen.Ref(attribute='parent', show=False), + show=showSubPages) +Page.pages.klass = Page +setattr(Page, Page.pages.back.attribute, Page.pages.back) + # The Tool class --------------------------------------------------------------- # Prefixes of the fields generated on the Tool. toolFieldPrefixes = ('defaultValue', 'podTemplate', 'formats', 'resultColumns', 'enableAdvancedSearch', 'numberOfSearchColumns', 'searchFields', 'optionalFields', 'showWorkflow', 'showAllStatesInPhase') -defaultToolFields = ('title', 'users', 'groups', 'translations', +defaultToolFields = ('title', 'users', 'groups', 'translations', 'pages', 'enableNotifications', 'unoEnabledPython','openOfficePort', 'numberOfResultsPerPage', 'listBoxesMaximumWidth', 'appyVersion') @@ -197,6 +217,7 @@ defaultToolFields = ('title', 'users', 'groups', 'translations', class Tool(ModelClass): # In a ModelClass we need to declare attributes in the following list. _appy_attributes = list(defaultToolFields) + folder = True # Tool attributes title = gen.String(show=False, page=gen.Page('main', show=False)) @@ -222,6 +243,9 @@ class Tool(ModelClass): link=False, show='view', back=gen.Ref(attribute='trToTool', show=False), page=gen.Page('translations', show='view')) + pages = gen.Ref(Page, multiplicity=(0,None), add=True, link=False, + show='view', back=gen.Ref(attribute='toTool3', show=False), + page=gen.Page('pages', show='view')) enableNotifications = gen.Boolean(default=True, page=gen.Page('notifications', show=False)) @@ -235,4 +259,5 @@ class Tool(ModelClass): for k in toClean: exec 'del klass.%s' % k klass._appy_attributes = list(defaultToolFields) + klass.folder = True # ------------------------------------------------------------------------------ diff --git a/gen/templates/wrappers.pyt b/gen/templates/wrappers.pyt index 92f85b7..6c6e5de 100644 --- a/gen/templates/wrappers.pyt +++ b/gen/templates/wrappers.pyt @@ -1,11 +1,13 @@ # ------------------------------------------------------------------------------ from appy.gen import * Grp = Group # Avoid name clashes with the Group class below and appy.gen.Group +Pge = Page # Avoid name clashes with the Page class below and appy.gen.Page from appy.gen.wrappers import AbstractWrapper from appy.gen.wrappers.ToolWrapper import ToolWrapper as WTool from appy.gen.wrappers.UserWrapper import UserWrapper as WUser from appy.gen.wrappers.GroupWrapper import GroupWrapper as WGroup from appy.gen.wrappers.TranslationWrapper import TranslationWrapper as WT +from appy.gen.wrappers.PageWrapper import PageWrapper as WPage from Globals import InitializeClass from AccessControl import ClassSecurityInfo tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields @@ -14,6 +16,10 @@ tfw = {"edit":"f","cell":"f","view":"f"} # Layout for Translation fields + +Page.pages.klass = Page +setattr(Page, Page.pages.back.attribute, Page.pages.back) + # ------------------------------------------------------------------------------ diff --git a/gen/ui/appy.css b/gen/ui/appy.css index 4a42c60..57a24b9 100644 --- a/gen/ui/appy.css +++ b/gen/ui/appy.css @@ -1,4 +1,5 @@ -body { font: 75% Helvetica,Arial,sans-serif; background-color: #EAEAEA; } +body { font: 75% Helvetica,Arial,sans-serif; background-color: #EAEAEA; + margin-top: 18px} pre { font: 100% Helvetica,Arial,sans-serif; margin: 0} h1 { font-size: 11pt; margin:0;} h2 { font-size: 10pt; margin:0; font-style: italic; font-weight: normal;} @@ -38,12 +39,12 @@ img {border: 0} .xhtml img { margin-right: 5px } .main { width: 900px; background-color: white; box-shadow: 3px 3px 3px #A9A9A9; - border-style: solid; border-width: 1px; border-color: grey; } + border-style: solid; border-width: 1px; border-color: grey} .top { height: 75px; margin-left: 3em; vertical-align: top;} .lang { margin-right: 3px; } -.userStrip { background-color: #d7dee4; height: 40px; +.userStrip { background-color: #d7dee4; height: 35px; border-top: 1px solid #5F7983; border-bottom: 1px solid #5F7983; } -.login { margin-top: 2px; margin-bottom: 2px; color: white;} +.login { margin-top: 2px; margin-bottom: 2px; color: black;} .buttons { margin-left: 4px;} .fakeButton { border: 1px solid #D7DEE4; background-color: #fde8e0; padding: 0px 8px 2px; font: italic 92% Helvetica,Arial,sans-serif} @@ -104,3 +105,4 @@ img {border: 0} .history th { font-style: italic; text-align; left;} .topSpace { margin-top: 15px;} .discreet { color: grey} +.pageLink { padding-left: 6px; font-style: italic} diff --git a/gen/ui/template.pt b/gen/ui/template.pt index a7e7240..c2740bc 100644 --- a/gen/ui/template.pt +++ b/gen/ui/template.pt @@ -29,7 +29,11 @@ - + Links to main pages + Language selector (links or listbox)
+ +