Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
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()
Ejemplo n.º 3
0
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()
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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()
Ejemplo n.º 7
0
def new(request):
    """Handles the ``/parts/{pid}/assets/new/{new_type}`` URL, providing the UI and
    backend for creating a new :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()
    if part:
        if part.allow('edit', request.current_user):
            crumbs = create_part_crumbs(
                request, part, {
                    'title': 'Add %s' %
                    (request.matchdict['new_type'].title()),
                    'url': request.current_route_url()
                })
            if request.method == 'POST':
                try:
                    schema = NewAssetSchema()
                    if request.matchdict['new_type'] == 'asset':
                        schema.fields['data'].not_empty = True
                    params = schema.to_python(request.params,
                                              State(request=request))
                    if not params['filename'] and params['data'] is None:
                        raise formencode.Invalid(
                            'You must specify either a file or filename',
                            None,
                            None,
                            error_dict={
                                'filename':
                                'You must specify either a file or filename',
                                'data':
                                'You must specify either a file or filename'
                            })
                    dbsession = DBSession()
                    progress = get_user_part_progress(dbsession,
                                                      request.current_user,
                                                      part)
                    with transaction.manager:
                        dbsession.add(part)
                        if progress:
                            dbsession.add(progress)
                        mimetype = 'application/binary'
                        if params['filename'] is not None:
                            mimetype = guess_type(params['filename'])
                        if params['data'] is not None:
                            mimetype = guess_type(params['data'].filename)
                            if params['filename'] is None:
                                params['filename'] = params['data'].filename
                        if request.matchdict['new_type'] == 'template':
                            new_order = [a.order for a in part.templates]
                        elif request.matchdict['new_type'] == 'asset':
                            new_order = [a.order for a in part.assets]
                        elif request.matchdict['new_type'] == 'file':
                            new_order = [a.order for a in progress.files
                                         ] if progress else []
                        new_order.append(0)
                        new_order = max(new_order) + 1
                        new_asset = Asset(
                            filename=params['filename'],
                            mimetype=mimetype[0]
                            if mimetype[0] else 'application/binary',
                            type=request.matchdict['new_type'],
                            order=new_order,
                            data=params['data'].file.read()
                            if params['data'] is not None else None,
                            etag=hashlib.sha512(
                                params['data'].file.read()).hexdigest()
                            if params['data'] is not None else None)
                        dbsession.add(new_asset)
                        part.all_assets.append(new_asset)
                    if request.is_xhr:
                        request.override_renderer = 'json'
                        dbsession.add(new_asset)
                        dbsession.add(part)
                        return {
                            'part': {
                                'id': part.id
                            },
                            'asset': {
                                'id': new_asset.id,
                                'filename': new_asset.filename
                            }
                        }
                    else:
                        dbsession.add(part)
                        raise HTTPSeeOther(
                            request.route_url('part.view', pid=part.id))
                except formencode.Invalid as e:
                    return {
                        'errors': e.error_dict,
                        'values': request.params,
                        'part': part,
                        'crumbs': crumbs,
                        'help': ['user', 'teacher', 'asset', 'new.html']
                    }
            return {
                'part': part,
                'crumbs': crumbs,
                'help': ['user', 'teacher', 'asset', 'new.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Ejemplo n.º 8
0
def edit(request):
    """Handles the ``/parts/{pid}/assets/{aid}/edit`` URL, providing
    the UI and backend for editing :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': 'Edit %s' % (asset.type.title()),
                    'url': request.current_route_url()
                })
            if request.method == 'POST':
                try:
                    params = EditAssetSchema().to_python(
                        request.params, State(request=request))
                    dbsession = DBSession()
                    with transaction.manager:
                        dbsession.add(asset)
                        asset.filename = params['filename']
                        if params['data'] is not None:
                            asset.data = params['data'].file.read()
                            asset.etag = hashlib.sha512(asset.data).hexdigest()
                            mimetype = guess_type(params['data'].filename)
                            if mimetype[0]:
                                mimetype = mimetype[0]
                            else:
                                if params['mimetype'] != 'other':
                                    mimetype = params['mimetype']
                                else:
                                    mimetype = params['mimetype_other']
                        elif params['content'] is not None:
                            asset.data = params['content'].encode('utf-8')
                            asset.etag = hashlib.sha512(asset.data).hexdigest()
                            if params['mimetype'] != 'other':
                                mimetype = params['mimetype']
                            else:
                                mimetype = params['mimetype_other']
                        else:
                            if params['mimetype'] != 'other':
                                mimetype = params['mimetype']
                            else:
                                mimetype = params['mimetype_other']
                        asset.mimetype = mimetype
                    dbsession.add(part)
                    dbsession.add(asset)
                    raise HTTPSeeOther(
                        request.route_url('part.view', pid=part.id))
                except formencode.Invalid as e:
                    return {
                        'errors': e.error_dict,
                        'values': request.params,
                        'part': part,
                        'asset': asset,
                        'crumbs': crumbs,
                        'help': ['user', 'teacher', 'asset', 'edit.html']
                    }
            return {
                'part': part,
                'asset': asset,
                'crumbs': crumbs,
                'help': ['user', 'teacher', 'asset', 'edit.html']
            }
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Ejemplo n.º 9
0
def users(request):
    """Handles the ``parts/{pid}/users`` URL, displaying the
    :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):
            crumbs = create_part_crumbs(request,
                                        part,
                                        {'title': 'Users',
                                         'url': request.current_route_url()})
            try:
                start = int(request.params['start']) if 'start' in request.params else 0
            except:
                start = 0
            query = dbsession.query(UserPartRole).join(UserPartRole.user, UserPartRole.part).\
                filter(Part.id == request.matchdict['pid'])
            query_params = []
            if 'role' in request.params:
                if request.params['role'] == 'active':
                    query = query.filter(UserPartRole.role != 'block')
                elif request.params['role']:
                    query = query.filter(UserPartRole.role == request.params['role'])
                    query_params.append(('role', request.params['role']))
            else:
                query = query.filter(UserPartRole.role != 'block')
            if 'q' in request.params and request.params['q']:
                query = query.filter(or_(User.display_name.contains(request.params['q']),
                                         User.email.contains(request.params['q'])))
                query_params.append(('q', request.params['q']))
            total_users = query.count()
            if total_users < start:
                start = 0
            if start >= 0:
                users = query.offset(start).limit(30)
            else:
                users = query
            pages = []
            if start == 0:
                pages.append({'type': 'prev'})
            else:
                pages.append({'type': 'prev',
                              'url': request.route_url('part.users',
                                                       pid=part.id,
                                                       _query=query_params + [('start', max(start - 30, 0))])})
            for idx in range(0, int(math.ceil(total_users / 30.0))):
                if idx * 30 == start:
                    pages.append({'type': 'current',
                                  'label': idx + 1})
                else:
                    pages.append({'type': 'item',
                                  'label': idx + 1,
                                  'url': request.route_url('part.users',
                                                           pid=part.id,
                                                           _query=query_params + [('start', idx * 30)])})
            if start + 30 >= total_users:
                pages.append({'type': 'next'})
            else:
                pages.append({'type': 'next',
                              'url': request.route_url('part.users',
                                                       pid=part.id,
                                                       _query=query_params + [('start', start + 30)])})
            current_page = max(0, start / 30)
            return {'part': part,
                    'users': users,
                    'pages': pages,
                    'current_page': current_page,
                    'total_users': total_users,
                    'crumbs': crumbs,
                    'help': ['user', 'teacher', 'module', 'users.html']}
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Ejemplo n.º 10
0
def add(request):
    """Handles the ``parts/{pid}/users/add`` URL, providing the functionality for adding a
    :class:`~wte.models.User` to 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):
            crumbs = create_part_crumbs(request,
                                        part,
                                        [{'title': 'Users',
                                         'url': request.route_url('part.users', pid=part.id)},
                                         {'title': 'Add',
                                          'url': request.current_route_url()}])
            users = None
            if 'q' in request.params:
                users = dbsession.query(User).outerjoin(UserPartRole).\
                    filter(or_(User.display_name.contains(request.params['q']),
                               User.email.contains(request.params['q']))).\
                    filter(User.id.notin_(dbsession.query(User.id).join(UserPartRole).
                                          join(Part).filter(Part.id == part.id)))
            start = 0
            if 'start' in request.params:
                try:
                    start = max(0, int(request.params['start']))
                except:
                    pass
            if users:
                total_users = users.count()
                users = users.offset(start).limit(20).all()
            else:
                total_users = 0
            pages = []
            if start == 0:
                pages.append({'type': 'prev'})
            else:
                pages.append({'type': 'prev',
                              'url': request.route_url('part.users',
                                                       pid=part.id,
                                                       _query=[('q', request.params['q']
                                                                if 'q' in request.params else ''),
                                                               ('start', max(start - 30, 0))])})
            for idx in range(0, int(math.ceil(total_users / 30.0))):
                if idx * 30 == start:
                    pages.append({'type': 'current',
                                  'label': idx + 1})
                else:
                    pages.append({'type': 'item',
                                  'label': idx + 1,
                                  'url': request.route_url('part.users',
                                                           pid=part.id,
                                                           _query=[('q', request.params['q']
                                                                    if 'q' in request.params else ''),
                                                                   ('start', idx * 30)])})
            if start + 30 >= total_users:
                pages.append({'type': 'next'})
            else:
                pages.append({'type': 'next',
                              'url': request.route_url('part.users',
                                                       pid=part.id,
                                                       _query=[('q', request.params['q']
                                                                if 'q' in request.params else ''),
                                                               ('start', start + 30)])})
            if request.method == 'POST':
                try:
                    params = AddUserSchema().to_python(request.params, State(request=request))
                    with transaction.manager:
                        dbsession.add(part)
                        for user_id in params['user_id']:
                            if not dbsession.query(UserPartRole).\
                                filter(and_(UserPartRole.part_id == part.id,
                                            UserPartRole.user_id == user_id)).first():
                                dbsession.add(UserPartRole(user_id=user_id,
                                                           part_id=part.id,
                                                           role=params['role']))
                    raise HTTPSeeOther(request.route_url('part.users', pid=request.matchdict['pid']))
                except formencode.api.Invalid as e:
                    e.params = request.params
                    return {'errors': e.error_dict,
                            'values': request.params,
                            'part': part,
                            'users': users,
                            'total_users': total_users,
                            'start': start,
                            'pages': pages,
                            'crumbs': crumbs,
                            'help': ['user', 'teacher', 'module', 'users.html']}
            return {'part': part,
                    'users': users,
                    'total_users': total_users,
                    'start': start,
                    'pages': pages,
                    'crumbs': crumbs,
                    'help': ['user', 'teacher', 'module', 'users.html']}
        else:
            unauthorised_redirect(request)
    else:
        raise HTTPNotFound()
