Example #1
0
def feedback(request):
    """
    Send feedback to the authors of the system.

    GET parameters:
        html
            turn on the HTML version of the API

    POST parameters (JSON):
        text:
            the main feedback content
        email (optional):
            user's e-mail
        username (optional):
            user's name
    """
    if request.method == 'GET':
        return render(request, 'feedback_feedback.html', {}, help_text=feedback.__doc__)
    if request.method == 'POST':
        feedback_data = json_body(request.body.decode("utf-8"))
        feedback_data['user_agent'] = Session.objects.get_current_session().http_user_agent.content
        if not feedback_data.get('username'):
            feedback_data['username'] = request.user.username
        if not feedback_data.get('email'):
            feedback_data['email'] = request.user.email
        comment = Comment.objects.create(
            username=feedback_data['username'],
            email=feedback_data['email'],
            text=feedback_data['text'])
        if get_config('proso_feedback', 'send_emails', default=True):
            feedback_domain = get_config('proso_feedback', 'domain', required=True)
            feedback_to = get_config('proso_feedback', 'to', required=True)
            if is_likely_worthless(feedback_data):
                mail_from = 'spam@' + feedback_domain
            else:
                mail_from = 'feedback@' + feedback_domain
            text_content = render_to_string("emails/feedback.plain.txt", {
                "feedback": feedback_data,
                "user": request.user,
            })
            html_content = render_to_string("emails/feedback.html", {
                "feedback": feedback_data,
                "user": request.user,
            })
            subject = feedback_domain + ' feedback ' + str(comment.id)
            mail = EmailMultiAlternatives(
                subject,
                text_content,
                mail_from,
                feedback_to,
            )
            mail.attach_alternative(html_content, "text/html")
            mail.send()
            LOGGER.debug("email sent %s\n", text_content)
        return HttpResponse('ok', status=201)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(request.method))
Example #2
0
 def has_answer_more_items(self, items, user=None):
     cached_all = {}
     answer_cache = cache.get('database_environment__has_answer', {})
     for item in items:
         cache_key = '{}_{}'.format(item, user)
         found = answer_cache.get(cache_key)
         if found:
             cached_all[item] = True
     to_find = [i for i in items if i not in set(cached_all.keys())]
     if len(cached_all) != 0:
         LOGGER.debug('cache hit for having answers, items {} and user {}'.format(len(cached_all), user))
     if len(to_find) != 0:
         LOGGER.debug('cache miss for having answers, items {} and user {}'.format(len(to_find), user))
         with closing(connection.cursor()) as cursor:
             where, where_params = self._where({'user_id': user, 'item_id': to_find}, False, for_answers=True)
             cursor.execute(
                 'SELECT item_id, 1 FROM proso_models_answer WHERE '
                 + where + ' GROUP BY item_id',
                 where_params)
             for i, _ in cursor:
                 cached_all[i] = True
             cache_expiration = get_config('proso_models', 'having_answers.cache_expiration', default=30 * 24 * 60 * 60)
             # trying to decrease probability of race condition
             answer_cache = cache.get('database_environment__has_answer', {})
             for item in cached_all.keys():
                 cache_key = '{}_{}'.format(item, user)
                 answer_cache[cache_key] = True
             cache.set('database_environment__has_answer', answer_cache, cache_expiration)
     return {i: cached_all.get(i, False) for i in items}
Example #3
0
def create_class_code(sender, instance, created=False, **kwargs):
    if not instance.code:
        condition = True
        while condition:
            code = random_string(
                get_config('proso_user', 'generated_code_length', default=5))
            condition = Class.objects.filter(code=code).exists()
        instance.code = code
Example #4
0
def login_student(request):
    """
    Log in student

    POST parameters (JSON):
        student:
            profile id of the student
    """
    if not get_config('proso_user', 'allow_login_students', default=False):
        return render_json(request, {
            'error': _('Log in as student is not allowed.'),
            'error_type': 'login_student_not_allowed'
        },
                           template='class_create_student.html',
                           help_text=login_student.__doc__,
                           status=403)

    if request.method == 'GET':
        return render(request,
                      'class_login_student.html', {},
                      help_text=login_student.__doc__)
    elif request.method == 'POST':
        if not request.user.is_authenticated() or not hasattr(
                request.user, "userprofile"):
            return render_json(request, {
                'error': _('User is not logged in.'),
                'error_type': 'user_unauthorized'
            },
                               template='class_create_student.html',
                               status=401)
        data = json_body(request.body.decode("utf-8"))
        try:
            student = User.objects.get(
                userprofile=data.get('student'),
                userprofile__classes__owner=request.user.userprofile)
        except User.DoesNotExist:
            return render_json(request, {
                'error': _('Student not found'),
                'error_type': 'student_not_found'
            },
                               template='class_login_student.html',
                               status=401)
        if not student.is_active:
            return render_json(
                request, {
                    'error': _('The account has not been activated.'),
                    'error_type': 'account_not_activated'
                },
                template='class_login_student.html',
                status=401)
        student.backend = 'django.contrib.auth.backends.ModelBackend'
        login(request, student)
        request.method = "GET"
        return profile(request)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(
            request.method))
