Skip to content

Commit

Permalink
Merge pull request #79 from OpenLXP/competency_search
Browse files Browse the repository at this point in the history
Competency_search
  • Loading branch information
KarenAJ authored Oct 26, 2024
2 parents 2b6eb6b + 8ca8547 commit 8c90d36
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 4 deletions.
6 changes: 3 additions & 3 deletions app/configurations/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class XDSUIConfigurationAdmin(admin.ModelAdmin):
list_display = ('search_results_per_page', 'xds_configuration',
'created', 'modified',)
fields = [('search_results_per_page', 'xds_configuration',
'course_img_fallback')]
'course_img_fallback', 'ui_logo')]


@admin.register(CourseInformationMapping)
Expand All @@ -32,11 +32,11 @@ class CourseInformationMappingAdmin(admin.ModelAdmin):
'course_type', 'course_time',
'course_instructor', 'course_deliveryMode',
'course_thumbnail', 'course_derived_from',
'xds_ui_configuration')
'course_competency', 'xds_ui_configuration')
fields = ['course_title', 'course_description',
'course_url', 'course_code', 'course_startDate',
'course_endDate', 'course_provider',
'course_type', 'course_time',
'course_instructor', 'course_deliveryMode',
'course_thumbnail', 'course_derived_from',
'xds_ui_configuration']
'course_competency', 'xds_ui_configuration']
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.14 on 2024-09-18 13:53

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('configurations', '0006_courseinformationmapping_course_derived_from'),
]

operations = [
migrations.AddField(
model_name='courseinformationmapping',
name='course_competency',
field=models.CharField(default='Course.CourseLearningOutcome', help_text='Enter the mapping for the reference to the competency taught', max_length=200),
),
]
18 changes: 18 additions & 0 deletions app/configurations/migrations/0008_xdsuiconfiguration_ui_logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-09-26 16:21

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('configurations', '0007_courseinformationmapping_course_competency'),
]

operations = [
migrations.AddField(
model_name='xdsuiconfiguration',
name='ui_logo',
field=models.ImageField(upload_to='images/'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-09-27 15:33

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('configurations', '0008_xdsuiconfiguration_ui_logo'),
]

operations = [
migrations.AlterField(
model_name='xdsuiconfiguration',
name='ui_logo',
field=models.ImageField(blank=True, null=True, upload_to='images/'),
),
]
13 changes: 12 additions & 1 deletion app/configurations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
from django.dispatch import receiver
from django.forms import ValidationError
from django.urls import reverse
from es_api.utils.queries_base import BaseQueries
from model_utils.models import TimeStampedModel

from es_api.utils.queries_base import BaseQueries
from users.models import Organization, XDSUser

logger = logging.getLogger('dict_config_logger')
Expand Down Expand Up @@ -92,6 +93,9 @@ class XDSUIConfiguration(TimeStampedModel):
course_img_fallback = models.ImageField(upload_to='images/',
null=True,
blank=True)
ui_logo = models.ImageField(upload_to='images/',
null=True,
blank=True)

def get_absolute_url(self):
""" URL for displaying individual model records."""
Expand Down Expand Up @@ -200,6 +204,13 @@ class CourseInformationMapping(TimeStampedModel):
"course derived from found in the"
" elasticsearch")

course_competency = models.CharField(max_length=200,
default="Course."
"CourseLearningOutcome",
help_text="Enter the mapping for "
"the reference to the "
"competency taught")

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 @@ -353,6 +353,31 @@ def test_search_for_derived(self):
self.assertRaises(ValueError, query.search_for_derived, "test",
{"page": "hello"})

def test_search_by_competency(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_competency = "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_by_competency, "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 @@ -195,3 +195,33 @@ def test_search_derived_with_reference(self):
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(json.loads(response.content), {'test': "value"})


@tag('unit')
class SearchCompetencyTests(APITestCase):
def test_search_competency_no_reference(self):
"""
Test that the /es-api/ endpoint sends an HTTP error when no
reference is provided
"""
url = reverse('es_api:search-competency')
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&p=1" % (reverse('es_api:search-competency'))
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 @@ -13,4 +13,6 @@
path('suggest/', views.SuggestionsView.as_view(), name='suggest'),
path('derived-from/', views.SearchDerivedView.as_view(),
name='search-derived'),
path('teaches/', views.SearchCompetencyView.as_view(),
name='search-competency'),
]
28 changes: 28 additions & 0 deletions app/es_api/utils/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,34 @@ def search_by_keyword(self, keyword="", filters={}):

return response

def search_by_competency(self, comp_uuid="", filters={}):
"""This method takes in a competency ID string + a page number and queries
ElasticSearch for the term then returns the Response Object"""
course_mapping = CourseInformationMapping.objects.first()

q = Q("match",
**{course_mapping.course_competency: comp_uuid})

# 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 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
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 @@ -146,6 +146,66 @@ def get(self, request):
content_type="application/json")


class SearchCompetencyView(APIView):
"""This method defines an API for querying to ElasticSearch
for competencies"""

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_by_competency(
comp_uuid=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 @@ -274,6 +274,7 @@
"/es-api/",
"/es-api/suggest/",
"/es-api/derived-from/",
"/es-api/teaches/",
"/api/experiences/[a-zA-Z0-9]+/",
"/api/spotlight-courses",
]
Expand Down
5 changes: 5 additions & 0 deletions nginx.default
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include /etc/nginx/mime.types;
server {
listen 8020;
server_name example.org;
client_max_body_size 50M;

location /static {
root /opt/app/openlxp-xds;
Expand All @@ -15,6 +16,10 @@ server {
root /opt/app/openlxp-xds;
}

location /images {
root /opt/app/openlxp-xds;
}

location / {
proxy_pass http://unix:/opt/xds.sock;
proxy_set_header Host $http_host;
Expand Down

0 comments on commit 8c90d36

Please sign in to comment.