view MPIWGProjects.py @ 228:afc96bc56817

also show multiple projects with the same number (including none) in the tree.
author casties
date Tue, 05 Nov 2013 13:58:45 +0100
parents 95e0087b9e19
children d4216a848547
line wrap: on
line source

"""This contains the class MPIWG Projects
for organizing and maintaining the different project pages

$author dwinter 26.06.2008

"""
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from OFS.Image import Image
from App.ImageFile import ImageFile
from OFS.SimpleItem import SimpleItem
from OFS.Folder import Folder
from AccessControl import ClassSecurityInfo
from Acquisition import aq_parent
from Globals import package_home
import urllib
import re
import os 
import sys
import logging
import time
import unicodedata

import xml.etree.ElementTree as ET

from SrvTxtUtils import getInt, unicodify, utf8ify, serialize, refreshingImageFileIndexHtml, shortenString
from Products.ZDBInterface.ZDBInterfaceFolder import ZDBInterfaceFolder

import xmlhelper  # Methoden zur Verwaltung der projekt xml
from HashTree import HashTree
import MPIWGHelper

# TODO: better names for the fields
fieldLabels = {'WEB_title':'WEB_Title',
         'xdata_01':'Responsible Scientists',
         'xdata_02':'Department',
         'xdata_03':'Historical Persons',
         'xdata_04':'Time period',
         'xdata_05':'Sorting number',
         'xdata_06':'Keywords',
         'xdata_07':'Short title',
         'xdata_08':'Other involved scholars' ,
         'xdata_09':'Disciplines',
         'xdata_10':'Themes',
         'xdata_11':'Object Digitallibrary',
         'xdata_12':'Cooperation partners',
         'xdata_13':'Funding institutions',
         'WEB_project_header':'WEB_project_header',
         'WEB_project_description':'WEB_project_description',
         'WEB_related_pub':'WEB_related_pub'}

definedFields = fieldLabels.keys() # TODO: should this be sorted?

editableFields = ('xdata_07', 'xdata_01', 'xdata_05', 'xdata_08', 'xdata_12', 'xdata_13')

# die folgenden Klassen sind jetzt in einzelne Files ausgelagert aus Kompatibilitaetsgruenden, bleiben die Klassen hier noch drin.
# Sonst funktionieren die alten Webseiten nicht mehr.

import MPIWGRoot
import MPIWGLink
import MPIWGTemplate

class MPIWGRoot(MPIWGRoot.MPIWGRoot):
    """depricated"""
    
class MPIWGLink(MPIWGLink.MPIWGLink):
    """depricated"""
    
class MPIWGTemplate(MPIWGTemplate.MPIWGTemplate):
    """depricated"""

    
class MPIWGProject_publication(Folder):
    """publications object fuer project"""

    meta_type = "MPIWGProject_publication"
    
    text = None
    link = None
    bookId = None
    
    # templates
    edit = PageTemplateFile('zpt/project/related_publication/edit_basic', globals())
    
    
    redirect = MPIWGHelper.redirect


    def hasLinkToBookPage(self):
        """teste ob eingebener link zu einer MPIWG Book page geht"""
        logging.debug("MPIWGProject_publication - begin hasLinkToBookPage")
        if not self.link:
            return False  # es gibt keinen link
        
        paths = self.link.split('/')
        if len(paths) > 2:
            # book page should be in folder books
            bookid = None
            try:
                idx = paths.index('books')
                bookid = paths[idx + 1]
                book = self.en.books[bookid]
                self.bookId = bookid
                return True
            
            except:
                logging.debug("hasLinkToBookPage: not a book page link=%s"%self.link)

        self.bookId = None
        return False

    
    def getBookId(self):
        """Return the book page id."""
        return self.bookId
    
        
    getUrl = MPIWGHelper.getUrl


    def editPublication(self, text=None, description=None, link=None, RESPONSE=None):
        """edit a publication"""

        if (not text) and (not description):
            pt = self.edit
            return pt()
       
        if text:
            self.text = text
        
        if description:
            self.description = description
        
        if link:
            self.link = link
            self.hasLinkToBookPage()
        
        if RESPONSE:
            self.redirect(RESPONSE, "../managePublications")


class MPIWGProject_relatedProject(SimpleItem):
    """publications object fuer project"""

    meta_type = "MPIWGProject_relatedProject"
    
    objid = None
    projectLabel = None
    
    # templates
    edit = PageTemplateFile('zpt/project/related_project/edit_basic', globals())
    

    redirect =  MPIWGHelper.redirect

    
    def getProjectId(self):
        """Return the related project id."""
        return self.objid


    def getProject(self):
        """Return the related project object."""
        return getattr(self.projects, self.objid, None)

    
    def getProjectTitle(self):
        """Return the title of the related project."""
        return getattr(self, 'projectWEB_title', None)

    
    def getProjectLabel(self):
        """Return the label of the related project."""
        label = getattr(self, 'projectLabel', None)
        if not label:
            proj = self.getProject()
            if proj is not None:
                label = proj.getLabel()
                
            self.projectLabel = label
            
        return label

        
    getUrl = MPIWGHelper.getUrl


    def editRelatedProject(self, link=None, RESPONSE=None):
        """edit a publication"""

        if (not link):
            pt = self.editDescription
            return pt()
        
        # hole die id des projektes
        splitted = link.split("/")
        
        # teste ob es das project gibt
        if len(splitted) < 1:
            self.redirect(RESPONSE, 'errorRelatedProjects?link=' + link)
        
        objid = splitted[-1]
        object = getattr(self.projects, objid, None)
        
        if object is None:
            self.redirect(RESPONSE, 'errorRelatedProjects?link=' + link)
       
        self.orginallink = link[0:]
        self.objid = objid[0:]
    
        self.projectWEB_title = object.getProjectTitle()
        self.projectLabel = object.getLabel()
       
        self.enabled = True;
       
        if RESPONSE:
            self.redirect(RESPONSE, "../manageRelatedProjects")


class MPIWGProject_image(Image):
    """Images for Projects"""

    meta_type = "MPIWGProject_image"

    # templates
    oldShowImage = PageTemplateFile('zpt/project/image/projectImageView', globals())
    editForm = PageTemplateFile('zpt/project/image/edit_basic', globals())

    getUrl = MPIWGHelper.getUrl


    def getCaption(self):
        """Return the image caption."""
        return getattr(self, 'caption', None)

    
    def getLink(self):
        """Return the image link."""
        return getattr(self, 'link', None)
    

    def editImage(self, file=None, caption=None, link=None, RESPONSE=None):
        """edit the Image"""
        if (not file) and (not caption):
            pt = self.editForm
            return pt()

        if file and (not file.filename.strip() == ""):
            self.manage_upload(file)

        if caption:
            if isinstance(caption, list):
                caption = caption[0]
               
            self.caption = caption

        if link:
            self.link = link

        if RESPONSE:
            self.redirect(RESPONSE, "../manageImages")


class MPIWGProject_InfoBlock(SimpleItem):
    """publications object fuer project"""

    meta_type = "MPIWGProject_InfoBlock"
    
    # templates
    edit = PageTemplateFile('zpt/project/infoblock/edit_items', globals())
    
    
    redirect =  MPIWGHelper.redirect


    def __init__(self, id, title=None):
        """Create info block."""
        self.id = id
        self.title = title
        self.place = 0
        self.items = []
        

    def getTitle(self):
        """Return the title."""
        return self.title
    

    def getItems(self):
        """Return the list of items."""
        return self.items
    

    def setItems(self, items):
        """Set the list of items."""
        self.items = items
        self._p_changed = True


    def addItem(self, item=None, text=None, link=None, RESPONSE=None):
        """Add an item to the InfoBox"""
        if item is None:
            item = {'text': text, 'link': link}
            
        self.items.append(item)
        self._p_changed = True
        if RESPONSE is not None:
            self.redirect(RESPONSE, 'edit')
        
        
    def deleteItem(self, idx, RESPONSE=None):
        """Delete an item from the info block."""
        try:
            del self.items[int(idx)]
            self._p_changed = True
        except:
            logging.error("InfoBlock deleteItem: error deleting item %s!"%idx)
        
        if RESPONSE is not None:
            self.redirect(RESPONSE, 'edit')
        
        
    def moveItem(self, idx, op, RESPONSE=None):
        """Move items up or down the list."""
        try:
            idx = int(idx)
            if op == 'up':
                if idx > 0:
                    self.items[idx-1], self.items[idx] = self.items[idx], self.items[idx-1]
            elif op == 'down':
                if idx < len(self.items)-1:
                    self.items[idx], self.items[idx+1] = self.items[idx+1], self.items[idx]
                    
            self._p_changed = True
        except:
            logging.error("InfoBlock moveItem: error moving item at %s!"%idx)

        if RESPONSE is not None:
            self.redirect(RESPONSE, 'edit')
            
            
    def editItems(self, REQUEST, RESPONSE=None):
        """Change items from request form."""
        form = REQUEST.form
        for k in form:
            t, n = k.split('_')
            if t in ['text', 'link']:
                try:
                    logging.debug("editItems: change[%s].%s = %s"%(n,t,repr(form[k])))
                    self.items[int(n)][t] = form[k]
                except:
                    logging.error("InfoBlock editItems: error changing item %s!"%k)
                 
        self._p_changed = True
        if RESPONSE is not None:
            self.redirect(RESPONSE, 'edit')
        
                    

