Example #1
0
def reorder_page(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    if experiment:
        page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                                 Page.experiment == experiment)).first()
    else:
        page = None
    if experiment and page:
        try:
            params = ReorderSchema().to_python(request.params, State(request=request))
            with transaction.manager:
                visible_idx = 1
                for idx, qid in enumerate(params['item']):
                    question = dbsession.query(Question).filter(and_(Question.id == qid,
                                                                     Question.page == page)).first()
                    if question is not None:
                        dbsession.add(question.q_type)
                        question.order = idx
                        if question['frontend', 'visible'] and question['frontend', 'title']:
                            question['frontend', 'question_number'] = visible_idx
                            visible_idx = visible_idx + 1
            return {'status': 'ok'}
        except formencode.Invalid:
            return {'status': 'error'}
    else:
        raise HTTPNotFound()
Example #2
0
def transitions(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    if experiment:
        page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                                 Page.experiment == experiment)).first()
    else:
        page = None
    if experiment and page:
        pages_questions = {}
        for tmp_page in experiment.pages:
            pages_questions[tmp_page.id] = {'page': tmp_page,
                                            'questions': dict([(q.id, q) for q in tmp_page.questions])}
        return {'experiment': experiment,
                'page': page,
                'pages_questions': pages_questions,
                'crumbs': [{'title': 'Experiments',
                            'url': request.route_url('dashboard')},
                           {'title': experiment.title,
                            'url': request.route_url('experiment.view', eid=experiment.id)},
                           {'title': 'Pages',
                            'url': request.route_url('experiment.page', eid=experiment.id)},
                           {'title': '%s (%s)' % (page.title, page.name)
                            if page.title else 'No title (%s)' % page.name,
                            'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)}]}
    else:
        raise HTTPNotFound()
Example #3
0
def edit(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    if experiment:
        page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                                 Page.experiment == experiment)).first()
    else:
        page = None
    if experiment and page:
        qtgroups = dbsession.query(QuestionTypeGroup).filter(QuestionTypeGroup.parent_id == None).\
            order_by(QuestionTypeGroup.order)
        return {'experiment': experiment,
                'page': page,
                'participant': Participant(id=-1),
                'qtgroups': qtgroups,
                'crumbs': [{'title': 'Experiments',
                            'url': request.route_url('dashboard')},
                           {'title': experiment.title,
                            'url': request.route_url('experiment.view', eid=experiment.id)},
                           {'title': 'Pages',
                            'url': request.route_url('experiment.page', eid=experiment.id)},
                           {'title': '%s (%s)' % (page.title, page.name)
                            if page.title else 'No title (%s)' % page.name,
                            'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)}]}
    else:
        raise HTTPNotFound()
Example #4
0
def latinsquare_estimate(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(
        Experiment.id == request.matchdict['eid']).first()
    data_set = dbsession.query(DataSet).filter(
        and_(DataSet.id == request.matchdict['did'],
             DataSet.experiment_id == request.matchdict['eid'],
             DataSet.type == 'latinsquare')).first()
    if experiment and data_set:
        try:
            params = LatinSquareEstimateSchema().to_python(
                request.params,
                State(request=request,
                      dbsession=dbsession,
                      experiment=experiment))
            count = latinsquare_estimate_count(dbsession, params['source_a'],
                                               params['mode_a'],
                                               params['source_b'],
                                               params['mode_b'])
            if count is None:
                return {'count': ''}
            else:
                return {'count': count}
        except formencode.Invalid:
            return {'count': ''}
    else:
        raise HTTPNotFound()
Example #5
0
def data(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                             Page.experiment_id == request.matchdict['eid'])).first()
    if experiment and page:
        data_sets = dbsession.query(DataSet).filter(DataSet.experiment_id == experiment.id)
        if request.method == 'POST':
            try:
                schema = DataAttachmentSchema()
                mode = ''
                if 'data_set' in request.params and \
                        request.params['data_set'] in [str(ds.id) for ds in data_sets if ds.type == 'dataset']:
                    schema.add_field('data_items', formencode.validators.Int(not_empty=True))
                    mode = 'dataset'
                params = schema.to_python(request.params, State(request=request,
                                                                dbsession=dbsession,
                                                                experiment=experiment))
                with transaction.manager:
                    dbsession.add(page)
                    page.dataset_id = params['data_set']
                    if mode == 'dataset':
                        page['data'] = {'type': 'dataset',
                                        'item_count': params['data_items']}
                dbsession.add(experiment)
                dbsession.add(page)
                raise HTTPFound(request.route_url('experiment.page.data', eid=experiment.id, pid=page.id))
            except formencode.Invalid as e:
                return {'experiment': experiment,
                        'page': page,
                        'data_sets': data_sets,
                        'crumbs': [{'title': 'Experiments',
                                    'url': request.route_url('dashboard')},
                                   {'title': experiment.title,
                                    'url': request.route_url('experiment.view', eid=experiment.id)},
                                   {'title': 'Pages',
                                    'url': request.route_url('experiment.page', eid=experiment.id)},
                                   {'title': '%s (%s)' % (page.title, page.name)
                                    if page.title else 'No title (%s)' % page.name,
                                    'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)},
                                   {'title': 'Data',
                                    'url': request.route_url('experiment.page.data', eid=experiment.id, pid=page.id)}],
                        'errors': e.error_dict,
                        'values': request.params}
        return {'experiment': experiment,
                'page': page,
                'data_sets': data_sets,
                'crumbs': [{'title': 'Experiments',
                            'url': request.route_url('dashboard')},
                           {'title': experiment.title,
                            'url': request.route_url('experiment.view', eid=experiment.id)},
                           {'title': 'Pages',
                            'url': request.route_url('experiment.page', eid=experiment.id)},
                           {'title': '%s (%s)' % (page.title, page.name)
                            if page.title else 'No title (%s)' % page.name,
                            'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)},
                           {'title': 'Data',
                            'url': request.route_url('experiment.page.data', eid=experiment.id, pid=page.id)}]}
    else:
        raise HTTPNotFound()
Example #6
0
def view_asset(request):
    """Handles the ``/parts/{pid}/files/name/assets/{filename}``
    URL, sending back the correct :class:`~wte.models.Asset`.

    Requires that the user has "view" rights on the :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part.type == 'page':
        part = part.parent
    asset = dbsession.query(Asset).join(Part.assets).\
        filter(and_(Asset.filename == request.matchdict['filename'],
                    Part.id == part.id)).first()
    if part and asset:
        if part.allow('view', request.current_user):
            if 'If-None-Match' in request.headers and request.headers[
                    'If-None-Match'] == asset.etag:
                raise HTTPNotModified()
            headerlist = [('Content-Type', str(asset.mimetype))]
            if asset.etag is not None:
                headerlist.append(('ETag', str(asset.etag)))
            if 'download' in request.params:
                if request.params['download'].lower() == 'true':
                    headerlist.append(
                        ('Content-Disposition',
                         str('attachment; filename="%s"' % (asset.filename))))
            return Response(body=asset.data, headerlist=headerlist)
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #7
0
def add_transition(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    if experiment:
        page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                                 Page.experiment == experiment)).first()
    else:
        page = None
    if experiment and page:
        try:
            with transaction.manager:
                dbsession.add(page)
                if page.next:
                    order = max([t.order for t in page.next]) + 1
                else:
                    order = 1
                transition = Transition(source=page, order=order)
                dbsession.add(transition)
            dbsession.add(experiment)
            dbsession.add(page)
            dbsession.add(transition)
            raise HTTPFound(request.route_url('experiment.page.transition',
                                              eid=experiment.id, pid=page.id,
                                              _anchor='transition-%i' % transition.id))
        except formencode.Invalid:
            raise HTTPFound(request.route_url('experiment.page.transition', eid=experiment.id, pid=page.id))
    else:
        raise HTTPNotFound()
Example #8
0
def settings(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                             Page.experiment_id == request.matchdict['eid'])).first()
    if experiment and page:
        if request.method == 'POST':
            try:
                params = PageSettingsSchema().to_python(request.params,
                                                        State(request=request,
                                                              dbsession=dbsession,
                                                              experiment=experiment,
                                                              page_id=request.matchdict['pid']))
                with transaction.manager:
                    dbsession.add(page)
                    page.name = params['name']
                    page.title = params['title']
                    page.styles = params['styles']
                    page.scripts = params['scripts']
                    page['number_questions'] = params['number_questions']
                dbsession.add(page)
                dbsession.add(experiment)
                raise HTTPFound(request.route_url('experiment.page.settings', eid=experiment.id, pid=page.id))
            except formencode.Invalid as e:
                return {'experiment': experiment,
                        'page': page,
                        'crumbs': [{'title': 'Experiments',
                                    'url': request.route_url('dashboard')},
                                   {'title': experiment.title,
                                    'url': request.route_url('experiment.view', eid=experiment.id)},
                                   {'title': 'Pages',
                                    'url': request.route_url('experiment.page', eid=experiment.id)},
                                   {'title': '%s (%s)' % (page.title, page.name)
                                    if page.title else 'No title (%s)' % page.name,
                                    'url': request.route_url('experiment.page.edit',
                                                             eid=experiment.id,
                                                             pid=page.id)},
                                   {'title': 'Settings',
                                    'url': request.route_url('experiment.page.settings',
                                                             eid=experiment.id,
                                                             pid=page.id)}],
                        'values': request.params,
                        'errors': e.error_dict}
        return {'experiment': experiment,
                'page': page,
                'crumbs': [{'title': 'Experiments',
                            'url': request.route_url('dashboard')},
                           {'title': experiment.title,
                            'url': request.route_url('experiment.view', eid=experiment.id)},
                           {'title': 'Pages',
                            'url': request.route_url('experiment.page', eid=experiment.id)},
                           {'title': '%s (%s)' % (page.title, page.name)
                            if page.title else 'No title (%s)' % page.name,
                            'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)},
                           {'title': 'Settings',
                            'url': request.route_url('experiment.page.settings', eid=experiment.id, pid=page.id)}]}
    else:
        raise HTTPNotFound()
Example #9
0
def edit_question(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    if experiment:
        page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                                 Page.experiment == experiment)).first()
    else:
        page = None
    if page:
        question = dbsession.query(Question).filter(and_(Question.id == request.matchdict['qid'],
                                                         Question.page == page)).first()
    else:
        question = None
    if experiment and page and question:
        try:
            params = QuestionEditSchema(question['backend', 'fields']).to_python(request.params,
                                                                                 State(request=request,
                                                                                       dbsession=dbsession))
            with transaction.manager:
                dbsession.add(question)
                # Update question parameters
                for key, value in params.items():
                    if isinstance(value, list):
                        new_value = []
                        for sub_value in value:
                            if isinstance(sub_value, dict):
                                if len([v for v in sub_value.values() if v is None]) == 0:
                                    new_value.append(sub_value)
                            else:
                                if sub_value is not None:
                                    new_value.append(sub_value)
                        value = new_value
                    if key != 'csrf_token':
                        question[key] = value
                # Update all question numbering
                dbsession.add(page)
                visible_idx = 1
                for q in page.questions:
                    if q['frontend', 'visible'] and q['frontend', 'title']:
                        q['frontend', 'question_number'] = visible_idx
                        visible_idx = visible_idx + 1
            dbsession.add(experiment)
            dbsession.add(page)
            dbsession.add(question)
            data = render_to_response('ess:templates/page/edit_question.kajiki',
                                      {'experiment': experiment,
                                       'page': page,
                                       'question': question,
                                       'participant': Participant(id=-1)},
                                      request=request)
            return {'status': 'ok',
                    'fragment': data.body.decode('utf-8')}
        except formencode.Invalid as e:
            return {'status': 'error',
                    'errors': e.unpack_errors()}
    else:
        raise HTTPNotFound()
Example #10
0
def action(request):
    """Handles the ``/users/action`` URL, applying the given action to the
    list of selected users. Requires that the current
    :class:`~wte.models.User` has the "admin.users.view"
    :class:`~wte.models.Permission`.
    """
    dbsession = DBSession()
    try:
        query_params = []
        for param in ['q', 'status', 'start']:
            if param in request.params and request.params[param]:
                query_params.append((param, request.params[param]))
        params = ActionSchema().to_python(request.params,
                                          State(request=request))
        if params['action'] != 'delete' or params['confirm']:
            with transaction.manager:
                for user in dbsession.query(User).filter(
                        User.id.in_(params['user_id'])):
                    if params['action'] == 'validate':
                        if user.status == 'unconfirmed' and user.allow(
                                'edit', request.current_user):
                            user.status = 'active'
                    elif params['action'] == 'delete':
                        if user.allow('delete', request.current_user):
                            dbsession.delete(user)
                    elif params['action'] == 'password':
                        if user.status == 'active' and user.allow(
                                'edit', request.current_user):
                            token = TimeToken(
                                user.id, 'reset_password',
                                datetime.now() + timedelta(seconds=1200))
                            dbsession.add(token)
                            dbsession.flush()
                            if 'user.password_reset' in active_callbacks:
                                active_callbacks['user.password_reset'](
                                    request, user, token)
            raise HTTPSeeOther(request.route_url('users', _query=query_params))
        else:
            return {
                'params':
                params,
                'users':
                dbsession.query(User).filter(User.id.in_(params['user_id'])),
                'query_params':
                query_params,
                'crumbs':
                create_user_crumbs(request, [{
                    'title': 'Confirm',
                    'url': request.current_route_url()
                }])
            }
    except Invalid as e:
        print(e)
        request.session.flash(
            'Please select the action you wish to apply and the users to apply it to',
            queue='error')
        raise HTTPSeeOther(request.route_url('users', _query=query_params))
Example #11
0
def delete(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                             Page.experiment_id == request.matchdict['eid'])).first()
    if experiment and page:
        if request.method == 'POST':
            try:
                DeleteSchema().to_python(request.params,
                                         State(request=request,
                                               dbsession=dbsession))
                with transaction.manager:
                    dbsession.add(experiment)
                    dbsession.add(page)
                    if experiment.start == page:
                        experiment.start = None
                    dbsession.delete(page)
                dbsession.add(experiment)
                raise HTTPFound(request.route_url('experiment.page', eid=experiment.id))
            except formencode.Invalid as e:
                return {'experiment': experiment,
                        'page': page,
                        'crumbs': [{'title': 'Experiments',
                                    'url': request.route_url('dashboard')},
                                   {'title': experiment.title,
                                    'url': request.route_url('experiment.view', eid=experiment.id)},
                                   {'title': 'Pages',
                                    'url': request.route_url('experiment.page', eid=experiment.id)},
                                   {'title': '%s (%s)' % (page.title, page.name)
                                    if page.title else 'No title (%s)' % page.name,
                                    'url': request.route_url('experiment.page.edit',
                                                             eid=experiment.id,
                                                             pid=page.id)},
                                   {'title': 'Delete',
                                    'url': request.route_url('experiment.page.delete',
                                                             eid=experiment.id,
                                                             pid=page.id)}],
                        'values': request.params,
                        'errors': e.error_dict}
        return {'experiment': experiment,
                'page': page,
                'crumbs': [{'title': 'Experiments',
                            'url': request.route_url('dashboard')},
                           {'title': experiment.title,
                            'url': request.route_url('experiment.view', eid=experiment.id)},
                           {'title': 'Pages',
                            'url': request.route_url('experiment.page', eid=experiment.id)},
                           {'title': '%s (%s)' % (page.title, page.name)
                            if page.title else 'No title (%s)' % page.name,
                            'url': request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)},
                           {'title': 'Delete',
                            'url': request.route_url('experiment.page.delete', eid=experiment.id, pid=page.id)}]}
    else:
        raise HTTPNotFound()
def delete_part_task(request):
    """Handles the ``parts/{pid}/timed-tasks/{tid}/delete`` URL, providing the UI
    and backend for deleting an existing :class:`~wte.models.TimedTask` that
    belongs to a :class:`~wte.models.Part`.

    Requires that the user has "edit" rights on the
    :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    task = dbsession.query(TimedTask).filter(
        TimedTask.id == request.matchdict['tid']).first()
    if part and task:
        if part.allow('edit', request.current_user):
            crumbs = create_part_crumbs(
                request, part,
                [{
                    'title': 'Timed Actions',
                    'url': request.route_url('part.timed_task', pid=part.id)
                }, {
                    'title': 'Delete',
                    'url': request.current_route_url
                }])
            if request.method == 'POST':
                try:
                    CSRFSchema().to_python(request.params,
                                           State(request=request))
                    dbsession = DBSession()
                    with transaction.manager:
                        dbsession.delete(task)
                    dbsession.add(part)
                    raise HTTPSeeOther(
                        request.route_url('part.timed_task', pid=part.id))
                except formencode.Invalid as e:
                    return {
                        'errors': e.error_dict,
                        'part': part,
                        'task': task,
                        'crumbs': crumbs,
                        'include_footer': True,
                        'help': ['user', 'teacher', 'timed_actions.html']
                    }
            return {
                'part': part,
                'task': task,
                'crumbs': crumbs,
                'include_footer': True,
                'help': ['user', 'teacher', 'timed_actions.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #13
0
def latinsquare_view(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(
        Experiment.id == request.matchdict['eid']).first()
    data_set = dbsession.query(DataSet).filter(
        and_(DataSet.id == request.matchdict['did'],
             DataSet.experiment_id == request.matchdict['eid'],
             DataSet.type == 'latinsquare')).first()
    if experiment and data_set:
        source_a = dbsession.query(DataSet).filter(
            and_(DataSet.id == data_set['source_a'],
                 DataSet.type == 'dataset')).first()
        source_b = dbsession.query(DataSet).filter(
            and_(DataSet.id == data_set['source_b'],
                 DataSet.type == 'dataset')).first()
        return {
            'experiment':
            experiment,
            'data_set':
            data_set,
            'sources': {
                'a': source_a,
                'b': source_b
            },
            'crumbs': [{
                'title': 'Experiments',
                'url': request.route_url('dashboard')
            }, {
                'title':
                experiment.title,
                'url':
                request.route_url('experiment.view', eid=experiment.id)
            }, {
                'title':
                'Latin Squares',
                'url':
                request.route_url('experiment.latinsquare', eid=experiment.id)
            }, {
                'title':
                data_set.name,
                'url':
                request.route_url('experiment.data.view',
                                  eid=experiment.id,
                                  did=data_set.id)
            }]
        }
    else:
        raise HTTPNotFound()
Example #14
0
def delete(request):
    """Handles the ``/parts/{pid}/assets/{aid}/delete`` URL,
    providing the UI and backend for deleting :class:`~wte.models.Asset`.

    Requires that the user has "edit" rights on the current :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    asset = dbsession.query(Asset).join(Part.all_assets).\
        filter(and_(Asset.id == request.matchdict['aid'],
                    Part.id == request.matchdict['pid'])).first()
    if part and asset:
        if part.allow('edit', request.current_user):
            crumbs = create_part_crumbs(request, part, {
                'title': 'Delete Asset',
                'url': request.current_route_url()
            })
            if request.method == 'POST':
                try:
                    CSRFSchema().to_python(request.params,
                                           State(request=request))
                    dbsession = DBSession()
                    with transaction.manager:
                        dbsession.add(asset)
                        asset.parts = []
                        dbsession.delete(asset)
                    dbsession.add(part)
                    raise HTTPSeeOther(
                        request.route_url('part.view', pid=part.id))
                except formencode.Invalid as e:
                    return {
                        'errors': e.error_dict,
                        'part': part,
                        'asset': asset,
                        'crumbs': crumbs,
                        'help': ['user', 'teacher', 'asset', 'delete.html']
                    }
            return {
                'part': part,
                'asset': asset,
                'crumbs': crumbs,
                'help': ['user', 'teacher', 'asset', 'delete.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #15
0
def save_file(request):
    """Handles the ``/parts/{pid}/files/id/{fid}/save``
    URL, updating the :class:`~wte.models.Asset`'s content.

    Requires that the user has "view" rights on the :class:`~wte.models.Part`.
    It will also only update an :class:`~wte.models.Asset` belonging to the
    current :class:`~wte.models.User`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part:
        if part.allow('view', request.current_user):
            progress = get_user_part_progress(dbsession, request.current_user,
                                              part)
            for user_file in progress.files:
                if user_file.id == int(request.matchdict['fid']):
                    if 'content' in request.params:
                        with transaction.manager:
                            dbsession.add(user_file)
                            user_file.data = request.params['content'].encode(
                                'utf-8')
                            user_file.etag = hashlib.sha512(
                                user_file.data).hexdigest()
                        return {'status': 'saved'}
                    else:
                        return {'status': 'no-changes'}
            raise HTTPNotFound()
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
def view_part_tasks(request):
    """Handles the ``parts/{pid}/timed-tasks`` URL, displaying the
    :class:`~wte.models.TimedTask`\ s for the given :class:`~wte.models.Part`.

    Requires that the user has "edit" rights on the
    :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part:
        if part.allow('edit', request.current_user):
            crumbs = create_part_crumbs(request, part, {
                'title': 'Edit Timed Actions',
                'url': request.current_route_url()
            })
            available_tasks = [('change_status', 'Change Status')]
            return {
                'part': part,
                'crumbs': crumbs,
                'available_tasks': available_tasks,
                'include_footer': True,
                'help': ['user', 'teacher', 'timed_actions.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #17
0
def search(request):
    """Handles the ``/parts/{pid}/assets/search`` URL, searching
    for all :class:`~wte.models.Asset` that have a filename that
    matches the 'q' request parameter and that belong to either the
    current :class:`~wte.models.Part` or any of its ancestors.
    The current user must have the "view" permission on the current
    :class:`~wte.models.Part` to see any results.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part:
        if part.allow('view', request.current_user):
            assets = []
            if 'q' in request.params:
                while part is not None:
                    assets.extend([
                        asset for asset in part.assets
                        if request.params['q'] in asset.filename
                    ])
                    part = part.parent
            return [{
                'id': asset.filename,
                'value': asset.filename
            } for asset in assets]
        else:
            raise unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
class DBTester(object):
    """The :class:`~ess_test.conftest.DBTester` provides functionality for interacting with the
    database..
    """

    def __init__(self):
        self._dbsession = DBSession()

    def get_model(self, name, query_params = None):
        """Retrieve a single instance of the model ``name``, filtered by the ``query``.

        :param name: The name of the model to get the instance for
        :type name: ``unicode``
        :param query: The query to use for selecting the instance as a tuple of ``(field, comparator, value)``
        :type query: ``tuple``
        :return: The result of the query
        """
        cls = getattr(models, name)
        query = self._dbsession.query(cls)
        if query_params:
            if query_params[1] == '==':
                query = query.filter(getattr(cls, query_params[0]) == query_params[2])
        return query.first()

    def create_model(self, name, params):
        """Create a new instance of the model ``name`` with the given ``params``.

        :param name: The name of the model to create the instance of
        :type name: ``unicode``
        :param params: The initial parameters to use for creating the instance
        :type params: ``dict``
        :return: The new instance
        """
        cls = getattr(models, name)
        loaded = list(self._dbsession.identity_map.values())
        with transaction.manager:
            model = cls(**params)
            self._dbsession.add(model)
        self._dbsession.add(model)
        self._dbsession.add_all(loaded)
        return model

    def update(self, obj, **kwargs):
        """Update the given ``obj`` with the key/value parameters.

        :param obj: The obj to update
        :type obj: :class:`pywebtools.sqlalchemy.Base`
        """
        loaded = list(self._dbsession.identity_map.values())
        with transaction.manager:
            self._dbsession.add(obj)
            for key, value in kwargs.items():
                if isinstance(value, Base):
                    self._dbsession.add(value)
                setattr(obj, key, value)
        self._dbsession.add_all(loaded)

    def flush(self):
        """Flush the session."""
        self._dbsession.flush()
Example #19
0
def delete(request):
    """Handles the "/users/{uid}/delete" URL, providing the form and backend
    functionality for deleting a :class:`~wte.models.User`. Also deletes all
    the data that is linked to that :class:`~wte.models.User`.
    """
    dbsession = DBSession()
    user = dbsession.query(User).filter(
        User.id == request.matchdict['uid']).first()
    if user:
        if user.allow('delete', request.current_user):
            if request.method == 'POST':
                try:
                    CSRFSchema().to_python(request.params,
                                           State(request=request))
                    with transaction.manager:
                        dbsession.delete(user)
                    request.session.flash('The account has been deleted',
                                          queue='info')
                    if request.current_user.has_permission('admin.users.view'):
                        raise HTTPSeeOther(request.route_url('users'))
                    else:
                        raise HTTPSeeOther(request.route_url('root'))
                except Invalid as e:
                    return {
                        'errors':
                        e.error_dict,
                        'user':
                        user,
                        'crumbs':
                        create_user_crumbs(
                            request,
                            [{
                                'title': user.display_name,
                                'url': request.route_url('user.view',
                                                         uid=user.id)
                            }, {
                                'title':
                                'Delete',
                                'url':
                                request.route_url('user.delete', uid=user.id)
                            }])
                    }
            return {
                'user':
                user,
                'crumbs':
                create_user_crumbs(
                    request,
                    [{
                        'title': user.display_name,
                        'url': request.route_url('user.view', uid=user.id)
                    }, {
                        'title': 'Delete',
                        'url': request.route_url('user.delete', uid=user.id)
                    }])
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #20
0
def view_file(request):
    """Handles the ``parts/{ptid}/pages/{pid}/users/{uid}/files/name/{filename}``
    URL, sending back the correct :class:`~wte.models.Asset`.

    Requires that the user has "view" rights on the :class:`~wte.models.Part`.
    It will also only send an :class:`~wte.models.Asset` belonging to the current
    :class:`~wte.models.User`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part:
        if part.allow('view', request.current_user):
            progress = get_user_part_progress(dbsession, request.current_user,
                                              part)
            for user_file in progress.files:
                if user_file.filename == request.matchdict['filename']:
                    if 'If-None-Match' in request.headers and request.headers[
                            'If-None-Match'] == user_file.etag:
                        raise HTTPNotModified()
                    headers = [('Content-Type', str(user_file.mimetype))]
                    if user_file.etag is not None:
                        headers.append(('ETag', str(user_file.etag)))
                    if 'download' in request.params:
                        headers.append(('Content-Disposition',
                                        str('attachment; filename="%s"' %
                                            (user_file.filename))))
                    return Response(body=user_file.data, headerlist=headers)
            raise HTTPNotFound()
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
def actions(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(
        Experiment.id == request.matchdict['eid']).first()
    if experiment:
        return {
            'experiment':
            experiment,
            'crumbs': [{
                'title': 'Experiments',
                'url': request.route_url('dashboard')
            }, {
                'title':
                experiment.title,
                'url':
                request.route_url('experiment.view', eid=experiment.id)
            }, {
                'title':
                'Actions',
                'url':
                request.route_url('experiment.actions', eid=experiment.id)
            }]
        }
    else:
        raise HTTPNotFound()
Example #22
0
def run_change_status(task_id):
    """Run the status change task for the :class:`~wte.models.TimedTask` with the
    id ``task_id``. It changes the :class:`~wte.models.TimedTask`\ s status to the
    value of the "target_status" key in the :attr:`~wte.models.TimedTask.options`.
    """
    dbsession = DBSession()
    task = dbsession.query(TimedTask).filter(TimedTask.id == task_id).first()
    if task:
        part = dbsession.query(Part).filter(Part.id == task.part_id).first()
        if part and 'target_status' in task.options:
            with transaction.manager:
                dbsession.add(part)
                dbsession.add(task)
                part.status = task.options['target_status']
            with transaction.manager:
                dbsession.add(task)
                task.status = 'completed'
Example #23
0
def run_timed_tasks(args):
    """Runs all timed tasks where the timestamp is in the past and the status is "ready".

    All :class:`~wte.models.TimedTask` that are to be run are given a unique "run-{random-number}"
    ``status`` to uniquely identify them for this run. Individual task runners are then
    responsible for setting that status to "completed" after the task completes successfully
    or to "failed" if it failed.

    All task runners are run in independent :class:`threading.Thread`\ s. After all
    :class:`~threading.Thread` complete, any :class:`~wte.models.TimedTask` that still have
    the unique "run-{random-number}" status are automatically set to the "failed" status.
    """
    settings = get_appsettings(args.configuration)
    setup_logging(args.configuration)
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    dbsession = DBSession()
    tasks = dbsession.query(TimedTask).filter(
        and_(TimedTask.timestamp <= func.now(), TimedTask.status == 'ready'))
    rnd = randint(0, 1000000)
    with transaction.manager:
        tasks.update({TimedTask.status: 'running-%i' % (rnd)},
                     synchronize_session=False)
    tasks = dbsession.query(TimedTask).filter(
        TimedTask.status == 'running-%i' % (rnd))
    task_count = tasks.count()
    if task_count > 0:
        logging.getLogger('wte').info('Running %i tasks' % (task_count))
        threads = []
        for task in tasks:
            if task.name == 'change_status':
                threads.append(
                    Thread(None, target=run_change_status, args=(task.id, )))
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()
        dbsession.flush()
        failed_count = tasks.count()
        if failed_count > 0:
            logging.getLogger('wte').error('%i tasks failed' % (failed_count))
        else:
            logging.getLogger('wte').info('All tasks completed')
        with transaction.manager:
            tasks.update({TimedTask.status: 'failed'})
Example #24
0
def data_item_edit(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(
        Experiment.id == request.matchdict['eid']).first()
    data_set = dbsession.query(DataSet).filter(
        and_(DataSet.id == request.matchdict['did'],
             DataSet.experiment_id == request.matchdict['eid'],
             DataSet.type == 'dataset')).first()
    data_item = dbsession.query(DataItem).filter(
        and_(DataItem.id == request.matchdict['diid'],
             DataItem.dataset_id == request.matchdict['did'])).first()
    if experiment and data_set and data_item:
        try:
            schema = DynamicSchema()
            schema.add_field('csrf_token', CSRFValidator())
            for column in data_set['columns']:
                schema.add_field(
                    column,
                    formencode.validators.UnicodeString(if_empty='',
                                                        if_missing=''))
            params = schema.to_python(request.params, State(request=request))
            with transaction.manager:
                dbsession.add(data_item)
                dbsession.add(data_set)
                data = {}
                for column in data_set['columns']:
                    data[column] = params[column]
                data_item['values'] = data
            dbsession.add(data_item)
            return {
                'status': 'ok',
                'id': data_item.id,
                'order': data_item.order,
                'values': data_item['values']
            }
        except formencode.Invalid as e:
            return {'status': 'error', 'errors': e.unpack_errors()}
        dbsession.add(experiment)
        dbsession.add(data_set)
        dbsession.add(data_item)
        raise HTTPFound(
            request.route_url('experiment.data.view',
                              eid=experiment.id,
                              did=data_set.id))
    else:
        raise HTTPNotFound()
Example #25
0
def export(request):
    dbsession = DBSession()
    experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first()
    page = dbsession.query(Page).filter(and_(Page.id == request.matchdict['pid'],
                                             Page.experiment_id == request.matchdict['eid'])).first()
    if experiment and page:
        try:
            CSRFSchema().to_python(request.params, State(request=request))
            request.response.headers['Content-Disposition'] = 'attachment; filename="%s.json"' % page.name
            return PageIOSchema(include_data=('questions', 'questions.q_type', 'questions.q_type.parent',
                                              'questions.q_type.q_type_group', 'questions.q_type.q_type_group.parent',
                                              'questions.q_type.parent.q_type_group',
                                              'questions.q_type.parent.q_type_group.parent')).dump(page).data
        except formencode.Invalid:
            raise HTTPFound(request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id))
    else:
        raise HTTPNotFound()
def new_part_task(request):
    """Handles the ``parts/{pid}/timed-tasks/new`` URL, providing the UI and
    backend for creating a new :class:`~wte.models.TimedTask`\ s for
    a given :class:`~wte.models.Part`.

    Requires that the user has "edit" rights on the
    :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    part = dbsession.query(Part).filter(
        Part.id == request.matchdict['pid']).first()
    if part:
        if part.allow('edit', request.current_user):
            crumbs = create_part_crumbs(request, part, {
                'title': 'Timed Actions',
                'url': request.current_route_url()
            })
            available_tasks = [('change_status', 'Change Status')]
            if request.method == 'POST':
                try:
                    params = NewTimedTaskSchema().to_python(
                        request.params, State(request=request))
                    with transaction.manager:
                        title = 'Unknown Task'
                        if params['name'] == 'change_status':
                            title = 'Change Status'
                        new_task = TimedTask(name=params['name'],
                                             part_id=part.id,
                                             title=title,
                                             status='new')
                        dbsession.add(new_task)
                    dbsession.add(part)
                    dbsession.add(new_task)
                    raise HTTPSeeOther(
                        request.route_url('part.timed_task.edit',
                                          pid=part.id,
                                          tid=new_task.id))
                except formencode.Invalid as e:
                    return {
                        'errors': e.error_dict,
                        'values': request.params,
                        'part': part,
                        'crumbs': crumbs,
                        'available_tasks': available_tasks,
                        'include_footer': True,
                        'help': ['user', 'teacher', 'timed_actions.html']
                    }
            return {
                'part': part,
                'crumbs': crumbs,
                'available_tasks': available_tasks,
                'include_footer': True,
                'help': ['user', 'teacher', 'timed_actions.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Example #27
0
def login(request):
    """Handles the "user.login" URL, checking the submitted username and
    password against the stored :class:`~pywebtools.pyramid.auth.models.User` and setting the
    necessary session variables if the login is successful.

    Uses either the ``return_to`` parameter in the request to redirect on success or
    the "user.login" redirection route, with parameter replacement "{uid}" will be replaced
    with the logged in user's identifier.
    """
    dbsession = DBSession()
    if request.current_user.logged_in:
        if 'return_to' in request.params and request.params[
                'return_to'] != request.current_route_url():
            if '_default' in active_redirects and \
                    request.params['return_to'] != request.route_url(active_redirects['_default']):
                raise HTTPSeeOther(request.params['return_to'])
        redirect(request, 'user.login', uid=request.current_user.id)
    if request.method == 'POST':
        try:
            dbsession = DBSession()
            params = LoginSchema().to_python(
                request.params,
                State(dbsession=dbsession, request=request, user_class=User))
            user = dbsession.query(User).filter(
                User.email == params['email'].lower()).first()
            request.current_user = user
            request.current_user.logged_in = True
            request.session['uid'] = user.id
            request.session.new_csrf_token()
            if 'return_to' in request.params and request.params[
                    'return_to'] != request.current_route_url():
                if '_default' in active_redirects and \
                        request.params['return_to'] != request.route_url(active_redirects['_default']):
                    raise HTTPSeeOther(request.params['return_to'])
            redirect(request, 'user.login', uid=request.current_user.id)
        except Invalid as e:
            return {
                'errors':
                e.error_dict if e.error_dict else {
                    'email': e.msg,
                    'password': e.msg
                },
                'values':
                request.params,
                'crumbs': [{
                    'title': 'Login',
                    'url': request.route_url('user.login'),
                    'current': True
                }]
            }
    return {
        'crumbs': [{
            'title': 'Login',
            'url': request.route_url('user.login'),
            'current': True
        }]
    }
Example #28
0
def check_answers(request):
    """Handles the "/parts/{pid}/quiz/check_answers" URL, checking whether a
    :class:`~wte.models.QuizAnswer` exists and if so, returning whether it was
    answered correctly and what the answer was.

    The response does not distinguish between initial or finally correct, but will return
    the last answer the user provided.
    """
    try:
        params = CheckAnswerSchema().to_python(request.params,
                                               State(request=request))
        dbsession = DBSession()
        quiz = dbsession.query(Quiz).filter(
            and_(Quiz.part_id == request.matchdict['pid'],
                 Quiz.name == params['quiz'])).first()
        if quiz:
            answer = dbsession.query(QuizAnswer).filter(
                and_(QuizAnswer.user_id == request.current_user.id,
                     QuizAnswer.quiz_id == quiz.id,
                     QuizAnswer.question == params['question'])).first()
            if answer:
                if answer.initial_correct:
                    return {
                        'status': 'correct',
                        'answer': json.loads(answer.initial_answer)
                    }
                elif answer.final_correct:
                    return {
                        'status': 'correct',
                        'answer': json.loads(answer.final_answer)
                    }
                elif answer.final_correct is None:
                    return {
                        'status': 'incorrect',
                        'answer': json.loads(answer.initial_answer)
                    }
                else:
                    return {
                        'status': 'incorrect',
                        'answer': json.loads(answer.final_answer)
                    }
        return {'status': 'unanswered'}
    except Invalid as e:
        return {'status': 'error', 'errors': e.error_dict}
Example #29
0
def set_answers(request):
    """Handles the "/parts/{pid}/quiz/set_answers" URL and stores the answers to a
    :class:`~wte.text_formatter.docutils_ext.QuizQuestion`.

    If no previous :class:`~wte.models.QuizAnswer` exists, creates one and sets
    the initial answers / correctness to the data supplied. If one exists,
    then sets the final answer / correctness and updates the attempts count.
    """
    try:
        dbsession = DBSession()
        part = dbsession.query(Part).filter(
            Part.id == request.matchdict['pid']).first()
        if part and part.has_role('student', request.current_user):
            params = SetAnswersSchema().to_python(request.params,
                                                  State(request=request))
            with transaction.manager:
                quiz = dbsession.query(Quiz).filter(
                    and_(Quiz.part_id == request.matchdict['pid'],
                         Quiz.name == params['quiz'])).first()
                if quiz:
                    answer = dbsession.query(QuizAnswer).filter(
                        and_(QuizAnswer.user_id == request.current_user.id,
                             QuizAnswer.quiz_id == quiz.id, QuizAnswer.question
                             == params['question'])).first()
                    if answer:
                        if not answer.initial_correct and not answer.final_correct:
                            answer.attempts = answer.attempts + 1
                            answer.final_answer = json.dumps(params['answer'])
                            answer.final_correct = params['correct']
                    else:
                        dbsession.add(
                            QuizAnswer(user_id=request.current_user.id,
                                       quiz=quiz,
                                       question=params['question'],
                                       initial_answer=json.dumps(
                                           params['answer']),
                                       initial_correct=params['correct'],
                                       final_answer=None,
                                       final_correct=None,
                                       attempts=1))
        return {}
    except Invalid as e:
        return {'errors': e.error_dict}
Example #30
0
    def has_permission(self, permission):
        """Checks whether the user has been granted the given ``permission``,
        either directly or via a :class:`~pywebtools.pyramid.auth.models.PermissionGroup`.

        :param permission: The permission to check for
        :type permission: `unicode`
        :return: ``True`` if the user has the permission, ``False`` otherwise
        :rtype: `bool`
        """
        if hasattr(self, '_permissions'):
            return permission in self._permissions
        else:
            dbsession = DBSession()
            direct_perm = dbsession.query(Permission.name).join(
                User, Permission.users).filter(User.id == self.id)
            group_perm = dbsession.query(Permission.name).join(PermissionGroup, Permission.permission_groups).\
                join(User, PermissionGroup.users).filter(User.id == self.id)
            self._permissions = [p[0] for p in direct_perm.union(group_perm)]
            return self.has_permission(permission)