We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Liked photos API endpoint
To add an overview of liked photos to ThaliApp, we need a new endpoint for liked photos.

I think it would be best to have `api/v2/photos/photos/` with `liked` boolean GET filter. It will need to do some filtering to prevent photos that are not published in an album from being returned.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/photos/api/v2/urls.py`
Content:
```
1 """Photos app API v2 urls."""
2 from django.urls import include, path
3 
4 from photos.api.v2.views import AlbumDetailView, AlbumListView, PhotoLikeView
5 
6 app_name = "photos"
7 
8 urlpatterns = [
9     path(
10         "photos/",
11         include(
12             [
13                 path("albums/", AlbumListView.as_view(), name="album-list"),
14                 path(
15                     "albums/<slug:slug>/",
16                     AlbumDetailView.as_view(),
17                     name="album-detail",
18                 ),
19                 path(
20                     "photos/<int:pk>/like/", PhotoLikeView.as_view(), name="photo-like"
21                 ),
22             ]
23         ),
24     ),
25 ]
26
```

Path: `website/photos/api/v2/views.py`
Content:
```
1 from django.db.models import Count, Prefetch, Q
2 
3 from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope
4 from rest_framework import filters, status
5 from rest_framework.exceptions import PermissionDenied
6 from rest_framework.generics import ListAPIView, RetrieveAPIView
7 from rest_framework.response import Response
8 from rest_framework.views import APIView
9 
10 from photos import services
11 from photos.api.v2.serializers.album import AlbumListSerializer, AlbumSerializer
12 from photos.models import Album, Like, Photo
13 
14 
15 class AlbumListView(ListAPIView):
16     """Returns an overview of all albums."""
17 
18     serializer_class = AlbumListSerializer
19     queryset = Album.objects.filter(hidden=False)
20     permission_classes = [
21         IsAuthenticatedOrTokenHasScope,
22     ]
23     required_scopes = ["photos:read"]
24     filter_backends = (filters.SearchFilter,)
25     search_fields = ("title", "date", "slug")
26 
27 
28 class AlbumDetailView(RetrieveAPIView):
29     """Returns the details of an album."""
30 
31     serializer_class = AlbumSerializer
32     permission_classes = [
33         IsAuthenticatedOrTokenHasScope,
34     ]
35     required_scopes = ["photos:read"]
36     lookup_field = "slug"
37 
38     def retrieve(self, request, *args, **kwargs):
39         if not services.is_album_accessible(request, self.get_object()):
40             raise PermissionDenied
41         return super().retrieve(request, *args, **kwargs)
42 
43     def get_queryset(self):
44         photos = Photo.objects.select_properties("num_likes")
45         if self.request.member:
46             photos = photos.annotate(
47                 member_likes=Count("likes", filter=Q(likes__member=self.request.member))
48             )
49         return Album.objects.filter(hidden=False).prefetch_related(
50             Prefetch("photo_set", queryset=photos)
51         )
52 
53 
54 class PhotoLikeView(APIView):
55     permission_classes = [IsAuthenticatedOrTokenHasScope]
56     required_scopes = ["photos:read"]
57 
58     def get(self, request, **kwargs):
59         photo_id = kwargs.get("pk")
60         try:
61             photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)
62         except Photo.DoesNotExist:
63             return Response(status=status.HTTP_404_NOT_FOUND)
64 
65         return Response(
66             {
67                 "liked": photo.likes.filter(member=request.member).exists(),
68                 "num_likes": photo.num_likes,
69             },
70             status=status.HTTP_200_OK,
71         )
72 
73     def post(self, request, **kwargs):
74         photo_id = kwargs.get("pk")
75         try:
76             photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)
77         except Photo.DoesNotExist:
78             return Response(status=status.HTTP_404_NOT_FOUND)
79 
80         _, created = Like.objects.get_or_create(photo=photo, member=request.member)
81 
82         if created:
83             return Response(
84                 {
85                     "liked": photo.likes.filter(member=request.member).exists(),
86                     "num_likes": photo.num_likes,
87                 },
88                 status=status.HTTP_201_CREATED,
89             )
90         return Response(
91             {
92                 "liked": photo.likes.filter(member=request.member).exists(),
93                 "num_likes": photo.num_likes,
94             },
95             status=status.HTTP_200_OK,
96         )
97 
98     def delete(self, request, **kwargs):
99         photo_id = kwargs.get("pk")
100         try:
101             photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)
102         except Photo.DoesNotExist:
103             return Response(status=status.HTTP_404_NOT_FOUND)
104 
105         try:
106             like = Like.objects.filter(photo__album__hidden=False).get(
107                 member=request.member, photo__pk=photo_id
108             )
109         except Like.DoesNotExist:
110             return Response(
111                 {
112                     "liked": False,
113                     "num_likes": photo.num_likes,
114                 },
115                 status=status.HTTP_204_NO_CONTENT,
116             )
117 
118         like.delete()
119 
120         return Response(
121             {
122                 "liked": False,
123                 "num_likes": photo.num_likes,
124             },
125             status=status.HTTP_202_ACCEPTED,
126         )
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/website/photos/api/v2/urls.py b/website/photos/api/v2/urls.py
--- a/website/photos/api/v2/urls.py
+++ b/website/photos/api/v2/urls.py
@@ -1,7 +1,12 @@
 """Photos app API v2 urls."""
 from django.urls import include, path
 