class MPIWGProject(Folder):
    """Class for Projects"""
    
    security = ClassSecurityInfo()
    meta_type = 'MPIWGProject'
    
    manage_options = Folder.manage_options + (
        {'label':'Load New File', 'action':'loadNewFileForm'},
        {'label':'Edit', 'action':'editDescription'},
        )
        # {'label':'Edit ProjectInfo','action':'editMPIWGProjectForm'},
        # {'label':'Edit BasisInfo','action':'editMPIWGBasisForm'},
        # {'label':'Edit Publications','action':'editMPIWGRelatedPublicationsForm'},
        # {'label':'Edit Themes & Disciplines','action':'editMPIWGDisciplinesThemesForm'},
        # {'label':'Versionmanager','action':'versionManageForm'},

    #
    # templates
    #
    project_html = PageTemplateFile('zpt/project/project_index_html', globals())
    # edit templates
    edit_css = ImageFile('css/edit.css', globals())
    # make css refreshable for development
    edit_css.index_html = refreshingImageFileIndexHtml
    # user-accessible editing templates
    edit_template = PageTemplateFile('zpt/project/edit_template', globals())
    security.declareProtected('View management screens', 'editBasic')
    editBasic = PageTemplateFile('zpt/project/edit_basic', globals())
    security.declareProtected('View management screens', 'editDescription')
    editDescription = PageTemplateFile('zpt/project/edit_description', globals())
    security.declareProtected('View management screens', 'editRelatedProjectsForm')
    editRelatedProjectsForm = PageTemplateFile('zpt/project/edit_related_projects', globals())
    editRelatedProjectsError = PageTemplateFile('zpt/project/edit_related_projects_error', globals())
    security.declareProtected('View management screens', 'editImagesForm')
    editImagesForm = PageTemplateFile('zpt/project/edit_images', globals())    
    security.declareProtected('View management screens', 'editPublicationsForm')
    editPublicationsForm = PageTemplateFile('zpt/project/edit_publications', globals())
    security.declareProtected('View management screens', 'editInfoBlocksForm')
    editInfoBlocksForm = PageTemplateFile('zpt/project/edit_infoblocks', globals())
    security.declareProtected('View management screens', 'editAdditionalPublicationsForm')
    editAdditionalPublicationsForm = PageTemplateFile('zpt/project/pubman/change_publications', globals())
    security.declareProtected('View management screens', 'editAddAdditionalPublications')
    editAddAdditionalPublications = PageTemplateFile('zpt/project/pubman/add_publications', globals())
    security.declareProtected('View management screens', 'edit')
    edit = editDescription
    # management templates
    security.declareProtected('View management screens', 'loadNewFileForm')
    loadNewFileForm = PageTemplateFile('zpt/project/manage_newfile', globals())
    description_only_html = PageTemplateFile('zpt/project/description_only_html', globals())
    # additional pages
    additional_publications_html = PageTemplateFile('zpt/project/pubman/show_publications', globals())


    def __init__(self, id, argv=None):
        """initiere classe"""
        self.creationTime = time.strftime("%Y%m%d%H%M%S", time.localtime())[0:]
        self.id = id
        self.title = id
        self.isActiveFlag = True  # Flag is true is the project is still active, False if accomplished
        self.responsibleScientistsList = []  # enthaelt die Lister der verantwortlichen Wissenschaftler in der Form (NAME, KEY), key ist "" flass Wissenschaftler nicht an unserem Haus
        self.projectThumb = None
        
        if argv:
            for arg in definedFields:
                try:
                        setattr(self, arg, argv[arg])
                except:
                        setattr(self, arg, "")
        else:
            for arg in definedFields:
                setattr(self, arg, '')
            

    def index_html(self):
        """default html representation"""
        # TODO: do we need to do date-stuff?
        # get template
        pt = self.project_html
        # render template
        return pt()


    redirect =  MPIWGHelper.redirect


    def getDefinedFields(self):
        """show all defined fields."""
        return definedFields


    def getFieldLabels(self):
        """Return the field labels dict."""
        return fieldLabels
    

    def getEditableFields(self):
        """giveListofDatafields"""
        return editableFields
    

    def getNumber(self):
        """returns sorting number"""
        n = getattr(self, 'xdata_05', None)
        if isinstance(n, list):
            # compat with old lists
            return n[0]
        else:
            return n
    
    
    def getProjectTitle(self, forSorting=False):
        """returns the project title"""
        t = getattr(self, 'WEB_title', None)
        if isinstance(t, list):
            # compat with old lists
            t = t[0]
        
        if forSorting and t is not None and len(t) > 0:
            # remove stopwords and signs for sorting
            if t.lower().startswith('a '):
                t = t[2:]
            elif isinstance(t, unicode) and unicodedata.category(t[0])[0] != 'L':
                t = t[1:]
                
        return t
    
    
    def getLabel(self):
        """returns label (or title) of this project"""
        l = getattr(self, 'xdata_07', None)
        if isinstance(l, list):
            # compat with old lists
            l = l[0]
            
        if l:
            return l
        else:
            return self.getProjectTitle()

    
    def getResponsibleScientists(self):
        """returns the responsible scientists as string"""
        t = getattr(self, 'xdata_01', None)
        if isinstance(t, list):
            # compat with old lists
            return t[0]
        else:
            return t
    
    
    def getResponsibleScientistsList(self):
        """returns a list with the responsible scientists as dicts with name, key, and shortname"""
        return getattr(self, 'responsibleScientistsList', [])

    
    def setResponsibleScientistsList(self, nameDict):
        """sets the responsibleScientistsList from nameDict.
        List will be ordered like the responsible scientists field."""
        names = self.getResponsibleScientists()
        if names.find(";") > -1:  # rate Trenner ist ;
            nameList = names.split(";")  
        else:
            nameList = names.split(",")
            
        scientistsList = []
        for name in nameList:
            name = unicodify(name.strip())
            if not name:
                continue
            
            logging.debug("setResponsibleScientistsList: name=%s"%repr(name))
            if name in nameDict:
                # found in data
                data = nameDict[name]
                scientistsList.append({'name': name, 'key': data['key'], 'username': data['username']})
            else:
                scientistsList.append({'name': name})
        
        logging.debug("setResponsibleScientistsList: nameDict=%s new list=%s"%(repr(nameDict),repr(scientistsList)))
        self.responsibleScientistsList = scientistsList
        # force update of the Properties list in the ZopeDB
        self._p_changed = 1
        
              
    def getInvolvedScholars(self):
        """returns the other involved scholars"""
        t = getattr(self, 'xdata_08', None)
        if isinstance(t, list):
            # compat with old lists
            return t[0]
        else:
            return t

        
    def getCooperationPartners(self):
        """returns the cooperation partners"""
        t = getattr(self, 'xdata_12', None)
        if isinstance(t, list):
            # compat with old lists
            return t[0]
        else:
            return t
        
        
    def getMPIWGProject(self):
        """Return this project for acquisition."""
        return self

     
    def getMPIWGProjectUrl(self):
        """Return this project for acquisition."""
        return self.absolute_url()
     
              
    getUrl = MPIWGHelper.getUrl
                
              
    def getThumbUrl(self, default=None):
        """returns the URL of the project thumbnail image"""
        thumb = getattr(self, 'projectThumb', None)
        if thumb is None:
            # get thumb from list (thumb is last image)
            imgs = self.getImageList()
            if len(imgs) > 0:
                thumb = imgs[-1]
                self.projectThumb = thumb

        if thumb is None:
            return default
        
        return thumb.absolute_url()

              
    def getDepartment(self):
        """returns the department of this project"""
        num = self.getNumber()
        pp = num.find('.')
        if pp > 0:
            num = num[:pp]
            
        return self.en.getMPIWGRoot().getDepartment(projectNumber=num)


    def getDepartmentId(self):
        """returns the id of the department of this project"""
        dep = self.getDepartment()
        if dep is not None:
            return dep.getId()
        
        return None


    def getDescription(self, filter=None, length=0):
        """returns the project description"""
        text = getattr(self, 'WEB_project_description', None)
        if isinstance(text, list):
            # compat with old lists
            text = text[0]

        if filter == 'plaintext':
            # filter out any tags, keep only text
            try:
                xmltext = utf8ify("<div>%s</div>"%text)
                dom = ET.fromstring(xmltext)
                plaintext = ""
                for elem in dom.iter():
                    if elem.tag == 'style':
                        # ignore tag
                        continue
                    
                    if elem.text:
                        plaintext += elem.text
                    if elem.tail:
                        plaintext += elem.tail
                        
                    if length > 0 and len(plaintext) > length:
                        break

                text = plaintext
            except Exception, e:
                logging.warn("getDesciption: error parsing description! Returning everything. %s"%e)
                
        if length > 0 and len(text) > length:
            # try to not break words
            if text[length] not in [' ', '.', '?', '!']:
                # search the last blank
                length = text.rfind(' ', 0, length)

            return text[:length] + '...'
        
        return text
            
        
    def getSuperProjects(self):
        """returns a list of ancestor projects to the root"""
        tree = self.getProjectTree()
        return tree.getAncestorsOf(self.getNumber())
    

    def getSubProjects(self, active=1):
        """returns a list of child projects"""
        tree = self.getProjectTree()
        return [p for p in tree.getChildrenOf(self.getNumber()) if p.checkActive(active)]
    
              
    def getRelatedDigitalSources(self):
        """returns the related digital sources"""
        t = getattr(self, 'xdata_11', None)
        if isinstance(t, list):
            # compat with old lists
            return t[0]
        else:
            return t

        
    def getFundingInstitutions(self):
        """returns the funding institutions"""
        t = getattr(self, 'xdata_13', None)
        if isinstance(t, list):
            # compat with old lists
            return t[0]
        else:
            return t

              
    def _moveObjectPlace(self, objectList, objectId, direction):
        """Move object with objectId from objectList in direction 
        by changing its place attribute."""
        if not objectId or not direction:
            return

        for idx in range(len(objectList)):
            object = objectList[idx]
            if object.getId() == objectId:
                if direction == 'up':
                    if idx > 0:
                        # move up
                        objectList[idx -1].place += 1
                        objectList[idx].place -= 1
                        
                elif direction == 'down':
                    if idx < len(objectList) - 1:
                        # move down
                        objectList[idx + 1].place -= 1
                        objectList[idx].place += 1
                        
                return
                      

    def getImageList(self):
        """returns the sorted list of images for this project"""
        items = self.objectValues(spec='MPIWGProject_image')
        # sort by place
        return sorted(items, key=lambda x:int(getattr(x, 'place', 0)))



    def addImage(self, fileHd, caption=None, link=None, RESPONSE=None, filename=None):
        """add an MPIWG_Project_image"""

        if not filename:
            filename = fileHd.filename

        if not fileHd:
            fileHd = file(os.path.join(package_home(globals()), 'blank.gif'))
            
        filename = filename.replace(' ','_')
            
        while filename in self.objectIds(spec='MPIWGProject_image'):
            # file with this name exists
            fs = filename.split('.')
            try:
                part = fs[-2]
                # assume next-to-last part is name
                if part[-2] == '_':
                    # *_n -- increment n
                    fs[-2] = part[:-1] + chr(ord(part[-1]) + 1)
                else:
                    fs[-2] += '_1'
                    
                filename = '.'.join(fs)
            except:
                filename += '_1'
            
        newImage = MPIWGProject_image(filename, filename, fileHd)

        self._setObject(filename, newImage)
        obj = getattr(self, filename)
        obj.caption = caption
        obj.enabled = True;
        obj.place = self._getLastImageNumber() + 1
        obj.id = filename
        obj.link = link

        # invalidate thumbnail
        self.projectThumb = None
        
        if RESPONSE is not None:            
            self.redirect(RESPONSE, 'manageImages')


    def _getLastImageNumber(self):
        items = self.getImageList()
        if not items:
            return 0
        else:
            return getattr(items[-1], 'place', 0)

        
    def manageImages(self, name=None, op=None):
        """manage images"""
        self._moveObjectPlace(self.getImageList(), name, op)
 
        # invalidate thumbnail
        self.projectThumb = None
        
        pt = self.editImagesForm
        return pt()


    def deleteImage(self, id, RESPONSE=None):
        """delete Image id"""
        try:
            self.manage_delObjects([id])
        except:
            logging.error("ERROR MPIWG: %s %s" % sys.exc_info()[0:2])
                
        # invalidate thumbnail
        self.projectThumb = None
        
        if RESPONSE:
            self.redirect(RESPONSE, 'manageImages')

 
    def getPublicationList(self):
        """returns the list of related publications"""
        items = self.objectValues(spec='MPIWGProject_publication')
        # sort by place
        items.sort(key=lambda x:int(getattr(x, 'place', 0)))
        return items          
        
    
    def addPublication(self, text=None, link=None, RESPONSE=None):
        """add an MPIWG_Publication"""
        if text or link:
            number = self._getLastPublicationNumber() + 1
            name = "publication_" + str(number)
            while hasattr(self, name):
                number += 1
                name = "publication_" + str(number)
            
            newPublication = MPIWGProject_publication(name)
        
            self._setObject(name, newPublication)
            obj = getattr(self, name)
            obj.text = text
            obj.link = link
            obj.enabled = True;
            obj.place = self._getLastPublicationNumber() + 1
            obj.id = name
            # hasLinkToBookPage updates bookid if available
            if obj.hasLinkToBookPage() and not text:
                # take title from book page
                try:
                    book = self.en.books[obj.bookId]
                    obj.text = book.getInfo('title')
                except:
                    pass

        if RESPONSE is not None:
            self.redirect(RESPONSE, 'managePublications')


    def _getLastPublicationNumber(self):
        items = self.getPublicationList()
        if not items:
            return 0
        else:
            return getattr(items[-1], 'place', 0)

        
    def managePublications(self, name=None, op=None):
        """manage publications"""
        self._moveObjectPlace(self.getPublicationList(), name, op)

        pt = self.editPublicationsForm
        return pt()

    
    def deletePublication(self, id, RESPONSE=None):
            """delete Publication id"""
            self.manage_delObjects([id])
            if RESPONSE:
                self.redirect(RESPONSE, 'managePublications')
              

    def getRelatedProjectList(self):
        """returns the list of related projects"""
        items = self.objectValues(spec='MPIWGProject_relatedProject')
        # sort by place
        items.sort(key=lambda x:int(getattr(x, 'place', 0)))
        return items
        

    def addRelatedProject(self, link, RESPONSE=None):
        """add a MPIWGProject_relatedProject"""
        number = self._getLastRelatedProjectNumber() + 1
        name = "related_project_" + str(number)
        while hasattr(self, name):
            number += 1
            name = "related_project_" + str(number)
        
        # hole die id des projektes
        splitted = link.split("/")
        
        # teste ob es das project gibt
        if len(splitted) < 1:
            self.redirect(RESPONSE, 'errorRelatedProjects?link=' + link)
        
        objid = splitted[-1]
        object = getattr(self.projects, objid, None)
        
        if object == None:
            self.redirect(RESPONSE, 'errorRelatedProjects?link=' + link)
            return
        
        newPublication = MPIWGProject_relatedProject(name)

        self._setObject(name, newPublication)
        obj = getattr(self, name)
        obj.orginallink = link[0:]
        obj.objid = objid[0:]
        logging.debug("add relobj:objid" + repr(obj.objid))
        obj.projectWEB_title = object.getProjectTitle()
        logging.debug("add relobj:webtitle" + repr(obj.projectWEB_title))
        obj.enabled = True;
        obj.place = self._getLastRelatedProjectNumber() + 1
        obj.id = name
        if RESPONSE is not None:
            self.redirect(RESPONSE, 'manageRelatedProjects')

     
    def _getLastRelatedProjectNumber(self):
        items = self.getRelatedProjectList()
        if not items:
            return 0
        else:
            return getattr(items[-1], 'place', 0)

        
    def manageRelatedProjects(self, name=None, op=None):
        """manage related projects"""
        self._moveObjectPlace(self.getRelatedProjectList(), name, op)

        pt = self.editRelatedProjectsForm
        return pt()    


    def deleteRelatedProject(self, id, RESPONSE=None):
        """delete Publication id"""
        self.manage_delObjects([id])
        if RESPONSE:
            self.redirect(RESPONSE, 'manageRelatedProjects')


    def errorRelatedProjects(self, link):
        """error creating a related project"""
        pt = self.editRelatedProjectsError
        return pt(link=link)


    def getInfoBlockList(self):
        """returns the list of related projects"""
        items = self.objectValues(spec='MPIWGProject_InfoBlock')
        # sort by place
        items.sort(key=lambda x:int(getattr(x, 'place', 0)))
        return items
        

    def addInfoBlock(self, block_title=None, item_text=None, item_link=None, RESPONSE=None):
        """add a MPIWGProject_InfoBlock"""
        if block_title:
            number = self._getLastInfoBlockNumber() + 1
            name = "infoblock_" + str(number)
            while hasattr(self, name):
                number += 1
                name = "infoblock_" + str(number)
                    
            newBlock = MPIWGProject_InfoBlock(name, block_title)
            # add block to project
            self._setObject(name, newBlock)
            obj = getattr(self, name)
            obj.place = self._getLastInfoBlockNumber() + 1
            if item_text:
                obj.addItem(text=item_text, link=item_link)

        if RESPONSE is not None:
            self.redirect(RESPONSE, 'manageInfoBlocks')

     
    def _getLastInfoBlockNumber(self):
        items = self.getInfoBlockList()
        if not items:
            return 0
        else:
            return getattr(items[-1], 'place', 0)

        
    def manageInfoBlocks(self, name=None, op=None):
        """manage related projects"""
        self._moveObjectPlace(self.getInfoBlockList(), name, op)

        pt = self.editInfoBlocksForm
        return pt()    


    def deleteInfoBlock(self, id, RESPONSE=None):
        """delete Publication id"""
        self.manage_delObjects([id])
        if RESPONSE:
            self.redirect(RESPONSE, 'manageInfoBlocks')


    def getAdditionalPublicationList(self):
        """hole publications aus der datenbank"""
        query="select * from pubmanbiblio_projects where lower(key_main) = lower(%s) order by priority DESC"
        return self.executeZSQL(query,[self.getId()])

        
    def hasAdditionalPublications(self):
        """test if extended publication list exists"""
        query="select count(*) from pubmanbiblio_projects where lower(key_main) = lower(%s)"
        res= self.executeZSQL(query,[self.getId()])        
        if res[0].count > 0:
            return True
        else:
            return False


    def addAdditionalPublicationsFromPubman(self,REQUEST):
        """addPublications from pubman"""
        data=REQUEST.form
        if data.get("method",None) is None:
            pt = self.editAddAdditionalPublications
            return pt()
        
        if data.get("method") == "search":
            entries= self.mpiwgPubman.search(data)
            pt = self.editAddAdditionalPublications
            return pt(values=entries)
        
        if data.get("method") == "add":
            return self.addEntriesToAdditionalPublicationList(data)
            #pt=PageTemplateFile(os.path.join(package_home(globals()),'zpt/staff/pubman','add_publications.zpt')).__of__(self)

         
    def addEntriesToAdditionalPublicationList(self,data):
        """fuege eintrage aus data zur publications liste.
        
        @param data Map mit escidocID --> value
        value muss "add" sein damit hinzugefuegt wird"""
        for key in data.keys():
            if key.startswith('escidoc:'):
                query="INSERT INTO pubmanbiblio_projects (key_main,escidocId) values (%s,%s)"
                if data.get(key)=="add":
                    self.executeZSQL(query,[self.getId(),key])
        
        if hasattr(self,'REQUEST'):
            return self.REQUEST.response.redirect("changeAdditionalPublications")

    
    def changeAdditionalPublications(self,REQUEST):
        """change published publications"""
        data=REQUEST.form       
        if data.get("method","change"):
            for key in data.keys():
                splitted=key.split("__") #format escidoc_id__p fuer priority, nur escidocid
                value=data[key]
                if len(splitted)==1:
                    self.deleteFromAdditionalPublicationList(key);
                
                elif(splitted[1]) == "p":
                    self.setAdditionalPublicationPriority(splitted[0],value);
                    
        pt = self.editAdditionalPublicationsForm
        return pt()

    
    def deleteFromAdditionalPublicationList(self,escidocid):
        """Loessche publication with escidoc id from publication list"""
        query ="DELETE FROM pubmanbiblio_projects WHERE escidocid=%s and key_main=%s"
        self.executeZSQL(query,[escidocid,self.getId()]);

 
    def setAdditionalPublicationPriority(self,escidocid,value):
        query="update pubmanbiblio_projects set priority=%s where escidocid=%s and key_main=%s"
        try:
            value = int(value)
            self.executeZSQL(query,[value,escidocid,self.getId()]);
        except:
            logging.error("couldn't change:")
            logging.error(escidocid)
            logging.error(value)
        

    def getActualVersion(self, date=None):
        """actuelle version"""
        def sortProjectsByTime(x, y):
            return cmp(x[1].archiveTime, y[1].archiveTime)

        if not date:
            if self.isCurrentVersion():
                return self
            else:
                return None

        # suche ob aeltere versionen vorhanden sind

        finds = self.ZopeFind(self, obj_metatypes=['MPIWGProject'])
        if not finds:  # wenn nicht dann teste ob die aktuelle version schon existiert hat.
            ad = getattr(self, 'creationTime', '20050101000000')
            if int(date) > int(ad):
                return self
            else:
                return None

        else:
            finds.sort(sortProjectsByTime)

        for find in finds:
            # gehe durch die alten Projekte und finde das entprechende
            if (int(find[1].archiveTime) > int(date)) and (int(date) > int(getattr(find[1], 'creationTime', '20050101000000'))):
                return find[1]

        # kein passendes gefunden, dann teste ob das aktuelle in frage kommt
        ad = getattr(self, 'creationTime', '20050101000000')
        
        if int(date) > int(ad):
        
            return self
        else:
            return None
          
        
    def isCurrentVersion(self):
        """Return if project is the current version."""
        currentTime = time.localtime()
        # print getattr(self,'archiveTime',currentTime)
        return (getattr(self, 'archiveTime', currentTime) >= currentTime)
        
        
    def copyObjectToArchive(self):
        """kopiere aktuelles objekt ins archiv"""
        logging.info("copytoarchive 1")
        cb = aq_parent(self).manage_copyObjects(self.getId())
        logging.info("copytoarchive 2")
        self.manage_pasteObjects(cb)
        logging.info("copytoarchive 3")
        actualTime = time.localtime()
        
        self.manage_renameObject(self.getId(), self.getId() + "_" + time.strftime("%Y%m%d%H%M%S", actualTime))
        logging.info("copytoarchive 4")
        obj = getattr(self, self.getId() + "_" + time.strftime("%Y%m%d%H%M%S", actualTime))
        obj.setArchiveTime(time.strftime("%Y%m%d%H%M%S", actualTime))
        logging.info("copytoarchive 5")
        ids = [x[0] for x in self.ZopeFind(obj, obj_metatypes=['MPIWGProject'])]
        logging.info("copytoarchive 6")
        obj.manage_delObjects(ids)
        logging.info("copytoarchive 7")

        
    def setArchiveTime(self, time):
        """set Archive Time"""
        self.archiveTime = time[0:]

        
    def delArchiveTime(self):
        """delete archive time"""
        del self.archiveTime

   
    def isActiveProject(self):
        """check if the project is still active, default is true."""
        return getattr(self, 'isActiveFlag', True)

 
    def checkActive(self, active):
        """returns if the project state matches the active state.
        active = 0 : all projects
        active = 1 : active projects
        active = 2 : inactive projects
        """
        act = getattr(self, 'isActiveFlag', True)
        return (active == 1 and act) or (active == 0) or (active == 2 and not act)

 
    def isArchivedProject(self):
        """check if the project is archived"""
        completed = getattr(self, 'completedAt', '')
        # completed leer 
        if completed == "" :
            return False;
        if completed == 0:
            return False;
        
        return True

        
    def checkArchived(self, archived):
        """returns if the project state matches the archived state.
        archived = 0 : all projects
        archived = 1 : current projects
        archived = 2 : archived projects
        """
        arch = self.isArchivedProject()
        return (archived == 1 and not arch) or (archived == 0) or (archived == 2 and arch)        

        
    def setActiveFlag(self, status=True):
        """set the active flag"""
        self.isActiveFlag = status

        
    def setCompletedAt(self, date):
        """set the date of completion, date should be in the form DD.MM.YYYY or MM.YYYY or YYYY"""
        # logging.info("DATE:"+repr(date))
        transformedDate = self.transformDate(date);
        # logging.info("transformed"+repr(transformedDate))
        if transformedDate is not None:
            setattr(self, "completedAt", transformedDate)
            return True;
        else:
            return False;
    
    def setStartedAt(self, date):
        """set the date of start, date should be in the form DD.MM.YYYY or MM.YYYY or YYYY"""
        # logging.info("DATE:"+repr(date))
        transformedDate = self.transformDate(date);
        # logging.info("transformed"+repr(transformedDate))
        if transformedDate is not None:
            setattr(self, "startedAt", transformedDate)
            return True;
        else:
            return False;

    def getCompletedAt(self):
        """gibt das transformierte Datum zurueck, an dem das Projekt beendet wurde."""
        date = getattr(self, 'completedAt', '')
        if date:
            return self.reTransformDate(date);
        else:
            return ""
        
    def getStartedAt(self):
        """gibt das transformierte Datum zurueck, an dem Projekt begonnen wurde."""
        date = getattr(self, 'startedAt', '')
        if date:
            return self.reTransformDate(date);
        else:
            return '';
        
    def reTransformDate(self, date):
        """transformiert , transformdate zurueck"""
        year = int(date / 10000)
        month = int((date - year * 10000) / 100)
        day = int((date - year * 10000 - month * 100))
        if (day == 0) and (month == 0):
            return """%s""" % year;
        if day == 0 :
            return """%s.%s""" % (month, year);
        
        return """%s.%s.%s""" % (day, month, year);
        
        
    def transformDate(self, date):
        """transformiert ein Datum von DD.MM.YYYY, MM.YYYY,YYYY nach  YYYYMMDD, alle nicht angebenen Werte
        werden auf 0 gesetzt, es wird null zurueckgegeben falls das Datum ungueltig ist""" 
   
        if (date == None):
            return None;
        
          
        if (date.lstrip().rstrip() == "") :
            return "";
        
        splitted = date.split(".")
        length = len(splitted)
        year = 0
        month = 0
        day = 0
        if length > 3:
            return "";
        if length == 3:
            day = int(splitted[0])
        if length > 1:
            month = int(splitted[length - 2])
        
        if length > 0:
            try:
                year = int(splitted[length - 1])
            except:
                pass
        
       # # logging.info("month:"+(month))
        if not (0 <= month < 13):
            return None;
        
        if not(0 <= day < 32):
            return None;
        
        if (year > 0) and (year < 1900):  # jahr nicht vierstellig eingegeben
            year = 2000 + year;
        return year * 10000 + month * 100 + day
        
        
    
    def checkDate(self, date):
        """teste ob zum Zeitpunkt date eine andere version existierte"""
        

        def sortProjectsByTime(x, y):
            return cmp(x[1].archiveTime, y[1].archiveTime)

        # suche ob aeltere versionen vorhanden sind

        finds = self.ZopeFind(self, obj_metatypes=['MPIWGProject'])
        if not finds:  # wenn nicht dann teste ob die aktuelle version schon existiert hat.
            ad = getattr(self, 'creationTime', '20050101000000')
            if int(date) > int(ad):
                return self.REQUEST['URL1'] + "/" + self.getId()
            else:
                return self.REQUEST['URL1'] + "/no_project"

        else:
            finds.sort(sortProjectsByTime)

        for find in finds:
            # gehe durch die alten Projekte und finde das entprechende
            if (int(find[1].archiveTime) > int(date)) and (int(date) > int(getattr(find[1], 'creationTime', '20050101000000'))):
                return self.REQUEST['URL1'] + "/" + find[1].getId()

        # kein passendes gefunden, dann teste ob das aktuelle in frage kommt
        ad = getattr(self, 'creationTime', '20050101000000')
        
        if int(date) > int(ad):        
            return self.REQUEST['URL1'] + "/" + self.getId()
        else:
            return self.REQUEST['URL1'] + "/no_project"
                    
            
    def saveFromPreview(self, RESPONSE=None):
        """save content aus preview"""
        self.WEB_project_description = self.previewTemplate.WEB_project_description[0:]
        #self.REQUEST.RESPONSE.redirect("./index.html")
        if RESPONSE is not None:
            return self.editDescription()

        
    def saveEditedContent(self, kupu=None, preview=None, RESPONSE=None):
        """save Edited content"""
        # logging.debug("saveEditedContent kupu=%s preview=%s"%(kupu,preview))

        if preview:
            kupu = preview
            
        # find content of body tags
        start = kupu.find("<body>")
        end = kupu.find("</body>")
        newcontent = kupu[start + 6:end]
     
        if preview:
            return self.preview(newcontent)

        self.copyObjectToArchive()
        self.WEB_project_description = newcontent[0:]
       
        #self.REQUEST.RESPONSE.redirect("./index.html")
        
        if RESPONSE is not None:
            return self.editDescription()
            

    def getBreadcrumbs(self):
        """return list of breadcrumbs from here to the root"""
        crumbs = []
        # skip direct parent Folder /projects/
        parent = aq_parent(aq_parent(self))
        # get parents breadcrumbs
        if hasattr(parent, 'getBreadcrumbs'):
            crumbs = parent.getBreadcrumbs()
        
        # try to get acquisition URL from parent
        if hasattr(parent, 'absolute_url'):
            baseUrl = "%s/%s" % (parent.absolute_url(), 'projects')
        else:
            baseUrl = "/en/research/projects"
            
        # add in the internal project hierarchy
        tree = self.getProjectTree()
        ap = tree.getAncestorsOf(self.getNumber())
        # start with grandparents
        for p in ap:
            label = shortenString(p.getLabel(), 13)
            crumbs.append({'text':label, 'url':p.getUrl(baseUrl=baseUrl), 'title':p.getLabel(), 'object':p})            

        # add this project
        crumbs.append({'text':self.getLabel(), 'url':self.getUrl(baseUrl=baseUrl), 'title':self.getLabel(), 'object':self})
            
        return crumbs

    # TODO: is this used?
    def preview(self, description):
        """preview"""
        # logging.debug("preview description=%s"%description)
        tmpPro = getattr(self, "previewTemplate", None)
        if not tmpPro:
            tmpPro = MPIWGProject("previewTemplate")
            self._setObject("previewTemplate", tmpPro)
        for field in definedFields:
            setattr(tmpPro, field, getattr(self, field))
        tmpPro.WEB_project_description = description[0:]
        tmpPro.invisible = True
        pt = PageTemplateFile('zpt/project/edit_preview_frame', globals()).__of__(self)
        return pt()
        

    def isResponsibleScientist(self, key):
        """teste ob eine Person in der Liste der respl. scientists auftaucht"""
        for resp in self.getResponsibleScientistsList():
            logging.debug("resp=%s key=%s"%(repr(resp),repr(key)))
            # TODO: we need to get ASCII keys!!
            if utf8ify(resp.get('key', '')).lower() == utf8ify(key).lower():
                return True
        
        return False
        
        
    def identifyNames(self, nameList):
        """Bekommt eine Komma oder Semikolon getrennte Liste mit Name der Form Vorname MittelName(n) Nachname
        und ordnet diese dann Mitarbeiter IDs zu.
        
        Returns a dict with full names as keys and a row of person objects from the database as values.
        
        @param nameList
        """
        if nameList.find(";") > -1:  # rate Trenner ist ;
            names = nameList.split(";")  
        else:
            names = nameList.split(",")
            
        # #nameList=nameList.replace(";",",") # falls ; als Trenner ersetze    
        returnNamesDict = {}
       
        for name in names:
            name = name.strip()
            if not name:
                continue
            nameSplitted = name.split(" ")
            if len(nameSplitted) > 1:  # vor und nachname angegeben)
                lastname = nameSplitted[-1]
                firstname = nameSplitted[0]
            else:
                firstname = ""
                lastname = nameSplitted[0]
               
            # finde Mitarbeiter mit den entsprechenden Name
            
            # firstname=self.myCapitalize(firstname).encode('utf-8')
            # lastname=self.myCapitalize(lastname).encode('utf-8')
            logging.debug("Search: %s %s %s" % (name, repr(firstname), repr(lastname)))
            try:
                # cataloggedNames=self.MembersCatalog(firstName=firstname,lastName=lastname)
                # TODO: I think this does not work without firstname
                # try to find names in members db by searching for sub-words
                cataloggedNames = self.executeZSQL("select * from personal_www where first_name ~* ('\m'||%s||'\M') and last_name ~* ('\m'||%s||'\M')", (firstname, lastname))
                if len(cataloggedNames) == 0:
                    # PostgreSQL has a bug with \m and words ending in non-ASCII :-(
                    cataloggedNames = self.executeZSQL("select * from personal_www where %s in (select regexp_split_to_table(lower(first_name), '\s+')) and %s in (select regexp_split_to_table(lower(last_name), '\s+'))", (firstname.lower(), lastname.lower()))
            except:
                cataloggedNames = []
                logging.error("ERROR: identifyNames %s %s" % sys.exc_info()[0:2])
            
            if len(cataloggedNames) > 0:
                returnNamesDict[name] = cataloggedNames
            else:
                returnNamesDict[name] = []
       
        logging.debug("identified names: %s" % repr(returnNamesDict))
        return returnNamesDict

        
    def editMPIWGProject(self, fromEdit=None, createNewVersion=True, RESPONSE=None):
        """edit the project and archive the old version"""
        logging.debug("editMPIWGProject(fromEdit=%s, createNewVersion=%s)"%(fromEdit,createNewVersion))
        if createNewVersion:
            self.copyObjectToArchive()  # archive the object

        formdata = self.REQUEST.form
        
        # set all definedFields
        for x in definedFields:
            if formdata.has_key(x):
                setattr(self, x, unicodify(formdata[x]))
                if x == 'xdata_05':
                    # changing project number invalidates project tree
                    self.resetProjectTree()

        completedAt = formdata.get('completedAt')
        if not self.setCompletedAt(completedAt):
            RESPONSE.redirect('./editMPIWGBasisEditor?error=dateWrong')
  
        startedAt = formdata.get('startedAt')
        if not self.setStartedAt(startedAt):
            RESPONSE.redirect('./editMPIWGBasisEditor?error=dateWrong')
        
        if self.REQUEST.has_key('active'):
            self.setActiveFlag(True)
        else:
            self.setActiveFlag(False)
        
        # make dict of responsible scientists
        checkedScientists = {}
        names = {}
        keys = {}
        for key in formdata: 
            # gehe durch das Formular
            keyParts = key.split("_")
            if keyParts[0] == "responsibleScientist": 
                # wenn es ein Feld der Form reponsibleScientist_nr_KEY gibt
                nr = keyParts[2]
                if keyParts[1] == "name":
                    names[nr] = unicodify(formdata[key])
                elif keyParts[1] == "key":
                    keys[nr] = formdata[key]
         
        for nr in names:
            name = names[nr]
            key = keys.get(nr, None)
            username = None
            if key:
                # get username from db
                member = self.en.getMPIWGRoot().getStaffFolder().getMember(key=key)
                if member is not None:
                    username = member.getUsername()
                    
            # schreibe keys und namen in die Liste
            checkedScientists[names[nr]] = {'name' : name, 'key' : key, 'username' : username}
             
        # update responsibleScientistsList
        self.setResponsibleScientistsList(checkedScientists)
        self.updateProjectMembers()
        
        if fromEdit and (RESPONSE is not None):
            return self.editBasic()
            
        if RESPONSE is not None:
            RESPONSE.redirect('manage_main')


    def getContent(self, field, filter=None):
        """Inhalt des Feldes"""
        val = getattr(self, field, '')
        if isinstance(val, list):
            val = val[0]
            
        return val


    def loadNewFile(self, RESPONSE=None):
        """einlesen des neuen files"""
        fileupload = self.REQUEST['fileupload']
        if fileupload:
            file_name = fileupload.filename
            filedata = fileupload.read()

            argv = xmlhelper.proj2hash(filedata)
            # print argv.keys()
            for arg in definedFields:
                
                # print arg,argv[arg],getattr(self,arg)
                try:
                    temp = argv[arg][0:]
                    # old=getattr(self,arg)
                    setattr(self, arg, temp)
                    # print old,getattr(self,arg)
                except:
                    """nothing"""
                    
        if RESPONSE is not None:
            RESPONSE.redirect('manage_main')


    def tagTheProject(self, RESPONSE=None):
        """TAG"""
        id = self.getId();
        tmpl = getattr(self.thesaurus, "main.html")
        if RESPONSE:
            RESPONSE.redirect("./thesaurus/main.html?project=" + id)
        return
    
    
    def moveObjectDigitallibraryToInfoBlock(self):
        """Move text from 'Object Digitallibrary' to InfoBlock."""
        text = self.getRelatedDigitalSources()
        if text:
            logging.debug("Moving 'Object Digitallibrary' to InfoBlock: %s"%repr(text))
            self.addInfoBlock(block_title='Related digital sources', item_text=text, item_link=None)
            delattr(self, 'xdata_11')
    
    
    def hasRelatedPublicationsOldVersion(self):
        """teste ob es related publications gibt"""        
        ret = True;
        if (getattr(self, 'WEB_related_pub', '') == ''):
            ret = False;  # nichts im alten feld
        logging.debug("webrel:" + repr(ret))
        if (getattr(self, 'WEB_related_pub_copied', False)):
            ret = False;  # alte daten sind schon kopiert worden
        
        logging.debug("webrel_copied:" + repr(ret))
        publications = self.ZopeFind(self, obj_metatypes=['MPIWGProject_publication']);
        
        if(len(publications) > 0):
            ret = False;  # es gibt publicationen in der neuen liste
              
        logging.debug("len(publ)" + repr(ret))
        
        return ret;


    def copyPublicationsToList(self, RESPONSE=None):
        """copy publications in to list"""
        publicationTxt = getattr(self, 'WEB_related_pub', '')
        if isinstance(publicationTxt, list):
            publicationTxt = publicationTxt[0]

        pubSplits = publicationTxt.split("<p>")

        for pubSplit in pubSplits:
            pubSplit = pubSplit.replace("</p>", "")
            self.addPublication(pubSplit)

        setattr(self, "WEB_related_pub_copied", True);
        
        if RESPONSE:
            self.redirect(RESPONSE, 'managePublications')
        

    def hasInlineImage(self):
        """Return the number of inline images in the description."""
        text = self.getDescription()
        cnt = text.count('<p class="picture">')
        return cnt
    
    
    def copyImageToMargin(self, RESPONSE=None):  
        """copy inline images to marginal images"""
        # getImages from WEB_project_description
        description = self.getDescription()
        
        text2 = description
        splitted = text2.split("""<p class="picture">""")
        
        imageURLs = []
        imageCaptions = []
        for split in splitted[1:]:
                tmp = split.split("</p>")
                # return repr(splitted[1])
                
                try:
                    imageURLs.append(tmp[0].split("\"")[1].encode('utf-8'))
                except:
                    
                    try:
                        imageURLs.append(tmp[0].split("src=")[1].split(" ")[0].encode('utf-8'))
                    except:
                        imageURLs.append("")
                
                split2 = "</p>".join(tmp[1:])
                splitted = split2.split("""<p class="picturetitle">""")
                if len(splitted) > 1:
                    tmp = splitted[1].split("</p>")
                    imageCaptions.append(tmp[0].encode('utf-8'))

                else:
                    # keine caption
                    imageCaptions.append("")
     
        # eintragen:
        for imageURL in imageURLs:
            if not imageURL:
                # no URL - no image
                continue
            
            filename = imageURL.split("/")[-1]
            # lege neues images object an, mit leerem bild

            if filename in self:
                # existiert das bild schon, dann neuen filenamen
                filename = "project_image_" + filename
                if filename in self:
                    # exists too - assume its already converted
                    logging.warn("copyImageToMargin: image %s exists - skipping!"%filename)
                    continue
            
            self.addImage(None, imageCaptions[imageURLs.index(imageURL)], filename=filename)
            # hole die bilddaten aus der url
            url = self.absolute_url() + "/" + imageURL
            # url=self.absolute_url()+"/"+filename
        
            try:  # relative url
                data = urllib.urlopen(url).read()
            except:
                try:  # absolute
                    data = urllib.urlopen(self.imageURL).read()
                except:
                    logging.error("copyImageToMargin: can't open: %s" % url)
            
            obj = getattr(self, filename)
            obj.update_data(data)
        
        # clean description
        logging.debug("copyImageToMargin: description:%s"%repr(description))
        dom = ET.fromstring(utf8ify("<html>%s</html>"%description))
        for e in dom.findall(".//p[@class='picture']"):
            # remove contents
            e.clear()
            # remove tag
            e.tag = None
            
        for e in dom.findall(".//p[@class='picturetitle']"):
            # remove contents
            e.clear()
            # remove tag
            e.tag = None
            
        # remove html tag
        dom.tag = None
        # set as new description
        description = unicodify(serialize(dom))
        logging.debug("copyImageToMargin: new description:%s"%repr(description))
        setattr(self, 'WEB_project_description', description)
        
        if RESPONSE:
            self.redirect(RESPONSE, 'manageImages')


    def scaleImage(self,REQUEST=None,RESPONSE=None):
        """scale the last image"""
        from PIL import Image;
        from cStringIO import  StringIO;
        import ImageFilter
        
        images = self.getImageList();
        if len(images)>0:
            img = images[-1] #nimmt das letze
            
            logging.debug(img)
            datStringIO = StringIO();
            """
            data=img.data
            if isinstance(data, str):
                    datStringIO.write(data)
            else:
                
                while data is not None:
                        datStringIO.write(data.data)
                        data=data.next

            """
            
            logging.debug(img.absolute_url())
            
            url = img.absolute_url()
            if not url.startswith("http"):
                url=REQUEST['URL0']+url
            ul = urllib.urlopen(url)
            datStringIO = StringIO(ul.read());
            
            
            try:
                pilImg = Image.open(datStringIO)
               
               
            except:
                 logging.error("scale image input:"+self.getId())
                 return
                
            w,h = pilImg.size
            logging.debug("oldsize: %s %s"%(w,h))
            targetW=120.
            targetH=75.
            
            if targetW == w and targetH == h:
                return
            facW=targetW/w 
            
            h1 = int(h*facW)
            
            if h1<targetH: #bild zu klein in h
                facH=targetH/h
                
                w1=int(w*facH)
                 
                fs = min(max(int(1/facH)+1,3),10)
                logging.debug(fs)
                if (1/facH) > 2:
                        pilImg = pilImg.filter(ImageFilter.BLUR)
             
                try:
                    pilImg = pilImg.filter(ImageFilter.MaxFilter(fs))
                except:
                    pilImg = pilImg.filter(ImageFilter.MaxFilter(fs-1))
               
                logging.debug("Xnew size: %s %s"%(w1,targetH))
                res = pilImg.resize((w1,int(targetH)),Image.ANTIALIAS);
                
                
            else:
                fs = min(max(int(1/facW)+1,3),10)
                logging.debug(fs)
                
                if (1/facW) > 2:
                    try:
                        pilImg = pilImg.filter(ImageFilter.BLUR)
                    except:
                        pass #some image types cannot be filter, just ignore
                    
                try:
                    pilImg = pilImg.filter(ImageFilter.MaxFilter(fs))
                except:
                    
                    
                    try:
                        pilImg = pilImg.filter(ImageFilter.MaxFilter(fs-1))
                    except:
                        pass
                logging.debug("ynew size: %s %s"%(targetW,h1))
                res = pilImg.resize((int(targetW),h1))
                
            nw,nh = res.size
               
            cutW1=int(nw/2-(targetW/2))  
            cutW2=int(nw/2+(targetW/2))   
            
            cutH1=int(nh/2-(targetH/2))   
            cutH2=int(nh/2+(targetH/2))    
            
            res2 = res.crop((cutW1,cutH1,cutW2,cutH2))
            
            outS =  StringIO()
          
            #res2.save("/tmp/"+self.getId()+".jpg")
           
            try:
                res2.save(outS,"JPEG")
            
                self.addImage(outS, None, RESPONSE, filename="thumb.jpg")
        
            except:
                logging.error("scale image:"+self.getId())        
            
        
    def updateProjectMembers(self, updateResponsibleScientistsList=False):
        """Update project-member table."""
        # projects are identified by id
        pid = self.getId()

        # clear projects_members table
        self.executeZSQL("delete from projects_members where project_id = %s", [pid])

        for m in self.getResponsibleScientistsList():
            memberKey = m.get('key', None)
            if not memberKey or not isinstance(memberKey, basestring):
                logging.error("updateProjectMembers: not a valid member key: %s" % repr(memberKey))
                continue
                        
            # fill projects_members table
            self.executeZSQL("insert into projects_members (project_id, member_key) values (%s, %s)", (pid, utf8ify(memberKey)))
    
    

        