Example #5
0
def get_forbidden_categories(user):
    result = []
    for sub_type, categories in get_config('proso_subscription', 'premium_categories', default={}).items():
        if Subscription.objects.is_active(user, sub_type):
            continue
        for category in categories:
            if random.random() < (1 - category.get('random_access', 0)):
                result.append(category['id'])
    return result
def options(request, json_list, nested):
    environment = get_environment()
    user_id = get_user_id(request)
    time = get_time(request)
    if is_time_overridden(request):
        environment.shift_time(time)
    item_selector = get_item_selector()
    option_selector = get_option_selector(item_selector)
    option_sets = get_option_set().get_option_for_flashcards([
        (question['payload'], question['question_type'])
        for question in json_list
        if question['payload']['object_type'] == 'fc_flashcard'
    ])
    metas = [question.get('meta', {}) for question in json_list]
    test_position = _test_index(metas)
    selected_items = [question['payload']['item_id'] for question in json_list
                      if question['payload']['object_type'] == 'fc_flashcard']
    allow_zero_option = {}
    for question in json_list:
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        if len(option_sets[question['payload']['item_id']]) == 0 and 'term_secondary' not in question['payload']:
            # If we do not have enough options, we have to force direction
            question['question_type'] = FlashcardAnswer.FROM_TERM
        disable_open_questions = False
        if question['payload']['disable_open_questions']:
            disable_open_questions = True
        elif question['payload']['restrict_open_questions']:
            disable_open_questions = question['question_type'] in {FlashcardAnswer.FROM_DESCRIPTION, FlashcardAnswer.FROM_TERM_TO_TERM_SECONDARY}
        allow_zero_option[question['payload']['item_id']] = question['question_type'] in {FlashcardAnswer.FROM_TERM, FlashcardAnswer.FROM_TERM_SECONDARY_TO_TERM} and not disable_open_questions

    all_options = {i: options for i, options in zip(selected_items, option_selector.select_options_more_items(
        environment, user_id, selected_items, time, option_sets,
        allow_zero_options=allow_zero_option
    ))}
    options_json_list = []
    # HACK: Here, we have to take into account reference questions with zero
    # options. In case of zero options we have to force a question type if the
    # restriction for zero options is enabled.
    config_zero_options_restriction = get_config('proso_models', 'options_count.parameters.allow_zero_options_restriction', default=False)
    for i, question in enumerate(json_list):
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        if test_position is not None and test_position == i:
            if 'term_secondary' not in question['payload'] and config_zero_options_restriction:
                question['question_type'] = FlashcardAnswer.FROM_TERM
            question['payload']['options'] = []
            continue
        options = all_options[question['payload']['item_id']]
        question['payload']['options'] = [Item.objects.item_id_to_json(o) for o in options]
        options_json_list += question['payload']['options']
    item2object(request, options_json_list, nested=True)
    for question in json_list:
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        sort_key = 'term_secondary' if question['question_type'] == FlashcardAnswer.FROM_TERM_TO_TERM_SECONDARY else 'term'
        question['payload']['options'] = sorted(question['payload']['options'], key=lambda o: o[sort_key]['name'])
