Fix API regex for notes and attachments
[cds-indico.git] / indico / MaKaC / badge.py
blob0506329901ac403c9fe240e1835bb437b09c883b
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
26 class BadgeTemplateManager(Persistent):
27 """ This class is used to manage the badge templates
28 of a given conference.
29 An instance of this class contains the list of templates
30 of a conference. This conference is called the owner of the manager.
31 """
33 def __init__(self, conf):
34 """ Class constructor
35 conf: the conference who owns this manager
36 """
37 self.__conf = conf
38 self.__templates = {}
39 self.__counter = Counter(1)
40 self.__tempBackgrounds = {}
41 self.__tempBackgroundCounters = {}
42 self._PDFOptions = BadgePDFOptions(conf)
45 def notifyModification(self):
46 self._p_changed = 1
48 def getTemplateById(self, templateId):
49 """
50 Returns a BadgeTemplate object, given an id
51 """
52 return self.__templates[templateId]
54 def getTemplateData(self, templateId):
55 """
56 Returns the data (a list with all the information about a template)
57 of a template, directly.
58 """
59 return self.__templates[templateId].getData()
61 def getTemplates(self):
62 """ Returns a dictionary of (templateId, BadgeTemplate) keys and values
63 """
64 return self.__templates
66 def hasTemplate(self, templateId):
67 """ Tests if there is a template stored with the given templateId
68 """
69 return self.__templates.has_key(templateId)
71 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 WConfModifBadgeDesign.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 WConfModifBadgeDesign.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 self.__templates[templateId].setData(loads(templateData))
91 self.__templates[templateId].archiveTempBackgrounds(self.__conf)
92 else:
93 self.__templates[templateId] = BadgeTemplate(templateId, loads(templateData))
95 self.notifyModification()
97 def addTemplate(self, templ, templateId):
98 if self.__templates.has_key(templateId):
99 return None
100 else:
101 self.__templates[templateId] = templ
102 return templ
104 def deleteTemplate(self, templateId):
105 """ Deletes a template, if it exists (otherwise it does nothing)
106 Also deletes its backgrounds.
108 if self.__templates.has_key(templateId):
109 self.__templates[templateId].deleteBackgrounds()
110 del(self.__templates[templateId])
111 self.notifyModification()
113 def copyTemplate(self, templateId):
114 """Duplicates a template"""
115 if self.__templates.has_key(templateId):
116 srcTempl = self.getTemplateById(templateId)
117 destTempl = srcTempl.clone(self)
118 tplData = destTempl.getData()
119 tplData[0] += " (copy)"
120 destTempl.setData(tplData)
122 def getPDFOptions(self):
123 if not hasattr(self, "_PDFOptions"):
124 self._PDFOptions = BadgePDFOptions(self.__conf)
125 return self._PDFOptions
127 def getOwner(self):
128 return self.__conf
131 def getNewTempFile( ):
132 cfg = Config.getInstance()
133 tempPath = cfg.getUploadedFilesTempDir()
134 tempFileName = tempfile.mkstemp( suffix="IndicoBadge.tmp", dir = tempPath )[1]
135 return tempFileName
137 def saveFileToTemp( fd ):
138 fileName = getNewTempFile()
139 f = open( fileName, "wb" )
140 f.write( fd.read() )
141 f.close()
142 return fileName
144 class BadgeTemplate (Persistent):
145 """ This class represents a badge template, which
146 will be used to print badges.
149 def __init__(self, id, templateData):
150 """ Class Constructor
151 templateData is the templateData string used in the method storeTemplate() of the class
152 BadgeTemplateManager, transformed to a Python object with the function loads().
153 IMPORTANT NOTE: loads() builds an objet with unicode objects inside.
154 if these objects are then concatenated to str objects (for example in an Indico HTML template),
155 this can give problems. In those cases transform the unicode object to str with .encode('utf-8').
156 Thus, its structure is a list composed of:
157 -The name of the template
158 -A dictionary with 2 keys: width and height of the template, in pixels.
159 -A number which is the number of pixels per cm. It is defined in ConfModifBadgeDesign.tpl. Right now its value is 50.
160 -The index of the background used in the template, among the several backgrounds of the template. -1 if none
161 -A list of dictionaries. Each dictionary has the attributes of one of the items of the template.
164 self.__id = id
165 self.__templateData = templateData
166 self.__cleanData()
167 self.__backgroundCounter = Counter() #for the backgrounds (in the future there may be more than 1 background stored per template)
168 self.__backgrounds = {} #dictionary with the archived backgrounds(key: id, value: LocalFile object)
169 self.__tempBackgroundsFilePaths = {} #dictionary with the temporary, not archived yet backgrounds (key: id, value: filepath string)
170 self.notifyModification()
172 def clone(self, templMan, templId=None):
173 if templId == None:
174 templId = templMan.getNewTemplateId()
175 templData = self.getData()[:]
176 templData[3] = -1
177 newTempl = BadgeTemplate(templId, templData)
178 templMan.addTemplate(newTempl, templId)
179 if self.getBackground(self.getUsedBackgroundId())[1] != None:
180 templData[3] = 0
181 fpath = self.getBackground(self.getUsedBackgroundId())[1].getFilePath()
182 # make a copy of the original file
183 newPath = saveFileToTemp(open(fpath,"r"))
184 newTempl.addTempBackgroundFilePath(newPath)
185 newTempl.archiveTempBackgrounds(templMan.getOwner())
186 templMan.notifyModification()
187 return newTempl
189 def notifyModification(self):
190 self._p_changed = 1
192 def getData(self):
193 """ Returns the list with all the information of the template.
194 Useful so that javascript can analyze it on its own.
197 # ensure that each item's got a key (in order to avoid
198 # using the name as key).
199 for item in self.__templateData[4]:
200 if not "key" in item:
201 item['key'] = item['name']
202 ##############################
204 return self.__templateData
206 def setData(self, templateData):
207 """ Sets the data of the template
209 self.__templateData = templateData
210 self.__cleanData()
211 self.notifyModification()
214 def getName(self):
215 """ Returns the name of the template
217 return self.__templateData[0].encode('utf-8')
220 def getWidth(self):
221 """ Returns the width of the template, in pixels
223 return self.__templateData[1]["width"]
226 def getHeight(self):
227 """ Returns the height of the template, in pixels
229 return self.__templateData[1]["height"]
232 def getPixelsPerCm(self):
233 """ Returns the ratio pixels / cm of the template.
234 This ratio is defined in the HTML template. Right now its value should be 50.
236 return self.__templateData[2]
238 def getItems(self):
239 """ Returns a list of object of the class BadgeTemplateItem with
240 all the items of a template.
242 return [BadgeTemplateItem(itemData, self) for itemData in self.__templateData[4]]
244 def getItem(self, name):
245 """ Returns an object of the class BadgeTemplateItem
246 which corresponds to the item whose name is 'name'
248 return BadgeTemplateItem(filter(lambda item: item['name'] == name, self.__templateData[4])[0])
250 def pixelsToCm(self, length):
251 """ Transforms a length in pixels to a length in cm.
252 Uses the pixelsPerCm value stored in the template
254 return float(length) / self.__templateData[2]
256 def getWidthInCm(self):
257 """ Returns the width of the template in cm
259 return self.pixelsToCm(self.__templateData[1]["width"])
261 def getHeightInCm(self):
262 """ Returns the height of the template in cm
264 return self.pixelsToCm(self.__templateData[1]["height"])
266 def getAllBackgrounds(self):
267 """ Returns the list of stored background
268 Each background is a LocalFile object
270 return self.__backgrounds
272 def getUsedBackgroundId(self):
273 """ Returns the id of the currently used background
274 This id corresponds to a stored, archived background
276 return int(self.__templateData[3])
278 def getBackground(self, backgroundId):
279 """ Returns a tuple made of:
280 -a boolean
281 -a background based on an id
282 There are 3 possibilities:
283 -the background has already been archived. Then the boolean value is True,
284 and the background is a LocalFile object,
285 -the background is still temporary and has not been archived yet. Then the
286 boolean is False and the background returned is a string with the file path
287 to the temporary file.
288 -there is no background with such id. Then the method returns (None, None)
290 if self.__backgrounds.has_key(backgroundId):
291 return True, self.__backgrounds[backgroundId]
292 if self.__tempBackgroundsFilePaths.has_key(backgroundId):
293 return False, self.__tempBackgroundsFilePaths[backgroundId]
294 return None, None
296 def addTempBackgroundFilePath(self, filePath):
297 """ Adds a filePath of a temporary background to the dictionary of temporary backgrounds.
299 backgroundId = int(self.__backgroundCounter.newCount())
300 self.__tempBackgroundsFilePaths[backgroundId] = filePath
301 self.notifyModification()
302 return backgroundId
304 def archiveTempBackgrounds(self, conf):
305 """ Archives all the temporary backgrounds of this template.
306 This method archives all of the temporary backgrounds of this template, which are
307 stored in the form of filepath strings, in the __tempBackgroundsFilePaths dictionary,
308 to a dictionary which stores LocalFile objects. The ids are copied, since there is a
309 shared id counter for both dictionaries.
310 After the archiving, the __tempBackgroundsFilePaths dictionary is reset to {}
314 for backgroundId, filePath in self.__tempBackgroundsFilePaths.iteritems():
315 cfg = Config.getInstance()
316 tempPath = cfg.getUploadedFilesSharedTempDir()
317 filePath = os.path.join(tempPath, filePath)
318 fileName = "background" + str(backgroundId) + "_t" + self.__id + "_c" + conf.id
320 from MaKaC.conference import LocalFile
321 file = LocalFile()
322 file.setName( fileName )
323 file.setDescription( "Background " + str(backgroundId) + " of the template " + self.__id + " of the conference " + conf.id )
324 file.setFileName( fileName )
326 file.setFilePath( filePath )
327 file.setOwner( conf )
328 file.setId( fileName )
329 file.archive( conf._getRepository() )
330 self.__backgrounds[backgroundId] = file
332 self.notifyModification()
333 self.__tempBackgroundsFilePaths = {}
335 def deleteTempBackgrounds(self):
336 """ Deletes all the temporary backgrounds of this template
338 self.__tempBackgroundsFilePaths = {}
340 def deleteBackgrounds(self):
341 """ Deletes all of the template archived backgrounds.
342 To be used when a template is deleted.
344 for localFile in self.__backgrounds.values():
345 localFile.delete()
347 def __cleanData(self):
348 """ Private method which cleans the list passed by the javascript in WConfModifBadgeDesign.tpl,
349 so that it can be properly used later.
350 The following actions are taken:
351 -When an item is erased while creating or editing a template, the item object is substitued
352 by a "False" value. We have to remove these "False" values from the list.
353 -When an item is moved, the coordinates of the item are stored for example like this: 'x':'124px', 'y':'45px'.
354 We have to remove that 'px' at the end.
356 self.__templateData[4] = filter ( lambda item: item != False, self.__templateData[4]) # to remove items that have been deleted
357 for item in self.__templateData[4]:
358 if isinstance(item['x'],basestring) and item['x'][-2:] == 'px':
359 item['x'] = item['x'][0:-2]
360 if isinstance(item['y'],basestring) and item['y'][-2:] == 'px':
361 item['y'] = item['y'][0:-2]
364 class BadgeTemplateItem:
365 """ This class represents one of the items of a badge template
366 It is not stored in the database, just used for convenience access methods.
369 def __init__(self, itemData, badgeTemplate):
370 """ Constructor
371 -itemData must be a dictionary with the attributes of the item
372 Example:
373 {'fontFamilyIndex': 0, 'styleIndex': 1, 'bold': True, 'key': 'Country', 'fontFamily': 'Arial',
374 'color': 'blue', 'selected': false, 'fontSizeIndex': 5, 'id': 0, 'width': 250, 'italic': False,
375 'fontSize': 'x-large', 'textAlignIndex': 1, 'y': 40, 'x': 210, 'textAlign': 'Right',
376 'colorIndex': 2}
377 The several 'index' attributes and the 'selected' attribute can be ignored, they are client-side only.
378 -badgeTemplate is the badgeTemplate which owns this item.
380 ###TODO:
381 ### fontFamilyIndex and fontFamily are linked. So, if fontFamily changes, fontFamilyIndex must be changed
382 ### as well. This is done in the client. Howerver, it needs to be improved because if we need to change
383 ## fontFamily in the server (for example with a script) or we add a new font, the indexes will not be
384 ## synchronized anymore.
385 self.__itemData = itemData
386 self.__badgeTemplate = badgeTemplate
388 def getKey(self):
389 """ Returns the key of the item (non-translated name).
390 The name of an item idientifies the kind of item it is: "Name", "Country", "Fixed Text"...
392 if "key" in self.__itemData:
393 return self.__itemData['key']
394 else:
395 return self.__itemData['name']
397 def getFixedText(self):
398 """ Returns the text content of a Fixed Text item.
399 To be used only on items whose name is "Fixed Text"
401 return self.__itemData['text']
403 def getX(self):
404 """ Returns the x coordinate of the item, in pixels.
406 return self.__itemData['x']
408 def getXInCm(self):
409 """ Returns the x coordinate of the item, in cm.
411 return self.__badgeTemplate.pixelsToCm(self.getX())
413 def getY(self):
414 """ Returns the y coordinate of the item, in pixels.
416 return self.__itemData['y']
418 def getYInCm(self):
419 """ Returns the y coordinate of the item, in cm.
421 return self.__badgeTemplate.pixelsToCm(self.getY())
423 def getFont(self):
424 """ Returns the name of the font used by this item.
426 return self.__itemData['fontFamily']
428 def getFontSize(self):
429 """ Returns the font size used by this item.
430 Actual possible values are: 'xx-small', 'x-small', 'small', 'normal', 'large', 'x-large', 'xx-large'
431 They each correspond to one of the 7 HTML sizes.
433 return self.__itemData['fontSize']
435 def getColor(self):
436 """ Returns the color used by the item, as a string.
438 return self.__itemData['color']
440 def getWidth(self):
441 """ Returns the width of the item, in pixels.
443 return self.__itemData['width']
445 def getWidthInCm(self):
446 """ Returns the width of the item, in cm.
448 return self.__badgeTemplate.pixelsToCm(self.getWidth())
450 def isBold(self):
451 """ Checks of the item is bold (returns a boolean)
453 return self.__itemData['bold']
455 def isItalic(self):
456 """ Checks of the item is italic (returns a boolean)
458 return self.__itemData['italic']
460 def getTextAlign(self):
461 """ Returns the text alignment of the item, as a string.
462 Actual possible values: 'Left', 'Right', 'Center', 'Justified'
464 return self.__itemData['textAlign']
466 class BadgePDFOptions(Persistent):
467 """ This class stores the badge PDF options for a conference.
468 Badge PDF options include, for now, the page margins and margins between badges.
469 The default values are CERN's defaults in cm.
472 def __init__(self, conference):
473 if conference.getId() == "default":
474 #Best values for CERN printing service
475 self.__topMargin = 1.6
476 self.__bottomMargin = 1.1
477 self.__leftMargin = 1.6
478 self.__rightMargin = 1.4
479 self.__marginColumns = 1.0
480 self.__marginRows = 0.0
481 self._pageSize = "A4"
482 self._landscape = False
483 self._drawDashedRectangles = True
484 else:
485 from MaKaC.conference import CategoryManager
486 defaultConferencePDFOptions = CategoryManager().getDefaultConference().getBadgeTemplateManager().getPDFOptions()
487 self.__topMargin = defaultConferencePDFOptions.getTopMargin()
488 self.__bottomMargin = defaultConferencePDFOptions.getBottomMargin()
489 self.__leftMargin = defaultConferencePDFOptions.getLeftMargin()
490 self.__rightMargin = defaultConferencePDFOptions.getRightMargin()
491 self.__marginColumns = defaultConferencePDFOptions.getMarginColumns()
492 self.__marginRows = defaultConferencePDFOptions.getMarginRows()
493 self._pageSize = defaultConferencePDFOptions.getPagesize()
494 self._landscape = defaultConferencePDFOptions.getLandscape()
495 self._drawDashedRectangles = defaultConferencePDFOptions.getDrawDashedRectangles()
499 def getTopMargin(self):
500 return self.__topMargin
502 def getBottomMargin(self):
503 return self.__bottomMargin
505 def getLeftMargin(self):
506 return self.__leftMargin
508 def getRightMargin(self):
509 return self.__rightMargin
511 def getMarginColumns(self):
512 return self.__marginColumns
514 def getMarginRows(self):
515 return self.__marginRows
517 def getPagesize(self):
518 if not hasattr(self, "_pageSize"):
519 self._pageSize = "A4"
520 return self._pageSize
522 def getDrawDashedRectangles(self):
523 """ Returns if we should draw a dashed rectangle around each badge or not.
524 Will return a Boolean
526 if not hasattr(self, "_drawDashedRectangles"):
527 self._drawDashedRectangles = True
528 return self._drawDashedRectangles
530 def getLandscape(self):
531 try:
532 return self._landscape
533 except AttributeError:
534 self._landscape = False
535 return False
537 def setTopMargin(self, value):
538 self.__topMargin = value
540 def setBottomMargin(self, value):
541 self.__bottomMargin = value
543 def setLeftMargin(self, value):
544 self.__leftMargin = value
546 def setRightMargin(self, value):
547 self.__rightMargin = value
549 def setMarginColumns(self, value):
550 self.__marginColumns = value
552 def setMarginRows(self, value):
553 self.__marginRows = value
555 def setPagesize(self, value):
556 self._pageSize = value
558 def setDrawDashedRectangles(self, value):
559 """ Sets if we should draw a dashed rectangle around each badge or not.
560 value must be a Boolean
562 self._drawDashedRectangles = value
564 def setLandscape(self, value):
565 self._landscape = value