diff --git a/website/forms.py b/website/forms.py index 1d9391f..cd32f52 100755 --- a/website/forms.py +++ b/website/forms.py @@ -9,72 +9,37 @@ seconds = () -def _get_category_choices(): - """Distinct FOSS categories for new-question form, sorted alphabetically.""" - categories = list( - TutorialResources.objects.filter( - Q(status=1) | Q(status=2), - language__name='English', - tutorial_detail__foss__show_on_homepage=1, - ) - .values_list('tutorial_detail__foss__foss', flat=True) - .distinct() - ) - # Remove empty/None and sort case-insensitively (A-Z) - categories = [c for c in categories if c] - categories = sorted(set(categories), key=lambda x: x.lower()) - return [('', 'Select a Category')] + [(c, c) for c in categories] - - class NewQuestionForm(forms.Form): - category = forms.ChoiceField( - choices=[], - widget=forms.Select(attrs={}), - required=True, - error_messages={'required': 'State field is required.'}, - ) + category = forms.ChoiceField(choices=[('', 'Select a Category'), ] + list(TutorialResources.objects.filter( + Q(status=1) | Q(status=2), language__name='English',tutorial_detail__foss__show_on_homepage=1).values_list('tutorial_detail__foss__foss', + 'tutorial_detail__foss__foss').distinct()), + widget=forms.Select(attrs={}), required=True, error_messages={'required': 'State field is required.'}) title = forms.CharField(max_length=200) body = forms.CharField(widget=forms.Textarea()) def __init__(self, *args, **kwargs): - # Values that can be passed explicitly (e.g. from the spoken website) category = kwargs.pop('category', None) selecttutorial = kwargs.pop('tutorial', None) + select_min = kwargs.pop('minute_range', None) select_sec = kwargs.pop('second_range', None) - super(NewQuestionForm, self).__init__(*args, **kwargs) - self.fields['category'].choices = _get_category_choices() tutorial_choices = ( ("Select a Tutorial", "Select a Tutorial"), ) - - # If no explicit minute/second values were provided (e.g. normal POST), - # preserve any values that came from submitted form data so that - # validation errors (like missing reCAPTCHA) do not wipe them out. - data = args[0] if args else {} - if select_min is None and data and 'minute_range' in data: - select_min = data.get('minute_range') - if select_sec is None and data and 'second_range' in data: - select_sec = data.get('second_range') - - # When a minute/second value is available (from the spoken website or a - # previous form submission), show that value as the only selectable - # option; otherwise show the default placeholders. - if select_min: + # check minute_range, secpnd_range coming from spoken website + # user clicks on post question link through website + if (select_min is None and select_sec is None): minutes = ( (select_min, select_min), ) - else: - minutes = ( - ("", "min"), - ) - - if select_sec: seconds = ( (select_sec, select_sec), ) else: + minutes = ( + ("", "min"), + ) seconds = ( ("", "sec"), ) @@ -83,9 +48,7 @@ def __init__(self, *args, **kwargs): category = args[0]['category'] if FossCategory.objects.filter(foss=category).exists(): self.fields['category'].initial = category - tutorials = TutorialDetails.objects.using('spoken').filter( - foss__foss=category - ).order_by('level', 'order') + tutorials = TutorialDetails.objects.using('spoken').filter(foss__foss=category) for tutorial in tutorials: tutorial_choices += ((tutorial.tutorial, tutorial.tutorial),) self.fields['tutorial'] = forms.CharField(widget=forms.Select(choices=tutorial_choices)) diff --git a/website/views.py b/website/views.py index e4ad525..831ab09 100755 --- a/website/views.py +++ b/website/views.py @@ -138,43 +138,41 @@ def home(request): show_spam_list = is_administrator(request.user) or is_forumsadmin(request.user) if show_spam_list: spam_questions_full = _get_home_spam_questions(base_queryset) - # spam paginator spam_paginator = Paginator(spam_questions_full, 10) spam_page_number = request.GET.get('spam_page') spam_questions = spam_paginator.get_page(spam_page_number) - # paginate active questions recent_paginator = Paginator(questions_full, 10) active_paginator = Paginator(active_questions_full, 10) - recent_page_number = request.GET.get('recent_page') active_page_number = request.GET.get('active_page') - questions = recent_paginator.get_page(recent_page_number) active_questions = active_paginator.get_page(active_page_number) - + if spam_questions is None: all_questions = list(questions.object_list) + list(active_questions.object_list) + list(slider_questions) else: - all_questions = list(questions.object_list) + list(active_questions.object_list) + list(slider_questions) + list(spam_questions.object_list) + all_questions = ( + list(questions.object_list) + list(active_questions.object_list) + + list(slider_questions) + list(spam_questions.object_list) + ) uids = set() for q in all_questions: uids.add(q.uid) if q.last_post_by: uids.add(q.last_post_by) - + users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - - # Attach usernames to question objects so templates don't trigger queries + for q in all_questions: q.cached_user = users.get(q.uid, "Unknown User") q.cached_last_post_user = users.get(q.last_post_by, "Unknown User") if q.last_post_by else "Unknown User" categories = _get_home_categories() category_question_map = _get_home_category_question_map(categories, slider_questions) - + context = { 'questions': questions, 'active_questions': active_questions, @@ -187,7 +185,8 @@ def home(request): def questions(request): - questions = Question.objects.filter(status=1).annotate(total_answers=Count('answer')).order_by('category', 'tutorial') + questions = Question.objects.filter(status=1).order_by('category', 'tutorial') + questions = questions.annotate(total_answers=Count('answer')) raw_get_data = request.GET.get('o', None) @@ -213,22 +212,16 @@ def questions(request): questions = paginator.page(1) except EmptyPage: questions = paginator.page(paginator.num_pages) - # Attach cached usernames to avoid N+1 in templates - uids = {q.uid for q in questions} - users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - for q in questions: - q.cached_user = users.get(q.uid, "Unknown User") - context = { 'questions': questions, 'header': header, - 'ordering': ordering, - } + 'ordering': ordering + } return render(request, 'website/templates/questions.html', context) def hidden_questions(request): - questions = Question.objects.filter(status=0).annotate(total_answers=Count('answer')).order_by('-date_created') + questions = Question.objects.filter(status=0).order_by('date_created').reverse() paginator = Paginator(questions, 20) page = request.GET.get('page') @@ -238,14 +231,8 @@ def hidden_questions(request): questions = paginator.page(1) except EmptyPage: questions = paginator.page(paginator.num_pages) - # Attach cached usernames similar to questions() view - uids = {q.uid for q in questions} - users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - for q in questions: - q.cached_user = users.get(q.uid, "Unknown User") - context = { - 'questions': questions, + 'questions': questions } return render(request, 'website/templates/questions.html', context) @@ -256,39 +243,19 @@ def get_question(request, question_id=None, pretty_url=None): category = FossCategory.objects.all().order_by('foss') if pretty_url != pretty_title: return HttpResponseRedirect('/question/' + question_id + '/' + pretty_title) - # Prefetch answers and their comments to avoid N+1 queries - answers = ( - question.answer_set.all() - .prefetch_related('answercomment_set') - .order_by('date_created') - ) + answers = question.answer_set.all() form = AnswerQuesitionForm() if question.status in (0,2): label = "Show" else: label = "Hide" - # Cache usernames for question, answers and comments - uids = {question.uid} - for answer in answers: - uids.add(answer.uid) - for comment in answer.answercomment_set.all(): - uids.add(comment.uid) - - users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - - question.cached_user = users.get(question.uid, "Unknown User") - for answer in answers: - answer.cached_user = users.get(answer.uid, "Unknown User") - for comment in answer.answercomment_set.all(): - comment.cached_user = users.get(comment.uid, "Unknown User") - context = { 'question': question, 'answers': answers, 'category': category, 'form': form, - 'label': label, + 'label': label } user_has_role = has_role(request.user) context['require_recaptcha'] = not user_has_role @@ -573,8 +540,17 @@ def new_question(request): if not recaptcha_response: messages.error(request, "Please complete the reCAPTCHA verification.") - form = NewQuestionForm(request.POST) + _cat = request.POST.get('category') + _tut = request.POST.get('tutorial') + _min = request.POST.get('minute_range') or None + _sec = request.POST.get('second_range') or None + form = NewQuestionForm(request.POST, category=_cat, tutorial=_tut, + minute_range=_min, second_range=_sec) context['form'] = form + context['category'] = _cat + context['tutorial'] = _tut + context['minute_range'] = _min + context['second_range'] = _sec context['recaptcha_site_key'] = settings.RECAPTCHA_SITE_KEY context['require_recaptcha'] = True context.update(csrf(request)) @@ -593,8 +569,17 @@ def new_question(request): recaptcha_json = recaptcha_result.json() except requests.RequestException as e: messages.error(request, "Error verifying reCAPTCHA. Please try again.") - form = NewQuestionForm(request.POST) + _cat = request.POST.get('category') + _tut = request.POST.get('tutorial') + _min = request.POST.get('minute_range') or None + _sec = request.POST.get('second_range') or None + form = NewQuestionForm(request.POST, category=_cat, tutorial=_tut, + minute_range=_min, second_range=_sec) context['form'] = form + context['category'] = _cat + context['tutorial'] = _tut + context['minute_range'] = _min + context['second_range'] = _sec context['recaptcha_site_key'] = settings.RECAPTCHA_SITE_KEY context['require_recaptcha'] = True context.update(csrf(request)) @@ -603,8 +588,17 @@ def new_question(request): # check if verification was successful if not recaptcha_json.get('success', False): messages.error(request, "reCAPTCHA verification failed. Please try again.") - form = NewQuestionForm(request.POST) + _cat = request.POST.get('category') + _tut = request.POST.get('tutorial') + _min = request.POST.get('minute_range') or None + _sec = request.POST.get('second_range') or None + form = NewQuestionForm(request.POST, category=_cat, tutorial=_tut, + minute_range=_min, second_range=_sec) context['form'] = form + context['category'] = _cat + context['tutorial'] = _tut + context['minute_range'] = _min + context['second_range'] = _sec context['recaptcha_site_key'] = settings.RECAPTCHA_SITE_KEY context['require_recaptcha'] = True context.update(csrf(request)) @@ -649,7 +643,17 @@ def new_question(request): return HttpResponseRedirect('/') # If form not valid -> re-render with errors + _cat = request.POST.get('category') + _tut = request.POST.get('tutorial') + _min = request.POST.get('minute_range') or None + _sec = request.POST.get('second_range') or None + form = NewQuestionForm(request.POST, category=_cat, tutorial=_tut, + minute_range=_min, second_range=_sec) context['form'] = form + context['category'] = _cat + context['tutorial'] = _tut + context['minute_range'] = _min + context['second_range'] = _sec context['recaptcha_site_key'] = settings.RECAPTCHA_SITE_KEY # check if user needs to complete captcha user_has_role = request.user.is_authenticated and request.user.groups.exists() @@ -708,11 +712,7 @@ def user_answers(request, user_id): if str(user_id) == str(request.user.id): total = Answer.objects.filter(uid=user_id).count() total = int(total - (total % 10 - 10)) - answers = ( - Answer.objects.filter(uid=user_id) - .select_related('question') - .order_by('-date_created')[marker:marker + 10] - ) + answers = Answer.objects.filter(uid=user_id).order_by('date_created').reverse()[marker:marker + 10] context = { 'answers': answers, 'total': total, @@ -725,26 +725,9 @@ def user_answers(request, user_id): @login_required def user_notifications(request, user_id): if str(user_id) == str(request.user.id): - notifications = list( - Notification.objects.filter(uid=user_id).order_by('-date_created') - ) - - # Prefetch related questions, answers and posters in bulk - qids = {n.qid for n in notifications if n.qid} - aids = {n.aid for n in notifications if n.aid} - pids = {n.pid for n in notifications if n.pid} - - questions = {q.id: q for q in Question.objects.filter(id__in=qids)} - answers = {a.id: a for a in Answer.objects.filter(id__in=aids)} - users = {u.id: u.username for u in User.objects.filter(id__in=pids)} - - for n in notifications: - n.cached_question = questions.get(n.qid) - n.cached_answer = answers.get(n.aid) - n.cached_poster = users.get(n.pid, "Unknown User") - + notifications = Notification.objects.filter(uid=user_id).order_by('date_created').reverse() context = { - 'notifications': notifications, + 'notifications': notifications } return render(request, 'website/templates/notifications.html', context) return HttpResponse("go away ...") @@ -908,22 +891,13 @@ def ajax_similar_questions(request): user_title = clean_user_data(title) # Increase the threshold as the Forums questions increase THRESHOLD = 0.3 - MAX_CANDIDATES = 200 - MAX_RESULTS = 20 - top_ques = [] - # Limit number of candidate questions to keep CPU usage bounded - questions = Question.objects.filter( - category=category, - tutorial=tutorial, - ).order_by('-date_created')[:MAX_CANDIDATES] - + questions = Question.objects.filter(category=category,tutorial=tutorial) for question in questions: - question.similarity = get_similar_questions(user_title, question.title) - if question.similarity >= THRESHOLD: + question.similarity= get_similar_questions(user_title,question.title) + if question.similarity >= THRESHOLD: top_ques.append(question) - - top_ques = sorted(top_ques, key=lambda x: x.similarity, reverse=True)[:MAX_RESULTS] + top_ques = sorted(top_ques,key=lambda x : x.similarity, reverse=True) context = { 'questions': top_ques, 'questions_count':len(top_ques) @@ -976,23 +950,18 @@ def ajax_keyword_search(request): questions = Question.objects.filter( Q(title__icontains=key) | Q(category__icontains=key) | Q(tutorial__icontains=key) | Q(body__icontains=key), status=1 - ).annotate(total_answers=Count('answer')).order_by('-date_created') - + ).order_by('-date_created') paginator = Paginator(questions, 20) page = request.POST.get('page') + if page: + page = int(request.POST.get('page')) + questions = paginator.page(page) try: questions = paginator.page(page) except PageNotAnInteger: questions = paginator.page(1) except EmptyPage: questions = paginator.page(paginator.num_pages) - - # Attach cached usernames for the current page - uids = {q.uid for q in questions} - users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - for q in questions: - q.cached_user = users.get(q.uid, "Unknown User") - context = { 'questions': questions } @@ -1005,38 +974,19 @@ def ajax_time_search(request): tutorial = request.POST.get('tutorial') minute_range = request.POST.get('minute_range') second_range = request.POST.get('second_range') - questions = Question.objects.none() + questions = None if category: - questions = Question.objects.filter( - category=category.replace(' ', '-'), - status=1, - ) + questions = Question.objects.filter(category=category.replace(' ', '-'), status=1) if tutorial: questions = questions.filter(tutorial=tutorial.replace(' ', '-')) if minute_range: - questions = questions.filter(minute_range=minute_range) + questions = questions.filter(category=category.replace( + ' ', '-'), tutorial=tutorial.replace(' ', '-'), minute_range=minute_range) if second_range: - questions = questions.filter(second_range=second_range) - - questions = questions.annotate(total_answers=Count('answer')).order_by('-date_created') - - paginator = Paginator(questions, 20) - page = request.POST.get('page') - try: - questions = paginator.page(page) - except PageNotAnInteger: - questions = paginator.page(1) - except EmptyPage: - questions = paginator.page(paginator.num_pages) - - # Attach cached usernames for the current page - uids = {q.uid for q in questions} - users = {u.id: u.username for u in User.objects.filter(id__in=uids)} - for q in questions: - q.cached_user = users.get(q.uid, "Unknown User") - + questions = questions.filter(category=category.replace( + ' ', '-'), tutorial=tutorial.replace(' ', '-'), second_range=second_range) context = { - 'questions': questions, + 'questions': questions } return render(request, 'website/templates/ajax-time-search.html', context) @@ -1061,30 +1011,27 @@ def forums_mail(to='', subject='', message=''): def unanswered_notification(request): - unanswered_questions = ( - Question.objects.filter(status=1) - .annotate(answer_count=Count('answer')) - .filter(answer_count=0) - ) + questions = Question.objects.filter(status=1) total_count = 0 message = """ The following questions are left unanswered. Please take a look at them.

""" - for question in unanswered_questions: - total_count += 1 - message += """ - #{0}
- Title: {1}
- Category: {2}
- Link: {3}
-
- """.format( - total_count, - question.title, - question.category, - 'http://forums.spoken-tutorial.org/question/' + str(question.id) - ) + for question in questions: + if not question.answer_set.count(): + total_count += 1 + message += """ + #{0}
+ Title: {1}
+ Category: {2}
+ Link: {3}
+
+ """.format( + total_count, + question.title, + question.category, + 'http://forums.spoken-tutorial.org/question/' + str(question.id) + ) to = "team@spoken-tutorial.org, team@fossee.in" subject = "Unanswered questions in the forums." if total_count: