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
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
212 keyword_object = Keyword.objects.get(id=id)
213 except (ValueError, ObjectDoesNotExist):
214 keyword_object = Keyword()
216 keyword_object.note = self.get_object()
217 keyword_object.word = word
218 keyword_object.definition = definition
219 keyword_object.save()
222 class NoteQuizView(TemplateView):
223 template_name = 'notes/note_base.html'
225 def get_context_data(self, **kwargs):
226 note = Note.objects.get(slug=self.kwargs['slug'])
228 note_page_context_helper(note, self.request, kwargs)
230 kwargs['note'] = note
231 kwargs['questions'] = quiz_from_keywords(note)
232 kwargs['show_quiz'] = True
234 return super(NoteQuizView, self).get_context_data(**kwargs)
237 class NoteSearchView(ListView):
238 template_name = 'notes/search_results.html'
240 def get_queryset(self):
241 if not 'query' in self.request.GET:
242 return Note.objects.none()
244 if 'page' in self.request.GET:
245 page = int(self.request.GET['page'])
250 index = SearchIndex()
252 if 'course_id' in self.request.GET:
253 raw_results = index.search(self.request.GET['query'],
254 self.request.GET['course_id'],
257 raw_results = index.search(self.request.GET['query'],
261 logger.error("Error with IndexDen:\n" + traceback.format_exc())
263 return Note.objects.none()
267 instances = Note.objects.in_bulk(raw_results.ordered_ids)
269 for id in raw_results.ordered_ids:
271 results.append((instances[id], raw_results.snippet_dict[id]))
272 self.has_more = raw_results.has_more
276 def get_context_data(self, **kwargs):
277 if 'query' in self.request.GET:
278 kwargs['query'] = self.request.GET['query']
280 if 'course_id' in self.request.GET:
281 kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
284 kwargs['error'] = True
285 return super(NoteSearchView, self).get_context_data(**kwargs)
287 # If query returned more search results than could
288 # fit on one page, show "Next" button
290 kwargs['has_next'] = True
291 if 'page' in self.request.GET:
292 kwargs['next_page'] = int(self.request.GET['page']) + 1
294 kwargs['next_page'] = 1
296 # If the user is looking at a search result page
297 # that isn't the first one, show "Prev" button
298 if 'page' in self.request.GET and \
299 int(self.request.GET['page']) > 0:
300 kwargs['has_prev'] = True
301 kwargs['prev_page'] = int(self.request.GET['page']) - 1
303 return super(NoteSearchView, self).get_context_data(**kwargs)
306 def process_note_thank_events(request_user, note):
307 # Give points to the person who uploaded this note
308 if note.user != request_user and note.user:
309 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.THANKS)
311 # If note thanks exceeds a threshold, create a Mechanical
312 # Turk task to get some keywords for it
314 submit_extract_keywords_hit(note)
317 def thank_note(request, pk):
318 """Record that somebody has thanked a note."""
319 return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
322 def process_note_flag_events(request_user, note):
323 # Take a point away from person flagging this note
324 if request_user.is_authenticated():
325 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
326 # If this is the 6th time this note has been flagged,
327 # punish the uploader
328 if note.flags == 6 and note.user:
329 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
332 def flag_note(request, pk):
333 """Record that somebody has flagged a note."""
334 return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
337 def process_downloaded_note(request_user, note):
338 """Record that somebody has downloaded a note"""
339 if request_user.is_authenticated() and request_user != note.user:
340 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
341 if request_user.is_authenticated() and note.user:
342 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
345 def downloaded_note(request, pk):
346 """Record that somebody has flagged a note."""
347 return ajax_pk_base(Note, request, pk, process_downloaded_note)
350 def edit_note_tags(request, pk):
352 Saves the posted string of tags
354 if request.method == "POST" and request.is_ajax() and request.user.is_authenticated() and request.user.get_profile().can_edit_items():
355 note = Note.objects.get(pk=pk)
356 note.tags.set(request.body)
358 note_json = serializers.serialize('json', [note,])
359 resp = json.loads(note_json)[0]
360 resp['fields']['tags'] = list(note.tags.names())
362 return HttpResponse(json.dumps(resp), mimetype="application/json")
364 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
365 mimetype="application/json")