Skip to content

Commit

Permalink
✨(api) allow retrieving courses by their code as well as their pk
Browse files Browse the repository at this point in the history
Richie knows the code but not the ID of courses.
  • Loading branch information
sampaccoud committed Jul 7, 2023
1 parent 71a58e4 commit 06b0584
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 10 deletions.
36 changes: 27 additions & 9 deletions src/backend/joanie/core/api/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""
Client API endpoints
"""
import uuid

from django.db import IntegrityError, transaction
from django.db.models import Count, OuterRef, Q, Subquery
from django.http import HttpResponse
Expand All @@ -12,6 +14,7 @@
from rest_framework.decorators import action
from rest_framework.exceptions import NotFound
from rest_framework.exceptions import ValidationError as DRFValidationError
from rest_framework.generics import get_object_or_404
from rest_framework.response import Response

from joanie.core import enums, filters, models, permissions, serializers
Expand Down Expand Up @@ -629,24 +632,24 @@ class CourseAccessViewSet(
"""
API ViewSet for all interactions with course accesses.
GET /api/courses/<course_id>/accesses/:<course_access_id>
GET /api/courses/<course_id|course_code>/accesses/:<course_access_id>
Return list of all course accesses related to the logged-in user or one
course access if an id is provided.
POST /api/courses/<course_id>/accesses/ with expected data:
POST /api/courses/<course_id|course_code>/accesses/ with expected data:
- user: str
- role: str [owner|admin|member]
Return newly created course access
PUT /api/courses/<course_id>/accesses/<course_access_id>/ with expected data:
PUT /api/courses/<course_id|course_code>/accesses/<course_access_id>/ with expected data:
- role: str [owner|admin|member]
Return updated course access
PATCH /api/courses/<course_id>/accesses/<course_access_id>/ with expected data:
PATCH /api/courses/<course_id|course_code>/accesses/<course_access_id>/ with expected data:
- role: str [owner|admin|member]
Return partially updated course access
DELETE /api/courses/<course_id>/accesses/<course_access_id>/
DELETE /api/courses/<course_id|course_code>/accesses/<course_access_id>/
Delete targeted course access
"""

Expand Down Expand Up @@ -702,16 +705,16 @@ class CourseViewSet(
GET /api/courses/
Return list of all courses related to the logged-in user.
GET /api/courses/:<course_id>
GET /api/courses/:<course_id|course_code>
Return one course if an id is provided.
GET /api/courses/:<course_id>/wish
GET /api/courses/:<course_id|course_code>/wish
Return wish status on this course for the authenticated user
POST /api/courses/:<course_id>/wish
POST /api/courses/:<course_id|course_code>/wish
Confirm a wish on this course for the authenticated user
DELETE /api/courses/:<course_id>/wish
DELETE /api/courses/:<course_id|course_code>/wish
Delete any existing wish on this course for the authenticated user
"""

Expand All @@ -723,6 +726,21 @@ class CourseViewSet(
serializer_class = serializers.CourseSerializer
ordering = ["-created_on"]

def get_object(self):
"""Allow getting a course by its pk or by its code."""
queryset = self.filter_queryset(self.get_queryset())
try:
uuid.UUID(self.kwargs["pk"])
except ValueError:
filter_field = "code__iexact"
else:
filter_field = "pk"

obj = get_object_or_404(queryset, **{filter_field: self.kwargs["pk"]})
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj

def get_queryset(self):
"""
Custom queryset to get user courses
Expand Down
2 changes: 1 addition & 1 deletion src/backend/joanie/core/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class CourseFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Course

code = factory.Sequence(lambda n: n)
code = factory.Sequence(lambda n: f"{n:06d}")
title = factory.Sequence(lambda n: f"Course {n}")
cover = factory.django.ImageField(
filename="cover.png", format="png", width=1, height=1
Expand Down
31 changes: 31 additions & 0 deletions src/backend/joanie/tests/core/test_api_course.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,37 @@ def test_api_course_get_authenticated_with_access(self, _):
},
)

@mock.patch.object(
fields.ThumbnailDetailField,
"to_representation",
return_value="_this_field_is_mocked",
)
def test_api_course_get_authenticated_by_code(self, _):
"""
Authenticated users should be able to get a course through its code
if they have access to it.
"""
user = factories.UserFactory()
token = self.get_user_token(user.username)

course = factories.CourseFactory(code="MYCODE-0088")
factories.UserCourseAccessFactory(user=user, course=course)
factories.CourseProductRelationFactory(
course=course,
product=factories.ProductFactory(),
organizations=[factories.OrganizationFactory()],
)

with self.assertNumQueries(8):
response = self.client.get(
"/api/v1.0/courses/mycode-0088/",
HTTP_AUTHORIZATION=f"Bearer {token}",
)

self.assertEqual(response.status_code, 200)
content = response.json()
self.assertEqual(content["id"], str(course.id))

def test_api_course_create_anonymous(self):
"""
Anonymous users should not be able to create a course.
Expand Down

0 comments on commit 06b0584

Please sign in to comment.