def logout(request): """Handles the "user.logout" URL and deletes the current session, thus logging the user out. Redirects to the "user.logout" redirection route. """ if request.method == 'POST': try: CSRFSchema().to_python(request.params, State(request=request)) request.current_user.logged_in = False request.session.delete() redirect(request, 'user.logout') except Invalid as e: return { 'errors': e.error_dict, 'crumbs': [{ 'title': 'Logout', 'url': request.route_url('user.logout'), 'current': True }] } return { 'crumbs': [{ 'title': 'Logout', 'url': request.route_url('user.logout'), 'current': True }] }
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()
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()
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()
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()
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()
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()
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 }] }
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()
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))
def import_experiment(request): if request.method == 'POST': try: params = ImportExperimentSchema().to_python( request.params, State(request=request)) dbsession = DBSession() with transaction.manager: experiment, errors = ExperimentIOSchema(include_schemas=[s for s in all_io_schemas if s != ExperimentIOSchema]).\ loads(params['source'].file.read().decode('utf-8')) if errors: raise formencode.Invalid( '. '.join([ '%s: %s' % (e['source']['pointer'], e['detail']) for e in errors['errors'] ]), None, None) for page in experiment.pages: replace_questions(page, dbsession) experiment.owner = request.current_user experiment.external_id = uuid.uuid1().hex, experiment.status = 'develop' dbsession.add(experiment) dbsession.flush() for latin_square in experiment.latin_squares: fix_latinsquare(latin_square, experiment.data_sets) for page in experiment.pages: for transition in page.next: fix_transition(transition, experiment.pages) dbsession.add(experiment) raise HTTPFound( request.route_url('experiment.view', eid=experiment.id)) except formencode.Invalid as e: return { 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': 'Import', 'url': request.route_url('experiment.import') }], 'errors': { 'source': str(e) }, 'values': request.params } return { 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': 'Import', 'url': request.route_url('experiment.import') }] }
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()
def import_page(request): dbsession = DBSession() experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first() if experiment: if request.method == 'POST': try: params = ImportPageSchema().to_python(request.params, State(request=request, dbsession=dbsession)) with transaction.manager: dbsession.add(experiment) page, errors = 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')).\ loads(params['source'].file.read().decode('utf-8')) if errors: raise formencode.Invalid('. '.join(['%s: %s' % (e['source']['pointer'], e['detail']) for e in errors['errors']]), None, None) dbsession.add(page) replace_questions(page, dbsession) page.experiment = experiment dbsession.add(experiment) dbsession.add(page) raise HTTPFound(request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)) except formencode.Invalid as e: return {'experiment': experiment, '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': 'Import a Page', 'url': request.route_url('experiment.page.import', eid=experiment.id)}], 'errors': {'source': str(e)}, 'values': request.params} return {'experiment': experiment, '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': 'Import a Page', 'url': request.route_url('experiment.page.import', eid=experiment.id)}]} else: raise HTTPNotFound()
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()
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 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()
def create(request): dbsession = DBSession() experiment = dbsession.query(Experiment).filter(Experiment.id == request.matchdict['eid']).first() if experiment: if request.method == 'POST': try: params = CreatePageSchema().to_python(request.params, State(request=request, dbsession=dbsession, experiment=experiment)) with transaction.manager: page = Page(experiment=experiment, name=params['name'], title=params['title'], styles='', scripts='') dbsession.add(page) dbsession.add(experiment) if experiment.start is None: experiment.start = page dbsession.add(experiment) dbsession.add(page) raise HTTPFound(request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)) except formencode.Invalid as e: return {'experiment': experiment, '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': 'Add a Page', 'url': request.route_url('experiment.page.create', eid=experiment.id)}], 'errors': e.error_dict, 'values': request.params} return {'experiment': experiment, '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': 'Add a Page', 'url': request.route_url('experiment.page.create', eid=experiment.id)}]} else: raise HTTPNotFound()
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}
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}
def create(request): if request.method == 'POST': try: params = CreateExperimentSchema().to_python( request.params, State(request=request)) dbsession = DBSession() with transaction.manager: experiment = Experiment(title=params['title'], summary='', styles='', scripts='', status='develop', language='en', external_id=uuid.uuid1().hex, owned_by=request.current_user.id, public=False) dbsession.add(experiment) dbsession.add(experiment) raise HTTPFound( request.route_url('experiment.view', eid=experiment.id)) except formencode.Invalid as e: return { 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': 'Create', 'url': request.route_url('experiment.create') }], 'errors': e.error_dict, 'values': request.params } return { 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': 'Create', 'url': request.route_url('experiment.create') }] }
def add_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 qtype = dbsession.query(QuestionType).filter(QuestionType.id == request.matchdict['qtid']).first() if experiment and page and qtype: try: params = AddQuestionSchema().to_python(request.params, State(request=request)) with transaction.manager: dbsession.add(page) if params['order'] is None: params['order'] = len(page.questions) question = Question(q_type=qtype, page=page, order=params['order']) if question['frontend', 'visible']: visible_idx = [q['frontend', 'question_number'] for q in page.questions if q['frontend', 'visible'] and q['frontend', 'title'] and q['frontend', 'question_number']] if visible_idx: visible_idx = max(visible_idx) + 1 else: visible_idx = 1 question['frontend', 'question_number'] = visible_idx dbsession.add(question) dbsession.add(experiment) dbsession.add(page) dbsession.add(question) raise HTTPFound(request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id, _anchor='question-%i' % question.id)) except formencode.Invalid: raise HTTPFound(request.route_url('experiment.page.edit', eid=experiment.id, pid=page.id)) else: raise HTTPNotFound()
def reorder_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: params = ReorderSchema().to_python(request.params, State(request=request)) with transaction.manager: for idx, tid in enumerate(params['item']): transition = dbsession.query(Transition).filter(and_(Transition.id == tid, Transition.source == page)).first() if transition is not None: transition.order = idx return {'status': 'ok'} except formencode.Invalid: return {'status': 'error'} else: raise HTTPNotFound()
def action(request): """Handles the ``parts/{pid}/users/action`` URL, loads the interface for changing :class:`~wte.models.User` registered for a :class:`~wte.models.Part`. Requires that the user has "users" 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('users', request.current_user): query_params = [] for param in ['q', 'role', 'start']: if param in request.params and request.params[param]: query_params.append((param, request.params[param])) try: params = ActionSchema().to_python(request.params, State(request=request)) except formencode.api.Invalid: request.session.flash('Please select the action you wish to apply ' + 'and the users you wish to apply it to', queue='error') raise HTTPSeeOther(request.route_url('part.users', pid=part.id, _query=query_params)) crumbs = create_part_crumbs(request, part, [{'title': 'Users', 'url': request.route_url('part.users', pid=part.id)}, {'title': 'Update', 'url': request.current_route_url()}]) users = dbsession.query(UserPartRole).filter(UserPartRole.id.in_(params['role_id'])).all() return {'part': part, 'params': params, 'query_params': query_params, 'users': users, 'crumbs': crumbs, 'help': ['user', 'teacher', 'module', 'users.html']} else: unauthorised_redirect(request) else: raise HTTPNotFound()
def data_reorder(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() if experiment and data_set: try: params = ReorderSchema().to_python(request.params, State(request=request)) with transaction.manager: for idx, qid in enumerate(params['item']): item = dbsession.query(DataItem).filter( and_(DataItem.id == qid, DataItem.dataset_id == data_set.id)).first() if item is not None: item.order = idx return {'status': 'ok'} except formencode.Invalid: return {'status': 'error'} else: raise HTTPNotFound()
def data_delete(request): mode = 'latinsquare' if 'latinsquare' in request.matched_route.name else 'dataset' 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 == mode)).first() if experiment and data_set: try: CSRFSchema().to_python(request.params, State(request=request)) with transaction.manager: dbsession.delete(data_set) dbsession.add(experiment) raise HTTPFound( request.route_url('experiment.%s' % mode, eid=experiment.id)) except formencode.Invalid: raise HTTPFound( request.route_url('experiment.%s.view' % mode, eid=experiment.id, did=data_set.id)) else: raise HTTPNotFound()
def export_settings(request): dbsession = DBSession() experiment = dbsession.query(Experiment).filter( Experiment.id == request.matchdict['eid']).first() if experiment: if request.method == 'POST': try: schema = ExportSchema() questions = [ str(q.id) for p in experiment.pages for q in p.questions if q['frontend', 'generates_response'] ] schema.add_field( 'question', formencode.ForEach( formencode.validators.OneOf(questions, not_empty=True))) for data_set in experiment.data_sets: schema.add_field( 'data_set_identifier_%s' % data_set.id, formencode.validators.OneOf( ['_id'] + [str(c) for c in data_set['columns']])) params = schema.to_python(request.params, State(request=request)) def data_item_column_mapper(data_item): if params['data_set_identifier_%s' % data_item.dataset_id] == '_id': return data_item.id else: return data_item['values'][params[ 'data_set_identifier_%s' % data_item.dataset_id]] columns = set(['_participant']) if params['include_incomplete']: query = dbsession.query(Participant).filter( and_(Participant.experiment_id == experiment.id)) columns.add('_completed') else: query = dbsession.query(Participant).filter( and_(Participant.experiment_id == experiment.id, Participant.completed == True)) # noqa: 712 if params['include_useragent']: columns.add('_user_agent.screen_size') columns.add('_user_agent.string') for participant in query: if params['include_useragent']: if 'user_agent' in participant: if 'input_types' in participant['user_agent']: columns.update([ '_user_agent.input_type.%s' % input_type for input_type in participant['user_agent'] ['input_types'] ]) for answer in participant.answers: if str(answer.question.id) not in params['question']: continue if 'response' not in answer or answer[ 'response'] is None: continue column = '%s.%s' % (answer.question.page.name, answer.question['name']) new_columns = [] if answer.question[ 'frontend', 'display_as'] == 'select_simple_choice': if answer.question['frontend', 'allow_other']: if answer.question['frontend', 'allow_multiple']: if isinstance( answer['response']['response'], list): new_columns = [ '%s.%s' % (column, a) for a in answer['response']['response'] ] else: new_columns = [ '%s.%s' % (column, answer['response']['response']) ] new_columns.append('%s.other_response' % (column)) else: new_columns = [ column, '%s.other_response' % column ] else: if answer.question['frontend', 'allow_multiple']: if isinstance(answer['response'], list): new_columns = [ '%s.%s' % (column, a) for a in answer['response'] ] else: new_columns = [ '%s.%s' % (column, answer['response']) ] else: new_columns = [column] if answer.question['frontend', 'randomise_answers']: new_columns.append('%s.answer_order' % column) elif answer.question[ 'frontend', 'display_as'] == 'select_grid_choice': for key, value in answer['response'].items(): sub_column = '%s.%s' % (column, key) if answer.question['frontend', 'allow_multiple']: if isinstance(value, list): sub_column = [ '%s.%s' % (sub_column, a) for a in value ] else: sub_column = [ '%s.%s' % (sub_column, value) ] else: sub_column = [sub_column] new_columns.extend(sub_column) if answer.question['frontend', 'randomise_answers']: new_columns.append('%s.answer_order' % column) if answer.question['frontend', 'randomise_questions']: new_columns.append('%s.question_order' % column) elif answer.question['frontend', 'display_as'] == 'ranking': new_columns.extend([ '%s.%s' % (column, a['value']) for a in answer.question['frontend', 'answers'] ]) if answer.question['frontend', 'randomise_answers']: new_columns.append('%s.answer_order' % column) else: if answer.question['frontend', 'allow_multiple']: if isinstance(answer['response'], list): new_columns = [ '%s.%s' % (column, a) for a in answer['response'] ] else: new_columns = [ '%s.%s' % (column, answer['response']) ] else: new_columns = [column] if answer.question.page.dataset_id is not None: new_columns = [ '%s.%s' % (c, data_item_column_mapper(di)) for c in new_columns for di in answer.question.page.data_set.items ] columns.update(new_columns) columns = list(columns) columns.sort(key=lambda k: k.split('.')) io = StringIO() writer = DictWriter(io, fieldnames=columns, restval=params['na_value'], extrasaction='ignore') writer.writeheader() for participant in query.order_by(Participant.id): responses = {'_participant': participant.id} if params['include_incomplete']: responses['_completed'] = participant.completed if params['include_useragent']: if 'user_agent' in participant: if 'input_types' in participant['user_agent']: for column in columns: if column.startswith( '_user_agent.input_type.'): responses[column] = 0 for input_type in participant['user_agent'][ 'input_types']: responses['_user_agent.input_type.%s' % input_type] = 1 if 'screen_size' in participant['user_agent']: responses[ '_user_agent.screen_size'] = participant[ 'user_agent']['screen_size'] if 'user_agent' in participant['user_agent']: responses['_user_agent.string'] = participant[ 'user_agent']['user_agent'] for answer in participant.answers: if 'response' not in answer or answer[ 'response'] is None: continue column = '%s.%s' % (answer.question.page.name, answer.question['name']) if answer.question[ 'frontend', 'display_as'] == 'select_simple_choice': if answer.question['frontend', 'allow_other']: if answer.question['frontend', 'allow_multiple']: for c in columns: if c.startswith(column): responses[c] = 0 if isinstance( answer['response']['response'], list): for sub_answer in answer['response'][ 'response']: if answer.data_item_id is None: responses['%s.%s' % (column, sub_answer)] = 1 else: responses['%s.%s.%s' % ( column, sub_answer, data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses['%s.%s' % (column, answer['response'] ['response'])] = 1 else: responses['%s.%s.%s' % ( column, answer['response']['response'], data_item_column_mapper( answer.data_item))] = 1 if answer['response']['other']: if answer.data_item_id is None: responses['%s.other_response' % column] = answer[ 'response']['other'] else: responses['%s.other_response.%s' % (column, data_item_column_mapper(answer.data_item))] = \ answer['response']['other'] else: if answer.data_item_id is None: if isinstance(answer['response'], dict): responses[column] = answer[ 'response']['response'] if answer['response']['other']: responses[ '%s.other_response' % column] = answer[ 'response']['other'] else: responses[column] = answer[ 'response'] else: responses['%s.%s' % (column, data_item_column_mapper(answer.data_item))] = \ answer['response']['response'] if answer['response']['other']: responses['%s.other_response.%s' % (column, data_item_column_mapper(answer.data_item))] = \ answer['response']['other'] else: if answer.question['frontend', 'allow_multiple']: for c in columns: if c.startswith(column): responses[c] = 0 if isinstance(answer['response'], list): for sub_answer in answer['response']: if answer.data_item_id is None: responses['%s.%s' % (column, sub_answer)] = 1 else: responses['%s.%s.%s' % ( column, sub_answer, data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses['%s.%s' % (column, answer['response'])] = 1 else: responses[ '%s.%s.%s' % (column, answer['response'], data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses[column] = answer['response'] else: responses['%s.%s' % (column, data_item_column_mapper(answer.data_item))] = \ answer['response'] if answer.question['frontend', 'randomise_answers']: responses['%s.answer_order' % column] = answer['answer_order'] elif answer.question[ 'frontend', 'display_as'] == 'select_grid_choice': for key, value in answer['response'].items(): sub_column = '%s.%s' % (column, key) if answer.question['frontend', 'allow_multiple']: for c in columns: if c.startswith(sub_column): responses[c] = 0 if isinstance(value, list): for sub_answer in value: if answer.data_item_id is None: responses['%s.%s' % (sub_column, sub_answer)] = 1 else: responses['%s.%s.%s' % ( sub_column, sub_answer, data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses['%s.%s' % (sub_column, value)] = 1 else: responses[ '%s.%s.%s' % (sub_column, value, data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses[sub_column] = value else: responses[ '%s.%s' % (sub_column, data_item_column_mapper( answer.data_item))] = value if answer.question['frontend', 'randomise_answers']: responses['%s.answer_order' % column] = answer['answer_order'] if answer.question['frontend', 'randomise_questions']: responses['%s.question_order' % column] = answer['question_order'] elif answer.question['frontend', 'display_as'] == 'ranking': for idx, sub_answer in enumerate( answer['response']): if answer.data_item_id is None: responses['%s.%s' % (column, sub_answer)] = idx else: responses['%s.%s.%s' % (column, sub_answer, data_item_column_mapper( answer.data_item))] = idx if answer.question['frontend', 'randomise_answers']: responses['%s.answer_order' % column] = answer['answer_order'] else: if answer.question['frontend', 'allow_multiple']: for c in columns: if c.startswith(column): responses[c] = 0 if isinstance(answer['response'], list): for sub_answer in answer['response']: if answer.data_item_id is None: responses['%s.%s' % (column, sub_answer)] = 1 else: responses[ '%s.%s.%s' % (column, sub_answer, data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses['%s.%s' % (column, answer['response'])] = 1 else: responses['%s.%s.%s' % (column, answer['response'], data_item_column_mapper( answer.data_item))] = 1 else: if answer.data_item_id is None: responses[column] = answer['response'] else: responses['%s.%s' % (column, data_item_column_mapper(answer.data_item))] = \ answer['response'] writer.writerow(responses) return Response(body=io.getvalue().encode('utf8'), headers=[('Content-Type', 'text/csv'), ('Content-Disposition', 'attachment; filename="%s.csv"' % experiment.title)]) except formencode.Invalid as e: return { 'experiment': experiment, 'values': request.params, 'errors': e.error_dict, 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': experiment.title, 'url': request.route_url('experiment.view', eid=experiment.id) }, { 'title': 'Results', 'url': request.route_url('experiment.results', eid=experiment.id) }, { 'title': 'Export', 'url': request.route_url('experiment.results.export', eid=experiment.id) }] } return { 'experiment': experiment, 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': experiment.title, 'url': request.route_url('experiment.view', eid=experiment.id) }, { 'title': 'Results', 'url': request.route_url('experiment.results', eid=experiment.id) }, { 'title': 'Export', 'url': request.route_url('experiment.results.export', eid=experiment.id) }] } else: raise HTTPNotFound()
def reset(request): dbsession = DBSession() experiment = dbsession.query(Experiment).filter( Experiment.id == request.matchdict['eid']).first() if experiment: if request.method == 'POST': try: ResetSchema().to_python(request.params, State(request=request)) with transaction.manager: dbsession.add(experiment) experiment.participants = [] dbsession.add(experiment) raise HTTPFound( request.route_url('experiment.results', eid=experiment.id)) except formencode.Invalid as e: return { 'experiment': experiment, 'values': request.params, 'errors': e.error_dict, 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': experiment.title, 'url': request.route_url('experiment.view', eid=experiment.id) }, { 'title': 'Results', 'url': request.route_url('experiment.results', eid=experiment.id) }, { 'title': 'Reset', 'url': request.route_url('experiment.results.reset', eid=experiment.id) }] } return { 'experiment': experiment, 'crumbs': [{ 'title': 'Experiments', 'url': request.route_url('dashboard') }, { 'title': experiment.title, 'url': request.route_url('experiment.view', eid=experiment.id) }, { 'title': 'Results', 'url': request.route_url('experiment.results', eid=experiment.id) }, { 'title': 'Clear', 'url': request.route_url('experiment.results.reset', eid=experiment.id) }] } else: raise HTTPNotFound()
def edit_part_task(request): """Handles the ``parts/{pid}/timed-tasks/{tid}/edit`` URL, providing the UI and backend for editing 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': 'Edit', 'url': request.current_route_url }]) if request.method == 'POST': try: options = [] if task.name == 'change_status': status = ['available', 'unavailable'] if part.type == 'module': status.append('archived') options.append(('target_status', formencode.validators.OneOf(status))) params = EditTimedTaskSchema(options).to_python( request.params, State(request=request)) dbsession = DBSession() with transaction.manager: dbsession.add(task) task.timestamp = datetime.combine( params['date'], params['time']) if 'options' in params and params['options']: task.options = params['options'] task.status = 'ready' dbsession.add(part) raise HTTPSeeOther( request.route_url('part.timed_task', pid=part.id)) except formencode.Invalid as e: return { 'errors': e.error_dict, 'values': request.params, '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()
def permissions(request): """Handles the "/users/{uid}/permissions" URL, providing the form and backend functionality for setting the :class:`~wte.models.Permission` and :class:`~wte.models.PermissionGroup` that the :class:`~wte.models.User` belongs to. """ dbsession = DBSession() user = dbsession.query(User).filter( User.id == request.matchdict['uid']).first() if user: permission_groups = dbsession.query(PermissionGroup).order_by( PermissionGroup.title) permissions = dbsession.query(Permission).order_by(Permission.title) if request.method == 'POST': try: CSRFSchema(allow_extra_fields=True).to_python( request.params, State(request=request)) with transaction.manager: dbsession.add(user) ids = request.params.getall('permission_group') if ids: user.permission_groups = dbsession.query(PermissionGroup).\ filter(PermissionGroup.id.in_(ids)).all() else: user.permission_groups = [] ids = request.params.getall('permission') if ids: user.permissions = dbsession.query(Permission).filter( Permission.id.in_(ids)).all() else: user.permissions = [] dbsession.add(user) dbsession.add(request.current_user) if request.current_user.has_permission('admin.users.view'): raise HTTPSeeOther(request.route_url('users')) else: raise HTTPSeeOther( request.route_url('user.view', uid=user.id)) except Invalid as e: return { 'errors': e.error_dict, 'user': user, 'permission_groups': permission_groups, 'permissions': permissions, 'crumbs': create_user_crumbs( request, [{ 'title': user.display_name, 'url': request.route_url('user.view', uid=user.id) }, { 'title': 'Permissions', 'url': request.route_url('user.permissions', uid=user.id) }]) } return { 'user': user, 'permission_groups': permission_groups, 'permissions': permissions, 'crumbs': create_user_crumbs( request, [{ 'title': user.display_name, 'url': request.route_url('user.view', uid=user.id) }, { 'title': 'Permissions', 'url': request.route_url('user.permissions', uid=user.id) }]) } else: raise HTTPNotFound()