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.models import Course
13 from karmaworld.apps.notes.search import SearchIndex
14 from karmaworld.apps.quizzes.create_quiz import quiz_from_keywords
15 from karmaworld.apps.quizzes.forms import KeywordForm
16 from karmaworld.apps.quizzes.models import Keyword
17 from karmaworld.apps.quizzes.tasks import submit_extract_keywords_hit, get_extract_keywords_results
18 from karmaworld.apps.users.models import NoteKarmaEvent
19 from karmaworld.utils.ajax_utils import *
21 from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
22 from django.views.generic import DetailView, ListView, TemplateView
23 from django.views.generic import FormView
24 from django.views.generic import View
25 from django.views.generic.detail import SingleObjectMixin
27 from karmaworld.apps.notes.models import Note
28 from karmaworld.apps.notes.forms import NoteForm, NoteDeleteForm
31 logger = logging.getLogger(__name__)
33 THANKS_FIELD = 'thanks'
34 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
36 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
39 def note_page_context_helper(note, request, context):
41 if request.method == 'POST':
42 context['note_edit_form'] = NoteForm(request.POST)
44 tags_string = ','.join([str(tag) for tag in note.tags.all()])
45 context['note_edit_form'] = NoteForm(initial={'name': note.name,
48 context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
51 context['pdf_controls'] = True
53 if request.user.is_authenticated():
55 request.user.get_profile().thanked_notes.get(pk=note.pk)
56 context['already_thanked'] = True
57 except ObjectDoesNotExist:
61 request.user.get_profile().flagged_notes.get(pk=note.pk)
62 context['already_flagged'] = True
63 except ObjectDoesNotExist:
67 class NoteDetailView(DetailView):
68 """ Class-based view for the note html page """
70 context_object_name = u"note" # name passed to template
71 template_name = 'notes/note_base.html'
73 def get_context_data(self, **kwargs):
74 """ Generate custom context for the page rendering a Note
75 + if pdf, set the `pdf` flag
78 kwargs['show_note_container'] = True
80 note_page_context_helper(self.object, self.request, kwargs)
82 return super(NoteDetailView, self).get_context_data(**kwargs)
85 class NoteSaveView(FormView, SingleObjectMixin):
86 """ Save a Note and then view the page,
87 behaves the same as NoteDetailView, except for saving the
92 template_name = 'notes/note_base.html'
94 def post(self, request, *args, **kwargs):
95 self.object = self.get_object()
96 if self.object.user != request.user:
97 raise ValidationError("Only the owner of a note can edit it.")
98 return super(NoteSaveView, self).post(request, *args, **kwargs)
100 def get_success_url(self):
101 return self.object.get_absolute_url()
103 def get_context_data(self, **kwargs):
105 'object': self.get_object(),
107 return super(NoteSaveView, self).get_context_data(**context)
109 def form_valid(self, form):
110 """ Actions to take if the submitted form is valid
111 namely, saving the new data to the existing note object
113 if len(form.cleaned_data['name'].strip()) > 0:
114 self.object.name = form.cleaned_data['name']
115 # use *arg expansion to pass tags a list of tags
116 self.object.tags.set(*form.cleaned_data['tags'])
117 # User has submitted this form, so set the SHOW flag
118 self.object.is_hidden = False
120 return super(NoteSaveView, self).form_valid(form)
122 def form_invalid(self, form):
123 """ Do stuff when the form is invalid !!! TODO """
124 # TODO: implement def form_invalid for returning a form with input and error
125 print "running form_invalid"
130 class NoteView(View):
131 """ Notes superclass that wraps http methods """
133 def get(self, request, *args, **kwargs):
134 view = NoteDetailView.as_view()
135 return view(request, *args, **kwargs)
137 def post(self, request, *args, **kwargs):
138 view = NoteSaveView.as_view()
139 return view(request, *args, **kwargs)
142 class NoteDeleteView(FormView):
143 form_class = NoteDeleteForm
145 def form_valid(self, form):
146 self.note = Note.objects.get(id=form.cleaned_data['note'])
147 self.note.is_hidden = True
150 messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
152 return super(FormView, self).form_valid(form)
154 def get_success_url(self):
155 return self.note.course.get_absolute_url()
158 class NoteKeywordsView(FormView, SingleObjectMixin):
159 """ Class-based view for the note html page """
161 context_object_name = u"note" # name passed to template
162 form_class = formset_factory(KeywordForm)
163 template_name = 'notes/note_base.html'
165 def get_object(self, queryset=None):
166 return Note.objects.get(slug=self.kwargs['slug'])
168 def post(self, request, *args, **kwargs):
169 self.object = self.get_object()
170 if not self.request.user.is_authenticated():
171 raise ValidationError("Only authenticated users may set keywords.")
173 formset = self.form_class(request.POST)
174 if formset.is_valid():
175 self.keyword_form_valid(formset)
176 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
177 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
179 self.keyword_formset = formset
180 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
182 def get(self, request, *args, **kwargs):
183 self.object = self.get_object()
184 self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
185 return super(NoteKeywordsView, self).get(request, *args, **kwargs)
187 def get_context_data(self, **kwargs):
188 kwargs['keyword_prototype_form'] = KeywordForm
189 kwargs['keyword_formset'] = self.keyword_formset
190 kwargs['keywords'] = Keyword.objects.filter(note=self.get_object())
191 kwargs['show_keywords'] = True
193 note_page_context_helper(self.get_object(), self.request, kwargs)
195 return super(NoteKeywordsView, self).get_context_data(**kwargs)
197 def get_initial_keywords(self):
198 existing_keywords = self.get_object().keyword_set.order_by('id')
199 initial_data = [{'keyword': keyword.word, 'definition': keyword.definition, 'id': keyword.pk}
200 for keyword in existing_keywords]
203 def keyword_form_valid(self, formset):
205 word = form['keyword'].data
206 definition = form['definition'].data
211 keyword_object = Keyword.objects.get(id=id)
212 except (ValueError, ObjectDoesNotExist):
213 keyword_object = Keyword()
215 keyword_object.note = self.get_object()
216 keyword_object.word = word
217 keyword_object.definition = definition
218 keyword_object.save()
221 class NoteQuizView(TemplateView):
222 template_name = 'notes/note_base.html'
224 def get_context_data(self, **kwargs):
225 note = Note.objects.get(slug=self.kwargs['slug'])
227 note_page_context_helper(note, self.request, kwargs)
229 kwargs['note'] = note
230 kwargs['questions'] = quiz_from_keywords(note)
231 kwargs['show_quiz'] = True
233 return super(NoteQuizView, self).get_context_data(**kwargs)
236 class NoteSearchView(ListView):
237 template_name = 'notes/search_results.html'
239 def get_queryset(self):
240 if not 'query' in self.request.GET:
241 return Note.objects.none()
243 if 'page' in self.request.GET:
244 page = int(self.request.GET['page'])
249 index = SearchIndex()
251 if 'course_id' in self.request.GET:
252 raw_results = index.search(self.request.GET['query'],
253 self.request.GET['course_id'],
256 raw_results = index.search(self.request.GET['query'],
260 logger.error("Error with IndexDen:\n" + traceback.format_exc())
262 return Note.objects.none()
266 instances = Note.objects.in_bulk(raw_results.ordered_ids)
268 for id in raw_results.ordered_ids:
270 results.append((instances[id], raw_results.snippet_dict[id]))
271 self.has_more = raw_results.has_more
275 def get_context_data(self, **kwargs):
276 if 'query' in self.request.GET:
277 kwargs['query'] = self.request.GET['query']
279 if 'course_id' in self.request.GET:
280 kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
283 kwargs['error'] = True
284 return super(NoteSearchView, self).get_context_data(**kwargs)
286 # If query returned more search results than could
287 # fit on one page, show "Next" button
289 kwargs['has_next'] = True
290 if 'page' in self.request.GET:
291 kwargs['next_page'] = int(self.request.GET['page']) + 1
293 kwargs['next_page'] = 1
295 # If the user is looking at a search result page
296 # that isn't the first one, show "Prev" button
297 if 'page' in self.request.GET and \
298 int(self.request.GET['page']) > 0:
299 kwargs['has_prev'] = True
300 kwargs['prev_page'] = int(self.request.GET['page']) - 1
302 return super(NoteSearchView, self).get_context_data(**kwargs)
305 def handle_edit_note(request):
306 course = Course.objects.get(pk=pk)
307 original_name = course.name
308 course_form = CourseForm(request.POST or None, instance=course)
310 if course_form.is_valid():
313 course_json = serializers.serialize('json', [course,])
314 resp = json.loads(course_json)[0]
316 if (course.name != original_name):
318 resp['fields']['new_url'] = course.get_absolute_url()
320 return HttpResponse(json.dumps(resp), mimetype="application/json")
322 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
323 'errors': course_form.errors}),
324 mimetype="application/json")
326 def edit_note(request, pk):
328 Saves the edited note metadata
330 ajax_base(request, edit_note, ['POST'])
333 def process_note_thank_events(request_user, note):
334 # Give points to the person who uploaded this note
335 if note.user != request_user and note.user:
336 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.THANKS)
338 # If note thanks exceeds a threshold, create a Mechanical
339 # Turk task to get some keywords for it
341 submit_extract_keywords_hit(note)
344 def thank_note(request, pk):
345 """Record that somebody has thanked a note."""
346 return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
349 def process_note_flag_events(request_user, note):
350 # Take a point away from person flagging this note
351 if request_user.is_authenticated():
352 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
353 # If this is the 6th time this note has been flagged,
354 # punish the uploader
355 if note.flags == 6 and note.user:
356 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
359 def flag_note(request, pk):
360 """Record that somebody has flagged a note."""
361 return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
364 def process_downloaded_note(request_user, note):
365 """Record that somebody has downloaded a note"""
366 if request_user.is_authenticated() and request_user != note.user:
367 NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
368 if request_user.is_authenticated() and note.user:
369 NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
372 def downloaded_note(request, pk):
373 """Record that somebody has flagged a note."""
374 return ajax_pk_base(Note, request, pk, process_downloaded_note)
377 def edit_note_tags(request, pk):
379 Saves the posted string of tags
381 if request.method == "POST" and request.is_ajax() and request.user.is_authenticated() and request.user.get_profile().can_edit_items():
382 note = Note.objects.get(pk=pk)
383 note.tags.set(request.body)
385 note_json = serializers.serialize('json', [note,])
386 resp = json.loads(note_json)[0]
387 resp['fields']['tags'] = list(note.tags.names())
389 return HttpResponse(json.dumps(resp), mimetype="application/json")
391 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
392 mimetype="application/json")