3 # Copyright (C) 2012 FinalsClub Foundation
9 from django.contrib import messages
11 from django.core import serializers
12 from django.core.exceptions import ValidationError
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.forms.formsets import formset_factory
16 from django.http import HttpResponse
17 from django.http import HttpResponseRedirect
18 from django.http import HttpResponseForbidden
19 from django.http import HttpResponseBadRequest
20 from django.views.generic import DetailView, ListView, TemplateView
21 from django.views.generic import UpdateView, FormView
22 from django.views.generic import View
23 from django.views.generic.detail import SingleObjectMixin
25 from karmaworld.apps.notes.forms import NoteForm
26 from karmaworld.apps.notes.forms import NoteDeleteForm
27 from karmaworld.apps.notes.models import Note
28 from karmaworld.apps.notes.models import NoteMarkdown
29 from karmaworld.apps.notes.models import KEYWORD_MTURK_THRESHOLD
30 from karmaworld.apps.notes.search import SearchIndex
31 from karmaworld.apps.users.models import NoteKarmaEvent
32 from karmaworld.apps.courses.forms import CourseForm
33 from karmaworld.apps.quizzes.forms import KeywordForm
34 from karmaworld.apps.quizzes.tasks import submit_extract_keywords_hit
35 from karmaworld.apps.quizzes.tasks import get_extract_keywords_results
36 from karmaworld.apps.courses.models import Course
37 from karmaworld.apps.quizzes.models import Keyword
38 from karmaworld.apps.quizzes.create_quiz import quiz_from_keywords
40 from karmaworld.utils.ajax_utils import ajax_pk_base
41 from karmaworld.utils.ajax_utils import ajax_increment
44 logger = logging.getLogger(__name__)
46 THANKS_FIELD = 'thanks'
47 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
49 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
51 def note_page_context_helper(note, request, context):
53 if request.method == 'POST':
54 if not note.allows_edit_by(request.user):
55 # This user is Balrog. It. Shall. Not. Pass.
56 return HttpResponseForbidden()
57 # Only save tags if not forbidden above.
58 context['note_edit_form'] = NoteForm(request.POST)
60 tags_string = ','.join([str(tag) for tag in note.tags.all()])
61 initial = {"name": note.name, "tags": tags_string}
63 initial["html"] = note.notemarkdown.html
64 except NoteMarkdown.DoesNotExist:
66 context['note_edit_form'] = NoteForm(initial=initial)
68 context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
71 context['pdf_controls'] = True
73 if request.user.is_authenticated():
75 request.user.get_profile().thanked_notes.get(pk=note.pk)
76 context['already_thanked'] = True
77 except ObjectDoesNotExist:
81 request.user.get_profile().flagged_notes.get(pk=note.pk)
82 context['already_flagged'] = True
83 except ObjectDoesNotExist:
86 class NoteView(UpdateView):
89 context_object_name = "note"
90 template_name = "notes/note_base.html"
92 def get_success_url(self):
93 return self.object.get_absolute_url()
95 def form_valid(self, form):
96 self.note = self.object
97 # Ensure that the requesting user has permission to edit.
98 if self.note.allows_edit_by(self.request.user):
99 return super(NoteView, self).form_valid(form)
101 messages.error(self.request, 'Permission denied.')
102 return HttpResponseRedirect(self.get_success_url())
104 def get_context_data(self, **kwargs):
105 context = super(NoteView, self).get_context_data(**kwargs)
106 context['show_note_container'] = True
107 context['pdf_controls'] = self.object.is_pdf()
108 u = self.request.user
109 context['already_thanked'] = (
110 u.is_authenticated() and
111 u.get_profile().thanked_notes.filter(pk=self.object.pk).exists()
113 context['already_flagged'] = (
114 u.is_authenticated() and
115 u.get_profile().flagged_notes.filter(pk=self.object.pk).exists()
117 context['note_delete_form'] = NoteDeleteForm(initial={'note': self.object.id})
118 context['note_edit_form'] = context.get('form')
121 def get_initial(self, **kwargs):
122 initial = super(NoteView, self).get_initial()
124 initial["html"] = self.object.notemarkdown.html
125 except NoteMarkdown.DoesNotExist:
127 initial["tags"] = ",".join([unicode(tag) for tag in self.object.tags.all()])
131 class NoteDeleteView(FormView):
132 form_class = NoteDeleteForm
134 def form_valid(self, form):
135 self.note = Note.objects.get(id=form.cleaned_data['note'])
136 # Ensure that the requesting user has permission to delete.
137 if self.note.allows_delete_by(self.request.user):
138 self.note.is_hidden = True
140 messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
142 messages.error(self.request, 'Permission denied.')
144 return super(FormView, self).form_valid(form)
146 def get_success_url(self):
147 return self.note.course.get_absolute_url()
150 class NoteKeywordsView(FormView, SingleObjectMixin):
151 """ Class-based view for the note html page """
153 context_object_name = u"note" # name passed to template
154 form_class = formset_factory(KeywordForm)
155 template_name = 'notes/note_base.html'
157 def get_object(self, queryset=None):
158 return Note.objects.get(slug=self.kwargs['slug'])
160 def post(self, request, *args, **kwargs):
161 self.object = self.get_object()
162 if not self.request.user.is_authenticated():
163 raise ValidationError("Only authenticated users may set keywords.")
165 formset = self.form_class(request.POST)
166 if formset.is_valid():
167 self.keyword_form_valid(formset)
168 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
169 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
171 self.keyword_formset = formset
172 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
174 def get(self, request, *args, **kwargs):
175 self.object = self.get_object()
176 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
177 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
179 def get_context_data(self, **kwargs):
180 kwargs['keyword_prototype_form'] = KeywordForm
181 kwargs['keyword_formset'] = self.keyword_formset
182 kwargs['keywords'] = Keyword.objects.filter(note=self.get_object())
183 kwargs['show_keywords'] = True
185 ret = note_page_context_helper(self.get_object(), self.request, kwargs)
186 # check for errors returned by the helper.
190 return super(NoteKeywordsView, self).get_context_data(**kwargs)
192 def get_initial_keywords(self):
193 existing_keywords = self.get_object().keyword_set.order_by('id')
194 initial_data = [{'keyword': keyword.word, 'definition': keyword.definition, 'id': keyword.pk}
195 for keyword in existing_keywords]
198 def keyword_form_valid(self, formset):
200 word = form['keyword'].data
201 definition = form['definition'].data
203 # If the user has deleted an existing keyword
204 if not word and not definition and id:
206 keyword_object = Keyword.objects.get(id=id)
207 keyword_object.delete()
208 except (ValueError, ObjectDoesNotExist):
211 # otherwise get or create a keyword
212 elif word or definition:
214 keyword_object = Keyword.objects.get(id=id)
215 except (ValueError, ObjectDoesNotExist):
216 keyword_object = Keyword()
217 NoteKarmaEvent.create_event(self.request.user, self.get_object(), NoteKarmaEvent.CREATED_KEYWORD)
219 keyword_object.note = self.get_object()
220 keyword_object.word = word
221 keyword_object.definition = definition
222 keyword_object.unreviewed = False
223 keyword_object.save()
226 class NoteQuizView(TemplateView):
227 template_name = 'notes/note_base.html'
229 def get_context_data(self, **kwargs):
230 note = Note.objects.get(slug=self.kwargs['slug'])
232 ret = note_page_context_helper(note, self.request, kwargs)
233 # check for errors returned by the helper.
237 kwargs['note'] = note
238 kwargs['questions'] = quiz_from_keywords(note)
239 kwargs['show_quiz'] = True
241 return super(NoteQuizView, self).get_context_data(**kwargs)
244 class NoteSearchView(ListView):
245 template_name = 'notes/search_results.html'
247 def get_queryset(self):
248 if not 'query' in self.request.GET:
249 return Note.objects.none()
251 if 'page' in self.request.GET:
252 page = int(self.request.GET['page'])
257 index = SearchIndex()
259 if 'course_id' in self.request.GET:
260 raw_results = index.search(self.request.GET['query'],
261 self.request.GET['course_id'],
264 raw_results = index.search(self.request.GET['query'],
268 logger.error("Error with IndexDen:\n" + traceback.format_exc())
270 return Note.objects.none()
274 instances = Note.objects.in_bulk(raw_results.ordered_ids)
276 for id in raw_results.ordered_ids:
278 results.append((instances[id], raw_results.snippet_dict[id]))
279 self.has_more = raw_results.has_more
283 def get_context_data(self, **kwargs):
284 if 'query' in self.request.GET:
285 kwargs['query'] = self.request.GET['query']
287 if 'course_id' in self.request.GET:
288 kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
291 kwargs['error'] = True
292 return super(NoteSearchView, self).get_context_data(**kwargs)
294 # If query returned more search results than could
295 # fit on one page, show "Next" button
297 kwargs['has_next'] = True
298 if 'page' in self.request.GET:
299 kwargs['next_page'] = int(self.request.GET['page']) + 1
301 kwargs['next_page'] = 1
303 # If the user is looking at a search result page
304 # that isn't the first one, show "Prev" button
305 if 'page' in self.request.GET and \
306 int(self.request.GET['page']) > 0:
307 kwargs['has_prev'] = True
308 kwargs['prev_page'] = int(self.request.GET['page']) - 1
310 return super(NoteSearchView, self).get_context_data(**kwargs)
313 def process_note_thank_events(request_user, note):
314 # Give points to the person who uploaded this note
315 if note.user != request_user and note.user:
316 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.THANKS)
318 # If note thanks exceeds a threshold, create a Mechanical
319 # Turk task to get some keywords for it
320 if note.thanks == KEYWORD_MTURK_THRESHOLD:
321 submit_extract_keywords_hit.delay(note)
324 def thank_note(request, pk):
325 """Record that somebody has thanked a note."""
326 return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
329 def process_note_flag_events(request_user, note):
330 # Take a point away from person flagging this note
331 if request_user.is_authenticated():
332 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
333 # If this is the 6th time this note has been flagged,
334 # punish the uploader
335 if note.flags == 6 and note.user:
336 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
339 def flag_note(request, pk):
340 """Record that somebody has flagged a note."""
341 return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
344 def process_downloaded_note(request_user, note):
345 """Record that somebody has downloaded a note"""
346 if request_user.is_authenticated() and request_user != note.user:
347 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
348 if request_user.is_authenticated() and note.user and note.user != request_user:
349 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
352 def downloaded_note(request, pk):
353 """Record that somebody has flagged a note."""
354 return ajax_pk_base(Note, request, pk, process_downloaded_note)
357 def edit_note_tags(request, pk):
359 Saves the posted string of tags
361 note = Note.objects.get(pk=pk)
362 if request.method == "POST" and request.is_ajax() and note.allows_tags_by(request.user):
363 note.tags.set(request.body)
365 note_json = serializers.serialize('json', [note,])
366 resp = json.loads(note_json)[0]
367 resp['fields']['tags'] = list(note.tags.names())
369 return HttpResponse(json.dumps(resp), mimetype="application/json")
370 if request.method != "POST" or not request.is_ajax():
371 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
372 mimetype="application/json")
374 return HttpResponseForbidden(json.dumps({'status': 'fail', 'message': 'Not permitted'}),
375 mimetype="application/json")