3 # Copyright (C) 2012 FinalsClub Foundation
4 """ Views for the KarmaNotes Courses app """
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
12 from django.http import HttpResponse, HttpResponseBadRequest
13 from django.views.decorators.cache import cache_page
14 from django.views.generic import View
15 from django.views.generic import DetailView
16 from django.views.generic import TemplateView
17 from django.views.generic.list import ListView
18 from django.views.generic.edit import CreateView
20 from karmaworld.apps.courses.models import Course
21 from karmaworld.apps.courses.models import School
22 from karmaworld.apps.courses.forms import CourseForm
23 from karmaworld.apps.notes.models import Note
24 from karmaworld.apps.users.models import CourseKarmaEvent
25 from karmaworld.apps.notes.forms import FileUploadForm
26 from karmaworld.utils import ajax_increment, format_session_increment_field
27 from django.contrib import messages
30 USER_PROFILE_FLAGS_FIELD = 'flagged_courses'
33 # https://docs.djangoproject.com/en/1.5/topics/class-based-views/mixins/#an-alternative-better-solution
34 class CourseListView(View):
36 Composite view to list all courses and processes new course additions.
39 def get(self, request, *args, **kwargs):
40 if request.user.is_authenticated():
41 return CourseListSubView.as_view()(request, *args, **kwargs)
42 # Cache the homepage for non-authenicated users
44 return cache_page(CourseListSubView.as_view(), 60 * 60)(request, *args, **kwargs)
46 def post(self, request, *args, **kwargs):
47 ret = CourseAddFormView.as_view()(request, *args, **kwargs)
48 # Check to see if the form came back with errors.
49 if hasattr(ret, 'context_data') and \
50 ret.context_data.has_key('form') and \
51 not ret.context_data['form'].is_valid():
52 # Invalid form. Render as if by get(), but replace the form.
53 badform = ret.context_data['form']
54 request.method = 'GET' # trick get() into returning something
55 ret = self.get(request, *args, **kwargs)
56 # Replace blank form with invalid form.
57 ret.context_data['course_form'] = badform
58 ret.context_data['jump_to_form'] = True
60 messages.add_message(request, messages.SUCCESS, 'You\'ve just created this course. Nice!')
64 class CourseListSubView(ListView):
65 """ Lists all courses. Called by CourseListView. """
68 def get_queryset(self):
69 return Course.objects.all().select_related('note_set', 'school', 'department', 'department__school')
71 def get_context_data(self, **kwargs):
72 """ Add the CourseForm to ListView context """
73 # get the original context
74 context = super(CourseListSubView, self).get_context_data(**kwargs)
75 # get the total number of notes
76 context['note_count'] = Note.objects.count()
77 # get the course form for the form at the bottom of the homepage
78 context['course_form'] = CourseForm()
81 for course in self.object_list:
83 schools.add(course.school)
84 elif course.department and course.department.school:
85 schools.add(course.department.school)
87 context['schools'] = sorted(list(schools), key=lambda x: x.name)
89 # Include settings constants for honeypot
90 for key in ('HONEYPOT_FIELD_NAME', 'HONEYPOT_VALUE'):
91 context[key] = getattr(settings, key)
96 class CourseAddFormView(CreateView):
97 """ Processes new course additions. Called by CourseListView. """
99 form_class = CourseForm
101 def get_template_names(self):
102 """ template_name must point back to CourseListView url """
103 # TODO clean this up. "_list" template might come from ListView above.
104 return ['courses/course_list.html',]
107 class CourseDetailView(DetailView):
108 """ Class-based view for the course html page """
110 context_object_name = u"course" # name passed to template
112 def get_context_data(self, **kwargs):
113 """ filter the Course.note_set to return no Drafts """
114 kwargs = super(CourseDetailView, self).get_context_data()
115 kwargs['note_set'] = self.object.note_set.filter(is_hidden=False)
117 # For the Filepicker Partial template
118 kwargs['file_upload_form'] = FileUploadForm()
119 kwargs['note_categories'] = Note.NOTE_CATEGORIES
121 if self.request.user.is_authenticated():
123 self.request.user.get_profile().flagged_courses.get(pk=self.object.pk)
124 kwargs['already_flagged'] = True
125 except ObjectDoesNotExist:
131 class AboutView(TemplateView):
132 """ Display the About page with the Schools leaderboard """
133 template_name = "about.html"
135 def get_context_data(self, **kwargs):
136 """ get the list of schools with the most files for leaderboard """
137 if 'schools' not in kwargs:
138 kwargs['schools'] = School.objects.filter(file_count__gt=0).order_by('-file_count')[:20]
142 def school_list(request):
143 """ Return JSON describing Schools that match q query on name """
144 if not (request.method == 'POST' and request.is_ajax()
145 and request.POST.has_key('q')):
146 #return that the api call failed
147 return HttpResponseBadRequest(json.dumps({'status':'fail'}), mimetype="application/json")
149 # if an ajax get request with a 'q' name query
150 # get the schools as a id name dict,
151 _query = request.POST['q']
152 matching_school_aliases = list(School.objects.filter(alias__icontains=_query))
153 matching_school_names = sorted(list(School.objects.filter(name__icontains=_query)[:20]),key=lambda o:len(o.name))
154 _schools = matching_school_aliases[:2] + matching_school_names
155 schools = [{'id': s.id, 'name': s.name} for s in _schools]
158 return HttpResponse(json.dumps({'status':'success', 'schools': schools}), mimetype="application/json")
161 def school_course_list(request):
162 """Return JSON describing courses we know of at the given school
163 that match the query """
164 if not (request.method == 'POST' and request.is_ajax()
165 and request.POST.has_key('q')
166 and request.POST.has_key('school_id')):
167 # return that the api call failed
168 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
169 mimetype="application/json")
171 _query = request.POST['q']
173 _school_id = int(request.POST['school_id'])
175 return HttpResponseBadRequest(json.dumps({'status': 'fail',
176 'message': 'could not convert school id to integer'}),
177 mimetype="application/json")
181 school = School.objects.get(id__exact=_school_id)
182 except (MultipleObjectsReturned, ObjectDoesNotExist):
183 return HttpResponseBadRequest(json.dumps({'status': 'fail',
184 'message': 'school id did not match exactly one school'}),
185 mimetype="application/json")
187 # Look up matching courses
188 _courses = Course.objects.filter(school__exact=school.id, name__icontains=_query)
189 courses = [{'name': c.name} for c in _courses]
192 return HttpResponse(json.dumps({'status':'success', 'courses': courses}),
193 mimetype="application/json")
196 def school_course_instructor_list(request):
197 """Return JSON describing instructors we know of at the given school
198 teaching the given course
199 that match the query """
200 if not(request.method == 'POST' and request.is_ajax()
201 and request.POST.has_key('q')
202 and request.POST.has_key('course_name')
203 and request.POST.has_key('school_id')):
204 # return that the api call failed
205 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
206 mimetype="application/json")
208 _query = request.POST['q']
209 _course_name = request.POST['course_name']
211 _school_id = int(request.POST['school_id'])
213 return HttpResponseBadRequest(json.dumps({'status': 'fail',
214 'message':'could not convert school id to integer'}),
215 mimetype="application/json")
219 school = School.objects.get(id__exact=_school_id)
220 except (MultipleObjectsReturned, ObjectDoesNotExist):
221 return HttpResponseBadRequest(json.dumps({'status': 'fail',
222 'message': 'school id did not match exactly one school'}),
223 mimetype="application/json")
225 # Look up matching courses
226 _courses = Course.objects.filter(school__exact=school.id,
227 name__exact=_course_name,
228 instructor_name__icontains=_query)
229 instructors = [{'name': c.instructor_name, 'url': c.get_absolute_url()} for c in _courses]
232 return HttpResponse(json.dumps({'status':'success', 'instructors': instructors}),
233 mimetype="application/json")
236 def process_course_flag_events(request_user, course):
237 # Take a point away from person flagging this course
238 if request_user.is_authenticated():
239 CourseKarmaEvent.create_event(request_user, course, CourseKarmaEvent.GIVE_FLAG)
242 def flag_course(request, pk):
243 """Record that somebody has flagged a note."""
244 return ajax_increment(Course, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_course_flag_events)
247 def edit_course(request, pk):
249 Saves the edited course content
251 if request.method == "POST" and request.is_ajax():
252 course = Course.objects.get(pk=pk)
253 original_name = course.name
254 course_form = CourseForm(request.POST or None, instance=course)
256 if course_form.is_valid():
259 course_json = serializers.serialize('json', [course,])
260 resp = json.loads(course_json)[0]
262 if (course.name != original_name):
264 resp['fields']['new_url'] = course.get_absolute_url()
266 return HttpResponse(json.dumps(resp), mimetype="application/json")
268 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
269 'errors': course_form.errors}),
270 mimetype="application/json")
272 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
273 mimetype="application/json")