Fix API regex for notes and attachments
[cds-indico.git] / indico / MaKaC / poster.py
blobb5b473e2922610a00d95c5a22e16be004b5c13d2
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 import os
18 from persistent import Persistent
19 import tempfile
21 from indico.util.json import loads
22 from MaKaC.common.Counter import Counter
23 from indico.core.config import Config
24 import conference
27 class PosterTemplateManager(Persistent):
28 """ This class is used to manage the poster templates
29 of a given conference.
30 An instance of this class contains the list of templates
31 of a conference. This conference is called the owner of the manager.
32 """
34 def __init__(self, conf):
35 """ Class constructor
36 conf: the conference who owns this manager
37 """
38 self.__conf = conf
39 self.__templates = {}
40 self.__counter = Counter(1)
41 self.__tempBackgrounds = {}
42 self.__tempBackgroundCounters = {}
44 def notifyModification(self):
45 self._p_changed = 1
47 def getTemplateById(self, templateId):
48 """
49 Returns a PosterTemplate object, given an id
50 """
51 return self.__templates[templateId]
53 def getTemplateData(self, templateId):
54 """
55 Returns the data (a list with all the information about a template)
56 of a template, directly.
57 """
58 return self.__templates[templateId].getData()
60 def getTemplates(self):
61 """ Returns a dictionary of (templateId, PosterTemplate) keys and values
62 """
63 return self.__templates
65 def hasTemplate(self, templateId):
66 """ Tests if there is a template stored with the given templateId
67 """
68 return self.__templates.has_key(templateId)
70 def getNewTemplateId(self):
72 """ Returns a new an unused templateId
73 Increments the templateId counter
74 """
75 return self.__counter.newCount()
77 def storeTemplate(self, templateId, templateData):
78 """
79 Adds a template to the conference.
80 templateData is a string produced by converting the object "template" of the save() javascript
81 function of WConfModifPosterDesign.tpl into a JSON string.
82 The string templateData is a list composed of:
83 -The name of the template
84 -A dictionary with 2 keys: width and height of the template, in pixels.
85 -A number which is the number of pixels per cm. It is defined in WConfModifPosterDesign.tpl. Right now its value is 50.
86 -A list of dictionaries. Each dictionary has the attributes of one of the items of the template.
87 If the template had any temporary backgrounds, they are archived.
88 """
89 if self.__templates.has_key(templateId):
90 # template already exists
91 self.__templates[templateId].setData(loads(templateData))
92 self.__templates[templateId].archiveTempBackgrounds(self.__conf)
93 else:
94 # template does not exist
95 self.__templates[templateId] = PosterTemplate(templateId, loads(templateData))
96 self.notifyModification()
98 def addTemplate(self, templ, templateId):
99 if self.__templates.has_key(templateId):
100 return None
101 else:
102 self.__templates[templateId] = templ
103 return templ
105 def deleteTemplate(self, templateId):
106 """ Deletes a template, if it exists (otherwise it does nothing)
107 Also deletes its backgrounds.
109 if self.__templates.has_key(templateId):
110 self.__templates[templateId].deleteBackgrounds()
111 del(self.__templates[templateId])
112 self.notifyModification()
114 def copyTemplate(self, templateId):
115 """Duplicates a template"""
116 if self.__templates.has_key(templateId):
117 srcTempl = self.getTemplateById(templateId)
118 destTempl = srcTempl.clone(self)
119 tplData = destTempl.getData()
120 tplData[0] += " (copy)"
121 destTempl.setData(tplData)
123 def getOwner(self):
124 return self.__conf
126 def getNewTempFile( ):
127 cfg = Config.getInstance()
128 tempPath = cfg.getUploadedFilesTempDir()
129 tempFileName = tempfile.mkstemp( suffix="IndicoPoster.tmp", dir = tempPath )[1]
130 return tempFileName
132 def saveFileToTemp( fd ):
133 fileName = getNewTempFile()
134 f = open( fileName, "wb" )
135 f.write( fd.read() )
136 f.close()
137 return fileName
139 class PosterTemplate (Persistent):
140 """ This class represents a poster template, which
141 will be used to print posters.
144 def __init__(self, id, templateData):
145 """ Class Constructor
146 templateData is the templateData string used in the method storeTemplate() of the class
147 PosterTemplateManager, transformed to a Python object with the function loads().
148 IMPORTANT NOTE: loads() builds an objet with unicode objects inside.
149 if these objects are then concatenated to str objects (for example in an Indico HTML template),
150 this can give problems. In those cases transform the unicode object to str with .encode('utf-8')
151 Thus, its structure is a list composed of:
152 -The name of the template
153 -A dictionary with 2 keys: width and height of the template, in pixels.
154 -A number which is the number of pixels per cm. It is defined in WConfModifPosterDesign.tpl. Right now its value is 50.
155 -The index of the background used in the template, among the several backgrounds of the template. -1 if none
156 -A list of dictionaries. Each dictionary has the attributes of one of the items of the template.
159 self.__id = id
160 self.__templateData = templateData
161 self.__cleanData()
162 self.__backgroundCounter = Counter() #for the backgrounds (in the future there may be more than 1 background stored per template)
163 self.__backgrounds = {} #dictionary with the archived backgrounds(key: id, value: LocalFile object)
164 self.__tempBackgroundsFilePaths = {} #dictionary with the temporary, not archived yet backgrounds (key: id, value: filepath string)
165 self.__bgPositions = {} #dictionary with the background positioning, for each of them (key: id, value: String ('Center','Stretch'))
166 self.notifyModification()
168 def clone(self, templMan, templId=None):
169 if templId == None:
170 templId = templMan.getNewTemplateId()
171 templData = self.getData()[:]
172 templData[3] = -1
173 newTempl = PosterTemplate(templId, templData)
174 templMan.addTemplate(newTempl, templId)
175 if self.getBackground(self.getUsedBackgroundId())[1] != None:
176 templData[3] = 0
177 position = self.getBackgroundPosition(self.getUsedBackgroundId())
178 fpath = self.getBackground(self.getUsedBackgroundId())[1].getFilePath()
179 # make a copy of the original file
180 newPath = saveFileToTemp(open(fpath,"r"))
181 newTempl.addTempBackgroundFilePath(newPath,position)
182 newTempl.archiveTempBackgrounds(templMan.getOwner())
183 templMan.notifyModification()
184 return newTempl
186 def notifyModification(self):
187 self._p_changed = 1
189 def getData(self):
190 """ Returns the list with all the information of the template.
191 Useful so that javascript can analyze it on its own.
194 # ensure that each item's got a key (in order to avoid
195 # using the name as key).
196 for item in self.__templateData[4]:
197 if not "key" in item:
198 item['key'] = item['name']
199 ##############################
200 return self.__templateData
202 def setData(self, templateData):
203 """ Sets the data of the template
205 self.__templateData = templateData
206 self.__cleanData()
207 self.notifyModification()
209 def getName(self):
210 """ Returns the name of the template
212 return self.__templateData[0].encode('utf-8')
215 def getWidth(self):
216 """ Returns the width of the template, in pixels
218 return self.__templateData[1]["width"]
221 def getHeight(self):
222 """ Returns the height of the template, in pixels
224 return self.__templateData[1]["height"]
227 def getPixelsPerCm(self):
228 """ Returns the ratio pixels / cm of the template.
229 This ratio is defined in the HTML template. Right now its value should be 50.
231 return self.__templateData[2]
233 def getItems(self):
234 """ Returns a list of object of the class PosterTemplateItem with
235 all the items of a template.
237 return [PosterTemplateItem(itemData, self) for itemData in self.__templateData[4]]
239 def getItem(self, name):
240 """ Returns an object of the class PosterTemplateItem
241 which corresponds to the item whose name is 'name'
243 return PosterTemplateItem(filter(lambda item: item['name'] == name, self.__templateData[4])[0])
245 def pixelsToCm(self, length):
246 """ Transforms a length in pixels to a length in cm.
247 Uses the pixelsPerCm value stored in the template
249 return float(length) / self.__templateData[2]
251 def getWidthInCm(self):
252 """ Returns the width of the template in cm
254 return self.pixelsToCm(self.__templateData[1]["width"])
256 def getHeightInCm(self):
257 """ Returns the height of the template in cm
259 return self.pixelsToCm(self.__templateData[1]["height"])
261 def getAllBackgrounds(self):
262 """ Returns the list of stored background
263 Each background is a LocalFile object
265 return self.__backgrounds
267 def getUsedBackgroundId(self):
268 """ Returns the id of the currently used background
269 This id corresponds to a stored, archived background
271 return int(self.__templateData[3])
273 def getBackground(self, backgroundId):
274 """ Returns a tuple made of:
275 -a boolean
276 -a background based on an id
277 There are 3 possibilities:
278 -the background has already been archived. Then the boolean value is True,
279 and the background is a LocalFile object,
280 -the background is still temporary and has not been archived yet. Then the
281 boolean is False and the background returned is a string with the file path
282 to the temporary file.
283 -there is no background with such id. Then the method returns (None, None)
286 if self.__backgrounds.has_key(backgroundId):
287 return True, self.__backgrounds[backgroundId]
288 if self.__tempBackgroundsFilePaths.has_key(backgroundId):
289 return False, self.__tempBackgroundsFilePaths[backgroundId][0]
290 return None, None
292 def getBackgroundPosition(self, backgroundId):
293 if self.__bgPositions.has_key(backgroundId):
294 return self.__bgPositions[backgroundId]
295 elif self.__tempBackgroundsFilePaths.has_key(backgroundId):
296 return self.__tempBackgroundsFilePaths[backgroundId][1]
297 else:
298 return None
300 def addTempBackgroundFilePath(self, filePath, position):
301 """ Adds a filePath of a temporary background to the dictionary of temporary backgrounds
302 and registers its positioning.
304 backgroundId = int(self.__backgroundCounter.newCount())
305 self.__tempBackgroundsFilePaths[backgroundId] = (filePath,position)
306 self.notifyModification()
307 return backgroundId
309 def archiveTempBackgrounds(self, conf):
310 """ Archives all the temporary backgrounds of this template.
311 This method archives all of the temporary backgrounds of this template, which are
312 stored in the form of filepath strings, in the __tempBackgroundsFilePaths dictionary,
313 to a dictionary which stores LocalFile objects. The ids are copied, since there is a
314 shared id counter for both dictionaries.
315 After the archiving, the __tempBackgroundsFilePaths dictionary is reset to {}
318 for backgroundId, (filePath, bgPosition) in self.__tempBackgroundsFilePaths.iteritems():
319 cfg = Config.getInstance()
320 tempPath = cfg.getUploadedFilesSharedTempDir()
321 filePath = os.path.join(tempPath, filePath)
322 fileName = "background" + str(backgroundId) + "_t" + self.__id + "_c" + conf.id
324 file = conference.LocalFile()
325 file.setName( fileName )
326 file.setDescription( "Background " + str(backgroundId) + " of the template " + self.__id + " of the conference " + conf.id )
327 file.setFileName( fileName )
328 file.setFilePath( filePath )
330 file.setOwner( conf )
331 file.setId( fileName )
332 file.archive( conf._getRepository() )
334 self.__backgrounds[backgroundId] = file
335 self.__bgPositions[backgroundId] = bgPosition
337 self.notifyModification()
338 self.__tempBackgroundsFilePaths = {}
340 def deleteTempBackgrounds(self):
341 """ Deletes all the temporary backgrounds of this template
343 self.__tempBackgroundsFilePaths = {}
345 def deleteBackgrounds(self):
346 """ Deletes all of the template archived backgrounds.
347 To be used when a template is deleted.
349 for localFile in self.__backgrounds.values():
350 localFile.delete()
352 def __cleanData(self):
353 """ Private method which cleans the list passed by the javascript in WConfModifPosterDesign.tpl,
354 so that it can be properly used later.
355 The following actions are taken:
356 -When an item is erased while creating or editing a template, the item object is substitued
357 by a "False" value. We have to remove these "False" values from the list.
358 -When an item is moved, the coordinates of the item are stored for example like this: 'x':'124px', 'y':'45px'.
359 We have to remove that 'px' at the end.
361 self.__templateData[4] = filter ( lambda item: item != False, self.__templateData[4]) # to remove items that have been deleted
362 for item in self.__templateData[4]:
363 if isinstance(item['x'],basestring) and item['x'][-2:] == 'px':
364 item['x'] = item['x'][0:-2]
365 if isinstance(item['y'],basestring) and item['y'][-2:] == 'px':
366 item['y'] = item['y'][0:-2]
369 class PosterTemplateItem:
370 """ This class represents one of the items of a poster template
371 It is not stored in the database, just used for convenience access methods.
374 def __init__(self, itemData, posterTemplate):
375 """ Constructor
376 -itemData must be a dictionary with the attributes of the item
377 Example:
378 'fontFamilyIndex': 0, 'styleIndex': 1, 'bold': True, 'key': 'Country', 'fontFamily': 'Arial',
379 'color': 'blue', 'selected': false, 'fontSizeIndex': 5, 'id': 0, 'width': 250, 'italic': False,
380 'fontSize': 'x-large', 'textAlignIndex': 1, 'y': 40, 'x': 210, 'textAlign': 'Right',
381 'colorIndex': 2}
382 The several 'index' attributes and the 'selected' attribute can be ignored, they are client-side only.
383 -posterTemplate is the posterTemplate which owns this item.
385 ###TODO:
386 ### fontFamilyIndex and fontFamily are linked. So, if fontFamily changes, fontFamilyIndex must be changed
387 ### as well. This is done in the client. Howerver, it needs to be improved because if we need to change
388 ## fontFamily in the server (for example with a script) or we add a new font, the indexes will not be
389 ## synchronized anymore.
390 self.__itemData = itemData
391 self.__posterTemplate = posterTemplate
393 def getKey(self):
394 """ Returns the key of the item.
395 The key of an item idientifies the kind of item it is: "Name", "Country", "Fixed Text"...
397 if "key" in self.__itemData:
398 return self.__itemData['key']
399 else:
400 return self.__itemData['name']
402 def getFixedText(self):
403 """ Returns the text content of a Fixed Text item.
404 To be used only on items whose name is "Fixed Text"
406 return self.__itemData['text']
408 def getX(self):
409 """ Returns the x coordinate of the item, in pixels.
411 return self.__itemData['x']
413 def getXInCm(self):
414 """ Returns the x coordinate of the item, in cm.
416 return self.__posterTemplate.pixelsToCm(self.getX())
418 def getY(self):
419 """ Returns the y coordinate of the item, in pixels.
421 return self.__itemData['y']
423 def getYInCm(self):
424 """ Returns the y coordinate of the item, in cm.
426 return self.__posterTemplate.pixelsToCm(self.getY())
428 def getFont(self):
429 """ Returns the name of the font used by this item.
431 return self.__itemData['fontFamily']
433 def getFontSize(self):
434 """ Returns the font size used by this item.
435 Actual possible values are: 'xx-small', 'x-small', 'small', 'normal', 'large', 'x-large', 'xx-large'
436 They each correspond to one of the 7 HTML sizes.
438 return self.__itemData['fontSize']
440 def getColor(self):
441 """ Returns the color used by the item, as a string.
443 return self.__itemData['color']
445 def getWidth(self):
446 """ Returns the width of the item, in pixels.
448 return self.__itemData['width']
450 def getWidthInCm(self):
451 """ Returns the width of the item, in cm.
453 return self.__posterTemplate.pixelsToCm(self.getWidth())
455 def isBold(self):
456 """ Checks of the item is bold (returns a boolean)
458 return self.__itemData['bold']
460 def isItalic(self):
461 """ Checks of the item is italic (returns a boolean)
463 return self.__itemData['italic']
465 def getTextAlign(self):
466 """ Returns the text alignment of the item, as a string.
467 Actual possible values: 'Left', 'Right', 'Center', 'Justified'
469 return self.__itemData['textAlign']