d1db236ebf1649efb0544ba891c6dba5e3cca2e6
[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.forms import KeywordForm
15 from karmaworld.apps.quizzes.models import Keyword
16 from karmaworld.apps.users.models import NoteKarmaEvent
17 from karmaworld.utils.ajax_utils import *
18
19 from django.http import HttpResponse, HttpResponseBadRequest
20 from django.views.generic import DetailView, ListView
21 from django.views.generic import FormView
22 from django.views.generic import View
23 from django.views.generic.detail import SingleObjectMixin
24
25 from karmaworld.apps.notes.models import Note
26 from karmaworld.apps.notes.forms import NoteForm, NoteDeleteForm
27
28
29 logger = logging.getLogger(__name__)
30
31 THANKS_FIELD = 'thanks'
32 USER_PROFILE_THANKS_FIELD = 'thanked_notes'
33 FLAG_FIELD = 'flags'
34 USER_PROFILE_FLAGS_FIELD = 'flagged_notes'
35
36
37 def note_page_context_helper(note, request, context):
38
39     if request.method == 'POST':
40         context['note_edit_form'] = NoteForm(request.POST)
41     else:
42         tags_string = ','.join([str(tag) for tag in note.tags.all()])
43         context['note_edit_form'] = NoteForm(initial={'name': note.name,
44                                                       'tags': tags_string})
45
46     context['note_delete_form'] = NoteDeleteForm(initial={'note': note.id})
47
48     if note.is_pdf():
49         context['pdf_controls'] = True
50
51     if request.user.is_authenticated():
52         try:
53             request.user.get_profile().thanked_notes.get(pk=note.pk)
54             context['already_thanked'] = True
55         except ObjectDoesNotExist:
56             pass
57
58         try:
59             request.user.get_profile().flagged_notes.get(pk=note.pk)
60             context['already_flagged'] = True
61         except ObjectDoesNotExist:
62             pass
63
64
65 class NoteDetailView(DetailView):
66     """ Class-based view for the note html page """
67     model = Note
68     context_object_name = u"note" # name passed to template
69
70     def get_context_data(self, **kwargs):
71         """ Generate custom context for the page rendering a Note
72             + if pdf, set the `pdf` flag
73         """
74
75         kwargs['show_note_container'] = True
76
77         note_page_context_helper(self.object, self.request, kwargs)
78
79         return super(NoteDetailView, self).get_context_data(**kwargs)
80
81
82 class NoteSaveView(FormView, SingleObjectMixin):
83     """ Save a Note and then view the page, 
84         behaves the same as NoteDetailView, except for saving the
85         NoteForm ModelForm
86     """
87     form_class = NoteForm
88     model = Note
89     template_name = 'notes/note_detail.html'
90
91     def post(self, request, *args, **kwargs):
92         self.object = self.get_object()
93         if self.object.user != request.user:
94             raise ValidationError("Only the owner of a note can edit it.")
95         return super(NoteSaveView, self).post(request, *args, **kwargs)
96
97     def get_success_url(self):
98         return self.object.get_absolute_url()
99
100     def get_context_data(self, **kwargs):
101         context = {
102             'object': self.get_object(),
103         }
104         return super(NoteSaveView, self).get_context_data(**context)
105
106     def form_valid(self, form):
107         """ Actions to take if the submitted form is valid
108             namely, saving the new data to the existing note object
109         """
110         if len(form.cleaned_data['name'].strip()) > 0:
111             self.object.name = form.cleaned_data['name']
112         # use *arg expansion to pass tags a list of tags
113         self.object.tags.set(*form.cleaned_data['tags'])
114         # User has submitted this form, so set the SHOW flag
115         self.object.is_hidden = False
116         self.object.save()
117         return super(NoteSaveView, self).form_valid(form)
118
119     def form_invalid(self, form):
120         """ Do stuff when the form is invalid !!! TODO """
121         # TODO: implement def form_invalid for returning a form with input and error
122         print "running form_invalid"
123         print form
124         print form.errors
125
126
127 class NoteView(View):
128     """ Notes superclass that wraps http methods """
129
130     def get(self, request, *args, **kwargs):
131         view = NoteDetailView.as_view()
132         return view(request, *args, **kwargs)
133
134     def post(self, request, *args, **kwargs):
135         view = NoteSaveView.as_view()
136         return view(request, *args, **kwargs)
137
138
139 class NoteDeleteView(FormView):
140     form_class = NoteDeleteForm
141
142     def form_valid(self, form):
143         self.note = Note.objects.get(id=form.cleaned_data['note'])
144         self.note.is_hidden = True
145         self.note.save()
146
147         messages.success(self.request, 'The note "{0}" was deleted successfully.'.format(self.note.name))
148
149         return super(FormView, self).form_valid(form)
150
151     def get_success_url(self):
152         return self.note.course.get_absolute_url()
153
154
155 class NoteKeywordsView(FormView, SingleObjectMixin):
156     """ Class-based view for the note html page """
157     model = Note
158     context_object_name = u"note" # name passed to template
159     form_class = formset_factory(KeywordForm)
160     template_name = 'notes/note_detail.html'
161
162     def get_object(self, queryset=None):
163         return Note.objects.get(slug=self.kwargs['slug'])
164
165     def post(self, request, *args, **kwargs):
166         self.object = self.get_object()
167         formset = self.form_class(request.POST)
168         if formset.is_valid():
169             self.keyword_form_valid(formset)
170             self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
171             return super(NoteKeywordsView, self).get(request, *args, **kwargs)
172         else:
173             self.keyword_formset = formset
174             return super(NoteKeywordsView, self).get(request, *args, **kwargs)
175
176     def get(self, request, *args, **kwargs):
177         self.object = self.get_object()
178         self.keyword_formset = self.form_class(initial=self.get_initial_keywords())
179         return super(NoteKeywordsView, self).get(request, *args, **kwargs)
180
181     def get_context_data(self, **kwargs):
182         kwargs['keyword_prototype_form'] = KeywordForm
183         kwargs['keyword_formset'] = self.keyword_formset
184         kwargs['keywords'] = Keyword.objects.filter(note=self.get_object())
185         kwargs['show_keywords'] = True
186
187         note_page_context_helper(self.get_object(), self.request, kwargs)
188
189         return super(NoteKeywordsView, self).get_context_data(**kwargs)
190
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]
195         return initial_data
196
197     def keyword_form_valid(self, formset):
198         for form in formset:
199             word = form['keyword'].data
200             definition = form['definition'].data
201             id = form['id'].data
202             if word == '':
203                 continue
204             try:
205                 keyword_object = Keyword.objects.get(id=id)
206             except (ValueError, ObjectDoesNotExist):
207                 keyword_object = Keyword()
208
209             keyword_object.note = self.get_object()
210             keyword_object.word = word
211             keyword_object.definition = definition
212             keyword_object.save()
213
214
215 class NoteSearchView(ListView):
216     template_name = 'notes/search_results.html'
217
218     def get_queryset(self):
219         if not 'query' in self.request.GET:
220             return Note.objects.none()
221
222         if 'page' in self.request.GET:
223             page = int(self.request.GET['page'])
224         else:
225             page = 0
226
227         try:
228             index = SearchIndex()
229
230             if 'course_id' in self.request.GET:
231                 raw_results = index.search(self.request.GET['query'],
232                                                   self.request.GET['course_id'],
233                                                   page=page)
234             else:
235                 raw_results = index.search(self.request.GET['query'],
236                                             page=page)
237
238         except Exception:
239             logger.error("Error with IndexDen:\n" + traceback.format_exc())
240             self.error = True
241             return Note.objects.none()
242         else:
243             self.error = False
244
245         instances = Note.objects.in_bulk(raw_results.ordered_ids)
246         results = []
247         for id in raw_results.ordered_ids:
248             if id in instances:
249                 results.append((instances[id], raw_results.snippet_dict[id]))
250         self.has_more = raw_results.has_more
251
252         return results
253
254     def get_context_data(self, **kwargs):
255         if 'query' in self.request.GET:
256             kwargs['query'] = self.request.GET['query']
257
258         if 'course_id' in self.request.GET:
259             kwargs['course'] = Course.objects.get(id=self.request.GET['course_id'])
260
261         if self.error:
262             kwargs['error'] = True
263             return super(NoteSearchView, self).get_context_data(**kwargs)
264
265         # If query returned more search results than could
266         # fit on one page, show "Next" button
267         if self.has_more:
268             kwargs['has_next'] = True
269             if 'page' in self.request.GET:
270                 kwargs['next_page'] = int(self.request.GET['page']) + 1
271             else:
272                 kwargs['next_page'] = 1
273
274         # If the user is looking at a search result page
275         # that isn't the first one, show "Prev" button
276         if 'page' in self.request.GET and \
277             int(self.request.GET['page']) > 0:
278             kwargs['has_prev'] = True
279             kwargs['prev_page'] = int(self.request.GET['page']) - 1
280
281         return super(NoteSearchView, self).get_context_data(**kwargs)
282
283
284 def handle_edit_note(request):
285     course = Course.objects.get(pk=pk)
286     original_name = course.name
287     course_form = CourseForm(request.POST or None, instance=course)
288
289     if course_form.is_valid():
290         course_form.save()
291
292         course_json = serializers.serialize('json', [course,])
293         resp = json.loads(course_json)[0]
294
295         if (course.name != original_name):
296             course.set_slug()
297             resp['fields']['new_url'] = course.get_absolute_url()
298
299         return HttpResponse(json.dumps(resp), mimetype="application/json")
300     else:
301         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
302                                       'errors': course_form.errors}),
303                                       mimetype="application/json")
304
305 def edit_note(request, pk):
306     """
307     Saves the edited note metadata
308     """
309     ajax_base(request, edit_note, ['POST'])
310
311
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)
316
317
318 def thank_note(request, pk):
319     """Record that somebody has thanked a note."""
320     return ajax_increment(Note, request, pk, THANKS_FIELD, USER_PROFILE_THANKS_FIELD, process_note_thank_events)
321
322
323 def process_note_flag_events(request_user, note):
324     # Take a point away from person flagging this note
325     if request_user.is_authenticated():
326         NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.GIVE_FLAG)
327     # If this is the 6th time this note has been flagged,
328     # punish the uploader
329     if note.flags == 6 and note.user:
330         NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.GET_FLAGGED)
331
332
333 def flag_note(request, pk):
334     """Record that somebody has flagged a note."""
335     return ajax_increment(Note, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_note_flag_events)
336
337
338 def process_downloaded_note(request_user, note):
339     """Record that somebody has downloaded a note"""
340     if request_user.is_authenticated() and request_user != note.user:
341         NoteKarmaEvent.create_event(request_user, note, NoteKarmaEvent.DOWNLOADED_NOTE)
342     if request_user.is_authenticated() and note.user:
343         NoteKarmaEvent.create_event(note.user, note, NoteKarmaEvent.HAD_NOTE_DOWNLOADED)
344
345
346 def downloaded_note(request, pk):
347     """Record that somebody has flagged a note."""
348     return ajax_pk_base(Note, request, pk, process_downloaded_note)
349
350
351 def edit_note_tags(request, pk):
352     """
353     Saves the posted string of tags
354     """
355     if request.method == "POST" and request.is_ajax() and request.user.is_authenticated() and request.user.get_profile().can_edit_items():
356         note = Note.objects.get(pk=pk)
357         note.tags.set(request.body)
358
359         note_json = serializers.serialize('json', [note,])
360         resp = json.loads(note_json)[0]
361         resp['fields']['tags'] = list(note.tags.names())
362
363         return HttpResponse(json.dumps(resp), mimetype="application/json")
364     else:
365         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
366                                       mimetype="application/json")
367