Example #7
0
 def confusing_factor_more_items(self, item, items, user=None):
     cached_all = {}
     confusing_factor_cache = cache.get('database_environment__confusing_factor', {})
     for item_secondary in items:
         _items = self._sorted([item, item_secondary])
         cache_key = '{}_{}_{}'.format(_items[0], _items[1], user)
         cached_item = confusing_factor_cache.get(cache_key)
         if cached_item:
             cached_all[item_secondary] = int(cached_item)
     to_find = [i for i in items if i not in list(cached_all.keys())]
     if len(cached_all) != 0:
         LOGGER.debug('cache hit for confusing factor, item {}, {} other items and user {}'.format(item, len(cached_all), user))
     if len(to_find) != 0:
         LOGGER.debug('cache miss for confusing factor, item {}, {} other items and user {}'.format(item, len(to_find), user))
         where, where_params = self._where({
             'item_answered_id': to_find,
             'item_asked_id': to_find,
         }, force_null=False, for_answers=True, conjuction=False)
         user_where, user_params = self._column_comparison('user_id', user, force_null=False)
         with closing(connection.cursor()) as cursor:
             cursor.execute(
                 '''
                 SELECT
                     item_asked_id,
                     item_answered_id,
                     COUNT(id) AS confusing_factor
                 FROM
                     proso_models_answer
                 WHERE guess = 0 AND (item_asked_id = %s OR item_asked_id = %s) AND
                 ''' + user_where + ' AND (' + where + ') GROUP BY item_asked_id, item_answered_id', [item, item] + user_params + where_params)
             found = {}
             for item_asked, item_answered, count in cursor:
                 if item_asked == item:
                     found[item_answered] = found.get(item_answered, 0) + count
                 else:
                     found[item_asked] = found.get(item_asked, 0) + count
             for i in to_find:
                 found[i] = found.get(i, 0)
             cache_expiration = get_config('proso_models', 'confusing_factor.cache_expiration', default=24 * 60 * 60)
             # trying to decrease probability of race condition
             confusing_factor_cache = cache.get('database_environment__confusing_factor', {})
             for item_secondary, count in found.items():
                 _items = self._sorted([item, item_secondary])
                 cache_key = '{}_{}_{}'.format(_items[0], _items[1], user)
                 confusing_factor_cache[cache_key] = count
                 cached_all[item_secondary] = count
             cache.set('database_environment__confusing_factor', confusing_factor_cache, cache_expiration)
     return [cached_all[i] for i in items]
def login_student(request):
    """
    Log in student

    POST parameters (JSON):
        student:
            profile id of the student
    """
    if not get_config('proso_user', 'allow_login_students', default=False):
        return render_json(request, {
            'error': _('Log in as student is not allowed.'),
            'error_type': 'login_student_not_allowed'
        }, template='class_create_student.html', help_text=login_student.__doc__, status=403)

    if request.method == 'GET':
        return render(request, 'class_login_student.html', {}, help_text=login_student.__doc__)
    elif request.method == 'POST':
        if not request.user.is_authenticated() or not hasattr(request.user, "userprofile"):
            return render_json(request, {
                'error': _('User is not logged in.'),
                'error_type': 'user_unauthorized'
            }, template='class_create_student.html', status=401)
        data = json_body(request.body.decode("utf-8"))
        try:
            student = User.objects.get(userprofile=data.get('student'),
                                       userprofile__classes__owner=request.user.userprofile)
        except User.DoesNotExist:
            return render_json(request, {
                'error': _('Student not found'),
                'error_type': 'student_not_found'
            }, template='class_login_student.html', status=401)
        if not student.is_active:
            return render_json(request, {
                'error': _('The account has not been activated.'),
                'error_type': 'account_not_activated'
            }, template='class_login_student.html', status=401)
        student.backend = 'django.contrib.auth.backends.ModelBackend'
        login(request, student)
        request.method = "GET"
        return profile(request)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(request.method))
