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_queryset(self):
64 return Course.objects.all().select_related('note_set', 'school', 'department', 'department__school')
66 def get_context_data(self, **kwargs):
67 """ Add the CourseForm to ListView context """
68 # get the original context
69 context = super(CourseListSubView, self).get_context_data(**kwargs)
70 # get the total number of notes
71 context['note_count'] = Note.objects.count()
72 # get the course form for the form at the bottom of the homepage
73 context['course_form'] = CourseForm()
75 # Include settings constants for honeypot
76 for key in ('HONEYPOT_FIELD_NAME', 'HONEYPOT_VALUE'):
77 context[key] = getattr(settings, key)
82 class CourseAddFormView(CreateView):
83 """ Processes new course additions. Called by CourseListView. """
85 form_class = CourseForm
87 def get_template_names(self):
88 """ template_name must point back to CourseListView url """
89 # TODO clean this up. "_list" template might come from ListView above.
90 return ['courses/course_list.html',]
93 class CourseDetailView(DetailView):
94 """ Class-based view for the course html page """
96 context_object_name = u"course" # name passed to template
98 def get_context_data(self, **kwargs):
99 """ filter the Course.note_set to return no Drafts """
100 kwargs = super(CourseDetailView, self).get_context_data()
101 kwargs['note_set'] = self.object.note_set.filter(is_hidden=False)
103 # For the Filepicker Partial template
104 kwargs['file_upload_form'] = FileUploadForm()
106 if self.request.user.is_authenticated():
108 self.request.user.get_profile().flagged_courses.get(pk=self.object.pk)
109 kwargs['already_flagged'] = True
110 except ObjectDoesNotExist:
116 class AboutView(TemplateView):
117 """ Display the About page with the Schools leaderboard """
118 template_name = "about.html"
120 def get_context_data(self, **kwargs):
121 """ get the list of schools with the most files for leaderboard """
122 if 'schools' not in kwargs:
123 kwargs['schools'] = School.objects.filter(file_count__gt=0).order_by('-file_count')[:20]
127 def school_list(request):
128 """ Return JSON describing Schools that match q query on name """
129 if not (request.method == 'POST' and request.is_ajax()
130 and request.POST.has_key('q')):
131 #return that the api call failed
132 return HttpResponseBadRequest(json.dumps({'status':'fail'}), mimetype="application/json")
134 # if an ajax get request with a 'q' name query
135 # get the schools as a id name dict,
136 _query = request.POST['q']
137 matching_school_aliases = list(School.objects.filter(alias__icontains=_query))
138 matching_school_names = sorted(list(School.objects.filter(name__icontains=_query)[:20]),key=lambda o:len(o.name))
139 _schools = matching_school_aliases[:2] + matching_school_names
140 schools = [{'id': s.id, 'name': s.name} for s in _schools]
143 return HttpResponse(json.dumps({'status':'success', 'schools': schools}), mimetype="application/json")
146 def school_course_list(request):
147 """Return JSON describing courses we know of at the given school
148 that match the query """
149 if not (request.method == 'POST' and request.is_ajax()
150 and request.POST.has_key('q')
151 and request.POST.has_key('school_id')):
152 # return that the api call failed
153 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
154 mimetype="application/json")
156 _query = request.POST['q']
158 _school_id = int(request.POST['school_id'])
160 return HttpResponseBadRequest(json.dumps({'status': 'fail',
161 'message': 'could not convert school id to integer'}),
162 mimetype="application/json")
166 school = School.objects.get(id__exact=_school_id)
167 except (MultipleObjectsReturned, ObjectDoesNotExist):
168 return HttpResponseBadRequest(json.dumps({'status': 'fail',
169 'message': 'school id did not match exactly one school'}),
170 mimetype="application/json")
172 # Look up matching courses
173 _courses = Course.objects.filter(school__exact=school.id, name__icontains=_query)
174 courses = [{'name': c.name} for c in _courses]
177 return HttpResponse(json.dumps({'status':'success', 'courses': courses}),
178 mimetype="application/json")
181 def school_course_instructor_list(request):
182 """Return JSON describing instructors we know of at the given school
183 teaching the given course
184 that match the query """
185 if not(request.method == 'POST' and request.is_ajax()
186 and request.POST.has_key('q')
187 and request.POST.has_key('course_name')
188 and request.POST.has_key('school_id')):
189 # return that the api call failed
190 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'query parameters missing'}),
191 mimetype="application/json")
193 _query = request.POST['q']
194 _course_name = request.POST['course_name']
196 _school_id = int(request.POST['school_id'])
198 return HttpResponseBadRequest(json.dumps({'status': 'fail',
199 'message':'could not convert school id to integer'}),
200 mimetype="application/json")
204 school = School.objects.get(id__exact=_school_id)
205 except (MultipleObjectsReturned, ObjectDoesNotExist):
206 return HttpResponseBadRequest(json.dumps({'status': 'fail',
207 'message': 'school id did not match exactly one school'}),
208 mimetype="application/json")
210 # Look up matching courses
211 _courses = Course.objects.filter(school__exact=school.id,
212 name__exact=_course_name,
213 instructor_name__icontains=_query)
214 instructors = [{'name': c.instructor_name, 'url': c.get_absolute_url()} for c in _courses]
217 return HttpResponse(json.dumps({'status':'success', 'instructors': instructors}),
218 mimetype="application/json")
221 def process_course_flag_events(request_user, course):
222 # Take a point away from person flagging this course
223 if request_user.is_authenticated():
224 CourseKarmaEvent.create_event(request_user, course, CourseKarmaEvent.GIVE_FLAG)
227 def flag_course(request, pk):
228 """Record that somebody has flagged a note."""
229 return ajax_increment(Course, request, pk, FLAG_FIELD, USER_PROFILE_FLAGS_FIELD, process_course_flag_events)
232 def edit_course(request, pk):
234 Saves the edited course content
236 if request.method == "POST" and request.is_ajax():
237 course = Course.objects.get(pk=pk)
238 original_name = course.name
239 course_form = CourseForm(request.POST or None, instance=course)
241 if course_form.is_valid():
244 course_json = serializers.serialize('json', [course,])
245 resp = json.loads(course_json)[0]
247 if (course.name != original_name):
249 resp['fields']['new_url'] = course.get_absolute_url()
251 return HttpResponse(json.dumps(resp), mimetype="application/json")
253 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Validation error',
254 'errors': course_form.errors}),
255 mimetype="application/json")
257 return HttpResponseBadRequest(json.dumps({'status': 'fail', 'message': 'Invalid request'}),
258 mimetype="application/json")