source: trunk/ConfigurationMethods.py @ 3057

Revision 3057, 21.0 KB checked in by jukka, 9 years ago (diff)

Worked with community section and portfolios.

  • Property svn:eol-style set to native
Line 
1# Copyright 2006 by the LeMill Team (see AUTHORS)
2#
3# This file is part of LeMill.
4#
5# LeMill is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LeMill is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with LeMill; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19# -*- coding: iso-8859-1 -*-
20
21from Acquisition import aq_get
22from AccessControl import Permissions, getSecurityManager
23from zExceptions import BadRequest
24from OFS.PropertyManager import PropertyManager
25from zLOG import INFO, ERROR
26from Globals import package_home
27from Products.ZCatalog.Catalog import CatalogError
28
29from Products.CMFCore.utils import getToolByName
30from Products.CMFCore.FSFile import FSFile
31
32from Products.CMFCore.DirectoryView import registerDirectory, addDirectoryViews, registerFileExtension
33
34from Products.CMFCore.ActionInformation import ActionInformation
35from Products.CMFPlone.migrations.migration_util import safeEditProperty
36from Products.CMFPlone.PloneFolder import addPloneFolder
37from Products.CMFPlone.setup.SetupBase import SetupWidget
38from Products.SiteErrorLog.SiteErrorLog import manage_addErrorLog
39from Products.Archetypes.public import listTypes, registerType
40from Products.Archetypes.Extensions.utils \
41     import installTypes, install_subskin
42from Products.PythonScripts.PythonScript import PythonScript
43from Products.LeMill.LatexTool import workingDir
44from Acquisition import aq_inner, aq_parent
45
46
47from DateTime import DateTime
48import string, types
49from cStringIO import StringIO
50from os import path
51
52from config import *
53
54
55# All methods starting with "setup" will be used in the customization policy
56# The methods will be sorted by ascii value, so if something  needs to happen before
57# something else, make it so (see method setup001Dependencies).
58
59# Note that all methods need to be repeatable - they should not fail
60# if they are run again when all that they do has already been done.
61# Also, the methods should be able to upgrade, meaning that older customizations
62# should be changed to match the newest specifications.
63
64def setup001Dependencies(self, portal):
65    """Install all necessary products into the plone instance."""
66    qi=getToolByName(portal, 'portal_quickinstaller')
67    # We need ourselves installed into the plone instance,
68    # since we provide portal tools and content
69    qi.installProduct('LeMill')
70    # LanguageTool is also necessary
71    qi.installProduct('PloneLanguageTool')
72    if FS_STORAGE:
73        qi.installProduct('FileSystemStorage')
74
75def setupSkin(self,portal):
76    """Create new custom skin."""
77    skinsTool = getToolByName(portal,"portal_skins")
78
79    # Register our own skin folder as a viewable folder
80    try:
81        addDirectoryViews(skinsTool,SKINS_DIR,GLOBALS)
82    except:
83        # If the directory is already registered, we just continue
84        pass
85
86    # Create our new skin, making it a copy of BASE_SKIN and adding
87    # our skin layer just after "custom".
88    if SKIN_NAME not in skinsTool.getSkinSelections():
89        path = skinsTool.getSkinPath(BASE_SKIN)
90        path = map(string.strip,string.split(path,','))
91        if SKIN_NAME not in path:
92            try:
93                path.insert(path.index('custom')+1,SKIN_NAME)
94            except ValueError:
95                path.append(SKIN_NAME)
96        path=string.join(path,',')
97        skinsTool.addSkinSelection(SKIN_NAME,path)
98
99    # Select our first skin as default
100    skinsTool.default_skin=SKIN_NAME
101
102    # Allow fonts into skins.
103    registerFileExtension('ttf', FSFile)
104
105
106def addObject(portal,id,type,title,desc=None):
107    """Convenience method for creating new content items."""
108    try:
109        portal.invokeFactory(id=id,type_name=type)
110    except BadRequest:
111        # If the id already existed, we'll just quietly be happy about it.
112        pass
113    finalizeObject(portal,id,title,desc)
114
115def finalizeObject(portal,id,title,desc=None):
116    """Set content object properties in place."""
117    ob=getattr(portal,id)
118   
119    # Set basic properties of object
120    ob.setTitle(to_unicode(title))
121    if desc:
122        ob.setDescription(to_unicode(desc))
123       
124    # Publish content
125    ob.setState="public"
126
127def setupFrontPage(self,portal):
128    """Setup the front page."""
129    # Make sure that old PythonScript for redirecting isn't there anymore
130    # the content section.
131    try:
132        portal._delObject('front-page')
133    except AttributeError:
134        pass
135
136
137def setupLatexSupport(self, portal):
138    """Setup localfs for latex-images."""
139    if not LATEX_IMAGES_STORAGE_PATH:
140        return
141   
142    if not hasattr(portal, LATEX_IMAGES_STORAGE_PATH):
143        ob = None
144        try:
145            from Products.LocalFS.LocalFS import LocalFS
146            ob = LocalFS(LATEX_IMAGES_STORAGE_PATH, '', workingDir, None, None)
147            portal._setObject(LATEX_IMAGES_STORAGE_PATH, ob, set_owner=1)
148
149        except ImportError: # no localfs
150            pass
151
152def setupFontsFolder(self, portal):
153    """ Setup fonts for pdf-creation """
154   
155    if not hasattr(portal, 'fonts'):
156        ob = None
157        fontpath=path.join(package_home(globals()), 'fonts/')
158        ob = None
159        try:
160            from Products.LocalFS.LocalFS import LocalFS
161            ob = LocalFS('fonts', '', fontpath, None, None)
162            portal._setObject('fonts', ob, set_owner=1)
163
164        except ImportError: # no localfs
165            pass
166
167
168
169                 
170def setupFolders(self,portal):
171    """Setup the basic structure of the site."""
172
173    def createSection(base, section):
174        section_id=section.lower()
175        section_type=SECTION_FOLDER_TYPES[section]
176        if not hasattr(base, section_id):
177            addObject(base,section_id,section_type,section)
178        else:
179            folder = getattr(base, section_id)
180            if folder.portal_type != section_type:
181                base._delObject(section_id)
182                addObject(base,section_id,section_type,section)
183        folder = getattr(base,section_id)
184        folder.manage_permission(ADD_CONTENT_PERMISSION, ('Member',), acquire=1)
185        folder.manage_permission(LIST_FOLDER_CONTENTS, ('Member',), acquire=1)
186        folder.manage_permission(ADD_TOPICS, ('Member',), acquire=1)
187        return folder
188
189
190    # Loop through all main sections
191    for (section, subsections) in SECTIONS.items():
192        section=createSection(portal, section)
193        # Loop through their subsections
194        for subsection in subsections:
195            subsection=createSection(section, subsection)                           
196
197    # Setup additional Trash folder for deleted resources
198    # It might be a good idea to make any actions including View only usable by users with Manager permission
199    trash_folder = 'Trash'
200    trash_type = 'LargeTrashFolder'
201
202    if not hasattr(portal, trash_folder.lower()):
203        addObject(portal,trash_folder.lower(),trash_type,trash_folder)
204    folder = getattr(portal,trash_folder.lower())
205    folder.manage_permission(ADD_CONTENT_PERMISSION, ('Member',), acquire=1)
206    for section, toplist in SECTION_TOPLISTS.items():
207        section=getattr(portal, section)
208        for top3 in toplist:
209            if not hasattr(section, top3):
210                section.manage_addProperty(top3, [], 'lines')
211       
212       
213
214
215def setupReadOnlyMode(self, portal):
216    stp = getToolByName(portal,'portal_properties').site_properties
217    if not hasattr(stp, 'readonly_mode'):
218        stp.manage_addProperty('readonly_mode',False,'boolean')
219
220
221def setupSiteSyndication(self, portal):
222    # Enable syndication
223    syn_tool = getToolByName(portal, 'portal_syndication')
224    syn_tool.editProperties(isAllowed=True)
225    #safeEditProperty(syn_tool,'isAllowed',True)
226   
227def setupCatalog(self,portal):
228    # put 'language' and 'subject' in catalog index and retrieved metadata
229    # from http://plone.org/documentation/how-to/adding-new-fields-to-smart-folders-search
230    # these are metadata fields from basic plone objects.
231
232    class args:
233            def __init__(self, **kw):
234                self.__dict__.update(kw)
235            def keys(self):
236                return self.__dict__.keys()
237
238
239    catalog_tool = getToolByName(portal, 'portal_catalog')
240    # (jukka, 10.1.2008) I commented away deleting indexes before adding them, as this forces us to always update catalog after running these
241    # We can restore them if we do changes that really change probably existing indexes
242
243    #try:
244    #    catalog_tool._removeIndex("Language")
245    #except:
246    #    pass
247    #try:
248    #    catalog_tool.delColumn("Language")
249    #except:
250    #    pass
251
252    extra = args(doc_attr='Language',
253                 lexicon_id='plone_lexicon',
254                 index_type='Okapi BM25 Rank')
255    try:
256        catalog_tool.manage_addIndex("Language", "FieldIndex", extra)
257    except CatalogError:
258        pass # index already exists
259    try:
260        catalog_tool.manage_addColumn("Language")
261    except CatalogError:
262        pass # metadata already exists
263
264    #try:
265    #    catalog_tool.delColumn("sortable_title")
266    #except:
267    #    pass
268    try:
269        catalog_tool.manage_addIndex("sortable_title", "FieldIndex")   
270    except CatalogError:
271        pass # index already exists
272    try:
273        catalog_tool.manage_addColumn("sortable_title")   
274    except CatalogError:
275        pass # metadata already exists
276    #try:
277    #    catalog_tool.delColumn("UID")
278    #except:
279    #    pass
280
281    extra = args(doc_attr='UID',
282                 lexicon_id='plone_lexicon',
283                 index_type='Okapi BM25 Rank')
284    try:
285        catalog_tool.manage_addIndex("UID", "FieldIndex", extra)   
286    except CatalogError:
287        pass # index already exists
288    try:
289        catalog_tool.manage_addColumn("UID")
290    except CatalogError:
291        pass # metadata already exists
292
293    # The meta_type was gone from the catalog Metadata, we need it back
294    try:
295        catalog_tool.manage_addColumn("meta_type")
296    except CatalogError:
297        pass # metadata already exists
298
299
300    try:
301        catalog_tool.manage_addColumn("postCount")
302    except CatalogError:
303        pass # metadata already exists
304    try:
305        catalog_tool.manage_addColumn("getLastCommentDate")
306    except CatalogError:
307        pass # metadata already exists
308   
309       
310    # BAD INDEXES AND METADATA:
311#    bads=['Description','EffectiveDate','ExpirationDate','Subject','effective','end','exclude_from_nav','expires',   
312#'getActivity_score','getAge_group','getCategory','getDescription','getEndUserRole','getFullname','getIcon',
313#'getLearningContext','getLearningResourceType','getLocation','getNickname','getObjSize','getRemoteUrl',
314#'getShortDescription','getUsed_content','in_reply_to','is_default_page','is_folderish','location','start','getRawRelatedActivities', 'effectiveRange', 'getEventType']
315#
316#    for b in bads:
317#        try:
318#            catalog_tool.delColumn(b)
319#            print 'removed metadata %s' % b
320#        except:
321#            pass
322#        try:
323#            catalog_tool._removeIndex(b)
324#            print 'removed index %s' % b
325#        except:
326#            pass
327
328
329def setupTopics(self, portal):
330    """ From v 3.0-> we don't use topics anymore. This method makes sure they are removed. :/ """
331    removed_topics = {
332        'content': ['tags','published','browse_resources','drafts','language','subject_area','target_group','browse_references','browse_pieces','recent'],
333        'methods': ['language','tags','published','browse_methods'],
334        'tools': ['language','tags','published','browse_tools'],
335        'community':['portfolio','browse_people','language','country','skills','interests','subject_area','browse_groups','tags','g_language','group_subject_area']
336    }
337    for section,topic_ids in removed_topics.items():
338        section=getattr(portal, section)
339        del_ids=[]
340        for topic_id in topic_ids:
341            topic= getattr(section, topic_id, None)
342            if topic and getattr(topic, 'meta_type','') == 'ATTopic':
343                del_ids.append(topic_id)
344        if del_ids:
345            print 'deleting obsolete topics:', del_ids
346            section.manage_delObjects(del_ids)
347    # Also remove every Topic from catalog, there are some legacy stuff that has survived.
348    pc=portal.portal_catalog
349    for topic_md in list(pc(portal_type='Topic')): # needs to be list because lazy catalog results behave oddly if catalog objects are changed during iteration
350        topic=topic_md.getObject()
351        if topic:
352            topic_parent=topic.aq_inner.aq_parent
353            print 'deleting: ', topic.id
354            topic_parent.manage_delObjects([topic.id])
355        try:
356            pc.uncatalog_object(topic_md.data_record_id_)               
357        except KeyError:
358            print 'tried to uncatalog object but failed'
359
360
361def setupRemoteLeMills(self, portal):
362    #If variable set, tries to add REMOTE_SERVERS to be included in LeMill searches
363    search_tool=getToolByName(portal, 'lemill_search')
364    remote_boxes=search_tool.get_remote_lemilles()
365    for box in REMOTE_SERVERS:
366        add = 1
367        for r in remote_boxes.values():
368            if box == r['URL']:
369                add = 0
370        if add:
371            search_tool.setNewLocation(box)
372
373
374def setupCleanDuplicateActions(self, portal):
375    # Migrating Plone or LeMill causes duplication of certain actions. This is Plone's fault.
376    # addNewActions in Plone's ConfigurationMethods creates these actions without checking if they already exist.
377    atool=getToolByName(portal, 'portal_actions')
378    acts = atool._cloneActions()
379    clean_acts=[]
380    home_check = ownership_check = rename_check = paste_check = delete_check = cut_check = copy_check = 0
381    # Go through actions and add only first instances of the following actions to the new actions list:
382    for a in acts:
383        if a.getId()=='index_html': #'Home'
384            if home_check==0:
385                home_check=1
386                clean_acts.append(a)
387        elif a.getId()=='change_ownership':
388            if ownership_check==0:
389                ownership_check=1
390                clean_acts.append(a)
391        elif a.getId()=='rename':
392            if rename_check==0:
393                rename_check=1
394                clean_acts.append(a)
395        elif a.getId()=='paste':
396            if paste_check==0:
397                paste_check=1
398                clean_acts.append(a)
399        elif a.getId()=='cut':
400            if cut_check==0:
401                copy_check=1
402                clean_acts.append(a)
403        elif a.getId()=='copy':
404            if copy_check==0:
405                copy_check=1
406                clean_acts.append(a)
407        elif a.getId()=='delete':
408            if delete_check==0:
409                delete_check=1
410                clean_acts.append(a)
411        else:
412            clean_acts.append(a)
413    atool._actions = tuple( clean_acts )
414   
415
416def setupFactoryTypes(self, portal):
417    """ set types that should use portal_factory """
418    ft = getToolByName(portal, 'portal_factory')
419    types_list = ft.getFactoryTypes()
420    for t in ALL_CONTENT_TYPES:
421        types_list[t] = 1
422    ft.manage_setPortalFactoryTypes(None, types_list)
423
424
425language_list = ['cs','de','en','es','et','fi','fr','hu','ka','lt','pl','ru','se']
426# Still missing: ['nl','sl']
427
428def setupLanguageTool(self, portal):
429    """ configure plone language tool to use browser language request negotation """
430    lt = getToolByName(portal, 'portal_languages')
431    if lt is None:
432        return
433    lt.manage_setLanguageSettings('en', #default language
434            language_list, # supported languages (needs to be a list, not a tuple)
435            1, # use cookie negotiation
436            1, # use request negotiation
437            0, # use path negotiation
438            0, # force language URLs (content)
439            1, # allow fallback (content)
440            1, # use combined languages
441            0, # display flags
442            0, # start neutral (content)
443            )
444
445# TODO: when we get too many translations, we'll need to shorten the primary list
446primary_language_list = language_list
447
448def setupProperties(self, portal):
449    lt = getToolByName(portal, 'lemill_tool')
450    if not hasattr(lt, 'portal_integration'):
451        safeEditProperty(lt, 'portal_integration', value=False, data_type="boolean")
452    if not hasattr(lt, 'portal_search_link'):
453        safeEditProperty(lt, 'portal_search_link', value="TODO", data_type="string")
454    if not hasattr(lt, 'primary_languages'):
455        safeEditProperty(lt, 'primary_languages', value=primary_language_list, data_type="tokens")
456    if not hasattr(lt, 'allow_banning'):
457        safeEditProperty(lt, 'allow_banning', value=False, data_type="boolean")
458    if not hasattr(lt, 'irc_link'):
459        safeEditProperty(lt, 'irc_link', value='http://neumann.uiah.fi/irc/irc.cgi', data_type="string")
460    lt.language_dict=dict(LANGUAGES)
461    safeEditProperty(portal, 'validate_email', value=False, data_type="boolean")
462   
463def setupLeMillFAQ(self, portal):
464    # Create MultimediaMaterial resource lemill-faq based on docs/FAQ.html
465    faqid='lemill-faq'
466    context=portal.content.webpages
467    if not hasattr(context, faqid):
468        addObject(context, faqid, 'MultimediaMaterial', 'LeMill FAQ')
469    else:
470        return 1 # If there is a lemill_faq don't overwrite it, because we don't want to lose community's changes
471    faq=getattr(context, faqid)   
472    homedir=package_home(globals())
473    file=open(homedir+'/docs/FAQ.html', 'r').read()
474    title=file[file.find('<title>')+7:file.rfind('</title>')]
475    body=file[file.find('<body>')+6:file.rfind('</body>')]
476    faq.edit(title=title, bodyText=body, language='en', tags=['faq', 'lemill'])
477    faq.manage_afterAdd(faq,context)
478    faq.at_post_create_script()
479    faq.state = 'public'
480    faq.reindexObject()
481
482def setupLeMillAbout(self, portal):
483    # Create MultimediaMaterial resuurce about-lemill based on docs/About.html
484    aboutid='about-lemill'
485    context=portal.content.webpages
486    if not hasattr(context, aboutid):
487        addObject(context, aboutid, 'MultimediaMaterial', 'About LeMill')
488    else:
489        return 1 # If there is a about-lemill don't overwrite it
490    about=getattr(context, aboutid)
491    homedir=package_home(globals())
492    file=open(homedir+'/docs/About.html', 'r').read()
493    title=file[file.find('<title>')+7:file.rfind('</title>')]
494    body=file[file.find('<body>')+6:file.rfind('</body>')]
495    about.edit(title=title, bodyText=body, language='en', tags=['about', 'lemill'])
496    about.manage_afterAdd(about,context)
497    about.at_post_create_script()
498    about.state = 'public'
499    about.reindexObject()
500    #about.manage_permission(MODIFY_CONTENT, ('Manager','Owner'), acquire=0)
501       
502def setupUnassignedDiscussionsGroup(self, portal):
503    # Create empty group to hold discussions for items not assigned to any group.
504   
505    if not hasattr(portal, 'community'):
506        return 1
507    parent=portal.community.groups
508    if hasattr(parent, 'unassigned_discussions'):
509        return 1
510    # We force our way through stupid allowed-content-types-limitations
511    if 'Large Plone Folder' not in parent.allowed_content_types:
512        parent.allowed_content_types=parent.allowed_content_types+('Large Plone Folder',)
513    if parent.filter_content_types==True:
514        parent.filter_content_types=False
515    addObject(parent, 'unassigned_discussions', 'Large Plone Folder', 'Discussions for resources not assigned to groups')
516    obj=parent.unassigned_discussions
517    syn_tool = getToolByName(portal, 'portal_syndication', None)
518    syn_tool.enableSyndication(obj)
519    obj.manage_permission(ADD_CONTENT_PERMISSION, ('Member',), acquire=1)
520    obj.manage_permission(LIST_FOLDER_CONTENTS, ('Member',), acquire=1)
521
522   
523       
524
525#########
526# Collect all setup functions into a setup widget
527
528afunctions = {}
529for f in dir():
530    if f.startswith('setup'):
531        func = eval("%s" % f)
532        if type(func) == types.FunctionType:
533            afunctions[f] = func
534
535class LeMillSetup(SetupWidget):
536    type = 'LeMill Setup'
537
538    description = """Customization methods needed by the LeMill Plone portal"""
539
540    functions = afunctions
541
542    def setup(self):
543        pass
544
545    def delItems(self, fns):
546        out = []
547        out.append(('Currently there is no way to remove a function', INFO))
548        return out
549
550    def addItems(self, fns):
551        """This method is called when configuration methods need to be applied.
552        fns is the list of function names that need to be executed in order."""
553        out = []
554        for fn in fns:
555            # All functions are executed with the portal as the first actual parameter
556            self.functions[fn](self, self.portal)
557            out.append(('Function %s has been applied' % fn, INFO))
558        return out
559
560    def installed(self):
561        return []
562
563    def available(self):
564        """Get a list of availabel functions."""
565        funcs = self.functions.keys()
566        # Sort, so we get a pre-determined order.
567        # The functions need to be named properly
568        funcs.sort()
569        return funcs
Note: See TracBrowser for help on using the repository browser.