From 9bdc358e5915db3d063e54c73880d7d8b4198649 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Fri, 20 Feb 2026 16:37:48 -0500 Subject: [PATCH] commenced DRF efforts w. package installation, creation of apps.api, w. UTs & ITs to ensure core efficacy; core.settings & .urls changed to accomodate --- requirements.dev.txt | 1 + requirements.txt | 1 + src/apps/api/__init__.py | 0 src/apps/api/serializers.py | 17 ++++++ src/apps/api/tests/__init__.py | 0 src/apps/api/tests/integrated/__init__.py | 0 src/apps/api/tests/integrated/test_views.py | 62 +++++++++++++++++++++ src/apps/api/tests/unit/__init__.py | 0 src/apps/api/tests/unit/test_serializers.py | 20 +++++++ src/apps/api/urls.py | 11 ++++ src/apps/api/views.py | 34 +++++++++++ src/core/settings.py | 2 + src/core/urls.py | 1 + 13 files changed, 149 insertions(+) create mode 100644 src/apps/api/__init__.py create mode 100644 src/apps/api/serializers.py create mode 100644 src/apps/api/tests/__init__.py create mode 100644 src/apps/api/tests/integrated/__init__.py create mode 100644 src/apps/api/tests/integrated/test_views.py create mode 100644 src/apps/api/tests/unit/__init__.py create mode 100644 src/apps/api/tests/unit/test_serializers.py create mode 100644 src/apps/api/urls.py create mode 100644 src/apps/api/views.py diff --git a/requirements.dev.txt b/requirements.dev.txt index 6f57054..2dbd799 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -9,6 +9,7 @@ dj-database-url Django==6.0 django-stubs==5.2.8 django-stubs-ext==5.2.8 +djangorestframework gunicorn==23.0.0 h11==0.16.0 idna==3.11 diff --git a/requirements.txt b/requirements.txt index 2575734..c66c561 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ Django==6.0 dj-database-url django-stubs==5.2.8 django-stubs-ext==5.2.8 +djangorestframework gunicorn==23.0.0 lxml==6.0.2 psycopg2-binary diff --git a/src/apps/api/__init__.py b/src/apps/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/api/serializers.py b/src/apps/api/serializers.py new file mode 100644 index 0000000..640a41f --- /dev/null +++ b/src/apps/api/serializers.py @@ -0,0 +1,17 @@ +from rest_framework import serializers +from apps.dashboard.models import Item, List + + +class ItemSerializer(serializers.ModelSerializer): + class Meta: + model = Item + fields = ["id", "text"] + +class ListSerializer(serializers.ModelSerializer): + name = serializers.ReadOnlyField() + url = serializers.CharField(source="get_absolute_url", read_only=True) + items = ItemSerializer(many=True, read_only=True, source="item_set") + + class Meta: + model = List + fields = ["id", "name", "url", "items"] diff --git a/src/apps/api/tests/__init__.py b/src/apps/api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/api/tests/integrated/__init__.py b/src/apps/api/tests/integrated/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/api/tests/integrated/test_views.py b/src/apps/api/tests/integrated/test_views.py new file mode 100644 index 0000000..96b1ee9 --- /dev/null +++ b/src/apps/api/tests/integrated/test_views.py @@ -0,0 +1,62 @@ +from django.test import TestCase +from rest_framework.test import APIClient + +from apps.dashboard.models import Item, List +from apps.lyric.models import User + +class BaseAPITest(TestCase): + # Helper fns + def setUp(self): + self.client = APIClient() + self.user = User.objects.create_user("test@example.com") + self.client.force_authenticate(user=self.user) + +class ListDetailAPITest(BaseAPITest): + def test_returns_list_with_items(self): + list_ = List.objects.create(owner=self.user) + Item.objects.create(text="item 1", list=list_) + Item.objects.create(text="item 2", list=list_) + + response = self.client.get(f"/api/lists/{list_.id}/") + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.data["id"], list_.id) + self.assertEqual(len(response.data["items"]), 2) + +class ListItemsAPITest(BaseAPITest): + + def test_can_add_item_to_list(self): + list_ = List.objects.create(owner=self.user) + + response = self.client.post( + f"/api/lists/{list_.id}/items/", + {"text": "a new item"}, + ) + + self.assertEqual(response.status_code, 201) + self.assertEqual(Item.objects.count(), 1) + self.assertEqual(Item.objects.first().text, "a new item") + +class ListsAPITest(BaseAPITest): + def test_get_returns_only_users_lists(self): + list1 = List.objects.create(owner=self.user) + Item.objects.create(text="item 1", list=list1) + other_user = User.objects.create_user("other@example.com") + List.objects.create(owner=other_user) + + response = self.client.get("/api/lists/") + + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data), 1) + self.assertEqual(response.data[0]["id"], list1.id) + + def test_post_creates_list_with_item(self): + response = self.client.post( + "/api/lists/", + {"text": "first item"}, + ) + + self.assertEqual(response.status_code, 201) + self.assertEqual(List.objects.count(), 1) + self.assertEqual(List.objects.first().owner, self.user) + self.assertEqual(Item.objects.first().text, "first item") \ No newline at end of file diff --git a/src/apps/api/tests/unit/__init__.py b/src/apps/api/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/apps/api/tests/unit/test_serializers.py b/src/apps/api/tests/unit/test_serializers.py new file mode 100644 index 0000000..273b3d3 --- /dev/null +++ b/src/apps/api/tests/unit/test_serializers.py @@ -0,0 +1,20 @@ +from django.test import SimpleTestCase +from apps.api.serializers import ItemSerializer, ListSerializer + + +class ItemSerializerTest(SimpleTestCase): + def test_fields(self): + serializer = ItemSerializer() + self.assertEqual( + set(serializer.fields.keys()), + {"id", "text"}, + ) + +class ListSerializerTest(SimpleTestCase): + def test_fields(self): + serializer = ListSerializer() + self.assertEqual( + set(serializer.fields.keys()), + {"id", "name", "url", "items"}, + ) + \ No newline at end of file diff --git a/src/apps/api/urls.py b/src/apps/api/urls.py new file mode 100644 index 0000000..4baac40 --- /dev/null +++ b/src/apps/api/urls.py @@ -0,0 +1,11 @@ +from django.urls import path + +from . import views + + +urlpatterns = [ + path('', views.ListsAPI.as_view(), name='api_lists'), + path('/', views.ListDetailAPI.as_view(), name='api_list_detail'), + path('/items/', views.ListItemsAPI.as_view(), name='api_list_items'), +] + diff --git a/src/apps/api/views.py b/src/apps/api/views.py new file mode 100644 index 0000000..eb083ff --- /dev/null +++ b/src/apps/api/views.py @@ -0,0 +1,34 @@ +from django.shortcuts import get_object_or_404 +from rest_framework.views import APIView +from rest_framework.response import Response + +from apps.dashboard.models import Item, List +from apps.api.serializers import ItemSerializer, ListSerializer + + +class ListDetailAPI(APIView): + def get(self, request, list_id): + list_ = get_object_or_404(List, id=list_id) + serializer = ListSerializer(list_) + return Response(serializer.data) + +class ListItemsAPI(APIView): + def post(self, request, list_id): + list_ = get_object_or_404(List, id=list_id) + serializer = ItemSerializer(data=request.data) + if serializer.is_valid(): + serializer.save(list=list_) + return Response(serializer.data, status=201) + return Response(serializer.errors, status=400) + +class ListsAPI(APIView): + def get(self, request): + lists = List.objects.filter(owner=request.user) + serializer = ListSerializer(lists, many=True) + return Response(serializer.data) + + def post(self, request): + list_ = List.objects.create(owner=request.user) + item = Item.objects.create(text=request.data.get("text", ""), list=list_) + serializer = ListSerializer(list_) + return Response(serializer.data, status=201) diff --git a/src/core/settings.py b/src/core/settings.py index bb946be..5b3522f 100644 --- a/src/core/settings.py +++ b/src/core/settings.py @@ -54,8 +54,10 @@ INSTALLED_APPS = [ # Custom apps 'apps.dashboard', 'apps.lyric', + 'apps.api', 'functional_tests', # Depend apps + 'rest_framework', ] # if 'DJANGO_DEBUG_FALSE' not in os.environ: diff --git a/src/core/urls.py b/src/core/urls.py index 8ba67c7..b7b537b 100644 --- a/src/core/urls.py +++ b/src/core/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path('', dash_views.home_page, name='home'), path('apps/dashboard/', include('apps.dashboard.urls')), path('apps/lyric/', include('apps.lyric.urls')), + path('api/lists/', include('apps.api.urls')), ] # Please remove the following urlpattern