-from photos.api.v2.views import AlbumDetailView, AlbumListView, PhotoLikeView
+from photos.api.v2.views import (
+    AlbumDetailView,
+    AlbumListView,
+    LikedPhotosListView,
+    PhotoLikeView,
+)
 
 app_name = "photos"
 
@@ -19,6 +24,9 @@
                 path(
                     "photos/<int:pk>/like/", PhotoLikeView.as_view(), name="photo-like"
                 ),
+                path(
+                    "photos/liked/", LikedPhotosListView.as_view(), name="liked-photos"
+                ),
             ]
         ),
     ),
diff --git a/website/photos/api/v2/views.py b/website/photos/api/v2/views.py
--- a/website/photos/api/v2/views.py
+++ b/website/photos/api/v2/views.py
@@ -8,7 +8,11 @@
 from rest_framework.views import APIView
 
 from photos import services
-from photos.api.v2.serializers.album import AlbumListSerializer, AlbumSerializer
+from photos.api.v2.serializers.album import (
+    AlbumListSerializer,
+    AlbumSerializer,
+    PhotoListSerializer,
+)
 from photos.models import Album, Like, Photo
 
 
@@ -51,6 +55,35 @@
         )
 
 
+class LikedPhotosListView(ListAPIView):
+    """Returns the details the liked album."""
+
+    serializer_class = PhotoListSerializer
+    permission_classes = [
+        IsAuthenticatedOrTokenHasScope,
+    ]
+    required_scopes = ["photos:read"]
+
+    def get(self, request, *args, **kwargs):
+        if not self.request.member:
+            return Response(
+                data={
+                    "detail": "You need to be a member in order to view your liked photos."
+                },
+                status=status.HTTP_403_FORBIDDEN,
+            )
+        return self.list(request, *args, **kwargs)
+
+    def get_queryset(self):
+        return (
+            Photo.objects.filter(likes__member=self.request.member, album__hidden=False)
+            .annotate(
+                member_likes=Count("likes", filter=Q(likes__member=self.request.member))
+            )
+            .select_properties("num_likes")
+        )
+
+
 class PhotoLikeView(APIView):
     permission_classes = [IsAuthenticatedOrTokenHasScope]
     required_scopes = ["photos:read"]

