From 346d5b91a1c07aad0b51fca09fbac2aa1014d586 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 10:58:20 -0400 Subject: [PATCH 1/7] implement subdomain-based course displays --- common/djangoapps/external_auth/views.py | 7 ++- common/djangoapps/student/views.py | 7 ++- lms/djangoapps/courseware/courses.py | 78 +++++++++++++++++++++++- lms/djangoapps/courseware/views.py | 6 +- lms/envs/common.py | 6 ++ 5 files changed, 95 insertions(+), 9 deletions(-) diff --git a/common/djangoapps/external_auth/views.py b/common/djangoapps/external_auth/views.py index 0425f3e158..35e59db0ca 100644 --- a/common/djangoapps/external_auth/views.py +++ b/common/djangoapps/external_auth/views.py @@ -1,3 +1,4 @@ +import functools import json import logging import random @@ -156,7 +157,7 @@ def edXauth_signup(request, eamap=None): log.debug('ExtAuth: doing signup for %s' % eamap.external_email) - return student_views.main_index(extra_context=context) + return student_views.main_index(request, extra_context=context) #----------------------------------------------------------------------------- # MIT SSL @@ -206,7 +207,7 @@ def edXauth_ssl_login(request): pass if not cert: # no certificate information - go onward to main index - return student_views.main_index() + return student_views.main_index(request) (user, email, fullname) = ssl_dn_extract_info(cert) @@ -216,4 +217,4 @@ def edXauth_ssl_login(request): credentials=cert, email=email, fullname=fullname, - retfun = student_views.main_index) + retfun = functools.partial(student_views.main_index, request)) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index b6aa62e03d..e02a5f4562 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -68,9 +68,9 @@ def index(request): from external_auth.views import edXauth_ssl_login return edXauth_ssl_login(request) - return main_index(user=request.user) + return main_index(request, user=request.user) -def main_index(extra_context = {}, user=None): +def main_index(request, extra_context={}, user=None): ''' Render the edX main page. @@ -93,7 +93,8 @@ def main_index(extra_context = {}, user=None): entry.summary = soup.getText() # The course selection work is done in courseware.courses. - universities = get_courses_by_university(None) + universities = get_courses_by_university(None, + domain=request.META['HTTP_HOST']) context = {'universities': universities, 'entries': entries} context.update(extra_context) return render_to_response('index.html', context) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 2e74853760..5818252cf1 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -2,8 +2,8 @@ from collections import defaultdict from fs.errors import ResourceNotFoundError from functools import wraps import logging -from path import path +from path import path from django.conf import settings from django.http import Http404 @@ -142,19 +142,95 @@ def get_course_info_section(course, section_key): raise KeyError("Invalid about key " + str(section_key)) +<<<<<<< HEAD def get_courses_by_university(user): +======= +def course_staff_group_name(course): + ''' + course should be either a CourseDescriptor instance, or a string (the + .course entry of a Location) + ''' + if isinstance(course, str) or isinstance(course, unicode): + coursename = course + else: + # should be a CourseDescriptor, so grab its location.course: + coursename = course.location.course + return 'staff_%s' % coursename + +def has_staff_access_to_course(user, course): + ''' + Returns True if the given user has staff access to the course. + This means that user is in the staff_* group, or is an overall admin. + TODO (vshnayder): this needs to be changed to allow per-course_id permissions, not per-course + (e.g. staff in 2012 is different from 2013, but maybe some people always have access) + + course is the course field of the location being accessed. + ''' + if user is None or (not user.is_authenticated()) or course is None: + return False + if user.is_staff: + return True + + # note this is the Auth group, not UserTestGroup + user_groups = [x[1] for x in user.groups.values_list()] + staff_group = course_staff_group_name(course) + if staff_group in user_groups: + return True + return False + +def has_staff_access_to_course_id(user, course_id): + """Helper method that takes a course_id instead of a course name""" + loc = CourseDescriptor.id_to_location(course_id) + return has_staff_access_to_course(user, loc.course) + + +def has_staff_access_to_location(user, location): + """Helper method that checks whether the user has staff access to + the course of the location. + + location: something that can be passed to Location + """ + return has_staff_access_to_course(user, Location(location).course) + +def has_access_to_course(user, course): + '''course is the .course element of a location''' + if course.metadata.get('ispublic'): + return True + return has_staff_access_to_course(user,course) + + +def get_courses_by_university(user, domain=None): +>>>>>>> implement subdomain-based course displays ''' Returns dict of lists of courses available, keyed by course.org (ie university). Courses are sorted by course.number. ''' + subdomain = domain.split(".")[0] + # TODO: Clean up how 'error' is done. # filter out any courses that errored. + if settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): + if subdomain in settings.COURSE_LISTINGS: + visible_courses = settings.COURSE_LISTINGS[subdomain] + else: + visible_courses = frozenset(settings.COURSE_LISTINGS['default']) + courses = [c for c in modulestore().get_courses() if isinstance(c, CourseDescriptor)] courses = sorted(courses, key=lambda course: course.number) universities = defaultdict(list) for course in courses: +<<<<<<< HEAD if has_access(user, course, 'see_exists'): universities[course.org].append(course) +======= + if settings.MITX_FEATURES.get('ACCESS_REQUIRE_STAFF_FOR_COURSE'): + if not has_access_to_course(user,course): + continue + if settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): + if course.id not in visible_courses: + continue + universities[course.org].append(course) +>>>>>>> implement subdomain-based course displays return universities diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index ab63872170..b6a56830e0 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -63,7 +63,8 @@ def courses(request): ''' Render "find courses" page. The course selection work is done in courseware.courses. ''' - universities = get_courses_by_university(request.user) + universities = get_courses_by_university(request.user, + domain=request.META['HTTP_HOST']) return render_to_response("courses.html", {'universities': universities}) @@ -241,7 +242,8 @@ def university_profile(request, org_id): raise Http404("University Profile not found for {0}".format(org_id)) # Only grab courses for this org... - courses = get_courses_by_university(request.user)[org_id] + courses = get_courses_by_university(request.user, + domain=request.META['HTTP_HOST'])[org_id] context = dict(courses=courses, org_id=org_id) template_file = "university_profile/{0}.html".format(org_id).lower() diff --git a/lms/envs/common.py b/lms/envs/common.py index 303e73ea81..45818c0ff2 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -49,6 +49,11 @@ MITX_FEATURES = { ## Doing so will cause all courses to be released on production 'DISABLE_START_DATES': False, # When True, all courses will be active, regardless of start date + # When True, will only publicly list courses by the subdomain. Expects you + # to define COURSE_LISTINGS, a dictionary mapping subdomains to lists of + # course_ids (see dev_int.py for an example) + 'SUBDOMAIN_COURSE_LISTINGS' : False, + 'ENABLE_TEXTBOOK' : True, 'ENABLE_DISCUSSION' : True, @@ -61,6 +66,7 @@ MITX_FEATURES = { 'ACCESS_REQUIRE_STAFF_FOR_COURSE': False, 'AUTH_USE_OPENID': False, 'AUTH_USE_MIT_CERTIFICATES' : False, + } # Used for A/B testing From 7386d66c0f354a089d36d473c5a3a0786d792f17 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 11:11:54 -0400 Subject: [PATCH 2/7] Add dev_int.py config for testing course listings by subdomain --- lms/envs/dev_int.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lms/envs/dev_int.py diff --git a/lms/envs/dev_int.py b/lms/envs/dev_int.py new file mode 100644 index 0000000000..83c7fb0aba --- /dev/null +++ b/lms/envs/dev_int.py @@ -0,0 +1,32 @@ +""" +This enables use of course listings by subdomain. To see it in action, point the +following domains to 127.0.0.1 in your /etc/hosts file: + + berkeley.dev + harvard.dev + mit.dev + +Note that OS X has a bug where using *.local domains is excruciatingly slow, so +use *.dev domains instead for local testing. +""" +from .dev import * + +MITX_FEATURES['SUBDOMAIN_COURSE_LISTINGS'] = True + +COURSE_LISTINGS = { + 'default' : ['BerkeleyX/CS169.1x/2012_Fall', + 'BerkeleyX/CS188.1x/2012_Fall', + 'HarvardX/CS50x/2012', + 'HarvardX/PH207x/2012_Fall' + 'MITx/3.091x/2012_Fall', + 'MITx/6.002x/2012_Fall', + 'MITx/6.00x/2012_Fall'], + + 'berkeley': ['BerkeleyX/CS169.1x/2012_Fall', + 'BerkeleyX/CS188.1x/2012_Fall'], + + 'harvard' : ['HarvardX/CS50x/2012'], + + 'mit' : ['MITx/3.091x/2012_Fall', + 'MITx/6.00x/2012_Fall'] +} From d0f2641890c835aa7263f125ec9fd71a4ff5fac9 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 11:12:44 -0400 Subject: [PATCH 3/7] Account for the fact that sometimes we don't get HTTP_HOST (like for tests) --- common/djangoapps/student/views.py | 2 +- lms/djangoapps/courseware/courses.py | 20 +++++++++----------- lms/djangoapps/courseware/views.py | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index e02a5f4562..a99b46fd13 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -94,7 +94,7 @@ def main_index(request, extra_context={}, user=None): # The course selection work is done in courseware.courses. universities = get_courses_by_university(None, - domain=request.META['HTTP_HOST']) + domain=request.META.get('HTTP_HOST')) context = {'universities': universities, 'entries': entries} context.update(extra_context) return render_to_response('index.html', context) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 5818252cf1..02144ac3e0 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -205,19 +205,18 @@ def get_courses_by_university(user, domain=None): Returns dict of lists of courses available, keyed by course.org (ie university). Courses are sorted by course.number. ''' - subdomain = domain.split(".")[0] - # TODO: Clean up how 'error' is done. # filter out any courses that errored. - if settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): - if subdomain in settings.COURSE_LISTINGS: - visible_courses = settings.COURSE_LISTINGS[subdomain] - else: - visible_courses = frozenset(settings.COURSE_LISTINGS['default']) - courses = [c for c in modulestore().get_courses() if isinstance(c, CourseDescriptor)] courses = sorted(courses, key=lambda course: course.number) + + if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): + subdomain = settings.COURSE_LISTINGS.get(domain.split(".")[0], 'default') + visible_courses = frozenset(settings.COURSE_LISTINGS[subdomain]) + else: + visible_courses = frozenset(c.id for c in courses) + universities = defaultdict(list) for course in courses: <<<<<<< HEAD @@ -227,9 +226,8 @@ def get_courses_by_university(user, domain=None): if settings.MITX_FEATURES.get('ACCESS_REQUIRE_STAFF_FOR_COURSE'): if not has_access_to_course(user,course): continue - if settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): - if course.id not in visible_courses: - continue + if course.id not in visible_courses: + continue universities[course.org].append(course) >>>>>>> implement subdomain-based course displays return universities diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index b6a56830e0..eaeb667b3e 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -64,7 +64,7 @@ def courses(request): Render "find courses" page. The course selection work is done in courseware.courses. ''' universities = get_courses_by_university(request.user, - domain=request.META['HTTP_HOST']) + domain=request.META.get('HTTP_HOST')) return render_to_response("courses.html", {'universities': universities}) @@ -243,7 +243,7 @@ def university_profile(request, org_id): # Only grab courses for this org... courses = get_courses_by_university(request.user, - domain=request.META['HTTP_HOST'])[org_id] + domain=request.META.get('HTTP_HOST'))[org_id] context = dict(courses=courses, org_id=org_id) template_file = "university_profile/{0}.html".format(org_id).lower() From 7f8f70297179628250d72b3e4666d48f19700678 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 11:21:15 -0400 Subject: [PATCH 4/7] Fix silly error that was pulling the wrong info from COURSE_LISTINGS --- lms/djangoapps/courseware/courses.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 02144ac3e0..3eaff318a5 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -212,7 +212,9 @@ def get_courses_by_university(user, domain=None): courses = sorted(courses, key=lambda course: course.number) if domain and settings.MITX_FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'): - subdomain = settings.COURSE_LISTINGS.get(domain.split(".")[0], 'default') + subdomain = domain.split(".")[0] + if subdomain not in settings.COURSE_LISTINGS: + subdomain = 'default' visible_courses = frozenset(settings.COURSE_LISTINGS[subdomain]) else: visible_courses = frozenset(c.id for c in courses) From 7d90617c5e9fb898609334c9c67d3a2dea062792 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 12:21:59 -0400 Subject: [PATCH 5/7] Merge with master --- lms/djangoapps/courseware/courses.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 3eaff318a5..e142614f46 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -142,9 +142,6 @@ def get_course_info_section(course, section_key): raise KeyError("Invalid about key " + str(section_key)) -<<<<<<< HEAD -def get_courses_by_university(user): -======= def course_staff_group_name(course): ''' course should be either a CourseDescriptor instance, or a string (the @@ -200,7 +197,6 @@ def has_access_to_course(user, course): def get_courses_by_university(user, domain=None): ->>>>>>> implement subdomain-based course displays ''' Returns dict of lists of courses available, keyed by course.org (ie university). Courses are sorted by course.number. @@ -221,16 +217,10 @@ def get_courses_by_university(user, domain=None): universities = defaultdict(list) for course in courses: -<<<<<<< HEAD - if has_access(user, course, 'see_exists'): - universities[course.org].append(course) -======= - if settings.MITX_FEATURES.get('ACCESS_REQUIRE_STAFF_FOR_COURSE'): - if not has_access_to_course(user,course): - continue + if not has_access(user, course, 'see_exists'): + continue if course.id not in visible_courses: continue universities[course.org].append(course) ->>>>>>> implement subdomain-based course displays return universities From f535f44e625663d21f26d8879f293206ec60cf39 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 14:32:36 -0400 Subject: [PATCH 6/7] Fix typo that caused two classes to not get loaded --- lms/envs/dev_int.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/envs/dev_int.py b/lms/envs/dev_int.py index 83c7fb0aba..12123e12d4 100644 --- a/lms/envs/dev_int.py +++ b/lms/envs/dev_int.py @@ -17,7 +17,7 @@ COURSE_LISTINGS = { 'default' : ['BerkeleyX/CS169.1x/2012_Fall', 'BerkeleyX/CS188.1x/2012_Fall', 'HarvardX/CS50x/2012', - 'HarvardX/PH207x/2012_Fall' + 'HarvardX/PH207x/2012_Fall', 'MITx/3.091x/2012_Fall', 'MITx/6.002x/2012_Fall', 'MITx/6.00x/2012_Fall'], From 1c2b6e80885e200c96e770c1b54b24c71b536608 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Wed, 15 Aug 2012 14:39:56 -0400 Subject: [PATCH 7/7] Remove stuff that I should have deleted during the rebase --- lms/djangoapps/courseware/courses.py | 53 ---------------------------- 1 file changed, 53 deletions(-) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index e142614f46..f0b82a3c9c 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -142,59 +142,6 @@ def get_course_info_section(course, section_key): raise KeyError("Invalid about key " + str(section_key)) -def course_staff_group_name(course): - ''' - course should be either a CourseDescriptor instance, or a string (the - .course entry of a Location) - ''' - if isinstance(course, str) or isinstance(course, unicode): - coursename = course - else: - # should be a CourseDescriptor, so grab its location.course: - coursename = course.location.course - return 'staff_%s' % coursename - -def has_staff_access_to_course(user, course): - ''' - Returns True if the given user has staff access to the course. - This means that user is in the staff_* group, or is an overall admin. - TODO (vshnayder): this needs to be changed to allow per-course_id permissions, not per-course - (e.g. staff in 2012 is different from 2013, but maybe some people always have access) - - course is the course field of the location being accessed. - ''' - if user is None or (not user.is_authenticated()) or course is None: - return False - if user.is_staff: - return True - - # note this is the Auth group, not UserTestGroup - user_groups = [x[1] for x in user.groups.values_list()] - staff_group = course_staff_group_name(course) - if staff_group in user_groups: - return True - return False - -def has_staff_access_to_course_id(user, course_id): - """Helper method that takes a course_id instead of a course name""" - loc = CourseDescriptor.id_to_location(course_id) - return has_staff_access_to_course(user, loc.course) - - -def has_staff_access_to_location(user, location): - """Helper method that checks whether the user has staff access to - the course of the location. - - location: something that can be passed to Location - """ - return has_staff_access_to_course(user, Location(location).course) - -def has_access_to_course(user, course): - '''course is the .course element of a location''' - if course.metadata.get('ispublic'): - return True - return has_staff_access_to_course(user,course) - def get_courses_by_university(user, domain=None): '''