Fix datepicker arrows style on hover
[cds-indico.git] / indico / MaKaC / webinterface / rh / evaluationModif.py
blobdad2832ba9a63565ff8fdd0f0c4e0fef520bed1b
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/>.
16 from cStringIO import StringIO
17 from flask import session
19 from MaKaC.webinterface import urlHandlers
20 from MaKaC.webinterface.rh import conferenceModif
21 from MaKaC.webinterface.pages import evaluations
22 from datetime import datetime
23 from MaKaC.evaluation import Box,Choice,Textbox,Textarea,Password,Select,Radio,Checkbox
24 from MaKaC.evaluation import Evaluation,Question,MultipleChoicesAnswer,TextAnswer
25 from MaKaC.common import utils
26 from MaKaC.errors import FormValuesError, MaKaCError
27 from MaKaC.export.excel import ExcelGenerator
28 from xml.dom.minidom import parseString,Element
29 from indico.core.config import Config
30 from MaKaC.i18n import _
31 from indico.web.flask.util import send_file
34 class RHEvaluationBase(conferenceModif.RHConferenceModifBase):
35 """Basis of an Evaluation"""
37 def _checkProtection(self):
38 conferenceModif.RHConferenceModifBase._checkProtection(self)
39 if not self._conf.hasEnabledSection("evaluation"):
40 raise MaKaCError( _("The Evaluation form was disabled by the conference managers."), _("evaluation"))
42 class RHEvaluationSetup(RHEvaluationBase):
43 """Modification of an Evaluation."""
45 def _process(self):
46 return evaluations.WPConfModifEvaluationSetup(self, self._conf).display()
48 class RHEvaluationSetupChangeStatus( RHEvaluationBase ):
49 """changes status of an Evaluation."""
51 def _checkParams( self, params ):
52 RHEvaluationBase._checkParams( self, params )
53 self._newStatus = params.get("changeTo", False)
55 def _process( self ):
56 evaluation = self._conf.getEvaluation()
57 if self._newStatus == "True" :
58 evaluation.setVisible(True)
59 else:
60 evaluation.setVisible(False)
61 evaluation.notifyModification()
62 self._redirect(urlHandlers.UHConfModifEvaluationSetup.getURL(self._conf))
64 class RHEvaluationSetupSpecialAction( RHEvaluationBase ):
65 """processes a special action."""
67 def _process( self ):
68 params = self.getRequestParams()
69 evaluation = self._conf.getEvaluation()
70 sessionVarName = "selectedSubmissions_%s_%s"%(self._conf.getId(), evaluation.getId())
71 if 'exportXML' in params:
72 return self._exportXml(evaluation, params)
73 elif 'importXML' in params:
74 return evaluations.WPConfModifEvaluationSetupImportXml( self, self._conf ).display()
75 elif 'importedXML' in params:
76 self._importedXml(evaluation, params)
77 elif 'removeSubmissions' in params:
78 session.pop(sessionVarName, None)
79 evaluation.removeAllSubmissions()
80 elif 'removeQuestions' in params:
81 session.pop(sessionVarName, None)
82 evaluation.removeAllQuestions()
83 elif 'reinit' in params:
84 session.pop(sessionVarName, None)
85 evaluation.reinit()
86 self._redirect(urlHandlers.UHConfModifEvaluationSetup.getURL(self._conf))
88 def _exportXml(self, evaluation, params):
89 """exportation of an evaluation."""
90 from MaKaC.common.xmlGen import XMLGen
91 xmlGen = XMLGen()
92 evaluation.exportXml(xmlGen)
93 return send_file(Evaluation._XML_FILENAME, StringIO(xmlGen.getXml()), 'XML', inline=False)
95 def _importedXml(self, evaluation, params):
96 """ Importation of an evaluation.
97 Note: original 'isVisible' and the dates are kept.
98 """
99 try:
100 xmlString = params["xmlFile"].file.read()
101 except AttributeError: #no file given
102 self._redirect(urlHandlers.UHConfModifEvaluationSetup.getURL(self._conf))
103 return
104 try:
105 doc = parseString(xmlString)
106 except:
107 raise MaKaCError( _("""System can't import an evaluation from your file.
108 Be sure to import the right XML file."""), _("evaluation"))
109 #parse begins
110 evalNode = self._getElement(doc,"evaluation")
111 if params.get("configOption","")=="imported":
112 #parse node /evaluation/
113 title = self._getValue(evalNode,"title")
114 announcement = self._getValue(evalNode,"announcement")
115 submissionsLimit = self._getValue(evalNode,"submissionsLimit")
116 contactInfo = self._getValue(evalNode,"contactInfo")
117 mandatoryAccount = self._getValue(evalNode,"mandatoryAccount")
118 mandatoryParticipant = self._getValue(evalNode,"mandatoryParticipant")
119 anonymous = self._getValue(evalNode,"anonymous")
120 evaluation.setTitle(title)
121 evaluation.setAnnouncement(announcement)
122 evaluation.setSubmissionsLimit(submissionsLimit)
123 evaluation.setContactInfo(contactInfo)
124 evaluation.setMandatoryAccount(mandatoryAccount)
125 evaluation.setMandatoryParticipant(mandatoryParticipant)
126 if anonymous.strip()=="" :
127 evaluation.setAnonymous(True)
128 else :
129 evaluation.setAnonymous(anonymous)
130 #parse node /evaluation/notifications/
131 notificationsNode = self._getElement(evalNode, "notifications")
132 evaluation.removeAllNotifications()
133 for notificationNode in notificationsNode.childNodes :
134 if isinstance(notificationNode, Element) :
135 from MaKaC.registration import Notification
136 notification = Notification()
137 toList = self._getValue(notificationNode,"toList")
138 ccList = self._getValue(notificationNode,"ccList")
139 notification.setToList( utils.getEmailList(toList) )
140 notification.setCCList( utils.getEmailList(ccList) )
141 evaluation.setNotification(notificationNode.tagName, notification)
142 if params.get("questionsOption","")!="current" : # in ["imported", "both"]
143 if params.get("questionsOption","")=="imported" :
144 evaluation.removeAllQuestions()
145 #parse node /evaluation/questions/
146 questionsNode = self._getElement(evalNode,"questions")
147 for questionNode in questionsNode.childNodes :
148 if isinstance(questionNode, Element):
149 questionValue= self._getValue(questionNode,"questionValue")
150 keyword = self._getValue(questionNode,"keyword")
151 required = self._getValue(questionNode,"required")
152 description = self._getValue(questionNode,"description")
153 help = self._getValue(questionNode,"help")
154 try:
155 question = eval(questionNode.tagName)()
156 if isinstance(question, Question):
157 question.setQuestionValue(questionValue)
158 question.setKeyword(keyword)
159 question.setRequired(required)
160 question.setDescription(description)
161 question.setHelp(help)
162 evaluation.insertQuestion(question)
163 except NameError:
164 raise MaKaCError( _("""xml parse error: unknown question type "%s"
165 """)%questionNode.tagName, _("evaluation"))
166 if isinstance(question, Box):
167 defaultAnswer= self._getValue(questionNode,"defaultAnswer")
168 question.setDefaultAnswer(defaultAnswer)
169 elif isinstance(question, Choice):
170 #parse node /evaluation/questions/*/choiceItems/
171 choiceItemsNode = self._getElement(questionNode,"choiceItems")
172 for choiceItemNode in choiceItemsNode.childNodes :
173 if isinstance(choiceItemNode, Element):
174 itemText = self._getValue(choiceItemNode,"itemText")
175 if itemText.strip()!="" :
176 isSelected = self._getValue(choiceItemNode,"isSelected")
177 question.insertChoiceItem(itemText, isSelected)
179 def _getElement(self, container, tagname):
180 """ Find the first element with given tagname contained in given container element.
181 If not found, return an empty Element tagged "_empty" (in order to avoid crashes).
183 Params:
184 container -- an Element containing the wanted element
185 tagname -- tag (str) of the wanted element
187 if not hasattr(container, "getElementsByTagName"):
188 return Element("_empty")
189 #get a leaf node with given tagname contained within given node
190 leafNodes = container.getElementsByTagName(tagname)
191 if len(leafNodes)>0 :
192 return leafNodes[0]
193 else :
194 return Element("_empty")
196 def _getValue(self, container, tagname):
197 """ finds the first leaf element with given tagname contained in given element,
198 and returns its value.
200 Params:
201 container -- a non leaf Element containing the wanted leaf element
202 tagname -- tag (str) of the wanted leaf element
204 if not hasattr(container, "getElementsByTagName"):
205 return ""
206 leafNode = self._getElement(container, tagname)
207 #given a leaf node, returns its value.
208 return utils.nodeValue(leafNode)
210 class RHEvaluationSetupDataModif( RHEvaluationBase ):
211 """called when you want to change general parameters of your evaluation."""
213 def _process( self ):
214 return evaluations.WPConfModifEvaluationSetupDataModif( self, self._conf ).display()
216 class RHEvaluationSetupPerformDataModif( RHEvaluationBase ):
217 """performs changes to general parameters of the evaluation."""
219 def _process( self ):
220 params = self.getRequestParams()
221 if params.has_key("modify"):
222 from MaKaC.registration import Notification
223 evaluation = self._conf.getEvaluation()
224 try:
225 sDate = datetime( int( params["sYear"] ), \
226 int( params["sMonth"] ), \
227 int( params["sDay"] ) )
228 except ValueError,e:
229 raise FormValuesError("The start date you have entered is not correct: %s"%e, "Evaluation")
230 try:
231 eDate = datetime( int( params["eYear"] ), \
232 int( params["eMonth"] ), \
233 int( params["eDay"] ) )
234 except ValueError,e:
235 raise FormValuesError("The end date you have entered is not correct: %s"%e, "Evaluation")
236 if eDate < sDate :
237 raise FormValuesError("End date can't be before start date!", "Evaluation")
238 evaluation.setStartDate(sDate)
239 evaluation.setEndDate(eDate)
240 evaluation.setAnonymous(params.has_key("anonymous"))
241 evaluation.setAnnouncement(params.get("announcement",""))
242 evaluation.setTitle( params.get("title","Evaluation") )
243 evaluation.setContactInfo( params.get("contactInfo","") )
244 evaluation.setMandatoryParticipant(params.has_key("mandatoryParticipant"))
245 evaluation.setMandatoryAccount(params.has_key("mandatoryAccount"))
246 try:
247 evaluation.setSubmissionsLimit( params.get("submissionsLimit",0) )
248 except ValueError,e:
249 raise FormValuesError("You must enter an integer for 'Max number of submissions'!", "Evaluation")
250 #notifications
251 evaluationStartNotifyTo = utils.getEmailList(params.get("evaluationStartNotifyTo", ""))
252 evaluationStartNotifyCc = utils.getEmailList(params.get("evaluationStartNotifyCc", ""))
253 newSubmissionNotifyTo = utils.getEmailList(params.get("newSubmissionNotifyTo", ""))
254 newSubmissionNotifyCc = utils.getEmailList(params.get("newSubmissionNotifyCc", ""))
255 if params.has_key("notifyAllAdherents") :
256 if self.getWebFactory()!=None : #Event == Meeting/Lecture
257 for participant in evaluation.getConference().getParticipation().getParticipantList() :
258 email = participant.getEmail()
259 if email not in evaluationStartNotifyTo :
260 evaluationStartNotifyTo.append(email)
261 else : #Event == Conference
262 for registrant in evaluation.getConference().getRegistrantsList() :
263 email = registrant.getEmail()
264 if email not in evaluationStartNotifyTo :
265 evaluationStartNotifyTo.append(email)
266 if len( evaluationStartNotifyTo + evaluationStartNotifyCc ) < 1 :
267 evaluation.removeNotification(Evaluation._EVALUATION_START)
268 else :
269 evaluationStartNotification = Notification()
270 evaluationStartNotification.setToList(evaluationStartNotifyTo)
271 evaluationStartNotification.setCCList(evaluationStartNotifyCc)
272 evaluation.setNotification(Evaluation._EVALUATION_START, evaluationStartNotification)
273 if len( newSubmissionNotifyTo + newSubmissionNotifyCc ) < 1 :
274 evaluation.removeNotification(Evaluation._NEW_SUBMISSION)
275 else :
276 newSubmissionNotification = Notification()
277 newSubmissionNotification.setToList(newSubmissionNotifyTo)
278 newSubmissionNotification.setCCList(newSubmissionNotifyCc)
279 evaluation.setNotification(Evaluation._NEW_SUBMISSION, newSubmissionNotification)
281 #redirecting...
282 self._redirect(urlHandlers.UHConfModifEvaluationSetup.getURL(self._conf))
285 class RHEvaluationEdit(RHEvaluationBase):
286 """Edition of questions of an Evaluation."""
288 def _process(self):
289 return evaluations.WPConfModifEvaluationEdit(self, self._conf).display()
291 class RHEvaluationEditPerformChanges( RHEvaluationBase ):
292 """performs changes for Evaluation questions."""
294 def _process( self ):
295 ####################
296 #get some variables#
297 ####################
298 self._evaluation = self._conf.getEvaluation()
299 params = self.getRequestParams()
300 save = params.has_key("save")
301 type = params.get("type", "")
302 mode = params.get("mode","")
303 questionValue = params.get("questionValue","").strip()
304 keyword = params.get("keyword","").strip()
306 ###################
307 #check consistency#
308 ###################
309 if save:
310 if questionValue=="" or keyword=="":
311 raise FormValuesError("Please enter question and keyword!", "Evaluation")
312 if type in Question._CHOICE_SUBTYPES :
313 choiceItem_1 = params.get("choiceItem_1","").strip()
314 choiceItem_2 = params.get("choiceItem_2","").strip()
315 if choiceItem_1=="" or choiceItem_2=="":
316 raise FormValuesError("Please enter all values for Choice Items.", "Evaluation")
317 if choiceItem_1==choiceItem_2:
318 raise FormValuesError("Please enter different values for Choice Items.", "Evaluation")
320 ##########
321 #Add mode#
322 ##########
323 if mode==Question._ADD and save:
324 #create new Question depending on the type selected...
325 if type == Question._TEXTBOX: question = Textbox()
326 elif type == Question._TEXTAREA: question = Textarea()
327 elif type == Question._PASSWORD: question = Password()
328 elif type == Question._SELECT: question = Select()
329 elif type == Question._RADIO: question = Radio()
330 elif type == Question._CHECKBOX: question = Checkbox()
331 else:
332 raise FormValuesError("unknown question type!", "Evaluation")
333 #set params for question
334 self._setQuestionParams(question, params)
336 ###########
337 #Edit mode#
338 ###########
339 elif mode==Question._EDIT:
340 #the user edited a question.
341 if save:
342 #DON'T USE params:newPos BUT params:questionPos which is edited question position!!!
343 question = self._evaluation.getQuestionAt(params.get("questionPos",-1))
344 if question == None:
345 raise FormValuesError("No question found at the given position!", "Evaluation")
346 else:
347 #set params for question
348 self._setQuestionParams(question, params)
349 #look if the user requested to change the position of a question.
350 else:
351 questions_nb = self._evaluation.getNbOfQuestions()
352 for i in range(1, questions_nb+1):
353 posChange = int(params.get("posChange_"+str(i), -1))
354 if posChange>0:
355 self._evaluation.getQuestionAt(i).setPosition(posChange)
357 #############
358 #Delete mode#
359 #############
360 elif mode==Question._REMOVE:
361 #DON'T USE params:newPos BUT params:questionPos which is edited question position!!!
362 self._evaluation.removeQuestion(int(params.get("questionPos",-1)))
364 #redirecting...
365 self._redirect(urlHandlers.UHConfModifEvaluationEdit.getURL(self._conf))
367 def _setQuestionParams(self, question, params):
368 """ set the parameters for the given question with the dictionary (params).
369 Params:
370 question -- question to be edited.
371 params -- dictionary of parameters.
373 #check
374 if not isinstance(question, Question) or not isinstance(params, dict): return
375 #setting the attributes for the Question...
376 question.setQuestionValue(params.get("questionValue",""))
377 question.setKeyword(params.get("keyword",""))
378 question.setRequired(params.has_key("required"))
379 question.setDescription(params.get("description",""))
380 question.setHelp(params.get("help",""))
381 # question.setLevel(params.get("level",1))
382 #position
383 newPos = int(params.get("newPos",-1))
384 #[ADD] When no evaluation for this question: add question in the evaluation.
385 if question.getEvaluation()==None:
386 #USE params:newPos for setting the new posision.
387 self._evaluation.insertQuestion(question, newPos)
388 #[EDIT] When this question is linked to an evaluation: set its position.
389 else:
390 question.setPosition(newPos)
391 #default answer
392 if isinstance(question, Box):
393 question.setDefaultAnswer(params.get("defaultAnswer",""))
394 #choice items
395 elif isinstance(question, Choice):
396 selectedChoiceItems = params.get("selectedChoiceItems",[])
397 #reinit choice items if already exists.
398 question.removeAllChoiceItems()
399 #init variables for while loop
400 currentItemId = 1
401 itemText = ""
402 while itemText != None:
403 itemText = params.get( "choiceItem_"+str(currentItemId) , None )
404 #If text has been well filled in, add it. Otherwise do nothing (i.e. don't add it).
405 if itemText!=None and itemText.strip()!="" :
406 isSelected = str(currentItemId) in selectedChoiceItems
407 question.insertChoiceItem(itemText, isSelected)
408 currentItemId += 1
411 class RHEvaluationPreview(RHEvaluationBase):
412 """Preview of an Evaluation."""
414 def _process(self):
415 params = self.getRequestParams()
416 if params.get("status","")==_("submitted") and params.has_key("submit"):
417 return evaluations.WPConfModifEvaluationPreviewSubmitted(self, self._conf).display()
418 else:
419 return evaluations.WPConfModifEvaluationPreview(self, self._conf).display()
422 class RHEvaluationResults(RHEvaluationBase):
423 """Results of an Evaluation."""
425 def _process(self):
426 return evaluations.WPConfModifEvaluationResults(self, self._conf).display()
429 class RHEvaluationResultsOptions(RHEvaluationBase):
430 """Do asked actions for the results."""
432 def _process(self):
433 ####################
434 #get some variables#
435 ####################
436 evaluation = self._conf.getEvaluation()
437 params = self.getRequestParams()
438 export = params.has_key("exportStats")
439 select = params.has_key(Evaluation._SELECT_SUBMITTERS)
440 remove = params.has_key(Evaluation._REMOVE_SUBMITTERS)
442 ##############
443 #export stats# (CSV)
444 ##############
445 if export:
446 excelGen = ExcelGenerator()
447 #csv: header
448 excelGen.addValue("Pseudonym")
449 excelGen.addValue("submissionDate")
450 excelGen.addValue("modificationDate")
451 for question in evaluation.getQuestions() :
452 excelGen.addValue(question.getKeyword())
453 #fill blank cells for the choice items if there are.
454 if question.getAnswerClass()==MultipleChoicesAnswer :
455 #above: only Checkbox is concerned but this way it's more generic.
456 for i in range(1, question.getNbOfChoiceItems()) :
457 excelGen.addValue("---")
458 excelGen.newLine()
459 #csv: data
460 for submission in evaluation.getSubmissions() :
461 excelGen.newLine()
462 excelGen.addValue(submission.getSubmitterName())
463 excelGen.addValue(submission.getSubmissionDate(str))
464 excelGen.addValue(submission.getModificationDate(str))
465 for question in evaluation.getQuestions() :
466 answer = submission.getDictQuestionsAnswers().get(question,None)
467 if question.getAnswerClass()==TextAnswer :
468 if answer==None :
469 excelGen.addValue("")
470 else:
471 excelGen.addValue(utils.putbackQuotes(answer.getAnswerValue()))
472 elif question.getAnswerClass()==MultipleChoicesAnswer :
473 if answer==None :
474 for i in range(0, question.getNbOfChoiceItems()) :
475 excelGen.addValue("")
476 else:
477 for ci in question.getChoiceItemsOrderedKeys() :
478 if ci in answer.getAnswerValue() :
479 excelGen.addValue(utils.putbackQuotes(ci))
480 else :
481 excelGen.addValue("")
482 return send_file(Evaluation._CSV_FILENAME, StringIO(excelGen.getExcelContent()), 'CSV')
484 ########
485 #remove#
486 ########
487 elif remove:
488 return evaluations.WPConfModifEvaluationResultsSubmitters(
489 self, self._conf, Evaluation._REMOVE_SUBMITTERS).display()
490 ########
491 #select#
492 ########
493 elif select:
494 return evaluations.WPConfModifEvaluationResultsSubmitters(
495 self, self._conf, Evaluation._SELECT_SUBMITTERS).display()
496 #redirecting...
497 self._redirect(urlHandlers.UHConfModifEvaluationResults.getURL(self._conf))
499 class RHEvaluationResultsSubmittersActions(RHEvaluationBase):
500 """Do asked actions for the submitters."""
502 def _process(self):
503 ####################
504 #get some variables#
505 ####################
506 evaluation = self._conf.getEvaluation()
507 params = self.getRequestParams()
508 remove = params.has_key(Evaluation._REMOVE_SUBMITTERS)
509 select = params.has_key(Evaluation._SELECT_SUBMITTERS)
510 sessionVarName = "selectedSubmissions_%s_%s"%(self._conf.getId(), evaluation.getId())
512 ########
513 #remove#
514 ########
515 if remove:
516 removedSubmitters = set(params.get("removedSubmitters", []))
517 if removedSubmitters:
518 #remove submissions in session variable
519 if sessionVarName in session:
520 selectedSubmissions = session.setdefault(sessionVarName, set())
521 selectedSubmissions -= removedSubmitters
522 session.modified = True
523 #remove submissions in database
524 removedSubmissions = set(s for s in evaluation.getSubmissions() if s.getId() in removedSubmitters)
525 for submission in removedSubmissions:
526 evaluation.removeSubmission(submission)
528 ##########
529 #selected#
530 ##########
531 elif select:
532 selectedSubmitters = params.get("selectedSubmitters", [])
533 if isinstance(selectedSubmitters, str):
534 selectedSubmitters = [selectedSubmitters]
535 #insert selected submissions list in a session variable
536 selected = set(s.getId() for s in evaluation.getSubmissions() if s.getId() in selectedSubmitters)
537 if len(selected) != len(evaluation.getSubmissions()):
538 session[sessionVarName] = selected
539 else:
540 # Everything selected => DELETE session var since that means "everything" and will stay that way when
541 # a new evaluation is submitted.
542 session.pop(sessionVarName, None)
544 #redirecting...
545 self._redirect(urlHandlers.UHConfModifEvaluationResults.getURL(self._conf))