diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 579cfe3..2e4a69f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +*/__pycache__/ + /books /covers \ No newline at end of file diff --git a/alttextbackend/__pycache__/urls.cpython-311.pyc b/alttextbackend/__pycache__/urls.cpython-311.pyc index 36bf25a..5b4ab8d 100644 Binary files a/alttextbackend/__pycache__/urls.cpython-311.pyc and b/alttextbackend/__pycache__/urls.cpython-311.pyc differ diff --git a/alttextbackend/__pycache__/views.cpython-311.pyc b/alttextbackend/__pycache__/views.cpython-311.pyc deleted file mode 100644 index b36e85d..0000000 Binary files a/alttextbackend/__pycache__/views.cpython-311.pyc and /dev/null differ diff --git a/alttextbackend/urls.py b/alttextbackend/urls.py index 08bf21f..d0d18a8 100644 --- a/alttextbackend/urls.py +++ b/alttextbackend/urls.py @@ -21,16 +21,16 @@ from .views.books import BooksView from .views.books_bookid import BooksBookidView from .views.books_bookid_export import BooksBookidExportView from .views.books_bookid_images import BooksBookidImagesView -from .views.books_bookid_src import BooksBookidSrcView +from .views.books_bookid_image import BooksBookidImageView from .views.images_hash import ImagesHashView urlpatterns = [ # path("admin/", admin.site.urls), # path("api-auth/", include("rest_framework.urls")), path("books", BooksView.as_view()), - path("books/", BooksBookidView.as_view()), - path("books//export", BooksBookidExportView.as_view()), - path("books//images", BooksBookidImagesView.as_view()), - path("books//", BooksBookidSrcView.as_view()), + path("books/", BooksBookidView.as_view()), + path("books//export", BooksBookidExportView.as_view()), + path("books//images", BooksBookidImagesView.as_view()), + path("books//", BooksBookidImageView.as_view()), path("images/", ImagesHashView.as_view()), ] diff --git a/alttextbackend/views/__pycache__/books.cpython-311.pyc b/alttextbackend/views/__pycache__/books.cpython-311.pyc index 483ce07..ffa3687 100644 Binary files a/alttextbackend/views/__pycache__/books.cpython-311.pyc and b/alttextbackend/views/__pycache__/books.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/books_bookid.cpython-311.pyc b/alttextbackend/views/__pycache__/books_bookid.cpython-311.pyc index bdcf5d3..c98368b 100644 Binary files a/alttextbackend/views/__pycache__/books_bookid.cpython-311.pyc and b/alttextbackend/views/__pycache__/books_bookid.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/books_bookid_export.cpython-311.pyc b/alttextbackend/views/__pycache__/books_bookid_export.cpython-311.pyc index c6c6ddd..0a3a2ce 100644 Binary files a/alttextbackend/views/__pycache__/books_bookid_export.cpython-311.pyc and b/alttextbackend/views/__pycache__/books_bookid_export.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/books_bookid_image.cpython-311.pyc b/alttextbackend/views/__pycache__/books_bookid_image.cpython-311.pyc new file mode 100644 index 0000000..2208826 Binary files /dev/null and b/alttextbackend/views/__pycache__/books_bookid_image.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/books_bookid_images.cpython-311.pyc b/alttextbackend/views/__pycache__/books_bookid_images.cpython-311.pyc index cd52112..2e827d6 100644 Binary files a/alttextbackend/views/__pycache__/books_bookid_images.cpython-311.pyc and b/alttextbackend/views/__pycache__/books_bookid_images.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/books_bookid_src.cpython-311.pyc b/alttextbackend/views/__pycache__/books_bookid_src.cpython-311.pyc index 24232f1..6e54407 100644 Binary files a/alttextbackend/views/__pycache__/books_bookid_src.cpython-311.pyc and b/alttextbackend/views/__pycache__/books_bookid_src.cpython-311.pyc differ diff --git a/alttextbackend/views/__pycache__/images_hash.cpython-311.pyc b/alttextbackend/views/__pycache__/images_hash.cpython-311.pyc index 0f5f35e..e7d5bad 100644 Binary files a/alttextbackend/views/__pycache__/images_hash.cpython-311.pyc and b/alttextbackend/views/__pycache__/images_hash.cpython-311.pyc differ diff --git a/alttextbackend/views/books.py b/alttextbackend/views/books.py index 930b421..5bc55d7 100644 --- a/alttextbackend/views/books.py +++ b/alttextbackend/views/books.py @@ -8,23 +8,55 @@ from django.core.files.base import ContentFile from uuid import uuid4 -class BookSerializer(serializers.Serializer): - title = serializers.CharField() - description = serializers.CharField() - cover = serializers.ImageField() - file = serializers.FileField() +class GetBooksSerializer(serializers.Serializer): + titleQ = serializers.CharField(required=False) + authorQ = serializers.CharField(required=False) + sortBy = serializers.ChoiceField(choices=['title', 'author'], style={'base_template': 'radio.html'}, default = 'title') + sortOrder = serializers.ChoiceField(choices=['asc', 'desc'], style={'base_template': 'radio.html'}, default = 'asc') + limit = serializers.IntegerField(min_value=1, required=False) + skip = serializers.IntegerField(min_value=0, required=False) +class AddBookSerializer(serializers.Serializer): + title = serializers.CharField(required=True, allow_blank=False) + author = serializers.CharField(required=True, allow_blank=False) + description = serializers.CharField(required=False, allow_blank=True) + file = serializers.FileField(required=True) + cover = serializers.ImageField(required=False) class BooksView(APIView): parser_classes = (FormParser, MultiPartParser) - serializer_class = BookSerializer + serializer_class = AddBookSerializer + def get_serializer_class(self): + if self.request.method == 'GET': + return GetBooksSerializer + elif self.request.method == 'POST': + return AddBookSerializer + return super().get_serializer_class() def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer_class = self.get_serializer_class() + serializer = serializer_class(data=request.query_params) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + # Access validated data + validated_data = serializer.validated_data + title_query = validated_data.get('titleQ') + author_query = validated_data.get('authorQ') + sort_by = validated_data.get('sortBy') + sort_order = validated_data.get('sortOrder') + limit = validated_data.get('limit') + skip = validated_data.get('skip') + + # TODO: perform logic + + # TODO: return books + return Response(validated_data, status=status.HTTP_200_OK) def post(self, request, *args, **kwargs): # validate request data - serializer = self.serializer_class(data=request.data) + serializer_class = self.get_serializer_class() + serializer = serializer_class(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) validated_data = serializer.validated_data @@ -41,7 +73,7 @@ class BooksView(APIView): # TODO: ensure book has valid root html file - # TODO: start analyzing book + # TODO: analyze book and images, store them in database # save cover image covers_path = "./covers/" diff --git a/alttextbackend/views/books_bookid.py b/alttextbackend/views/books_bookid.py index b48ccff..16ec09a 100644 --- a/alttextbackend/views/books_bookid.py +++ b/alttextbackend/views/books_bookid.py @@ -7,17 +7,96 @@ from django.core.files.storage import default_storage from django.core.files.base import ContentFile from uuid import uuid4 +class GetBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + +class UpdateBookSerialzer(serializers.Serializer): + bookid = serializers.CharField(required=True) + title = serializers.CharField(required=False, allow_blank=False) + author = serializers.CharField(required=False, allow_blank=False) + description = serializers.CharField(required=False, allow_blank=True) + cover = serializers.ImageField(required=False) + +class AnalyzeBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + +class OverwriteBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + file = serializers.FileField(required=True) + +class DeleteBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + class BooksBookidView(APIView): parser_classes = (FormParser, MultiPartParser) - def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer_class = UpdateBookSerialzer + def get_serializer_class(self): + if self.request.method == 'GET': + return GetBookSerializer + elif self.request.method == 'PATCH': + return UpdateBookSerialzer + elif self.request.method == 'PUT': + return AnalyzeBookSerializer + elif self.request.method == 'DELETE': + return DeleteBookSerializer + return super().get_serializer_class() - def post(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + def get(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) + + def patch(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + data = request.data + data['bookid'] = kwargs.get('bookid') + serializer = serializer_class(data=data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) def put(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer_class = self.get_serializer_class() + serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) + + def post(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + data = request.data + data['bookid'] = kwargs.get('bookid') + serializer = serializer_class(data=request.data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) def delete(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer_class = self.get_serializer_class() + serializer = serializer_class(data={"bookid": kwargs.get('bookid')}) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) diff --git a/alttextbackend/views/books_bookid_export.py b/alttextbackend/views/books_bookid_export.py index 42d0491..15b21cc 100644 --- a/alttextbackend/views/books_bookid_export.py +++ b/alttextbackend/views/books_bookid_export.py @@ -7,8 +7,19 @@ from django.core.files.storage import default_storage from django.core.files.base import ContentFile from uuid import uuid4 +class ExportBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + class BooksBookidExportView(APIView): parser_classes = (FormParser, MultiPartParser) + serializer_class = ExportBookSerializer def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer = self.serializer_class(data={"bookid": kwargs.get('bookid')}) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) diff --git a/alttextbackend/views/books_bookid_image.py b/alttextbackend/views/books_bookid_image.py new file mode 100644 index 0000000..6cdbc05 --- /dev/null +++ b/alttextbackend/views/books_bookid_image.py @@ -0,0 +1,74 @@ +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status, permissions, serializers +from rest_framework.exceptions import ValidationError +from rest_framework.parsers import FormParser, MultiPartParser +from django.core.files.storage import default_storage +from django.core.files.base import ContentFile +from uuid import uuid4 + +class GetImageBySrc(serializers.Serializer): + bookid = serializers.CharField(required=True) + src = serializers.CharField(required=True) + +class UpdateImageBySrc(serializers.Serializer): + bookid = serializers.CharField(required=True) + src = serializers.CharField(required=True) + alt = serializers.CharField(required=True) + beforeContext = serializers.CharField(required=False) + afterContext = serializers.CharField(required=False) + +class AnalyzeImageBySrc(serializers.Serializer): + bookid = serializers.CharField(required=True) + src = serializers.CharField(required=True) + +class BooksBookidImageView(APIView): + parser_classes = (FormParser, MultiPartParser) + def get_serializer_class(self): + if self.request.method == 'GET': + return GetImageBySrc + elif self.request.method == 'PATCH': + return UpdateImageBySrc + elif self.request.method == 'PUT': + return AnalyzeImageBySrc + return super().get_serializer_class() + + def get(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + data = request.query_params + data['bookid'] = kwargs.get('bookid') + serializer = serializer_class(data=data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) + + def patch(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + data = request.data + data.update(request.query_params) + data['bookid'] = kwargs.get('bookid') + serializer = serializer_class(data=data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) + + def put(self, request, *args, **kwargs): + serializer_class = self.get_serializer_class() + data = request.query_params + data['bookid'] = kwargs.get('bookid') + serializer = serializer_class(data=data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) diff --git a/alttextbackend/views/books_bookid_images.py b/alttextbackend/views/books_bookid_images.py index 566e6a3..7036cf0 100644 --- a/alttextbackend/views/books_bookid_images.py +++ b/alttextbackend/views/books_bookid_images.py @@ -7,8 +7,19 @@ from django.core.files.storage import default_storage from django.core.files.base import ContentFile from uuid import uuid4 +class ImagesFromBookSerializer(serializers.Serializer): + bookid = serializers.CharField(required=True) + class BooksBookidImagesView(APIView): parser_classes = (FormParser, MultiPartParser) + serializer_class = ImagesFromBookSerializer def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + serializer = self.serializer_class(data={"bookid": kwargs.get('bookid')}) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + validated_data = serializer.validated_data + + # TODO: IMPLEMENT LOGIC + + return Response(validated_data, status=status.HTTP_200_OK) diff --git a/alttextbackend/views/books_bookid_src.py b/alttextbackend/views/books_bookid_src.py deleted file mode 100644 index 81ae422..0000000 --- a/alttextbackend/views/books_bookid_src.py +++ /dev/null @@ -1,20 +0,0 @@ -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status, permissions, serializers -from rest_framework.exceptions import ValidationError -from rest_framework.parsers import FormParser, MultiPartParser -from django.core.files.storage import default_storage -from django.core.files.base import ContentFile -from uuid import uuid4 - -class BooksBookidSrcView(APIView): - parser_classes = (FormParser, MultiPartParser) - - def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) - - def patch(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) - - def put(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) diff --git a/alttextbackend/views/images_hash.py b/alttextbackend/views/images_hash.py index 7191087..0503f91 100644 --- a/alttextbackend/views/images_hash.py +++ b/alttextbackend/views/images_hash.py @@ -7,8 +7,20 @@ from django.core.files.storage import default_storage from django.core.files.base import ContentFile from uuid import uuid4 +class GetImagesByHashSerializer(serializers.Serializer): + hash = serializers.CharField(required=True) + class ImagesHashView(APIView): parser_classes = (FormParser, MultiPartParser) + serializer_class = GetImagesByHashSerializer def get(self, request, *args, **kwargs): - return Response({"TODO": "TODO"}, status=status.HTTP_200_OK) + image_hash = kwargs.get('hash') + data = {'hash': image_hash} + serializer = self.serializer_class(data=data) + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + # TODO: IMPLEMENT LOGIC + + return Response(data, status=status.HTTP_200_OK) diff --git a/manage.py b/manage.py index ad28ebc..e3f14aa 100644 --- a/manage.py +++ b/manage.py @@ -2,7 +2,7 @@ """Django's command-line utility for administrative tasks.""" import os import sys - +from dotenv import load_dotenv def main(): """Run administrative tasks.""" @@ -19,4 +19,5 @@ def main(): if __name__ == '__main__': + load_dotenv() main() diff --git a/openapi.yaml b/openapi.yaml index 199eed5..e0f97b4 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -36,6 +36,31 @@ paths: explode: true schema: type: string + - name: authorQ + in: query + description: String to match the author to. + required: false + explode: true + schema: + type: string + - name: sortBy + in: query + description: Field to sort by. + required: false + explode: true + schema: + type: string + enum: ["title", "author"] + default: "title" + - name: sortOrder + in: query + description: Order to sort by. + required: false + explode: true + schema: + type: string + enum: ["asc", "desc"] + default: "asc" - name: limit in: query description: Max number of books to return. @@ -164,7 +189,6 @@ paths: $ref: '#/components/schemas/Book' '500': description: Internal Server Error - # TODO: POST ROUTE TO UPLOAD NEW BOOK FILE put: tags: - Books @@ -180,6 +204,32 @@ paths: $ref: '#/components/schemas/Book' '500': description: Internal Server Error + post: + tags: + - Books + summary: Upload a new book file to a book object. + description: Upload a new book to a given book object (by its id), and re-analyze it (essentially creating a new book, except keeping the same bookid). + operationId: overwriteBook + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + book: + type: string + description: Zip file of the book. + format: binary + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Book' + '500': + description: Internal Server Error delete: tags: - Books @@ -278,7 +328,7 @@ paths: $ref: '#/components/schemas/Image' '500': description: Internal Server Error - /books/{bookid}/{src}: + /books/{bookid}/image: parameters: - name: bookid in: path @@ -289,7 +339,7 @@ paths: type: string example: "123e4567-e89b-12d3-a456-426614174000" - name: src - in: path + in: query description: Src of the image. required: true explode: true @@ -347,7 +397,7 @@ paths: - Images summary: Re-analyze an image. description: Generate an image's alt-text (written to genAlt field in image object). - operationId: analyzeImage + operationId: analyzeImageBySrc responses: '200': description: Successful operation diff --git a/requirements.txt b/requirements.txt index 54d91e9..bd6121a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ django -django_rest_framework \ No newline at end of file +django_rest_framework +python-dotenv \ No newline at end of file