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))
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}
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
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))
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'])
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))
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))
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 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))
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 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))
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))
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__)
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))