Exemplo n.º 1
0
    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))
Exemplo n.º 2
0
    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()
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
    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()
Exemplo n.º 5
0
    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()
Exemplo n.º 6
0
    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()
Exemplo n.º 7
0
    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()
Exemplo n.º 8
0
    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()
Exemplo n.º 9
0
    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()
Exemplo n.º 10
0
    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()