def modify_org(self, user_email, org_name, code, reason): with model.session_scope() as session: org = session.query(model.Organisation).\ filter(func.lower(model.Organisation.name) == func.lower(org_name)).one() to_son = ToSon( r'/id$', r'/name$', r'/title$', r'/locations.*$', r'/meta.*$', r'/surveygroups$', r'/[0-9]+$', ) to_son.exclude( r'/locations/[0-9]+/id$', r'/locations/[0-9]+/organisation.*$', r'/meta/id$', r'/meta/organisation.*$', ) org_son = to_son(org) with base.mock_user(user_email): post_data = org_son.copy() response = self.fetch("/organisation/%s.json" % org_son['id'], method='PUT', body=json_encode(post_data), expected=code) self.assertIn(reason, response.reason, "%s operating on %s" % (user_email, org_name))
def query(self, submission_id): '''Get a list.''' qnode_id = self.get_argument('qnodeId', '') if not qnode_id: raise errors.ModelError("qnode ID required") with model.session_scope() as session: user_session = self.get_user_session(session) submission = (session.query(model.Submission).options( joinedload('organisation')).filter_by( id=submission_id).first()) if not submission: raise errors.MissingDocError("No such submission") policy = user_session.policy.derive({ 'org': submission.organisation, 'submission': submission, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('response_view') rnode = (session.query(model.ResponseNode).get( (submission_id, qnode_id))) if not rnode: responses = [] else: responses = rnode.responses to_son = ToSon( # Fields to match from any visited object r'/id$', r'/score$', r'/approval$', r'/modified$', r'/not_relevant$', r'^/[0-9]+/error$', # Descend into nested objects r'/[0-9]+$', r'/measure$', ) if user_session.user.role == 'clerk': to_son.exclude(r'/score$') sons = to_son(responses) self.set_header("Content-Type", "application/json") self.write(json_encode(sons)) self.finish()
def get(self, program_id): ''' Get a single program. ''' if program_id == "": self.query() return 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.MissingDocError("No such program") policy = user_session.policy.derive({ 'program': program, 'surveygroups': program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('program_view') to_son = ToSon( r'/ob_type$', r'/id$', r'/tracking_id$', r'/title$', r'</description$', r'/created$', r'/deleted$', r'/is_editable$', r'^/error$', r'/has_quality$', r'/hide_aggregate$', r'/[0-9+]$', ) if policy.check('surveygroup_browse'): to_son.add(r'^/surveygroups$') if not policy.check('author'): to_son.exclude( r'/response_types.*score$', r'/response_types.*formula$', ) son = to_son(program) self.set_header("Content-Type", "application/json") self.write(json_encode(son)) self.finish()
def get(self, organisation_id): if organisation_id == "": self.query() return with model.session_scope() as session: user_session = self.get_user_session(session) org = (session.query(model.Organisation).options( joinedload('surveygroups')).get(organisation_id)) if not org: raise errors.MissingDocError("No such organisation") policy = user_session.policy.derive({ 'org': org, 'surveygroups': org.surveygroups, }) policy.verify('org_view') # Check that user shares a common surveygroup with this org. # Admins need access to orgs outside their surveygroups though. if not policy.check('admin'): policy.verify('surveygroup_interact') to_son = ToSon( r'/id$', r'/name$', r'/title$', r'/deleted$', r'/url$', r'/locations.*$', r'/meta.*$', r'/[0-9+]$', ) to_son.exclude( r'/locations/.*/organisation(_id)?$', r'/locations/.*/id$', r'/meta/organisation(_id)?$', r'/meta/id$', ) if policy.check('surveygroup_browse'): to_son.add(r'^/surveygroups$') son = to_son(org) self.set_header("Content-Type", "application/json") self.write(json_encode(son)) self.finish()
def get(self, submission_id, qnode_id): if qnode_id == '': self.query(submission_id) return with model.session_scope() as session: user_session = self.get_user_session(session) rnode = ( session.query(model.ResponseNode) .options(joinedload('submission')) .options(joinedload('submission.organisation')) .get((submission_id, qnode_id))) if rnode: submission = rnode.submission else: # Create an empty one, and roll back later (because GET # shouldn't modify anything). ## seperrate for fix issue by tang ## qnode, submission = ( ## session.query(model.QuestionNode, model.Submission) ## .join(model.Program) ## .join(model.Submission) ## .filter(model.QuestionNode.id == qnode_id, ## model.Submission.id == submission_id) ## .first()) qnode = ( session.query(model.QuestionNode) #, model.Submission) .join(model.Program) .join(model.Submission) .filter(model.QuestionNode.id == qnode_id, model.Submission.id == submission_id) .first()) submission = ( session.query(model.Submission) .join(model.Program) .join(model.QuestionNode) .filter(model.QuestionNode.id == qnode_id, model.Submission.id == submission_id) .first()) if not qnode: raise errors.MissingDocError("No such category") rnode = model.ResponseNode( qnode=qnode, qnode_id=qnode_id, submission=submission, submission_id=submission_id, score=0, n_draft=0, n_final=0, n_reviewed=0, n_approved=0, n_not_relevant=0) policy = user_session.policy.derive({ 'org': submission.organisation, 'submission': submission, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('rnode_view') to_son = ToSon( # Fields to match from any visited object r'/ob_type$', r'/id$', r'/score$', r'/total_weight$', r'/submission_id$', r'/qnode_id$', r'/n_draft$', r'/n_final$', r'/n_reviewed$', r'/n_approved$', r'/n_measures$', r'/n_not_relevant$', r'/(max_)?importance$', r'/(max_)?urgency$', r'^/error$', # Descend into nested objects r'/qnode$', # The IDs of rnodes and responses are not part of the API r'!^/id$', ) if user_session.user.role == 'clerk': to_son.exclude( r'/score$', r'/total_weight$', ) son = to_son(rnode) # Don't commit empty rnode here: GET should not change anything! session.rollback() self.set_header("Content-Type", "application/json") self.write(json_encode(son)) self.finish()
def query(self, submission_id): '''Get a list.''' parent_id = self.get_argument('parentId', '') root = self.get_argument('root', None) if root is not None and parent_id != '': raise errors.ModelError( "Can't specify parent ID when requesting roots") if root is None and parent_id == '': raise errors.ModelError( "'root' or parent ID required") 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, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('rnode_view') if root is not None: children = submission.rnodes else: rnode = ( session.query(model.ResponseNode) .get((submission_id, parent_id))) if not rnode: # Rnodes get created from the bottom of the tree up, so if # the parent doesn't exist, its children shouldn't either. children = [] else: children = rnode.children to_son = ToSon( # Fields to match from any visited object r'/id$', r'/score$', r'/total_weight$', r'/n_draft$', r'/n_final$', r'/n_reviewed$', r'/n_approved$', r'/n_measures$', r'/n_not_relevant$', r'/max_importance$', r'/max_urgency$', r'^/[0-9]+/error$', # Descend into nested objects r'/[0-9]+$', r'/qnode$', # The IDs of rnodes and responses are not part of the API r'!^/[0-9]+/id$', ) if user_session.user.role == 'clerk': to_son.exclude( r'/score$', r'/total_weight$' ) sons = to_son(children) for son in sons: answer=0 ids = [] pIds=[son.qnode.id] for pId in pIds: sIds = ( session.query(model.QuestionNode) .filter(model.QuestionNode.parent_id == pId)) if not sIds.first(): ids.append(pId) else: for cid in sIds: pIds.append(cid.id) answerResponses = ( session.query(model.Response, model.Measure, model.QnodeMeasure) .filter(model.Response.submission_id == submission_id) .filter(model.Response.measure_id == model.Measure.id) .filter(model.Measure.id == model.QnodeMeasure.measure_id) .filter(model.QnodeMeasure.qnode_id.in_(ids))) #for answerResponse in answerResponses: # if answerResponse.Response.variables != {}: # answer=answer+1 #son.qnode['nAnswer']=answer question = 0 answer = 0 for answerResponse in answerResponses: #if answerResponse.Response.variables != {}: if len(answerResponse.Response.response_parts)>0: #calculate answer question number hasAnswer = True seq = 0 rt = answerResponse.Measure.response_type.parts for r, p in enumerate(rt): if (not 'submeasure_seq' in p ): if (seq > 0): question += 1 if (hasAnswer): answer += 1 else: hasAnswer=True if ((answerResponse.Response.response_parts[r] is None or answerResponse.Response.response_parts[r] == {} or ((not 'index' in answerResponse.Response.response_parts[r].keys()) and (not 'value' in answerResponse.Response.response_parts[r].keys()))) and hasAnswer): hasAnswer=False else: if (hasAnswer or seq != p['submeasure_seq']): if (seq != p['submeasure_seq']): if (seq > 0): question += 1 if (hasAnswer): answer += 1 else: hasAnswer=True seq = p['submeasure_seq'] if ((answerResponse.Response.response_parts[r] is None or answerResponse.Response.response_parts[r] == {} or ((not 'index' in answerResponse.Response.response_parts[r].keys()) and (not 'value' in answerResponse.Response.response_parts[r].keys()))) and hasAnswer): hasAnswer=False if (seq > 0 and hasAnswer): answer += 1 question += 1 son.qnode['nAnswer']=answer son.qnode['nQuestion']=question self.set_header("Content-Type", "application/json") self.write(json_encode(sons)) self.finish()
def query_by_level(self, level): level = int(level) program_id = self.get_argument('programId', '') survey_id = self.get_argument('surveyId', '') term = self.get_argument('term', '') parent_not = self.get_argument('parent__not', None) deleted = self.get_argument('deleted', '') if deleted != '': deleted = truthy(deleted) else: deleted = None if not survey_id: raise errors.ModelError("Survey ID required") with model.session_scope() as session: user_session = self.get_user_session(session) survey = ( session.query(model.Survey) .options(joinedload('program')) .options(joinedload('program.surveygroups')) .get((survey_id, program_id))) policy = user_session.policy.derive({ 'program': survey.program, 'survey': survey, 'surveygroups': survey.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_view') # Use Postgres' WITH statement # http://www.postgresql.org/docs/9.1/static/queries-with.html # http://docs.sqlalchemy.org/en/rel_1_0/orm/query.html#sqlalchemy.orm.query.Query.cte # http://stackoverflow.com/a/28084743/320036 # Start by selecting root nodes QN1 = model.QuestionNode start = ( session.query( QN1, literal(0).label('level'), array([QN1.seq]).label('path'), (QN1.seq + 1).concat('.').label('pathstr'), (QN1.deleted).label('any_deleted')) .filter(QN1.parent_id == None, QN1.program_id == program_id, QN1.survey_id == survey_id) .cte(name='root', recursive=True)) # Now iterate down the tree to the desired level QN2 = aliased(model.QuestionNode, name='qnode2') recurse = ( session.query( QN2, (start.c.level + 1).label('level'), start.c.path.concat(QN2.seq).label('path'), start.c.pathstr.concat(QN2.seq + 1).concat('.').label( 'pathstr'), (start.c.any_deleted | QN2.deleted).label( 'any_deleted')) .filter(QN2.parent_id == start.c.id, QN2.program_id == start.c.program_id, QN2.survey_id == start.c.survey_id, start.c.level <= level)) # Combine iterated result with root cte = start.union_all(recurse) # Discard all but the lowest level subquery = ( session.query(cte.c.id, cte.c.pathstr, cte.c.any_deleted) .filter(cte.c.level == level) .order_by(cte.c.path) .subquery()) # Select again to get the actual qnodes query = ( session.query( model.QuestionNode, subquery.c.pathstr, subquery.c.any_deleted) .filter(model.QuestionNode.program_id == program_id) .join(subquery, model.QuestionNode.id == subquery.c.id)) if parent_not == '': query = query.filter( model.QuestionNode.parent_id != None) elif parent_not is not None: query = query.filter( model.QuestionNode.parent_id != parent_not) if term != '': query = query.filter( model.QuestionNode.title.ilike('%{}%'.format(term))) if deleted is not None: query = query.filter(subquery.c.any_deleted == deleted) query = self.paginate(query) to_son = ToSon( # Fields to match from any visited object r'/id$', r'/title$', r'/deleted$', r'/n_measures$' ) if truthy(self.get_argument('desc', False)): to_son.add(r'</description$') if user_session.user.role == 'clerk': to_son.exclude(r'/total_weight$') sons = [] for qnode, path, deleted in query.all(): son = to_son(qnode) son['path'] = path son['anyDeleted'] = deleted sons.append(son) self.set_header("Content-Type", "application/json") self.write(json_encode(sons)) self.finish()
def get(self, qnode_id): if not qnode_id: self.query() return 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 category") policy = user_session.policy.derive({ 'survey': qnode.survey, 'surveygroups': qnode.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_view') to_son = ToSon( # Fields to match from any visited object r'/ob_type$', r'/id$', r'/title$', r'/seq$', r'/deleted$', r'/total_weight$', r'/n_measures$', r'/is_editable$', r'^/error$', r'^/group$', r'/program/tracking_id$', r'/program/created$', r'/program/hide_aggregate$', # Fields to match from only the root object r'<^/description$', # Ascend into nested parent objects r'/parent$', r'/survey$', r'/survey/structure.*$', r'/survey/program$', # Response types needed here when creating a new measure r'/response_types.*$', ) if user_session.user.role == 'clerk': to_son.exclude(r'/total_weight$') son = to_son(qnode) sibling_query = ( session.query(model.QuestionNode) .filter(model.QuestionNode.program_id == qnode.program_id, model.QuestionNode.survey_id == qnode.survey_id, model.QuestionNode.parent_id == qnode.parent_id, model.QuestionNode.deleted == False)) prev = ( sibling_query .filter(model.QuestionNode.seq < qnode.seq) .order_by(model.QuestionNode.seq.desc()) .first()) next_ = ( sibling_query .filter(model.QuestionNode.seq > qnode.seq) .order_by(model.QuestionNode.seq) .first()) if prev is not None: son['prev'] = str(prev.id) if next_ is not None: son['next'] = str(next_.id) self.set_header("Content-Type", "application/json") self.write(json_encode(son)) self.finish()
def query(self): '''Get list.''' level = self.get_argument('level', '') if level: self.query_by_level(level) return program_id = self.get_argument('programId', '') survey_id = self.get_argument('surveyId', '') parent_id = self.get_argument('parentId', '') root = self.get_argument('root', None) term = self.get_argument('term', '') parent_not = self.get_argument('parent__not', '') deleted = self.get_argument('deleted', '') if root is not None and parent_id: raise errors.ModelError( "Can't specify parent ID when requesting roots") with model.session_scope() as session: user_session = self.get_user_session(session) if parent_id: parent = ( session.query(model.QuestionNode) .options(joinedload('survey')) .options(joinedload('program')) .options(joinedload('program.surveygroups')) .get((parent_id, program_id))) if not parent: raise errors.MissingDocError("No such parent category") if survey_id and survey_id != str(parent.survey_id): raise errors.ModelError("Category is not in that survey") survey = parent.survey elif survey_id: survey = ( session.query(model.Survey) .options(joinedload('program')) .options(joinedload('program.surveygroups')) .get((survey_id, program_id))) if not survey: raise errors.MissingDocError("No such survey") else: raise errors.ModelError("Survey or parent ID required") policy = user_session.policy.derive({ 'program': survey.program, 'survey': survey, 'surveygroups': survey.program.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('qnode_view') query = ( session.query(model.QuestionNode) .filter(model.QuestionNode.program_id == program_id)) if survey_id: query = query.filter_by(survey_id=survey_id) if parent_id: query = query.filter_by(parent_id=parent_id) elif root is not None: query = query.filter_by(parent_id=None) if term: query = query.filter( model.QuestionNode.title.ilike('%{}%'.format(term))) if parent_not: query = query.filter( model.QuestionNode.parent_id != parent_not) if deleted: deleted = truthy(deleted) query = query.filter(model.QuestionNode.deleted == deleted) query = query.order_by(model.QuestionNode.seq, model.QuestionNode.deleted.desc()) query = self.paginate(query, optional=True) to_son = ToSon( # Fields to match from any visited object r'/ob_type$', r'/id$', r'/title$', r'/group$', r'/seq$', r'/deleted$', r'/n_measures$', r'/total_weight$', r'^/[0-9]+/error$', r'/parent$', r'/survey$', r'/survey/structure.*$', r'/survey/program$', # Descend into nested objects r'/[0-9]+$', ) if truthy(self.get_argument('desc', False)): to_son.add(r'</description$') if user_session.user.role == 'clerk': to_son.exclude(r'/total_weight$') sons = to_son(query.all()) # test use session to keep status #status_session = self.get_secure_cookie('status') #status_ids = status_session.decode('utf8') #status_array = status_ids.split(',') #for son in sons: # if son.id in status_array: # son['hideDetail'] = True ##################################### for son in sons: question=0 ids = [] pIds=[son.id] for pId in pIds: sIds = ( session.query(model.QuestionNode) .filter(model.QuestionNode.parent_id == pId)) if not sIds.first(): ids.append(pId) else: for cid in sIds: pIds.append(cid.id) qnodeMeasures = ( session.query(model.Measure, model.QnodeMeasure) .filter(model.Measure.id == model.QnodeMeasure.measure_id) .filter(model.QnodeMeasure.qnode_id.in_(ids))) for qnodeMeasure in qnodeMeasures: rt = ( session.query(model.ResponseType) .filter(model.ResponseType.id == qnodeMeasure.Measure.response_type_id).first()) seq = 0 for p in rt.parts: if "submeasure_seq" in p and p["submeasure_seq"] > question: seq = p["submeasure_seq"] else: question += 1 question = question + seq son['nQuestion'] = question # if son.id in status_array: # son['hideDetail'] = True self.set_header("Content-Type", "application/json") self.write(json_encode(sons)) self.finish()
def get(self, submission_id, measure_id): '''Get a single response.''' if not measure_id: self.query(submission_id) return version = self.get_argument('version', '') with model.session_scope() as session: user_session = self.get_user_session(session) response = (session.query(model.Response).get( (submission_id, measure_id))) if response: submission = response.submission dummy = False else: # Synthesise response so it can be returned. The session will # be rolled back to avoid actually making this change. submission = (session.query( model.Submission).get(submission_id)) if not submission: raise errors.MissingDocError("No such submission") qnode_measure = (session.query(model.QnodeMeasure).get( (submission.program_id, submission.survey_id, measure_id))) if not qnode_measure: raise errors.MissingDocError( "That survey has no such measure") response = model.Response( qnode_measure=qnode_measure, submission=submission, user_id=user_session.user.id, comment='', response_parts=[], variables={}, not_relevant=False, approval='draft', modified=datetime.datetime.fromtimestamp(0), ) dummy = True response_history = self.get_version(session, response, version) policy = user_session.policy.derive({ 'org': submission.organisation, 'submission': submission, 'surveygroups': submission.surveygroups, }) policy.verify('surveygroup_interact') policy.verify('response_view') to_son = ToSon( # Fields to match from any visited object r'/ob_type$', r'/id$', r'/title$', r'/name$', r'/description$', r'/seq$', r'/parent$', r'/parents$', r'/parents/[0-9]+$', # Fields to match from only the root object r'^/submission_id$', r'^/measure_id$', r'<^/comment$', r'^/response_parts.*$', r'^/not_relevant$', r'^/attachments$', r'^/audit_reason$', r'^/error$', r'^/approval$', r'^/version$', r'^/modified$', r'^/latest_modified$', r'^/quality$', # Descend r'/parent$', r'/measure$', r'/submission$', r'/user$', r'/qnode_measure$', r'/parents$', ) if dummy: to_son.add(r'!/user$') to_son.exclude( # The IDs of rnodes and responses are not part of the API r'^/id$', r'/parent/id$') if response_history is None: son = to_son(response) else: son = to_son(response_history) submission = (session.query(model.Submission).filter_by( id=response_history.submission_id).first()) measure = (session.query(model.Measure).filter_by( id=response_history.measure_id, program_id=submission.program_id).first()) qnode_measure = measure.get_qnode_measure(submission.survey_id) parent = model.ResponseNode.from_qnode(qnode_measure.qnode, submission) user = (session.query(model.AppUser).filter_by( id=response_history.user_id).first()) dummy_relations = { 'parent': parent, 'measure': measure, 'submission': submission, 'user': user, } son.update(to_son(dummy_relations)) # Always include the mtime of the most recent version. This is used # to avoid edit conflicts. dummy_relations = { 'latest_modified': response.modified, } son.update(to_son(dummy_relations)) def gather_variables(response): source_responses = { mv.source_qnode_measure: model.Response.from_measure(mv.source_qnode_measure, response.submission) for mv in response.qnode_measure.source_vars } source_variables = { source_qnode_measure: response and response.variables or {} for source_qnode_measure, response in source_responses.items() } variables_by_target = { mv.target_field: source_variables[mv.source_qnode_measure].get( mv.source_field) for mv in response.qnode_measure.source_vars } # Filter out blank/null variables return {k: v for k, v in variables_by_target.items() if v} son['sourceVars'] = gather_variables(response) # Explicit rollback to avoid committing dummy response. session.rollback() self.set_header("Content-Type", "application/json") self.write(json_encode(son)) self.finish()