082a7e41f67de89fd4d81dcb5743e2b83abe6da1
[oweals/karmaworld.git] / karmaworld / apps / courses / views.py
1 #!/usr/bin/env python
2 # -*- coding:utf8 -*-
3 # Copyright (C) 2012  FinalsClub Foundation
4 """ Views for the KarmaNotes Courses app """
5
6 import json
7 from django.conf import settings
8 from django.core import serializers
9 from django.core.exceptions import MultipleObjectsReturned
10 from django.core.exceptions import ObjectDoesNotExist
11
12 from django.http import HttpResponse, HttpResponseBadRequest
13 from django.views.generic import View
14 from django.views.generic import DetailView
15 from django.views.generic import TemplateView
16 from django.views.generic.list import ListView
17 from django.views.generic.edit import CreateView
18
19 from karmaworld.apps.courses.models import Course
20 from karmaworld.apps.courses.models import School
21 from karmaworld.apps.courses.forms import CourseForm
22 from karmaworld.apps.notes.models import Note
23 from karmaworld.apps.users.models import CourseKarmaEvent
24 from karmaworld.apps.notes.forms import FileUploadForm
25 from karmaworld.utils import ajax_increment, format_session_increment_field
26 from django.contrib import messages
27
28 FLAG_FIELD = 'flags'
29 USER_PROFILE_FLAGS_FIELD = 'flagged_courses'
30
31
32 # https://docs.djangoproject.com/en/1.5/topics/class-based-views/mixins/#an-alternative-better-solution
33 class CourseListView(View):
34     """
35     Composite view to list all courses and processes new course additions.
36     """
37
38     def get(self, request, *args, **kwargs):
39         return CourseListSubView.as_view()(request, *args, **kwargs)
40
41     def post(self, request, *args, **kwargs):
42         ret = CourseAddFormView.as_view()(request, *args, **kwargs)
43         # Check to see if the form came back with errors.
44         if hasattr(ret, 'context_data') and \
45            ret.context_data.has_key('form') and \
46            not ret.context_data['form'].is_valid():
47             # Invalid form. Render as if by get(), but replace the form.
48             badform = ret.context_data['form']
49             request.method = 'GET' # trick get() into returning something
50             ret = self.get(request, *args, **kwargs)
51             # Replace blank form with invalid form.
52             ret.context_data['course_form'] = badform
53             ret.context_data['jump_to_form'] = True
54         else:
55             messages.add_message(request, messages.SUCCESS, 'You\'ve just created this course. Nice!')
56         return ret
57
58
59 class CourseListSubView(ListView):
60     """ Lists all courses. Called by CourseListView. """
61     model = Course
62
63     def get_context_data(self, **kwargs):
64         """ Add the CourseForm to ListView context """
65         # get the original context
66         context = super(CourseListSubView, self).get_context_data(**kwargs)
67         # get the total number of notes
68         context['note_count'] = Note.objects.count()
69         # get the course form for the form at the bottom of the homepage
70         context['course_form'] = CourseForm()
71
72         # Include settings constants for honeypot
73         for key in ('HONEYPOT_FIELD_NAME', 'HONEYPOT_VALUE'):
74             context[key] = getattr(settings, key)
75
76         return context
77
78
79 class CourseAddFormView(CreateView):
80     """ Processes new course additions. Called by CourseListView. """
81     model = Course
82     form_class = CourseForm
83
84     def get_template_names(self):
85         """ template_name must point back to CourseListView url """
86         # TODO clean this up. "_list" template might come from ListView above.
87         return ['courses/course_list.html',]
88
89
90 class CourseDetailView(DetailView):
91     """ Class-based view for the course html page """
92     model = Course
93     context_object_name = u"course" # name passed to template
94
95     def get_context_data(self, **kwargs):
96         """ filter the Course.note_set to return no Drafts """
97         kwargs = super(CourseDetailView, self).get_context_data()
98         kwargs['note_set'] = self.object.note_set.filter(is_hidden=False)
99
100         # For the Filepicker Partial template
101         kwargs['file_upload_form'] = FileUploadForm()
102
103         if self.request.user.is_authenticated():
104             try:
105                 self.request.user.get_profile().flagged_courses.get(pk=self.object.pk)
106                 kwargs['already_flagged'] = True
107             except ObjectDoesNotExist:
108                 pass
109
110         return kwargs
111
112
113 class AboutView(TemplateView):
114     """ Display the About page with the Schools leaderboard """
115     template_name = "about.html"
116
117     def get_context_data(self, **kwargs):
118         """ get the list of schools with the most files for leaderboard """
119         if 'schools' not in kwargs:
120             kwargs['schools'] = School.objects.filter(file_count__gt=0).order_by('-file_count')[:20]
121         return kwargs
122
123
124 def school_list(request):
125     """ Return JSON describing Schools that match q query on name """
126     if not (request.method == 'POST' and request.is_ajax()
127                         and request.POST.has_key('q')):
128         #return that the api call failed
129         return HttpResponseBadRequest(json.dumps({'status':'fail'}), mimetype="application/json")
130
131     # if an ajax get request with a 'q' name query
132     # get the schools as a id name dict,
133     _query = request.POST['q']
134     matching_school_aliases = list(School.objects.filter(alias__icontains=_query))
135     matching_school_names = sorted(list(School.objects.filter(name__icontains=_query)[:20]),key=lambda o:len(o.name))
136     _schools = matching_school_aliases[:2] + matching_school_names
137     schools = [{'id': s.id, 'name': s.name} for s in _schools]
138
139     # return as json
140     return HttpResponse(json.dumps({'status':'success', 'schools': schools}), mimetype="application/json")
141
142
143 def school_course_list(request):
144     """Return JSON describing courses we know of at the given school
145      that match the query """
146     if not (request.method == 'POST' and request.is_ajax()
147                         and request.POST.has_key('q')
148                         and request.POST.has_key('school_id')):
149         # return that the api call failed
150         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
151                                     mimetype="application/json")
152
153     _query = request.POST['q']
154     try:
155       _school_id = int(request.POST['school_id'])
156     except:
157       return HttpResponseBadRequest(json.dumps({'status': 'fail',
158                                               'message': 'could not convert school id to integer'}),
159                                   mimetype="application/json")
160
161     # Look up the school
162     try:
163         school = School.objects.get(id__exact=_school_id)
164     except (MultipleObjectsReturned, ObjectDoesNotExist):
165         return HttpResponseBadRequest(json.dumps({'status': 'fail',
166                                                 'message': 'school id did not match exactly one school'}),
167                                     mimetype="application/json")
168
169     # Look up matching courses
170     _courses = Course.objects.filter(school__exact=school.id, name__icontains=_query)
171     courses = [{'name': c.name} for c in _courses]
172
173     # return as json
174     return HttpResponse(json.dumps({'status':'success', 'courses': courses}),
175                         mimetype="application/json")
176
177
178 def school_course_instructor_list(request):
179     """Return JSON describing instructors we know of at the given school
180        teaching the given course
181        that match the query """
182     if not(request.method == 'POST' and request.is_ajax()
183                         and request.POST.has_key('q')
184                         and request.POST.has_key('course_name')
185                         and request.POST.has_key('school_id')):
186         # return that the api call failed
187         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
188                                     mimetype="application/json")
189
190     _query = request.POST['q']
191     _course_name = request.POST['course_name']
192     try:
193       _school_id = int(request.POST['school_id'])
194     except:
195       return HttpResponseBadRequest(json.dumps({'status': 'fail',
196                                               'message':'could not convert school id to integer'}),
197                                   mimetype="application/json")
198
199     # Look up the school
200     try:
201         school = School.objects.get(id__exact=_school_id)
202     except (MultipleObjectsReturned, ObjectDoesNotExist):
203         return HttpResponseBadRequest(json.dumps({'status': 'fail',
204                                                   'message': 'school id did not match exactly one school'}),
205                                     mimetype="application/json")
206
207     # Look up matching courses
208     _courses = Course.objects.filter(school__exact=school.id,
209                                      name__exact=_course_name,
210                                      instructor_name__icontains=_query)
211     instructors = [{'name': c.instructor_name, 'url': c.get_absolute_url()} for c in _courses]
212
213     # return as json
214     return HttpResponse(json.dumps({'status':'success', 'instructors': instructors}),
215                         mimetype="application/json")
216
217
218 def process_course_flag_events(request_user, course):
219     # Take a point away from person flagging this course
220     if request_user.is_authenticated():
221         CourseKarmaEvent.create_event(request_user, course, CourseKarmaEvent.GIVE_FLAG)
222
223
224 def flag_course(request, pk):
225     """Record that somebody has flagged a note."""
226     return ajax_increment(Course, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_course_flag_events)
227
228
229 def edit_course(request, pk):
230     """
231     Saves the edited course content
232     """
233     if request.method == "POST" and request.is_ajax():
234         course = Course.objects.get(pk=pk)
235         original_name = course.name
236         course_form = CourseForm(request.POST or None, instance=course)
237
238         if course_form.is_valid():
239             course_form.save()
240
241             course_json = serializers.serialize('json', [course,])
242             resp = json.loads(course_json)[0]
243
244             if (course.name != original_name):
245                 course.set_slug()
246                 resp['fields']['new_url'] = course.get_absolute_url()
247
248             return HttpResponse(json.dumps(resp), mimetype="application/json")
249         else:
250             return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
251                                           'errors': course_form.errors}),
252                                           mimetype="application/json")
253     else:
254         return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
255                                       mimetype="application/json")