# Copyright (c) 2009 - 2016, UChicago Argonne, LLC.
# See LICENSE file for details.
'''
Data model for a review session: proposals, reviewers, topics, and analyses
'''
import datetime
from lxml import etree
import history
import os, sys
from PyQt4 import QtCore
import StringIO
import traceback
import main_window
import prop_mvc_data
import resources
import reviewer
import revu_mvc_data
import tools
import topics
UI_FILE = 'main_window.ui'
AGUP_MASTER_ROOT_TAG = 'AGUP_Review_Session'
AGUP_XML_SCHEMA_FILE = resources.resource_file('agup_review_session.xsd')
AGUP_MASTER_VERSION = '1.0'
SUBJECT_STRENGTH_FULL = 1.0
[docs]class AGUP_Data(QtCore.QObject):
'''
Complete data for a PRP review session
'''
def __init__(self, parent_window = None, config = None):
import settings
QtCore.QObject.__init__(self)
self.parent_window = parent_window
self.settings = config or settings.ApplicationQSettings()
self.clearAllData()
self.modified = False
[docs] def clearAllData(self):
'''
clear all data (except for self.settings)
'''
import email_template
self.proposals = prop_mvc_data.AGUP_Proposals_List(self)
self.reviewers = revu_mvc_data.AGUP_Reviewers_List(self)
self.topics = topics.Topics()
self.email = email_template.EmailTemplate()
[docs] def openPrpFile(self, filename):
'''
'''
if not os.path.exists(filename):
history.addLog('PRP File not found: ' + filename)
return False
self.clearAllData()
filename = str(filename)
self.importTopics(filename)
self.importReviewers(filename)
self.importProposals(filename)
self.importEmailTemplate(filename)
self.modified = False
return True
[docs] def write(self, filename):
'''
write this data to an XML file
'''
if self.topics is None: return
if self.proposals is None: return
if self.reviewers is None: return
doc = etree.parse( StringIO.StringIO('<' + AGUP_MASTER_ROOT_TAG + '/>') )
root = doc.getroot()
root.attrib['cycle'] = self.proposals.cycle
root.attrib['version'] = AGUP_MASTER_VERSION
root.attrib['time'] = str(datetime.datetime.now())
self.topics.writeXml(root, False)
self.reviewers.writeXmlNode(root)
self.proposals.writeXmlNode(root)
self.email.writeXmlNode(root)
s = etree.tostring(doc, pretty_print=True, xml_declaration=True, encoding=tools.XML_CODEPOINT)
open(filename, 'w').write(s)
[docs] def importProposals(self, xmlFile):
'''
import a Proposals XML file as generated by the APS or proposals from a PRP Project file
'''
props = prop_mvc_data.AGUP_Proposals_List(self)
props.importXml(xmlFile)
if len(self.reviewers) == 0:
self._auto_assess(props)
self._restore_assignments(props)
_review_cycle_settings = self.settings.getReviewCycle()
_review_cycle_proposals = props.cycle or _review_cycle_settings
if _review_cycle_settings in (None, '', _review_cycle_proposals):
self.proposals = props
self.settings.setReviewCycle(_review_cycle_proposals or '')
else:
msg = 'Cannot import proposals from cycle "' + _review_cycle_proposals
msg += '" into PRP session for cycle "' + _review_cycle_settings + '"'
raise KeyError(msg)
def _auto_assess(self, props):
'''
get the list of reviewers and topics from the proposals, assess topic values for each proposal
'''
# get list of subjects from all proposals
names = []
subjects = []
for prop in props:
for full_name in prop.eligible_reviewers.keys():
if full_name not in names:
names.append(full_name)
for subject in prop.getSubjects():
if subject not in subjects:
subjects.append(subject)
self.topics.addTopics(subjects)
# build up a list of reviewers from the proposals
for item, full_name in enumerate(names):
sort_name = full_name.split()[-1] + '_' + str(item)
rvwr_obj = reviewer.AGUP_Reviewer_Data()
for key in rvwr_obj.tagList:
rvwr_obj.setKey(key, 'unknown')
rvwr_obj.setKey('full_name', full_name)
rvwr_obj.setKey('name', sort_name)
rvwr_obj.addTopics(subjects)
rr = self.reviewers.reviewers
rr[sort_name] = rvwr_obj
self.reviewers.reviewer_sort_list.append(sort_name)
self.reviewers.reviewer_sort_list = sorted(rr.keys())
# auto-assign topic values to proposal
for prop in props:
prop.addTopics(subjects) # proposal gets all the known topics
for subject in prop.getSubjects():
prop.setTopic(subject, SUBJECT_STRENGTH_FULL) # this subject was selected by proposer
def _restore_assignments(self, props):
'''restore any assessments or assignments'''
queue_remove_reviewers = []
for new_proposal in props:
prop_id = new_proposal.getKey('proposal_id')
if self.proposals.exists(prop_id):
existing_prop = self.proposals.getProposal(prop_id)
new_proposal.topics = existing_prop.topics
for full_name, role in existing_prop.eligible_reviewers.items():
if full_name in ('', None):
continue
if role is None:
if full_name not in new_proposal.eligible_reviewers:
if full_name not in queue_remove_reviewers:
queue_remove_reviewers.append(full_name)
continue
if full_name not in new_proposal.eligible_reviewers:
msg = 'Reviewer "' + str(full_name)
msg += ' has assigned role of ' + str(role)
msg += ' on proposal ' + prop_id
msg += ' but is not on the list of reviewers for that proposal.'
raise ValueError(msg)
# assign the reviewer's role
rvwr = self.reviewers.getByFullName(full_name)
new_proposal.setAssignedReviewer(rvwr, role)
if len(new_proposal.getTopicList()) == 0:
subject_list = new_proposal.getSubjects()
new_proposal.addTopics(self.topics.getTopicList())
for subject in subject_list:
if self.topics.exists(subject):
new_proposal.setTopic(subject, SUBJECT_STRENGTH_FULL)
for full_name in queue_remove_reviewers:
self.reviewers.removeReviewer(full_name)
self.setReviewersModel(self.reviewers)
[docs] def importReviewers(self, xmlFile):
'''
import a complete set of reviewers (usually from a previous review cycle's file)
Completely replace the set of reviewers currently in place.
'''
rvwrs = revu_mvc_data.AGUP_Reviewers_List(self)
rvwrs.importXml(xmlFile) # pass exceptions straight to the caller
if len(rvwrs) > 0: # synchronize lists of topics
sort_name = rvwrs.getByIndex(0)
reviewer = rvwrs.getReviewer(sort_name)
for topic in reviewer.topics:
if not self.topics.exists(topic):
self.topics.add(topic)
self.proposals.addTopic(topic)
for topic in self.topics:
if not reviewer.topics.exists(topic):
rvwrs.addTopic(topic)
# this gets complicated
# TODO: remove those not in new set,
# merge those also in old set and new
# list of reviewers has changed, update the model
self.setReviewersModel(rvwrs)
self.reviewers = rvwrs
[docs] def setReviewersModel(self, rvwrs):
'''
set the data model for the list of reviewers
'''
if self.parent_window is not None:
win = self.parent_window.windows[main_window.REVIEWER_VIEW]
if win is not None:
win.setModel(rvwrs)
[docs] def importTopics(self, xmlFile):
'''
import a complete set of Topics (usually from a previous PRP Project file)
Completely replace the set of Topics currently in place.
'''
topics_obj = topics.Topics()
topics_obj.importXml(xmlFile, False)
# merge with other lists
for topic in topics_obj.topics:
if not self.topics.exists(topic):
self.topics.add(topic)
self.proposals.addTopic(topic)
self.reviewers.addTopic(topic)
[docs] def importEmailTemplate(self, xmlFile):
'''
import the email template support
'''
self.email.importXml(xmlFile) # pass exceptions straight to the caller
[docs] def getCycle(self):
'''the review cycle, as defined by the proposals'''
if self.proposals is None:
return ''
return self.proposals.cycle
def dev_test2():
agup = AGUP_Data()
agup.openPrpFile('project/agup_project.xml')
print agup
if __name__ == '__main__':
# developer_testing_of_this_module()
dev_test2()