Ejemplo n.º 11
0
def update(request):
    """Handles the ``parts/{pid}/users`` URL, applying the changes select when the
    user views :func:`~wte.views.user_role.action`.

    Requires that the user has "users" rights on the
    :class:`~wte.models.Part`.
    """
    dbsession = DBSession()
    dbsession.add(request.current_user)
    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:
                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 = []
                params = {}
                params = ActionSchema().to_python(request.params,
                                                  State(request=request))
                users = dbsession.query(UserPartRole).filter(UserPartRole.id.in_(params['role_id'])).all()
                if params['action'] == 'change_role':
                    params = ChangeRoleSchema().to_python(request.params, State(request=request))
                    with transaction.manager:
                        for role in users:
                            dbsession.add(role)
                            role.role = params['role']
                    raise HTTPSeeOther(request.route_url('part.users', pid=request.matchdict['pid'],
                                                         _query=query_params))
                elif params['action'] == 'remove':
                    schema = CSRFSchema()
                    schema.allow_extra_fields = True
                    schema.to_python(request.params, State(request=request))
                    with transaction.manager:
                        dbsession.add(part)
                        parts = get_all_parts(part)
                        for role in users:
                            dbsession.add(role)
                            for child_part in parts:
                                progress = dbsession.query(UserPartProgress).\
                                    filter(and_(UserPartProgress.part_id == child_part.id,
                                                UserPartProgress.user_id == role.user.id)).first()
                                if progress:
                                    dbsession.delete(progress)
                            dbsession.delete(role)
                    raise HTTPSeeOther(request.route_url('part.users', pid=request.matchdict['pid'],
                                                         _query=query_params))
                elif params['action'] == 'block':
                    schema = CSRFSchema()
                    schema.allow_extra_fields = True
                    schema.to_python(request.params, State(request=request))
                    with transaction.manager:
                        for role in users:
                            dbsession.add(role)
                            role.role = 'block'
                    raise HTTPSeeOther(request.route_url('part.users', pid=request.matchdict['pid'],
                                                         _query=query_params))
            except formencode.api.Invalid as e:
                return {'errors': e.error_dict,
                        'values': request.params,
                        'part': part,
                        'params': params,
                        'query_params': query_params,
                        'users': users,
                        'crumbs': crumbs,
                        'help': ['user', 'teacher', 'module', 'users.html']}
            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()