VC: Fix error on clone page for legacy-ID events
[cds-indico.git] / indico / MaKaC / evaluation.py
blobf10e9c294fa4d946cae1b0784cfce768ea11f987
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 from datetime import date, datetime, timedelta
19 from persistent import Persistent
20 from pytz import timezone
22 from MaKaC.registration import Notification
23 from MaKaC.common import utils
24 from MaKaC.common.Counter import Counter
25 from MaKaC.i18n import _
26 from MaKaC.common.timezoneUtils import nowutc
27 from MaKaC.webinterface.mail import GenericMailer, GenericNotification
29 from indico.core.config import Config
30 from indico.modules.events.evaluation import event_settings as evaluation_settings
31 from indico.modules.users.legacy import AvatarUserWrapper
34 class Evaluation(Persistent):
35 """Evaluation class : an evaluation belongs to a conference."""
37 ###########
38 #Constants#
39 ###########
40 _SUBMIT = "submit"
41 _EDIT = "edit"
42 _REMOVE = "remove"
43 _SELECT_SUBMITTERS = "select"
44 _REMOVE_SUBMITTERS = "remove"
45 _CSV_FILENAME = "evaluation-results.csv"
46 _XML_FILENAME = "myEvaluation.xml"
47 _EVALUATION_START = "evaluationStartNotify" #never change this value!
48 _NEW_SUBMISSION = "newSubmissionNotify" #never change this value!
49 _ALL_NOTIFICATIONS = [_EVALUATION_START, _NEW_SUBMISSION]
51 def __init__(self, conf, id=None):
52 if id==None :
53 self._id = str( conf._getEvaluationCounter().newCount() )
54 else :
55 self._id = id
56 self._conf = conf
57 self._questions = []
58 self._submissions = []
59 self.title = "" #must be "" because conf isn't yet ready
60 self.setStartDate(datetime(1,1,1)) #must be datetime(1,1,1) because conf isn't yet ready
61 self.setEndDate(datetime(1,1,1)) #must be datetime(1,1,1) because conf isn't yet ready
62 self.notifications = {} #contains pairs like this (notificationKey: notification)
63 self.visible = False
64 self.anonymous = True
65 self.announcement = ""
66 self.submissionsLimit = 0
67 self.contactInfo = ""
68 self.mandatoryParticipant = False
69 self.mandatoryAccount = False
70 self._submissionCounter = Counter(1)
72 def update_notification(self):
73 """Enables/disables the notification task for the evaluation.
75 If the evaluation is valid and not running/finished yet, the
76 task will be enabled, sending an email to the configured
77 recipients then the evaluation starts.
78 Otherwise the task will be disabled for the event.
79 """
80 notification = self.getNotification(Evaluation._EVALUATION_START)
81 if (self.visible and self.startDate.date() > date.today() and self.getNbOfQuestions() and
82 notification and (notification.getToList() or notification.getCCList())):
83 evaluation_settings.set(self._conf, 'send_notification', True)
84 else:
85 evaluation_settings.delete(self._conf, 'send_notification')
87 def getTimezone(self):
88 return self._conf.getTimezone()
90 def reinit(self):
91 """ reinit the evaluation.
92 Note: all current information are lost, except '_id' and '_conf' attributes.
93 Advice: the session variable 'selectedSubmissions_<ConfId>_<EvalId>' should be deleted.
94 """
95 self.removeAllQuestions()
96 self.__init__(self.getConference(), self.getId())
98 def removeReferences(self):
99 """remove all pointers to other objects."""
100 self.removeAllNotifications()
101 self.removeAllQuestions()
102 self.removeAllSubmissions()
103 self._conf = None
105 def clone(self, conference):
106 """returns a new Evaluation which is a copy of the current one (self)."""
107 evaluation = Evaluation(conference)
108 evaluation.setId(self.getId())
109 evaluation.setTitle(self.getTitle())
110 evaluation.setVisible(self.isVisible())
111 evaluation.setAnonymous(self.isAnonymous())
112 evaluation.setAnnouncement(self.getAnnouncement())
113 evaluation.setSubmissionsLimit(self.getSubmissionsLimit())
114 evaluation.setContactInfo(self.getContactInfo())
115 timeDifference = conference.getStartDate() - self.getConference().getStartDate()
116 evaluation.setStartDate(self.getStartDate() + timeDifference)
117 evaluation.setEndDate(self.getEndDate() + timeDifference)
118 evaluation.setMandatoryAccount(self.isMandatoryAccount())
119 evaluation.setMandatoryParticipant(self.isMandatoryParticipant())
120 for (notificationKey,notification) in self.getNotifications().items() :
121 evaluation.setNotification(notificationKey, notification.clone())
122 for q in self.getQuestions():
123 evaluation.insertQuestion(q.clone())
124 return evaluation
126 def exportXml(self, xmlGen):
127 """Write xml tags about this object in the given xml generator of type XMLGen."""
128 #evaluations/evaluation/ - start
129 xmlGen.openTag("evaluation")
130 xmlGen.writeTag("id", self.getId())
131 xmlGen.writeTag("conferenceId", self.getConference().getId())
132 xmlGen.writeTag("title", self.getTitle())
133 xmlGen.writeTag("visible", self.isVisible())
134 xmlGen.writeTag("anonymous", self.isAnonymous())
135 xmlGen.writeTag("announcement", self.getAnnouncement())
136 xmlGen.writeTag("submissionsLimit", self.getSubmissionsLimit())
137 xmlGen.writeTag("contactInfo", self.getContactInfo())
138 xmlGen.writeTag("mandatoryParticipant", self.isMandatoryParticipant())
139 xmlGen.writeTag("mandatoryAccount", self.isMandatoryAccount())
140 #evaluations/evaluation/notifications/ - start
141 xmlGen.openTag("notifications")
142 for (notificationKey,notification) in self.getNotifications().items() :
143 xmlGen.openTag(notificationKey)
144 xmlGen.writeTag( "toList", ", ".join(notification.getToList()) )
145 xmlGen.writeTag( "ccList", ", ".join(notification.getCCList()) )
146 xmlGen.closeTag(notificationKey)
147 #evaluations/evaluation/notifications/ - end
148 xmlGen.closeTag("notifications")
149 #evaluations/evaluation/questions/ - start
150 xmlGen.openTag("questions")
151 for question in self.getQuestions() :
152 question.exportXml(xmlGen)
153 #evaluations/evaluation/questions/ - end
154 xmlGen.closeTag("questions")
155 #evaluations/evaluation/ - end
156 xmlGen.closeTag("evaluation")
158 def notifyModification(self):
159 """indicates to the database that current object attributes have changed."""
160 self._p_changed = 1
161 self.update_notification()
163 def setVisible(self, visible):
164 """If visible, the evaluation is allowed to be shown in the Display Area."""
165 self.visible = utils._bool(visible)
167 def isVisible(self):
168 """returns if the evaluation is allowed to be shown in the Display Area."""
169 if not hasattr(self, "visible"):
170 self.visible = False
171 return self.visible
173 def setAnonymous(self, anonymous):
174 """if True, following submission are anonymous for the surveyor."""
175 self.anonymous = utils._bool(anonymous)
176 def isAnonymous(self):
177 """returns if the following submissions are anonymous for the surveyor."""
178 if not hasattr(self, "anonymous"):
179 self.anonymous = True
180 return self.anonymous
182 def setConference(self, conf):
183 self._conf = conf
184 def getConference(self):
185 if not hasattr(self, "_conf"):
186 self._conf = None
187 return self._conf
189 def setId(self, id):
190 self._id = str(id)
191 def getId(self):
192 if not hasattr(self, "_id"):
193 self._id = str( conf._getEvaluationCounter().newCount() )
194 return self._id
196 def setMandatoryParticipant(self, v=True):
197 self.mandatoryParticipant = utils._bool(v)
198 setMandatoryRegistrant = setMandatoryParticipant
199 def isMandatoryParticipant(self):
200 if not hasattr(self, "mandatoryParticipant"):
201 self.mandatoryParticipant = False
202 return self.mandatoryParticipant
203 isMandatoryRegistrant = isMandatoryParticipant
205 def setMandatoryAccount(self, v=True):
206 self.mandatoryAccount = utils._bool(v)
207 def isMandatoryAccount(self):
208 if not hasattr(self, "mandatoryAccount"):
209 self.mandatoryAccount = False
210 return self.mandatoryAccount
212 def setTitle( self, title ):
213 self.title = utils.removeQuotes(title)
214 def getTitle( self ):
215 if not hasattr(self, "title") or self.title=="":
216 conf = self.getConference()
217 if conf!=None and conf.getTitle()!="":
218 self.title = _("Evaluation for %s")%conf.getTitle()
219 else:
220 self.title = _("Evaluation")
221 return self.title
223 def setAnnouncement( self, announcement ):
224 self.announcement = utils.removeQuotes(announcement)
225 def getAnnouncement( self ):
226 if not hasattr(self, "announcement"):
227 self.announcement = ""
228 return self.announcement
230 def setSubmissionsLimit( self, submissionsLimit ):
231 self.submissionsLimit = utils._positiveInt(submissionsLimit)
232 def getSubmissionsLimit( self ):
233 if not hasattr(self, "submissionsLimit"):
234 self.submissionsLimit = 0
235 return self.submissionsLimit
237 def setStartDate(self, sd):
238 self.startDate = datetime(sd.year, sd.month, sd.day, 0, 0, 0)
240 def getStartDate( self ):
241 if not hasattr(self, "startDate") or self.startDate==datetime(1,1,1,0,0,0):
242 self.setStartDate(self._conf.getEndDate().date() + timedelta(days=1))
243 return timezone(self.getTimezone()).localize(self.startDate)
245 def setEndDate( self, ed ):
246 self.endDate = datetime(ed.year,ed.month,ed.day,23,59,59)
247 def getEndDate( self ):
248 if not hasattr(self, "endDate") or self.endDate==datetime(1,1,1,23,59,59):
249 self.setEndDate(self._conf.getEndDate() + timedelta(days=8))
250 return timezone(self.getTimezone()).localize(self.endDate)
252 def inEvaluationPeriod(self, date=nowutc()):
253 return date>=self.getStartDate() and date<=self.getEndDate()
255 def setContactInfo( self, contactInfo ):
256 self.contactInfo = utils.removeQuotes(contactInfo)
257 def getContactInfo( self ):
258 if not hasattr(self, "contactInfo"):
259 self.contactInfo = ""
260 return self.contactInfo
262 def insertQuestion(self, question, position=-1):
263 """ Insert a question in this evaluation at the given position (if given, otherwise at the end).
264 It also sets questions's evaluation to be this evaluation.
265 Note: first position = 1.
267 position = utils._int(position)-1 #For list, first position = 0 !!!
268 if question not in self.getQuestions():
269 if position>=0:
270 self.getQuestions().insert(position, question)
271 else:
272 self.getQuestions().append(question)
273 question.setEvaluation(self)
274 self.notifyModification()
276 def getQuestions(self):
277 """get the questions of this evaluation."""
278 if not hasattr(self, "_questions"):
279 self._questions = []
280 return self._questions
282 def getQuestionAt(self, position):
283 """ get a question from this evalution given by its position.
284 Note: first position = 1.
286 #check params
287 position = utils._int(position)-1 #For list, first position = 0 !!!
288 if position<0 or position>=self.getNbOfQuestions() : return None
289 #getting...
290 questions = self.getQuestions()
291 return questions[position]
293 def removeQuestion(self, position):
294 """ remove and return the questions given by its position from this evaluation.
295 Note: first position = 1.
297 #check params
298 position = utils._int(position)-1 #For list, first position = 0 !!!
299 if position<0 or position>=self.getNbOfQuestions() : return None
300 #removing...
301 q = self.getQuestions().pop(position)
302 q.removeReferences()
303 self.notifyModification()
304 return q
306 def removeAllQuestions(self):
307 """ remove all the questions of this evaluation.
308 Advice: the session variable 'selectedSubmissions_<ConfId>_<EvalId>' should be deleted.
310 #remove all submissions
311 self.removeAllSubmissions()
312 #remove references
313 for question in self.getQuestions():
314 #destroy links with other objects
315 question.removeReferences()
316 #reinit
317 self._questions = []
318 self.notifyModification()
320 def getNbOfQuestions(self):
321 """get the number of questions contained in this evaluation."""
322 return len(self.getQuestions())
324 def _getSubmissionCounter(self):
325 """returning counter for submitters, useful to give them a unique id."""
326 if not hasattr(self, "_submissionCounter"):
327 self._submissionCounter = Counter()
328 return self._submissionCounter
330 def insertSubmission(self, submission):
331 """ Insert a submission for this evaluation.
332 Params:
333 submission -- of type Submission
335 if submission not in self.getSubmissions():
336 self.getSubmissions().append(submission)
337 self.notifyModification()
339 def setSubmissions(self, submissions):
340 """set the submissions of this evaluation."""
341 self._submissions = submissions
342 def getSubmissions(self, ids=None):
343 """get the submissions of this evaluation."""
344 if not hasattr(self, "_submissions"):
345 self._submissions = []
346 if ids is not None:
347 return [s for s in self._submissions if s.getId() in ids]
348 return self._submissions
350 def getSubmissionById(self, id_):
351 return next((x for x in self.getSubmissions() if x._id == id_), None)
353 def getUserSubmission(self, user):
354 """ return the submission of the given user, or None if nothing found.
355 Params:
356 user -- an object generally of type Avatar.
358 for submission in self.getSubmissions():
359 if submission.getSubmitter()==user :
360 return submission
361 return None
363 def removeSubmission(self, submission):
364 """remove the given submission from the submissions list."""
365 submissions = self.getSubmissions()
366 if submissions.count(submission)>0:
367 submissions.remove(submission)
368 self.notifyModification()
369 submission.removeReferences()
371 def removeAllSubmissions(self):
372 """ remove all the submissions of this evaluation.
373 Advice: the session variable 'selectedSubmissions_<ConfId>_<EvalId>' should be deleted.
375 #remove references
376 for submission in self.getSubmissions():
377 #destroy links with other objects
378 submission.removeReferences()
379 self._submissions = []
380 self.notifyModification()
382 def getNbOfSubmissions(self):
383 """get the number of submissions contained in this evaluation."""
384 return len(self.getSubmissions())
386 def isFull(self):
387 """If the number of submissions has reached the number of submissions limit, return True. False otherwise."""
388 return self.getNbOfSubmissions() >= self.getSubmissionsLimit() and self.getSubmissionsLimit()!=0
390 def setNotification( self, notificationKey, notification=None ):
391 """ Set a Notification with given notification key and optional Notification instance.
392 Do nothing if the notification key is unknown (cf _ALL_NOTIFICATIONS).
393 Params:
394 notificationKey -- key of the Notification you want to set (cf _ALL_NOTIFICATIONS).
395 notification -- optional Notification instance.
397 if notificationKey in self._ALL_NOTIFICATIONS :
398 self.getNotifications()[notificationKey] = notification or Notification()
399 self.notifyModification()
401 def removeNotification(self, notificationKey):
402 """remove corresponding Notification with given notification key."""
403 notifications = self.getNotifications()
404 if notifications.has_key(notificationKey) :
405 notifications.pop(notificationKey, None)
406 self.notifyModification()
408 def removeAllNotifications(self):
409 """remove all notifications."""
410 self.notifications = {}
411 self.notifyModification()
413 def getNotifications(self):
414 """get the dictionnary with pairs like this ([int]notificationKey: [Notification]notification)."""
415 if not hasattr(self, "notifications"):
416 self.notifications = {}
417 return self.notifications
419 def getNotification(self, notificationKey):
420 """For given notification key gets the corresponding Notification or None if not found."""
421 return self.getNotifications().get(notificationKey, None)
424 class Question(Persistent):
425 """Question class : a question belongs to an evaluation."""
427 ###########
428 #Constants#
429 ###########
430 #Modes:
431 _ADD = "add"
432 _EDIT = "edit"
433 _REMOVE = "remove"
434 #question type names: subclasses
435 _TEXTBOX = "TextBox"
436 _TEXTAREA = "TextArea"
437 _PASSWORD = "PasswordBox"
438 _SELECT = "Select"
439 _RADIO = "RadioButton"
440 _CHECKBOX = "CheckBox"
441 #question type names: superclasses
442 _BOX = "Box"
443 _CHOICE = "Choice"
444 _BOX_SUBTYPES = [_TEXTBOX, _TEXTAREA, _PASSWORD]
445 _CHOICE_SUBTYPES = [_CHECKBOX, _RADIO, _SELECT]
446 #question type names: topclasses
447 _QUESTION = "Question"
448 _QUESTION_SUBTYPES = _BOX_SUBTYPES + _CHOICE_SUBTYPES
450 def __init__(self):
451 self._evaluation = None
452 self._answers = []
453 self.questionValue = "" #The question itself (text)
454 self.keyword = ""
455 self.required = False
456 self.description = ""
457 self.help = ""
458 # self.level = 1
460 def removeReferences(self):
461 """remove all pointers to other objects."""
462 self._evaluation = None
463 self.removeAllAnswers()
465 def clone(self):
466 """returns a new Question which is a copy of the current one (self)."""
467 q = self.getClass()() #e.g. if self is Textbox, it's the same as "q = Textbox()"
468 q.setQuestionValue(self.getQuestionValue())
469 q.setKeyword(self.getKeyword())
470 q.setRequired(self.isRequired())
471 q.setDescription(self.getDescription())
472 q.setHelp(self.getHelp())
473 # q.setLevel(self.getLevel())
474 return q
476 def _exportCommonAttributesXml(self, xmlGen):
477 """Write xml tags about common question attributes in the given xml generator of type XMLGen."""
478 xmlGen.writeTag("questionValue", self.getQuestionValue())
479 xmlGen.writeTag("keyword", self.getKeyword())
480 xmlGen.writeTag("required", self.isRequired())
481 xmlGen.writeTag("description", self.getDescription())
482 xmlGen.writeTag("help", self.getHelp())
484 def removeAnswer(self, answer):
485 """remove the given answer from its answers."""
486 answers = self.getAnswers()
487 if answers.count(answer)>0:
488 answers.remove(answer)
489 self.notifyModification()
490 answer.removeReferences()
492 def removeAllAnswers(self):
493 """remove all the answers of this question."""
494 #remove all answers and links to them
495 for answer in self.getAnswers():
496 submission = answer.getSubmission()
497 submission.removeAnswer(answer)
498 #delete the submission if it contains no more question
499 if submission.getNbOfAnswers()<1:
500 submission.removeReferences()
501 #reinit
502 self._answers = []
503 self.notifyModification()
505 def notifyModification(self):
506 """indicates to the database that current object attributes have changed."""
507 self._p_changed=1
509 def setEvaluation(self, evaluation):
510 """Sets the evaluation to which this question belongs."""
511 self._evaluation = evaluation
512 def getEvaluation(self):
513 """gets the evaluation to which this question belongs."""
514 if not hasattr(self, "_evaluation"):
515 self._evaluation = None
516 return self._evaluation
518 def getTypeName(self):
519 """gets type name of this question."""
520 return self._QUESTION
522 def getClass(self):
523 """gets the class of this object."""
524 return self.__class__
526 def getClassName(self):
527 """gets the class name of this object."""
528 return self.getClass().__name__
530 def setQuestionValue(self, questionValue):
531 """sets the question itself (i.e. the text), not a Question object."""
532 self.questionValue = utils.removeQuotes(questionValue)
533 def getQuestionValue(self):
534 """returns the question itself (i.e. the text), not a Question object."""
535 if not hasattr(self, "questionValue"):
536 self.questionValue = ""
537 return self.questionValue
539 def setKeyword(self, keyword):
540 """Set the keyword. A keyword is the question summarised in one word."""
541 self.keyword = utils.removeQuotes(keyword)
542 def getKeyword(self):
543 """Get the keyword. A keyword is the question summarised in one word."""
544 if not hasattr(self, "keyword"):
545 self.keyword = ""
546 return self.keyword
548 def setRequired(self, required):
549 """Set if an answer for the question is required."""
550 self.required = utils._bool(required)
551 def isRequired(self):
552 """Get if an answer for the question is required."""
553 if not hasattr(self, "required"):
554 self.required = False
555 return self.required
557 def setDescription(self, description):
558 self.description = utils.removeQuotes(description)
559 def getDescription(self):
560 if not hasattr(self, "description"):
561 self.description = ""
562 return self.description
564 def setHelp(self, help):
565 """sets help message."""
566 self.help = utils.removeQuotes(help)
567 def getHelp(self):
568 """gets help message."""
569 if not hasattr(self, "help"):
570 self.help = ""
571 return self.help
573 def setPosition(self, position):
574 """ sets position of a question within a form.
575 Note: first position = 1.
577 position = utils._positiveInt(position)
578 if position != self.getPosition():
579 questionsList = self.getEvaluation().getQuestions()
580 q = questionsList.pop(self.getPosition()-1) #For list, first position = 0 !
581 self.getEvaluation().insertQuestion(q, position)
582 def getPosition(self):
583 """ gets position of a question within a form.
584 Note: first position = 1.
586 return self.getEvaluation().getQuestions().index(self)+1
588 # def setLevel(self, level):
589 # """ sets level of a question, used for multipart questions. (e.g. 1.1,1.1a, 1.1b,...)
590 # The lower the level, the higher its importance.
591 # """
592 # self.level = utils._positiveInt(level)
593 # def getLevel(self):
594 # """ gets level of a question, used for multipart questions. (e.g. 1.1,1.1a, 1.1b,...)
595 # The lower the level, the higher its importance.
596 # """
597 # if not hasattr(self, "level"):
598 # self.level = 1
599 # return self.level
601 def insertAnswer(self, answer):
602 """Insert an answer for this question. It also sets answer's question to be this question."""
603 if answer not in self.getAnswers():
604 self.getAnswers().append(answer)
605 answer.setQuestion(self)
606 self.notifyModification()
608 def getAnswers(self, selectedSubmissions=None):
609 """ get the answers for this question.
610 This function is a shortcut for getting answers easily from this question.
611 In fact, answers and questions are not directly bound.
612 Params:
613 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
614 If the list is empty, we treat all the answers.
616 #check
617 if not hasattr(self, "_answers"):
618 self._answers = []
619 if selectedSubmissions is None:
620 return self._answers
621 #do all the gestion for the answers of a question !
622 tempAnswers = []
623 for answer in self._answers:
624 if answer.getSubmission().getId() in selectedSubmissions:
625 tempAnswers.append(answer)
626 return tempAnswers
628 def getNbOfAnswers(self, selectedSubmissions=None):
629 """ get the number of answers for this question.
630 Params:
631 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
632 If the list is empty, we treat all the answers.
634 return len(self.getAnswers(selectedSubmissions))
636 def getNbOfFilledAnswers(self, selectedSubmissions=None):
637 """ returns the number of not empty answers.
638 Params:
639 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
640 If the list is empty, we treat all the answers.
642 nb = 0
643 for answer in self.getAnswers(selectedSubmissions):
644 if answer.hasAnswerValue():
645 nb += 1
646 return nb
648 def areAllAnswersFilled(self, selectedSubmissions=None):
649 """ returns True if all the answers are filled, False otherwise.
650 Params:
651 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
652 If the list is empty, we treat all the answers.
654 return self.getNbOfFilledAnswers(selectedSubmissions) == self.getNbOfAnswers(selectedSubmissions)
656 def printAreAllAnswersFilled(self, selectedSubmissions=None):
657 return "%s %s"%(self.getNbOfFilledAnswers(selectedSubmissions), self.getNbOfAnswers(selectedSubmissions))
659 def getUserAnswer(self, user):
660 """ return the answer (of type Answer) of the given user, or None if nothing found.
661 Params:
662 user -- an object generally of type Avatar.
664 for answer in self.getAnswers():
665 if answer.getSubmission().getSubmitter()==user :
666 return answer
667 return None
669 def getUserAnswerValue(self, user):
670 """ return the answer value (of type str) of the given user, or None if nothing found.
671 Params:
672 user -- an object generally of type Avatar.
674 answer = self.getUserAnswer(user)
675 if answer==None:
676 return None
677 else:
678 return answer.getAnswerValue()
681 class Box(Question):
682 """A Box is a question to which you answer with the help of a box (e.g. Textbox, TextArea, PasswordBox)."""
684 def __init__(self):
685 Question.__init__(self)
686 self.defaultAnswer = ""
688 def clone(self):
689 """returns a new Question which is a copy of the current one (self).
691 q = Question.clone(self)
692 q.setDefaultAnswer(self.getDefaultAnswer())
693 return q
695 def exportXml(self, xmlGen):
696 """Write xml tags about this object in the given xml generator of type XMLGen."""
697 xmlGen.openTag(self.getClassName())
698 self._exportCommonAttributesXml(xmlGen)
699 xmlGen.writeTag("defaultAnswer", self.getDefaultAnswer())
700 xmlGen.closeTag(self.getClassName())
702 def setDefaultAnswer(self, defaultAnswer):
703 """sets the default answer of the question."""
704 self.defaultAnswer = utils.removeQuotes(defaultAnswer)
705 def getDefaultAnswer(self):
706 """gets the default answer of the question."""
707 if not hasattr(self, "defaultAnswer"):
708 self.defaultAnswer = ""
709 return self.defaultAnswer
711 def getTypeName(self):
712 """gets type name of this question."""
713 return self._BOX
716 class Choice(Question):
717 """A Choice is a question to which you answer with the help of a multiple choices (e.g. Radio buttons, Checkboxes, ...)."""
719 def __init__(self):
720 Question.__init__(self)
721 self.choiceItems = {};
722 self.choiceItemsOrderedKeys = [] #I want to keep the order the user inserted them.
724 def clone(self):
725 """returns a new Question which is a copy of the current one (self).
727 q = Question.clone(self)
728 for key in self.getChoiceItemsOrderedKeys():
729 q.insertChoiceItem(key, self.getChoiceItemsCorrespondingValue(key))
730 return q
732 def exportXml(self, xmlGen):
733 """Write xml tags about this object in the given xml generator of type XMLGen."""
734 xmlGen.openTag(self.getClassName())
735 self._exportCommonAttributesXml(xmlGen)
736 xmlGen.openTag("choiceItems")
737 for itemText in self.getChoiceItemsOrderedKeys() :
738 xmlGen.openTag("choiceItem")
739 xmlGen.writeTag("itemText", itemText)
740 xmlGen.writeTag("isSelected", self.getChoiceItemsCorrespondingValue(itemText))
741 xmlGen.closeTag("choiceItem")
742 xmlGen.closeTag("choiceItems")
743 xmlGen.closeTag(self.getClassName())
745 def insertChoiceItem(self, itemText, isSelected):
746 """ Insert a new choice item in the dictionnary.
747 A choiceItem is a pair like this : (itemText,isSelected).
749 Params:
750 itemText -- [str] text of the choiceItem.
751 isSelected -- [bool] if the choiceItem is selected.
753 #check params
754 itemText = utils.removeQuotes(itemText)
755 isSelected = utils._bool(isSelected)
756 #adding new item...
757 if itemText!=None and itemText!="" and itemText not in self.getChoiceItemsOrderedKeys():
758 self.getChoiceItems()[itemText] = isSelected
759 self.getChoiceItemsOrderedKeys().append(itemText)
760 #notify
761 self.notifyModification()
763 def getChoiceItems(self):
764 """Gets the dictionary of choice items. Its elements are pairs like this (itemText:isSelected)."""
765 if not hasattr(self, "choiceItems"):
766 self.choiceItems = {};
767 return self.choiceItems
769 def getChoiceItemsOrderedKeys(self):
770 """ Gets the list of ordered keys (itemText of type str) for choice items.
771 The keys are ordered in the same order as the user inserted them.
773 if not hasattr(self, "choiceItemsOrderedKeys"):
774 self.choiceItemsOrderedKeys = []
775 return self.choiceItemsOrderedKeys
777 def getChoiceItemsKeyAt(self, position):
778 """ Gets the key (itemText) at the given position.
779 Note: first position = 1.
781 #check params
782 position = utils._int(position)-1 #For list, first position = 0 !!!
783 keys = self.getChoiceItemsOrderedKeys()
784 if position<0 or position>=len(keys) : return ""
785 return keys[position]
787 def getChoiceItemsCorrespondingValue(self, itemText):
788 """ Given the key (itemText) returns the corresponding value (isSelected). """
789 return self.getChoiceItems().get(itemText, False)
791 def getNbOfChoiceItems(self):
792 """Gets the number of choice items."""
793 choiceItemsNb = len(self.getChoiceItems())
794 choiceItemsOrderedKeysNb = len(self.getChoiceItemsOrderedKeys())
795 #both should always be the same, but... we never know!
796 return min([choiceItemsNb , choiceItemsOrderedKeysNb])
798 def removeAllChoiceItems(self):
799 """remove all the choice items for this question."""
800 self.choiceItems = {}
801 self.choiceItemsOrderedKeys = []
802 self.notifyModification()
804 def getDefaultAnswers(self):
805 """gets the default answers (list of strings) of the question."""
806 keys = self.getChoiceItems().keys()
807 vals = self.getChoiceItems().values()
808 index=0
809 defaultAnswers=[]
810 for val in vals:
811 if val :
812 defaultAnswers.append(keys[index])
813 index+=1
814 return defaultAnswers
816 def getTypeName(self):
817 """gets type name of this question."""
818 return self._CHOICE
821 class Textbox(Box):
823 def __init__(self):
824 Box.__init__(self)
826 def getTypeName(self):
827 """gets type name of this question."""
828 return self._TEXTBOX
830 def getAnswerClass(self):
831 """gets the class of the corresponding answer(s) for this question."""
832 return TextAnswer
834 def displayHtml(self, **attributes):
835 """ Display the question in HTML.
836 Params:
837 attributes -- dictionary of attributes for <input>.
839 from MaKaC.webinterface.wcomponents import WUtils
840 attributes["type"] = "text"
841 attributes["class"] = "textType"
842 attributes["value"] = self.getDefaultAnswer()
843 return WUtils.createInput(**attributes)
845 def displayHtmlWithUserAnswer(self, user, **attributes):
846 """ Display the question in HTML with the answer of given user.
847 Params:
848 user -- object generally of type Avatar.
849 attributes -- dictionary of attributes for <input>.
851 from MaKaC.webinterface.wcomponents import WUtils
852 attributes["type"] = "text"
853 attributes["class"] = "textType"
854 attributes["value"] = self.getUserAnswerValue(user)
855 return WUtils.createInput(**attributes)
858 class Textarea(Box):
860 def __init__(self):
861 Box.__init__(self)
863 def getTypeName(self):
864 """gets type name of this question."""
865 return self._TEXTAREA
867 def getAnswerClass(self):
868 """gets the class of the corresponding answer(s) for this question."""
869 return TextAnswer
871 def displayHtml(self, **attributes):
872 """ Display the question in HTML.
873 Params:
874 attributes -- dictionary of attributes for <textarea>.
876 from MaKaC.webinterface.wcomponents import WUtils
877 return WUtils.createTextarea(self.getDefaultAnswer(), **attributes)
879 def displayHtmlWithUserAnswer(self, user, **attributes):
880 """ Display the question in HTML with the answer of given user.
881 Params:
882 user -- object generally of type Avatar.
883 attributes -- dictionary of attributes for <input>.
885 from MaKaC.webinterface.wcomponents import WUtils
886 return WUtils.createTextarea(self.getUserAnswerValue(user), **attributes)
889 class Password(Box):
891 def __init__(self):
892 Box.__init__(self)
894 def getTypeName(self):
895 """gets type name of this question."""
896 return self._PASSWORD
898 def getAnswerClass(self):
899 """gets the class of the corresponding answer(s) for this question."""
900 return TextAnswer
902 def displayHtml(self, **attributes):
903 """ Display the question in HTML.
904 Params:
905 attributes -- dictionary of attributes for <input>.
907 from MaKaC.webinterface.wcomponents import WUtils
908 attributes["type"] = "password"
909 attributes["class"] = "passwordType"
910 attributes["value"] = self.getDefaultAnswer()
911 return WUtils.createInput(**attributes)
913 def displayHtmlWithUserAnswer(self, user, **attributes):
914 """ Display the question in HTML with the answer of given user.
915 Params:
916 user -- object generally of type Avatar.
917 attributes -- dictionary of attributes for <input>.
919 from MaKaC.webinterface.wcomponents import WUtils
920 attributes["type"] = "password"
921 attributes["class"] = "passwordType"
922 attributes["value"] = self.getUserAnswerValue(user)
923 return WUtils.createInput(**attributes)
926 class Select(Choice):
928 def __init__(self):
929 Choice.__init__(self)
931 def getDefaultAnswer(self):
932 """gets the default answer (string) of the question."""
933 defaultAnswers = self.getDefaultAnswers()
934 if len(defaultAnswers)>0:
935 return defaultAnswers.pop()
936 else:
937 return ""
939 def getTypeName(self):
940 """gets type name of this question."""
941 return self._SELECT
943 def getAnswerClass(self):
944 """gets the class of the corresponding answer(s) for this question."""
945 return TextAnswer
947 def displayHtml(self, **attributes):
948 """ Display the question in HTML.
949 Params:
950 attributes -- dictionary of attributes for <select>.
952 from MaKaC.webinterface.wcomponents import WUtils
953 options = self.getChoiceItemsOrderedKeys()
954 selected = self.getDefaultAnswer()
955 return WUtils.createSelect(True, options, selected, **attributes)
957 def displayHtmlWithUserAnswer(self, user, **attributes):
958 """ Display the question in HTML with the answer of given user.
959 Params:
960 user -- object generally of type Avatar.
961 attributes -- dictionary of attributes for <input>.
963 from MaKaC.webinterface.wcomponents import WUtils
964 options = self.getChoiceItemsOrderedKeys()
965 selected = self.getUserAnswerValue(user)
966 return WUtils.createSelect(True, options, selected, **attributes)
968 def getNbOfAnswersLike(self, answerValue, selectedSubmissions=None):
969 """ [Statistics] Give the number of answers which are the same as the given answer value.
970 Params:
971 answerValue -- given answer value of type string.
972 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
973 If the list is empty, we treat all the answers.
975 try:
976 nb = 0
977 for answer in self.getAnswers(selectedSubmissions):
978 if answer.getAnswerValue()==str(answerValue):
979 nb += 1
980 return nb
981 except:
982 return 0
984 def getPercentageAnswersLike(self, answerValue, selectedSubmissions=None):
985 """ [Statistics] Give the percentage of answers like given answer value.
986 Params:
987 answerValue -- given answer value of type string.
988 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
989 If the list is empty, we treat all the answers.
991 try:
992 percent = float(self.getNbOfAnswersLike(answerValue,selectedSubmissions))*100/self.getNbOfAnswers(selectedSubmissions)
993 return utils._positiveInt(round(percent))
994 except:
995 return 0
998 class Radio(Choice):
1000 def __init__(self):
1001 Choice.__init__(self)
1003 def getDefaultAnswer(self):
1004 """gets the default answer (text) of the question."""
1005 defaultAnswers = self.getDefaultAnswers()
1006 if len(defaultAnswers)>0:
1007 return defaultAnswers.pop()
1008 else:
1009 return ""
1011 def getTypeName(self):
1012 """gets type name of this question."""
1013 return self._RADIO
1015 def getAnswerClass(self):
1016 """gets the class of the corresponding answer(s) for this question."""
1017 return TextAnswer
1019 def displayHtml(self, **attributes):
1020 """ Display the question in HTML.
1021 Params:
1022 attributes -- dictionary of attributes for <input>.
1024 from MaKaC.webinterface.wcomponents import WUtils
1025 attributes["type"] = "radio"
1026 choiceItemsHTML = ""
1027 for itemText in self.getChoiceItemsOrderedKeys():
1028 attributes["value"] = itemText
1029 isSelected = self.getChoiceItemsCorrespondingValue(itemText)
1030 if isSelected :
1031 attributes["checked"]="checked"
1032 else:
1033 attributes.pop("checked", None)
1034 choiceItemsHTML += WUtils.appendNewLine(WUtils.createInput(itemText, **attributes))
1035 return choiceItemsHTML
1037 def displayHtmlWithUserAnswer(self, user, **attributes):
1038 """ Display the question in HTML with the answer of given user.
1039 Params:
1040 user -- object generally of type Avatar.
1041 attributes -- dictionary of attributes for <input>.
1043 from MaKaC.webinterface.wcomponents import WUtils
1044 attributes["type"] = "radio"
1045 choiceItemsHTML = ""
1046 for itemText in self.getChoiceItemsOrderedKeys():
1047 attributes["value"] = itemText
1048 if itemText==self.getUserAnswerValue(user) :
1049 attributes["checked"]="checked"
1050 else:
1051 attributes.pop("checked", None)
1052 choiceItemsHTML += WUtils.appendNewLine(WUtils.createInput(itemText, **attributes))
1053 return choiceItemsHTML
1055 def getNbOfAnswersLike(self, answerValue, selectedSubmissions=None):
1056 """ [Statistics] Give the number of answers which are the same as the given answer value.
1057 Params:
1058 answerValue -- given answer value of type string.
1059 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
1060 If the list is empty, we treat all the answers.
1062 try:
1063 nb = 0
1064 for answer in self.getAnswers(selectedSubmissions):
1065 if answer.getAnswerValue()==str(answerValue):
1066 nb += 1
1067 return nb
1068 except:
1069 return 0
1071 def getPercentageAnswersLike(self, answerValue, selectedSubmissions=None):
1072 """ [Statistics] Give the percentage of answers like given answer value.
1073 Params:
1074 answerValue -- given answer value of type string.
1075 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
1076 If the list is empty, we treat all the answers.
1078 try:
1079 percent = float(self.getNbOfAnswersLike(answerValue,selectedSubmissions))*100/self.getNbOfAnswers(selectedSubmissions)
1080 return utils._positiveInt(round(percent))
1081 except:
1082 return 0
1085 class Checkbox(Choice):
1087 def __init__(self):
1088 Choice.__init__(self)
1090 def getTypeName(self):
1091 """gets type name of this question."""
1092 return self._CHECKBOX
1094 def getAnswerClass(self):
1095 """gets the class of the corresponding answer(s) for this question."""
1096 return MultipleChoicesAnswer
1098 def displayHtml(self, **attributes):
1099 """ Display the question in HTML.
1100 Params:
1101 attributes -- dictionary of attributes for <input>.
1103 from MaKaC.webinterface.wcomponents import WUtils
1104 attributes["type"] = "checkbox"
1105 choiceItemsHTML = ""
1106 for itemText in self.getChoiceItemsOrderedKeys():
1107 attributes["value"] = itemText
1108 isSelected = self.getChoiceItemsCorrespondingValue(itemText)
1109 if isSelected :
1110 attributes["checked"]="checked"
1111 else:
1112 attributes.pop("checked", None)
1113 choiceItemsHTML += WUtils.appendNewLine(WUtils.createInput(itemText, **attributes))
1114 return choiceItemsHTML
1116 def displayHtmlWithUserAnswer(self, user, **attributes):
1117 """ Display the question in HTML with the answer of given user.
1118 Params:
1119 user -- object generally of type Avatar.
1120 attributes -- dictionary of attributes for <input>.
1122 from MaKaC.webinterface.wcomponents import WUtils
1123 attributes["type"] = "checkbox"
1124 choiceItemsHTML = ""
1125 choiceItemsOrderedKeys = self.getChoiceItemsOrderedKeys()
1126 userAnswerValue = self.getUserAnswerValue(user)
1127 if userAnswerValue==None:
1128 userAnswerValue=[]
1129 for itemText in choiceItemsOrderedKeys:
1130 attributes["value"] = itemText
1131 if itemText in userAnswerValue:
1132 attributes["checked"]="checked"
1133 else:
1134 attributes.pop("checked", None)
1135 choiceItemsHTML += WUtils.appendNewLine(WUtils.createInput(itemText, **attributes))
1136 return choiceItemsHTML
1138 def getNbOfAnswersLike(self, answerValue, selectedSubmissions=None):
1139 """ [Statistics] Give the number of answers which are the same as the given answer value.
1140 Params:
1141 answerValue -- given answer value of type string.
1142 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
1143 If the list is empty, we treat all the answers.
1145 nb = 0
1146 for answer in self.getAnswers(selectedSubmissions):
1147 for selectedChoiceItem in answer.getSelectedChoiceItems():
1148 if str(selectedChoiceItem)==str(answerValue):
1149 nb += 1
1150 return nb
1152 def getNbOfAllSelectedChoiceItems(self, selectedSubmissions=None):
1153 """ [Statistics] Returns the number of all selected choice items for all answers for this question.
1154 Params:
1155 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
1156 If the list is empty, we treat all the answers.
1158 try:
1159 nb = 0
1160 for answer in self.getAnswers(selectedSubmissions):
1161 nb += answer.getNbOfSelectedChoiceItems()
1162 return nb
1163 except:
1164 return 0
1166 def getPercentageAnswersLike(self, answerValue, selectedSubmissions=None):
1167 """ [Statistics] Give the percentage of answers like given answer value.
1168 Params:
1169 answerValue -- given answer value of type string.
1170 selectedSubmissions -- [list of Submission] Only answers whose submission belongs in this list are treated.
1171 If the list is empty, we treat all the answers.
1173 try:
1174 percent = float(self.getNbOfAnswersLike(answerValue,selectedSubmissions))*100/self.getNbOfAllSelectedChoiceItems(selectedSubmissions)
1175 return utils._positiveInt(round(percent))
1176 except:
1177 return 0
1181 class Answer(Persistent):
1182 """Answer for a corresponding question..."""
1184 def __init__(self):
1185 self._question = None
1186 self._submission = None
1188 def removeReferences(self):
1189 """remove all pointers to other objects."""
1190 self._question = None
1191 self._submission = None
1193 def setQuestion(self, q):
1194 """Set the corresponding Question of this Answer."""
1195 self._question = q
1196 def getQuestion(self):
1197 """Get the corresponding Question of this Answer."""
1198 if not hasattr(self, "_question"):
1199 self._question = None
1200 return self._question
1202 def setSubmission(self, s):
1203 """Set the submission to which this answer belongs."""
1204 self._submission = s
1205 def getSubmission(self):
1206 """ Get submission to which this answer belongs.
1207 If the event is a conference: he is a Participant.
1208 Otherwise (lecture, meeting): he is a Registrant.
1210 if not hasattr(self, "_submission"):
1211 self._submission = None
1212 return self._submission
1215 class TextAnswer(Answer):
1216 """Answer which is just a text. (e.g. for Textbox, Textarea, Passwordbox, RadioButtons, Select)"""
1218 def __init__(self):
1219 Answer.__init__(self)
1220 self._answerValue = ""
1222 def setAnswerValue(self, a):
1223 """Set the answer value (str) of this object Answer."""
1224 if a==[] or a==None: a=""
1225 self._answerValue = utils.removeQuotes(a)
1227 def getAnswerValue(self):
1228 """Get the answer value (str) of this object Answer."""
1229 if not hasattr(self, "_answerValue"):
1230 self._answerValue = ""
1231 return self._answerValue
1233 def hasAnswerValue(self):
1234 """Returns False if the answer value is empty, True otherwise."""
1235 return self.getAnswerValue()!=""
1238 class MultipleChoicesAnswer(Answer):
1239 """Answer (list of selected items) for a question with multiple choices (i.e. Checkbox)."""
1241 def __init__(self):
1242 Answer.__init__(self)
1243 self._selectedChoiceItems = [] #list of str
1245 def setSelectedChoiceItems(self, selectedAnswers):
1246 """ Sets the list of selected choice items.
1248 Params:
1249 selectedAnswers -- [str or list-of-str] selected answers for the question.
1251 if selectedAnswers=="" or selectedAnswers==None:
1252 self._selectedChoiceItems = []
1253 elif isinstance(selectedAnswers, str):
1254 self._selectedChoiceItems = [utils.removeQuotes(selectedAnswers)]
1255 elif isinstance(selectedAnswers, list):
1256 self._selectedChoiceItems = [utils.removeQuotes(ci) for ci in selectedAnswers]
1257 self.notifyModification()
1258 setAnswerValue = setSelectedChoiceItems
1260 def getSelectedChoiceItems(self):
1261 """ Gets the list of selected choice items (str).
1263 if not hasattr(self, "_selectedChoiceItems"):
1264 self._selectedChoiceItems = []
1265 return self._selectedChoiceItems
1266 getAnswerValue = getSelectedChoiceItems
1268 def hasSelectedChoiceItems(self):
1269 """Returns False if the answer value is empty, True otherwise."""
1270 return self.getNbOfSelectedChoiceItems()>0
1271 hasAnswerValue = hasSelectedChoiceItems
1273 def getNbOfSelectedChoiceItems(self):
1274 """ Gets the number of selected choice items. """
1275 return len(self.getSelectedChoiceItems())
1277 def removeAllSelectedChoiceItems(self):
1278 """remove all the selected choice items for this question."""
1279 self._selectedChoiceItems = []
1280 self.notifyModification()
1282 def notifyModification(self):
1283 """indicates to the database that current object attributes have changed."""
1284 self._p_changed=1
1287 class Submission(Persistent):
1288 """When you submit an evaluation, you create a Submission instance."""
1290 def __init__(self, evaluation, submitter):
1291 """ Initiation + insert this submission in evaluation's submissions.
1292 Params:
1293 evaluation -- [Evaluation] evaluation to which this submission belongs.
1294 submitter -- [Avatar/None] submitter who submitted this submission.
1296 self._evaluation = evaluation
1297 self._id = str(evaluation._getSubmissionCounter().newCount())
1298 self.setSubmitter(submitter)
1299 self._answers = []
1300 self.submissionDate = nowutc()
1301 self.modificationDate = None
1302 self.anonymous = evaluation.isAnonymous()
1303 self._evaluation.insertSubmission(self)
1305 def __cmp__(self, other):
1306 if type(self) is not type(other):
1307 # This is actually dangerous and the ZODB manual says not to do this
1308 # because it relies on memory order. However, this branch should never
1309 # be taken anyway since we do not store different types in the same set
1310 # or use them as keys.
1311 return cmp(hash(self), hash(other))
1312 if self.getConference() == other.getConference():
1313 return cmp(self.getId(), other.getId())
1314 return cmp(self.getConference(), other.getConference())
1316 def getLocator(self):
1317 d = self.getConference().getLocator()
1318 d['submission_id'] = self._id
1319 return d
1321 def removeReferences(self):
1322 """remove all pointers to other objects."""
1323 self._evaluation = None
1324 self.removeSubmitter()
1325 self.removeAllAnswers()
1327 def setId(self, id):
1328 self._id = str(id)
1330 def getId(self):
1331 if not hasattr(self, "_id"):
1332 self._id = str( self._evaluation._getSubmissionCounter().newCount() )
1333 return self._id
1335 def notifyModification(self):
1336 """indicates to the database that current object attributes have changed."""
1337 self._p_changed=1
1339 def setEvaluation(self, evaluation):
1340 """Sets the evaluation to which this submission is bound."""
1341 self._evaluation = evaluation
1342 def getEvaluation(self):
1343 """gets the evaluation to which this submission is bound."""
1344 if not hasattr(self, "_evaluation"):
1345 self._evaluation = None
1346 return self._evaluation
1348 def getConference(self):
1349 """gets the conference to which this submission's evaluation is bound."""
1350 evaluation = self.getEvaluation()
1351 if evaluation:
1352 return evaluation.getConference()
1354 def setAnonymous(self, anonymous):
1355 """if True, submission is anonymous."""
1356 self.anonymous = utils._bool(anonymous)
1357 def isAnonymous(self):
1358 """returns if the submission is anonymous."""
1359 if not hasattr(self, "anonymous"):
1360 self.anonymous = True
1361 return self.anonymous
1363 def setSubmitter(self, submitter):
1364 """Set the submitter. He is of type None when anonymous, Avatar otherwise."""
1365 if isinstance(submitter, AvatarUserWrapper):
1366 submitter.linkTo(self, "submitter")
1367 self._submitter = submitter
1368 def getSubmitter(self):
1369 """ Get the submitter. He is of type None when anonymous, Avatar otherwise."""
1370 if not hasattr(self, "_submitter"):
1371 self._submitter = None
1372 return self._submitter
1373 def removeSubmitter(self):
1374 """remove the submitter, i.e. he is set to None."""
1375 submitter = self.getSubmitter()
1376 if isinstance(submitter, AvatarUserWrapper):
1377 submitter.unlinkTo(self, "submitter")
1378 self._submitter = None
1380 def getSubmitterName(self):
1381 """returns name of submitter"""
1382 submitter = self.getSubmitter()
1383 if not self.isAnonymous() and isinstance(submitter, AvatarUserWrapper):
1384 return submitter.getFullName()
1385 else :
1386 return "Anonymous (%s)"%self.getId()
1388 def notifyByEmail(self, message=""):
1389 """Notifies concerned people (given by To and Cc) by email about the given message [str]."""
1390 try:
1391 evaluation = self.getEvaluation()
1392 newSubmissionNotify = evaluation.getNotification(Evaluation._NEW_SUBMISSION)
1393 if newSubmissionNotify != None :
1394 toList = newSubmissionNotify.getToList()
1395 ccList = newSubmissionNotify.getCCList()
1396 if len(toList+ccList) > 0 :
1397 subject = "Notification for evaluation '%s'"%evaluation.getTitle()
1398 conf = evaluation.getConference()
1399 supportEmail = conf.getSupportInfo().getEmail(returnNoReply=True, caption=True)
1401 notification = GenericNotification({'fromAddr': supportEmail,
1402 'toList': toList,
1403 'ccList': ccList,
1404 'subject': subject,
1405 'body': message})
1407 GenericMailer.send(notification)
1408 except Exception:
1409 if Config.getInstance().getDebug():
1410 raise
1412 def notifySubmissionSubmitted(self):
1413 """notification when a new submission arrive."""
1414 evaluation = self.getEvaluation()
1415 self.notifyByEmail( _("""New submission from *%s* for \nevaluation *%s*.
1416 """)%(self.getSubmitterName(), evaluation.getTitle()) )
1418 def notifySubmissionModified(self):
1419 """notification when a submission is modified."""
1420 evaluation = self.getEvaluation()
1421 self.notifyByEmail( _("""*%s* modified his submission for \nevaluation *%s*.
1422 """)%(self.getSubmitterName(), evaluation.getTitle()) )
1424 def addNewAnswer(self, question, answerValue):
1425 """ Add a new answer for this submission.
1426 Params:
1427 question -- of type Question
1428 answerValue -- of type str / list of str
1430 answer = question.getAnswerClass()() #instantiate a new corresponding Answer()
1431 answer.setSubmission(self)
1432 question.insertAnswer(answer)
1433 answer.setAnswerValue(answerValue) #answer's question must be set!!! (done through question.insertAnswer)
1434 self.getAnswers().append(answer)
1435 self.notifyModification()
1437 def getAnswers(self):
1438 """get the answers of this submission."""
1439 if not hasattr(self, "_answers"):
1440 self._answers = []
1441 return self._answers
1443 def getNbOfAnswers(self):
1444 """get the number of answers for this question."""
1445 return len(self.getAnswers())
1447 def removeAnswer(self, answer):
1448 """remove the given answer from its answers."""
1449 answers = self.getAnswers()
1450 if answers.count(answer)>0:
1451 answers.remove(answer)
1452 self.notifyModification()
1453 answer.removeReferences()
1455 def removeAllAnswers(self):
1456 """remove all the answers of this submission."""
1457 #remove all answers and links to them
1458 for answer in self.getAnswers():
1459 answer.getQuestion().removeAnswer(answer)
1460 #reinit
1461 self._answers = []
1462 self.notifyModification()
1464 def getDictQuestionsAnswers(self):
1465 """ Returns a dictionnary like this {Question : Answer},
1466 with a Question as key and its corresponding Answer as value.
1467 Helpful because submission.getAnswers() order doesn't
1468 correspond necessarily to evaluation.getQuestions() order.
1469 Note: A question without answers doesn't appear in the dictionary.
1471 dictQuestionsAnswers = {}
1472 for answer in self.getAnswers() :
1473 dictQuestionsAnswers[answer.getQuestion()] = answer
1474 return dictQuestionsAnswers
1476 def getSubmissionDate(self, format=datetime):
1477 """ Get the submission date.
1478 Params:
1479 format -- output format (datetime or str)
1481 if not hasattr(self, "submissionDate"):
1482 self.submissionDate = nowutc()
1483 if format==str :
1484 return self.submissionDate.strftime("%x %H:%M")
1485 else:
1486 return self.submissionDate
1488 def setModificationDate(self):
1489 """Update the modification date (of type 'datetime') to now."""
1490 self.modificationDate = nowutc()
1491 def getModificationDate(self, format=datetime):
1492 """ If the format is str:
1493 get the modification date (of type 'str'), or "" if there is no modification.
1494 Else:
1495 get the modification date (of type 'datetime'), or None if there is no modification.
1497 Params:
1498 format -- output format (datetime or str)
1500 if not hasattr(self, "modificationDate"):
1501 self.modificationDate = None
1502 if format==str :
1503 if self.modificationDate==None :
1504 return ""
1505 else :
1506 return self.modificationDate.strftime("%x %H:%M")
1507 else :
1508 return self.modificationDate