Skip to content

Commit

Permalink
add derived from (#56)
Browse files Browse the repository at this point in the history
* add derived from
add es-search api for derived from
add configuration mapping for derived from

* add api to open endpoints

* Added test cases

* Fixed tests
  • Loading branch information
JDTobin authored Aug 7, 2023
1 parent f27100b commit 9b8cdd6
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 2 deletions.
6 changes: 4 additions & 2 deletions app/configurations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ class CourseInformationMappingAdmin(admin.ModelAdmin):
'course_url', 'course_code', 'course_startDate',
'course_endDate', 'course_provider',
'course_instructor', 'course_deliveryMode',
'course_thumbnail', 'xds_ui_configuration')
'course_thumbnail', 'course_derived_from',
'xds_ui_configuration')

fields = ['course_title', 'course_description',
'course_url', 'course_code', 'course_startDate',
'course_endDate', 'course_provider',
'course_instructor', 'course_deliveryMode',
'course_thumbnail', 'xds_ui_configuration']
'course_thumbnail', 'course_derived_from',
'xds_ui_configuration']
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.20 on 2023-07-25 17:35

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('configurations', '0005_auto_20220721_0744'),
]

operations = [
migrations.AddField(
model_name='courseinformationmapping',
name='course_derived_from',
field=models.CharField(default='P2881_Core.DerivedFrom', help_text='Enter the mapping for the reference to the course derived from found in the elasticsearch', max_length=200),
),
]
8 changes: 8 additions & 0 deletions app/configurations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@ class CourseInformationMapping(TimeStampedModel):
"thumbnail of"
" the course found in the"
" elasticsearch")

course_derived_from = models.CharField(max_length=200,
default="P2881_Core.DerivedFrom",
help_text="Enter the mapping for "
"the reference to the "
"course derived from found in the"
" elasticsearch")

xds_ui_configuration = models \
.OneToOneField(XDSUIConfiguration,
on_delete=models.CASCADE,
Expand Down
25 changes: 25 additions & 0 deletions app/es_api/tests/test_utils_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,31 @@ def test_search_by_filters(self):

self.assertEqual(result, expected_result)

def test_search_for_derived(self):
"""Test that calling search_for_derived with a invalid page # \
(e.g. string) value will throw an error"""
with patch('es_api.utils.queries.'
'XDSConfiguration.objects') as xdsCfg, \
patch('elasticsearch_dsl.Search.execute') as es_execute, \
patch('es_api.utils.queries.SearchFilter.objects') as sfObj, \
patch('es_api.utils.queries.'
'CourseInformationMapping.objects') as cimobj:
configObj = XDSConfiguration(target_xis_metadata_api="dsds")
uiConfigObj = XDSUIConfiguration(search_results_per_page=10,
xds_configuration=configObj)
cimobj.first().course_derived_from = "test"
xdsCfg.xdsuiconfiguration = uiConfigObj
xdsCfg.first.return_value = configObj
sfObj.return_value = []
sfObj.filter.return_value = []
es_execute.return_value = {
"test": "test"
}
query = XSEQueries('test', 'test')

self.assertRaises(ValueError, query.search_for_derived, "test",
{"page": "hello"})


class XDSUserTests(TestCase):
def test_user_org_filter_blank(self):
Expand Down
30 changes: 30 additions & 0 deletions app/es_api/tests/test_views_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,33 @@ def test_suggestions_exception(self):

self.assertEqual(response.status_code,
status.HTTP_500_INTERNAL_SERVER_ERROR)


@tag('unit')
class SearchDerivedTests(APITestCase):
def test_search_derived_no_reference(self):
"""
Test that the /es-api/ endpoint sends an HTTP error when no
reference is provided
"""
url = reverse('es_api:search-derived')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_search_derived_with_reference(self):
"""
Test that the /es-api/ endpoint succeeds when a valid
reference is provided
"""
url = "%s?reference=hello" % (reverse('es_api:search-derived'))
with patch('es_api.views.XSEQueries') as query, \
patch('es_api.views.SearchFilter.objects') as sf1Obj, \
patch('es_api.views.XDSConfiguration.objects'):
sf1Obj.return_value = []
sf1Obj.filter.return_value = []
result_json = json.dumps({"test": "value"})
query.get_results.return_value = result_json
query.return_value = query
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(json.loads(response.content), {'test': "value"})
2 changes: 2 additions & 0 deletions app/es_api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
path('filter-search/', views.FiltersView.as_view(), name='filters'),
path('', views.SearchIndexView.as_view(), name='search-index'),
path('suggest/', views.SuggestionsView.as_view(), name='suggest'),
path('derived-from/', views.SearchDerivedView.as_view(),
name='search-derived'),
]
30 changes: 30 additions & 0 deletions app/es_api/utils/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,36 @@ def search_by_keyword(self, keyword="", filters={}):

