def read(request, key): if 'user' in request.GET: user = get_user_id(request) else: user = None item = int(request.GET['item']) if 'item' in request.GET else None item_secondary = int(request.GET['item_secondary']) if 'item_secondary' in request.GET else None time = get_time(request) environment = get_environment() if is_time_overridden(request): environment.shift_time(time) value = environment.read(key, user=user, item=item, item_secondary=item_secondary) if value is None: return render_json( request, {'error': 'value with key "%s" not found' % key}, template='models_json.html', status=404) else: return render_json( request, { 'object_type': 'value', 'key': key, 'item_primary_id': item, 'item_secondary_id': item_secondary, 'user_id': user, 'value': value }, template='models_json.html' )
def read(request, key): if 'user' in request.GET: user = get_user_id(request) else: user = None item = int(request.GET['item']) if 'item' in request.GET else None item_secondary = int(request.GET['item_secondary'] ) if 'item_secondary' in request.GET else None time = get_time(request) environment = get_environment() if is_time_overridden(request): environment.shift_time(time) value = environment.read(key, user=user, item=item, item_secondary=item_secondary) if value is None: return render_json(request, {'error': 'value with key "%s" not found' % key}, template='models_json.html', status=404) else: return render_json(request, { 'object_type': 'value', 'key': key, 'item_primary_id': item, 'item_secondary_id': item_secondary, 'user_id': user, 'value': value }, template='models_json.html')
def audit(request, key): if 'user' in request.GET: user = get_user_id(request) else: user = None limit = 100 if request.user.is_staff: limit = request.GET.get('limit', limit) item = int(request.GET['item']) if 'item' in request.GET else None item_secondary = int(request.GET['item_secondary']) if 'item_secondary' in request.GET else None time = get_time(request) environment = get_environment() if is_time_overridden(request): environment.shift_time(time) values = environment.audit( key, user=user, item=item, item_secondary=item_secondary, limit=limit) def _to_json_audit(audit): (time, value) = audit return { 'object_type': 'value', 'key': key, 'item_primary_id': item, 'item_secondary_id': item_secondary, 'user_id': user, 'value': value, 'time': time.strftime('%Y-%m-%d %H:%M:%S') } return render_json(request, list(map(_to_json_audit, values)), template='models_json.html')
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'])
def status(request): user_id = get_user_id(request) time = get_time(request) environment = get_environment() if is_time_overridden(request): environment.shift_time(time) return render_json(request, { 'object_type': 'status', 'number_of_answers': environment.number_of_answers(user=user_id), 'number_of_correct_answers': environment.number_of_correct_answers(user=user_id), 'environment_info': get_active_environment_info(), }, template='models_json.html')
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'] 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] 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: # If we do not have enough options, we have to force direction question['question_type'] = FlashcardAnswer.FROM_TERM allow_zero_option[question['payload']['item_id']] = question[ 'question_type'] == FlashcardAnswer.FROM_TERM is_flashcard_question = [ question['payload']['object_type'] == 'fc_flashcard' for question in json_list ] if not all(is_flashcard_question): # TODO: We should support mixed questions in the future raise Exception('All questions must be for flashcards!') all_options = option_selector.select_options_more_items( environment, user_id, selected_items, time, option_sets, allow_zero_options=allow_zero_option) options_json_list = [] for i, (question, options) in enumerate(zip(json_list, all_options)): if test_position is not None and test_position == i: question['question_type'] = FlashcardAnswer.FROM_TERM question['payload']['options'] = [] continue 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=False)
def prediction(request, json_list, nested): if 'stats' not in request.GET: return object_item_ids = [x['item_id'] for x in json_list] user = get_user_id(request) time = get_time(request) predictions = _predictive_model().predict_more_items(_environment(request), user, object_item_ids, time) mastery_threshold = get_mastery_trashold() for object_json, prediction in zip(json_list, predictions): object_json['prediction'] = float("{0:.2f}".format(prediction)) object_json['mastered'] = prediction >= mastery_threshold if "new_user_predictions" in request.GET: user = -1 predictions = _predictive_model().predict_more_items(_environment(request), user, object_item_ids, time) for object_json, prediction in zip(json_list, predictions): object_json['new_user_prediction'] = float("{0:.2f}".format(prediction)) return json_list
def avg_prediction(request, json_list, nested): if 'stats' not in request.GET: return object_item_ids = [x['item_id'] for x in json_list] leaves = models.Item.objects.get_leaves(object_item_ids) all_leaves = list(set(flatten(leaves.values()))) user = get_user_id(request) time = get_time(request) predictions = dict(list(zip(all_leaves, _predictive_model().predict_more_items( _environment(request), user, all_leaves, time )))) mastery_threshold = get_mastery_trashold() for object_json in json_list: leaf_predictions = [predictions[leave] for leave in leaves[object_json['item_id']]] object_json['avg_predicton'] = numpy.mean(leaf_predictions) object_json['mastered'] = sum([p > mastery_threshold for p in leaf_predictions])
def audit(request, key): if 'user' in request.GET: user = get_user_id(request) else: user = None limit = 100 if request.user.is_staff: limit = request.GET.get('limit', limit) item_identifier = request.GET['item'] if 'item' in request.GET else None item_secondary_identifier = request.GET[ 'item_secondary'] if 'item_secondary' in request.GET else None translated = Item.objects.translate_identifiers([ i for i in [item_identifier, item_secondary_identifier] if i is not None ], get_language(request)) item = translated.get(item_identifier) item_secondary = translated.get(item_secondary_identifier) time = get_time(request) environment = get_environment() if is_time_overridden(request): environment.shift_time(time) values = environment.audit(key, user=user, item=item, item_secondary=item_secondary, limit=limit) def _to_json_audit(audit): (time, value) = audit return { 'object_type': 'value', 'key': key, 'item_primary_id': item, 'item_secondary_id': item_secondary, 'user_id': user, 'value': value, 'time': time.strftime('%Y-%m-%d %H:%M:%S') } return render_json(request, list(map(_to_json_audit, values)), template='models_json.html')
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'])
def _environment(request): environment = models.get_environment() if is_time_overridden(request): time = get_time(request) environment.shift_time(time) return environment
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__)
def user_stats(request): """ Get user statistics for selected groups of items time: time in format '%Y-%m-%d_%H:%M:%S' used for practicing user: identifier of the user (only for stuff users) username: username of user (only for users with public profile) filters: -- use this or body json as in BODY mastered: use model to compute number of mastered items - can be slowed language: language of the items BODY json in following format: { "#identifier": [] -- custom identifier (str) and filter ... } """ timer('user_stats') response = {} data = None if request.method == "POST": data = json.loads(request.body.decode("utf-8"))["filters"] if "filters" in request.GET: data = load_query_json(request.GET, "filters") if data is None: return render_json(request, {}, template='models_user_stats.html', help_text=user_stats.__doc__) environment = get_environment() if is_time_overridden(request): environment.shift_time(get_time(request)) user_id = get_user_id(request) language = get_language(request) filter_names, filter_filters = list(zip(*sorted(data.items()))) reachable_leaves = Item.objects.filter_all_reachable_leaves_many( filter_filters, language) all_leaves = sorted(list(set(flatten(reachable_leaves)))) answers = environment.number_of_answers_more_items(all_leaves, user_id) correct_answers = environment.number_of_correct_answers_more_items( all_leaves, user_id) if request.GET.get("mastered"): timer('user_stats_mastered') mastery_threshold = get_mastery_trashold() predictions = Item.objects.predict_for_overview( environment, user_id, all_leaves) mastered = dict( list(zip(all_leaves, [p >= mastery_threshold for p in predictions]))) LOGGER.debug( "user_stats - getting predictions for items took %s seconds", (timer('user_stats_mastered'))) for identifier, items in zip(filter_names, reachable_leaves): if len(items) == 0: response[identifier] = { "filter": data[identifier], "number_of_items": 0, } else: response[identifier] = { "filter": data[identifier], "number_of_items": len(items), "number_of_practiced_items": sum(answers[i] > 0 for i in items), "number_of_answers": sum(answers[i] for i in items), "number_of_correct_answers": sum(correct_answers[i] for i in items), } if request.GET.get("mastered"): response[identifier]["number_of_mastered_items"] = sum( mastered[i] for i in items) return render_json(request, response, template='models_user_stats.html', help_text=user_stats.__doc__)
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 flashcards 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) if len(practice_filter) > 0: item_ids = Item.objects.filter_all_reachable_leaves(practice_filter, get_language(request)) else: item_ids = Item.objects.get_all_available_leaves() item_ids = list(set(item_ids) - set(avoid)) 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) return render_json(request, result, template='models_json.html', help_text=practice.__doc__)
def user_stats(request): """ Get user statistics for selected groups of items time: time in format '%Y-%m-%d_%H:%M:%S' used for practicing user: identifier of the user (only for stuff users) username: username of user (only for users with public profile) filters: -- use this or body json as in BODY mastered: use model to compute number of mastered items - can be slowed language: language of the items BODY json in following format: { "#identifier": [] -- custom identifier (str) and filter ... } """ timer('user_stats') response = {} data = None if request.method == "POST": data = json.loads(request.body.decode("utf-8"))["filters"] if "filters" in request.GET: data = load_query_json(request.GET, "filters") if data is None: return render_json(request, {}, template='models_user_stats.html', help_text=user_stats.__doc__) environment = get_environment() if is_time_overridden(request): environment.shift_time(get_time(request)) user_id = get_user_id(request) language = get_language(request) filter_names, filter_filters = list(zip(*sorted(data.items()))) reachable_leaves = Item.objects.filter_all_reachable_leaves_many(filter_filters, language) all_leaves = flatten(reachable_leaves) answers = dict(list(zip(all_leaves, environment.number_of_answers_more_items(all_leaves, user_id)))) correct_answers = dict(list(zip(all_leaves, environment.number_of_correct_answers_more_items(all_leaves, user_id)))) if request.GET.get("mastered"): timer('user_stats_mastered') mastery_threshold = get_mastery_trashold() predictions = get_predictive_model().predict_more_items(environment, user_id, all_leaves, get_time(request)) mastered = dict(list(zip(all_leaves, [p >= mastery_threshold for p in predictions]))) LOGGER.debug("user_stats - getting predictions for flashcards took %s seconds", (timer('user_stats_mastered'))) for identifier, items in zip(filter_names, reachable_leaves): if len(items) == 0: response[identifier] = { "filter": data[identifier], "number_of_flashcards": 0, } else: response[identifier] = { "filter": data[identifier], "number_of_flashcards": len(items), "number_of_practiced_flashcards": sum(answers[i] > 0 for i in items), "number_of_answers": sum(answers[i] for i in items), "number_of_correct_answers": sum(correct_answers[i] for i in items), } if request.GET.get("mastered"): response[identifier]["number_of_mastered_flashcards"]= sum(mastered[i] for i in items) return render_json(request, response, template='models_user_stats.html', help_text=user_stats.__doc__)