return self.text
ALL_QUESTION_CLASSES = (MultipleChoiceQuestion, FlashCardQuestion, TrueFalseQuestion)
+ALL_QUESTION_CLASSES_NAMES = {
+ MultipleChoiceQuestion.__name__: MultipleChoiceQuestion,
+ FlashCardQuestion.__name__: FlashCardQuestion,
+ TrueFalseQuestion.__name__: TrueFalseQuestion,
+}
# -*- 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):
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
#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
--- /dev/null
+
+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);
+});
+
+
+
-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
{% endblock %}
{% block bodyscripts %}
- <script src="{{ STATIC_URL }}js/quiz.js"></script>
+ <script src="{{ STATIC_URL }}js/keyword.js"></script>
{% endblock %}
{% block title %}
<link rel="stylesheet" type="text/css" media="all" href="{{ STATIC_URL }}css/quiz.css">
{% endblock %}
+{% block bodyscripts %}
+ <script>
+ var csrf_token = "{{ csrf_token }}";
+ var num_quiz_questions = {{ questions|length }};
+ var current_question_index = 0;
+ var check_answer_url = '{% url 'quiz_answer' %}';
+ </script>
+ <script src="{{ STATIC_URL }}js/setup-ajax.js"></script>
+ <script src="{{ STATIC_URL }}js/quiz.js"></script>
+{% endblock %}
+
{% block title %}
{{ quiz.name }}
{% endblock %}
</div>
{% endif %}
- <form action="{{ quiz.get_absolute_url }}" method="post">
- {% csrf_token %}
- {% for item in questions %}
-
- <div class="row">
- <div class="small-10 columns small-centered quiz-center">
+ {% for item in questions %}
+ {% with question=item.1 %}
+ <div class="row hide question"
+ data-question-id="{{ question.id }}"
+ data-question-index="{{ forloop.counter0 }}"
+ data-question-type="{{ item.0 }}">
+ <div class="small-10 large-6 columns small-centered">
{% if 'MultipleChoiceQuestion' in item.0 %}
- {% with question=item.1 %}
- <div class="question multiple-choice">
- <p>{{ question.question_text }}</p>
- <ul>
- {% for choice in question.choices.all %}
- <li><input id="choice_{{ question.id }}_{{ choice.id }}"
- type="radio"
- name="{{ question.id }}"
- value="{{ choice.id }}">
- <label for="choice_{{ question.id }}_{{ choice.id }}">{{ choice.text }}</label></li>
- {% endfor %}
- </ul>
- </div>
- {% endwith %}
- {% endif %}
-
- {% if 'TrueFalseQuestion' in item.0 %}
- {% with question=item.1 %}
- <div class="question true-false">
- <p>{{ question.text }}</p>
- <li><input id="choice_{{ question.id }}_true"
+ <p class="question-text">{{ question.question_text }}</p>
+ <ul>
+ {% for choice in question.choices.all %}
+ <li><input id="choice_{{ question.id }}_{{ choice.id }}"
type="radio"
name="{{ question.id }}"
- value="true">
- <label for="choice_{{ question.id }}_true">True</label></li>
- <li><input id="choice_{{ question.id }}_false"
- type="radio"
- name="{{ question.id }}"
- value="false">
- <label for="choice_{{ question.id }}_false">False</label></li>
- </div>
- {% endwith %}
+ value="{{ choice.id }}"
+ data-choice-id="{{ choice.id }}">
+ <label for="choice_{{ question.id }}_{{ choice.id }}">{{ choice.text }}</label></li>
+ {% endfor %}
+ </ul>
{% endif %}
- {% if 'FlashCardQuestion' in item.0 %}
- {% with question=item.1 %}
- <div class="question flash-card">
- <p>{{ question.definition_side }}</p>
- <input type="text"
+ {% if 'TrueFalseQuestion' in item.0 %}
+ <p class="question-text">{{ question.text }}</p>
+ <li><input id="choice_{{ question.id }}_true"
+ type="radio"
+ name="{{ question.id }}"
+ value="true">
+ <label for="choice_{{ question.id }}_true">True</label></li>
+ <li><input id="choice_{{ question.id }}_false"
+ type="radio"
name="{{ question.id }}"
- id="text_{{ question.id }}">
+ value="false">
+ <label for="choice_{{ question.id }}_false">False</label></li>
+ {% endif %}
- </div>
- {% endwith %}
+ {% if 'FlashCardQuestion' in item.0 %}
+ <p class="question-text">{{ question.definition_side }}</p>
+ <input type="text"
+ name="{{ question.id }}"
+ id="text_{{ question.id }}">
{% endif %}
+
+ <div class="row">
+ <div class="small-4 columns">
+ <button class="prev-question {% if forloop.first %}disabled{% endif %}">Previous</button>
+ </div>
+ <div class="small-4 columns">
+ <button class="check-answer">Check Answer</button>
+ </div>
+ <div class="small-4 columns">
+ <button class="next-question {% if forloop.last %}disabled{% endif %}">Next</button>
+ </div>
+ </div>
+
</div>
</div>
- <br/>
- {% endfor %}
- <div class="row">
- <div class="small-12 columns center">
- <button type="submit">Submit</button>
- </div>
- </div>
- </form>
+ {% endwith %}
+ {% endfor %}
</div>
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
# Ajax endpoint to edit a course
url(r'^ajax/course/edit/(?P<pk>[\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