# Copyright (C) 2017 Semester.ly Technologies, LLC
#
# Semester.ly is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Semester.ly is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import collections
import json
from datetime import datetime
from django.http import HttpResponse
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from pytz import timezone
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from analytics.models import SharedCourseView
from courses.serializers import CourseSerializer
from student.models import Student
from student.utils import get_classmates_from_course_id
from timetable.models import Semester, Course
from timetable.school_mappers import SCHOOLS_MAP
from helpers.mixins import ValidateSubdomainMixin, FeatureFlowView
from helpers.decorators import validate_subdomain
from parsing.models import DataUpdate
# TODO: use CBV
@validate_subdomain
[docs]def all_courses(request):
"""
Generates the full course directory page. Includes links to all courses
and is sorted by department.
"""
school = request.subdomain
school_name = SCHOOLS_MAP[school].name # TODO: use single groupby query
dep_to_courses = collections.OrderedDict()
departments = Course.objects.filter(school=school) \
.order_by('department').values_list('department', flat=True).distinct()
for department in departments:
dep_to_courses[department] = Course.objects.filter(school=school,
department=department).all()
context = {
'course_map': dep_to_courses,
'school': school,
'school_name': school_name}
return render_to_response("all_courses.html",
context,
context_instance=RequestContext(request))
# TODO: use implementation in student
# TODO: should send along with course response
[docs]def get_classmates_in_course(request, school, sem_name, year, course_id):
"""
Finds all classmates for the authenticated user who also have a
timetable with the given course.
"""
school = school.lower()
sem, _ = Semester.objects.get_or_create(name=sem_name, year=year)
json_data = {'current': [], 'past': []}
course = Course.objects.get(school=school, id=course_id)
student = None
is_logged_in = request.user.is_authenticated()
if is_logged_in and Student.objects.filter(user=request.user).exists():
student = Student.objects.get(user=request.user)
if student and student.user.is_authenticated() and student.social_courses:
json_data = get_classmates_from_course_id(
school, student, course.id, sem)
return HttpResponse(json.dumps(json_data), content_type="application/json")
# TODO delete or rewrite as CBV
@validate_subdomain
[docs]def course_page(request, code):
"""
Generates a static course page for the provided course code and
school (via subdomain). Completely outside of the React framework
purely via Django templates.
"""
school = request.subdomain
try:
school_name = SCHOOLS_MAP[school].name
course_obj = Course.objects.filter(code__iexact=code)[0]
# TODO: hard coding (section type, semester)
current_year = datetime.now().year
semester, _ = Semester.objects.get_or_create(
name='Fall', year=current_year)
course_dict = CourseSerializer(course_obj,
context={'semester': semester, 'school': school}).data
l = course_dict['sections'].get('L', {}).values()
t = course_dict['sections'].get('T', {}).values()
p = course_dict['sections'].get('P', {}).values()
avg = round(course_obj.get_avg_rating(), 2)
evals = course_dict['evals']
clean_evals = evals
for i, v in enumerate(evals):
for k, e in v.items():
if isinstance(evals[i][k], basestring):
clean_evals[i][k] = evals[i][k].replace(u'\xa0', u' ')
if k == "year":
clean_evals[i][k] = evals[i][k].replace(":", " ")
if school == "jhu":
course_url = "/course/" + course_dict['code'] + "/F"
else:
course_url = "/course/" + course_dict['code'] + "/F"
context = {
'school': school,
'school_name': school_name,
'course': course_dict,
'lectures': l if l else None,
'tutorials': t if t else None,
'practicals': p if p else None,
'url': course_url,
'evals': clean_evals,
'avg': avg
}
return render_to_response("course_page.html",
context,
context_instance=RequestContext(request))
except Exception as e:
return HttpResponse(str(e))
[docs]class CourseDetail(ValidateSubdomainMixin, APIView):
"""View that handles individual course entities."""
[docs] def get(self, request, sem_name, year, course_id):
""" Return detailed data about a single course. Currently used for course modals. """
school = request.subdomain
sem, _ = Semester.objects.get_or_create(name=sem_name, year=year)
course = get_object_or_404(Course, school=school, id=course_id)
student = None
is_logged_in = request.user.is_authenticated()
if is_logged_in and Student.objects.filter(user=request.user).exists():
student = Student.objects.get(user=request.user)
json_data = CourseSerializer(course,
context={'semester': sem, 'student': student,
'school': request.subdomain})
return Response(json_data.data, status=status.HTTP_200_OK)
class SchoolList(APIView):
def get(self, request, school):
"""
Provides the basic school information including the schools
areas, departments, levels, and the time the data was last updated
"""
# TODO - last_updated should encode per-semester last updated statuses
last_updated = DataUpdate.objects.filter(
school=school,
update_type=DataUpdate.COURSES
).order_by('timestamp').first()
if last_updated is not None:
last_updated = '{} {}'.format(
last_updated.timestamp.strftime('%Y-%m-%d %H:%M'),
last_updated.timestamp.tzname()
)
json_data = {
'areas': sorted(list(Course.objects.filter(school=school)
.exclude(areas__exact='')
.values_list('areas', flat=True)
.distinct())),
'departments': sorted(list(Course.objects.filter(school=school)
.exclude(department__exact='')
.values_list('department', flat=True)
.distinct())),
'levels': sorted(list(Course.objects.filter(school=school)
.exclude(level__exact='')
.values_list('level', flat=True)
.distinct())),
'last_updated': last_updated
}
return Response(json_data, status=status.HTTP_200_OK)
[docs]class CourseModal(FeatureFlowView):
"""
A :obj:`FeatureFlowView` for loading a course share link
which directly opens the course modal on the frontend. Therefore,
this view overrides the *get_feature_flow* method to fill intData
with the detailed course json for the modal.abs
Saves a :obj:`SharedCourseView` for analytics purposes.
"""
feature_name = "SHARE_COURSE"
def get_feature_flow(self, request, code, sem_name, year):
semester, _ = Semester.objects.get_or_create(name=sem_name, year=year)
code = code.upper()
course = get_object_or_404(Course, school=self.school, code=code)
course_json = CourseSerializer(course,
context={'semester': semester, 'school': self.school,
'student': self.student})
# analytics
SharedCourseView.objects.create(
student=self.student,
shared_course=course,
).save()
return {'sharedCourse': course_json.data, 'semester': semester}