Example #9
0
 def confusing_factor_more_items(self, item, items, user=None):
     cached_all = {}
     confusing_factor_cache = cache.get('database_environment__confusing_factor', {})
     for item_secondary in items:
         cache_key = '{}_{}_{}'.format(item, item_secondary, user)
         cached_item = confusing_factor_cache.get(cache_key)
         if cached_item:
             cached_all[item_secondary] = int(cached_item)
     to_find = [i for i in items if i not in list(cached_all.keys())]
     if len(cached_all) != 0:
         LOGGER.debug('cache hit for confusing factor, item {}, {} other items and user {}'.format(item, len(cached_all), user))
     if len(to_find) != 0:
         LOGGER.debug('cache miss for confusing factor, item {}, {} other items and user {}'.format(item, len(to_find), user))
         user_where, user_params = self._column_comparison('user_id', user, force_null=False)
         with closing(connection.cursor()) as cursor:
             cursor.execute(
                 '''
                 SELECT DISTINCT main.item_id, c.identifier
                 FROM proso_flashcards_flashcard AS main
                 INNER JOIN proso_flashcards_flashcard AS fc
                     ON (
                         main.term_id = fc.term_id OR
                         main.term_secondary_id = fc.term_id
                     )
                 INNER JOIN proso_flashcards_context AS c
                     ON c.id = fc.context_id
                 WHERE
                     fc.term_secondary_id IS NULL AND
                     fc.active AND
                     main.item_id IN (''' + ','.join('%s' for _ in [item] + to_find) + ')',
                 [item] + to_find
             )
             context_mapping = defaultdict(set)
             for i, c in cursor:
                 context_mapping[i].add(c)
         with closing(connection.cursor()) as cursor:
             cursor.execute(
                 '''
                 SELECT
                     main.item_answered_id,
                     COUNT(main.id)::float / (open.count + closed.count) as confusing_factor
                 FROM
                     proso_models_answer as main
                 INNER JOIN (
                     SELECT item_asked_id as item, COUNT(*) as count
                     FROM proso_models_answer
                     WHERE item_asked_id = %s AND guess = 0 AND ''' + user_where + '''
                     GROUP BY 1
                 ) AS open ON open.item = main.item_asked_id
                 INNER JOIN (
                     SELECT proso_models_answer.item_asked_id, fc.item_id as item_option_id, COUNT(*) as count
                     FROM proso_models_answer
                     INNER JOIN proso_flashcards_flashcardanswer_options AS options ON proso_models_answer.id = options.flashcardanswer_id
                     INNER JOIN proso_flashcards_flashcard AS fc on fc.id = options.flashcard_id
                     WHERE proso_models_answer.item_asked_id = %s AND ''' + user_where + '''
                     GROUP BY 1, 2
                 ) AS closed ON main.item_asked_id = closed.item_asked_id AND main.item_answered_id = closed.item_option_id
                 WHERE
                     main.item_asked_id = %s
                 AND main.item_answered_id IS NOT NULL
                 AND main.item_asked_id != main.item_answered_id
                 AND ''' + user_where + '''
                 GROUP BY 1, open.count, closed.count
                 ''', [item] + user_params + [item] + user_params + [item] + user_params)
             found = {}
             for item_answered, count in cursor:
                 found[item_answered] = count
             for i in to_find:
                 found[i] = 1000 * (found.get(i, 0.05) + 0 if len(context_mapping[item]) == 0 else (len(context_mapping[i] & context_mapping[item]) / len(context_mapping[i] | context_mapping[item])))
             cache_expiration = get_config('proso_models', 'confusing_factor.cache_expiration', default=24 * 60 * 60)
             # trying to decrease probability of race condition
             confusing_factor_cache = cache.get('database_environment__confusing_factor', {})
             for item_secondary, count in found.items():
                 cache_key = '{}_{}_{}'.format(item, item_secondary, user)
                 confusing_factor_cache[cache_key] = count
                 cached_all[item_secondary] = count
             cache.set('database_environment__confusing_factor', confusing_factor_cache, cache_expiration)
     return [cached_all[i] for i in items]
def create_student(request):
    """ Create new user in class

    POST parameters (JSON):
        class:
            id of the class
        username (optional):
            username of student, if not provided username is create based on name
        password (optional):
            password of student
        first_name:
            first_name of student
        last_name (optional):
            last_name of student
        email (optional):
           e-mail of student
    """

    if not get_config('proso_user', 'allow_create_students', default=False):
        return render_json(request, {
            'error': _('Creation of new users is not allowed.'),
            'error_type': 'student_creation_not_allowed'
        }, template='class_create_student.html', help_text=create_student.__doc__, status=403)

    if request.method == 'GET':
        return render(request, 'class_create_student.html', {}, help_text=create_student.__doc__)
    if request.method == 'POST':
        if not request.user.is_authenticated() or not hasattr(request.user, "userprofile"):
            return render_json(request, {
                'error': _('User is not logged in.'),
                'error_type': 'user_unauthorized'
            }, template='class_create_student.html', status=401)
        data = json_body(request.body.decode("utf-8"))
        try:
            cls = Class.objects.get(pk=data['class'], owner=request.user.userprofile)
        except (Class.DoesNotExist, KeyError):
            return render_json(request, {
                'error': _('Class with given id not found.'),
                'error_type': 'class_not_found',
            }, template='class_create_student.html', status=404)

        if 'first_name' not in data or not data['first_name']:
            return render_json(request, {
                'error': _('First name code is missing.'),
                'error_type': 'missing_first_name'
            }, template='class_create_student.html', status=400)

        user = User(first_name=data['first_name'])
        if data.get('last_name'):
            user.last_name = data['last_name']
        if data.get('email'):
            if User.objects.filter(email=data['email']).exists():
                return render_json(request, {
                    'error': _('There is already a user with the given e-mail.'),
                    'error_type': 'email_exists'
                }, template='class_create_student.html', status=400)
            user.email = data['email']
        if data.get('username'):
            if User.objects.filter(username=data['username']).exists():
                return render_json(request, {
                    'error': _('There is already a user with the given username.'),
                    'error_type': 'username_exists'
                }, template='class_create_student.html', status=400)
            user.username = data['username']
        else:
            user.username = get_unused_username(user)
        if data.get('password'):
            user.set_password(data['password'])

        user.save()
        cls.members.add(user.userprofile)

        return render_json(request, user.userprofile.to_json(nested=True), template='class_create_student.html', status=201)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(request.method))
