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.forms.formsets import formset_factory
15 from django.http import HttpResponse
16 from django.http import HttpResponseRedirect
17 from django.http import HttpResponseForbidden
18 from django.http import HttpResponseBadRequest
19 from django.views.generic import DetailView, ListView, TemplateView
20 from django.views.generic import UpdateView, FormView
21 from django.views.generic import View
22 from django.views.generic.detail import SingleObjectMixin
24 from karmaworld.apps.notes.forms import NoteForm
25 from karmaworld.apps.notes.forms import NoteDeleteForm
26 from karmaworld.apps.notes.models import Note
27 from karmaworld.apps.notes.models import NoteMarkdown
28 from karmaworld.apps.notes.models import KEYWORD_MTURK_THRESHOLD
29 from karmaworld.apps.notes.search import SearchIndex
30 from karmaworld.apps.users.models import NoteKarmaEvent
31 from karmaworld.apps.courses.forms import CourseForm
32 from karmaworld.apps.quizzes.forms import KeywordForm
33 from karmaworld.apps.quizzes.tasks import submit_extract_keywords_hit
34 from karmaworld.apps.quizzes.tasks import get_extract_keywords_results
35 from karmaworld.apps.courses.models import Course
36 from karmaworld.apps.quizzes.models import Keyword
37 from karmaworld.apps.quizzes.create_quiz import quiz_from_keywords
39 from karmaworld.utils.ajax_utils import ajax_pk_base
40 from karmaworld.utils.ajax_utils import ajax_increment
43 logger = logging.getLogger(__name__)
45 THANKS_FIELD = 'thanks'
46 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
48 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
50 def note_page_context_helper(note, request, context):
52 if request.method == 'POST':
53 if not note.allows_edit_by(request.user):
54 # This user is Balrog. It. Shall. Not. Pass.
55 return HttpResponseForbidden()
56 # Only save tags if not forbidden above.
57 context['note_edit_form'] = NoteForm(request.POST)
59 tags_string = ','.join([str(tag) for tag in note.tags.all()])
60 initial = {"name": note.name, "tags": tags_string}
62 initial["html"] = note.notemarkdown.html
63 except NoteMarkdown.DoesNotExist:
65 context['note_edit_form'] = NoteForm(initial=initial)
67 context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
70 context['pdf_controls'] = True
72 if request.user.is_authenticated():
74 request.user.get_profile().thanked_notes.get(pk=note.pk)
75 context['already_thanked'] = True
76 except ObjectDoesNotExist:
80 request.user.get_profile().flagged_notes.get(pk=note.pk)
81 context['already_flagged'] = True
82 except ObjectDoesNotExist:
85 class NoteView(UpdateView):
88 context_object_name = "note"
89 template_name = "notes/note_base.html"
91 def get_success_url(self):
92 return self.object.get_absolute_url()
94 def form_valid(self, form):
95 self.note = self.object
96 # Ensure that the requesting user has permission to edit.
97 if self.note.allows_edit_by(self.request.user):
98 return super(NoteView, self).form_valid(form)
100 messages.error(self.request, 'Permission denied.')
101 return HttpResponseRedirect(self.get_success_url())
103 def get_context_data(self, **kwargs):
104 context = super(NoteView, self).get_context_data(**kwargs)
105 context['show_note_container'] = True
106 context['pdf_controls'] = self.object.is_pdf()
107 u = self.request.user
108 context['already_thanked'] = (
109 u.is_authenticated() and
110 u.get_profile().thanked_notes.filter(pk=self.object.pk).exists()
112 context['already_flagged'] = (
113 u.is_authenticated() and
114 u.get_profile().flagged_notes.filter(pk=self.object.pk).exists()
116 context['note_delete_form'] = NoteDeleteForm(initial={'note': self.object.id})
117 context['note_edit_form'] = context.get('form')
120 def get_initial(self, **kwargs):
121 initial = super(NoteView, self).get_initial()
123 initial["html"] = self.object.notemarkdown.html
124 except NoteMarkdown.DoesNotExist:
126 initial["tags"] = ",".join([unicode(tag) for tag in self.object.tags.all()])
130 class NoteDeleteView(FormView):
131 form_class = NoteDeleteForm
133 def form_valid(self, form):
134 self.note = Note.objects.get(id=form.cleaned_data['note'])
135 # Ensure that the requesting user has permission to delete.
136 if self.note.allows_delete_by(self.request.user):
137 self.note.is_hidden = True
139 messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
141 messages.error(self.request, 'Permission denied.')
143 return super(FormView, self).form_valid(form)
145 def get_success_url(self):
146 return self.note.course.get_absolute_url()
149 class NoteKeywordsView(FormView, SingleObjectMixin):
150 """ Class-based view for the note html page """
152 context_object_name = u"note" # name passed to template
153 form_class = formset_factory(KeywordForm)
154 template_name = 'notes/note_base.html'
156 def get_object(self, queryset=None):
157 return Note.objects.get(slug=self.kwargs['slug'])
159 def post(self, request, *args, **kwargs):
160 self.object = self.get_object()
161 if not self.request.user.is_authenticated():
162 raise ValidationError("Only authenticated users may set keywords.")
164 formset = self.form_class(request.POST)
165 if formset.is_valid():
166 self.keyword_form_valid(formset)
167 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
168 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
170 self.keyword_formset = formset
171 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
173 def get(self, request, *args, **kwargs):
174 self.object = self.get_object()
175 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
176 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
178 def get_context_data(self, **kwargs):
179 kwargs['keyword_prototype_form'] = KeywordForm
180 kwargs['keyword_formset'] = self.keyword_formset
181 kwargs['keywords'] = Keyword.objects.filter(note=self.get_object())
182 kwargs['show_keywords'] = True
184 ret = note_page_context_helper(self.get_object(), self.request, kwargs)
185 # check for errors returned by the helper.
189 return super(NoteKeywordsView, self).get_context_data(**kwargs)
191 def get_initial_keywords(self):
192 existing_keywords = self.get_object().keyword_set.order_by('id')
193 initial_data = [{'keyword': keyword.word, 'definition': keyword.definition, 'id': keyword.pk}
194 for keyword in existing_keywords]
197 def keyword_form_valid(self, formset):
199 word = form['keyword'].data
200 definition = form['definition'].data
202 # If the user has deleted an existing keyword
203 if not word and not definition and id:
205 keyword_object = Keyword.objects.get(id=id)
206 keyword_object.delete()
207 except (ValueError, ObjectDoesNotExist):
210 # otherwise get or create a keyword
211 elif word or definition:
213 keyword_object = Keyword.objects.get(id=id)
214 except (ValueError, ObjectDoesNotExist):
215 keyword_object = Keyword()
216 NoteKarmaEvent.create_event(self.request.user, self.get_object(), NoteKarmaEvent.CREATED_KEYWORD)
218 keyword_object.note = self.get_object()
219 keyword_object.word = word
220 keyword_object.definition = definition
221 keyword_object.unreviewed = False
222 keyword_object.save()
225 class NoteQuizView(TemplateView):
226 template_name = 'notes/note_base.html'
228 def get_context_data(self, **kwargs):
229 note = Note.objects.get(slug=self.kwargs['slug'])
231 ret = note_page_context_helper(note, self.request, kwargs)
232 # check for errors returned by the helper.
236 kwargs['note'] = note
237 kwargs['questions'] = quiz_from_keywords(note)
238 kwargs['show_quiz'] = True
240 return super(NoteQuizView, self).get_context_data(**kwargs)
243 class NoteSearchView(ListView):
244 template_name = 'notes/search_results.html'
246 def get_queryset(self):
247 if not 'query' in self.request.GET:
248 return Note.objects.none()
250 if 'page' in self.request.GET:
251 page = int(self.request.GET['page'])
256 index = SearchIndex()
258 if 'course_id' in self.request.GET:
259 raw_results = index.search(self.request.GET['query'],
260 self.request.GET['course_id'],
263 raw_results = index.search(self.request.GET['query'],
267 logger.error("Error with IndexDen:\n" + traceback.format_exc())
269 return Note.objects.none()
273 instances = Note.objects.in_bulk(raw_results.ordered_ids)
275 for id in raw_results.ordered_ids:
277 results.append((instances[id], raw_results.snippet_dict[id]))
278 self.has_more = raw_results.has_more
282 def get_context_data(self, **kwargs):
283 if 'query' in self.request.GET:
284 kwargs['query'] = self.request.GET['query']
286 if 'course_id' in self.request.GET:
287 kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
290 kwargs['error'] = True
291 return super(NoteSearchView, self).get_context_data(**kwargs)
293 # If query returned more search results than could
294 # fit on one page, show "Next" button
296 kwargs['has_next'] = True
297 if 'page' in self.request.GET:
298 kwargs['next_page'] = int(self.request.GET['page']) + 1
300 kwargs['next_page'] = 1
302 # If the user is looking at a search result page
303 # that isn't the first one, show "Prev" button
304 if 'page' in self.request.GET and \
305 int(self.request.GET['page']) > 0:
306 kwargs['has_prev'] = True
307 kwargs['prev_page'] = int(self.request.GET['page']) - 1
309 return super(NoteSearchView, self).get_context_data(**kwargs)
312 def process_note_thank_events(request_user, note):
313 # Give points to the person who uploaded this note
314 if note.user != request_user and note.user:
315 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.THANKS)
317 # If note thanks exceeds a threshold, create a Mechanical
318 # Turk task to get some keywords for it
319 if note.thanks == KEYWORD_MTURK_THRESHOLD:
320 submit_extract_keywords_hit.delay(note)
323 def thank_note(request, pk):
324 """Record that somebody has thanked a note."""
325 return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
328 def process_note_flag_events(request_user, note):
329 # Take a point away from person flagging this note
330 if request_user.is_authenticated():
331 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
332 # If this is the 6th time this note has been flagged,
333 # punish the uploader
334 if note.flags == 6 and note.user:
335 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
338 def flag_note(request, pk):
339 """Record that somebody has flagged a note."""
340 return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
343 def process_downloaded_note(request_user, note):
344 """Record that somebody has downloaded a note"""
345 if request_user.is_authenticated() and request_user != note.user:
346 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
347 if request_user.is_authenticated() and note.user and note.user != request_user:
348 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
351 def downloaded_note(request, pk):
352 """Record that somebody has flagged a note."""
353 return ajax_pk_base(Note, request, pk, process_downloaded_note)
356 def edit_note_tags(request, pk):
358 Saves the posted string of tags
360 note = Note.objects.get(pk=pk)
361 if request.method == "POST" and request.is_ajax() and note.allows_tags_by(request.user):
362 note.tags.set(request.body)
364 note_json = serializers.serialize('json', [note,])
365 resp = json.loads(note_json)[0]
366 resp['fields']['tags'] = list(note.tags.names())
368 return HttpResponse(json.dumps(resp), mimetype="application/json")
369 if request.method != "POST" or not request.is_ajax():
370 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
371 mimetype="application/json")
373 return HttpResponseForbidden(json.dumps({'status': 'fail', 'message': 'Not permitted'}),
374 mimetype="application/json")