diff --git a/TEKDB/.coveragerc b/TEKDB/.coveragerc index d19f2f43..766cf9b2 100644 --- a/TEKDB/.coveragerc +++ b/TEKDB/.coveragerc @@ -1,3 +1,4 @@ [run] omit = - */migrations/* \ No newline at end of file + */migrations/* + */tests/* \ No newline at end of file diff --git a/TEKDB/TEKDB/admin.py b/TEKDB/TEKDB/admin.py index 497cbc54..04c5bbb5 100644 --- a/TEKDB/TEKDB/admin.py +++ b/TEKDB/TEKDB/admin.py @@ -104,6 +104,10 @@ def __init__(self, *args, **kwargs): class CitationsForm(forms.ModelForm): + class Meta: + model = Citations + fields = "__all__" + def __init__(self, *args, **kwargs): super(CitationsForm, self).__init__(*args, **kwargs) self.fields["referencetype"].queryset = LookupReferenceType.objects.order_by( @@ -138,6 +142,10 @@ class Meta: class ResourcesForm(forms.ModelForm): + class Meta: + model = Resources + fields = "__all__" + def __init__(self, *args, **kwargs): super(ResourcesForm, self).__init__(*args, **kwargs) self.fields[ @@ -632,7 +640,6 @@ def save_model(self, request, obj, form, change): mediatype__startswith="other" ).first() mediatype = media_type_instance - filename = file.name.split(".")[0] media_instance = Media( @@ -965,10 +972,10 @@ class PlacesAdmin(NestedRecordAdminProxy, RecordModelAdmin): change_list_template = "admin/TEKDB/places/change_list.html" def changelist_view(self, request, extra_context=None): - from .views import getPlacesGeoJSON + from .views import get_places_geojson extra_context = extra_context or {} - extra_context["results_geojson"] = getPlacesGeoJSON(request) + extra_context["results_geojson"] = get_places_geojson(request) return super(PlacesAdmin, self).changelist_view( request, extra_context=extra_context ) diff --git a/TEKDB/TEKDB/context_processors.py b/TEKDB/TEKDB/context_processors.py index 80510962..ee0a3357 100644 --- a/TEKDB/TEKDB/context_processors.py +++ b/TEKDB/TEKDB/context_processors.py @@ -32,24 +32,20 @@ def search_settings(request=None): pass if configs: - try: - min_search_rank = ( - configs.min_search_rank - if configs.min_search_rank - else search_config["MIN_SEARCH_RANK"] - ) - min_search_similarity = ( - configs.min_search_similarity - if configs.min_search_similarity - else search_config["MIN_SEARCH_SIMILARITY"] - ) - search_config = { - "MIN_SEARCH_RANK": min_search_rank, - "MIN_SEARCH_SIMILARITY": min_search_similarity, - } - except Exception: - print("No min_search_rank or min_search_similarity in Configuration") - pass + min_search_rank = ( + configs.min_search_rank + if configs.min_search_rank + else search_config["MIN_SEARCH_RANK"] + ) + min_search_similarity = ( + configs.min_search_similarity + if configs.min_search_similarity + else search_config["MIN_SEARCH_SIMILARITY"] + ) + search_config = { + "MIN_SEARCH_RANK": min_search_rank, + "MIN_SEARCH_SIMILARITY": min_search_similarity, + } return search_config diff --git a/TEKDB/TEKDB/models.py b/TEKDB/TEKDB/models.py index 3ccdb002..a4dd8d3a 100644 --- a/TEKDB/TEKDB/models.py +++ b/TEKDB/TEKDB/models.py @@ -71,6 +71,8 @@ def run_keyword_search(model, keyword, fields, fk_fields, weight_lookup, sort_fi annotations[f"headline_{relationship_name}"] = SearchHeadline( relationship_name, query, start_sel="", stop_sel="" ) + # vector is only false if we have no fields to look through. + # Thus far we don't have any models that only search through fk fields. if not vector: vector = SearchVector(relationship_name, weight=weight_lookup[val[0]]) else: @@ -715,11 +717,6 @@ def get_response_format(self): "feature": feature, } - # def get_record_dict(self, user, srid=3857): - # record_dict = super(Places, self).get_record_dict(user, srid) - # record_dict['map_pin'] = settings.RECORD_ICONS['map_pin'] - # return record_dict - def get_related_objects(self, object_id): # place = Places.objects.get(pk=object_id) alt_names = self.placealtindigenousname_set.all() @@ -869,38 +866,6 @@ def keyword_search( return run_keyword_search( Resources, keyword, fields, fk_fields, weight_lookup, sort_field ) - ################################ - # NEW APPROACH ################# - ################################ - # vector = SearchVector('commonname', weight='A') + \ - # SearchVector('indigenousname', weight='A') + \ - # SearchVector('resourceclassificationgroup__resourceclassificationgroup', weight='B') + \ - # SearchVector('genus', weight='C') + \ - # SearchVector('species', weight='C') - # #HoW TO GET Alt Names?) - # query = SearchQuery(keyword) - # similarity=TrigramSimilarity('commonname', keyword, weight='A') + \ - # TrigramSimilarity('indigenousname', keyword, weight='A') + \ - # TrigramSimilarity('resourceclassificationgroup__resourceclassificationgroup', keyword, weight='B') + \ - # TrigramSimilarity('genus', keyword, weight='C') + \ - # TrigramSimilarity('species', keyword, weight='C') - - ################################ - # OLD APPROACH ################# - ################################ - # group_qs = LookupResourceGroup.objects.filter(resourceclassificationgroup__icontains=keyword) - # group_loi = [group.pk for group in group_qs] - # alt_name_qs = ResourceAltIndigenousName.objects.filter(altindigenousname__icontains=keyword) - # alt_name_loi = [ran.resourceid.pk for ran in alt_name_qs] - # - # return Resources.objects.filter( - # Q(commonname__icontains=keyword) | - # Q(indigenousname__icontains=keyword) | - # Q(genus__icontains=keyword) | - # Q(species__icontains=keyword) | - # Q(resourceclassificationgroup__in=group_loi) | - # Q(pk__in=alt_name_loi) - # ) def image(self): return settings.RECORD_ICONS["resource"] @@ -1179,10 +1144,8 @@ class Meta: def keyword_search(keyword): resource_qs = Resources.keyword_search(keyword) resource_loi = [resource.pk for resource in resource_qs] - place_qs = Places.keyword_search(keyword) place_loi = [place.pk for place in place_qs] - part_qs = LookupPartUsed.objects.filter(partused__icontains=keyword) part_loi = [part.pk for part in part_qs] @@ -1194,7 +1157,6 @@ def keyword_search(keyword): timing_qs = LookupTiming.objects.filter(timing__icontains=keyword) timing_loi = [timing.pk for timing in timing_qs] - return PlacesResourceEvents.objects.filter( Q(resourceid__in=resource_loi) | Q(placeid__in=place_loi) @@ -1555,42 +1517,6 @@ def keyword_search( sort_field, ) - # def keyword_search(keyword): - # placeresource_qs = PlacesResourceEvents.keyword_search(keyword) - # placeresource_loi = [placeresource.pk for placeresource in placeresource_qs] - - # part_qs = LookupPartUsed.objects.filter(partused__icontains=keyword) - # part_loi = [part.pk for part in part_qs] - - # activity_qs = LookupActivity.objects.filter(activity__icontains=keyword) - # activity_loi = [activity.pk for activity in activity_qs] - - # participant_qs = LookupParticipants.objects.filter(participants__icontains=keyword) - # participant_loi = [participant.pk for participant in participant_qs] - - # technique_qs = LookupTechniques.objects.filter(techniques__icontains=keyword) - # technique_loi = [technique.pk for technique in technique_qs] - - # use_qs = LookupCustomaryUse.objects.filter(usedfor__icontains=keyword) - # use_loi = [use.pk for use in use_qs] - - # timing_qs = LookupTiming.objects.filter(timing__icontains=keyword) - # timing_loi = [timing.pk for timing in timing_qs] - - # return ResourcesActivityEvents.objects.filter( - # Q(placeresourceid__in=placeresource_loi) | - # Q(relationshipdescription__icontains=keyword) | - # Q(partused__in=part_loi) | - # Q(activityshortdescription__in=activity_loi) | - # Q(activitylongdescription__icontains=keyword) | - # Q(participants__in=participant_loi) | - # Q(technique__in=technique_loi) | - # Q(gear__icontains=keyword) | - # Q(customaryuse__in=use_loi) | - # Q(timing__in=timing_loi) | - # Q(timingdescription__icontains=keyword) - # ) - def image(self): return settings.RECORD_ICONS["activity"] @@ -1776,7 +1702,7 @@ def relationships(self): relationship_list = [] interviewee_citations = [x.get_query_json() for x in self.interviewee.all()] interviewer_citations = [x.get_query_json() for x in self.interviewer.all()] - # citations = list(set(interviewee_citations) | set(interviewer_citations)) + if len(interviewee_citations) > 0: relationship_list.append( {"key": "Sources as interviewee", "value": interviewee_citations} diff --git a/TEKDB/TEKDB/tests/test_admin.py b/TEKDB/TEKDB/tests/test_admin.py index a5de7147..8a713b86 100644 --- a/TEKDB/TEKDB/tests/test_admin.py +++ b/TEKDB/TEKDB/tests/test_admin.py @@ -1,11 +1,11 @@ # from django.conf import settings from django.test import RequestFactory +from unittest.mock import patch from django.core.files.uploadedfile import SimpleUploadedFile from django.contrib.auth import get_user_model from django.contrib.admin.sites import AdminSite from django.urls import reverse import os -from TEKDB.admin import MediaBulkUploadAdmin from TEKDB.forms import MediaBulkUploadForm from TEKDB.models import MediaBulkUpload, Media from TEKDB.tests.test_models import ITKTestCase @@ -13,6 +13,140 @@ User = get_user_model() +class MediaFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin1", password="password", email="admin1@example.com" + ) + + def test_media_admin_add(self): + from TEKDB.admin import MediaForm + + form = MediaForm() + + self.assertIn("mediatype", form.fields) + queryset = form.fields["mediatype"].queryset + self.assertTrue(queryset.exists()) + + +class ResourcesActivityEventsFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin3", password="password", email="admin3@example.com" + ) + + def test_resources_activity_events_admin_add(self): + from TEKDB.admin import ResourcesActivityEventsForm + + form = ResourcesActivityEventsForm() + + self.assertIn("participants", form.fields) + queryset = form.fields["participants"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("technique", form.fields) + queryset = form.fields["technique"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("activityshortdescription", form.fields) + queryset = form.fields["activityshortdescription"].queryset + self.assertTrue(queryset.exists()) + + +class CitationsFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin4", password="password", email="admin4@example.com" + ) + + def test_citations_admin_add(self): + from TEKDB.admin import CitationsForm + + form = CitationsForm() + + self.assertIn("referencetype", form.fields) + queryset = form.fields["referencetype"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("intervieweeid", form.fields) + queryset = form.fields["intervieweeid"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("interviewerid", form.fields) + queryset = form.fields["interviewerid"].queryset + self.assertTrue(queryset.exists()) + + +class PlacesFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin5", password="password", email="admin5@example.com" + ) + + def test_places_admin_add(self): + from TEKDB.admin import PlacesForm + + form = PlacesForm() + + self.assertIn("planningunitid", form.fields) + queryset = form.fields["planningunitid"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("primaryhabitat", form.fields) + queryset = form.fields["primaryhabitat"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("tribeid", form.fields) + queryset = form.fields["tribeid"].queryset + self.assertTrue(queryset.exists()) + + +class ResourcesFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin6", password="password", email="admin6@example.com" + ) + + def test_resources_admin_add(self): + from TEKDB.admin import ResourcesForm + + form = ResourcesForm() + + self.assertIn("resourceclassificationgroup", form.fields) + queryset = form.fields["resourceclassificationgroup"].queryset + self.assertTrue(queryset.exists()) + + +class PlacesResourceEventFormAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin7", password="password", email="admin7@example.com" + ) + + def test_places_resource_event_admin_add(self): + from TEKDB.admin import PlacesResourceEventForm + + form = PlacesResourceEventForm() + + self.assertIn("partused", form.fields) + queryset = form.fields["partused"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("season", form.fields) + queryset = form.fields["season"].queryset + self.assertTrue(queryset.exists()) + + self.assertIn("timing", form.fields) + queryset = form.fields["timing"].queryset + self.assertTrue(queryset.exists()) + + class MediaBulkUploadAdminTest(ITKTestCase): def setUp(self): self.factory = RequestFactory() @@ -21,21 +155,89 @@ def setUp(self): ) def test_media_bulk_upload_admin_add(self): + from TEKDB.models import ( + Places, + Resources, + Citations, + ResourcesActivityEvents, + PlacesResourceEvents, + ) + from TEKDB.admin import MediaBulkUploadAdmin + url = reverse("admin:TEKDB_mediabulkupload_add") test_image = SimpleUploadedFile( "./test_image.jpg", b"\x00\x00\x00\x00", content_type="image" ) - # TODO: Associate the images with 1+ Places, Resources, Citations, Activities, and PlacesResources + place = Places.objects.create(indigenousplacename="Test Place") + resource = Resources.objects.create(commonname="Test Resource") + citation = Citations.objects.create( + referencetext="Test Citation", referencetype_id=1 + ) + placeresource = PlacesResourceEvents.objects.create( + placeid=place, resourceid=resource + ) + activity = ResourcesActivityEvents.objects.create(placeresourceid=placeresource) + + post_data = { + "files": [test_image, test_image], + "places": [place.pk], + "resources": [resource.pk], + "citations": [citation.pk], + "activities": [activity.pk], + "placesresources": [placeresource.pk], + } + + request = self.factory.post(url, post_data) + request.user = self.user + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + bulk_form = MediaBulkUploadForm(request.POST) + bulk_form.is_valid() + bulk_admin.save_model( + obj=MediaBulkUpload(), request=request, form=bulk_form, change=None + ) + + self.assertTrue(Media.objects.filter(medianame="test_image").exists()) + self.assertEqual(Media.objects.filter(medianame="test_image").count(), 2) + + self.assertTrue(Places.objects.filter(pk=place.pk).exists()) + self.assertTrue(Resources.objects.filter(pk=resource.pk).exists()) + self.assertTrue(Citations.objects.filter(pk=citation.pk).exists()) + self.assertTrue(ResourcesActivityEvents.objects.filter(pk=activity.pk).exists()) + self.assertTrue( + PlacesResourceEvents.objects.filter(pk=placeresource.pk).exists() + ) + + for media in Media.objects.filter(medianame="test_image"): + self.assertTrue(os.path.exists(media.mediafile.path)) + os.remove( + media.mediafile.path + ) # Clean up the uploaded files after the test + self.assertFalse(os.path.exists(media.mediafile.path)) + media.delete() + + # Clean up related objects + activity.delete() + placeresource.delete() + citation.delete() + resource.delete() + place.delete() + + def test_media_bulk_upload_admin_other_types(self): + from TEKDB.admin import MediaBulkUploadAdmin + + url = reverse("admin:TEKDB_mediabulkupload_add") + test_other_type = SimpleUploadedFile( + "./test_thing.shp", b"\x00\x00\x00\x00", content_type="other" + ) request = self.factory.post( url, { - # 'mediabulkname': 'Test Bulk Upload', - # 'mediabulkdate': '2024-12-12', - "files": [test_image, test_image], + "files": [test_other_type, test_other_type], }, ) + request.user = self.user bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) bulk_form = MediaBulkUploadForm(request.POST) @@ -44,10 +246,7 @@ def test_media_bulk_upload_admin_add(self): obj=MediaBulkUpload(), request=request, form=bulk_form, change=None ) - self.assertTrue(Media.objects.filter(medianame="test_image").exists()) - self.assertTrue(Media.objects.filter(medianame="test_image").count() == 2) - - for media in Media.objects.filter(medianame="test_image"): + for media in Media.objects.filter(medianame="test_thing"): self.assertTrue(os.path.exists(media.mediafile.path)) os.remove( media.mediafile.path @@ -55,21 +254,355 @@ def test_media_bulk_upload_admin_add(self): self.assertFalse(os.path.exists(media.mediafile.path)) media.delete() - ### We don't need to test the admin change and delete views for now - # def test_media_bulk_upload_admin_change(self): - # media_bulk_upload = MediaBulkUpload.objects.create(mediabulkname='Test Bulk Upload', mediabulkdate='2023-10-01') - # url = reverse('admin:TEKDB_mediabulkupload_change', args=[media_bulk_upload.id]) - # response = self.client.post(url, { - # 'mediabulkname': 'Updated Bulk Upload', - # 'mediabulkdate': '2023-10-01', - # }) - # self.assertEqual(response.status_code, 302) - # media_bulk_upload.refresh_from_db() - # self.assertEqual(media_bulk_upload.mediabulkname, 'Updated Bulk Upload') - - # def test_media_bulk_upload_admin_delete(self): - # media_bulk_upload = MediaBulkUpload.objects.create(mediabulkname='Test Bulk Upload', mediabulkdate='2023-10-01') - # url = reverse('admin:TEKDB_mediabulkupload_delete', args=[media_bulk_upload.id]) - # response = self.client.post(url, {'post': 'yes'}) - # self.assertEqual(response.status_code, 302) - # self.assertFalse(MediaBulkUpload.objects.filter(id=media_bulk_upload.id).exists()) + def test_media_bulk_upload_admin_thumbnail_gallery(self): + from TEKDB.admin import MediaBulkUploadAdmin + from TEKDB.models import MediaBulkUpload, Media + + url = reverse("admin:TEKDB_mediabulkupload_add") + test_image = SimpleUploadedFile( + "./thumbnail_test_image.jpg", b"\x00\x00\x00\x00", content_type="image" + ) + test_video = SimpleUploadedFile( + "./thumbnail_test_video.mp4", b"\x00\x00\x00\x00", content_type="video" + ) + test_audio = SimpleUploadedFile( + "./thumbnail_test_audio.mp3", b"\x00\x00\x00\x00", content_type="audio" + ) + test_text = SimpleUploadedFile( + "./thumbnail_test_text.txt", b"\x00\x00\x00\x00", content_type="text" + ) + test_other = SimpleUploadedFile( + "./thumbnail_test_thing.shp", b"\x00\x00\x00\x00", content_type="other" + ) + test_unknown_type = SimpleUploadedFile( + "./thumbnail_test_unknown.xyz", b"\x00\x00\x00\x00", content_type="unknown" + ) + + post_data = { + "files": [ + test_image, + test_video, + test_audio, + test_text, + test_other, + test_unknown_type, + ], + } + + request = self.factory.post(url, post_data) + request.user = self.user + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + bulk_form = MediaBulkUploadForm(request.POST) + bulk_form.is_valid() + bulk_upload_obj = MediaBulkUpload() + bulk_admin.save_model( + obj=bulk_upload_obj, request=request, form=bulk_form, change=None + ) + + html = bulk_admin.thumbnail_gallery(bulk_upload_obj) + + self.assertIn("test_image", html) + self.assertIn("img", html) + self.assertIn("test_video", html) + self.assertIn("Your browser does not support the video tag.", html) + self.assertIn("test_audio", html) + self.assertIn("audio-x-generic.svg", html) + self.assertIn("test_text", html) + self.assertIn("doc-text.svg", html) + self.assertIn("test_thing", html) + self.assertIn("unknown-mail.png", html) + self.assertIn("test_unknown", html) + + # Clean up + for media in Media.objects.filter(medianame__icontains="thumbnail_test"): + if hasattr(media, "mediafile") and media.mediafile: + if os.path.exists(media.mediafile.path): + os.remove(media.mediafile.path) + media.delete() + + def test_media_bulk_upload_admin_get_readonly_fields(self): + from TEKDB.models import MediaBulkUpload + from TEKDB.admin import MediaBulkUploadAdmin + + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + readonly_fields_obj_exists = bulk_admin.get_readonly_fields( + request=None, obj=MediaBulkUpload() + ) + self.assertNotIn("thumbnail_gallery", readonly_fields_obj_exists) + self.assertIn("id", readonly_fields_obj_exists) + + readonly_fields_obj_not_exists = bulk_admin.get_readonly_fields( + request=None, obj=None + ) + self.assertIn("thumbnail_gallery", readonly_fields_obj_not_exists) + self.assertNotIn("id", readonly_fields_obj_not_exists) + + def test_media_bulk_upload_admin_has_change_permission(self): + from TEKDB.models import MediaBulkUpload + from TEKDB.admin import MediaBulkUploadAdmin + + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + has_change_perm = bulk_admin.has_change_permission( + request=None, obj=MediaBulkUpload() + ) + self.assertFalse(has_change_perm) + + def test_media_bulk_upload_admin_has_delete_permission(self): + from TEKDB.models import MediaBulkUpload + from TEKDB.admin import MediaBulkUploadAdmin + + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + has_delete_perm = bulk_admin.has_delete_permission( + request=None, obj=MediaBulkUpload() + ) + self.assertTrue(has_delete_perm) + + def test_media_bulk_upload_admin_has_add_permission(self): + from TEKDB.models import MediaBulkUpload + from TEKDB.admin import MediaBulkUploadAdmin + + bulk_admin = MediaBulkUploadAdmin(model=MediaBulkUpload, admin_site=AdminSite()) + has_add_perm = bulk_admin.has_add_permission(request=None) + self.assertTrue(has_add_perm) + + +class RecordAdminProxyTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin8", password="password", email="admin8@example.com" + ) + + def test_save_model_sets_enteredby_fields(self): + from TEKDB.admin import RecordAdminProxy + from TEKDB.models import Media + from django import forms + + class MediaForm(forms.ModelForm): + class Meta: + model = Media + fields = "__all__" + + # Create a minimal Media instance + media = Media.objects.create(medianame="test_media") + request = self.factory.post("/test/") + request.user = self.user + form = MediaForm(instance=media) + admin = RecordAdminProxy(Media, AdminSite()) + admin.save_model(request, media, form, change=False) + self.assertEqual(media.enteredbyname, self.user.username) + self.assertEqual(media.enteredbytribe, getattr(self.user, "affiliation", None)) + self.assertEqual(media.enteredbytitle, getattr(self.user, "title", None)) + self.assertEqual(media.modifiedbyname, self.user.username) + self.assertEqual(media.modifiedbytribe, getattr(self.user, "affiliation", None)) + self.assertEqual(media.modifiedbytitle, getattr(self.user, "title", None)) + media.delete() + + def test_save_formset_sets_enteredby_fields(self): + from TEKDB.admin import RecordAdminProxy + from TEKDB.models import Media + from django import forms + + class MediaForm(forms.ModelForm): + class Meta: + model = Media + fields = "__all__" + + media1 = Media.objects.create(medianame="test_media1") + media2 = Media.objects.create(medianame="test_media2") + request = self.factory.post("/test/") + request.user = self.user + + class MediaFormset: + def save(self, commit): + return [media1, media2] + + form = MediaForm() + formset = MediaFormset() + admin = RecordAdminProxy(Media, AdminSite()) + admin.save_formset(request, form, formset, change=False) + for media in [media1, media2]: + self.assertEqual(media.enteredbyname, self.user.username) + self.assertEqual( + media.enteredbytribe, getattr(self.user, "affiliation", None) + ) + self.assertEqual(media.enteredbytitle, getattr(self.user, "title", None)) + self.assertEqual(media.modifiedbyname, self.user.username) + self.assertEqual( + media.modifiedbytribe, getattr(self.user, "affiliation", None) + ) + self.assertEqual(media.modifiedbytitle, getattr(self.user, "title", None)) + media1.delete() + media2.delete() + + +class NestedRecordAdminProxyTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin9", password="password", email="admin9@example.com" + ) + + def test_save_model(self): + from TEKDB.admin import NestedRecordAdminProxy + from TEKDB.models import Places + from django import forms + + class PlacesForm(forms.ModelForm): + class Meta: + model = Places + fields = "__all__" + + place = Places.objects.create(indigenousplacename="test_place") + request = self.factory.post("/test/") + request.user = self.user + form = PlacesForm(instance=place) + admin = NestedRecordAdminProxy(Places, AdminSite()) + admin.save_model(request, place, form, change=False) + self.assertEqual(place.enteredbyname, self.user.username) + self.assertEqual(place.enteredbytribe, getattr(self.user, "affiliation", None)) + self.assertEqual(place.enteredbytitle, getattr(self.user, "title", None)) + self.assertEqual(place.modifiedbyname, self.user.username) + self.assertEqual(place.modifiedbytribe, getattr(self.user, "affiliation", None)) + self.assertEqual(place.modifiedbytitle, getattr(self.user, "title", None)) + place.delete() + + def test_save_formset(self): + from TEKDB.admin import NestedRecordAdminProxy + from TEKDB.models import Places + from django import forms + + class PlacesForm(forms.ModelForm): + class Meta: + model = Places + fields = "__all__" + + place1 = Places.objects.create(indigenousplacename="test_place1") + place2 = Places.objects.create(indigenousplacename="test_place2") + request = self.factory.post("/test/") + request.user = self.user + + class PlacesFormset: + def save(self, commit): + return [place1, place2] + + form = PlacesForm() + formset = PlacesFormset() + admin = NestedRecordAdminProxy(Places, AdminSite()) + admin.save_formset(request, form, formset, change=False) + for place in [place1, place2]: + self.assertEqual(place.enteredbyname, self.user.username) + self.assertEqual( + place.enteredbytribe, getattr(self.user, "affiliation", None) + ) + self.assertEqual(place.enteredbytitle, getattr(self.user, "title", None)) + self.assertEqual(place.modifiedbyname, self.user.username) + self.assertEqual( + place.modifiedbytribe, getattr(self.user, "affiliation", None) + ) + self.assertEqual(place.modifiedbytitle, getattr(self.user, "title", None)) + place1.delete() + place2.delete() + + def test_needs_review_true(self): + from TEKDB.admin import NestedRecordAdminProxy + from TEKDB.models import Places + + place = Places.objects.create(indigenousplacename="test_place") + request = self.factory.post("/test/") + request.user = self.user + + admin = NestedRecordAdminProxy(Places, AdminSite()) + result_true = admin.needs_Review(place) + self.assertIn("icon-alert.svg", result_true) + place.delete() + + def test_needs_review_false(self): + from TEKDB.admin import NestedRecordAdminProxy + from TEKDB.models import Places + + place = Places.objects.create( + indigenousplacename="test_place", needsReview=False + ) + request = self.factory.post("/test/") + request.user = self.user + + admin = NestedRecordAdminProxy(Places, AdminSite()) + result_false = admin.needs_Review(place) + self.assertIn("", result_false) + place.delete() + + +class RecordModelAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin10", password="password", email="admin10@example.com" + ) + + def test_needs_review_true(self): + from TEKDB.admin import RecordModelAdmin + from TEKDB.models import Places + + place = Places.objects.create(indigenousplacename="test_place") + request = self.factory.post("/test/") + request.user = self.user + + admin = RecordModelAdmin(Places, AdminSite()) + result_true = admin.needs_Review(place) + self.assertIn("icon-alert.svg", result_true) + place.delete() + + def test_needs_review_false(self): + from TEKDB.admin import RecordModelAdmin + from TEKDB.models import Places + + place = Places.objects.create( + indigenousplacename="test_place", needsReview=False + ) + request = self.factory.post("/test/") + request.user = self.user + + admin = RecordModelAdmin(Places, AdminSite()) + result_false = admin.needs_Review(place) + self.assertIn("", result_false) + place.delete() + + def test_change_view(self): + from TEKDB.admin import RecordModelAdmin + from TEKDB.models import Places + + place = Places.objects.create(indigenousplacename="test_place") + url = reverse("admin:TEKDB_places_change", args=[place.placeid]) + request = self.factory.get(url) + request.user = self.user + + admin = RecordModelAdmin(Places, AdminSite()) + with patch.object(RecordModelAdmin, "has_change_permission", return_value=True): + response = admin.change_view(request, str(place.placeid)) + self.assertEqual(response.status_code, 200) + place.delete() + + +class PlacesAdminTest(ITKTestCase): + def setUp(self): + self.factory = RequestFactory() + self.user = User.objects.create_superuser( + username="admin11", password="password", email="admin11@example.com" + ) + + def test_places_admin_changelist_view(self): + from TEKDB.admin import PlacesAdmin + from TEKDB.models import Places + + place = Places.objects.create(indigenousplacename="test_place") + url = reverse("admin:TEKDB_places_changelist") + request = self.factory.get(url) + request.user = self.user + + admin = PlacesAdmin(Places, AdminSite()) + with patch.object(PlacesAdmin, "has_change_permission", return_value=True): + response = admin.changelist_view(request) + self.assertEqual(response.status_code, 200) + self.assertIn("results_geojson", response.context_data) + place.delete() diff --git a/TEKDB/TEKDB/tests/test_context_processors.py b/TEKDB/TEKDB/tests/test_context_processors.py new file mode 100644 index 00000000..eb36d925 --- /dev/null +++ b/TEKDB/TEKDB/tests/test_context_processors.py @@ -0,0 +1,119 @@ +from django.test import TestCase, RequestFactory, override_settings +from TEKDB.context_processors import search_settings + + +class TestSearchSettingsContextProcessor(TestCase): + def setUp(self): + self.factory = RequestFactory() + + def test_search_settings_no_settings(self): + from unittest import mock + import builtins + + request = self.factory.get("/") + + real_import = builtins.__import__ + + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + if ( + name == "django.conf" + or name.startswith("django.conf") + or name == "TEKDB" + ): + raise ImportError("Mock missing settings module") + return real_import(name, globals, locals, fromlist, level) + + with mock.patch("builtins.__import__", side_effect=mock_import): + context = search_settings(request) + + self.assertIn("MIN_SEARCH_RANK", context) + self.assertIn("MIN_SEARCH_SIMILARITY", context) + self.assertEqual(context["MIN_SEARCH_RANK"], 0.01) + self.assertEqual(context["MIN_SEARCH_SIMILARITY"], 0.1) + + @override_settings() + def test_search_settings_missing_in_settings(self): + from django.conf import settings + + del settings.MIN_SEARCH_RANK + del settings.MIN_SEARCH_SIMILARITY + request = self.factory.get("/") + context = search_settings(request) + self.assertIn("MIN_SEARCH_RANK", context) + self.assertIn("MIN_SEARCH_SIMILARITY", context) + self.assertEqual(context["MIN_SEARCH_RANK"], 0.01) # Default + self.assertEqual(context["MIN_SEARCH_SIMILARITY"], 0.1) # Default + + @override_settings(MIN_SEARCH_RANK=0.05, MIN_SEARCH_SIMILARITY=0.2) + def test_search_settings_from_settings(self): + request = self.factory.get("/") + context = search_settings(request) + self.assertIn("MIN_SEARCH_RANK", context) + self.assertIn("MIN_SEARCH_SIMILARITY", context) + self.assertEqual(context["MIN_SEARCH_RANK"], 0.05) + self.assertEqual(context["MIN_SEARCH_SIMILARITY"], 0.2) + + def test_search_settings_configs_overrides_settings(self): + from configuration.models import Configuration + + Configuration.objects.create(min_search_rank=0.03, min_search_similarity=0.15) + + request = self.factory.get("/") + context = search_settings(request) + self.assertIn("MIN_SEARCH_RANK", context) + self.assertIn("MIN_SEARCH_SIMILARITY", context) + self.assertEqual(context["MIN_SEARCH_RANK"], 0.03) + self.assertEqual(context["MIN_SEARCH_SIMILARITY"], 0.15) + + @override_settings( + MIN_SEARCH_RANK=0.05, + ) + def test_search_settings_configs_missing_search_rank(self): + from configuration.models import Configuration + + Configuration.objects.create(min_search_rank=None, min_search_similarity=0.2) + + request = self.factory.get("/") + context = search_settings(request) + self.assertIn("MIN_SEARCH_RANK", context) + self.assertIn("MIN_SEARCH_SIMILARITY", context) + self.assertEqual(context["MIN_SEARCH_RANK"], 0.05) # From settings + self.assertEqual(context["MIN_SEARCH_SIMILARITY"], 0.2) # From config + + +class TestAddMapDefaultContextProcessor(TestCase): + def setUp(self): + self.factory = RequestFactory() + + @override_settings( + DATABASE_GEOGRAPHY={ + "default_lon": 13839794, + "default_lat": 5171449, + "default_zoom": 5, + } + ) + def test_add_map_default_context(self): + from TEKDB.context_processors import add_map_default_context + + request = self.factory.get("/") + context = add_map_default_context(request) + self.assertIn("default_lon", context) + self.assertIn("default_lat", context) + self.assertIn("default_zoom", context) + self.assertEqual(context["default_lat"], 5171449) + self.assertEqual(context["default_lon"], 13839794) + self.assertEqual(context["default_zoom"], 5) + + def test_add_map_default_context_override_settings_with_configs(self): + from configuration.models import Configuration + from django.contrib.gis.geos import Polygon + from TEKDB.context_processors import add_map_default_context + + Configuration.objects.create( + geometry=Polygon.from_bbox([2000000, 1000000, 3000000, 2000000]) + ) + + request = self.factory.get("/") + context = add_map_default_context(request) + self.assertIn("map_extent", context) + self.assertEqual(context["map_extent"], [2000000, 1000000, 3000000, 2000000]) diff --git a/TEKDB/TEKDB/tests/test_files/test_wrong_import.txt b/TEKDB/TEKDB/tests/test_files/test_wrong_import.txt new file mode 100644 index 00000000..e69de29b diff --git a/TEKDB/TEKDB/tests/test_models.py b/TEKDB/TEKDB/tests/test_models.py index 277eabe2..394ddab7 100644 --- a/TEKDB/TEKDB/tests/test_models.py +++ b/TEKDB/TEKDB/tests/test_models.py @@ -38,6 +38,8 @@ LookupTiming, LookupTribe, LookupUserInfo, + Locality, + Users, ) # from .forms import * @@ -148,6 +150,46 @@ def test_empty_string_search(self): self.assertTrue(len(resultlist) == model.objects.count()) + def test_all_search_results(self): + """ + Test that a model_type of 'all' returns results from specific categories + """ + categories = [ + "resources", + "places", + "citations", + "media", + "resourcesactivityevents", + ] + + from explore.views import get_model_by_type + + query_models = get_model_by_type("all") + lowercase_query_model_names = [model.__name__.lower() for model in query_models] + + for category in categories: + self.assertIn(category, lowercase_query_model_names) + + def test_no_keyword_get_results(self): + """ + Test that an empty string search returns all objects + """ + keyword = None + + from explore.views import get_results + + search_results = get_results( + keyword, + categories=["resources"], + ) + all_ranks = set([x["rank"] for x in search_results]) + all_similarities = set([x["similarity"] for x in search_results]) + all_headlines = set([x["headline"] for x in search_results]) + + self.assertEqual(all_ranks, {0}) + self.assertEqual(all_similarities, {0}) + self.assertEqual(all_headlines, {None}) + def test_phrase_search(self): """ Test that a phrase search returns all objects that contain the phrase @@ -317,6 +359,14 @@ def test_get_greatest_similarity_attribute_multiple_matches(self): #################################################### +class RecordTest(ITKTestCase): + def test_get_related_objects(self): + from TEKDB.models import Record + + related_objects = Record.get_related_objects(Record, 1) + self.assertEqual(len(related_objects), 0) + + # Places class PlacesTest(ITKSearchTest): def test_placess(self): @@ -392,6 +442,32 @@ def test_place_id_collision(self): collision_result = test_model_id_collision(Places, insertion_object, self) self.assertTrue(collision_result) + def test_relationships(self): + """ + Test that place relationships are correctly identified + """ + place = Places.objects.get(pk=21) # Place with all types of relationships + relationships = place.relationships() + self.assertEqual(len(relationships), 4) + + def test_link(self): + """ + Test that place link is correctly generated + """ + place = Places.objects.get(pk=21) + link = place.link() + self.assertEqual(link, "/explore/places/21/") + + def test_get_response_format_no_feature(self): + """ + Test that get_response_format works when no feature is provided + """ + place = Places.objects.get(pk=28) # Place with no geometry + response = place.get_response_format() + self.assertIn("id", response) + self.assertIn("feature", response) + self.assertIsNone(response["feature"]) + # Resources class ResourcesTest(ITKSearchTest): @@ -489,6 +565,31 @@ def test_resource_id_collision(self): collision_result = test_model_id_collision(Resources, insertion_object, self) self.assertTrue(collision_result) + def test_subtitle(self): + """ + Test that resource subtitle is correctly generated + """ + resource = Resources.objects.get(pk=151) # abalone, Black + subtitle = resource.subtitle() + self.assertEqual(subtitle, "cracherdoii") + + def test_relationships(self): + """ + Test that resource relationships are correctly identified + """ + ResourcesMediaEvents.objects.create( + **{ + "resourceid": Resources.objects.get(pk=207), + "mediaid": Media.objects.get(pk=6), + } + ) + resource = Resources.objects.get( + pk=207 + ) # Resource with multiple types of relationships + relationships = resource.relationships() + + self.assertEqual(len(relationships), 4) + # ResourcesActivityEvents ('Activities') class ResourcesActivityEventsTest(ITKSearchTest): @@ -528,6 +629,64 @@ def test_activity_id_collision(self): ) self.assertTrue(collision_result) + def test_relationships(self): + """ + Test that activity relationships are correctly returned + """ + activity = ResourcesActivityEvents.objects.get(pk=31) + ResourceActivityMediaEvents.objects.create( + **{ + "pk": 2, + "resourceactivityid": activity, + "mediaid": Media.objects.all()[0], + } + ) + relationships = activity.relationships() + + self.assertEqual(len(relationships), 3) + + def test_data(self): + """ + Test that activity data is correctly generated with all fields + """ + activity = ResourcesActivityEvents.objects.create( + **{ + "placeresourceid": PlacesResourceEvents.objects.all()[0], + "relationshipdescription": "This is a test activity.", + "partused": LookupPartUsed.objects.all()[0], + "activityshortdescription": LookupActivity.objects.all()[0], + "activitylongdescription": "This is a long description of the activity.", + "participants": LookupParticipants.objects.all()[0], + "technique": LookupTechniques.objects.all()[0], + "gear": "Test gear", + "customaryuse": "customary use", + "timing": LookupTiming.objects.all()[0], + "timingdescription": "This is a description of the timing.", + } + ) + activity.save() + retrieved_activity = ResourcesActivityEvents.objects.get(pk=activity.pk) + data = retrieved_activity.data() + + keys = [ + "place", + "resource", + "excerpt", + "part used", + "activity type", + "full description", + "participants", + "technique", + "gear", + "customary use", + "timing", + "timing description", + ] + + data_keys = [item["key"].lower() for item in data] + for key in keys: + self.assertIn(key, data_keys) + # Citations (Bibliographic 'Sources') class CitationsTest(ITKSearchTest): @@ -600,6 +759,72 @@ def test_citation_id_collision(self): collision_result = test_model_id_collision(Citations, insertion_object, self) self.assertTrue(collision_result) + def test_relationships(self): + """ + Test that citation relationships are correctly identified + """ + interviewee = People.objects.create(firstname="Test", lastname="Interviewee") + interviewer = People.objects.create(firstname="Test", lastname="Interviewer") + citation = Citations.objects.create( + referencetext="Test Citation for Relationships", + referencetype=LookupReferenceType.objects.get( + pk=3 + ), # interview documenttype + intervieweeid=interviewee, + interviewerid=interviewer, + ) + + # create one of each relationship type + PlacesCitationEvents.objects.create( + placeid=Places.objects.first(), citationid=citation + ) + ResourcesCitationEvents.objects.create( + resourceid=Resources.objects.first(), citationid=citation + ) + MediaCitationEvents.objects.create( + mediaid=Media.objects.first(), citationid=citation + ) + ResourceActivityCitationEvents.objects.create( + resourceactivityid=ResourcesActivityEvents.objects.first(), + citationid=citation, + ) + PlacesResourceCitationEvents.objects.create( + placeresourceid=PlacesResourceEvents.objects.first(), citationid=citation + ) + relationships = citation.relationships() + self.assertEqual(len(relationships), 6) + + def test_title_text_interview(self): + """ + Test that citation title_text is correctly generated for interview type + """ + citation = Citations.objects.get(pk=12) # Citation with interview referencetype + title_text = citation.title_text + self.assertEqual(title_text, "Jay Tosanic: interviewed by Manahe Herman") + + def test_title_text_non_interview(self): + """ + Test that citation title_text is correctly generated + """ + citation = Citations.objects.get( + pk=11 + ) # Citation of non interview referencetype + title_text = citation.title_text + self.assertEqual( + title_text, "Traditional Marine Harvesting of Native Americans" + ) + + def test_description_text(self): + """ + Test that citation description_text is correctly generated + """ + citation = Citations.objects.get( + pk=11 + ) # Citation of non interview referencetype + description_text = citation.description_text + expected_description = "Text on marine flora and fauna" + self.assertEqual(description_text, expected_description) + # Media class MediaTest(ITKSearchTest): @@ -1080,6 +1305,56 @@ def test_places_resource_get_query_json_no_map(self): self.assertNotIn("map", query_json) + def test_keyword_search(self): + """ + Test that keyword_search returns expected results + """ + place = Places.objects.create( + indigenousplacename="Keyword Place", + englishplacename="Keyword Place English", + ) + resource = Resources.objects.create( + commonname="Keyword Resource", indigenousname="Keyword Indigenous Resource" + ) + timing = LookupTiming.objects.create(timing="Seasonal") + event = PlacesResourceEvents.objects.create( + placeid=place, + resourceid=resource, + timing=timing, + ) + keyword = "keyword" + results = PlacesResourceEvents.keyword_search(keyword) + self.assertIn(event, results) + + def test_relationships(self): + place = Places.objects.get(pk=19) + resource = Resources.objects.create( + commonname="Test Resource", indigenousname="Test Resource Indigenous" + ) + + citation = Citations.objects.create( + referencetype=LookupReferenceType.objects.get(pk=1), + referencetext="Test Citation", + ) + + event = PlacesResourceEvents.objects.create( + placeid=place, + resourceid=resource, + relationshipdescription="Test Relationship", + ) + PlacesResourceCitationEvents.objects.create( + placeresourceid=event, + citationid=citation, + relationshipdescription="Test Place-Resource-Citation Relationship", + ) + ResourcesActivityEvents.objects.create( + placeresourceid=event, + relationshipdescription="Test Activity", + ) + relationships = event.relationships() + + self.assertEqual(len(relationships), 4) + class PlacesResourceEventsCascadeTest(ITKTestCase): # fixtures = ['/usr/local/apps/TEKDB/TEKDB/TEKDB/fixtures/all_dummy_data.json',] @@ -1160,6 +1435,72 @@ def test_place_citation_relationship_id_collision(self): ) self.assertTrue(collision_result) + def test_keyword_search(self): + """ + Test that keyword_search returns expected results + """ + place = Places.objects.create( + indigenousplacename="Keyword Place", + englishplacename="Keyword Place English", + ) + citation = Citations.objects.create( + referencetext="Keyword Citation", + referencetype=LookupReferenceType.objects.create(documenttype="Book"), + ) + event = PlacesCitationEvents.objects.create( + placeid=place, + citationid=citation, + relationshipdescription="Keyword Relationship", + ) + keyword = "keyword" + results = PlacesCitationEvents.keyword_search(keyword) + self.assertIn(event, results) + + def test_property_and_methods(self): + """ + Test that description_text is correctly generated + """ + place = Places.objects.create( + indigenousplacename="Description Place", + englishplacename="Description Place English", + ) + citation = Citations.objects.create( + referencetext="Description Citation", + referencetype=LookupReferenceType.objects.create(documenttype="Book"), + ) + relationship_description = "Description Relationship" + event = PlacesCitationEvents.objects.create( + placeid=place, + citationid=citation, + relationshipdescription=relationship_description, + ) + description_text = event.description_text + + self.assertEqual(description_text, relationship_description) + self.assertEqual(event.image(), settings.RECORD_ICONS["activity"]) + self.assertEqual(event.subtitle(), event.relationshipdescription) + self.assertEqual(event.link(), f"/explore/placescitationevents/{event.pk}/") + + def test_relationships(self): + place = Places.objects.create( + indigenousplacename="Relationship Place", + englishplacename="Relationship Place English", + ) + citation = Citations.objects.create( + referencetext="Relationship Citation", + referencetype=LookupReferenceType.objects.create(documenttype="Book"), + ) + event = PlacesCitationEvents.objects.create( + placeid=place, + citationid=citation, + relationshipdescription="Relationship Description", + ) + relationships = event.relationships() + + self.assertEqual( + len(relationships), 2 + ) # should have place and citation relationships + class PlacesCitationEventsCascadeTest(ITKTestCase): def setUp(self): @@ -1517,6 +1858,119 @@ def test_people_id_collision(self): collision_result = test_model_id_collision(People, insertion_object, self) self.assertTrue(collision_result) + def test_image(self): + """ + Test that the image is from the settings + """ + person = People.objects.create( + firstname="Image", + lastname="Tester", + ) + image_data = person.image() + + self.assertEqual(settings.RECORD_ICONS["person"], image_data) + + def test_link(self): + """ + Test that the link is correctly generated + """ + person = People.objects.create( + firstname="Link", + lastname="Tester", + ) + link = person.link() + + expected_link = f"/explore/people/{person.pk}" + self.assertEqual(expected_link, link) + + def test_data(self): + """ + Test that data method returns correct structure + """ + person = People.objects.create( + firstname="John", + lastname="Doe", + yearborn=1980, + village="Sample Village", + relationshiptootherpeople="Sample Relationship", + ) + data = person.data() + + keys = [ + "first name", + "last name", + "year born", + "village", + "relationship to others", + ] + + for item in data: + key = item["key"] + val = item["value"] + self.assertIn(key, keys) + if key == "first name": + self.assertEqual(val, "John") + elif key == "last name": + self.assertEqual(val, "Doe") + elif key == "year born": + self.assertEqual(val, "1980") + elif key == "village": + self.assertEqual(val, "Sample Village") + elif key == "relationship to others": + self.assertEqual(val, "Sample Relationship") + + def test_relationships(self): + """ + Test that relationships method returns correct sources + """ + person = People.objects.create( + firstname="John", + lastname="Doe", + ) + Citations.objects.create( + referencetype=LookupReferenceType.objects.get(pk=1), + referencetext="Interview Citation", + intervieweeid=person, + ) + Citations.objects.create( + referencetype=LookupReferenceType.objects.get(pk=1), + referencetext="Interviewer Citation", + interviewerid=person, + ) + + relationships = person.relationships() + self.assertEqual(len(relationships), 2) + + def test_get_query_json(self): + """ + Test that get_query_json method returns correct structure + """ + person = People.objects.create( + firstname="John", + lastname="Doe", + ) + query_json = person.get_query_json() + + self.assertEqual(query_json["name"], "John Doe") + self.assertEqual(query_json["link"], f"/explore/people/{person.pk}") + self.assertEqual(query_json["image"], settings.RECORD_ICONS["person"]) + + def test_get_record_dict(self): + """ + Test that get_record_dict method returns correct structure + """ + person = People.objects.create( + firstname="John", + lastname="Doe", + village="Sample Village", + ) + user = Users.objects.get(username="admin") + record_dict = person.get_record_dict(user=user) + + self.assertEqual(record_dict["name"], "John Doe") + self.assertEqual(record_dict["subtitle"], "Sample Village") + self.assertFalse(record_dict["map"]) + # LookupPlanningUnit class LookupPlanningUnitTest(ITKTestCase): @@ -1693,3 +2147,38 @@ def test_lookup_user_inf_id_collision(self): #################################################### # Locality + + +class LocalityTest(ITKTestCase): + def test_keyword_search(self): + """ + Test that keyword_search returns expected results + """ + place = Places.objects.create( + indigenousplacename="Keyword Place", + englishplacename="Keyword Place English", + ) + locality = Locality.objects.create( + englishname="Keyword Locality", + placeid=place, + ) + keyword = "keyword" + results = Locality.keyword_search(keyword) + self.assertIn(locality, results) + + def test_methods_properties(self): + """ + Test that methods and properties return expected results + """ + place = Places.objects.create( + indigenousplacename="Place", + englishplacename="Place English", + ) + locality = Locality.objects.create( + englishname="Locality", + indigenousname="Indigenous Locality", + placeid=place, + ) + self.assertEqual(locality.image(), settings.RECORD_ICONS["place"]) + self.assertEqual(locality.subtitle(), locality.indigenousname) + self.assertEqual(locality.link(), f"/explore/locality/{locality.pk}/") diff --git a/TEKDB/TEKDB/tests/test_views.py b/TEKDB/TEKDB/tests/test_views.py index 25a63112..47fbf429 100644 --- a/TEKDB/TEKDB/tests/test_views.py +++ b/TEKDB/TEKDB/tests/test_views.py @@ -474,52 +474,53 @@ def test_valid_admin_export(self): self.assertEqual(response.cookies["export_status"].value, "done") -class ImportTest(TransactionTestCase): - # fixtures = ['TEKDB/fixtures/all_dummy_data.json',] - - @classmethod - def setUp(cls): +class ImportDatabaseTest(TransactionTestCase): + def setUp(self): import_fixture_file( join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") ) + self.factory = RequestFactory() + self.credentials = b64encode(b"admin:admin").decode("ascii") + self.dummy_1_name = "Dummy Record 1" + self.zipname = join( + settings.BASE_DIR, "TEKDB", "tests", "test_files", "exported_db.zip" + ) + self.tempmediadir = tempfile.TemporaryDirectory() + self.wrong_file_name = join( + settings.BASE_DIR, + "TEKDB", + "tests", + "test_files", + "test_wrong_import.txt", + ) - cls.factory = RequestFactory() - - cls.credentials = b64encode(b"admin:admin").decode("ascii") - cls.dummy_1_name = "Dummy Record 1" - - cls.old_resources_count = Resources.objects.all().count() - new_record = Resources.objects.create(commonname=cls.dummy_1_name) + def test_successful_import(self): + self.old_resources_count = Resources.objects.all().count() + new_record = Resources.objects.create(commonname=self.dummy_1_name) new_record.save() # Resources.moderated_object.fget(new_record).approve() - cls.zipname = join( - settings.BASE_DIR, "TEKDB", "tests", "test_files", "exported_db.zip" - ) - cls.tempmediadir = tempfile.TemporaryDirectory() - cls.import_request = cls.factory.post( + self.import_request = self.factory.post( reverse("import_database"), { - "MEDIA_DIR": cls.tempmediadir.name, + "MEDIA_DIR": self.tempmediadir.name, "content_type": "application/zip", "content_disposition": "attachment; filename=uploaded.dump", }, - headers={"Authorization": f"Basic {cls.credentials}"}, + headers={"Authorization": f"Basic {self.credentials}"}, ) - with open(cls.zipname, "rb") as z: + with open(self.zipname, "rb") as z: import_file = InMemoryUploadedFile( z, "import_file", "exported_db.zip", "application/zip", - getsize(cls.zipname), + getsize(self.zipname), None, ) - cls.import_request.FILES["import_file"] = import_file - cls.import_request.user = Users.objects.get(username="admin") - response = ImportDatabase(cls.import_request) # noqa: F841 - - def test_import(self): + self.import_request.FILES["import_file"] = import_file + self.import_request.user = Users.objects.get(username="admin") + ImportDatabase(self.import_request) self.assertEqual(Resources.objects.all().count(), self.old_resources_count) self.assertEqual( Resources.objects.filter(commonname=self.dummy_1_name).count(), 0 @@ -533,117 +534,87 @@ def test_import(self): self.assertTrue(media_name in listdir(self.tempmediadir.name)) shutil.rmtree(self.tempmediadir.name) + def test_failed_import_wrong_method(self): + self.import_request = self.factory.get( + reverse("import_database"), + { + "MEDIA_DIR": self.tempmediadir.name, + "content_type": "application/zip", + "content_disposition": "attachment; filename=uploaded.dump", + }, + headers={"Authorization": f"Basic {self.credentials}"}, + ) + with open(self.zipname, "rb") as z: + import_file = InMemoryUploadedFile( + z, + "import_file", + "exported_db.zip", + "application/zip", + getsize(self.zipname), + None, + ) + self.import_request.FILES["import_file"] = import_file + self.import_request.user = Users.objects.get(username="admin") + response = ImportDatabase(self.import_request) + self.assertEqual(response.status_code, 405) + + def test_failed_import_incorrect_file_type(self): + self.import_request = self.factory.post( + reverse("import_database"), + { + "MEDIA_DIR": self.tempmediadir.name, + "content_type": "application/zip", + "content_disposition": "attachment; filename=uploaded.dump", + }, + headers={"Authorization": f"Basic {self.credentials}"}, + ) + # Upload a non-zip file + with open( + self.wrong_file_name, + "rb", + ) as f: + import_file = InMemoryUploadedFile( + f, + "import_file", + "test_wrong_import.txt", + "text/plain", + getsize(self.wrong_file_name), + None, + ) + self.import_request.FILES["import_file"] = import_file + self.import_request.user = Users.objects.get(username="admin") + response = ImportDatabase(self.import_request) + self.assertEqual(response.status_code, 400) + response.data = json.loads(response.content) + self.assertEqual( + "Uploaded file is not recognized as a zipfile. Be sure you have a valid backup file and try again.", + response.data["status_message"], + ) + + +class GetPlacesGeoJSONTest(TestCase): + def setUp(self): + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + self.factory = RequestFactory() + self.credentials = b64encode(b"admin:admin").decode("ascii") + + def test_get_places_geojson(self): + from TEKDB.views import get_places_geojson + + user = Users.objects.get(username="admin") + self.client.force_login(user) + self.import_request = self.factory.get( + "/admin/TEKDB/places/", + { + "content_type": "application/zip", + }, + headers={"Authorization": f"Basic {self.credentials}"}, + ) + self.import_request.user = user + + places_geojson = get_places_geojson(self.import_request) -### 2025-05-09: No one has any idea what a 'placeMap' is. -# class PlaceMapTest(TestCase): -# # fixtures = ['TEKDB/fixtures/all_dummy_data.json',] - -# @classmethod -# def setUp(cls): -# import_fixture_file(join(settings.BASE_DIR, 'TEKDB', 'fixtures', 'all_dummy_data.json')) - -# cls.factory = RequestFactory() -# cls.credentials = b64encode(b"admin:admin").decode("ascii") -# cls.dummy_1_name = "Dummy Record 1" - -# new_record = Places.objects.create(englishplacename="Dummy Record 1") -# new_record.save() - -# def test_placeMap(self): -# request = self.factory.get(reverse('placeMap')) -# request.user = Users.objects.get(username='admin') -# response = placeMap(request) -# self.assertEqual(response.status_code, 200) - - -# class ExportImportTest(TestCase): -# # fixtures = ['TEKDB/fixtures/all_dummy_data.json',] -# -# @classmethod -# def setUpTestData(cls): -# cls.factory = RequestFactory() -# cls.credentials = b64encode(b"admin:admin").decode("ascii") -# -# cls.dummy_1_name = "Dummy Record 1" -# cls.dummy_2_name = "Dummy Record 2" -# -# import ipdb; ipdb.set_trace() -# -# new_record = Resources.objects.create(commonname=cls.dummy_1_name) -# new_record.save() -# -# # Count records -# cls.original_media_count = Media.objects.all().count() -# cls.original_places_count = Places.objects.all().count() -# cls.original_resources_count = Resources.objects.all().count() -# cls.original_citations_count = Citations.objects.all().count() -# cls.original_activities_count = ResourcesActivityEvents.objects.all().count() -# -# export_request = create_export_request(cls) -# export_request.user = Users.objects.get(username='admin') -# response = ExportDatabase(export_request) -# cls.tempdir = tempfile.gettempdir() -# zipname = get_export_file_from_response(response, cls.tempdir) -# -# new_record2 = Resources.objects.create(commonname=cls.dummy_2_name) -# new_record2.save() -# -# cls.tempmediadir = tempfile.gettempdir() -# cls.import_request = cls.factory.post( -# reverse('import_database'), -# { -# 'MEDIA_DIR': cls.tempmediadir, -# }, -# headers = { -# "Authorization": f"Basic {cls.credentials}" -# }, -# ) -# # Attach .zip to request -# with open(zipname, 'rb') as z: -# cls.import_request.FILES['import_file'] = z -# cls.import_request.FILES['import_file'].read() -# z.seek(0) -# -# cls.import_request.user = Users.objects.get(username='admin') -# response = ImportDatabase(cls.import_request) -# -# remove(zipname) -# -# def test_export_import(self): -# ############################################################ -# ### IMPORT -# ############################################################ -# tempdir = self.tempdir -# # zipname = self.zipname -# -# # Prep for test import: -# # Create temp dir to write media to -# -# # view must take optional MEDIA_DIR location -# # MAKE SURE TEST DOESN"T OVERRIDE LIVE DB!!! -# self.assertEqual(Resources.objects.all().count(), self.original_resources_count ) -# self.assertEqual(Media.objects.all().count(), self.original_media_count) -# self.assertEqual(Places.objects.all().count(), self.original_places_count) -# self.assertEqual(Resources.objects.all().count(), self.original_resources_count) -# self.assertEqual(Citations.objects.all().count(), self.original_citations_count) -# self.assertEqual(ResourcesActivityEvents.objects.all().count(), self.original_activities_count) -# # test for added record -# dummy1_q = Resources.objects.filter(commonname=self.dummy_1_name) -# dummy2_q = Resources.objects.filter(commonname=self.dummy_2_name) -# self.assertEqual(dummy1_q.count(), 1) -# self.assertEqual(dummy2_q.count(), 0) -# self.assertEqual(dummy1_q[0].pk, first_resource.pk) -# # -# # import .zip -# # Create Request -# -# self.import_request.user = AnonymousUser() -# anon_response = ImportDatabase(self.import_request) -# # Users w/out adequate permissions should be redirected (302) -# self.assertEqual(anon_response.status_code, 302) -# self.import_request.user = Users.objects.get(username='readonly') -# bad_response = ImportDatabase(self.import_request) -# self.assertEqual(bad_response.status_code, 302) -# -# # self.assertEqual(response.status_code, 200) -# # Specify target MEDIA_DIR in request + self.assertTrue(isinstance(places_geojson, dict)) + self.assertIn("features", places_geojson) diff --git a/TEKDB/TEKDB/views.py b/TEKDB/TEKDB/views.py index 915b48cd..0da6aec9 100644 --- a/TEKDB/TEKDB/views.py +++ b/TEKDB/TEKDB/views.py @@ -88,7 +88,7 @@ def ExportDatabase(request, test=False): zip.write(media_file) response = FileResponse(open(tmp_zip.name, "rb")) - + finally: try: if not test: @@ -97,7 +97,7 @@ def ExportDatabase(request, test=False): except (PermissionError, NotADirectoryError): response.set_cookie("export_status", "error") pass - return response + return response def getDBTruncateCommand(): @@ -117,6 +117,10 @@ def ImportDatabase(request): if not request.method == "POST": status_code = 405 status_message = "Request method not allowed. Must be a post." + return JsonResponse( + {"status_code": status_code, "status_message": status_message}, + status=status_code, + ) else: if "MEDIA_DIR" in request.POST.keys() and os.path.exists( request.POST["MEDIA_DIR"] @@ -135,13 +139,15 @@ def ImportDatabase(request): for chunk in request.FILES["import_file"].chunks(): tmp_zip_file.write(chunk) tmp_zip_file.seek(0) + except Exception as e: status_code = 500 status_message = 'Unable to read the provided file. Be sure it is a zipped file containing a .json representing the database and a "media" directory containing any static files. {}'.format( e ) return JsonResponse( - {"status_code": status_code, "status_message": status_message} + {"status_code": status_code, "status_message": status_message}, + status=status_code, ) if zipfile.is_zipfile(tmp_zip_file): zip = zipfile.ZipFile(tmp_zip_file, "r") @@ -156,7 +162,8 @@ def ImportDatabase(request): { "status_code": status_code, "status_message": status_message, - } + }, + status=status_code, ) fixture_name = non_media[0] try: @@ -170,7 +177,8 @@ def ImportDatabase(request): { "status_code": status_code, "status_message": status_message, - } + }, + status=status_code, ) try: # Emptying DB tables @@ -187,7 +195,8 @@ def ImportDatabase(request): { "status_code": status_code, "status_message": status_message, - } + }, + status=status_code, ) try: @@ -216,7 +225,8 @@ def ImportDatabase(request): { "status_code": status_code, "status_message": status_message, - } + }, + status=status_code, ) try: @@ -235,7 +245,8 @@ def ImportDatabase(request): { "status_code": status_code, "status_message": status_message, - } + }, + status=status_code, ) status_code = 200 status_message = "Database import completed successfully." @@ -248,18 +259,20 @@ def ImportDatabase(request): "Request must have an attached zipfile to restore the database from" ) - return JsonResponse({"status_code": status_code, "status_message": status_message}) + return JsonResponse( + {"status_code": status_code, "status_message": status_message}, + status=status_code, + ) # Only Authenticated Users! @permission_required("TEKDB.change_list") -def getPlacesGeoJSON(request): +def get_places_geojson(request): from .models import Places import json # Get all places places = Places.objects.exclude(geometry__isnull=True) - # GeoJSON to store all places geojson = {"type": "FeatureCollection", "features": []} diff --git a/TEKDB/explore/tests/test_context_processors.py b/TEKDB/explore/tests/test_context_processors.py new file mode 100644 index 00000000..f1fb29cf --- /dev/null +++ b/TEKDB/explore/tests/test_context_processors.py @@ -0,0 +1,135 @@ +from django.test import TestCase, RequestFactory, override_settings +from explore.context_processors import explore_context + + +class TestExploreContext(TestCase): + def setUp(self): + self.factory = RequestFactory() + + def test_search_settings_no_settings(self): + from unittest import mock + import builtins + + request = self.factory.get("/") + + real_import = builtins.__import__ + + def mock_import(name, globals=None, locals=None, fromlist=(), level=0): + if ( + name == "django.conf" + or name.startswith("django.conf") + or name == "TEKDB" + ): + raise ImportError("Mock missing settings module") + return real_import(name, globals, locals, fromlist, level) + + with mock.patch("builtins.__import__", side_effect=mock_import): + context = explore_context(request) + + self.assertIn("proj_logo_text", context) + self.assertIn("proj_text_placement", context) + + @override_settings( + PROJ_LOGO_TEXT="", + PROJ_CSS={}, + PROJ_ICONS={}, + PROJ_IMAGE_SELECT="", + PROJ_IMAGE_ATTR="", + HOME_FONT_COLOR="#FFFFFF", + HOME_LEFT_BACKGROUND="#000000", + HOME_RIGHT_BACKGROUND="#000000", + MEDIA_ROOT="/media/", + MEDIA_URL="/media/", + ) + def test_explore_context_with_default_values(self): + request = self.factory.get("/") + context = explore_context(request) + + # Assertions for default values + self.assertEqual(context["proj_logo_text"], "ITK") + self.assertEqual(context["proj_text_placement"], "default") + self.assertEqual(context["proj_css"]["primary_a"], "#8f371c") + self.assertEqual( + context["proj_icons"]["logo"], "/static/explore/img/logos/logo_weave.svg" + ) + self.assertEqual( + context["proj_image_select"], + "/static/explore/img/homepage/5050508427_ec55eed5f4_o.jpg", + ) + self.assertEqual( + context["home_image_attribution"], + 'Image courtesy of Monteregina and used under the CC BY-NC-SA 2.0 Licence. No changes were made.', + ) + self.assertEqual(context["home_font_color"], "#FFFFFF") + self.assertEqual(context["homepage_left_background"], "#000000") + self.assertEqual(context["homepage_right_background"], "#000000") + self.assertEqual( + context["map_pin"], "/static/explore/img/icons/explore_map_pin.svg" + ) + self.assertEqual( + context["map_pin_selected"], + "/static/explore/img/icons/explore_map_pin_selected.svg", + ) + + @override_settings( + PROJ_LOGO_TEXT="Custom Logo", + PROJ_CSS={"primary_a": "#123456"}, + PROJ_ICONS={"logo": "/custom/logo.svg"}, + PROJ_IMAGE_SELECT="/custom/image.jpg", + PROJ_IMAGE_ATTR="Custom Attribution", + HOME_FONT_COLOR="#ABCDEF", + HOME_LEFT_BACKGROUND="#123123", + HOME_RIGHT_BACKGROUND="#321321", + MEDIA_ROOT="/media/", + MEDIA_URL="/media/", + ) + def test_explore_context_with_custom_settings(self): + request = self.factory.get("/") + context = explore_context(request) + + # Assertions for custom settings + self.assertEqual(context["proj_logo_text"], "Custom Logo") + self.assertEqual(context["proj_css"]["primary_a"], "#123456") + self.assertEqual(context["proj_icons"]["logo"], "/custom/logo.svg") + self.assertEqual(context["proj_image_select"], "/custom/image.jpg") + self.assertEqual(context["home_image_attribution"], "Custom Attribution") + self.assertEqual(context["home_font_color"], "#ABCDEF") + self.assertEqual(context["homepage_left_background"], "#123123") + self.assertEqual(context["homepage_right_background"], "#321321") + self.assertEqual( + context["map_pin"], "/static/explore/img/icons/explore_map_pin.svg" + ) + self.assertEqual( + context["map_pin_selected"], + "/static/explore/img/icons/explore_map_pin_selected.svg", + ) + + @override_settings( + PROJ_LOGO_TEXT="", + PROJ_CSS={}, + PROJ_ICONS={}, + MEDIA_ROOT="/media/", + MEDIA_URL="/media/", + ) + def test_explore_context_with_configuration_model(self): + from configuration.models import Configuration + + Configuration.objects.create( + preferredInitialism="Config Logo", + preferredInitialismPlacement="top", + homepage_image_attribution="Config Attribution", + homepage_font_color="#FFFFFF", + homepage_left_background="#000000", + homepage_right_background="#467011", + ) + + request = self.factory.get("/") + context = explore_context(request) + + # Assertions for configuration model + self.assertEqual(context["proj_logo_text"], "Config Logo") + self.assertEqual(context["proj_text_placement"], "top") + self.assertEqual(context["home_image_attribution"], "Config Attribution") + self.assertEqual(context["home_font_color"], "#FFFFFF") + self.assertEqual(context["homepage_left_background"], "#000000") + self.assertEqual(context["homepage_right_background"], "#467011") diff --git a/TEKDB/explore/tests/test_views.py b/TEKDB/explore/tests/test_views.py index 798e9a3f..5358ac57 100644 --- a/TEKDB/explore/tests/test_views.py +++ b/TEKDB/explore/tests/test_views.py @@ -5,6 +5,8 @@ from django.test.client import RequestFactory from django.urls import reverse from os.path import join +from unittest.mock import patch, MagicMock + from TEKDB.tests.test_views import import_fixture_file ######################################################################### @@ -13,6 +15,145 @@ ######################################################################### +class HomeViewTest(TestCase): + def test_home_view_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = True + mock_obj.html_content = "Test HTML Home" + mock_get.return_value = mock_obj + response = self.client.get("") + self.assertEqual(response.status_code, 200) + self.assertIn("Test HTML Home", response.content.decode()) + + def test_home_view_not_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = False + mock_obj.content = "Test Text Home" + mock_get.return_value = mock_obj + response = self.client.get("") + self.assertEqual(response.status_code, 200) + self.assertIn("Test Text Home", response.content.decode()) + + def test_home_view(self): + url = "" + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "home") + self.assertEqual(response.context["pageTitle"], "Welcome") + self.assertEqual( + response.context["pageContent"], + "

Welcome

Set Welcome Page Content In Admin

", + ) + + +class AboutViewTest(TestCase): + def test_about_view_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = True + mock_obj.html_content = "Test HTML About" + mock_get.return_value = mock_obj + response = self.client.get("/about/") + self.assertEqual(response.status_code, 200) + self.assertIn("Test HTML About", response.content.decode()) + + def test_about_view_not_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = False + mock_obj.content = "Test Text About" + mock_get.return_value = mock_obj + response = self.client.get("/about/") + self.assertEqual(response.status_code, 200) + self.assertIn("Test Text About", response.content.decode()) + + def test_about_view(self): + url = "/about/" + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "about") + self.assertEqual(response.context["pageTitle"], "About") + self.assertIn("Set About Page Content In Admin", response.content.decode()) + + +class HelpViewTest(TestCase): + def test_help_view_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = True + mock_obj.html_content = "Test HTML Help" + mock_get.return_value = mock_obj + response = self.client.get("/help/") + self.assertEqual(response.status_code, 200) + self.assertIn("Test HTML Help", response.content.decode()) + + def test_help_view_not_html_content(self): + with patch("explore.views.PageContent.objects.get") as mock_get: + mock_obj = MagicMock() + mock_obj.is_html = False + mock_obj.content = "Test Text Help" + mock_get.return_value = mock_obj + response = self.client.get("/help/") + self.assertEqual(response.status_code, 200) + self.assertIn("Test Text Help", response.content.decode()) + + def test_help_view(self): + url = "/help/" + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "help") + self.assertEqual(response.context["pageTitle"], "Help") + + +class ExploreViewTest(TestCase): + def setUp(self): + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + + self.factory = RequestFactory() + self.credentials = b64encode(b"admin:admin").decode("ascii") + + def test_explore_view(self): + from TEKDB.models import Users + + user = Users.objects.get(username="admin") + self.client.force_login(user) + url = "/explore/" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "explore") + self.assertEqual(response.context["pageTitle"], "Search") + self.assertEqual(response.context["user"], user) + + +class GetByModelTypeTest(TestCase): + def setUp(self): + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + + self.factory = RequestFactory() + self.credentials = b64encode(b"admin:admin").decode("ascii") + + def test_get_by_model_type(self): + # Test that a valid model_type returns the expected records + from TEKDB.models import Users + + user = Users.objects.get(username="admin") + self.client.force_login(user) + url = "/explore/Places/" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["category"], "Places") + self.assertEqual(response.context["page"], "Results") + self.assertEqual(response.context["pageTitle"], "Results") + + class SearchTest(TestCase): # fixtures = ['TEKDB/fixtures/all_dummy_data.json',] @@ -52,16 +193,60 @@ def setUp(self): def test_get_by_model_id(self): # Test that a valid model_type and id returns the expected record - from TEKDB.models import Users, Places + from TEKDB.models import ( + Users, + Places, + Resources, + Media, + PlacesResourceEvents, + Citations, + ResourcesActivityEvents, + ) - place = Places.objects.first() user = Users.objects.get(username="admin") self.client.force_login(user) + + place = Places.objects.first() url = f"/explore/Places/{place.pk}/" - response = self.client.get(url) - model_name = response.context[0]["model_name"] + place_response = self.client.get(url) + model_name = place_response.context[0]["model_name"] self.assertEqual(model_name, "Place") - self.assertEqual(response.status_code, 200) + self.assertEqual(place_response.status_code, 200) + + resource = Resources.objects.first() + url = f"/explore/resources/{resource.pk}/" + resource_response = self.client.get(url) + model_name = resource_response.context[0]["model_name"] + self.assertEqual(model_name, "Resource") + self.assertEqual(resource_response.status_code, 200) + + rae = ResourcesActivityEvents.objects.first() + url = f"/explore/resourcesactivityevents/{rae.pk}/" + rae_response = self.client.get(url) + model_name = rae_response.context[0]["model_name"] + self.assertEqual(model_name, "Activity") + self.assertEqual(rae_response.status_code, 200) + + media = Media.objects.first() + url = f"/explore/media/{media.pk}/" + media_response = self.client.get(url) + model_name = media_response.context[0]["model_name"] + self.assertEqual(model_name, "Media") + self.assertEqual(media_response.status_code, 200) + + pre = PlacesResourceEvents.objects.first() + url = f"/explore/placesresourceevents/{pre.pk}/" + pre_response = self.client.get(url) + model_name = pre_response.context[0]["model_name"] + self.assertEqual(model_name, "Place-Resource Event") + self.assertEqual(pre_response.status_code, 200) + + citation = Citations.objects.first() + url = f"/explore/citations/{citation.pk}/" + citation_response = self.client.get(url) + model_name = citation_response.context[0]["model_name"] + self.assertEqual(model_name, "Bibliographic Source") + self.assertEqual(citation_response.status_code, 200) def test_get_by_model_id_invalid_model(self): # Test that an invalid model_type returns an error message @@ -108,3 +293,261 @@ def test_get_by_model_id_with_map(self): ] for key in map_keys: self.assertIn(key, data) + + +class DownloadMediaFileTest(TestCase): + def setUp(self): + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + + self.factory = RequestFactory() + self.credentials = b64encode(b"admin:admin").decode("ascii") + + def test_download_media_file(self): + from TEKDB.models import Users, Media + + user = Users.objects.get(username="admin") + self.client.force_login(user) + + media = Media.objects.first() + url = f"/explore/Media/{media.pk}/download" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response["Content-Disposition"], + f"attachment; filename={media.mediafile}", + ) + self.assertEqual(response["Content-Type"], "application/force-download") + + def test_download_media_file_invalid_id(self): + from TEKDB.models import Users + + user = Users.objects.get(username="admin") + self.client.force_login(user) + + url = "/explore/Media/999999/download" + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + def test_invalid_model_type(self): + from TEKDB.models import Users + + user = Users.objects.get(username="admin") + self.client.force_login(user) + + url = "/explore/InvalidModel/1/download" + response = self.client.get(url) + + self.assertEqual(response.status_code, 404) + + +class GetSortedKeysTest(TestCase): + def test_get_sorted_keys(self): + from explore.views import get_sorted_keys + + keys = ["name", "link", "map"] + sorted_keys = ["name", "map", "link"] + self.assertEqual(sorted_keys, get_sorted_keys(keys)) + + def test_get_sorted_keys_unique_key(self): + from explore.views import get_sorted_keys + + keys = ["name", "link", "foo", "map", "bar"] + # should append keys not in priority list to end + sorted_keys = ["name", "map", "link", "foo", "bar"] + self.assertEqual(sorted_keys, get_sorted_keys(keys)) + + +class ExportRecordCsvTest(TestCase): + def setUp(self): + from os.path import join + from django.conf import settings + from TEKDB.tests.test_views import import_fixture_file + + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + from TEKDB.models import Users + + self.user = Users.objects.get(username="admin") + self.client.force_login(self.user) + + def test_export_record_csv(self): + from TEKDB.models import Places + + place = Places.objects.get(pk=31) + url = f"/export/Places/{place.pk}/csv/" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "text/csv") + + self.assertIn( + f'attachment; filename="Places_{place.pk}_{str(place)}.csv"', + response["Content-Disposition"], + ) + self.assertIn("id", response.content.decode()) + self.assertIn("name", response.content.decode()) + + +class ExportRecordXlsTest(TestCase): + def setUp(self): + from os.path import join + from django.conf import settings + from TEKDB.tests.test_views import import_fixture_file + + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + from TEKDB.models import Users + + self.user = Users.objects.get(username="admin") + self.client.force_login(self.user) + + def test_export_record_xls(self): + from TEKDB.models import Places + + place = Places.objects.get(pk=31) + url = f"/export/Places/{place.pk}/xls/" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response["Content-Type"], + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + + self.assertIn( + f'attachment; filename="Places_{place.pk}_{str(place)}.xlsx"', + response["Content-Disposition"], + ) + self.assertEqual( + response.content[:2], b"PK" + ) # XLSX files start with 'PK' (zip signature) + + +class SearchViewTest(TestCase): + def setUp(self): + from os.path import join + from django.conf import settings + from TEKDB.tests.test_views import import_fixture_file + from TEKDB.models import Users + + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + self.user = Users.objects.get(username="admin") + self.client.force_login(self.user) + + def test_search_view_get_request(self): + url = "/search/?query=test&category=places" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "Results") + self.assertEqual(response.context["pageTitle"], "Results") + self.assertEqual(response.context["query"], "test") + self.assertEqual(response.context["keyword"], "test") + self.assertEqual(response.context["categories"], '["places"]') + self.assertIn( + '', + response.context["category_checkboxes"], + ) + self.assertNotIn( + '', + response.context["category_checkboxes"], + ) + + def test_search_view_misspelled_category(self): + url = "/search/?query=test&category=placess,resourcess" + response = self.client.get(url) + # should resort to all + self.assertEqual(response.status_code, 200) + self.assertIn("test", response.context["query"]) + self.assertEqual( + response.context["categories"], + '["places", "resources", "activities", "sources", "media"]', + ) + self.assertNotIn( + '', + response.context["category_checkboxes"], + ) + + def test_search_view_no_category(self): + url = "/search/?query=test" + response = self.client.get(url) + # should resort to all + self.assertEqual(response.status_code, 200) + self.assertIn("test", response.context["query"]) + self.assertEqual( + response.context["categories"], + '["places", "resources", "activities", "sources", "media"]', + ) + self.assertNotIn( + '', + response.context["category_checkboxes"], + ) + + def test_search_view_post_request(self): + url = "/search/" + data = { + "query": "test", + "activities": "on", + "citations": "on", + "media": "on", + } + response = self.client.post(url, data) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["page"], "Results") + self.assertEqual(response.context["pageTitle"], "Results") + self.assertIn("test", response.context["query"]) + self.assertEqual( + response.context["categories"], '["activities", "sources", "media"]' + ) + + +class DownloadViewTest(TestCase): + def setUp(self): + from os.path import join + from django.conf import settings + from TEKDB.tests.test_views import import_fixture_file + + import_fixture_file( + join(settings.BASE_DIR, "TEKDB", "fixtures", "all_dummy_data.json") + ) + from TEKDB.models import Users + + self.user = Users.objects.get(username="admin") + self.client.force_login(self.user) + + def test_download_csv(self): + url = "/export?query=test&places=true&format=csv" + response = self.client.get(url, follow=True) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response["Content-Type"], "text/csv") + self.assertIn( + 'attachment; filename="TEK_RESULTS.csv"', response["Content-Disposition"] + ) + self.assertIn("id", response.content.decode()) + self.assertIn("name", response.content.decode()) + + def test_download_xlsx(self): + url = "/export?query=test&places=true&format=xlsx" + response = self.client.get(url) + + self.assertEqual(response.status_code, 200) + self.assertEqual( + response["Content-Type"], + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) + self.assertIn( + "attachment; filename=TEK_RESULTS.xlsx", response["Content-Disposition"] + ) + self.assertEqual( + response.content[:2], b"PK" + ) # XLSX files start with 'PK' (zip signature) diff --git a/TEKDB/explore/views.py b/TEKDB/explore/views.py index 534a428a..24c547f0 100644 --- a/TEKDB/explore/views.py +++ b/TEKDB/explore/views.py @@ -244,11 +244,11 @@ def download_media_file(request, model_type, id): else: obj = None - media = obj.media() - if media: + if obj is not None: import os from TEKDB.settings import MEDIA_ROOT + media = obj.media() file_path = os.path.join(MEDIA_ROOT, media["file"]) if os.path.exists(file_path): with open(file_path, "rb") as fh: @@ -260,9 +260,9 @@ def download_media_file(request, model_type, id): ) return response else: - return Http404 + raise Http404 else: - return Http404 + raise Http404 def get_sorted_keys(keys):