{"golden_diff": "diff --git a/website/photos/api/v2/urls.py b/website/photos/api/v2/urls.py\n--- a/website/photos/api/v2/urls.py\n+++ b/website/photos/api/v2/urls.py\n@@ -1,7 +1,12 @@\n \"\"\"Photos app API v2 urls.\"\"\"\n from django.urls import include, path\n \n-from photos.api.v2.views import AlbumDetailView, AlbumListView, PhotoLikeView\n+from photos.api.v2.views import (\n+    AlbumDetailView,\n+    AlbumListView,\n+    LikedPhotosListView,\n+    PhotoLikeView,\n+)\n \n app_name = \"photos\"\n \n@@ -19,6 +24,9 @@\n                 path(\n                     \"photos/<int:pk>/like/\", PhotoLikeView.as_view(), name=\"photo-like\"\n                 ),\n+                path(\n+                    \"photos/liked/\", LikedPhotosListView.as_view(), name=\"liked-photos\"\n+                ),\n             ]\n         ),\n     ),\ndiff --git a/website/photos/api/v2/views.py b/website/photos/api/v2/views.py\n--- a/website/photos/api/v2/views.py\n+++ b/website/photos/api/v2/views.py\n@@ -8,7 +8,11 @@\n from rest_framework.views import APIView\n \n from photos import services\n-from photos.api.v2.serializers.album import AlbumListSerializer, AlbumSerializer\n+from photos.api.v2.serializers.album import (\n+    AlbumListSerializer,\n+    AlbumSerializer,\n+    PhotoListSerializer,\n+)\n from photos.models import Album, Like, Photo\n \n \n@@ -51,6 +55,35 @@\n         )\n \n \n+class LikedPhotosListView(ListAPIView):\n+    \"\"\"Returns the details the liked album.\"\"\"\n+\n+    serializer_class = PhotoListSerializer\n+    permission_classes = [\n+        IsAuthenticatedOrTokenHasScope,\n+    ]\n+    required_scopes = [\"photos:read\"]\n+\n+    def get(self, request, *args, **kwargs):\n+        if not self.request.member:\n+            return Response(\n+                data={\n+                    \"detail\": \"You need to be a member in order to view your liked photos.\"\n+                },\n+                status=status.HTTP_403_FORBIDDEN,\n+            )\n+        return self.list(request, *args, **kwargs)\n+\n+    def get_queryset(self):\n+        return (\n+            Photo.objects.filter(likes__member=self.request.member, album__hidden=False)\n+            .annotate(\n+                member_likes=Count(\"likes\", filter=Q(likes__member=self.request.member))\n+            )\n+            .select_properties(\"num_likes\")\n+        )\n+\n+\n class PhotoLikeView(APIView):\n     permission_classes = [IsAuthenticatedOrTokenHasScope]\n     required_scopes = [\"photos:read\"]\n", "issue": "Liked photos API endpoint\nTo add an overview of liked photos to ThaliApp, we need a new endpoint for liked photos.\r\n\r\nI think it would be best to have `api/v2/photos/photos/` with `liked` boolean GET filter. It will need to do some filtering to prevent photos that are not published in an album from being returned.\n", "before_files": [{"content": "\"\"\"Photos app API v2 urls.\"\"\"\nfrom django.urls import include, path\n\nfrom photos.api.v2.views import AlbumDetailView, AlbumListView, PhotoLikeView\n\napp_name = \"photos\"\n\nurlpatterns = [\n    path(\n        \"photos/\",\n        include(\n            [\n                path(\"albums/\", AlbumListView.as_view(), name=\"album-list\"),\n                path(\n                    \"albums/<slug:slug>/\",\n                    AlbumDetailView.as_view(),\n                    name=\"album-detail\",\n                ),\n                path(\n                    \"photos/<int:pk>/like/\", PhotoLikeView.as_view(), name=\"photo-like\"\n                ),\n            ]\n        ),\n    ),\n]\n", "path": "website/photos/api/v2/urls.py"}, {"content": "from django.db.models import Count, Prefetch, Q\n\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import filters, status\nfrom rest_framework.exceptions import PermissionDenied\nfrom rest_framework.generics import ListAPIView, RetrieveAPIView\nfrom rest_framework.response import Response\nfrom rest_framework.views import APIView\n\nfrom photos import services\nfrom photos.api.v2.serializers.album import AlbumListSerializer, AlbumSerializer\nfrom photos.models import Album, Like, Photo\n\n\nclass AlbumListView(ListAPIView):\n    \"\"\"Returns an overview of all albums.\"\"\"\n\n    serializer_class = AlbumListSerializer\n    queryset = Album.objects.filter(hidden=False)\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"photos:read\"]\n    filter_backends = (filters.SearchFilter,)\n    search_fields = (\"title\", \"date\", \"slug\")\n\n\nclass AlbumDetailView(RetrieveAPIView):\n    \"\"\"Returns the details of an album.\"\"\"\n\n    serializer_class = AlbumSerializer\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"photos:read\"]\n    lookup_field = \"slug\"\n\n    def retrieve(self, request, *args, **kwargs):\n        if not services.is_album_accessible(request, self.get_object()):\n            raise PermissionDenied\n        return super().retrieve(request, *args, **kwargs)\n\n    def get_queryset(self):\n        photos = Photo.objects.select_properties(\"num_likes\")\n        if self.request.member:\n            photos = photos.annotate(\n                member_likes=Count(\"likes\", filter=Q(likes__member=self.request.member))\n            )\n        return Album.objects.filter(hidden=False).prefetch_related(\n            Prefetch(\"photo_set\", queryset=photos)\n        )\n\n\nclass PhotoLikeView(APIView):\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"photos:read\"]\n\n    def get(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        return Response(\n            {\n                \"liked\": photo.likes.filter(member=request.member).exists(),\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_200_OK,\n        )\n\n    def post(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        _, created = Like.objects.get_or_create(photo=photo, member=request.member)\n\n        if created:\n            return Response(\n                {\n                    \"liked\": photo.likes.filter(member=request.member).exists(),\n                    \"num_likes\": photo.num_likes,\n                },\n                status=status.HTTP_201_CREATED,\n            )\n        return Response(\n            {\n                \"liked\": photo.likes.filter(member=request.member).exists(),\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_200_OK,\n        )\n\n    def delete(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        try:\n            like = Like.objects.filter(photo__album__hidden=False).get(\n                member=request.member, photo__pk=photo_id\n            )\n        except Like.DoesNotExist:\n            return Response(\n                {\n                    \"liked\": False,\n                    \"num_likes\": photo.num_likes,\n                },\n                status=status.HTTP_204_NO_CONTENT,\n            )\n\n        like.delete()\n\n        return Response(\n            {\n                \"liked\": False,\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_202_ACCEPTED,\n        )\n", "path": "website/photos/api/v2/views.py"}], "after_files": [{"content": "\"\"\"Photos app API v2 urls.\"\"\"\nfrom django.urls import include, path\n\nfrom photos.api.v2.views import (\n    AlbumDetailView,\n    AlbumListView,\n    LikedPhotosListView,\n    PhotoLikeView,\n)\n\napp_name = \"photos\"\n\nurlpatterns = [\n    path(\n        \"photos/\",\n        include(\n            [\n                path(\"albums/\", AlbumListView.as_view(), name=\"album-list\"),\n                path(\n                    \"albums/<slug:slug>/\",\n                    AlbumDetailView.as_view(),\n                    name=\"album-detail\",\n                ),\n                path(\n                    \"photos/<int:pk>/like/\", PhotoLikeView.as_view(), name=\"photo-like\"\n                ),\n                path(\n                    \"photos/liked/\", LikedPhotosListView.as_view(), name=\"liked-photos\"\n                ),\n            ]\n        ),\n    ),\n]\n", "path": "website/photos/api/v2/urls.py"}, {"content": "from django.db.models import Count, Prefetch, Q\n\nfrom oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope\nfrom rest_framework import filters, status\nfrom rest_framework.exceptions import PermissionDenied\nfrom rest_framework.generics import ListAPIView, RetrieveAPIView\nfrom rest_framework.response import Response\nfrom rest_framework.views import APIView\n\nfrom photos import services\nfrom photos.api.v2.serializers.album import (\n    AlbumListSerializer,\n    AlbumSerializer,\n    PhotoListSerializer,\n)\nfrom photos.models import Album, Like, Photo\n\n\nclass AlbumListView(ListAPIView):\n    \"\"\"Returns an overview of all albums.\"\"\"\n\n    serializer_class = AlbumListSerializer\n    queryset = Album.objects.filter(hidden=False)\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"photos:read\"]\n    filter_backends = (filters.SearchFilter,)\n    search_fields = (\"title\", \"date\", \"slug\")\n\n\nclass AlbumDetailView(RetrieveAPIView):\n    \"\"\"Returns the details of an album.\"\"\"\n\n    serializer_class = AlbumSerializer\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"photos:read\"]\n    lookup_field = \"slug\"\n\n    def retrieve(self, request, *args, **kwargs):\n        if not services.is_album_accessible(request, self.get_object()):\n            raise PermissionDenied\n        return super().retrieve(request, *args, **kwargs)\n\n    def get_queryset(self):\n        photos = Photo.objects.select_properties(\"num_likes\")\n        if self.request.member:\n            photos = photos.annotate(\n                member_likes=Count(\"likes\", filter=Q(likes__member=self.request.member))\n            )\n        return Album.objects.filter(hidden=False).prefetch_related(\n            Prefetch(\"photo_set\", queryset=photos)\n        )\n\n\nclass LikedPhotosListView(ListAPIView):\n    \"\"\"Returns the details the liked album.\"\"\"\n\n    serializer_class = PhotoListSerializer\n    permission_classes = [\n        IsAuthenticatedOrTokenHasScope,\n    ]\n    required_scopes = [\"photos:read\"]\n\n    def get(self, request, *args, **kwargs):\n        if not self.request.member:\n            return Response(\n                data={\n                    \"detail\": \"You need to be a member in order to view your liked photos.\"\n                },\n                status=status.HTTP_403_FORBIDDEN,\n            )\n        return self.list(request, *args, **kwargs)\n\n    def get_queryset(self):\n        return (\n            Photo.objects.filter(likes__member=self.request.member, album__hidden=False)\n            .annotate(\n                member_likes=Count(\"likes\", filter=Q(likes__member=self.request.member))\n            )\n            .select_properties(\"num_likes\")\n        )\n\n\nclass PhotoLikeView(APIView):\n    permission_classes = [IsAuthenticatedOrTokenHasScope]\n    required_scopes = [\"photos:read\"]\n\n    def get(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        return Response(\n            {\n                \"liked\": photo.likes.filter(member=request.member).exists(),\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_200_OK,\n        )\n\n    def post(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        _, created = Like.objects.get_or_create(photo=photo, member=request.member)\n\n        if created:\n            return Response(\n                {\n                    \"liked\": photo.likes.filter(member=request.member).exists(),\n                    \"num_likes\": photo.num_likes,\n                },\n                status=status.HTTP_201_CREATED,\n            )\n        return Response(\n            {\n                \"liked\": photo.likes.filter(member=request.member).exists(),\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_200_OK,\n        )\n\n    def delete(self, request, **kwargs):\n        photo_id = kwargs.get(\"pk\")\n        try:\n            photo = Photo.objects.filter(album__hidden=False).get(pk=photo_id)\n        except Photo.DoesNotExist:\n            return Response(status=status.HTTP_404_NOT_FOUND)\n\n        try:\n            like = Like.objects.filter(photo__album__hidden=False).get(\n                member=request.member, photo__pk=photo_id\n            )\n        except Like.DoesNotExist:\n            return Response(\n                {\n                    \"liked\": False,\n                    \"num_likes\": photo.num_likes,\n                },\n                status=status.HTTP_204_NO_CONTENT,\n            )\n\n        like.delete()\n\n        return Response(\n            {\n                \"liked\": False,\n                \"num_likes\": photo.num_likes,\n            },\n            status=status.HTTP_202_ACCEPTED,\n        )\n", "path": "website/photos/api/v2/views.py"}]}