diff --git a/web/language/migrations/0095_placename_audio.py b/web/language/migrations/0095_placename_audio.py new file mode 100644 index 0000000..3b7cabe --- /dev/null +++ b/web/language/migrations/0095_placename_audio.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.4 on 2019-11-13 20:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('language', '0094_community_nation_id'), + ] + + operations = [ + migrations.AddField( + model_name='placename', + name='audio', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='language.Recording'), + ), + ] diff --git a/web/language/models.py b/web/language/models.py index c1d31cf..2344fea 100755 --- a/web/language/models.py +++ b/web/language/models.py @@ -236,10 +236,15 @@ class Meta: class PlaceName(CulturalModel): geom = models.GeometryField(null=True, default=None) - + + # 3 deprecated. Use Recording. audio_file = models.FileField(null=True, blank=True) audio_name = models.CharField(max_length=64, null=True, blank=True) audio_description = models.TextField(null=True, blank=True, default="") + # 3 deprecated. Use Recording. + audio = models.ForeignKey( + Recording, on_delete=models.SET_NULL, null=True, blank=True + ) kind = models.CharField(max_length=15, default="") diff --git a/web/language/serializers.py b/web/language/serializers.py index 4ae458a..b8b12f6 100644 --- a/web/language/serializers.py +++ b/web/language/serializers.py @@ -56,11 +56,7 @@ class Meta: class RecordingSerializer(serializers.ModelSerializer): class Meta: model = Recording - fields = ("audio_file", - "speaker", - "recorder", - "created", - "date_recorded") + fields = ("id", "audio_file", "speaker", "recorder", "created", "date_recorded") class LNASerializer(serializers.ModelSerializer): language = serializers.SlugRelatedField(read_only=True, slug_field="name") @@ -145,8 +141,7 @@ class LanguageDetailSerializer(serializers.ModelSerializer): required=False, ) language_audio = RecordingSerializer(read_only=True) - - + greeting_audio = RecordingSerializer(read_only=True) def to_representation(self, value): rep = super().to_representation(value) @@ -189,7 +184,15 @@ class Meta: "family_id", "champion_ids", "languagelink_ids", - "places" + "places", + "total_schools", + "avg_hrs_wk_languages_in_school", + "ece_programs", + "avg_hrs_wk_languages_in_ece", + "language_nests", + "avg_hrs_wk_languages_in_language_nests", + "community_adult_language_classes", + "fv_guid", ) @@ -248,6 +251,10 @@ class CommunityDetailSerializer(serializers.ModelSerializer): languages = LanguageSerializer(read_only=True, many=True) places = PlaceNameLightSerializer(many=True, read_only=True) medias = MediaLightSerializer(many=True, read_only=True) + audio = serializers.PrimaryKeyRelatedField( + queryset=Recording.objects.all(), allow_null=True, required=False + ) + audio_obj = RecordingSerializer(source="audio", read_only=True) # Atomic Writable APIs language_ids = serializers.PrimaryKeyRelatedField( @@ -282,11 +289,31 @@ def to_representation(self, value): if lid in by_lang: if lnadata.lna.year > by_lang[lid]["lna"]["year"]: by_lang[lid] = LNADataSerializer(lnadata).data + lna_name = by_lang[lid]["lna"]["name"] + print(lna_name) + by_lang[lid]['lna']['url'] = self.build_lna_external_url(lna_name) + # print(by_lang[lid]['lna']) else: by_lang[lid] = LNADataSerializer(lnadata).data + lna_name = by_lang[lid]["lna"]["name"] + print(lna_name) + by_lang[lid]['lna']['url'] = "denis" + # print(by_lang[lid]['lna']) + rep["lna_by_language"] = by_lang return rep + def build_lna_external_url(self, lna_name): + permalink = "https://maps.fpcc.ca/lna/" + try: + lna_external_id = lna_name.split('-')[0].strip().replace("LNA","") + print(lna_external_id) + lna_link = permalink + lna_external_id + print(lna_link) + except: + lna_link = permalink + return lna_link + class Meta: model = Community fields = ( @@ -294,6 +321,8 @@ class Meta: "name", "languages", "regions", + "audio", + "audio_obj", "audio_file", "champion_set", "communitylink_set", @@ -307,12 +336,17 @@ class Meta: "phone", "alt_phone", "fax", - "audio_file", "language_ids", "champion_ids", "communitylink_ids", "places", "medias", + "notes", + "nation_id", + "population_on_reserve", + "population_off_reserve", + "fv_guid", + "fv_archive_link", ) @@ -403,6 +437,10 @@ class PlaceNameDetailSerializer(serializers.ModelSerializer): ) category_obj = PlaceNameCategorySerializer(source="category", read_only=True) favourites = FavouritePlaceNameSerializer(many=True, read_only=True) + audio = serializers.PrimaryKeyRelatedField( + queryset=Recording.objects.all(), allow_null=True, required=False + ) + audio_obj = RecordingSerializer(source="audio", read_only=True) class Meta: model = PlaceName @@ -411,6 +449,8 @@ class Meta: "id", "geom", "other_names", + "audio", + "audio_obj", "audio_file", "audio_name", "audio_description", diff --git a/web/language/tests/tests_community.py b/web/language/tests/tests_community.py index 84de18d..6d96fb3 100644 --- a/web/language/tests/tests_community.py +++ b/web/language/tests/tests_community.py @@ -1,6 +1,7 @@ from django.test import TestCase from rest_framework.test import APITestCase from rest_framework import status +from django.utils import timezone from users.models import User, Administrator from django.contrib.gis.geos import GEOSGeometry, Point @@ -9,6 +10,8 @@ Language, Community, CommunityMember, + Champion, + Recording, ) @@ -29,6 +32,8 @@ def setUp(self): class CommunityAPITests(BaseTestCase): def setUp(self): super().setUp() + self.now = timezone.now() + self.community1 = Community.objects.create(name="Test Community 01") self.community2 = Community.objects.create(name="Test Community 02") self.language = Language.objects.create(name="Test Language") @@ -41,6 +46,14 @@ def setUp(self): 54.74840576223716 ] }""" + self.point = GEOSGeometry(self.FAKE_GEOM) + + self.recording = Recording.objects.create( + speaker = "Test recording", + recorder = "Test recording", + created = self.now, + date_recorded = self.now, + ) ###### ONE TEST TESTS ONLY ONE SCENARIO ###### @@ -49,9 +62,9 @@ def test_community_detail(self): Ensure we can retrieve a newly created community object. """ - point = GEOSGeometry(self.FAKE_GEOM) test_community = Community(name="Test community 001") - test_community.point = point + test_community.point = self.point + test_community.audio = self.recording test_community.save() response = self.client.get( @@ -60,6 +73,9 @@ def test_community_detail(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["id"], test_community.id) self.assertEqual(response.data["name"], "Test community 001") + self.assertEqual(response.data["audio"], self.recording.id) + self.assertEqual(response.data["audio_obj"]["speaker"], self.recording.speaker) + self.assertEqual(response.data["audio_obj"]["recorder"], self.recording.recorder) def test_community_list_route_exists(self): """ @@ -68,6 +84,41 @@ def test_community_list_route_exists(self): response = self.client.get("/api/community/", format="json") self.assertEqual(response.status_code, status.HTTP_200_OK) + def test_community_add_audio(self): + """ + Ensure we can add a community audio to a community object. + """ + # Must be logged in + self.assertTrue(self.client.login(username="testuser001", password="password")) + + # Check we're logged in + response = self.client.get("/api/user/auth/") + self.assertEqual(response.json()["is_authenticated"], True) + + test_community = Community(name="Test community audio") + test_community.point = self.point + test_community.save() + + response = self.client.patch( + "/api/community/{}/add_audio/".format(test_community.id), + { + "recording_id": self.recording.id + }, + format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.get( + "/api/community/{}/".format(test_community.id), format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["id"], test_community.id) + self.assertEqual(response.data["name"], "Test community audio") + self.assertEqual(response.data["audio_obj"]["id"], self.recording.id) + self.assertEqual(response.data["audio_obj"]["speaker"], self.recording.speaker) + def test_create_community_member_post(self): """ Ensure we can retrieve a newly created community member object. diff --git a/web/language/tests/tests_language.py b/web/language/tests/tests_language.py index 86b0613..297f960 100644 --- a/web/language/tests/tests_language.py +++ b/web/language/tests/tests_language.py @@ -1,6 +1,7 @@ from django.test import TestCase from rest_framework.test import APITestCase from rest_framework import status +from django.utils import timezone from users.models import User, Administrator from django.contrib.gis.geos import GEOSGeometry @@ -9,6 +10,7 @@ from language.models import ( Language, + Recording, ) @@ -52,6 +54,23 @@ def setUp(self): class LanguageAPITests(BaseTestCase): + def setUp(self): + super().setUp() + self.now = timezone.now() + + self.recording1 = Recording.objects.create( + speaker = "Test recording", + recorder = "Test recording", + created = self.now, + date_recorded = self.now, + ) + + self.recording2 = Recording.objects.create( + speaker = "Test recording", + recorder = "Test recording", + created = self.now, + date_recorded = self.now, + ) ###### ONE TEST TESTS ONLY ONE SCENARIO ###### @@ -70,6 +89,8 @@ def test_language_detail(self): test_language = Language(name="Test language 001") test_language.geom = poly + test_language.language_audio = self.recording1 + test_language.greeting_audio = self.recording2 test_language.save() response = self.client.get( @@ -82,6 +103,82 @@ def test_language_detail(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["id"], test_language.id) self.assertEqual(response.data["name"], "Test language 001") + self.assertEqual(response.data["language_audio"]["speaker"], self.recording1.speaker) + self.assertEqual(response.data["language_audio"]["recorder"], self.recording1.recorder) + + def test_language_add_language_audio(self): + """ + Ensure we can add a language audio to a language object. + """ + # Must be logged in + self.assertTrue(self.client.login(username="testuser001", password="password")) + + # Check we're logged in + response = self.client.get("/api/user/auth/") + self.assertEqual(response.json()["is_authenticated"], True) + + poly = GEOSGeometry(self.FAKE_GEOM) + + test_language = Language(name="Test language audio") + test_language.geom = poly + test_language.save() + + response = self.client.patch( + "/api/language/{}/add_language_audio/".format(test_language.id), + { + "recording_id": self.recording1.id + }, + format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.get( + "/api/language/{}/".format(test_language.id), format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["id"], test_language.id) + self.assertEqual(response.data["name"], "Test language audio") + self.assertEqual(response.data["language_audio"]["id"], self.recording1.id) + self.assertEqual(response.data["language_audio"]["speaker"], self.recording1.speaker) + + def test_language_add_greeting_audio(self): + """ + Ensure we can add a greeting audio to a language object. + """ + # Must be logged in + self.assertTrue(self.client.login(username="testuser001", password="password")) + + # Check we're logged in + response = self.client.get("/api/user/auth/") + self.assertEqual(response.json()["is_authenticated"], True) + + poly = GEOSGeometry(self.FAKE_GEOM) + + test_language = Language(name="Test greeting audio") + test_language.geom = poly + test_language.save() + + response = self.client.patch( + "/api/language/{}/add_greeting_audio/".format(test_language.id), + { + "recording_id": self.recording2.id + }, + format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + response = self.client.get( + "/api/language/{}/".format(test_language.id), format="json" + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["id"], test_language.id) + self.assertEqual(response.data["name"], "Test greeting audio") + self.assertEqual(response.data["greeting_audio"]["id"], self.recording2.id) + self.assertEqual(response.data["greeting_audio"]["speaker"], self.recording2.speaker) def test_language_list_route_exists(self): """ diff --git a/web/language/tests/tests_placename.py b/web/language/tests/tests_placename.py index 2ba1cba..dc05bce 100644 --- a/web/language/tests/tests_placename.py +++ b/web/language/tests/tests_placename.py @@ -1,6 +1,7 @@ from django.test import TestCase from rest_framework.test import APITestCase from rest_framework import status +from django.utils import timezone from users.models import User, Administrator @@ -14,6 +15,7 @@ Media, Favourite, Notification, + Recording, ) @@ -34,6 +36,8 @@ def setUp(self): class PlaceNameAPITests(BaseTestCase): def setUp(self): super().setUp() + self.now = timezone.now() + self.community = Community.objects.create(name="Test Community") self.community2 = Community.objects.create(name="Test Community 02") self.language1 = Language.objects.create(name="Test Language 01") @@ -51,6 +55,13 @@ def setUp(self): self.user2.set_password("password") self.user2.save() + self.recording = Recording.objects.create( + speaker = "Test recording", + recorder = "Test recording", + created = self.now, + date_recorded = self.now, + ) + ###### ONE TEST TESTS ONLY ONE SCENARIO ###### def test_placename_detail(self): @@ -72,8 +83,12 @@ def test_placename_detail_returned_fields(self): """ test_placename = PlaceName.objects.create( name = "Test placename 001", + audio = self.recording, audio_name = "string", audio_description = "string", + community = self.community, + language = self.language1, + category = self.category, ) response = self.client.get( "/api/placename/{}/".format(test_placename.id), format="json" @@ -82,6 +97,13 @@ def test_placename_detail_returned_fields(self): self.assertEqual(response.data["name"], "Test placename 001") self.assertEqual(response.data["audio_name"], "string") self.assertEqual(response.data["audio_description"], "string") + self.assertEqual(response.data["audio"], self.recording.id) + self.assertEqual(response.data["community"], self.community.id) + self.assertEqual(response.data["language"], self.language1.id) + self.assertEqual(response.data["category"], self.category.id) + # self.assertEqual(response.data["audio_obj"]["id"], self.recording.id) + self.assertEqual(response.data["audio_obj"]["speaker"], self.recording.speaker) + self.assertEqual(response.data["audio_obj"]["recorder"], self.recording.recorder) def test_placename_list_not_logged_in(self): """ @@ -473,6 +495,7 @@ def test_placename_post(self): "category": self.category.id, "community": self.community.id, "language": self.language1.id, + "audio": self.recording.id, }, format="json", ) @@ -483,17 +506,23 @@ def test_placename_post(self): self.assertEqual(place.name, "test place") self.assertEqual(place.community_id, self.community.id) self.assertEqual(place.language_id, self.language1.id) + self.assertEqual(place.category_id, self.category.id) + self.assertEqual(place.audio_id, self.recording.id) # now update it. response = self.client.patch( "/api/placename/{}/".format(created_id), - {"other_names": "updated other names", "community": None}, + {"other_names": "updated other names"}, format="json", ) self.assertEqual(response.status_code, status.HTTP_200_OK) place = PlaceName.objects.get(pk=created_id) self.assertEqual(place.other_names, "updated other names") + self.assertEqual(place.community_id, self.community.id) + self.assertEqual(place.language_id, self.language1.id) + self.assertEqual(place.category_id, self.category.id) + self.assertEqual(place.audio_id, self.recording.id) def test_placename_verify(self): # Must be logged in to submit a place. diff --git a/web/language/tests/tests_recording.py b/web/language/tests/tests_recording.py new file mode 100644 index 0000000..7457fce --- /dev/null +++ b/web/language/tests/tests_recording.py @@ -0,0 +1,124 @@ +from django.test import TestCase +from rest_framework.test import APITestCase +from rest_framework import status +from django.utils import timezone + +from users.models import User, Administrator +from language.models import ( + Recording, +) + + +class BaseTestCase(APITestCase): + def setUp(self): + self.user = User.objects.create( + username="testuser001", + first_name="Test", + last_name="user 001", + email="test@countable.ca", + is_staff=True, + is_superuser=True, + ) + self.user.set_password("password") + self.user.save() + + +class RecordingAPITests(BaseTestCase): + def setUp(self): + super().setUp() + self.now = timezone.now() + + ###### ONE TEST TESTS ONLY ONE SCENARIO ###### + + def test_recording_detail(self): + """ + Ensure we can retrieve a newly created recording object. + """ + test_recording = Recording.objects.create( + speaker = "Test speaker", + recorder = "Test recorder", + created = self.now, + date_recorded = self.now, + ) + + response = self.client.get( + "/api/recording/{}/".format(test_recording.id), format="json" + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data["speaker"], test_recording.speaker) + self.assertEqual(response.data["recorder"], test_recording.recorder) + + def test_recording_post(self): + """ + Ensure recording API POST method API works + """ + # Must be logged in to verify a media. + self.assertTrue(self.client.login(username="testuser001", password="password")) + + # Check we're logged in + response = self.client.get("/api/user/auth/") + self.assertEqual(response.json()["is_authenticated"], True) + + response = self.client.post( + "/api/recording/", + { + "speaker": "Test speaker", + "recorder": "Test recorder", + "date_recorded": "2019-01-01" + }, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + created_id = response.json()["id"] + + recording = Recording.objects.get(pk=created_id) + self.assertEqual(recording.speaker, "Test speaker") + self.assertEqual(recording.recorder, "Test recorder") + + def test_recording_patch(self): + """ + Ensure recording API PATCH method API works + """ + # Must be logged in to verify a media. + self.assertTrue(self.client.login(username="testuser001", password="password")) + + # Check we're logged in + response = self.client.get("/api/user/auth/") + self.assertEqual(response.json()["is_authenticated"], True) + + test_recording = Recording.objects.create( + speaker = "Test speaker", + recorder = "Test recorder", + created = self.now, + date_recorded = self.now, + ) + + response = self.client.patch( + "/api/recording/{}/".format(test_recording.id), + { + "speaker": "Test speaker2", + "recorder": "Test recorder2", + }, + format="json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + recording = Recording.objects.get(pk=test_recording.id) + self.assertEqual(recording.speaker, "Test speaker2") + self.assertEqual(recording.recorder, "Test recorder2") + + def test_recording_delete(self): + """ + Ensure recording API DELETE method API works + """ + test_recording = Recording.objects.create( + speaker = "Test speaker", + recorder = "Test recorder", + created = self.now, + date_recorded = self.now, + ) + + response = self.client.delete( + "/api/recording/{}/".format(test_recording.id), format="json" + ) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) diff --git a/web/language/urls.py b/web/language/urls.py index e460ae5..ff19009 100644 --- a/web/language/urls.py +++ b/web/language/urls.py @@ -4,7 +4,6 @@ from rest_framework import routers from .views import PlaceNameGeoList, LanguageGeoList, CommunityGeoList - from .views import ( LanguageViewSet, CommunityViewSet, @@ -15,6 +14,7 @@ FavouriteViewSet, NotificationViewSet, CommunityLanguageStatsViewSet, + RecordingViewSet, ) router = routers.DefaultRouter() @@ -32,6 +32,7 @@ router.register(r"media", MediaViewSet, basename="media") router.register(r"favourite", FavouriteViewSet, basename="favourite") router.register(r"notification", NotificationViewSet, basename="notification") +router.register(r"recording", RecordingViewSet, basename="recording") urlpatterns = [ url("language-geo/$", LanguageGeoList.as_view(), name="language-geo"), diff --git a/web/language/views/community.py b/web/language/views/community.py index 08e0fb9..9cd84ba 100644 --- a/web/language/views/community.py +++ b/web/language/views/community.py @@ -6,15 +6,11 @@ from users.models import User, Administrator from language.models import ( Language, - PlaceName, Community, CommunityMember, Champion, - PlaceNameCategory, - Media, - Favourite, - Notification, CommunityLanguageStats, + Recording, ) from django.views.decorators.cache import never_cache @@ -55,6 +51,21 @@ def list(self, request): serializer = self.serializer_class(queryset, many=True) return Response(serializer.data) + @action(detail=True, methods=["patch"]) + def add_audio(self, request, pk): + if 'recording_id' not in request.data.keys(): + return Response({"message": "No Recording was sent in the request"}) + if not pk: + return Response({"message": "No Community was sent in the request"}) + recording_id = int(request.data["recording_id"]) + community_id = int(pk) + community = Community.objects.get(pk=community_id) + recording = Recording.objects.get(pk=recording_id) + community.audio = recording + community.save() + return Response({"message": "Audio associated"}, + status=status.HTTP_200_OK) + @action(detail=True, methods=["post"]) def create_membership(self, request, pk): if 'user_id' not in request.data.keys(): diff --git a/web/language/views/language.py b/web/language/views/language.py index 875b214..bfc33d0 100644 --- a/web/language/views/language.py +++ b/web/language/views/language.py @@ -6,15 +6,8 @@ from users.models import User, Administrator from language.models import ( Language, - PlaceName, Community, - CommunityMember, - Champion, - PlaceNameCategory, - Media, - Favourite, - Notification, - CommunityLanguageStats, + Recording, ) from django.views.decorators.cache import never_cache @@ -47,6 +40,36 @@ class LanguageViewSet(BaseModelViewSet): .order_by("family", "name") ) + @action(detail=True, methods=["patch"]) + def add_language_audio(self, request, pk): + if 'recording_id' not in request.data.keys(): + return Response({"message": "No Recording was sent in the request"}) + if not pk: + return Response({"message": "No Language was sent in the request"}) + recording_id = int(request.data["recording_id"]) + language_id = int(pk) + language = Language.objects.get(pk=language_id) + recording = Recording.objects.get(pk=recording_id) + language.language_audio = recording + language.save() + return Response({"message": "Language audio associated"}, + status=status.HTTP_200_OK) + + @action(detail=True, methods=["patch"]) + def add_greeting_audio(self, request, pk): + if 'recording_id' not in request.data.keys(): + return Response({"message": "No Recording was sent in the request"}) + if not pk: + return Response({"message": "No Language was sent in the request"}) + recording_id = int(request.data["recording_id"]) + language_id = int(pk) + language = Language.objects.get(pk=language_id) + recording = Recording.objects.get(pk=recording_id) + language.greeting_audio = recording + language.save() + return Response({"message": "Greeting audio associated"}, + status=status.HTTP_200_OK) + def create_membership(self, request): user_id = int(request.data["user"]["id"]) language_id = int(request.data["language"]["id"]) diff --git a/web/language/views/others.py b/web/language/views/others.py index 1fdfe15..0549672 100644 --- a/web/language/views/others.py +++ b/web/language/views/others.py @@ -15,6 +15,7 @@ Favourite, Notification, CommunityLanguageStats, + Recording, ) from django.views.decorators.cache import never_cache @@ -28,6 +29,7 @@ from language.serializers import ( FavouriteSerializer, NotificationSerializer, + RecordingSerializer, ) from django.utils.decorators import method_decorator @@ -35,6 +37,11 @@ from web.permissions import IsAdminOrReadOnly +class RecordingViewSet(BaseModelViewSet): + serializer_class = RecordingSerializer + queryset = Recording.objects.all() + + class NotificationViewSet(BaseModelViewSet): serializer_class = NotificationSerializer queryset = Notification.objects.all() diff --git a/web/requirements.txt b/web/requirements.txt index 09cfa32..a625681 100644 --- a/web/requirements.txt +++ b/web/requirements.txt @@ -1,4 +1,4 @@ -Django==2.2.4 +Django==2.2.8 django_rest_swagger==2.2.0 PyMySQL==0.9.3 django_markdownx==2.0.28