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

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__)