Example #1
0
import os
import re
from functools import wraps

from flask import redirect, render_template, jsonify, current_app
from flask_login import login_required, current_user
from flask import make_response
from flask import send_file, url_for, redirect, request
from flask_restful.reqparse import RequestParser

from bouncer.constants import READ
from compair.authorization import require
from compair.kaltura import KalturaAPI
from compair.models import File
from compair.core import event
on_get_file = event.signal('GET_FILE')

attachment_download_parser = RequestParser()
attachment_download_parser.add_argument('name', default=None)

def register_api_blueprints(app):
    # Initialize rest of the api modules
    from .course import course_api
    app.register_blueprint(
        course_api,
        url_prefix='/api/courses')

    from .classlist import classlist_api
    app.register_blueprint(
        classlist_api,
        url_prefix='/api/courses/<course_uuid>/users')
Example #2
0
                                     nullable=False,
                                     help='Course term/semester is required.')
duplicate_course_parser.add_argument('sandbox', type=bool, default=False)
duplicate_course_parser.add_argument('start_date',
                                     required=True,
                                     nullable=False,
                                     help='Course start date is required.')
duplicate_course_parser.add_argument('end_date', default=None)
# has to add location parameter, otherwise MultiDict will screw up the list
duplicate_course_parser.add_argument('assignments',
                                     type=list,
                                     default=[],
                                     location='json')  #only ids and dates

# events
on_course_modified = event.signal('COURSE_MODIFIED')
on_course_get = event.signal('COURSE_GET')
on_course_delete = event.signal('COURSE_DELETE')
on_course_list_get = event.signal('COURSE_LIST_GET')
on_course_create = event.signal('COURSE_CREATE')
on_course_duplicate = event.signal('COURSE_DUPLICATE')


class CourseListAPI(Resource):
    @login_required
    def post(self):
        """
        Create new course
        """
        require(
            CREATE,
Example #3
0
from copy import copy
from flask import Blueprint, current_app
from flask_login import current_user, login_required
from flask_restful import Resource, marshal

from compair.models import User
from compair.core import abort, impersonation, event
from .util import new_restful_api
from . import dataformat

impersonation_api = Blueprint('impersonation_api', __name__)
IMPERSONATION_API_BASE_URL = '/api/impersonate'

# events
on_impersonation_started = event.signal('IMPERSONATION_STARTED')
on_impersonation_stopped = event.signal('IMPERSONATION_STOPPED')

class ImpersonationAPI(Resource):
    @login_required
    def post(self, user_uuid):
        if not current_app.config.get('IMPERSONATION_ENABLED', False):
            abort(404, title="Impersonation not supported", message="Impersonation function not enabled")

        original_user = User.query.get(current_user.id)
        impersonate_as_uuid = user_uuid
        impersonate_as_user = User.get_by_uuid_or_404(impersonate_as_uuid)
        if not impersonation.can_impersonate(impersonate_as_user.get_id()):
            abort(400, title="Impersonation Failed", message="Cannot perform impersonation")

        impersonation_success = impersonation.start_impersonation(impersonate_as_user.get_id())
Example #4
0
    'displayname': 6,
    'group_name': 7
}

CAS_OR_SAML_IMPORT = {
    'username': 0,
    'student_number': 1,
    'firstname': 2,
    'lastname': 3,
    'email': 4,
    'displayname': 5,
    'group_name': 6
}

# events
on_classlist_get = event.signal('CLASSLIST_GET')
on_classlist_upload = event.signal('CLASSLIST_UPLOAD')
on_classlist_enrol = event.signal('CLASSLIST_ENROL')
on_classlist_unenrol = event.signal('CLASSLIST_UNENROL')
on_classlist_instructor = event.signal('CLASSLIST_INSTRUCTOR_GET')
on_classlist_student = event.signal('CLASSLIST_STUDENT_GET')
on_classlist_update_users_course_roles = event.signal(
    'CLASSLIST_UPDATE_USERS_COURSE_ROLES')


def _parse_user_row(import_type, row):
    length = len(row)

    columns = COMPAIR_IMPORT
    if import_type == ThirdPartyType.cas.value or import_type == ThirdPartyType.saml.value:
        columns = CAS_OR_SAML_IMPORT
Example #5
0
from compair.models import Assignment, Course, AssignmentComment
from .util import new_restful_api, get_model_changes, pagination_parser

assignment_comment_api = Blueprint('assignment_comment_api', __name__)
api = new_restful_api(assignment_comment_api)

new_assignment_comment_parser = RequestParser()
new_assignment_comment_parser.add_argument('content', required=True)

existing_assignment_comment_parser = new_assignment_comment_parser.copy()
existing_assignment_comment_parser.add_argument('id',
                                                required=True,
                                                help="Comment id is required.")

# events
on_assignment_comment_modified = event.signal('ASSIGNMENT_COMMENT_MODIFIED')
on_assignment_comment_get = event.signal('ASSIGNMENT_COMMENT_GET')
on_assignment_comment_list_get = event.signal('ASSIGNMENT_COMMENT_LIST_GET')
on_assignment_comment_create = event.signal('ASSIGNMENT_COMMENT_CREATE')
on_assignment_comment_delete = event.signal('ASSIGNMENT_COMMENT_DELETE')