def manage_addMPIWGProjectForm(self):
    """form for adding the project"""
    pt = PageTemplateFile('zpt/project/manage_add_MPIWGProject', globals()).__of__(self)
    return pt()

def manage_addMPIWGProject(self, id, fileupload=None, RESPONSE=None):
    """method to add a project"""
    id = id.strip()
    if fileupload:
        filedata = fileupload.read()
        argv = xmlhelper.proj2hash(filedata)
        # print argv
        newObj = MPIWGProject(id, argv)

    else:
        newObj = MPIWGProject(id)

    self._setObject(id, newObj)
    
    if RESPONSE is not None:
        url = '%s/%s/editBasic'%(self.absolute_url(), id) 
        RESPONSE.redirect(url)


class MPIWGProjectFolder(ZDBInterfaceFolder):
    """Folder of project objects"""

    meta_type = "MPIWGProjectFolder"
    security = ClassSecurityInfo()
    
    # cached HashTree with project hierarchy
    _v_projectTree = None
                    
    def getProjectTree(self):
        """Return the project hierarchy tree (and cache it).
        
        Returns HashTree instance."""
        tree = self._v_projectTree 
        if tree is None:
            tree = HashTree(keySeparator='.', keyFn=getInt)
            for p in self.objectValues(spec='MPIWGProject'):
                # add all projects
                logging.debug("add to project tree: %s, %s"%(repr(p.getNumber()),p))
                tree.add(p.getNumber(), p)
                
            self._v_projectTree = tree
            # logging.debug("getProjectTree: tree=%s"%(tree.root.getSubtreeAsText()))

        return tree
    
    
    def getProjectsAsList(self, start=None, active=1, archived=1, depthFirst=True):
        """Return flattened list of projects, starting from start.

        active = 0 : all projects
        active = 1 : active projects
        active = 2 : inactive projects
        archived = 0 : all projects
        archived = 1 : current projects
        archived = 2 : archived projects
        """
        # logging.debug("getProjectsAsList(start=%s,active=%s,archived=%s)"%(repr(start),active,archived))
        tree = self.getProjectTree()
        node = tree.getNode(start)
        if node is None:
            return []

        pl = node.getSubtreeAsList(depthFirst=depthFirst)
        #logging.debug("getProjectsAsList: tree=%s"%node.getSubtreeAsText())
        #logging.debug("getProjectsAsList: node=(%s,%s) pl=%s"%(node.key,node.value,repr(pl)))
        #logging.debug("getProjectsAsList: node=(%s,%s) pl=%s"%(node.key,node.value,[p.getNumber() for p in pl]))
        # return filtered list
        return [p for p in pl if (p.checkActive(active) and p.checkArchived(archived))]     
    

    def getProject(self, projectNumber=None):
        """Return the matching project"""
        tree = self.getProjectTree()
        if projectNumber is not None:
            return tree.get(projectNumber)
        
        return None

    
    def getProjectsOfMember(self, key, active=1, archived=1):
        """Return a list of all projects of a member.
    
        @param key: member's key
        active = 0 : all projects
        active = 1 : active projects
        active = 2 : inactive projects
        archived = 0 : all projects
        archived = 1 : current projects
        archived = 2 : archived projects
        """
        projects = []
        # search project numbers
        res = self.executeZSQL("select * from projects_members where member_key = %s", [key])
        # find projects in folder
        for r in res:
            p = self.get(r.project_id, None)
            # check if active
            if p is not None and p.checkActive(active) and p.checkArchived(archived):
                projects.append(p)

        # sort by project number            
        projects.sort(key=lambda p:[getInt(n) for n in p.getNumber().split('.')])
        return projects
        
        
    def getMembersWithProjects(self, onlyActive=True):
        """Return a list of all members that have projects.
        
        @param onlyActive: only active members
         
        Returns a list of member objects.
        """
        members = []
        sf = self.en.getStaffFolder()
        res = self.executeZSQL('select distinct member_key from projects_members')
        if onlyActive:
            for r in res:
                m = sf.getMember(key=r.member_key)
                if m is not None and m.isActive():
                    members.append(m)
                    
        else:
            # not only active
            members = [sf.getMember(key=r.member_key) for r in res]

        return members
        
        
    def resetProjectTree(self):
        """Reset the project tree."""
        self._v_projectTree = None
        
        
    security.declareProtected('View management screens', 'changeProjectTree')
    def changeProjectTree(self, RESPONSE=None):
        """change the complete tree"""
        form=self.REQUEST.form
        onlyArchived=int(form.get("onlyArchived",0))
        onlyActive=int(form.get("onlyActive",0))
        dep=form.get("dep",None)
        
        fields = self.getProjectsAsList(start=dep, archived=onlyArchived, active=onlyActive)
        
        logging.info("GOT TREE!----------------------------------------------------")
        for field in form.keys():
            
            splitted=field.split('_')
            if (len(splitted)>1) and (splitted[1]=="runningNumber"): 
                #feld hat die Form Nummer_name und runnignNumber
                nr=int(splitted[0]) # nummer des Datensatzes
                project = fields[nr]

                #
                # change active
                #            
                if form.has_key('%s_active'%nr): # active flag is set
                    project.setActiveFlag(True)
                else:
                    project.setActiveFlag(False)
                    
                #    
                # nummer hat sich geaendert
                #
                entryChanged = False;
                pronum = project.getNumber()  
                formnum = form['%s_number'%nr]
                
                if not (pronum == formnum):
                    logging.debug("Changed!Number+++++++++++++++++++++++++++++++++")
                    logging.debug(repr(fields[nr].xdata_05)+" ---> "+ repr(form[str(nr)+'_number']))
                    entryChanged = True
                    
                #
                # completed hat sich geaendert
                #
                td = project.transformDate # hole die funktion zum transformieren des datums
                formstarted = form[str(nr)+'_started']                           
                formcompleted = form[str(nr)+'_completed']  
                                         
                if not (td(project.getCompletedAt()) == td(formcompleted)):
                    logging.info(repr(td(project.getCompletedAt()))+" ---> "+ repr(td(form[str(nr)+'_completed'])))
                    logging.info("Changed!Completed+++++++++++++++++++++++++++++++++")
                    entryChanged = True
                
                if not (td(project.getStartedAt()) == td(formstarted)):
                    logging.info(repr(td(project.getStartedAt()))+" ---> "+ repr(td(form[str(nr)+'_started'])))
                    logging.info("Changed!Started+++++++++++++++++++++++++++++++++")
                    entryChanged = True
                
                if entryChanged:
                    logging.info("Changed!+++++++++++++++++++++++++++++++++")
                    project.copyObjectToArchive()
                    project.xdata_05 = formnum
                    project.setCompletedAt(formcompleted)
                    project.setStartedAt(formstarted)
                    # reset tree
                    self._v_projectTree = None
                
        if RESPONSE is not None:
            RESPONSE.redirect(self.en.MPIWGrootURL()+'/admin/showTree', status=303)


    security.declareProtected('View management screens', 'updateAllProjectMembers')
    def updateAllProjectMembers(self, updateResponsibleScientistsList=False):
        """Re-create responsibleScientistsLists and projects_members table from all current projects."""
        # empty table
        self.executeZSQL('truncate table projects_members')
        cnt = 0
        # go through all projects
        for p in self.objectValues(spec='MPIWGProject'):
            cnt += 1
            logging.debug("updateAllProjectMembers: updating project %s" % p)
            p.updateProjectMembers(updateResponsibleScientistsList=updateResponsibleScientistsList)

        return "updated %s projects!" % cnt


    security.declareProtected('View management screens', 'updateAllProjects')
    def updateAllProjects(self, updateResponsibleScientistsList=False, RESPONSE=None):
        """Patch all current projects for legacy problems."""
        cnt = 0
        fulllog = ""
        # go through all projects
        for id, project in self.ZopeFind(self, obj_metatypes=['MPIWGProject'], search_sub=1):
            log = ""
            cnt += 1
            #
            # hasRelatedPublicationsOldVersion
            #
            if project.hasRelatedPublicationsOldVersion():
                log += "%s: update relatedPublicationsOldVersion!\n"%project.getId()
                logging.debug("updateAllProjects(%s): update relatedPublicationsOldVersion!"%project.getId())
                project.copyPublicationsToList()
                                
            #
            # create responsibleScientistsList automatically
            #
            if updateResponsibleScientistsList:
                newScientists = {}
                names = project.identifyNames(project.getResponsibleScientists())
                for name in names:
                    msg = "%s: regenerating responsibleScientistsList: name=%s\n"%(project.getId(), repr(name))
                    log += msg
                    logging.debug(msg)
                    members = names[name]
                    if len(members) > 0:
                        # take the first matching name
                        username = None
                        if members[0].e_mail:
                            username = re.sub('@mpiwg-berlin\.mpg\.de', '', members[0].e_mail)
                        newScientists[name] = {'name': name, 'key' : members[0].key, 'username' : username}
                    
                project.setResponsibleScientistsList(newScientists)
                
            else:
                #
                # old format responsibleScientistsList
                #
                memberlist = project.getResponsibleScientistsList()
                if len(memberlist) > 0 and isinstance(memberlist[0], tuple):
                    log += "%s: updating memberlist!\n"%project.getId()
                    logging.debug("updateAllProjects(%s): updating memberlist" % project.getId())
                    newScientists = {}
                    for m in memberlist:
                        name = m[0]
                        key = m[1] 
                        username = None
                        if key:
                            if isinstance(key, list):
                                key = key[0]
                                
                            # get username from db
                            member = self.en.getMPIWGRoot().getStaffFolder().getMember(key=key)
                            if member is not None:
                                username = member.getUsername()
                                
                        newScientists[name] = {'name': name, 'key' : key, 'username' : username}
                        
                    # set new list
                    project.setResponsibleScientistsList(newScientists)

            #
            # old inline images
            #
            if project.hasInlineImage():
                log += "%s: has inlineImage!\n"%project.getId()
                logging.debug("updateAllProjects(%s): has inlineImage!"%project.getId())
                try:
                    project.copyImageToMargin()
                except Exception, e:
                    log += "%s: ERROR in copyImageToMargin!\n"%project.getId()
                    logging.debug("updateAllProjects(%s): ERROR in copyImageToMargin: %s"%(project.getId(), e))
                                    
            
            #
            # remove old attributes
            #
            if hasattr(project, 'definedFields'):
                log += "%s: has definedFields!\n"%project.getId()
                logging.debug("updateAllProjects(%s): has definedFields!"%project.getId())
                delattr(project, 'definedFields')
             
            #
            # update related publications
            #
            for pub in project.getPublicationList():
                if not pub.text:
                    msg = "%s: publication has no text: %s!\n"%(project.getId(), pub)
                    log += msg
                    logging.warn(msg)
                    if not pub.link:
                        msg = "%s: publication has no link either! Deleting: %s!\n"%(project.getId(), pub)
                        log += msg
                        logging.error(msg)
                        project.deletePublication(pub.getId())
                        
                else:
                    # hasLinkToBookPage updates the bookId
                    pub.hasLinkToBookPage()
                
                
            #
            # update RelatedDigitalSources
            #
            project.moveObjectDigitallibraryToInfoBlock()
            
            #
            # unicodify
            #
            for field in ('WEB_title', 'xdata_01', 'xdata_07', 'xdata_08', 'xdata_11', 'xdata_12', 'xdata_13', 
                      'WEB_project_description', 'WEB_related_pub'):
                text = getattr(project, field, None)
                if isinstance(text, str):
                    log += "%s: has non-unicode field %s\n"%(project.getId(), field)
                    logging.debug("updateAllProjects(%s): has has non-unicode field %s\n"%(project.getId(), field))
                    setattr(project, field, unicodify(text))
                
            fulllog += log    
            if RESPONSE is not None:
                RESPONSE.write(log)
                           
        #
        # remove obsolete static class members (does this make sense?)
        #
        if hasattr(MPIWGProject, 'responsibleScientistsList'):
            log += "MPIWGProject has class member responsibleScientistsList\n"
            logging.debug("MPIWGProject has class member responsibleScientistsList\n")
            del MPIWGProject.responsibleScientistsList
            
        if hasattr(MPIWGProject, 'projectThumb'):
            log += "MPIWGProject has class member projectThumb\n"
            logging.debug("MPIWGProject has class member projectThumb\n")
            del MPIWGProject.projectThumb
            
        log += "\n DONE! updated %s projects!" % cnt
        fulllog += log
        if RESPONSE is not None:
                RESPONSE.write(log)
                RESPONSE.flush()
        else:
            return fulllog
          
    def scaleImages(self,REQUEST=None):
        """scaleImages"""
        prjs = self.getProjectsAsList()
        for prj in prjs:
            prj.scaleImage(REQUEST=REQUEST)
            logging.debug(prj.getId())
        
        
def manage_addMPIWGProjectFolderForm(self):
    """form for adding a MPIWGProjectFolder"""
    pt = PageTemplateFile('zpt/project/manage_add_MPIWGProjectFolder', globals()).__of__(self)
    return pt()

def manage_addMPIWGProjectFolder(self, id, title, RESPONSE=None):
    """add a MPIWGProjectFolder"""
    newObj = MPIWGProjectFolder(id, title)

    self._setObject(id, newObj)

    if RESPONSE is not None:
        RESPONSE.redirect('manage_main')