Example #11
0
def options(request, json_list, nested):
    environment = get_environment()
    user_id = get_user_id(request)
    time = get_time(request)
    if is_time_overridden(request):
        environment.shift_time(time)
    item_selector = get_item_selector()
    option_selector = get_option_selector(item_selector)
    option_sets = get_option_set().get_option_for_flashcards([
        (question['payload'], question['question_type'])
        for question in json_list
        if question['payload']['object_type'] == 'fc_flashcard'
    ])
    metas = [question.get('meta', {}) for question in json_list]
    test_position = _test_index(metas)
    selected_items = [
        question['payload']['item_id'] for question in json_list
        if question['payload']['object_type'] == 'fc_flashcard'
    ]
    allow_zero_option = {}
    for question in json_list:
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        if len(option_sets[question['payload']['item_id']]
               ) == 0 and 'term_secondary' not in question['payload']:
            # If we do not have enough options, we have to force direction
            question['question_type'] = FlashcardAnswer.FROM_TERM
        disable_open_questions = False
        if question['payload']['disable_open_questions']:
            disable_open_questions = True
        elif question['payload']['restrict_open_questions']:
            disable_open_questions = question['question_type'] in {
                FlashcardAnswer.FROM_DESCRIPTION,
                FlashcardAnswer.FROM_TERM_TO_TERM_SECONDARY
            }
        allow_zero_option[question['payload']
                          ['item_id']] = question['question_type'] in {
                              FlashcardAnswer.FROM_TERM,
                              FlashcardAnswer.FROM_TERM_SECONDARY_TO_TERM
                          } and not disable_open_questions

    all_options = {
        i: options
        for i, options in zip(
            selected_items,
            option_selector.select_options_more_items(
                environment,
                user_id,
                selected_items,
                time,
                option_sets,
                allow_zero_options=allow_zero_option))
    }
    options_json_list = []
    # HACK: Here, we have to take into account reference questions with zero
    # options. In case of zero options we have to force a question type if the
    # restriction for zero options is enabled.
    config_zero_options_restriction = get_config(
        'proso_models',
        'options_count.parameters.allow_zero_options_restriction',
        default=False)
    for i, question in enumerate(json_list):
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        if test_position is not None and test_position == i:
            if 'term_secondary' not in question[
                    'payload'] and config_zero_options_restriction:
                question['question_type'] = FlashcardAnswer.FROM_TERM
            question['payload']['options'] = []
            continue
        options = all_options[question['payload']['item_id']]
        question['payload']['options'] = [
            Item.objects.item_id_to_json(o) for o in options
        ]
        options_json_list += question['payload']['options']
    item2object(request, options_json_list, nested=True)
    for question in json_list:
        if question['payload']['object_type'] != 'fc_flashcard':
            continue
        sort_key = 'term_secondary' if question[
            'question_type'] == FlashcardAnswer.FROM_TERM_TO_TERM_SECONDARY else 'term'
        question['payload']['options'] = sorted(
            question['payload']['options'], key=lambda o: o[sort_key]['name'])