return response

def search_for_derived(self, reference="", filters={}):
"""This method takes in a reference string and queries
ElasticSearch for the items derived from it then returns the
Response Object"""

course_mapping = CourseInformationMapping.objects.first()

q = Q("match",
**{course_mapping.course_derived_from: reference})

# setting up the search object
self.search = self.search.query(q)

self.user_organization_filtering()

# getting the page size for result pagination
configuration = XDSConfiguration.objects.first()
uiConfig = configuration.xdsuiconfiguration

page_size = uiConfig.search_results_per_page
start_index = self.get_page_start(int(filters['page']), page_size)
end_index = start_index + page_size
self.search = self.search[start_index:end_index]

# call to elasticsearch to execute the query
response = self.search.execute()
logger.info(self.search.to_dict())

return response

def more_like_this(self, doc_id):
"""This method takes in a doc ID and queries the elasticsearch index for
courses with similar title or description"""
Expand Down
60 changes: 60 additions & 0 deletions app/es_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,66 @@ def get(self, request):
content_type="application/json")


class SearchDerivedView(APIView):
"""This method defines an API for querying to ElasticSearch
for derived experiences"""

def get_request_attributes(self, request):
"""helper method to get attributes"""
reference = ''
filters = {
'page': '1'
}

if request.GET.get('reference'):
reference = request.GET['reference']

if (request.GET.get('p')) and (request.GET.get('p') != ''):
filters['page'] = request.GET['p']

return reference, filters

def get(self, request):
results = []

reference, filters = self.get_request_attributes(request)

if reference != '':
errorMsg = {
"message": "error executing ElasticSearch query; " +
"Please contact an administrator"
}
errorMsgJSON = json.dumps(errorMsg)

try:
queries = XSEQueries(
XDSConfiguration.objects.first().target_xse_host,
XDSConfiguration.objects.first().target_xse_index,
user=request.user)
response = queries.search_for_derived(
reference=reference, filters=filters)
results = queries.get_results(response)
except HTTPError as http_err:
logger.error(http_err)
return HttpResponseServerError(errorMsgJSON,
content_type="application/json")
except Exception as err:
logger.error(err)
return HttpResponseServerError(errorMsgJSON,
content_type="application/json")
else:
logger.info(results)
return HttpResponse(results, content_type="application/json")
else:
error = {
"message": "Request is missing 'reference' query " +
"parameter"
}
errorJson = json.dumps(error)
return HttpResponseBadRequest(errorJson,
content_type="application/json")


class GetMoreLikeThisView(APIView):
"""This method defines an API for fetching results using the
more_like_this feature from elasticsearch. """
Expand Down
1 change: 1 addition & 0 deletions app/openlxp_xds_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@
"/es-api/more-like-this/[a-zA-Z0-9]+/",
"/es-api/",
"/es-api/suggest/",
"/es-api/derived-from/",
"/api/experiences/[a-zA-Z0-9]+/",
"/api/spotlight-courses",
]
Expand Down

0 comments on commit 9b8cdd6

Please sign in to comment.