6a4ea6e01bfc408887872c2315bb6969106919ff
[oweals/karmaworld.git] / karmaworld / apps / notes / views.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2012  FinalsClub Foundation
4
5 import traceback
6 import logging
7 from django.contrib import messages
8
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 *
20
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
26
27 from karmaworld.apps.notes.models import Note
28 from karmaworld.apps.notes.forms import NoteForm, NoteDeleteForm
29
30
31 logger = logging.getLogger(__name__)
32
33 THANKS_FIELD = 'thanks'
34 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
35 FLAG_FIELD = 'flags'
36 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
37
38
39 def note_page_context_helper(note, request, context):
40
41     if request.method == 'POST':
42         context['note_edit_form'] = NoteForm(request.POST)
43     else:
44         tags_string = ','.join([str(tag) for tag in note.tags.all()])
45         context['note_edit_form'] = NoteForm(initial={'name': note.name,
46                                                       'tags': tags_string})
47
48     context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
49
50     if note.is_pdf():
51         context['pdf_controls'] = True
52
53     if request.user.is_authenticated():
54         try:
55             request.user.get_profile().thanked_notes.get(pk=note.pk)
56             context['already_thanked'] = True
57         except ObjectDoesNotExist:
58             pass
59
60         try:
61             request.user.get_profile().flagged_notes.get(pk=note.pk)
62             context['already_flagged'] = True
63         except ObjectDoesNotExist:
64             pass
65
66
67 class NoteDetailView(DetailView):
68     """ Class-based view for the note html page """
69     model = Note
70     context_object_name = u"note" # name passed to template
71     template_name = 'notes/note_base.html'
72
73     def get_context_data(self, **kwargs):
74         """ Generate custom context for the page rendering a Note
75             + if pdf, set the `pdf` flag
76         """
77
78         kwargs['show_note_container'] = True
79
80         note_page_context_helper(self.object, self.request, kwargs)
81
82         return super(NoteDetailView, self).get_context_data(**kwargs)
83
84
85 class NoteSaveView(FormView, SingleObjectMixin):
86     """ Save a Note and then view the page, 
87         behaves the same as NoteDetailView, except for saving the
88         NoteForm ModelForm
89     """
90     form_class = NoteForm
91     model = Note
92     template_name = 'notes/note_base.html'
93
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)
99
100     def get_success_url(self):
101         return self.object.get_absolute_url()
102
103     def get_context_data(self, **kwargs):
104         context = {
105             'object': self.get_object(),
106         }
107         return super(NoteSaveView, self).get_context_data(**context)
108
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
112         """
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
119         self.object.save()
120         return super(NoteSaveView, self).form_valid(form)
121
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"
126         print form
127         print form.errors
128
129
130 class NoteView(View):
131     """ Notes superclass that wraps http methods """
132
133     def get(self, request, *args, **kwargs):
134         view = NoteDetailView.as_view()
135         return view(request, *args, **kwargs)
136
137     def post(self, request, *args, **kwargs):
138         view = NoteSaveView.as_view()
139         return view(request, *args, **kwargs)
140
141
142 class NoteDeleteView(FormView):
143     form_class = NoteDeleteForm
144
145     def form_valid(self, form):
146         self.note = Note.objects.get(id=form.cleaned_data['note'])
147         self.note.is_hidden = True
148         self.note.save()
149
150         messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
151
152         return super(FormView, self).form_valid(form)
153
154     def get_success_url(self):
155         return self.note.course.get_absolute_url()
156
157
158 class NoteKeywordsView(FormView, SingleObjectMixin):
159     """ Class-based view for the note html page """
160     model = Note
161     context_object_name = u"note" # name passed to template
162     form_class = formset_factory(KeywordForm)
163     template_name = 'notes/note_base.html'
164
165     def get_object(self, queryset=None):
166         return Note.objects.get(slug=self.kwargs['slug'])
167
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.")
172
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)
178         else:
179             self.keyword_formset = formset
180             return super(NoteKeywordsView, self).get(request, *args, **kwargs)
181
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)
186
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
192
193         note_page_context_helper(self.get_object(), self.request, kwargs)
194
195         return super(NoteKeywordsView, self).get_context_data(**kwargs)
196
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]
201         return initial_data
202
203     def keyword_form_valid(self, formset):
204         for form in formset:
205             word = form['keyword'].data
206             definition = form['definition'].data
207             id = form['id'].data
208             if word == '':
209                 continue
210             try:
211                 keyword_object = Keyword.objects.get(id=id)
212             except (ValueError, ObjectDoesNotExist):
213                 keyword_object = Keyword()
214
215             keyword_object.note = self.get_object()
216             keyword_object.word = word
217             keyword_object.definition = definition
218             keyword_object.save()
219
220
221 class NoteQuizView(TemplateView):
222     template_name = 'notes/note_base.html'
223
224     def get_context_data(self, **kwargs):
225         note = Note.objects.get(slug=self.kwargs['slug'])
226
227         note_page_context_helper(note, self.request, kwargs)
228
229         kwargs['note'] = note
230         kwargs['questions'] = quiz_from_keywords(note)
231         kwargs['show_quiz'] = True
232
233         return super(NoteQuizView, self).get_context_data(**kwargs)
234
235
236 class NoteSearchView(ListView):
237     template_name = 'notes/search_results.html'
238
239     def get_queryset(self):
240         if not 'query' in self.request.GET:
241             return Note.objects.none()
242
243         if 'page' in self.request.GET:
244             page = int(self.request.GET['page'])
245         else:
246             page = 0
247
248         try:
249             index = SearchIndex()
250
251             if 'course_id' in self.request.GET:
252                 raw_results = index.search(self.request.GET['query'],
253                                                   self.request.GET['course_id'],
254                                                   page=page)
255             else:
256                 raw_results = index.search(self.request.GET['query'],
257                                             page=page)
258
259         except Exception:
260             logger.error("Error with IndexDen:\n" + traceback.format_exc())
261             self.error = True
262             return Note.objects.none()
263         else:
264             self.error = False
265
266         instances = Note.objects.in_bulk(raw_results.ordered_ids)
267         results = []
268         for id in raw_results.ordered_ids:
269             if id in instances:
270                 results.append((instances[id], raw_results.snippet_dict[id]))
271         self.has_more = raw_results.has_more
272
273         return results
274
275     def get_context_data(self, **kwargs):
276         if 'query' in self.request.GET:
277             kwargs['query'] = self.request.GET['query']
278
279         if 'course_id' in self.request.GET:
280             kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
281
282         if self.error:
283             kwargs['error'] = True
284             return super(NoteSearchView, self).get_context_data(**kwargs)
285
286         # If query returned more search results than could
287         # fit on one page, show "Next" button
288         if self.has_more:
289             kwargs['has_next'] = True
290             if 'page' in self.request.GET:
291                 kwargs['next_page'] = int(self.request.GET['page']) + 1
292             else:
293                 kwargs['next_page'] = 1
294
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
301
302         return super(NoteSearchView, self).get_context_data(**kwargs)
303
304
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)
309
310     if course_form.is_valid():
311         course_form.save()
312
313         course_json = serializers.serialize('json', [course,])
314         resp = json.loads(course_json)[0]
315
316         if (course.name != original_name):
317             course.set_slug()
318             resp['fields']['new_url'] = course.get_absolute_url()
319
320         return HttpResponse(json.dumps(resp), mimetype="application/json")
321     else:
322         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
323                                       'errors': course_form.errors}),
324                                       mimetype="application/json")
325
326 def edit_note(request, pk):
327     """
328     Saves the edited note metadata
329     """
330     ajax_base(request, edit_note, ['POST'])
331
332
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)
337
338     # If note thanks exceeds a threshold, create a Mechanical
339     # Turk task to get some keywords for it
340     if note.thanks == 3:
341         submit_extract_keywords_hit(note)
342
343
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)
347
348
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)
357
358
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)
362
363
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)
370
371
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)
375
376
377 def edit_note_tags(request, pk):
378     """
379     Saves the posted string of tags
380     """
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)
384
385         note_json = serializers.serialize('json', [note,])
386         resp = json.loads(note_json)[0]
387         resp['fields']['tags'] = list(note.tags.names())
388
389         return HttpResponse(json.dumps(resp), mimetype="application/json")
390     else:
391         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
392                                       mimetype="application/json")
393