3 # Copyright (C) 2012 FinalsClub Foundation
7 from django.contrib import messages
9 from django.core import serializers
10 from django.core.exceptions import ValidationError
11 from django.forms.formsets import formset_factory
12 from karmaworld.apps.courses.forms import CourseForm
13 from karmaworld.apps.courses.models import Course
14 from karmaworld.apps.notes.search import SearchIndex
15 from karmaworld.apps.quizzes.create_quiz import quiz_from_keywords
16 from karmaworld.apps.quizzes.forms import KeywordForm
17 from karmaworld.apps.quizzes.models import Keyword
18 from karmaworld.apps.quizzes.tasks import submit_extract_keywords_hit, get_extract_keywords_results
19 from karmaworld.apps.users.models import NoteKarmaEvent
20 from karmaworld.utils.ajax_utils import *
22 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
23 from django.views.generic import DetailView, ListView, TemplateView
24 from django.views.generic import FormView
25 from django.views.generic import View
26 from django.views.generic.detail import SingleObjectMixin
28 from karmaworld.apps.notes.models import Note, KEYWORD_MTURK_THRESHOLD
29 from karmaworld.apps.notes.forms import NoteForm, NoteDeleteForm
32 logger = logging.getLogger(__name__)
34 THANKS_FIELD = 'thanks'
35 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
37 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
40 def note_page_context_helper(note, request, context):
42 if request.method == 'POST':
43 context['note_edit_form'] = NoteForm(request.POST)
45 tags_string = ','.join([str(tag) for tag in note.tags.all()])
46 context['note_edit_form'] = NoteForm(initial={'name': note.name,
49 context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
52 context['pdf_controls'] = True
54 if request.user.is_authenticated():
56 request.user.get_profile().thanked_notes.get(pk=note.pk)
57 context['already_thanked'] = True
58 except ObjectDoesNotExist:
62 request.user.get_profile().flagged_notes.get(pk=note.pk)
63 context['already_flagged'] = True
64 except ObjectDoesNotExist:
68 class NoteDetailView(DetailView):
69 """ Class-based view for the note html page """
71 context_object_name = u"note" # name passed to template
72 template_name = 'notes/note_base.html'
74 def get_context_data(self, **kwargs):
75 """ Generate custom context for the page rendering a Note
76 + if pdf, set the `pdf` flag
79 kwargs['show_note_container'] = True
81 note_page_context_helper(self.object, self.request, kwargs)
83 return super(NoteDetailView, self).get_context_data(**kwargs)
86 class NoteSaveView(FormView, SingleObjectMixin):
87 """ Save a Note and then view the page,
88 behaves the same as NoteDetailView, except for saving the
93 template_name = 'notes/note_base.html'
95 def post(self, request, *args, **kwargs):
96 self.object = self.get_object()
97 if self.object.user != request.user:
98 raise ValidationError("Only the owner of a note can edit it.")
99 return super(NoteSaveView, self).post(request, *args, **kwargs)
101 def get_success_url(self):
102 return self.object.get_absolute_url()
104 def get_context_data(self, **kwargs):
106 'object': self.get_object(),
108 return super(NoteSaveView, self).get_context_data(**context)
110 def form_valid(self, form):
111 """ Actions to take if the submitted form is valid
112 namely, saving the new data to the existing note object
114 if len(form.cleaned_data['name'].strip()) > 0:
115 self.object.name = form.cleaned_data['name']
116 # use *arg expansion to pass tags a list of tags
117 self.object.tags.set(*form.cleaned_data['tags'])
118 # User has submitted this form, so set the SHOW flag
119 self.object.is_hidden = False
121 return super(NoteSaveView, self).form_valid(form)
123 def form_invalid(self, form):
124 """ Do stuff when the form is invalid !!! TODO """
125 # TODO: implement def form_invalid for returning a form with input and error
126 print "running form_invalid"
131 class NoteView(View):
132 """ Notes superclass that wraps http methods """
134 def get(self, request, *args, **kwargs):
135 view = NoteDetailView.as_view()
136 return view(request, *args, **kwargs)
138 def post(self, request, *args, **kwargs):
139 view = NoteSaveView.as_view()
140 return view(request, *args, **kwargs)
143 class NoteDeleteView(FormView):
144 form_class = NoteDeleteForm
146 def form_valid(self, form):
147 self.note = Note.objects.get(id=form.cleaned_data['note'])
148 self.note.is_hidden = True
151 messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
153 return super(FormView, self).form_valid(form)
155 def get_success_url(self):
156 return self.note.course.get_absolute_url()
159 class NoteKeywordsView(FormView, SingleObjectMixin):
160 """ Class-based view for the note html page """
162 context_object_name = u"note" # name passed to template
163 form_class = formset_factory(KeywordForm)
164 template_name = 'notes/note_base.html'
166 def get_object(self, queryset=None):
167 return Note.objects.get(slug=self.kwargs['slug'])
169 def post(self, request, *args, **kwargs):
170 self.object = self.get_object()
171 if not self.request.user.is_authenticated():
172 raise ValidationError("Only authenticated users may set keywords.")
174 formset = self.form_class(request.POST)
175 if formset.is_valid():
176 self.keyword_form_valid(formset)
177 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
178 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
180 self.keyword_formset = formset
181 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
183 def get(self, request, *args, **kwargs):
184 self.object = self.get_object()
185 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
186 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
188 def get_context_data(self, **kwargs):
189 kwargs['keyword_prototype_form'] = KeywordForm
190 kwargs['keyword_formset'] = self.keyword_formset
191 kwargs['keywords'] = Keyword.objects.filter(note=self.get_object())
192 kwargs['show_keywords'] = True
194 note_page_context_helper(self.get_object(), self.request, kwargs)
196 return super(NoteKeywordsView, self).get_context_data(**kwargs)
198 def get_initial_keywords(self):
199 existing_keywords = self.get_object().keyword_set.order_by('id')
200 initial_data = [{'keyword': keyword.word, 'definition': keyword.definition, 'id': keyword.pk}
201 for keyword in existing_keywords]
204 def keyword_form_valid(self, formset):
206 word = form['keyword'].data
207 definition = form['definition'].data
209 # If the user has deleted an existing keyword
210 if not word and not definition and id:
212 keyword_object = Keyword.objects.get(id=id)
213 keyword_object.delete()
214 except (ValueError, ObjectDoesNotExist):
217 # otherwise get or create a keyword
218 elif word or definition:
220 keyword_object = Keyword.objects.get(id=id)
221 except (ValueError, ObjectDoesNotExist):
222 keyword_object = Keyword()
223 NoteKarmaEvent.create_event(self.request.user, self.get_object(), NoteKarmaEvent.CREATED_KEYWORD)
225 keyword_object.note = self.get_object()
226 keyword_object.word = word
227 keyword_object.definition = definition
228 keyword_object.unreviewed = False
229 keyword_object.save()
232 class NoteQuizView(TemplateView):
233 template_name = 'notes/note_base.html'
235 def get_context_data(self, **kwargs):
236 note = Note.objects.get(slug=self.kwargs['slug'])
238 note_page_context_helper(note, self.request, kwargs)
240 kwargs['note'] = note
241 kwargs['questions'] = quiz_from_keywords(note)
242 kwargs['show_quiz'] = True
244 return super(NoteQuizView, self).get_context_data(**kwargs)
247 class NoteSearchView(ListView):
248 template_name = 'notes/search_results.html'
250 def get_queryset(self):
251 if not 'query' in self.request.GET:
252 return Note.objects.none()
254 if 'page' in self.request.GET:
255 page = int(self.request.GET['page'])
260 index = SearchIndex()
262 if 'course_id' in self.request.GET:
263 raw_results = index.search(self.request.GET['query'],
264 self.request.GET['course_id'],
267 raw_results = index.search(self.request.GET['query'],
271 logger.error("Error with IndexDen:\n" + traceback.format_exc())
273 return Note.objects.none()
277 instances = Note.objects.in_bulk(raw_results.ordered_ids)
279 for id in raw_results.ordered_ids:
281 results.append((instances[id], raw_results.snippet_dict[id]))
282 self.has_more = raw_results.has_more
286 def get_context_data(self, **kwargs):
287 if 'query' in self.request.GET:
288 kwargs['query'] = self.request.GET['query']
290 if 'course_id' in self.request.GET:
291 kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
294 kwargs['error'] = True
295 return super(NoteSearchView, self).get_context_data(**kwargs)
297 # If query returned more search results than could
298 # fit on one page, show "Next" button
300 kwargs['has_next'] = True
301 if 'page' in self.request.GET:
302 kwargs['next_page'] = int(self.request.GET['page']) + 1
304 kwargs['next_page'] = 1
306 # If the user is looking at a search result page
307 # that isn't the first one, show "Prev" button
308 if 'page' in self.request.GET and \
309 int(self.request.GET['page']) > 0:
310 kwargs['has_prev'] = True
311 kwargs['prev_page'] = int(self.request.GET['page']) - 1
313 return super(NoteSearchView, self).get_context_data(**kwargs)
316 def process_note_thank_events(request_user, note):
317 # Give points to the person who uploaded this note
318 if note.user != request_user and note.user:
319 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.THANKS)
321 # If note thanks exceeds a threshold, create a Mechanical
322 # Turk task to get some keywords for it
323 if note.thanks == KEYWORD_MTURK_THRESHOLD:
324 submit_extract_keywords_hit.delay(note)
327 def thank_note(request, pk):
328 """Record that somebody has thanked a note."""
329 return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
332 def process_note_flag_events(request_user, note):
333 # Take a point away from person flagging this note
334 if request_user.is_authenticated():
335 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
336 # If this is the 6th time this note has been flagged,
337 # punish the uploader
338 if note.flags == 6 and note.user:
339 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
342 def flag_note(request, pk):
343 """Record that somebody has flagged a note."""
344 return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
347 def process_downloaded_note(request_user, note):
348 """Record that somebody has downloaded a note"""
349 if request_user.is_authenticated() and request_user != note.user:
350 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
351 if request_user.is_authenticated() and note.user:
352 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
355 def downloaded_note(request, pk):
356 """Record that somebody has flagged a note."""
357 return ajax_pk_base(Note, request, pk, process_downloaded_note)
360 def edit_note_tags(request, pk):
362 Saves the posted string of tags
364 if request.method == "POST" and request.is_ajax() and request.user.is_authenticated() and request.user.get_profile().can_edit_items():
365 note = Note.objects.get(pk=pk)
366 note.tags.set(request.body)
368 note_json = serializers.serialize('json', [note,])
369 resp = json.loads(note_json)[0]
370 resp['fields']['tags'] = list(note.tags.names())
372 return HttpResponse(json.dumps(resp), mimetype="application/json")
374 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
375 mimetype="application/json")