From 872685ca45d25fcc04a3d063e7a39657a6bf4746 Mon Sep 17 00:00:00 2001 From: Charles Connell Date: Sat, 1 Mar 2014 10:46:54 -0500 Subject: [PATCH] Working quiz taking interface --- karmaworld/apps/quizzes/models.py | 5 + karmaworld/apps/quizzes/views.py | 44 ++++++- karmaworld/assets/css/quiz.css | 20 ++++ karmaworld/assets/js/keyword.js | 41 +++++++ karmaworld/assets/js/quiz.js | 99 +++++++++++----- .../templates/quizzes/keyword_edit.html | 2 +- karmaworld/templates/quizzes/quiz.html | 111 ++++++++++-------- karmaworld/urls.py | 5 +- 8 files changed, 242 insertions(+), 85 deletions(-) create mode 100644 karmaworld/assets/js/keyword.js diff --git a/karmaworld/apps/quizzes/models.py b/karmaworld/apps/quizzes/models.py index cd161b5..76753d7 100644 --- a/karmaworld/apps/quizzes/models.py +++ b/karmaworld/apps/quizzes/models.py @@ -108,4 +108,9 @@ class TrueFalseQuestion(BaseQuizQuestion): return self.text ALL_QUESTION_CLASSES = (MultipleChoiceQuestion, FlashCardQuestion, TrueFalseQuestion) +ALL_QUESTION_CLASSES_NAMES = { + MultipleChoiceQuestion.__name__: MultipleChoiceQuestion, + FlashCardQuestion.__name__: FlashCardQuestion, + TrueFalseQuestion.__name__: TrueFalseQuestion, +} diff --git a/karmaworld/apps/quizzes/views.py b/karmaworld/apps/quizzes/views.py index 6b5294c..c67205d 100644 --- a/karmaworld/apps/quizzes/views.py +++ b/karmaworld/apps/quizzes/views.py @@ -2,17 +2,20 @@ # -*- coding:utf8 -*- # Copyright (C) 2014 FinalsClub Foundation from itertools import chain +import json from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.forms.formsets import formset_factory -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponseNotFound, HttpResponse from django.views.generic import DetailView, FormView from django.views.generic.detail import SingleObjectMixin from django.views.generic.edit import FormMixin, ProcessFormView from karmaworld.apps.notes.models import Note from karmaworld.apps.quizzes.forms import KeywordForm -from karmaworld.apps.quizzes.models import Quiz, ALL_QUESTION_CLASSES, Keyword +from karmaworld.apps.quizzes.models import Quiz, ALL_QUESTION_CLASSES, Keyword, BaseQuizQuestion, \ + ALL_QUESTION_CLASSES_NAMES, MultipleChoiceQuestion, MultipleChoiceOption, TrueFalseQuestion, FlashCardQuestion +from karmaworld.utils import ajax_base class QuizView(DetailView): @@ -85,3 +88,40 @@ class KeywordEditView(FormView): return super(KeywordEditView, self).form_valid(formset) + +def quiz_answer(request): + """Handle an AJAX request checking if a quiz answer is correct""" + if not (request.method == 'POST' and request.is_ajax()): + # return that the api call failed + return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'must be a POST ajax request'}), + mimetype="application/json") + + try: + question_type_str = request.POST['question_type'] + if question_type_str not in ALL_QUESTION_CLASSES_NAMES: + raise Exception("Not a valid question type") + question_type_class = ALL_QUESTION_CLASSES_NAMES[question_type_str] + question = question_type_class.objects.get(id=request.POST['id']) + + correct = False + + if question_type_class is MultipleChoiceQuestion: + answer = MultipleChoiceOption.objects.get(id=request.POST['answer']) + if answer.question == question and answer.correct: + correct = True + + elif question_type_class is TrueFalseQuestion: + answer = request.POST['answer'] == 'true' + correct = question.true == answer + + elif question_type_class is FlashCardQuestion: + answer = request.POST['answer'].lower() + correct = question.keyword_side.lower() == answer + + except Exception, e: + return HttpResponseBadRequest(json.dumps({'status': 'fail', + 'message': e.message, + 'exception': e.__class__.__name__}), + mimetype="application/json") + + return HttpResponse(json.dumps({'correct': correct}), mimetype="application/json") \ No newline at end of file diff --git a/karmaworld/assets/css/quiz.css b/karmaworld/assets/css/quiz.css index 946431a..4b51e87 100644 --- a/karmaworld/assets/css/quiz.css +++ b/karmaworld/assets/css/quiz.css @@ -78,4 +78,24 @@ #add-row-btn { cursor: pointer; +} + +.correct-answer-flash +{ + background-color: #67ff50; +} + +.correct-answer +{ + background-color: #b5ffaa; +} + +.wrong-answer-flash +{ + background-color: #ff3a3a; +} + +.wrong-answer +{ + background-color: #ffb4b4; } \ No newline at end of file diff --git a/karmaworld/assets/js/keyword.js b/karmaworld/assets/js/keyword.js new file mode 100644 index 0000000..6f0e6f1 --- /dev/null +++ b/karmaworld/assets/js/keyword.js @@ -0,0 +1,41 @@ + +function tabHandler(event) { + // check for: + // key pressed was TAB + // key was pressed in last row + if (event.which == 9 && + (!$(this).closest('div.keyword-form-row').next().hasClass('keyword-form-row'))) { + addForm(event); + } +} + +function addForm(event) { + var prototypeForm = $('#keyword-form-prototype div.keyword-form-row').clone().appendTo('#keyword-form-rows'); + var newForm = $('.keyword-form-row:last'); + var totalForms = $('#id_form-TOTAL_FORMS').attr('value'); + var newIdRoot = 'id_form-' + totalForms + '-'; + var newNameRoot = 'form-' + totalForms + '-'; + + var keywordInput = newForm.find('.keyword'); + keywordInput.attr('id', newIdRoot + 'keyword'); + keywordInput.attr('name', newNameRoot + 'keyword'); + + var definitionInput = newForm.find('.definition'); + definitionInput.attr('id', newIdRoot + 'definition'); + definitionInput.attr('name', newNameRoot + 'definition'); + definitionInput.keydown(tabHandler); + + var objectIdInput = newForm.find('.object-id'); + objectIdInput.attr('id', newIdRoot + 'id'); + objectIdInput.attr('name', newNameRoot + 'id'); + + $('#id_form-TOTAL_FORMS').attr('value', parseInt(totalForms)+1); +} + +$(function() { + $('.definition').keydown(tabHandler); + $('#add-row-btn').click(addForm); +}); + + + diff --git a/karmaworld/assets/js/quiz.js b/karmaworld/assets/js/quiz.js index fb86301..f78f4dc 100644 --- a/karmaworld/assets/js/quiz.js +++ b/karmaworld/assets/js/quiz.js @@ -1,39 +1,80 @@ -function tabHandler(event) { - // check for: - // key pressed was TAB - // key was pressed in last row - if (event.which == 9 && - (!$(this).closest('div.keyword-form-row').next().hasClass('keyword-form-row'))) { - addForm(event); + +function showQuestion() { + $('div.question').hide(); + $('div[data-question-index="' + current_question_index + '"]').show(); +} + +function nextQuestion() { + if (current_question_index+1 < num_quiz_questions) { + current_question_index += 1; + showQuestion(); } } -function addForm(event) { - var prototypeForm = $('#keyword-form-prototype div.keyword-form-row').clone().appendTo('#keyword-form-rows'); - var newForm = $('.keyword-form-row:last'); - var totalForms = $('#id_form-TOTAL_FORMS').attr('value'); - var newIdRoot = 'id_form-' + totalForms + '-'; - var newNameRoot = 'form-' + totalForms + '-'; +function prevQuestion() { + if (current_question_index-1 >= 0) { + current_question_index -= 1; + showQuestion(); + } +} - var keywordInput = newForm.find('.keyword'); - keywordInput.attr('id', newIdRoot + 'keyword'); - keywordInput.attr('name', newNameRoot + 'keyword'); +function checkAnswerCallback(data, textStatus, jqXHR) { + var question = $('div[data-question-index="' + current_question_index + '"]'); + var question_text = question.find('p.question-text'); + if (data.correct == true) { + question_text.removeClass('wrong-answer'); + question_text.removeClass('correct-answer'); + question_text.addClass('correct-answer-flash'); + question_text.switchClass('correct-answer-flash', 'correct-answer',1000); + } + if (data.correct == false) { + question_text.removeClass('wrong-answer'); + question_text.removeClass('correct-answer'); + question_text.addClass('wrong-answer-flash'); + question_text.switchClass('wrong-answer-flash', 'wrong-answer',1000); + } +} - var definitionInput = newForm.find('.definition'); - definitionInput.attr('id', newIdRoot + 'definition'); - definitionInput.attr('name', newNameRoot + 'definition'); - definitionInput.keydown(tabHandler); +function checkAnswer() { + var question = $('div[data-question-index="' + current_question_index + '"]'); + var question_id = question.attr('data-question-id'); + var question_type = question.attr('data-question-type'); - var objectIdInput = newForm.find('.object-id'); - objectIdInput.attr('id', newIdRoot + 'id'); - objectIdInput.attr('name', newNameRoot + 'id'); + var chosen_answer = null; - $('#id_form-TOTAL_FORMS').attr('value', parseInt(totalForms)+1); -} + if (question_type == 'MultipleChoiceQuestion') { + var checked = question.find('input:checked'); + if (checked.length == 1) { + chosen_answer = checked[0].getAttribute('data-choice-id'); + } + } + + else if (question_type == 'FlashCardQuestion') { + chosen_answer = question.find('input').val(); + } -$(function() { - $('.definition').keydown(tabHandler); - $('#add-row-btn').click(addForm); -}); + else if (question_type == 'TrueFalseQuestion') { + var checked = question.find('input:checked'); + if (checked.attr('value') == 'true') { + chosen_answer = true; + } + else if (checked.attr('value') == 'false') { + chosen_answer = false; + } + } + + message = { + question_type: question_type, + id: question_id, + answer: chosen_answer + }; + $.post(check_answer_url, message, checkAnswerCallback); +} +$(function () { + showQuestion(); + $('button.check-answer').click(checkAnswer); + $('button.prev-question').click(prevQuestion); + $('button.next-question').click(nextQuestion); +}); \ No newline at end of file diff --git a/karmaworld/templates/quizzes/keyword_edit.html b/karmaworld/templates/quizzes/keyword_edit.html index eb8af25..52b3d43 100644 --- a/karmaworld/templates/quizzes/keyword_edit.html +++ b/karmaworld/templates/quizzes/keyword_edit.html @@ -7,7 +7,7 @@ {% endblock %} {% block bodyscripts %} - + {% endblock %} {% block title %} diff --git a/karmaworld/templates/quizzes/quiz.html b/karmaworld/templates/quizzes/quiz.html index c691409..4b2ac84 100644 --- a/karmaworld/templates/quizzes/quiz.html +++ b/karmaworld/templates/quizzes/quiz.html @@ -6,6 +6,17 @@ {% endblock %} +{% block bodyscripts %} + + + +{% endblock %} + {% block title %} {{ quiz.name }} {% endblock %} @@ -59,68 +70,64 @@ {% endif %} -
- {% csrf_token %} - {% for item in questions %} - -
-
+ {% for item in questions %} + {% with question=item.1 %} +
+
{% if 'MultipleChoiceQuestion' in item.0 %} - {% with question=item.1 %} -
-

