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)
| |