Skip to content

Commit

Permalink
Merge pull request #201 from appsembler/appsembler/ficus/feature/taxoman
Browse files Browse the repository at this point in the history
Conditionally enable Taxoman for Ficus
  • Loading branch information
johnbaldwin authored Feb 9, 2018
2 parents 306cade + 6a9954a commit ff1608d
Show file tree
Hide file tree
Showing 12 changed files with 250 additions and 0 deletions.
40 changes: 40 additions & 0 deletions cms/djangoapps/contentstore/courseware_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
log = logging.getLogger('edx.modulestore')


# Interim code to get courseware_index to work with Taxoman
if hasattr(settings,'FEATURES') and settings.FEATURES.get('ENABLE_TAXOMAN', False):
try:
from taxoman_api.models import Facet, FacetValue, CourseFacetValue
using_taxoman = True
except ImportError:
log.error('Taxoman enabled, but unable to import taxoman_api package (ImportError')
using_taxoman = False
else:
using_taxoman = False


def strip_html_content_to_text(html_content):
""" Gets only the textual part for html content - useful for building text to be searched """
# Removing HTML-encoded non-breaking space characters
Expand Down Expand Up @@ -527,11 +539,31 @@ def from_course_mode(self, **kwargs):

return [mode.slug for mode in CourseMode.modes_for_course(course.id)]

def from_taxoman(self, **kwargs):
'''Fetches the assigned value to the facet in taxoman
'''
if using_taxoman:
course = kwargs.get('course', None)
if not course:
raise ValueError("Context dictionary does not contain expected argument 'course'")
course_facet_value = CourseFacetValue.objects.filter(
course_id=course.id,
facet_value__facet__slug=self.property_name).values_list(
'facet_value__value', flat=True)
return list(course_facet_value)
else:
# Interim hack: return an empty list, which should have a net zero
# effect if not enabling taxoman
return []

# Source location options - either from the course or the about info
FROM_ABOUT_INFO = from_about_dictionary
FROM_COURSE_PROPERTY = from_course_property
FROM_COURSE_MODE = from_course_mode

# Appsembler addition - Interim implementation
FROM_TAXOMAN = from_taxoman


class CourseAboutSearchIndexer(object):
"""
Expand Down Expand Up @@ -573,6 +605,14 @@ class CourseAboutSearchIndexer(object):
AboutInfo("catalog_visibility", AboutInfo.PROPERTY, AboutInfo.FROM_COURSE_PROPERTY),
]

# Appsembler addition
if using_taxoman:
for facet in Facet.objects.all():
print("facet = {}".format(facet))
ABOUT_INFORMATION_TO_INCLUDE.append(
AboutInfo(facet.slug, AboutInfo.PROPERTY, AboutInfo.FROM_TAXOMAN)
)

@classmethod
def index_about_information(cls, modulestore, course):
"""
Expand Down
2 changes: 2 additions & 0 deletions cms/djangoapps/search_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

API_VERSION = 'v0'
24 changes: 24 additions & 0 deletions cms/djangoapps/search_api/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@

from contentstore.courseware_index import CoursewareSearchIndexer
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore

def reindex_course(course_id):
"""
Arguments:
course_id - The course id for a course. This is the 'course_id' property
for the course as returend from:
<lms-host>/api/courses/v1/courses/
Raises:
InvalidKeyError - if the opaque course
SearchIndexingError - If the reindexing fails
References
course.py#reindex_course_and_check_access
"""
course_key = CourseKey.from_string(course_id)
with modulestore().bulk_operations(course_key):
return CoursewareSearchIndexer.do_course_reindex(modulestore(),
course_key)
10 changes: 10 additions & 0 deletions cms/djangoapps/search_api/permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from rest_framework.permissions import BasePermission


class IsStaffUser(BasePermission):
"""
Allow access to only staff users
"""
def has_permission(self, request, view):
return request.user and request.user.is_active and (
request.user.is_staff or request.user.is_superuser)
9 changes: 9 additions & 0 deletions cms/djangoapps/search_api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf.urls import url

from . import API_VERSION, views

urlpatterns = [
url(r'^$', views.SearchIndex.as_view(), name='search_api_index'),
url(r'^{}/reindex-course'.format(API_VERSION),
views.CourseIndexer.as_view(), name='reindex-course'),
]
97 changes: 97 additions & 0 deletions cms/djangoapps/search_api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@

import json

from django.conf import settings
from django.http import HttpResponse

from rest_framework.authentication import (
BasicAuthentication,
SessionAuthentication,
TokenAuthentication,
)
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from opaque_keys import InvalidKeyError

from . import api
from .permissions import IsStaffUser

# Restrict access to the LMS server
ALLOWED_ORIGIN = settings.LMS_BASE

description = """
Appembler Open edX search api.
Opens up access to Open edX'sa search infrastructure via HTTP (REST) API interfaces.
"""

class SearchIndex(APIView):
authentication_classes = (
BasicAuthentication,
SessionAuthentication,
TokenAuthentication
)

permission_classes = ( IsAuthenticated, IsStaffUser, )
def get(self, request, format=None):
return Response({
'message': 'CMS Search API',
})


class CourseIndexer(APIView):
authentication_classes = (
BasicAuthentication,
SessionAuthentication,
TokenAuthentication
)

permission_classes = ( IsAuthenticated, IsStaffUser, )

def get(self, request, format=None):
return Response({
'message': 'Course Indexer',
})