Example #12
0
def profile(request, status=200):
    """
    Get the user's profile. If the user has no assigned profile, the HTTP 404
    is returned. Make a POST request to modify the user's profile.

    GET parameters:
        html
            turn on the HTML version of the API
        username:
            username of user (only for users with public profile)
        stats:
            attache addition user statistics

    POST parameters (JSON):
        send_emails:
            switcher turning on sending e-mails to user
        public:
            swicher making the user's profile publicly available
        user:
            password:
                user's password
            password_check:
                user's password again to check it
            first_name (optional):
                user's first name
            last_name (optional):
                user's last name
    """
    if request.method == 'GET':
        if request.GET.get("username", False):
            try:
                user_profile = User.objects.get(username=request.GET.get("username"),
                                                userprofile__public=True).userprofile
            except ObjectDoesNotExist:
                raise Http404("user not found or have not public profile")
        else:
            user_id = get_user_id(request)
            if get_config('proso_user', 'google.openid.migration', default=True) and not is_user_id_overridden(request):
                migrated_user = migrate_google_openid_user(request.user)
                if migrated_user is not None:
                    auth.logout(request)
                    migrated_user.backend = 'social.backends.google.GoogleOAuth2'
                    auth.login(request, migrated_user)
            user_profile = get_object_or_404(UserProfile, user_id=user_id)
        return render_json(
            request, user_profile, status=status,
            template='user_profile.html', help_text=profile.__doc__)
    elif request.method == 'POST':
        with transaction.atomic():
            to_save = json_body(request.body.decode("utf-8"))
            user_id = get_user_id(request)
            user_profile = get_object_or_404(UserProfile, user_id=user_id)
            user = to_save.get('user', None)
            if 'send_emails' in to_save:
                user_profile.send_emails = bool(to_save['send_emails'])
            if 'public' in to_save:
                user_profile.public = bool(to_save['public'])
            if user:
                error = _save_user(request, user, new=False)
                if error:
                    return render_json(request, error, template='user_json.html', status=400)
            if 'properties' in to_save:
                user_profile.save_properties(to_save['properties'])
            user_profile.save()
        request.method = "GET"
        return profile(request, status=202)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(request.method))
Example #13
0
def practice(request):
    """
    Return the given number of questions to practice adaptively. In case of
    POST request, try to save the answer(s).

    GET parameters:
        filter:
            list of lists of identifiers (may be prefixed by minus sign to
            mark complement)
        language:
            language (str) of items
        avoid:
            list of item ids to avoid
        limit:
            number of returned questions (default 10, maximum 100)
        time:
            time in format '%Y-%m-%d_%H:%M:%S' used for practicing
        user:
            identifier for the practicing user (only for stuff users)
        stats:
            turn on the enrichment of the objects by some statistics
        html:
            turn on the HTML version of the API

    BODY:
        see answer resource
    """
    if request.user.id is None:  # Google Bot
        return render_json(
            request, {
                'error': _('There is no user available for the practice.'),
                'error_type': 'user_undefined'
            },
            status=400,
            template='models_json.html')

    limit = min(int(request.GET.get('limit', 10)), 100)
    # prepare
    user = get_user_id(request)
    time = get_time(request)
    avoid = load_query_json(request.GET, "avoid", "[]")
    practice_filter = get_filter(request)
    practice_context = PracticeContext.objects.from_content(practice_filter)
    environment = get_environment()
    item_selector = get_item_selector()
    if is_time_overridden(request):
        environment.shift_time(time)

    # save answers
    if request.method == 'POST':
        _save_answers(request, practice_context, False)
    elif request.method == 'GET':
        PracticeSet.objects.filter(answer__user_id=request.user.id).update(
            finished=True)

    if limit > 0:
        item_ids = Item.objects.filter_all_reachable_leaves(
            practice_filter, get_language(request))
        item_ids = list(set(item_ids) - set(avoid))
        limit_size = get_config('proso_models',
                                'practice.limit_item_set_size_to_select_from',
                                default=None)
        if limit_size is not None and limit_size < len(item_ids):
            item_ids = sample(item_ids, limit_size)
        if len(item_ids) == 0:
            return render_json(request, {
                'error':
                _('There is no item for the given filter to practice.'),
                'error_type':
                'empty_practice'
            },
                               status=404,
                               template='models_json.html')
        selected_items, meta = item_selector.select(environment,
                                                    user,
                                                    item_ids,
                                                    time,
                                                    practice_context.id,
                                                    limit,
                                                    items_in_queue=len(avoid))
        result = []
        for item, item_meta in zip(selected_items, meta):
            question = {
                'object_type': 'question',
                'payload': Item.objects.item_id_to_json(item),
            }
            if item_meta is not None:
                question['meta'] = item_meta
            result.append(question)
    else:
        result = []

    return render_json(request,
                       result,
                       template='models_json.html',
                       help_text=practice.__doc__)
