source: waeup_product/trunk/Extensions/install.py @ 153

Last change on this file since 153 was 152, checked in by joachim, 20 years ago

=studylevel + Title

  • Property svn:keywords set to Id
File size: 26.0 KB
RevLine 
[60]1#-*- mode: python; mode: fold -*-
[72]2# $Id: install.py 152 2005-11-04 17:07:18Z joachim $
[139]3from Products.ExternalMethod.ExternalMethod import ExternalMethod
[19]4from Products.CPSInstaller.CPSInstaller import CPSInstaller
[139]5from Products.CMFCore.CMFCorePermissions import View, ModifyPortalContent,AccessContentsInformation
[84]6from Products.WAeUP.WAeUPPermissions import UniversityManage, StudentManage,FacultyManage,\
7                                            DepartmentManage,CourseManage
[19]8try:
[23]9  from Products.CPSSubscriptions.permissions import CanNotifyContent
[19]10  CPSSubscriptions = True
11except ImportError:
12  CPSSubscriptions = False
13try:
[23]14  from Products.CPSForum.CPSForumPermissions import ForumManageComments
[19]15  CPSForum = True
16except ImportError:
17  CPSForum = False
18
[60]19from Products.WAeUP.Students import addStudentsFolder
20
[139]21SRPP_ID = "demouni"
22SRPP_TITLE = "Demo University"
23
[19]24class WAeUPInstaller(CPSInstaller):
25    """
26    WAeUP Installer
27    """
28    product_name = "WAeUP"
29
30    def install(self,portal):
31        "install"
[139]32        log = []
33        prlog = log.append
[143]34
[139]35        def pr(msg, prlog=prlog):
36            prlog('%s<br>' % msg)
[19]37
38        product_name = self.product_name
[139]39        pr("Starting %(product_name)s install" % vars() )
[119]40
[87]41        # Roles and Permissions ###(
[84]42        #
43        self.verifyRoles((
44            # A role that can manage the University.
45            'UniversityManager',
46            # A role that can manage the StudentsRecords.
47            'StudentManager',
48            # A role that can manage a Faculty.
49            'FacultyManager',
50            # A role that can manage a department.
51            'DepartmentManager',
52            # A role that can manage a Course.
53            'CourseManager',
[87]54            # The Student Role
55            'Student',
[84]56        ))
57        waeup_perms = {
58            UniversityManage:
[119]59                ('Manager', 'SectionManager','UniversityManager',
[84]60                 ),
61            StudentManage:
[119]62                ('Manager', 'SectionManager','UniversityManager', 'StudentManager'
[84]63                 ),
64            FacultyManage:
[119]65                ('Manager', 'SectionManager','UniversityManager', 'FacultyManager'
[84]66                 ),
67            DepartmentManage:
[119]68                ('Manager', 'SectionManager','UniversityManager', 'DepartmentManager'
[84]69                 ),
70            CourseManage:
[119]71                ('Manager', 'SectionManager','UniversityManager', 'CourseManager'
[84]72                 ),
73        }
74        self.setupPortalPermissions(waeup_perms)
[139]75        ###)
[84]76
[139]77        # external methods ###(
78        #
79        ext_methods = ( { 'id': 'waeup_migrate',
80                          'title': 'WAeUP (migrate from an earlier version)',
81                          'script': 'WAeUP.install',
82                          'method': 'migrate',
83                          'protected': 1,
84                        },
85                      )
86        portal_objectIds = portal.objectIds()
87        for meth in ext_methods:
88            method = meth['id']
89            if method in portal_objectIds:
90                portal._delObject(method)
91            pr('Creating %s External Method' % method)
92            ext_method = ExternalMethod(method,
93                                        meth['title'],
94                                        meth['script'],
95                                        meth['method'])
96            portal._setObject(method, ext_method)
97            if method in portal_objectIds:
98                manage_perms = portal[method].manage_permission
99                if meth['protected']:
100                    pr("Protecting %s" % method)
101                    manage_perms(View, roles=['Manager'], acquire=0)
102                    manage_perms(AccessContentsInformation, roles=['Manager'], acquire=0)
103                else:
104                    manage_perms(View, roles=['Manager'], acquire=1)
105        ###)
[87]106
[19]107        ##########################################
[119]108        # Actions
[19]109        ##########################################
[103]110        waeup_ptypes = "'University','StudentsFolder','Student','Jamb','Faculty','Department','Course'"
[119]111        waeup_ptypes += ",'AccoFolder','Accommodation','StudentPersonal','AcademicsFolder'"
[152]112        waeup_ptypes += ",'StudyLevel','Semester','CourseTicket'"
[84]113        if CPSSubscriptions: ###(
[119]114
[19]115            ptypes = "('Portal', 'CPSMailAccess Message', 'CPSMailAccess Box', 'CPSMailAccess Folder',"\
[103]116                     + waeup_ptypes + ")"
[19]117            condition = "object.portal_type not in %s" % ptypes
[119]118
[19]119            action = {'id' : 'notify_content',
120                      'name' : 'action_notify_content',
121                      'action' : 'string:${object_url}/content_notify_email_form',
122                      'condition' : 'python:%s' % condition,
123                      'permission' :  (CanNotifyContent,),
124                      'category' : 'object',
125                      }
126            self.deleteActions({'portal_subscriptions': ['notify_content',]})
127            self.verifyAction('portal_subscriptions', **action)
128        if CPSForum:
[103]129            condition = "object.portal_type not in (%s)" % waeup_ptypes
[119]130
[19]131            action = {'id' : 'activate_comments',
132                      'name': 'action_activate_comments',
133                      'action': 'string:${object/absolute_url}/set_comment_mode?mode=1',
134                      'condition' : 'python:%s' % condition,
135                      'permission': (ForumManageComments,),
136                      'category' : 'object',
137                      }
[57]138            self.deleteActions({'portal_subscriptions': ['activate_comments',]})
[19]139            self.verifyAction('portal_subscriptions', **action)
[57]140            # now portal_discussion
141            self.deleteActions({'portal_discussion': ['comment',
142                                                      'activate_comments',
143                                                      'deactivate_comments',
144                                                      'manage_comments']})
145            action = {'id' : 'comment',
146                      'name': 'action_comment',
147                      'action': "string:${object/absolute_url}/post_comment",
148                      'condition' : "python:getattr(object.getContent(), 'allow_discussion' , None) and object.hasCommentAction()",
149                      'permission': (View,),
150                      'category' : 'object',
151                      }
152            self.verifyAction('portal_discussion', **action)
153            action = {'id' : 'activate_comments',
154                      'name': 'action_activate_comments',
155                      'action': "string:${object/absolute_url}/set_comment_mode?mode=1",
156                      'condition' : "python:getattr(object.getTypeInfo(),'cps_proxy_type','') in ('document', 'folderishdocument') and getattr(object.getContent(), 'allow_discussion' , None) == 1",
157                      'permission': (ForumManageComments,),
158                      'category' : 'object',
159                      }
160            self.verifyAction('portal_discussion', **action)
161            action = {'id' : 'deactivate_comments',
162                      'name': 'action_deactivate_comments',
163                      'action': "string:${object/absolute_url}/set_comment_mode?mode=0",
164                      'condition' : "python:getattr(object.getTypeInfo(),'cps_proxy_type','') in ('document', 'folderishdocument') and getattr(object.getContent(), 'allow_discussion' , None) == 1",
165                      'permission': (ForumManageComments,),
166                      'category' : 'object',
167                      }
168            self.verifyAction('portal_discussion', **action)
169            action = {'id' : 'manage_comments',
170                      'name': 'action_manage_comments',
171                      'action': "string:${object/absolute_url}/manage_comment",
172                      'condition' : "python:getattr(object.getTypeInfo(),'cps_proxy_type','') in ('document', 'folderishdocument') and getattr(object.getContent(), 'allow_discussion' , None) == 1",
173                      'permission': (ForumManageComments,),
174                      'category' : 'object',
175                      }
176            self.verifyAction('portal_discussion', **action)
[119]177
[84]178###)
179
[19]180        ##########################################
[100]181        # main_tab actions ###(
[19]182        ##########################################
[143]183        path = "/sections/%(SRPP_ID)s" % globals()
[20]184        actions = ( { 'tool'      : 'portal_actions',
[100]185                        'id'        : 'student_administration',
[119]186                        'name'      : 'Students',
[143]187                        'action'    : 'string:${portal_url}/students',
188                        #'action'    : 'string:$portal_url%(path)s/students' % vars(),
[20]189                        'permission': (View, ),
[100]190                        'category'  : 'main_tabs',
[20]191                        'visible'   : 1,
192                      },
[100]193                      { 'tool'      : 'portal_actions',
[113]194                        'id'        : 'Academics',
195                        'name'      : 'Academics',
[143]196                        #'action'    : 'string:$portal_url%(path)s/academics' % vars(),
[139]197                        #'action'    : 'string:${portal_url}/sections/unidemo',
[143]198                        'action'    : 'string:${portal_url}/academics',
[100]199                        'permission': (View ),
200                        'category'  : 'main_tabs',
201                        'visible'   : 1,
202                      },
[103]203                      { 'tool'      : 'portal_actions',
204                        'id'        : 'accomodation',
205                        'name'      : 'Accommodation',
[143]206                        #'action'    : 'string:$portal_url%(path)s/accommodation' % vars(),
[124]207                        #'action'    : 'string:${portal_url}/sections/waeup/accommodation/accommodation_index_html',
[143]208                        'action'    : 'string:${portal_url}/accommodation',
[115]209                        'permissions': (View),
210                        #'permissions': (ModifyPortalContent,UniversityManage,StudentManage),
[103]211                        'category'  : 'main_tabs',
212                        'visible'   : 1,
213                      },
[49]214##                      { 'tool'      : 'portal_actions',
[100]215##                        'id'        : 'add_jamb',
216##                        'name'      : 'Add Student JAMB',
217##                        'action'    : 'string:${object/absolute_url}/create_jamb',
218##                        'permission': (),
219##                        'condition' : 'python: member and here.portal_type in ("StudentsFolder",) and "StudentManager" in member.getGroups()',
[49]220##                        'category'  : 'student',
221##                        'visible'   : 1,
222##                      },
[100]223##                      { 'tool'      : 'portal_actions',
224##                        'id'        : 'add_faculty',
225##                        'name'      : 'Add New Faculty',
226##                        'action'    : 'string:${object/absolute_url}/addFaculty',
227##                        'permission': (),
228##                        'condition' : 'python: member and here.portal_type in ("University",) and "StudentManager" in member.getGroups()',
229##                        'category'  : 'student',
230##                        'visible'   : 1,
231##                      },
232##                      { 'tool'      : 'portal_actions',
233##                        'id'        : 'add_department',
234##                        'name'      : 'Add New Department',
235##                        'action'    : 'string:${object/absolute_url}/addDepartment',
236##                        'permission': (),
237##                        'condition' : 'python: member and here.portal_type in ("Faculty",) and "StudentManager" in member.getGroups()',
238##                        'category'  : 'student',
239##                        'visible'   : 1,
240##                      },
241##                      { 'tool'      : 'portal_actions',
242##                        'id'        : 'add_course',
243##                        'name'      : 'Add New Course',
244##                        'action'    : 'string:${object/absolute_url}/addCourse',
245##                        'permission': (),
246##                        'condition' : 'python: member and here.portal_type in ("Department",) and "StudentManager" in member.getGroups()',
247##                        'category'  : 'student',
248##                        'visible'   : 1,
249##                      },
250##                      { 'tool'      : 'portal_actions',
251##                        'id'        : 'add_personal',
252##                        'name'      : 'Add personal Data',
253##                        'action'    : 'string:${object/absolute_url}/create_personal_form',
254##                        'permission': (),
255##                        'condition' : 'python: 0 and member and not here.portal_type in ("Jamb","StudentsFolder") and not "StudentManager" in member.getGroups()',
256##                        'category'  : 'student',
257##                        'visible'   : 1,
258##                      },
259##                      { 'tool'      : 'portal_actions',
260##                        'id'        : 'edit_jamb',
261##                        'name'      : 'Edit Jamb Data',
262##                        'action'    : 'cpsdocument_edit_form',
263##                        'permission': ( ),
264##                        'condition' : 'python: member and here.portal_type in ("Student",) and "StudentManager" in member.getGroups()',
265##                        'category'  : 'student',
266##                        'visible'   : 1,
267##                      },
[20]268                    )
269        self.verifyActions(actions)
270        ##########################################
[60]271###)
272
[119]273        # skins
[20]274        ##########################################
[139]275        pr("Verifying %(product_name)s skinss" % vars())
[19]276        wskins = { 'waeup_default' : 'Products/WAeUP/skins/waeup_default',
[84]277                   'waeup_faculty' : 'Products/WAeUP/skins/waeup_faculty',
[19]278                   'waeup_student' : 'Products/WAeUP/skins/waeup_student',
279                 }
280        self.verifySkins(wskins)
281        self.resetSkinCache()
[119]282
[139]283        # Themes ###(
284        #
285        # Importing portal themes
286##        theme_container = getattr(portal, 'portal_themes')
287##            # the first theme in each category is the default theme.
288##        themes_list = {'WAeUP':    ({'id': 'waeup_plain_theme',
289##                                  'file': 'waeup_plain_theme.zexp',
290##                                  },
291##                                  {'id': 'waeup_management_theme',
292##                                   'file': 'waeup_management_theme.zexp',
293##                                  },
294##                                 ),
295##                      }
296##
[143]297##
[139]298##        theme_ids = theme_container.objectIds()
299##        theme_container.manage_delObjects(theme_ids)
300##        target_themes = themes_list[Target]
301##        for theme in target_themes:
302##            pr(" Importing %s theme" % theme['id'])
303##            zexppath = os.path.join(zexpdir, theme['file'])
304##            try:
305##                theme_container._importObjectFromFile(zexppath)
306##            except:
307##                pr("    Could not import theme  %s" % theme['id'])
308##
309##        # set the first theme in the list as the default one.
310##        theme_container.setDefaultTheme(target_themes[0]['id'])
311###)
[143]312
313
[19]314        ##########################################
[20]315        # portal types
[19]316        ##########################################
317        dtypes = portal.getWAeUPTypes()
318        self.verifyFlexibleTypes(dtypes)
[45]319        self.allowContentTypes('University', ('Workspace','Section',))
[19]320        ##########################################
[20]321        # Schemas
[19]322        ##########################################
[139]323        pr("Verifying %(product_name)s schemas" % vars())
[19]324        self.verifySchemas(portal.getWAeUPSchemas())
325        ##########################################
[20]326        # widgets
[19]327        ##########################################
[139]328        pr("Verifying %(product_name)s widgets" % vars())
[20]329        self.verifyWidgets(portal.getWAeUPWidgets())
330        ##########################################
331        # layouts
332        ##########################################
[139]333        pr("Verifying %(product_name)s layouts" % vars())
[19]334        self.verifyLayouts(portal.getWAeUPLayouts())
335        ##########################################
[20]336        # Vocabularies
[19]337        ##########################################
[139]338        pr("Verifying %(product_name)s vocabularies" % vars())
[19]339        self.verifyVocabularies(portal.getWAeUPVocabularies())
[60]340        ##########################################
341        # Groups
342        ##########################################
[119]343
[19]344        self.installCustomWorkflows()
345        self.verifyWorkflowAssociation()
[119]346
[139]347        pr("End of specific %(product_name)s install" % vars())
348        #return '\n'.join(log)
[19]349        self.finalize()
350
[119]351
[152]352    def verifyPortlets(self, portlets=(), object=None): ###(
353        """Verify the existence of given portet in the object's portlet
354        container. If not found, a portlet is instantiated.
355        Existing portlets are not affected.
356
357        'portlets' is a tuple with the dictionaries given by the export tab
358        as entries.
359        The default object is the portal itself.
360
361        return the list a new portlet ids.
362        """
363
364        if object is None:
365            object = self.portal
366
367        self.log('Verifying portlets on %s' % object.absolute_url(relative=1))
368
369        portlet_container = self.getPortletContainer(object, create=1)
370
371        ttool = self.getTool('portal_types')
372
373        returned = []
374        for new_portlet in portlets:
375            existing_portlets = portlet_container.listPortlets()
376            updated = 0
377
378            # Check if the portlet needs an update
379            identifier = new_portlet.get('identifier')
380            if identifier:
381                for portlet in existing_portlets:
382                    if identifier == portlet.identifier:
383                        self.log(" Update of portlet: %s" % portlet)
384                        portlet.edit(**new_portlet)
385                        portlet_id = portlet.getId()
386                        updated = 1
387                        continue
388            slot = new_portlet.get('slot')
389            if slot:
390                for portlet in existing_portlets:
391                    if slot == portlet.slot:
392                        self.log(" Update of portlet: %s" % portlet)
393                        portlet.edit(**new_portlet)
394                        portlet_id = portlet.getId()
395                        updated = 1
396                        continue
397
398            if not updated:
399                self.log("   Creation of portlet: %s" % new_portlet)
400                portlet_id = self.portal.portal_cpsportlets.createPortlet(
401                    ptype_id=new_portlet['type'],
402                    context=object,
403                    **new_portlet)
404            if portlet_id not in returned:
405                returned.append(portlet_id)
406        return returned
407
408###)
409
410    def installCustomWorkflows(self): ###(
[19]411        """Installs custom workflows
412        """
413        from Products.WAeUP.Workflows.WAeUPWorkflow import \
414             waeupWorkflowsInstall
415
416        waeupWorkflowsInstall(self.context)
417
[152]418###)
[19]419
[152]420    def verifyWorkflowAssociation(self): ###(
[19]421        """Verify workflow association
422        """
[119]423        ws_chains = {
[19]424                      'Student': 'waeup_workspace_wf',
425                      'Jamb': 'waeup_workspace_wf',
426                      }
427
[45]428        se_chains = { 'University': 'waeup_section_wf',
[113]429                      'AcademicsFolder': 'waeup_section_wf',
[152]430                      'StudentsFolder': 'waeup_student_wf',
431                      'Student': 'waeup_student_wf',
432                      'StudentPersonal': 'waeup_student_wf',
[19]433                      'Jamb': 'waeup_section_wf',
[25]434                      'ScratchCard': 'waeup_section_wf',
[68]435                      'Faculty': 'waeup_section_wf',
436                      'Department': 'waeup_section_wf',
437                      'Course': 'waeup_section_wf',
[152]438                      'StudyLevel': 'waeup_student_wf',
439                      'Semester': 'waeup_student_wf',
440                      'CourseTicket': 'waeup_student_wf',
[103]441                      'AccoFolder': 'waeup_section_wf',
442                      'Accommodation': 'waeup_section_wf',
[19]443                      }
444
445        self.verifyLocalWorkflowChains(self.portal['workspaces'],
446                                       ws_chains,
447                                       destructive=1)
448        self.verifyLocalWorkflowChains(self.portal['sections'],
449                                       se_chains,
450                                       destructive=1)
451
[152]452###)
[19]453
[152]454
455def install(self): ###(
[19]456    installer = WAeUPInstaller(self)
457    installer.install(self)
[60]458    dirtool = getattr(self,'portal_directories')
459    groups = dirtool.groups
[96]460    for delEntry in ('StudentManager',):
461        if delEntry in groups.listEntryIds():
[119]462            groups.deleteEntry(delEntry)
[96]463    for newEntry in ('Students',):
[60]464        if newEntry not in groups.listEntryIds():
[119]465            groups.createEntry({'group': newEntry,
466                                'members': []})
[96]467    #groups.manage_setLocalGroupRoles(groupid = 'StudentManager',roles=('Manager',))
[60]468    #groups.manage_setLocalGroupRoles(groupid = 'Students',roles=('Contributor',))
469    sections = getattr(self,'sections')
[139]470    waeup = getattr(sections,SRPP_ID,None)
[60]471    if not waeup:
[139]472        sections.invokeFactory('University',SRPP_ID)
473        waeup = getattr(sections,SRPP_ID)
474        waeup.getContent().edit(mapping={'Title':SRPP_TITLE})
475        waeup.folder_localrole_add(member_ids=('group:Students',),
[60]476                                            member_role='SectionReviewer',
477                                            )
[139]478        waeup.invokeFactory('StudentsFolder','students')
479        students = getattr(waeup,'students').getContent()
480        students.edit(mapping={'Title':'Students'})
481        waeup.folder_localrole_add(member_ids=('group:Students',),
[60]482                                            member_role='SectionReviewer',
483                                            )
[139]484##        waeup.content_create(type_name='AcademicsFolder',title='academics')
[87]485##        sections.waeup.folder_localrole_add(member_ids=('group:StudentManager',),
486##                                            member_role='SectionManager',
487##                                            )
[139]488        waeup.students.manage_setLocalGroupRoles(groupid = 'Students',roles=('Contributor',))
489        waeup.invokeFactory('AcademicsFolder','academics', title='Academics')
490        academics = getattr(waeup,'academics').getContent()
491        academics.edit(mapping={'Title':'Academics'})
492        waeup.invokeFactory('AccoFolder','accommodation', title='Accommodation')
493        accommodation = getattr(waeup,'accommodation').getContent()
494        accommodation.edit(mapping={'Title':'Accommodation'})
495    if not hasattr(waeup,'academics'):
496        waeup.invokeFactory('AcademicsFolder','academics')
497        academics = getattr(waeup,'academics').getContent()
498        academics.edit(mapping={'Title':'Academics'})
499    if not hasattr(waeup,'accommodation'):
500        waeup.invokeFactory('AccoFolder','accommodation')
501        accommodation = getattr(waeup,'accommodation').getContent()
502        accommodation.edit(mapping={'Title':'Accommodation'})
503    waeup.manage_setLocalGroupRoles(groupid = 'role:Anonymous',roles=('SectionReader',))
[87]504    # portlets ###(
505    #
506    portlets = (
[100]507##              {'identifier': 'waeup_main_tabs',
508##              'type': 'Custom Portlet',
509##              'slot': 'main_tabs',
510##              'order': 0,
511##              'render_method': 'portlet_main_navigation',
512##              'Title': 'main tabs',
513##              },
[152]514              {#'identifier': 'waeup_breadcrumbs',
[87]515              'type': 'Breadcrumbs Portlet',
516              'slot': 'waeup_breadcrumbs',
[152]517              'first_item': 0,
[87]518              'display_site_root': 0,
519              'Title': 'waeup_breadcrumbs',
520              'display': 'horizontal_trail',
521              'display_hidden_folders': 0,
522              'order': 0,
523              },
[152]524              {#'identifier': 'waeup_main_tab_actions',
[100]525              'type': 'Actions Portlet',
526              'slot': 'main_tabs',
527              'order': 0,
528              'categories': ['main_tabs',],
529              'Title': 'waep_main_tab_actions',
530              },
[152]531              {#'identifier': 'waeup_object_actions',
[87]532              'type': 'Actions Portlet',
533              'slot': 'waeup_object_actions',
534              'order': 0,
[100]535              'categories': ['object',],
[87]536              'Title': 'waep_manager_actions',
537              },
[152]538              {#'identifier': 'waeup_left_top',
[87]539              'type': 'Custom Portlet',
540              'slot': 'left_top',
541              'order': 0,
542              'render_method': 'portlet_session_info',
543              'Title': 'Session Info',
544              },
545            )
[89]546    installer.verifyPortletContainer(waeup)
547    installer.verifyPortlets(portlets,waeup)
[87]548
549###)
[19]550    return installer.logResult()
[152]551    ###)
[139]552
[152]553def migrate(self): ###(
[139]554    "update data"
555    log = []
556    prlog = log.append
557
558    def pr(msg, prlog=prlog):
[140]559        prlog('%s' % msg)
[139]560
561    sections = getattr(self,'sections')
[140]562    waeup = getattr(sections,SRPP_ID,None)
[139]563    pr( "Start migrate")
564    pr("migrating courses")
[141]565    academics = getattr(waeup,'academics')
566    for fac in [getattr(academics,f.id) for f in academics.contentValues(filter={'portal_type': ('Faculty',)})]:
[139]567        pr("Faculty: %s" % fac.title_or_id())
568        for dep in [getattr(fac,d.id) for d in fac.contentValues(filter={'portal_type': ('Department',)})]:
569            pr("Department: %s" % dep.title_or_id())
570            for course in [getattr(dep,c.id) for c in dep.contentValues(filter={'portal_type': ('Course',)})]:
571                pr("Course: %s" % course.title_or_id())
572                content = course.getContent()
573                heading = getattr(content,'heading',course.id)
574                pr("heading = %(heading)s" % vars())
575                content.edit(mapping={'Title': heading})
576    pr("migrating halls")
577    for ac in [getattr(waeup,a.id) for a in waeup.contentValues(filter={'portal_type': ('AccoFolder',)})]:
578        for hall in [getattr(ac,h.id) for h in ac.contentValues(filter={'portal_type': ('Accommodation',)})]:
579            pr("Hall: %s" % hall.title_or_id())
580            content = hall.getContent()
581            heading = getattr(content,'heading',hall.id)
582            pr("heading = %(heading)s" % vars())
583            content.edit(mapping={'Title': heading})
584    return '\n'.join(log)
585               
[152]586###)
587
Note: See TracBrowser for help on using the repository browser.