Require authentication to set keywords, fixes #365
[oweals/karmaworld.git] / karmaworld / apps / quizzes / views.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2014  FinalsClub Foundation
4 from itertools import chain
5 import json
6 from django.core.exceptions import ObjectDoesNotExist
7 from django.core.urlresolvers import reverse
8 from django.forms.formsets import formset_factory
9 from django.http import HttpResponseRedirect, HttpResponseBadRequest, HttpResponseNotFound, HttpResponse
10
11 from django.views.generic import DetailView, FormView
12 from django.views.generic.detail import SingleObjectMixin
13 from django.views.generic.edit import FormMixin, ProcessFormView
14 from karmaworld.apps.notes.models import Note
15 from karmaworld.apps.quizzes.forms import KeywordForm
16 from karmaworld.apps.quizzes.models import Quiz, ALL_QUESTION_CLASSES, Keyword, BaseQuizQuestion, \
17     ALL_QUESTION_CLASSES_NAMES, MultipleChoiceQuestion, MultipleChoiceOption, TrueFalseQuestion, FlashCardQuestion
18 from karmaworld.utils import ajax_pk_base, ajax_base
19
20
21 class QuizView(DetailView):
22     queryset = Quiz.objects.all()
23     model = Quiz
24     context_object_name = 'quiz'  # name passed to template
25     template_name = 'quizzes/quiz.html'
26
27     def get_context_data(self, **kwargs):
28         all_questions = []
29         for cls in ALL_QUESTION_CLASSES:
30             all_questions.append(
31                 [(cls.__name__, o) for o in cls.objects.filter(quiz=self.object)]
32             )
33
34         result_list = sorted(chain.from_iterable(all_questions),
35                              key=lambda o: o[1].timestamp,
36                              reverse=True)
37
38         kwargs['questions'] = result_list
39         kwargs['quiz'] = self.object
40
41         return super(QuizView, self).get_context_data(**kwargs)
42
43
44 class KeywordEditView(FormView):
45     template_name = 'quizzes/keyword_edit.html'
46     form_class = formset_factory(KeywordForm)
47
48     def get(self, requests, *args, **kwargs):
49         self.lookup_note()
50         return super(KeywordEditView, self).get(requests, *args, **kwargs)
51
52     def post(self, requests, *args, **kwargs):
53         self.lookup_note()
54         return super(KeywordEditView, self).post(requests, *args, **kwargs)
55
56     def lookup_note(self):
57         self.note = Note.objects.get(slug=self.kwargs['slug'])
58
59     def get_success_url(self):
60         return reverse('keyword_edit', args=(self.note.course.school.slug, self.note.course.slug, self.note.slug))
61
62     def get_initial(self):
63         existing_keywords = self.note.keyword_set.order_by('id')
64         initial_data = [{'keyword': keyword.word, 'definition': keyword.definition, 'id': keyword.pk}
65                         for keyword in existing_keywords]
66         return initial_data
67
68     def get_context_data(self, **kwargs):
69         kwargs['note'] = self.note
70         kwargs['prototype_form'] = KeywordForm
71         return super(KeywordEditView, self).get_context_data(**kwargs)
72
73     def form_valid(self, formset):
74         for form in formset:
75             word = form['keyword'].data
76             definition = form['definition'].data
77             id = form['id'].data
78             if word == '':
79                 continue
80             try:
81                 keyword_object = Keyword.objects.get(id=id)
82             except (ValueError, ObjectDoesNotExist):
83                 keyword_object = Keyword()
84
85             keyword_object.note = self.note
86             keyword_object.word = word
87             keyword_object.definition = definition
88             keyword_object.save()
89
90         return super(KeywordEditView, self).form_valid(formset)
91
92
93 def quiz_answer(request):
94     """Handle an AJAX request checking if a quiz answer is correct"""
95     if not (request.method == 'POST' and request.is_ajax()):
96         # return that the api call failed
97         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'must be a POST ajax request'}),
98                                       mimetype="application/json")
99
100     try:
101         question_type_str = request.POST['question_type']
102         if question_type_str not in ALL_QUESTION_CLASSES_NAMES:
103             raise Exception("Not a valid question type")
104         question_type_class = ALL_QUESTION_CLASSES_NAMES[question_type_str]
105         question = question_type_class.objects.get(id=request.POST['id'])
106
107         correct = False
108
109         if question_type_class is MultipleChoiceQuestion:
110             answer = MultipleChoiceOption.objects.get(id=request.POST['answer'])
111             if answer.question == question and answer.correct:
112                 correct = True
113
114         elif question_type_class is TrueFalseQuestion:
115             answer = request.POST['answer'] == 'true'
116             correct = question.true == answer
117
118         elif question_type_class is FlashCardQuestion:
119             answer = request.POST['answer'].lower()
120             correct = question.keyword_side.lower() == answer
121
122     except Exception, e:
123         return HttpResponseBadRequest(json.dumps({'status': 'fail',
124                                                   'message': e.message,
125                                                   'exception': e.__class__.__name__}),
126                                       mimetype="application/json")
127
128     return HttpResponse(json.dumps({'correct': correct}), mimetype="application/json")
129
130
131 def set_keyword(annotation_uri, keyword, definition, ranges):
132     try:
133         keyword = Keyword.objects.get(note_id=annotation_uri, word=keyword, ranges=ranges)
134         keyword.definition = definition
135         keyword.save()
136     except ObjectDoesNotExist:
137         Keyword.objects.create(note_id=annotation_uri, word=keyword, definition=definition, ranges=ranges)
138
139
140 def delete_keyword(annotation_uri, keyword, definition, ranges):
141     keyword = Keyword.objects.get(note_id=annotation_uri, word=keyword, definition=definition, ranges=ranges)
142     keyword.definition = definition
143     keyword.delete()
144
145
146 def process_set_delete_keyword(request):
147     annotator_data = json.loads(request.raw_post_data)
148     annotation_uri = annotator_data['uri']
149     keyword = annotator_data['quote']
150     definition = annotator_data['text']
151     ranges = json.dumps(annotator_data['ranges'])
152
153     if not request.user.is_authenticated():
154         return HttpResponseForbidden(json.dumps({'status': 'fail', 'message': "Only authenticated users may set keywords"}),
155                                      mimetype="application/json")
156
157     try:
158         if request.method in ('POST', 'PUT'):
159             set_keyword(annotation_uri, keyword, definition, ranges)
160         elif request.method == 'DELETE':
161             delete_keyword(annotation_uri, keyword, definition, ranges)
162     except Exception, e:
163         return HttpResponseNotFound(json.dumps({'status': 'fail', 'message': e.message}),
164                                     mimetype="application/json")
165
166
167 def set_delete_keyword_annotator(request):
168     return ajax_base(request, process_set_delete_keyword, ('POST', 'PUT', 'DELETE'))
169
170
171 def get_keywords_annotator(request):
172     annotation_uri = request.GET['uri']
173
174     try:
175         keywords = Keyword.objects.filter(note_id=annotation_uri).exclude(ranges=None)
176         keywords_data = {
177             'total': len(keywords),
178             'rows': []
179         }
180         for keyword in keywords:
181             keyword_data = {
182                 'quote': keyword.word,
183                 'text': keyword.definition,
184                 'ranges': json.loads(keyword.ranges),
185                 'created': keyword.timestamp.isoformat(),
186             }
187             keywords_data['rows'].append(keyword_data)
188
189         return HttpResponse(json.dumps(keywords_data), mimetype='application/json')
190
191     except ObjectDoesNotExist, e:
192         return HttpResponseNotFound(json.dumps({'status': 'fail', 'message': e.message}),
193                                     mimetype="application/json")
194
195
196 def get_keywords_datatables(request):
197     annotation_uri = request.GET['uri']
198
199     try:
200         keywords = Keyword.objects.filter(note_id=annotation_uri)
201         keywords_data = {
202             'total': len(keywords),
203             'rows': []
204         }
205         for keyword in keywords:
206             keyword_data = {
207                 'quote': keyword.word,
208                 'text': keyword.definition,
209                 'ranges': json.loads(keyword.ranges),
210                 'created': keyword.timestamp.isoformat(),
211             }
212             keywords_data['rows'].append(keyword_data)
213
214         return HttpResponse(json.dumps(keywords_data), mimetype='application/json')
215
216     except ObjectDoesNotExist, e:
217         return HttpResponseNotFound(json.dumps({'status': 'fail', 'message': e.message}),
218                                     mimetype="application/json")
219