Example #14
0
def create_student(request):
    """ Create new user in class

    POST parameters (JSON):
        class:
            id of the class
        username (optional):
            username of student, if not provided username is create based on name
        password (optional):
            password of student
        first_name:
            first_name of student
        last_name (optional):
            last_name of student
        email (optional):
           e-mail of student
    """

    if not get_config('proso_user', 'allow_create_students', default=False):
        return render_json(request, {
            'error': _('Creation of new users is not allowed.'),
            'error_type': 'student_creation_not_allowed'
        },
                           template='class_create_student.html',
                           help_text=create_student.__doc__,
                           status=403)

    if request.method == 'GET':
        return render(request,
                      'class_create_student.html', {},
                      help_text=create_student.__doc__)
    if request.method == 'POST':
        if not request.user.is_authenticated() or not hasattr(
                request.user, "userprofile"):
            return render_json(request, {
                'error': _('User is not logged in.'),
                'error_type': 'user_unauthorized'
            },
                               template='class_create_student.html',
                               status=401)
        data = json_body(request.body.decode("utf-8"))
        try:
            cls = Class.objects.get(pk=data['class'],
                                    owner=request.user.userprofile)
        except (Class.DoesNotExist, KeyError):
            return render_json(request, {
                'error': _('Class with given id not found.'),
                'error_type': 'class_not_found',
            },
                               template='class_create_student.html',
                               status=404)

        if 'first_name' not in data or not data['first_name']:
            return render_json(request, {
                'error': _('First name code is missing.'),
                'error_type': 'missing_first_name'
            },
                               template='class_create_student.html',
                               status=400)

        user = User(first_name=data['first_name'])
        if data.get('last_name'):
            user.last_name = data['last_name']
        if data.get('email'):
            if User.objects.filter(email=data['email']).exists():
                return render_json(request, {
                    'error':
                    _('There is already a user with the given e-mail.'),
                    'error_type':
                    'email_exists'
                },
                                   template='class_create_student.html',
                                   status=400)
            user.email = data['email']
        if data.get('username'):
            if User.objects.filter(username=data['username']).exists():
                return render_json(request, {
                    'error':
                    _('There is already a user with the given username.'),
                    'error_type':
                    'username_exists'
                },
                                   template='class_create_student.html',
                                   status=400)
            user.username = data['username']
        else:
            user.username = get_unused_username(user)
        if data.get('password'):
            user.set_password(data['password'])

        user.save()
        cls.members.add(user.userprofile)

        return render_json(request,
                           user.userprofile.to_json(nested=True),
                           template='class_create_student.html',
                           status=201)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(
            request.method))
Example #15
0
def feedback(request):
    """
    Send feedback to the authors of the system.

    GET parameters:
        html
            turn on the HTML version of the API

    POST parameters (JSON):
        text:
            the main feedback content
        email (optional):
            user's e-mail
        username (optional):
            user's name
    """
    if request.method == 'GET':
        return render(request,
                      'feedback_feedback.html', {},
                      help_text=feedback.__doc__)
    if request.method == 'POST':
        feedback_data = json_body(request.body.decode("utf-8"))
        feedback_data['user_agent'] = Session.objects.get_current_session(
        ).http_user_agent.content
        if not feedback_data.get('username'):
            feedback_data['username'] = request.user.username
        if not feedback_data.get('email'):
            feedback_data['email'] = request.user.email
        comment = Comment.objects.create(username=feedback_data['username'],
                                         email=feedback_data['email'],
                                         text=feedback_data['text'])
        if get_config('proso_feedback', 'send_emails', default=True):
            feedback_domain = get_config('proso_feedback',
                                         'domain',
                                         required=True)
            feedback_to = get_config('proso_feedback', 'to', required=True)
            if is_likely_worthless(feedback_data):
                mail_from = 'spam@' + feedback_domain
            else:
                mail_from = 'feedback@' + feedback_domain
            text_content = render_to_string("emails/feedback.plain.txt", {
                "feedback": feedback_data,
                "user": request.user,
            })
            html_content = render_to_string("emails/feedback.html", {
                "feedback": feedback_data,
                "user": request.user,
            })
            subject = feedback_domain + ' feedback ' + str(comment.id)
            mail = EmailMultiAlternatives(
                subject,
                text_content,
                mail_from,
                feedback_to,
            )
            mail.attach_alternative(html_content, "text/html")
            mail.send()
            LOGGER.debug("email sent %s\n", text_content)
        return HttpResponse('ok', status=201)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(
            request.method))
