def process_once(config): count = 0 n_errors = 0 while True: with model.session_scope() as session: record = (session.query( model.Submission, model.Survey.modified).join(model.Survey).filter( (model.Submission.modified < model.Survey.modified) | ((model.Submission.modified == None) & (model.Survey.modified != None))).first()) if record is None: break sub, htime = record log.info("Processing %s, %s < %s", sub, sub and sub.modified, htime) if count == 0: log.info("Starting new job") calculator = Calculator.scoring(sub) calculator.mark_entire_survey_dirty(sub.survey) calculator.execute() if sub.error: n_errors += 1 count += 1 sub.modified = sub.survey.modified session.commit() log.info("Successfully recalculated scores for %d submissions.", count) log.info("Of those, %d contain user errors.", n_errors) return count, n_errors
def create_surveys(hsons): surveys = [] for hson in hsons: survey = model.Survey(program=program, title=hson['title'], description=hson['description'], deleted=hson.get('deleted', False)) survey.structure = hson['structure'] session.add(survey) # Explicitly add to collection because backref is one-way. if not hson.get('deleted', False): program.surveys.append(survey) survey.qnodes = create_qnodes(hson['qnodes'], survey) survey.qnodes.reorder() if 'dependencies' in hson: link_measures(survey, hson['dependencies']) calculator = Calculator.structural() calculator.mark_entire_survey_dirty(survey) calculator.execute() # print_survey(survey) return surveys
def create_submission(self, survey, user): session = object_session(survey) program = survey.program submission = model.Submission(program=program, organisation=user.organisation, survey=survey, title="First submission", approval='draft') session.add(submission) for m in program.measures: # Preload response type to avoid autoflush response_type = m.response_type qnode_measure = m.get_qnode_measure(survey) if not qnode_measure: continue response = model.Response(qnode_measure=qnode_measure, submission=submission, user=user) response.attachments = [] response.not_relevant = False response.modified = datetime.datetime.utcnow() response.approval = 'final' response.comment = "Response for %s" % m.title session.add(response) if response_type.name == 'Yes / No': response.response_parts = [{'index': 1, 'note': "Yes"}] elif response_type.name in { 'Numerical', 'External Numerical', 'Planned', 'Actual' }: response.response_parts = [{'value': 1}] else: raise ValueError("Unknown response type") response.attachments.append( model.Attachment(file_name="File %s 1" % m.title, url="Bar", storage='external', organisation=user.organisation)) response.attachments.append( model.Attachment(file_name="File %s 2" % m.title, url="Baz", storage='external', organisation=user.organisation)) response.attachments.append( model.Attachment(file_name="File %s 3" % m.title, blob=b'A blob', storage='external', organisation=user.organisation)) session.flush() calculator = Calculator.scoring(submission) calculator.mark_entire_survey_dirty(submission.survey) calculator.execute() submission.approval = 'final' session.flush() return submission
def put(self, response_type_id): '''Update existing''' program_id = self.get_argument('programId', '') with model.session_scope() as session: user_session = self.get_user_session(session) response_type = (session.query(model.ResponseType).get( (response_type_id, program_id))) if not response_type: raise errors.MissingDocError("No such response type") policy = user_session.policy.derive({ 'program': response_type.program, 'surveygroups': response_type.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('response_type_edit') if 'name' in self.request_son: rt_by_name = (session.query(model.ResponseType).filter( model.ResponseType.program_id == program_id).filter( model.ResponseType.name == self.request_son.name).first()) if rt_by_name and rt_by_name != response_type: raise errors.ModelError( "'" + self.request_son.name + "' as a response type of that name already exists") try: self._update(response_type, self.request_son) except ResponseTypeError as e: raise errors.ModelError(str(e)) except voluptuous.error.Error as e: raise errors.ModelError(str(e)) except Exception as e: raise errors.ModelError(str(e)) verbs = [] # Check if modified now to avoid problems with autoflush later if session.is_modified(response_type): verbs.append('update') calculator = Calculator.structural() for measure in response_type.measures: for qnode_measure in measure.qnode_measures: calculator.mark_measure_dirty(qnode_measure) calculator.execute() act = Activities(session) act.record(user_session.user, response_type, verbs) act.ensure_subscription(user_session.user, response_type, response_type.program, self.reason) self.get(response_type_id)
def delete(self, qnode_id): '''Delete existing.''' if not qnode_id: raise errors.MethodError("Question node ID required") program_id = self.get_argument('programId', '') with model.session_scope() as session: user_session = self.get_user_session(session) qnode = ( session.query(model.QuestionNode) .options(joinedload('program')) .options(joinedload('program.surveygroups')) .get((qnode_id, program_id))) if not qnode: raise errors.MissingDocError("No such question node") log.debug("deleting: %s", qnode) program = qnode.program survey = qnode.survey parent = qnode.parent policy = user_session.policy.derive({ 'program': program, 'survey': survey, 'surveygroups': program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_del') act = Activities(session) if not qnode.deleted: act.record(user_session.user, qnode, ['delete']) act.ensure_subscription( user_session.user, qnode, qnode.program, self.reason) qnode.deleted = True calculator = Calculator.structural() if parent: parent.children.reorder() calculator.mark_qnode_dirty(parent) else: survey.qnodes.reorder() calculator.mark_survey_dirty(survey) calculator.execute() self.finish()
def create_submission(self): # Respond to a survey with model.session_scope() as session: program = session.query(model.Program).one() user = (session.query( model.AppUser).filter_by(email='clerk').one()) organisation = (session.query( model.Organisation).filter_by(name='Utility').one()) survey = (session.query( model.Survey).filter_by(title='Survey 1').one()) submission = model.Submission(program_id=program.id, organisation_id=organisation.id, survey_id=survey.id, title="Submission", approval='draft') session.add(submission) for m in program.measures: # Preload response type to avoid autoflush response_type = m.response_type qnode_measure = m.get_qnode_measure(survey) if not qnode_measure: continue response = model.Response(submission=submission, qnode_measure=qnode_measure, user=user) response.attachments = [] response.not_relevant = False response.modified = sa.func.now() response.approval = 'final' response.comment = "Response for %s" % m.title session.add(response) if response_type.name == 'Yes / No': response.response_parts = [{'index': 1, 'note': "Yes"}] else: response.response_parts = [{'value': 1}] calculator = Calculator.scoring(submission) calculator.mark_entire_survey_dirty(submission.survey) calculator.execute() functions = list(submission.rnodes) self.assertAlmostEqual(functions[0].score, 33) self.assertAlmostEqual(functions[1].score, 0) self.assertAlmostEqual(functions[0].qnode.total_weight, 33) self.assertAlmostEqual(functions[1].qnode.total_weight, 0) return submission.id
def set_approval(self, session, rnode, approval, user_session): promote = self.get_arguments('promote') missing = self.get_argument('missing', '') submission = rnode.submission promoted = demoted = created = 0 calculator = Calculator.scoring(submission) for response, is_new in self.walk_responses( session, rnode, missing, user_session.user): if is_new: response.not_relevant = True response.approval = approval response.comment = "*Marked Not Relevant by bulk approval " \ "process (was previously empty)*" created += 1 else: i1 = APPROVAL_STATES.index(response.approval) i2 = APPROVAL_STATES.index(approval) if i1 < i2 and 'PROMOTE' in promote: response.approval = approval response.modified = func.now() promoted += 1 elif i1 > i2 and 'DEMOTE' in promote: response.approval = approval response.modified = func.now() demoted += 1 calculator.mark_measure_dirty(response.qnode_measure) calculator.execute() if created: self.reason("Created %d (NA)" % created) if demoted: self.reason("Demoted %d" % demoted) if promoted: self.reason("Promoted %d" % promoted) if created == promoted == demoted == 0: self.reason("No changes to approval status")
def put(self, submission_id): '''Update existing.''' if submission_id == '': raise errors.MethodError("Submission ID required") approval = self.get_argument('approval', '') with model.session_scope() as session: user_session = self.get_user_session(session) submission = session.query(model.Submission).get(submission_id) if not submission: raise errors.MissingDocError("No such submission") policy = user_session.policy.derive({ 'org': submission.organisation, 'approval': approval, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('submission_edit') current_level = 0 verbs = [] if approval: self.check_approval_down_one(session, submission, approval) current_level = APPROVAL_STATES.index(submission.approval) if approval != submission.approval: verbs.append('state') submission.approval = approval self._update(submission, self.request_son) if session.is_modified(submission): verbs.append('update') # update measures approval state, when save edited submission no approval, so here should not if approval != '': approval_level = APPROVAL_STATES.index(approval) if 'state' in verbs and approval_level > current_level: approval_level = approval_level - 1 responses = (session.query(model.Response).filter( model.Response.submission_id == submission.id, model.Response.approval == APPROVAL_STATES[approval_level])) for response in responses: # update = updater(response, error_factory=errors.ModelError) # update('title', son) response.approval = approval try: calculator = Calculator.scoring(submission) calculator.mark_measure_dirty( response.qnode_measure) calculator.execute() except ResponseTypeError as e: raise errors.ModelError(str(e)) act = Activities(session) act.record(user_session.user, response, verbs) act.ensure_subscription(user_session.user, response, response.submission, self.reason) if submission.deleted: submission.deleted = False verbs.append('undelete') act = Activities(session) act.record(user_session.user, submission, verbs) act.ensure_subscription(user_session.user, submission, submission.organisation, self.reason) self.get(submission_id)
def duplicate(self, submission, s_submission, session): measure_ids = { str(qm.measure_id) for qm in submission.survey.ordered_qnode_measures } qnode_ids = {str(q.id) for q in submission.survey.ordered_qnodes} s_rnodes = (session.query(model.ResponseNode).filter_by( submission_id=s_submission.id).filter( model.ResponseNode.qnode_id.in_(qnode_ids)).all()) for rnode in s_rnodes: if str(rnode.qnode_id) not in qnode_ids: continue # Duplicate session.expunge(rnode) make_transient(rnode) # Customise rnode.program = submission.program rnode.submission = submission session.add(rnode) session.flush() # No need to flush because no dependencies for response in s_submission.responses: if str(response.measure_id) not in measure_ids: continue attachments = list(response.attachments) # Fetch lazy-loaded fields response.comment # Duplicate session.expunge(response) make_transient(response) # Customise response.submission_id = submission.id response.program_id = submission.program_id response.survey_id = submission.survey_id response.approval = 'draft' session.add(response) session.flush() # Same thing for attachments for attachment in attachments: # Fetch lazy-loaded fields attachment.blob # Duplicate session.expunge(attachment) make_transient(attachment) attachment.id = None # Customise attachment.response = response session.add(attachment) session.flush() calculator = Calculator.scoring(submission) calculator.mark_entire_survey_dirty(submission.survey) calculator.execute()
def process_submission_file(self, all_rows, session, submission, user): program_qnodes = (session.query( model.QuestionNode).filter_by(program_id=submission.program.id)) try: order = title = '' for row_num in range(0, len(all_rows) - 1): order, title = self.parse_order_title(all_rows, row_num, "A") function = program_qnodes.filter_by(parent_id=None, title=title).one() log.debug("function: %s", function) function_order = order order, title = self.parse_order_title(all_rows, row_num, "B") process = program_qnodes.filter_by(parent_id=function.id, title=title).one() log.debug("process: %s", process) order, title = self.parse_order_title(all_rows, row_num, "C") subprocess = program_qnodes.filter_by(parent_id=process.id, title=title).one() log.debug("subprocess: %s", subprocess) order, title = self.parse_order_title(all_rows, row_num, "D") measure = [ qm.measure for qm in subprocess.qnode_measures if qm.measure.title.split('\n')[0] == title ] if len(measure) == 1: measure = measure[0] else: raise Exception( "This survey does not match the target survey.") log.debug("measure: %s", measure) log.debug("measure response_type: %s", measure.response_type.name) response = model.Response() response.program_id = submission.program.id response.survey_id = submission.survey.id response.measure_id = measure.id response.submission_id = submission.id response.user_id = user.id response.comment = all_rows[row_num][col2num("K")] # FIXME: Hard-coded; should be read from file response.not_relevant = False response.modified = datetime.datetime.utcnow() response.approval = 'draft' response_part = [] response_part.append( self.parse_response_type(all_rows, row_num, measure.response_type, "E")) if function_order != "7": response_part.append( self.parse_response_type(all_rows, row_num, measure.response_type, "F")) response_part.append( self.parse_response_type(all_rows, row_num, measure.response_type, "G")) response_part.append( self.parse_response_type(all_rows, row_num, measure.response_type, "H")) response.response_parts = response_part response.audit_reason = "Import" session.add(response) except sqlalchemy.orm.exc.NoResultFound: raise errors.ModelError( "Survey structure does not match: Row %d: %s %s" % (row_num + 2, order, title)) except ImportError as e: raise errors.ModelError("Row %d: %s %s: %s" % (row_num + 2, order, title, str(e))) except Exception as e: raise errors.InternalModelError( "Row %d: %s %s: %s" % (row_num + 2, order, title, str(e))) calculator = Calculator.scoring(submission) calculator.mark_entire_survey_dirty(submission.survey) calculator.execute()
def set_relevance(self, session, rnode, relevance, user_session): not_relevant = relevance == 'NOT_RELEVANT' if not_relevant: missing = self.get_argument('missing', '') else: # When marking responses as not NA, just ignore missing responses. # It's not possible to create new ones because a non-NA response # must have its response parts filled in. missing = 'IGNORE' submission = rnode.submission changed = failed = created = 0 calculator = Calculator.scoring(submission) for response, is_new in self.walk_responses( session, rnode, missing, user_session.user): if not_relevant: response.not_relevant = True if is_new: response.approval = submission.approval response.comment = ( "*Marked Not Relevant as a bulk action " "(was previously empty)*") created += 1 else: changed += 1 else: response.not_relevant = False changed += 1 policy = user_session.policy.derive({ 'org': response.submission.organisation, 'submission': response.submission, 'approval': response.approval, 'index': APPROVAL_STATES.index, 'surveygroups': response.submission.surveygroups, }) policy.verify('surveygroup_interact') try: policy.verify('response_edit') except errors.AuthzError as e: err = ( "Response %s: %s. You might need to downgrade the " "response's approval status. You can use the bulk " "approval tool for this.".format( response.qnode_measure.get_path(), e)) raise errors.AuthzError(err) calculator.mark_measure_dirty(response.qnode_measure) calculator.execute() if created: self.reason("Created %d" % created) if changed: self.reason("Changed %d" % changed) if failed: self.reason( "%d measures could not be changed, because a relevant " " response must have valid data." % failed) if created == changed == failed == 0: self.reason("No changes to relevance")
def put(self, submission_id, qnode_id): '''Save (create or update).''' approval = self.get_argument('approval', '') relevance = self.get_argument('relevance', '') with model.session_scope() as session: user_session = self.get_user_session(session) submission = ( session.query(model.Submission) .get(submission_id)) if not submission: raise errors.MissingDocError("No such submission") policy = user_session.policy.derive({ 'org': submission.organisation, 'submission': submission, 'approval': approval, 'index': APPROVAL_STATES.index, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('rnode_edit') rnode = ( session.query(model.ResponseNode) .get((submission_id, qnode_id))) verbs = [] if not rnode: qnode = ( session.query(model.QuestionNode) .get((qnode_id, submission.program.id))) if qnode is None: raise errors.MissingDocError("No such question node") rnode = model.ResponseNode.from_qnode( qnode, submission, create=True) importance = self.request_son.get('importance') if importance is not None: if int(importance) <= 0: self.request_son['importance'] = None elif int(importance) > 5: self.request_son['importance'] = 5 urgency = self.request_son.get('urgency') if urgency is not None: if int(urgency) <= 0: self.request_son['urgency'] = None elif int(urgency) > 5: self.request_son['urgency'] = 5 self._update(rnode, self.request_son) if session.is_modified(rnode): verbs.append('update') session.flush() if approval: policy.verify('submission_response_approval') yield self.set_approval( session, rnode, approval, user_session) verbs.append('state') if relevance: yield self.set_relevance( session, rnode, relevance, user_session) verbs.append('update') try: calculator = Calculator.scoring(submission) calculator.mark_qnode_dirty(rnode.qnode) calculator.execute() except ResponseTypeError as e: raise errors.ModelError(str(e)) act = Activities(session) act.record(user_session.user, rnode, verbs) act.ensure_subscription( user_session.user, rnode, rnode.submission, self.reason) self.get(submission_id, qnode_id)
def put(self, submission_id, measure_id): '''Save (create or update).''' with model.session_scope(version=True) as session: user_session = self.get_user_session(session) submission = (session.query(model.Submission).get(submission_id)) if not submission: raise errors.MissingDocError("No such submission") response = (session.query(model.Response).get( (submission_id, measure_id))) verbs = [] if response is None: program_id = submission.program_id survey_id = submission.survey_id qnode_measure = (session.query(model.QnodeMeasure).get( (program_id, survey_id, measure_id))) if qnode_measure is None: raise errors.MissingDocError("No such measure") response = model.Response(qnode_measure=qnode_measure, submission=submission, approval='draft') session.add(response) verbs.append('create') else: same_user = response.user.id == user_session.user.id td = datetime.datetime.utcnow() - response.modified hours_since_update = td.total_seconds() / 60 / 60 if same_user and hours_since_update < 8: response.version_on_update = False modified = self.request_son.get("latest_modified", 0) # Convert to int to avoid string conversion errors during # JSON marshalling. if int(modified) < int(response.modified.timestamp()): raise errors.ModelError( "This response has changed since you loaded the" " page. Please copy or remember your changes and" " refresh the page.") verbs.append('update') if self.request_son['approval'] != response.approval: verbs.append('state') self._update(response, self.request_son, user_session.user) if not session.is_modified(response) and 'update' in verbs: verbs.remove('update') policy = user_session.policy.derive({ 'org': submission.organisation, 'submission': submission, 'approval': response.approval, 'index': APPROVAL_STATES.index, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('response_edit') session.flush() # Prevent creating a second version during following operations response.version_on_update = False try: calculator = Calculator.scoring(submission) calculator.mark_measure_dirty(response.qnode_measure) calculator.execute() except ResponseTypeError as e: raise errors.ModelError(str(e)) act = Activities(session) act.record(user_session.user, response, verbs) act.ensure_subscription(user_session.user, response, response.submission, self.reason) self.get(submission_id, measure_id)
def process_structure_file(self, all_rows, session, program): response_types = self.create_response_types(session, program) survey = model.Survey() survey.program = program survey.title = "Imported Survey" survey.description = None with open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'aquamark_hierarchy.json')) as data_file: survey.structure = json.load(data_file) session.add(survey) session.flush() log.info("survey: %s" % survey.id) function_title_row = [{ "title": row[col2num("J")], "order": row[col2num("C")], "row_num": all_rows.index(row) } for row in all_rows if str(row[col2num("S")]) == "Function Header"] process_title_row = [{ "title": row[col2num("J")], "order": row[col2num("D")], "row_num": all_rows.index(row) } for row in all_rows if str(row[col2num("S")]) == "Process Header"] subprocess_title_row = [{ "title": row[col2num("J")], "order": row[col2num("E")], "row_num": all_rows.index(row) } for row in all_rows if str(row[col2num("S")]) == "SubProcess Header"] for function in function_title_row: function_order = int(function['order']) function_title = function['title'].replace( "{} - ".format(function_order), "") function_description = self.parse_description( all_rows, function['row_num']) qnode_function = model.QuestionNode() qnode_function.program = program qnode_function.survey = survey qnode_function.seq = function_order - 1 qnode_function.title = function_title qnode_function.description = bleach.clean(function_description, strip=True) session.add(qnode_function) session.flush() process_row = [ row for row in process_title_row if "{}.".format(function_order) in row['title'] ] for process in process_row: process_order = int(process['order']) process_title = process['title'].replace( "{}.{} - ".format(function_order, process_order), "") # print("process order:", process_order) # print("process title:", process_title) process_description = self.parse_description( all_rows, process['row_num'], "") qnode_process = model.QuestionNode() qnode_process.program = program qnode_process.survey = survey qnode_process.parent = qnode_function qnode_process.seq = process_order - 1 qnode_process.title = process_title qnode_process.description = bleach.clean(process_description, strip=True) # log.info("qnode_process: %s" % qnode_process) session.add(qnode_process) session.flush() subprocess_row = [ row for row in subprocess_title_row if "{}.{}.".format( function_order, process_order) in row['title'] ] for subprocess in subprocess_row: subprocess_order = int(subprocess['order']) subprocess_title = subprocess['title'].replace( "{}.{}.{} - ".format(function_order, process_order, subprocess_order), "") # print("subprocess order:", subprocess_order) # print("subprocess title:", subprocess_title) subprocess_description = self.parse_description( all_rows, subprocess['row_num'], "") qnode_subprocess = model.QuestionNode() qnode_subprocess.program = program qnode_subprocess.survey = survey qnode_subprocess.parent = qnode_process qnode_subprocess.seq = subprocess_order - 1 qnode_subprocess.title = subprocess_title qnode_subprocess.description = bleach.clean( subprocess_description, strip=True) session.add(qnode_subprocess) session.flush() measure_title_row = [ { "title": row[col2num("k")], "row_num": all_rows.index(row), "order": row[col2num("F")], "weight": row[col2num("L")], "resp_num": row[col2num("F")] } for row in all_rows if function_order == row[col2num("C")] and process_order == row[col2num("D")] and subprocess_order == row[col2num("E")] and row[col2num("F")] != 0 and row[col2num("G")] == 1 ] for measure in measure_title_row: measure_order = int(measure["order"]) measure_title = measure['title'].replace( "{}.{}.{}.{} - ".format(function_order, process_order, subprocess_order, measure_order), "") measure_description = self.parse_description( all_rows, measure['row_num'], "Description") # Comments are part of the response, so ignore that # row measure_weight = measure['weight'] m = model.Measure() m.program = program m.title = measure_title m.weight = measure_weight m.description = bleach.clean(measure_description, strip=True) rt_id = "standard" if function_order == 7: rt_id = "business-support-%s" % int( measure['resp_num']) # log.info("response_type: %s", rt_id) m.response_type = response_types[rt_id] session.add(m) session.flush() qnode_measure = model.QnodeMeasure( program=program, survey=survey, qnode=qnode_subprocess, measure=m) qnode_subprocess.qnode_measures.reorder() session.add(qnode_measure) session.flush() calculator = Calculator.structural() calculator.mark_entire_survey_dirty(survey) calculator.execute()
def put(self, program_id): ''' Update an existing program. ''' if program_id == '': raise errors.MethodError("Can't use PUT for new program (no ID).") editable = self.get_argument('editable', '') if editable != '': self._update_state(program_id, truthy(editable)) return with model.session_scope() as session: user_session = self.get_user_session(session) program = session.query(model.Program).get(program_id) if not program: raise errors.MissingDocError("No such program") try: groups_changed = assign_surveygroups(user_session, program, self.request_son) except ValueError as e: raise errors.ModelError(str(e)) policy = user_session.policy.derive({ 'program': program, 'surveygroups': program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('program_edit') if not program.is_editable: raise errors.MethodError("This program is closed for editing") calculator = Calculator.structural() if self.request_son['has_quality'] != program.has_quality: # Recalculate stats for surveys. This will trigger the # recalculation of the submissions in the recalculation # daemon. for survey in program.surveys: calculator.mark_survey_dirty(survey) self._update(program, self.request_son) calculator.execute() verbs = [] if session.is_modified(program) or groups_changed: verbs.append('update') if program.deleted: program.deleted = False verbs.append('undelete') act = Activities(session) act.record(user_session.user, program, verbs) act.ensure_subscription(user_session.user, program, program, self.reason) self.get(program_id)
def put(self, qnode_id): '''Update existing.''' if not qnode_id: self.ordering() return program_id = self.get_argument('programId', '') parent_id = self.get_argument('parentId', '') with model.session_scope() as session: user_session = self.get_user_session(session) qnode = ( session.query(model.QuestionNode) .options(joinedload('program')) .options(joinedload('program.surveygroups')) .get((qnode_id, program_id))) if not qnode: raise errors.MissingDocError("No such question node") policy = user_session.policy.derive({ 'program': qnode.program, 'survey': qnode.survey, 'surveygroups': qnode.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_edit') self._update(session, qnode, self.request_son) verbs = [] if session.is_modified(qnode): verbs.append('update') calculator = Calculator.structural() if parent_id and str(qnode.parent_id) != parent_id: # Change parent old_parent = qnode.parent new_parent = ( session.query(model.QuestionNode) .get((parent_id, program_id))) if new_parent.survey != qnode.survey: raise errors.ModelError("Can't move to different survey") if not new_parent: raise errors.ModelError("No such question node") old_parent.children.remove(qnode) old_parent.children.reorder() new_parent.children.append(qnode) new_parent.children.reorder() calculator.mark_qnode_dirty(old_parent) calculator.mark_qnode_dirty(qnode) self.reason("Moved from %s to %s" % ( old_parent.title, new_parent.title)) verbs.append('relation') if qnode.deleted: # Get a reference to the collection before changing the # deleted flag - otherwise, if a query is needed to # instantiate the collection, it will seem as if the object # is already in the collection and insert will not work as # expected. if qnode.parent: collection = qnode.parent.children else: collection = qnode.survey.qnodes qnode.deleted = False collection.insert(qnode.seq, qnode) collection.reorder() calculator.mark_qnode_dirty(qnode) verbs.append('undelete') calculator.execute() act = Activities(session) act.record(user_session.user, qnode, verbs) act.ensure_subscription( user_session.user, qnode, qnode.program, self.reason) self.get(qnode_id)
def post(self, qnode_id): '''Create new.''' if qnode_id: raise errors.MethodError("Can't use POST for existing object") program_id = self.get_argument('programId', '') survey_id = self.get_argument('surveyId', '') parent_id = self.get_argument('parentId', '') with model.session_scope() as session: user_session = self.get_user_session(session) program = ( session.query(model.Program) .options(joinedload('surveygroups')) .get(program_id)) if not program: raise errors.ModelError("No such program") qnode = model.QuestionNode(program=program) self._update(session, qnode, self.request_son) log.debug("new: %s", qnode) if survey_id: survey = ( session.query(model.Survey) .get((survey_id, program_id))) if not survey: raise errors.ModelError("No such survey") else: survey = None log.debug("survey: %s", survey) if parent_id: parent = ( session.query(model.QuestionNode) .get((parent_id, program_id))) if not parent: raise errors.ModelError("Parent does not exist") if not survey: survey = parent.survey elif parent.survey != survey: raise errors.ModelError( "Parent does not belong to that survey") else: parent = None qnode.survey = survey if parent: log.debug("Appending to parent") parent.children.append(qnode) parent.children.reorder() log.debug("committing: %s", parent.children) elif survey: log.debug("Appending to survey") survey.qnodes.append(qnode) survey.qnodes.reorder() log.debug("committing: %s", survey.qnodes) else: raise errors.ModelError("Parent or survey ID required") # Need to flush so object has an ID to record action against. session.flush() policy = user_session.policy.derive({ 'program': qnode.program, 'survey': qnode.survey, 'surveygroups': qnode.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_add') calculator = Calculator.structural() calculator.mark_qnode_dirty(qnode) calculator.execute() qnode_id = str(qnode.id) act = Activities(session) act.record(user_session.user, qnode, ['create']) act.ensure_subscription( user_session.user, qnode, qnode.program, self.reason) self.get(qnode_id)