def post(self, request, format=None):

request_data = json.loads(request.body)
course_id = request_data.get('course_id')
try:
results = api.reindex_course(course_id)
response_data = {
'course_id': course_id,
'status': 'OK',
'message': 'course reindex initiated',
'results': results,
}
response = Response(response_data)
response['Access-Control-Allow-Origin'] = ALLOWED_ORIGIN
response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response['Access-Control-Allow-Headers'] = '*'
return response
except Exception as e:
if isinstance(e, InvalidKeyError):
message = 'InvalidKeyError: Cannot find key for course string ' + \
'"{}"'.format(course_id)
status = 400
else:
message = 'Exception "{}" msg: {}'.format(e.__class__, e.message)
status = 500
return Response(json.dumps({
'course_id': course_id,
'status': 'ERROR',
'message': message,
}), status=status)

def options(self, request, format=None):
response = Response()
response['Access-Control-Allow-Origin'] = ALLOWED_ORIGIN
response['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
# Options do not allow wildcard for access-control-allow-headers
response['Access-Control-Allow-Headers'] = 'Content-Type'
return response
5 changes: 5 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,11 @@

# Unusual migrations
'database_fixups',

# Appsembler API Extensions
'rest_framework',
'rest_framework.authtoken',
'search_api',
)


Expand Down
5 changes: 5 additions & 0 deletions cms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,8 @@
url(r'^404$', handler404),
url(r'^500$', handler500),
)

# Appsembler API extensions
urlpatterns += (
url(r'^api/search/', include('search_api.urls')),
)
18 changes: 18 additions & 0 deletions lms/djangoapps/courseware/views/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@

log = logging.getLogger("edx.courseware")

# Interim code to get courseware views to work with Taxoman
if settings.TAXOMAN_ENABLED:
try:
from taxoman_api.models import Facet
using_taxoman = True
except ImportError:
log.error('Taxoman enabled, but unable to import taxoman_api package (ImportError')
using_taxoman = False
else:
using_taxoman = False




# Only display the requirements on learner dashboard for
# credit and verified modes.
Expand Down Expand Up @@ -144,6 +157,11 @@ def courses(request):
courses_list = []
programs_list = []
course_discovery_meanings = getattr(settings, 'COURSE_DISCOVERY_MEANINGS', {})
if using_taxoman:
for facet in Facet.objects.all():
if not course_discovery_meanings.get(facet.slug):
course_discovery_meanings[facet.slug] = { 'name': facet.name }

if not settings.FEATURES.get('ENABLE_COURSE_DISCOVERY'):
courses_list = get_courses(request.user)

Expand Down
16 changes: 16 additions & 0 deletions lms/envs/aws_appsembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
from .aws import *
from .appsembler import *

if FEATURES.get('ENABLE_TAXOMAN', False):
try:
import taxoman.settings
TAXOMAN_ENABLED = True
except ImportError:
TAXOMAN_ENABLED = False
else:
TAXOMAN_ENABLED = False


ENV_APPSEMBLER_FEATURES = ENV_TOKENS.get('APPSEMBLER_FEATURES', {})
for feature, value in ENV_APPSEMBLER_FEATURES.items():
APPSEMBLER_FEATURES[feature] = value
Expand Down Expand Up @@ -134,3 +144,9 @@
except ImportError:
pass


if TAXOMAN_ENABLED:
WEBPACK_LOADER['TAXOMAN_APP'] = {
'BUNDLE_DIR_NAME': taxoman.settings.bundle_dir_name,
'STATS_FILE': taxoman.settings.stats_file,
}
3 changes: 3 additions & 0 deletions lms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -2024,6 +2024,7 @@

# User API
'rest_framework',
'rest_framework.authtoken',
'openedx.core.djangoapps.user_api',

# Shopping cart
Expand Down Expand Up @@ -3042,3 +3043,5 @@
############## Settings for the Enterprise App ######################

ENTERPRISE_ENROLLMENT_API_URL = LMS_ROOT_URL + "/api/enrollment/v1/"

WEBPACK_LOADER = {}
21 changes: 21 additions & 0 deletions lms/envs/devstack_appsembler.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
# devstack_appsembler.py

import os

from .devstack import *
from .appsembler import *


if FEATURES.get('ENABLE_TAXOMAN', False):
try:
# Just a check, we don't need it for the settings
import taxoman_api
# We need this for webpack loader
import taxoman.settings
TAXOMAN_ENABLED = True
except ImportError:
TAXOMAN_ENABLED = False
else:
TAXOMAN_ENABLED = False


ENV_APPSEMBLER_FEATURES = ENV_TOKENS.get('APPSEMBLER_FEATURES', {})
for feature, value in ENV_APPSEMBLER_FEATURES.items():
APPSEMBLER_FEATURES[feature] = value
Expand Down Expand Up @@ -125,3 +140,9 @@

# override devstack.py automatic enabling of courseware discovery
FEATURES['ENABLE_COURSE_DISCOVERY'] = ENV_TOKENS['FEATURES'].get('ENABLE_COURSE_DISCOVERY', FEATURES['ENABLE_COURSE_DISCOVERY'])

if TAXOMAN_ENABLED:
WEBPACK_LOADER['TAXOMAN_APP'] = {
'BUNDLE_DIR_NAME': taxoman.settings.bundle_dir_name,
'STATS_FILE': taxoman.settings.stats_file,
}

0 comments on commit ff1608d

Please sign in to comment.