Example #16
0
def practice(request):
    """
    Return the given number of questions to practice adaptively. In case of
    POST request, try to save the answer(s).

    GET parameters:
        filter:
            list of lists of identifiers (may be prefixed by minus sign to
            mark complement)
        language:
            language (str) of items
        avoid:
            list of item ids to avoid
        limit:
            number of returned questions (default 10, maximum 100)
        time:
            time in format '%Y-%m-%d_%H:%M:%S' used for practicing
        user:
            identifier for the practicing user (only for stuff users)
        stats:
            turn on the enrichment of the objects by some statistics
        html:
            turn on the HTML version of the API

    BODY:
        see answer resource
    """
    if request.user.id is None:  # Google Bot
        return render_json(request, {
            'error': _('There is no user available for the practice.'),
            'error_type': 'user_undefined'
        }, status=400, template='models_json.html')

    limit = min(int(request.GET.get('limit', 10)), 100)
    # prepare
    user = get_user_id(request)
    time = get_time(request)
    avoid = load_query_json(request.GET, "avoid", "[]")
    practice_filter = get_filter(request)
    practice_context = PracticeContext.objects.from_content(practice_filter)
    environment = get_environment()
    item_selector = get_item_selector()
    if is_time_overridden(request):
        environment.shift_time(time)

    # save answers
    if request.method == 'POST':
        _save_answers(request, practice_context, False)
    elif request.method == 'GET':
        PracticeSet.objects.filter(answer__user_id=request.user.id).update(finished=True)

    if limit > 0:
        item_ids = Item.objects.filter_all_reachable_leaves(practice_filter, get_language(request), forbidden_identifiers=get_forbidden_items())
        item_ids = list(set(item_ids) - set(avoid))
        limit_size = get_config('proso_models', 'practice.limit_item_set_size_to_select_from', default=None)
        if limit_size is not None and limit_size < len(item_ids):
            item_ids = sample(item_ids, limit_size)
        if len(item_ids) == 0:
            return render_json(request, {
                'error': _('There is no item for the given filter to practice.'),
                'error_type': 'empty_practice'
            }, status=404, template='models_json.html')
        selected_items, meta = item_selector.select(environment, user, item_ids, time, practice_context.id, limit, items_in_queue=len(avoid))
        result = []
        for item, item_meta in zip(selected_items, meta):
            question = {
                'object_type': 'question',
                'payload': Item.objects.item_id_to_json(item),
            }
            if item_meta is not None:
                question['meta'] = item_meta
            result.append(question)
    else:
        result = []

    return render_json(request, result, template='models_json.html', help_text=practice.__doc__)
Example #17
0
def profile(request, status=200):
    """
    Get the user's profile. If the user has no assigned profile, the HTTP 404
    is returned. Make a POST request to modify the user's profile.

    GET parameters:
        html
            turn on the HTML version of the API
        username:
            username of user (only for users with public profile)
        stats:
            attache addition user statistics

    POST parameters (JSON):
        send_emails:
            switcher turning on sending e-mails to user
        public:
            swicher making the user's profile publicly available
        user:
            password:
                user's password
            password_check:
                user's password again to check it
            first_name (optional):
                user's first name
            last_name (optional):
                user's last name
    """
    if request.method == 'GET':
        if request.GET.get("username", False):
            try:
                user_profile = User.objects.get(
                    username=request.GET.get("username"),
                    userprofile__public=True).userprofile
            except ObjectDoesNotExist:
                raise Http404("user not found or have not public profile")
        else:
            user_id = get_user_id(request)
            if get_config('proso_user',
                          'google.openid.migration',
                          default=True) and not is_user_id_overridden(request):
                migrated_user = migrate_google_openid_user(request.user)
                if migrated_user is not None:
                    auth.logout(request)
                    migrated_user.backend = 'social.backends.google.GoogleOAuth2'
                    auth.login(request, migrated_user)
            user_profile = get_object_or_404(UserProfile, user_id=user_id)
        return render_json(request,
                           user_profile,
                           status=status,
                           template='user_profile.html',
                           help_text=profile.__doc__)
    elif request.method == 'POST':
        with transaction.atomic():
            to_save = json_body(request.body.decode("utf-8"))
            user_id = get_user_id(request)
            user_profile = get_object_or_404(UserProfile, user_id=user_id)
            user = to_save.get('user', None)
            if 'send_emails' in to_save:
                user_profile.send_emails = bool(to_save['send_emails'])
            if 'public' in to_save:
                user_profile.public = bool(to_save['public'])
            if user:
                error = _save_user(request, user, new=False)
                if error:
                    return render_json(request,
                                       error,
                                       template='user_json.html',
                                       status=400)
            if 'properties' in to_save:
                user_profile.save_properties(to_save['properties'])
            user_profile.save()
        request.method = "GET"
        return profile(request, status=202)
    else:
        return HttpResponseBadRequest("method %s is not allowed".format(
            request.method))