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.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
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
29 USER_PROFILE_FLAGS_FIELD = 'flagged_courses'
32 # https://docs.djangoproject.com/en/1.5/topics/class-based-views/mixins/#an-alternative-better-solution
33 class CourseListView(View):
35 Composite view to list all courses and processes new course additions.
38 def get(self, request, *args, **kwargs):
39 return CourseListSubView.as_view()(request, *args, **kwargs)
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
55 messages.add_message(request, messages.SUCCESS, 'You\'ve just created this course. Nice!')
59 class CourseListSubView(ListView):
60 """ Lists all courses. Called by CourseListView. """
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()
72 # Include settings constants for honeypot
73 for key in ('HONEYPOT_FIELD_NAME', 'HONEYPOT_VALUE'):
74 context[key] = getattr(settings, key)
79 class CourseAddFormView(CreateView):
80 """ Processes new course additions. Called by CourseListView. """
82 form_class = CourseForm
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',]
90 class CourseDetailView(DetailView):
91 """ Class-based view for the course html page """
93 context_object_name = u"course" # name passed to template
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)
100 # For the Filepicker Partial template
101 kwargs['file_upload_form'] = FileUploadForm()
103 if self.request.user.is_authenticated():
105 self.request.user.get_profile().flagged_courses.get(pk=self.object.pk)
106 kwargs['already_flagged'] = True
107 except ObjectDoesNotExist:
113 class AboutView(TemplateView):
114 """ Display the About page with the Schools leaderboard """
115 template_name = "about.html"
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]
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")
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]
140 return HttpResponse(json.dumps({'status':'success', 'schools': schools}), mimetype="application/json")
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")
153 _query = request.POST['q']
155 _school_id = int(request.POST['school_id'])
157 return HttpResponseBadRequest(json.dumps({'status': 'fail',
158 'message': 'could not convert school id to integer'}),
159 mimetype="application/json")
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")
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]
174 return HttpResponse(json.dumps({'status':'success', 'courses': courses}),
175 mimetype="application/json")
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")
190 _query = request.POST['q']
191 _course_name = request.POST['course_name']
193 _school_id = int(request.POST['school_id'])
195 return HttpResponseBadRequest(json.dumps({'status': 'fail',
196 'message':'could not convert school id to integer'}),
197 mimetype="application/json")
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")
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]
214 return HttpResponse(json.dumps({'status':'success', 'instructors': instructors}),
215 mimetype="application/json")
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)
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)
229 def edit_course(request, pk):
231 Saves the edited course content
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)
238 if course_form.is_valid():
241 course_json = serializers.serialize('json', [course,])
242 resp = json.loads(course_json)[0]
244 if (course.name != original_name):
246 resp['fields']['new_url'] = course.get_absolute_url()
248 return HttpResponse(json.dumps(resp), mimetype="application/json")
250 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
251 'errors': course_form.errors}),
252 mimetype="application/json")
254 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
255 mimetype="application/json")