Exemple #1
0
    def get(self, user_id):
        '''
        Get a single user.
        '''
        if not user_id:
            self.query()
            return

        with model.session_scope() as session:
            user_session = self.get_user_session(session)

            if user_id == 'current':
                user = user_session.user
            else:
                user = (
                    session.query(model.AppUser)
                    .options(joinedload('organisation'))
                    .options(joinedload('surveygroups'))
                    .get(user_id))
                if not user:
                    raise errors.MissingDocError("No such user")

            policy = user_session.policy.derive({
                'user': user,
                'surveygroups': user.surveygroups,
            })
            policy.verify('user_view')

            # Check that user shares a common surveygroup with the requesting
            # user.
            # Allow admins to access users outside their surveygroups though.
            if not policy.check('admin'):
                policy.verify('surveygroup_interact')

            to_son = ToSon(
                r'/id$',
                r'/name$',
                r'/title$',
                r'/email$',
                r'/email_interval$',
                r'/role$',
                r'/deleted$',
                # Descend into nested objects
                r'/organisation$',
                r'/[0-9+]$',
                # Exclude password from response. Not really necessary because
                # 1. it's hashed and 2. it's not in the list above. But just to
                # be safe.
                r'!password',
            )
            if policy.check('surveygroup_browse'):
                to_son.add(r'^/surveygroups$')
            son = to_son(user)
        self.set_header("Content-Type", "application/json")
        self.write(json_encode(son))
        self.finish()
Exemple #2
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()
Exemple #3
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()
Exemple #4
0
    def execute(self):
        qnode_pairs = self.get_qnodes()
        measure_pairs = self.get_measures()

        to_son = ToSon(
            r'/id$',
            r'/survey_id$',
            r'/parent_id$',
            r'/title$',
            r'</description$',
            r'/response_type_id$',
            r'/seq$',
            # Descend
            r'/[0-9]+$',
            r'^/[0-9]+/[^/]+$',
        )

        if self.include_scores:
            to_son.add(r'/weight$', )

        qnode_pairs = self.realise_soft_deletion(qnode_pairs)
        qnode_pairs = self.remove_soft_deletion_dups(qnode_pairs)
        qnode_diff = [{
            'type': 'qnode',
            'tags': [],
            'pair': pair,
        } for pair in to_son(qnode_pairs)]
        start = perf()
        self.add_qnode_metadata(qnode_pairs, qnode_diff)
        self.add_metadata(qnode_pairs, qnode_diff)
        duration = perf() - start
        self.timing.append("Qnode metadata took %gs" % duration)
        self.remove_unchanged_fields(qnode_diff)

        measure_pairs = self.realise_soft_deletion(measure_pairs)
        measure_pairs = self.remove_soft_deletion_dups(measure_pairs)
        measure_diff = [{
            'type': 'measure',
            'tags': [],
            'pair': pair,
        } for pair in to_son(measure_pairs)]
        start = perf()
        self.add_measure_metadata(measure_pairs, measure_diff)
        self.add_metadata(measure_pairs, measure_diff)
        duration = perf() - start
        self.timing.append("Measure metadata took %gs" % duration)
        self.remove_unchanged_fields(measure_diff)

        diff = qnode_diff + measure_diff

        def path_key(diff_item):
            a, b = diff_item['pair']
            if a and b:
                return 0, [int(c) for c in b['path'].split('.') if c != '']
            elif b:
                return 0, [int(c) for c in b['path'].split('.') if c != '']
            elif a:
                return 1, [int(c) for c in a['path'].split('.') if c != '']
            else:
                return 2

        start = perf()
        diff.sort(key=path_key)
        duration = perf() - start
        self.timing.append("Sorting took %gs" % duration)

        HA = model.Survey
        HB = aliased(model.Survey, name='survey_b')
        survey_a, survey_b = (self.session.query(HA, HB).join(
            HB, (HA.id == HB.id)).filter(HA.program_id == self.program_id_a,
                                         HB.program_id == self.program_id_b,
                                         HA.id == self.survey_id).first())
        to_son = ToSon(
            r'/id$',
            r'/title$',
            r'</description$',
        )
        top_level_diff = [{
            'type':
            'program',
            'tags': ['context'],
            'pair': [to_son(survey_a.program),
                     to_son(survey_b.program)]
        }, {
            'type': 'survey',
            'tags': ['context'],
            'pair': [to_son(survey_a), to_son(survey_b)]
        }]
        self.remove_unchanged_fields(top_level_diff)

        return top_level_diff + diff
Exemple #5
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()
Exemple #6
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()
Exemple #7
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()