{{ question.question_text }}

-
    - {% for choice in question.choices.all %} -
  • -
  • - {% endfor %} -
-
- {% endwith %} - {% endif %} - - {% if 'TrueFalseQuestion' in item.0 %} - {% with question=item.1 %} -
-

{{ question.text }}

-
  • {{ question.question_text }}

    +
      + {% for choice in question.choices.all %} +
    • -
    • -
    • -
    • -
  • - {% endwith %} + value="{{ choice.id }}" + data-choice-id="{{ choice.id }}"> + + {% endfor %} + {% endif %} - {% if 'FlashCardQuestion' in item.0 %} - {% with question=item.1 %} -
    -

    {{ question.definition_side }}

    - {{ question.text }}

    +
  • +
  • +
  • + value="false"> +
  • + {% endif %} -
    - {% endwith %} + {% if 'FlashCardQuestion' in item.0 %} +

    {{ question.definition_side }}

    + {% endif %} + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    -
    - {% endfor %} -
    -
    - -
    -
    - + {% endwith %} + {% endfor %}
    diff --git a/karmaworld/urls.py b/karmaworld/urls.py index 0834cd8..6f2046b 100644 --- a/karmaworld/urls.py +++ b/karmaworld/urls.py @@ -19,7 +19,7 @@ from karmaworld.apps.notes.views import NoteView, thank_note, NoteSearchView, fl from karmaworld.apps.notes.views import RawNoteDetailView from karmaworld.apps.moderation import moderator from karmaworld.apps.document_upload.views import save_fp_upload -from karmaworld.apps.quizzes.views import QuizView, KeywordEditView +from karmaworld.apps.quizzes.views import QuizView, KeywordEditView, quiz_answer from karmaworld.apps.users.views import ProfileView # See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#hooking-adminsite-instances-into-your-urlconf @@ -102,6 +102,9 @@ urlpatterns = patterns('', # Ajax endpoint to edit a course url(r'^ajax/course/edit/(?P[\d]+)/$', edit_course, name='edit_course'), + # Check if a quiz answer is correct + url(r'^ajax/quiz/check/$', quiz_answer, name='quiz_answer'), + # Valid url cases to the Note page # a: school/course/id # b: school/course/id/slug -- 2.25.1