# /
class AssignmentCommentRootAPI(Resource):
    # TODO pagination
    @login_required
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(
            READ,
Example #6
0
from sqlalchemy import func, and_, or_
from sqlalchemy.orm import undefer, joinedload

from . import dataformat
from compair.authorization import require
from compair.models import Course, Assignment, CourseRole, User, UserCourse, Comparison, \
    AnswerComment, Answer, File, AnswerScore, AnswerCommentType, PairingAlgorithm, \
    AssignmentGrade, Group
from .util import new_restful_api
from compair.core import event, abort

gradebook_api = Blueprint('gradebook_api', __name__)
api = new_restful_api(gradebook_api)

# events
on_gradebook_get = event.signal('GRADEBOOK_GET')


# /
class GradebookAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(
            assignment_uuid,
            joinedloads=['assignment_criteria']
        )
        require(MANAGE, assignment,
            title="Participation Results Unavailable",
            message="Sorry, your role in this course does not allow you to view student participation for this assignment.")
Example #7
0
from compair.authorization import allow, require
from compair.models import Assignment, Course, Answer, ComparisonExample
from .util import new_restful_api, get_model_changes

comparison_example_api = Blueprint('comparison_example_api', __name__)
api = new_restful_api(comparison_example_api)

new_comparison_example_parser = RequestParser()
new_comparison_example_parser.add_argument('answer1_id', required=True, nullable=False)
new_comparison_example_parser.add_argument('answer2_id', required=True, nullable=False)

existing_comparison_example_parser = new_comparison_example_parser.copy()
existing_comparison_example_parser.add_argument('id', required=True, nullable=False)

# events
on_comparison_example_modified = event.signal('COMPARISON_EXAMPLE_MODIFIED')
on_comparison_example_list_get = event.signal('COMPARISON_EXAMPLE_LIST_GET')
on_comparison_example_create = event.signal('COMPARISON_EXAMPLE_CREATE')
on_comparison_example_delete = event.signal('COMPARISON_EXAMPLE_DELETE')

# /id
class ComparisonExampleIdAPI(Resource):
    @login_required
    def post(self, course_uuid, assignment_uuid, comparison_example_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        comparison_example = ComparisonExample.get_active_by_uuid_or_404(comparison_example_uuid)
        require(EDIT, comparison_example,
            title="Comparison Example Not Saved",
            message="Sorry, your role in this course does not allow you to save practice answers.")
Example #8
0
user_id_course_list_parser = pagination_parser.copy()
user_id_course_list_parser.add_argument('search', required=False, default=None)
user_id_course_list_parser.add_argument('includeSandbox',
                                        type=string_to_bool,
                                        required=False,
                                        default=None)
user_id_course_list_parser.add_argument('orderBy',
                                        required=False,
                                        default=None)
user_id_course_list_parser.add_argument('reverse', type=bool, default=False)

user_course_status_list_parser = RequestParser()
user_course_status_list_parser.add_argument('ids', required=True, default=None)

# events
on_user_modified = event.signal('USER_MODIFIED')
on_user_get = event.signal('USER_GET')
on_user_list_get = event.signal('USER_LIST_GET')
on_user_create = event.signal('USER_CREATE')
on_user_course_get = event.signal('USER_COURSE_GET')
on_user_course_status_get = event.signal('USER_COURSE_STATUS_GET')
on_teaching_course_get = event.signal('USER_TEACHING_COURSE_GET')
on_user_edit_button_get = event.signal('USER_EDIT_BUTTON_GET')
on_user_notifications_update = event.signal('USER_NOTIFICATIONS_UPDATE')
on_user_password_update = event.signal('USER_PASSWORD_UPDATE')


def check_valid_system_role(system_role):
    system_roles = [
        SystemRole.sys_admin.value, SystemRole.instructor.value,
        SystemRole.student.value
Example #9
0
answer_list_parser.add_argument('orderBy', required=False, default=None)
answer_list_parser.add_argument('top', type=bool, required=False, default=None)
answer_list_parser.add_argument('ids', required=False, default=None)

user_answer_list_parser = RequestParser()
user_answer_list_parser.add_argument('draft', type=bool, required=False, default=False)

top_answer_parser = RequestParser()
top_answer_parser.add_argument(
    'top_answer', type=bool, required=True, nullable=False,
    help="Expected boolean value 'top_answer' is missing."
)


# events
on_answer_modified = event.signal('ANSWER_MODIFIED')
on_answer_get = event.signal('ANSWER_GET')
on_answer_list_get = event.signal('ANSWER_LIST_GET')
on_answer_create = event.signal('ANSWER_CREATE')
on_answer_delete = event.signal('ANSWER_DELETE')
on_set_top_answer = event.signal('SET_TOP_ANSWER')
on_user_answer_get = event.signal('USER_ANSWER_GET')

from compair.api.file import on_attach_file, on_detach_file

# /
class AnswerRootAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        """
        Return a list of answers for a assignment based on search criteria. The
Example #10
0
answer_comparison_list_parser.add_argument('author', type=str, required=False, default=None)

flag_parser = RequestParser()
flag_parser.add_argument('flagged', type=bool, required=True,
    help="Expected boolean value 'flagged' is missing."
)

top_answer_parser = RequestParser()
top_answer_parser.add_argument(
    'top_answer', type=bool, required=True,
    help="Expected boolean value 'top_answer' is missing."
)


# events
on_answer_modified = event.signal('ANSWER_MODIFIED')
on_answer_get = event.signal('ANSWER_GET')
on_answer_list_get = event.signal('ANSWER_LIST_GET')
on_answer_create = event.signal('ANSWER_CREATE')
on_answer_delete = event.signal('ANSWER_DELETE')
on_answer_flag = event.signal('ANSWER_FLAG')
on_set_top_answer = event.signal('SET_TOP_ANSWER')
on_user_answer_get = event.signal('USER_ANSWER_GET')
on_answer_comparisons_get = event.signal('ANSWER_COMPARISONS_GET')

# messages
answer_deadline_message = 'Answer deadline has passed.'

# /
class AnswerRootAPI(Resource):
    @login_required
Example #11
0
    'displayname': 6,
    'group_name': 7
}

CAS_OR_SAML_IMPORT = {
    'username': 0,
    'student_number': 1,
    'firstname': 2,
    'lastname': 3,
    'email': 4,
    'displayname': 5,
    'group_name': 6
}

# events
on_classlist_get = event.signal('CLASSLIST_GET')
on_classlist_upload = event.signal('CLASSLIST_UPLOAD')
on_classlist_enrol = event.signal('CLASSLIST_ENROL')
on_classlist_unenrol = event.signal('CLASSLIST_UNENROL')
on_classlist_instructor = event.signal('CLASSLIST_INSTRUCTOR_GET')
on_classlist_student = event.signal('CLASSLIST_STUDENT_GET')
on_classlist_update_users_course_roles = event.signal('CLASSLIST_UPDATE_USERS_COURSE_ROLES')


def _parse_user_row(import_type, row):
    length = len(row)

    columns = COMPAIR_IMPORT
    if import_type == ThirdPartyType.cas.value or import_type == ThirdPartyType.saml.value:
        columns = CAS_OR_SAML_IMPORT
Example #12
0
from compair.models import UserCourse, User, Course, CourseRole, \
    ThirdPartyUser, ThirdPartyType
from .util import new_restful_api
from .file import allowed_file

course_group_api = Blueprint('course_group_api', __name__)
api = new_restful_api(course_group_api)

USER_IDENTIFIER = 0
GROUP_NAME = 1

import_parser = RequestParser()
import_parser.add_argument('userIdentifier', type=str, required=True)

# events
on_course_group_get = event.signal('COURSE_GROUP_GET')
on_course_group_import = event.signal('COURSE_GROUP_IMPORT')
on_course_group_members_get = event.signal('COURSE_GROUP_MEMBERS_GET')

def import_members(course, identifier, members):
    # initialize list of users and their statuses
    invalids = []  #invalid entry - eg. no group name
    user_infile = [] # for catching duplicate users
    count = 0 # keep track of active groups

    # require all rows to have two columns if there are a minimum of one entry
    if len(members) > 0 and len(members[0]) != 2:
        invalids.append({'member': {}, 'message': 'Only user identifier and group name fields should be in the file.'})
        return {'success': count, 'invalids': invalids}
    elif identifier not in [ThirdPartyType.cas.value, 'username', 'student_number']:
        invalids.append({'member': {}, 'message': 'A valid user identifier is not given.'})
Example #13
0
new_consumer_parser = reqparse.RequestParser()
new_consumer_parser.add_argument('oauth_consumer_key', type=str, required=True, nullable=False)
new_consumer_parser.add_argument('oauth_consumer_secret', type=str)
new_consumer_parser.add_argument('global_unique_identifier_param', type=non_blank_text)
new_consumer_parser.add_argument('student_number_param', type=non_blank_text)

existing_consumer_parser = new_consumer_parser.copy()
existing_consumer_parser.add_argument('id', type=str, required=True, nullable=False)
existing_consumer_parser.add_argument('active', type=bool, default=True)

consumer_list_parser = pagination_parser.copy()
consumer_list_parser.add_argument('orderBy', type=str, required=False, default=None)
consumer_list_parser.add_argument('reverse', type=bool, default=False)

# events
on_consumer_list_get = event.signal('LTI_CONSUMER_LIST_GET')
on_consumer_get = event.signal('LTI_CONSUMER_GET')
on_consumer_update = event.signal('LTI_CONSUMER_EDIT')
on_consumer_create = event.signal('LTI_CONSUMER_CREATE')

# /
class ConsumerAPI(Resource):
    @login_required
    def get(self):
        require(MANAGE, LTIConsumer,
            title="Consumers Unavailable",
            message="Sorry, your system role does not allow you to view LTI consumers.")

        params = consumer_list_parser.parse_args()

        query = LTIConsumer.query
Example #14
0
from flask_login import current_user
from sqlalchemy import and_, or_

from . import dataformat
from compair.authorization import require
from compair.core import db, event
from compair.models import UserCourse, User, Course, CourseRole
from .util import new_restful_api

user_list_parser = RequestParser()
user_list_parser.add_argument("ids", type=list, required=True, default=[], location="json")

course_group_user_api = Blueprint("course_group_user_api", __name__)
api = new_restful_api(course_group_user_api)

on_course_group_user_create = event.signal("COURSE_GROUP_USER_CREATE")
on_course_group_user_list_create = event.signal("COURSE_GROUP_USER_LIST_CREATE")
on_course_group_user_delete = event.signal("COURSE_GROUP_USER_DELETE")
on_course_group_user_list_delete = event.signal("COURSE_GROUP_USER_LIST_CREATE")

# /:user_uuid/groups/:group_name
class GroupUserIdAPI(Resource):
    @login_required
    def post(self, course_uuid, user_uuid, group_name):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

        user_course = UserCourse.query.filter(
            and_(
                UserCourse.course_id == course.id,
                UserCourse.user_id == user.id,
Example #15
0
                                        type=non_blank_text,
                                        required=False,
                                        default=None)
user_id_course_list_parser.add_argument('orderBy',
                                        required=False,
                                        default=None)
user_id_course_list_parser.add_argument('reverse', type=bool, default=False)

user_course_status_list_parser = RequestParser()
user_course_status_list_parser.add_argument('ids',
                                            required=True,
                                            nullable=False,
                                            default=None)

# events
on_user_modified = event.signal('USER_MODIFIED')
on_user_get = event.signal('USER_GET')
on_user_list_get = event.signal('USER_LIST_GET')
on_user_create = event.signal('USER_CREATE')
on_user_course_get = event.signal('USER_COURSE_GET')
on_user_course_status_get = event.signal('USER_COURSE_STATUS_GET')
on_teaching_course_get = event.signal('USER_TEACHING_COURSE_GET')
on_user_edit_button_get = event.signal('USER_EDIT_BUTTON_GET')
on_user_notifications_update = event.signal('USER_NOTIFICATIONS_UPDATE')
on_user_password_update = event.signal('USER_PASSWORD_UPDATE')
on_user_lti_users_get = event.signal('USER_LTI_USERS_GET')
on_user_lti_user_unlink = event.signal('USER_LTI_USER_UNLINK')
on_user_third_party_users_get = event.signal('USER_THIRD_PARTY_USERS_GET')
on_user_third_party_user_delete = event.signal('USER_THIRD_PARTY_USER_DELETE')

Example #16
0
new_assignment_parser.add_argument('number_of_comparisons', type=int, required=True)
new_assignment_parser.add_argument('enable_self_evaluation', type=int, default=None)
new_assignment_parser.add_argument('pairing_algorithm', type=str, default=None)
new_assignment_parser.add_argument('rank_display_limit', type=int, default=None)
new_assignment_parser.add_argument('educators_can_compare', type=bool, default=False)
# has to add location parameter, otherwise MultiDict will screw up the list
new_assignment_parser.add_argument('criteria', type=list, default=[], location='json')
new_assignment_parser.add_argument('answer_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('comparison_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('self_evaluation_grade_weight', type=int, default=1)

existing_assignment_parser = new_assignment_parser.copy()
existing_assignment_parser.add_argument('id', type=str, required=True, help="Assignment id is required.")

# events
on_assignment_modified = event.signal('ASSIGNMENT_MODIFIED')
on_assignment_get = event.signal('ASSIGNMENT_GET')
on_assignment_list_get = event.signal('ASSIGNMENT_LIST_GET')
on_assignment_create = event.signal('ASSIGNMENT_CREATE')
on_assignment_delete = event.signal('ASSIGNMENT_DELETE')
on_assignment_list_get_status = event.signal('ASSIGNMENT_LIST_GET_STATUS')
on_assignment_get_status = event.signal('ASSIGNMENT_GET_STATUS')

def check_valid_pairing_algorithm(pairing_algorithm):
    pairing_algorithms = [
        PairingAlgorithm.adaptive.value,
        PairingAlgorithm.random.value
    ]
    if pairing_algorithm not in pairing_algorithms:
        abort(400)
Example #17
0
from flask_login import current_user, login_required, login_user, logout_user
from flask_restful import marshal, Resource

from compair.core import db, event, abort, impersonation
from compair.authorization import get_logged_in_user_permissions
from compair.models import User, LTIUser, LTIResourceLink, LTIUserResourceLink, UserCourse, LTIContext, \
    ThirdPartyUser, ThirdPartyType
from compair.cas import get_cas_login_url, validate_cas_ticket, get_cas_logout_url
from compair.saml import get_saml_login_url, get_saml_auth_response, get_saml_logout_url, _get_auth
from . import dataformat
from .util import new_restful_api

login_api = Blueprint("login_api", __name__, url_prefix='/api')

# events
on_login_with_method = event.signal('USER_LOGGED_IN_WITH_METHOD')
on_logout = event.signal('USER_LOGGED_OUT')


@login_api.route('/login', methods=['POST'])
def login():
    if not current_app.config.get('APP_LOGIN_ENABLED'):
        abort(
            403,
            title="Log In Failed",
            message=
            "Please try an alternate way of logging in. The ComPAIR login has been disabled by your system administrator."
        )

    # expecting login params to be in json format
    param = request.json
Example #18
0
# First declare a Flask Blueprint for this module
# Then pack the blueprint into a Flask-Restful API
comparison_api = Blueprint('comparison_api', __name__)
api = new_restful_api(comparison_api)

update_comparison_parser = RequestParser()
update_comparison_parser.add_argument('comparison_criteria',
                                      type=list,
                                      required=True,
                                      nullable=False,
                                      location='json')
update_comparison_parser.add_argument('draft', type=bool, default=False)

# events
on_comparison_get = event.signal('COMPARISON_GET')
on_comparison_create = event.signal('COMPARISON_CREATE')
on_comparison_update = event.signal('COMPARISON_UPDATE')


# /
class CompareRootAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        """
        Get (or create if needed) a comparison set for assignment.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(
            READ,
Example #19
0
new_answer_comment_parser.add_argument('attempt_ended', default=None)

existing_answer_comment_parser = new_answer_comment_parser.copy()
existing_answer_comment_parser.add_argument('id', required=True, help="Comment id is required.")

answer_comment_list_parser = pagination_parser.copy()
answer_comment_list_parser.add_argument('self_evaluation', required=False, default='true')
answer_comment_list_parser.add_argument('evaluation', required=False, default='true')
answer_comment_list_parser.add_argument('draft', required=False, default='false')
answer_comment_list_parser.add_argument('ids', required=False, default=None)
answer_comment_list_parser.add_argument('answer_ids', required=False, default=None)
answer_comment_list_parser.add_argument('assignment_id', required=False, default=None)
answer_comment_list_parser.add_argument('user_ids', required=False, default=None)

# events
on_answer_comment_modified = event.signal('ANSWER_COMMENT_MODIFIED')
on_answer_comment_get = event.signal('ANSWER_COMMENT_GET')
on_answer_comment_list_get = event.signal('ANSWER_COMMENT_LIST_GET')
on_answer_comment_create = event.signal('ANSWER_COMMENT_CREATE')
on_answer_comment_delete = event.signal('ANSWER_COMMENT_DELETE')

class AnswerCommentListAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid, **kwargs):
        """
        :query string ids: a comma separated comment uuids to query
        :query string answer_ids: a comma separated answer uuids for answer filter
        :query string assignment_id: filter the answer comments with a assignment uuid
        :query string user_ids: a comma separated user uuids that own the comments
        :query string self_evaluation: indicate whether the result should include self-evaluation comments or self-evaluation only.
                Possible values: true, false or only. Default true.
Example #20
0
from compair.core import db, event, abort
from compair.models import UserCourse, User, Course, CourseRole, \
    ThirdPartyUser, ThirdPartyType, Group, Answer
from .util import new_restful_api, get_model_changes

group_api = Blueprint('group_api', __name__)
api = new_restful_api(group_api)

new_group_parser = RequestParser()
new_group_parser.add_argument('name', required=True, nullable=False, help="Group name is required.")

existing_group_parser = new_group_parser.copy()
existing_group_parser.add_argument('id', required=True, nullable=False, help="Group id is required.")

# events
on_group_create = event.signal('GROUP_CREATE')
on_group_edit = event.signal('GROUP_EDIT')
on_group_get = event.signal('GROUP_GET')
on_group_delete = event.signal('GROUP_DELETE')

# /
class GroupRootAPI(Resource):
    @login_required
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        new_group = Group(course_id=course.id)

        require(CREATE, new_group,
            title="Group Not Saved",
            message="Sorry, your role in this course does not allow you to save groups.")
Example #21
0
answer_comment_list_parser.add_argument('draft',
                                        required=False,
                                        default='false')
answer_comment_list_parser.add_argument('ids', required=False, default=None)
answer_comment_list_parser.add_argument('answer_ids',
                                        required=False,
                                        default=None)
answer_comment_list_parser.add_argument('assignment_id',
                                        required=False,
                                        default=None)
answer_comment_list_parser.add_argument('user_ids',
                                        required=False,
                                        default=None)

# events
on_answer_comment_modified = event.signal('ANSWER_COMMENT_MODIFIED')
on_answer_comment_get = event.signal('ANSWER_COMMENT_GET')
on_answer_comment_list_get = event.signal('ANSWER_COMMENT_LIST_GET')
on_answer_comment_create = event.signal('ANSWER_COMMENT_CREATE')
on_answer_comment_delete = event.signal('ANSWER_COMMENT_DELETE')


class AnswerCommentListAPI(Resource):
    @login_required
    def get(self, **kwargs):
        """
        **Example request**:

        .. sourcecode:: http

            GET /api/answer/123/comments HTTP/1.1
Example #22
0
import mimetypes
import os
from functools import wraps

from flask import redirect, render_template, jsonify
from flask_login import login_required, current_user, current_app
from flask import make_response
from flask import send_file, url_for, redirect, request
from flask_restful.reqparse import RequestParser

from bouncer.constants import READ
from compair.authorization import require
from compair.kaltura import KalturaAPI
from compair.models import File
from compair.core import event
on_get_file = event.signal('GET_FILE')

attachment_download_parser = RequestParser()
attachment_download_parser.add_argument('name', default=None)

def register_api_blueprints(app):
    # Initialize rest of the api modules
    from .course import course_api
    app.register_blueprint(
        course_api,
        url_prefix='/api/courses')

    from .classlist import classlist_api
    app.register_blueprint(
        classlist_api,
        url_prefix='/api/courses/<course_uuid>/users')
Example #23
0
user_answer_list_parser = RequestParser()
user_answer_list_parser.add_argument('draft',
                                     type=bool,
                                     required=False,
                                     default=False)

top_answer_parser = RequestParser()
top_answer_parser.add_argument(
    'top_answer',
    type=bool,
    required=True,
    nullable=False,
    help="Expected boolean value 'top_answer' is missing.")

# events
on_answer_modified = event.signal('ANSWER_MODIFIED')
on_answer_get = event.signal('ANSWER_GET')
on_answer_list_get = event.signal('ANSWER_LIST_GET')
on_answer_create = event.signal('ANSWER_CREATE')
on_answer_delete = event.signal('ANSWER_DELETE')
on_set_top_answer = event.signal('SET_TOP_ANSWER')
on_user_answer_get = event.signal('USER_ANSWER_GET')

from compair.api.file import on_attach_file, on_detach_file


# /
class AnswerRootAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        """
Example #24
0
from copy import copy
from flask import Blueprint, current_app
from flask_login import current_user, login_required
from flask_restful import Resource, marshal

from compair.models import User
from compair.core import abort, impersonation, event
from .util import new_restful_api
from . import dataformat

impersonation_api = Blueprint('impersonation_api', __name__)
IMPERSONATION_API_BASE_URL = '/api/impersonate'

# events
on_impersonation_started = event.signal('IMPERSONATION_STARTED')
on_impersonation_stopped = event.signal('IMPERSONATION_STOPPED')


class ImpersonationAPI(Resource):
    @login_required
    def post(self, user_uuid):
        if not current_app.config.get('IMPERSONATION_ENABLED', False):
            abort(404,
                  title="Impersonation not supported",
                  message="Impersonation function not enabled")

        original_user = User.query.get(current_user.id)
        impersonate_as_uuid = user_uuid
        impersonate_as_user = User.get_by_uuid_or_404(impersonate_as_uuid)
        if not impersonation.can_impersonate(impersonate_as_user.get_id()):
            abort(400,
Example #25
0
from compair.core import db, event
from compair.authorization import require, allow
from compair.models import Assignment, Course, AssignmentComment
from .util import new_restful_api, get_model_changes, pagination_parser

assignment_comment_api = Blueprint('assignment_comment_api', __name__)
api = new_restful_api(assignment_comment_api)

new_assignment_comment_parser = RequestParser()
new_assignment_comment_parser.add_argument('content', type=str, required=True)

existing_assignment_comment_parser = new_assignment_comment_parser.copy()
existing_assignment_comment_parser.add_argument('id', type=str, required=True, help="Comment id is required.")

# events
on_assignment_comment_modified = event.signal('ASSIGNMENT_COMMENT_MODIFIED')
on_assignment_comment_get = event.signal('ASSIGNMENT_COMMENT_GET')
on_assignment_comment_list_get = event.signal('ASSIGNMENT_COMMENT_LIST_GET')
on_assignment_comment_create = event.signal('ASSIGNMENT_COMMENT_CREATE')
on_assignment_comment_delete = event.signal('ASSIGNMENT_COMMENT_DELETE')


# /
class AssignmentCommentRootAPI(Resource):
    # TODO pagination
    @login_required
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, assignment)
        restrict_user = not allow(MANAGE, assignment)
Example #26
0
from sqlalchemy import func, and_, or_
from sqlalchemy.orm import undefer, joinedload

from . import dataformat
from compair.authorization import require
from compair.models import Course, Assignment, CourseRole, User, UserCourse, Comparison, \
    AnswerComment, Answer, File, AnswerScore, AnswerCommentType, PairingAlgorithm, \
    AssignmentGrade, Group
from .util import new_restful_api
from compair.core import event, abort

gradebook_api = Blueprint('gradebook_api', __name__)
api = new_restful_api(gradebook_api)

# events
on_gradebook_get = event.signal('GRADEBOOK_GET')


# /
class GradebookAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(
            assignment_uuid, joinedloads=['assignment_criteria'])
        require(
            MANAGE,
            assignment,
            title="Participation Results Unavailable",
            message=
            "Sorry, your role in this course does not allow you to view student participation for this assignment."
Example #27
0
from flask_login import login_required, current_user
from flask_restful import Resource, marshal

from compair.core import allowed_file, random_generator
from compair.authorization import allow, require
from . import dataformat
from compair.core import db, event, abort
from compair.models import File, Assignment, KalturaMedia
from compair.kaltura import KalturaAPI
from .util import new_restful_api

file_api = Blueprint('file_api', __name__)
api = new_restful_api(file_api)

# events
on_save_file = event.signal('FILE_CREATE')
on_get_kaltura_token = event.signal('FILE_GET_KALTURA_TOKEN')
on_save_kaltura_file = event.signal('FILE_CREATE_KALTURA_FILE')

on_attach_file = event.signal('FILE_ATTACH')
on_detach_file = event.signal('FILE_DETACH')

# /
class FileAPI(Resource):
    @login_required
    def post(self):
        uploaded_file = request.files.get('file')

        if not uploaded_file:
            abort(400, title="File Not Uploaded", message="Sorry, no file was found to upload. Please try uploading again.")
        elif not allowed_file(uploaded_file.filename, current_app.config['ATTACHMENT_ALLOWED_EXTENSIONS']):
Example #28
0
    MembershipNoValidContextsException,
    MembershipNoResultsException,
    MembershipInvalidRequestException,
)
from .util import new_restful_api, get_model_changes, pagination_parser
from compair.tasks.lti_membership import update_lti_course_membership

from compair.api.classlist import display_name_generator
from lti.contrib.flask import FlaskToolProvider
from oauthlib.oauth1 import RequestValidator

lti_api = Blueprint("lti_api", __name__)
api = new_restful_api(lti_api)

# events
on_lti_course_link = event.signal("LTI_CONTEXT_COURSE_LINKED")
on_lti_course_membership_update = event.signal("LTI_CONTEXT_COURSE_MEMBERSHIP_UPDATE")
on_lti_course_membership_status_get = event.signal("LTI_CONTEXT_COURSE_MEMBERSHIP_STATUS_GET")

# /auth
class LTIAuthAPI(Resource):
    def post(self):
        """
        Kickstarts the LTI integration flow.
        """
        if not current_app.config.get("LTI_LOGIN_ENABLED"):
            return abort(403)

        tool_provider = FlaskToolProvider.from_flask_request(request=request)
        validator = ComPAIRRequestValidator()
        ok = tool_provider.is_valid_request(validator)
Example #29
0
    Assignment, UserCourse, CourseRole, AssignmentCriterion
from .util import new_restful_api

from compair.algorithms import InsufficientObjectsForPairException, \
    UserComparedAllObjectsException, UnknownPairGeneratorException

# First declare a Flask Blueprint for this module
# Then pack the blueprint into a Flask-Restful API
comparison_api = Blueprint('comparison_api', __name__)
api = new_restful_api(comparison_api)

update_comparison_parser = RequestParser()
update_comparison_parser.add_argument('comparisons', type=list, required=True, location='json')

# events
on_comparison_get = event.signal('COMPARISON_GET')
on_comparison_create = event.signal('COMPARISON_CREATE')
on_comparison_update = event.signal('COMPARISON_UPDATE')

#messages
comparison_deadline_message = 'Assignment comparison deadline has passed.'
educators_can_not_compare_message = 'Only students can compare answers in this assignment.'

# /
class CompareRootAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        """
        Get (or create if needed) a comparison set for assignment.
        """
        course = Course.get_active_by_uuid_or_404(course_uuid)
Example #30
0
user_course_list_parser.add_argument('search', required=False, default=None)
user_course_list_parser.add_argument('includeSandbox', type=string_to_bool, required=False, default=None)
user_course_list_parser.add_argument('period', type=non_blank_text, required=False, default=None)

user_id_course_list_parser = pagination_parser.copy()
user_id_course_list_parser.add_argument('search', required=False, default=None)
user_id_course_list_parser.add_argument('includeSandbox', type=string_to_bool, required=False, default=None)
user_id_course_list_parser.add_argument('period', type=non_blank_text, required=False, default=None)
user_id_course_list_parser.add_argument('orderBy', required=False, default=None)
user_id_course_list_parser.add_argument('reverse', type=bool, default=False)

user_course_status_list_parser = RequestParser()
user_course_status_list_parser.add_argument('ids', required=True, nullable=False, default=None)

# events
on_user_modified = event.signal('USER_MODIFIED')
on_user_get = event.signal('USER_GET')
on_user_list_get = event.signal('USER_LIST_GET')
on_user_create = event.signal('USER_CREATE')
on_user_course_get = event.signal('USER_COURSE_GET')
on_user_course_status_get = event.signal('USER_COURSE_STATUS_GET')
on_teaching_course_get = event.signal('USER_TEACHING_COURSE_GET')
on_user_edit_button_get = event.signal('USER_EDIT_BUTTON_GET')
on_user_notifications_update = event.signal('USER_NOTIFICATIONS_UPDATE')
on_user_password_update = event.signal('USER_PASSWORD_UPDATE')
on_user_lti_users_get = event.signal('USER_LTI_USERS_GET')
on_user_lti_user_unlink = event.signal('USER_LTI_USER_UNLINK')
on_user_third_party_users_get = event.signal('USER_THIRD_PARTY_USERS_GET')
on_user_third_party_user_delete = event.signal('USER_THIRD_PARTY_USER_DELETE')

def check_valid_system_role(system_role):
Example #31
0
new_consumer_parser.add_argument('oauth_consumer_key', type=str, required=True, nullable=False)
new_consumer_parser.add_argument('oauth_consumer_secret', type=str)
new_consumer_parser.add_argument('global_unique_identifier_param', type=non_blank_text)
new_consumer_parser.add_argument('student_number_param', type=non_blank_text)
new_consumer_parser.add_argument('custom_param_regex_sanitizer', type=non_blank_text)

existing_consumer_parser = new_consumer_parser.copy()
existing_consumer_parser.add_argument('id', type=str, required=True, nullable=False)
existing_consumer_parser.add_argument('active', type=bool, default=True)

consumer_list_parser = pagination_parser.copy()
consumer_list_parser.add_argument('orderBy', type=str, required=False, default=None)
consumer_list_parser.add_argument('reverse', type=bool, default=False)

# events
on_consumer_list_get = event.signal('LTI_CONSUMER_LIST_GET')
on_consumer_get = event.signal('LTI_CONSUMER_GET')
on_consumer_update = event.signal('LTI_CONSUMER_EDIT')
on_consumer_create = event.signal('LTI_CONSUMER_CREATE')

# /
class ConsumerAPI(Resource):
    @login_required
    def get(self):
        require(MANAGE, LTIConsumer,
            title="Consumers Unavailable",
            message="Sorry, your system role does not allow you to view LTI consumers.")

        params = consumer_list_parser.parse_args()

        query = LTIConsumer.query
Example #32
0
    LTIResourceLink, LTIUser, LTIUserResourceLink, LTINonce
from compair.models.lti_models import MembershipNoValidContextsException, \
    MembershipNoResultsException, MembershipInvalidRequestException
from .util import new_restful_api, get_model_changes, pagination_parser
from compair.tasks import update_lti_course_membership

lti_course_api = Blueprint('lti_course_api', __name__)
api = new_restful_api(lti_course_api)

context_list_parser = pagination_parser.copy()
context_list_parser.add_argument('orderBy', type=str, required=False, default=None)
context_list_parser.add_argument('reverse', type=bool, default=False)
context_list_parser.add_argument('search', required=False, default=None)

# events
on_lti_course_links_get = event.signal('LTI_CONTEXT_COURSE_LINKS')
on_lti_course_link_create = event.signal('LTI_CONTEXT_COURSE_LINKED')
on_lti_course_unlink = event.signal('LTI_CONTEXT_COURSE_UNLINKED')
on_lti_course_membership_update = event.signal('LTI_CONTEXT_COURSE_MEMBERSHIP_UPDATE')
on_lti_course_membership_status_get = event.signal('LTI_CONTEXT_COURSE_MEMBERSHIP_STATUS_GET')

# /context
class LTICourseLinksRootAPI(Resource):
    @login_required
    def get(self):
        require(READ, LTIContext,
            title="Course Links Unavailable",
            message="Sorry, your system role does not allow you to view LTI course links.")

        params = context_list_parser.parse_args()
Example #33
0
from flask import Blueprint, request, current_app
from bouncer.constants import READ, EDIT, CREATE, DELETE, MANAGE
from flask_login import login_required, current_user
from flask_restful import Resource, marshal

from compair.authorization import allow, require
from . import dataformat
from compair.core import db, event
from compair.models import File, Assignment, Answer
from .util import new_restful_api

file_api = Blueprint('file_api', __name__)
api = new_restful_api(file_api)

# events
on_save_file = event.signal('FILE_CREATE')
on_file_get = event.signal('FILE_GET')
on_file_delete = event.signal('FILE_DELETE')


def allowed_file(filename, allowed):
    return '.' in filename and \
        filename.rsplit('.', 1)[1] in allowed


def random_generator(size=8, chars=string.ascii_uppercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))


# /
class FileAPI(Resource):
Example #34
0
criterion_api = Blueprint('criterion_api', __name__)
api = new_restful_api(criterion_api)

new_criterion_parser = reqparse.RequestParser()
new_criterion_parser.add_argument('name', type=str, required=True)
new_criterion_parser.add_argument('description', type=str)
new_criterion_parser.add_argument('default', type=bool, default=True)

existing_criterion_parser = reqparse.RequestParser()
existing_criterion_parser.add_argument('id', type=str, required=True)
existing_criterion_parser.add_argument('name', type=str, required=True)
existing_criterion_parser.add_argument('description', type=str)
existing_criterion_parser.add_argument('default', type=bool, default=True)

# events
on_criterion_list_get = event.signal('CRITERION_LIST_GET')
on_criterion_get = event.signal('CRITERION_GET')
on_criterion_update = event.signal('CRITERION_EDIT')
on_criterion_create = event.signal('CRITERION_CREATE')

# /criteria - public + authored/default
# default = want criterion available to all of the author's assignments
class CriteriaAPI(Resource):
    @login_required
    def get(self):
        criteria = Criterion.query \
            .filter(or_(
                and_(
                    Criterion.user_id == current_user.id,
                    Criterion.default == True
                ),
Example #35
0
criterion_api = Blueprint('criterion_api', __name__)
api = new_restful_api(criterion_api)

new_criterion_parser = reqparse.RequestParser()
new_criterion_parser.add_argument('name', required=True, nullable=False)
new_criterion_parser.add_argument('description')
new_criterion_parser.add_argument('default', type=bool, default=True)

existing_criterion_parser = reqparse.RequestParser()
existing_criterion_parser.add_argument('id', required=True, nullable=False)
existing_criterion_parser.add_argument('name', required=True, nullable=False)
existing_criterion_parser.add_argument('description')
existing_criterion_parser.add_argument('default', type=bool, default=True)

# events
on_criterion_list_get = event.signal('CRITERION_LIST_GET')
on_criterion_get = event.signal('CRITERION_GET')
on_criterion_update = event.signal('CRITERION_EDIT')
on_criterion_create = event.signal('CRITERION_CREATE')


# /criteria - public + authored/default
# default = want criterion available to all of the author's assignments
class CriteriaAPI(Resource):
    @login_required
    def get(self):
        criteria = Criterion.query \
            .filter(or_(
                and_(
                    Criterion.user_id == current_user.id,
                    Criterion.default == True
Example #36
0
from compair.core import event
from compair.models import CourseRole, Assignment, UserCourse, Course, Answer, \
    AnswerComment, AssignmentCriterion, Comparison, AnswerCommentType
from .util import new_restful_api

report_api = Blueprint('report_api', __name__)
api = new_restful_api(report_api)

report_parser = reqparse.RequestParser()
report_parser.add_argument('group_name', type=str)
# may change 'type' to int
report_parser.add_argument('type', type=str, required=True)
report_parser.add_argument('assignment', type=str)

# events
on_export_report = event.signal('EXPORT_REPORT')
# should we have a different event for each type of report?


def name_generator(course, report_name, group_name, file_type="csv"):
    date = time.strftime("%Y-%m-%d--%H-%M-%S")
    group_name_output = ""
    if group_name:
        group_name_output = group_name + '-'
    return course.name + "-" + group_name_output + report_name + "--" + date + "." + file_type


class ReportRootAPI(Resource):
    @login_required
    def post(self, course_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
Example #37
0
new_assignment_parser.add_argument('educators_can_compare', type=bool, default=False)
# has to add location parameter, otherwise MultiDict will screw up the list
new_assignment_parser.add_argument('criteria', type=list, default=[], location='json')
new_assignment_parser.add_argument('answer_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('comparison_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('self_evaluation_grade_weight', type=int, default=1)

existing_assignment_parser = new_assignment_parser.copy()
existing_assignment_parser.add_argument('id', required=True, nullable=False, help="Assignment id is required.")

assignment_users_comparison_list_parser = pagination_parser.copy()
assignment_users_comparison_list_parser.add_argument('group', required=False, default=None)
assignment_users_comparison_list_parser.add_argument('author', required=False, default=None)

# events
on_assignment_modified = event.signal('ASSIGNMENT_MODIFIED')
on_assignment_get = event.signal('ASSIGNMENT_GET')
on_assignment_list_get = event.signal('ASSIGNMENT_LIST_GET')
on_assignment_create = event.signal('ASSIGNMENT_CREATE')
on_assignment_delete = event.signal('ASSIGNMENT_DELETE')
on_assignment_list_get_status = event.signal('ASSIGNMENT_LIST_GET_STATUS')
on_assignment_get_status = event.signal('ASSIGNMENT_GET_STATUS')
on_assignment_user_comparisons_get = event.signal('ASSIGNMENT_USER_COMPARISONS_GET')
on_assignment_users_comparisons_get = event.signal('ASSIGNMENT_USERS_COMPARISONS_GET')

from compair.api.file import on_attach_file, on_detach_file

def check_valid_pairing_algorithm(pairing_algorithm):
    pairing_algorithms = [
        PairingAlgorithm.adaptive.value,
        PairingAlgorithm.random.value,
Example #38
0
from compair.authorization import require
from compair.core import db, event, abort
from compair.models import UserCourse, User, Course, CourseRole
from .util import new_restful_api

user_list_parser = RequestParser()
user_list_parser.add_argument('ids',
                              type=list,
                              required=True,
                              default=[],
                              location='json')

course_group_user_api = Blueprint('course_group_user_api', __name__)
api = new_restful_api(course_group_user_api)

on_course_group_user_create = event.signal('COURSE_GROUP_USER_CREATE')
on_course_group_user_list_create = event.signal(
    'COURSE_GROUP_USER_LIST_CREATE')
on_course_group_user_delete = event.signal('COURSE_GROUP_USER_DELETE')
on_course_group_user_list_delete = event.signal(
    'COURSE_GROUP_USER_LIST_CREATE')


# /:user_uuid/groups/:group_name
class GroupUserIdAPI(Resource):
    @login_required
    def post(self, course_uuid, user_uuid, group_name):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        user = User.get_by_uuid_or_404(user_uuid)

        user_course = UserCourse.query \
Example #39
0
from bouncer.constants import READ, EDIT, CREATE, DELETE, MANAGE
from flask_login import login_required, current_user
from flask_restful import Resource, marshal, reqparse, marshal_with
from sqlalchemy import or_, and_

from . import dataformat
from compair.core import event, db
from compair.authorization import require, allow
from compair.models import Course, Criterion, Assignment, AssignmentCriterion, Comparison
from .util import new_restful_api

assignment_criterion_api = Blueprint('assignment_criterion_api', __name__)
api = new_restful_api(assignment_criterion_api)

# events
on_assignment_criterion_get = event.signal('ASSIGNMENT_CRITERION_GET')

# /
class AssignmentCriterionRootAPI(Resource):
    @login_required
    def get(self, course_uuid, assignment_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        assignment = Assignment.get_active_by_uuid_or_404(assignment_uuid)
        require(READ, course)

        criteria = Criterion.query \
            .join(AssignmentCriterion) \
            .filter(and_(
                AssignmentCriterion.assignment_id == assignment.id,
                AssignmentCriterion.active == True,
                Criterion.active == True)
Example #40
0
from compair.core import db, event, abort
from .util import new_restful_api, get_model_changes, pagination_parser
from compair.models import User, SystemRole, UserCourse, CourseRole
from compair.api.login import authenticate

from .classlist import display_name_generator
from .file import random_generator

demo_api = Blueprint('demo_api', __name__)
api = new_restful_api(demo_api)

new_user_demo_parser = RequestParser()
new_user_demo_parser.add_argument('system_role', type=str, required=True)

# events
on_user_demo_create = event.signal('USER_DEMO_CREATE')


def check_valid_system_role(system_role):
    system_roles = [
        SystemRole.sys_admin.value, SystemRole.instructor.value,
        SystemRole.student.value
    ]
    if system_role not in system_roles:
        abort(
            400,
            title="Demo Account Not Saved",
            message=
            "Please try again with a system role from the list of roles provided."
        )
Example #41
0
from compair.models import UserCourse, User, Course, CourseRole, \
    Group
from .util import new_restful_api, get_model_changes

user_list_parser = RequestParser()
user_list_parser.add_argument('ids',
                              type=list,
                              required=True,
                              nullable=False,
                              default=[],
                              location='json')

group_user_api = Blueprint('group_user_api', __name__)
api = new_restful_api(group_user_api)

on_group_user_list_get = event.signal('GROUP_USERS_GET')
on_group_user_get = event.signal('GROUP_USER_GET')
on_group_user_create = event.signal('GROUP_USER_CREATE')
on_group_user_list_create = event.signal('GROUP_USER_LIST_CREATE')
on_group_user_delete = event.signal('GROUP_USER_DELETE')
on_group_user_list_delete = event.signal('GROUP_USER_LIST_CREATE')


# /<group_uuid>/users/:user_uuid
class GroupUserIdAPI(Resource):
    @login_required
    def post(self, course_uuid, group_uuid, user_uuid):
        course = Course.get_active_by_uuid_or_404(course_uuid)
        group = Group.get_active_by_uuid_or_404(group_uuid)
        user = User.get_by_uuid_or_404(user_uuid)
Example #42
0
from compair.models import User, CourseRole, Assignment, UserCourse, Course, Answer, \
    AnswerComment, AssignmentCriterion, Comparison, AnswerCommentType, Group, File, KalturaMedia
from compair.kaltura import KalturaAPI
from .util import new_restful_api

report_api = Blueprint('report_api', __name__)
api = new_restful_api(report_api)

report_parser = reqparse.RequestParser()
report_parser.add_argument('group_id')
# may change 'type' to int
report_parser.add_argument('type', required=True, nullable=False)
report_parser.add_argument('assignment')

# events
on_export_report = event.signal('EXPORT_REPORT')
# should we have a different event for each type of report?


def name_generator(course, report_name, group, file_type="csv"):
    date = time.strftime("%Y-%m-%d--%H-%M-%S")
    group_name_output = ""
    if group:
        group_name_output = group.name + '-'
    # from https://gist.github.com/seanh/93666
    # return a file system safe filename
    valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
    filename = course.name + "-" + group_name_output + report_name + "--" + date + "." + file_type
    return ''.join(char for char in filename if char in valid_chars)

Example #43
0
new_assignment_parser.add_argument('educators_can_compare', type=bool, default=False)
# has to add location parameter, otherwise MultiDict will screw up the list
new_assignment_parser.add_argument('criteria', type=list, default=[], location='json')
new_assignment_parser.add_argument('answer_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('comparison_grade_weight', type=int, default=1)
new_assignment_parser.add_argument('self_evaluation_grade_weight', type=int, default=1)

existing_assignment_parser = new_assignment_parser.copy()
existing_assignment_parser.add_argument('id', required=True, help="Assignment id is required.")

assignment_users_comparison_list_parser = pagination_parser.copy()
assignment_users_comparison_list_parser.add_argument('group', required=False, default=None)
assignment_users_comparison_list_parser.add_argument('author', required=False, default=None)

# events
on_assignment_modified = event.signal('ASSIGNMENT_MODIFIED')
on_assignment_get = event.signal('ASSIGNMENT_GET')
on_assignment_list_get = event.signal('ASSIGNMENT_LIST_GET')
on_assignment_create = event.signal('ASSIGNMENT_CREATE')
on_assignment_delete = event.signal('ASSIGNMENT_DELETE')
on_assignment_list_get_status = event.signal('ASSIGNMENT_LIST_GET_STATUS')
on_assignment_get_status = event.signal('ASSIGNMENT_GET_STATUS')
on_assignment_user_comparisons_get = event.signal('ASSIGNMENT_USER_COMPARISONS_GET')
on_assignment_users_comparisons_get = event.signal('ASSIGNMENT_USERS_COMPARISONS_GET')

def check_valid_pairing_algorithm(pairing_algorithm):
    pairing_algorithms = [
        PairingAlgorithm.adaptive.value,
        PairingAlgorithm.random.value
    ]
    if pairing_algorithm not in pairing_algorithms:
Example #44
0
import os

from flask import Blueprint, jsonify, request, session as sess, current_app, url_for, redirect, Flask, render_template
from flask_login import current_user, login_required, login_user, logout_user
from compair import cas
from compair.core import db, event
from compair.authorization import get_logged_in_user_permissions
from compair.models import User, LTIUser, LTIResourceLink, LTIUserResourceLink, UserCourse, LTIContext, \
    ThirdPartyUser, ThirdPartyType

login_api = Blueprint("login_api", __name__, url_prefix='/api')

# events
on_login_with_method = event.signal('USER_LOGGED_IN_WITH_METHOD')
on_logout = event.signal('USER_LOGGED_OUT')

@login_api.route('/login', methods=['POST'])
def login():
    if not current_app.config.get('APP_LOGIN_ENABLED'):
        return "", 403

    # expecting login params to be in json format
    param = request.json
    if param is None:
        return jsonify({"error": 'Invalid login data format. Expecting json.'}), 400

    username = param['username']
    password = param['password']
    # grab the user from the username
    user = User.query.filter_by(username=username).first()
    if not user:
Example #45
0
existing_course_parser = new_course_parser.copy()
existing_course_parser.add_argument('id', type=str, required=True, help='Course id is required.')


duplicate_course_parser = reqparse.RequestParser()
duplicate_course_parser.add_argument('name', type=str, required=True, help='Course name is required.')
duplicate_course_parser.add_argument('year', type=int, required=True, help='Course year is required.')
duplicate_course_parser.add_argument('term', type=str, required=True, help='Course term/semester is required.')
duplicate_course_parser.add_argument('start_date', type=str, default=None)
duplicate_course_parser.add_argument('end_date', type=str, default=None)
# has to add location parameter, otherwise MultiDict will screw up the list
duplicate_course_parser.add_argument('assignments', type=list, default=[], location='json') #only ids and dates

# events
on_course_modified = event.signal('COURSE_MODIFIED')
on_course_get = event.signal('COURSE_GET')
on_course_delete = event.signal('COURSE_DELETE')
on_course_list_get = event.signal('COURSE_LIST_GET')
on_course_create = event.signal('COURSE_CREATE')
on_course_duplicate = event.signal('COURSE_DUPLICATE')


class CourseListAPI(Resource):
    @login_required
    def post(self):
        """
        Create new course
        """
        require(CREATE, Course)
        params = new_course_parser.parse_args()
Example #46
0
from flask_login import login_required, current_user
from flask_restful import Resource, marshal

from compair.core import allowed_file, random_generator
from compair.authorization import allow, require
from . import dataformat
from compair.core import db, event, abort
from compair.models import File, Assignment, KalturaMedia
from compair.kaltura import KalturaAPI
from .util import new_restful_api

file_api = Blueprint('file_api', __name__)
api = new_restful_api(file_api)

# events
on_save_file = event.signal('FILE_CREATE')
on_get_kaltura_token = event.signal('FILE_GET_KALTURA_TOKEN')
on_save_kaltura_file = event.signal('FILE_CREATE_KALTURA_FILE')


# /
class FileAPI(Resource):
    @login_required
    def post(self):
        uploaded_file = request.files.get('file')

        if not uploaded_file:
            abort(
                400,
                title="File Not Uploaded",
                message=
Example #47
0
from sqlalchemy import exc, asc, or_, and_, func

from . import dataformat
from compair.core import db, event, abort, random_generator, display_name_generator
from .util import new_restful_api, get_model_changes, pagination_parser
from compair.models import User, SystemRole, UserCourse, CourseRole
from compair.api.login import authenticate

demo_api = Blueprint('demo_api', __name__)
api = new_restful_api(demo_api)

new_user_demo_parser = RequestParser()
new_user_demo_parser.add_argument('system_role', type=str, required=True, nullable=False)

# events
on_user_demo_create = event.signal('USER_DEMO_CREATE')

def check_valid_system_role(system_role):
    system_roles = [
        SystemRole.sys_admin.value,
        SystemRole.instructor.value,
        SystemRole.student.value
    ]
    if system_role not in system_roles:
        abort(400, title="Demo Account Not Saved", message="Please try again with a system role from the list of roles provided.")

# /
class DemoListAPI(Resource):
    def post(self):
        if not current_app.config.get('DEMO_INSTALLATION', False):
            abort(404, title="Demo Accounts Unavailable", message="Sorry, the system settings do now allow the use of demo accounts.")
Example #48
0
update_password_parser = RequestParser()
update_password_parser.add_argument('oldpassword', type=str, required=False)
update_password_parser.add_argument('newpassword', type=str, required=True)

user_list_parser = pagination_parser.copy()
user_list_parser.add_argument('search', type=str, required=False, default=None)
user_list_parser.add_argument('ids', type=str, required=False, default=None)

user_course_list_parser = pagination_parser.copy()
user_course_list_parser.add_argument('search', type=str, required=False, default=None)

user_course_status_list_parser = RequestParser()
user_course_status_list_parser.add_argument('ids', type=str, required=True, default=None)

# events
on_user_modified = event.signal('USER_MODIFIED')
on_user_get = event.signal('USER_GET')
on_user_list_get = event.signal('USER_LIST_GET')
on_user_create = event.signal('USER_CREATE')
on_user_course_get = event.signal('USER_COURSE_GET')
on_user_course_status_get = event.signal('USER_COURSE_STATUS_GET')
on_teaching_course_get = event.signal('USER_TEACHING_COURSE_GET')
on_user_edit_button_get = event.signal('USER_EDIT_BUTTON_GET')
on_user_password_update = event.signal('USER_PASSWORD_UPDATE')

def check_valid_system_role(system_role):
    system_roles = [
        SystemRole.sys_admin.value,
        SystemRole.instructor.value,
        SystemRole.student.value
    ]