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.

This commit is contained in:
Gaetan Delannay 2012-03-26 19:09:45 +02:00
parent 8aa03a091a
commit 1e9e4df5a6
9 changed files with 113 additions and 28 deletions

View file

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

View file

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

View file

@ -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 '<table class="main" align="center" cellpadding="0"><tr>' \
'<td style="padding: 1em 1em 1em 1em">An error occurred. %s' \
'</td></tr></table>' % '\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 ()
# ------------------------------------------------------------------------------

View file

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

View file

@ -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
# ------------------------------------------------------------------------------

View file

@ -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
<!User!>
<!Group!>
<!Translation!>
<!Page!>
Page.pages.klass = Page
setattr(Page, Page.pages.back.attribute, Page.pages.back)
<!Tool!>
<!wrappers!>
# ------------------------------------------------------------------------------

View file

@ -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}

View file

@ -29,7 +29,11 @@
<td tal:attributes="style python: 'background-image: url(%s/ui/banner.jpg)' % appUrl">
<table width="100%">
<tr valign="top">
<td></td>
<tal:comment replace="nothing">Links to main pages</tal:comment>
<td>
<a tal:repeat="page tool/getMainPages" class="pageLink"
tal:content="page/title" tal:attributes="href page/absolute_url"></a>
</td>
<tal:comment replace="nothing">Language selector (links or listbox)</tal:comment>
<td align="right" tal:condition="tool/showLanguageSelector">
<tal:lgs define="languages tool/getLanguages;

View file

@ -18,8 +18,14 @@ def createObject(folder, id, className, appName, wf=True):
obj.portal_type = className
obj.id = id
obj._at_uid = id
userId = obj.getUser().getId()
# If user is anonymous, userIs is None
user = obj.getUser()
if not user.getId():
if user.name == 'System Processes':
userId = 'admin' # This is what happens when Zope is starting.
else:
userId = None # Anonymous.
else:
userId = user.getId()
obj.creator = userId or 'Anonymous User'
from DateTime import DateTime
obj.created = DateTime()