iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
自我挑戰組

轉職新手學 Django 及 DRF系列 第 29

Day 29 - 製作 API(七) 為 Book 增加圖片上傳功能

  • 分享至 

  • xImage
  •  

今天就來實作圖片上傳的功能及端點!

圖片上傳功能

一開始到 core/models.py 中,因為目前的 Book model 並沒有圖片的欄位,需要先增加欄位並且指定上傳路徑:

import os
import uuid
# ...

def book_image_file_path(instance, filename):
    """Generate file path for new book image."""
    ext = os.path.splitext(filename)[1]
    filename = f'{uuid.uuid4()}{ext}'

    return os.path.join('uploads', 'book', filename)

class Book(models.Model):
    # ...
    image = models.ImageField(null=True, upload_to=book_image_file_path)

下方的 image 欄位中指定的upload_to引數會對應到上面的函式,函式內的ext變數會被賦予該檔案的格式(os.path.splitext()會將檔案切成陣列,陣列中[1]即為檔案格式)。接著使用 uuid 產生一組獨特的字元做為新的檔案名。最後會將檔案儲存在uploads/book/中。此函式的目的為將原本檔案轉換為獨特值並保留檔案格式,避免出現重複的情況。

完成後記得進行資料庫更新:

python manage.py makemigrations
python manage.py migrate

接著來進行 serializer 的製作,進入 book/serializer.py 並修改及新增以下程式碼:

# ...
class BookDetailSerializer(BookSerializer):
    """Serializer for book detail view."""

    class Meta(BookSerializer.Meta):
        fields = BookSerializer.Meta.fields + ['description', 'image'] # 新增 image field

class BookImageSerialzer(serializers.ModelSerializer):
    """Serializer for uploading images to books."""

    class Meta:
        model = Book
        fields = ['id', 'image']
        read_only_fields = ['id']
        extra_kwargs = {'image': {'required': True}}

BookDetailSerializer中增加 image,讓 image 也可顯示在回應中。新增的BookImageSerialzer裡面指定 id 跟 image 欄位進行序列化,並指定 image 為必填欄位。完成後進入 book/views.py ,要再次修改get_serializer_class、新增導入項目以及新增程式碼如下:

from rest_framework import viewsets, status # 新導入 status
from rest_framework.decorators import action
from rest_framework.response import Response
# ...

class BookViewSet(viewsets.ModelViewSet):
    # ...
    def get_serializer_class(self):
        """Return the serializer class for request."""
        if self.action == 'list':
            return serializers.BookSerializer
        elif self.action == 'upload_image':
            return serializers.BookImageSerialzer

        return self.serializer_class

    @action(methods=['POST'], detail=True, url_path='upload_image')
    def upload_image(self, request, pk=None):
        """Upload an image to book."""
        book = self.get_object()
        serializer = self.get_serializer(book, data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response(serializer.error, status=status.HTTP_400_BAD_REQUEST)

這邊在get_serializer_class新增上傳圖片的條件。但預設 action 並無 upload_image,所以需要自定義在下方。函式上方的裝飾器裡,methods=['POST']表示只接受 POST 請求,detail=True表示此功能僅作用在特定一物件上;若detail=False則會作用在所有物件,整體來說,若要觸發 POST, API 請求中的 URL 需要包含書籍的主鍵(pk)和 'upload_image' 路徑,例如 /books/1/upload_image/。

函式內部首先先用get_object()取得該物件,並用此物件及請求中的資料初始化 serializer(就是呼叫get_serializer_class()),再來驗證資料正確性,若正確儲存資料並回傳 200;反之則回傳 400。

以上儲存後,到 rest_api/settings.py 中新增:

# ...
SPECTACULAR_SETTINGS = {
    'COMPONENT_SPLIT_REQUEST': True,
}

這樣在上傳檔案時才會出現選擇檔案的按鈕。

都儲存後,就打開伺服器測試看看吧

在 Swagger 介面中,upload_image的端點要使用multipart/formdata才能正確上傳

結語

到這邊,book RESTful API 算是完成了,而本次鐵人賽所有的作品也如期完成,最後來個小總結,明天見~


上一篇
Day 28 - 中場暫停(四) Python 及 Django 的圖片處理
下一篇
Day 30 - 回顧作品及鐵人賽完賽心得
系列文
轉職新手學 Django 及 DRF30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言