[Django] 24 - DRF: Add Image API

除了教程中提到的,还要考虑S3上的问题。 

 

 

Docker volumes

  • Store persistent data.
  • Volume we'll setup: 
    • /vol/web - store static and media subdirectories

 

GET /static/media/file.jpeg <- /vol/web/media/file.jpeg

GET /static/static/admin/style.css <-- /vol/web/static/admin/style.css

 

  • Dockerfile

In the Dockerfile

mkdir -p /vol/web/media
mkdir -p /vol/web/static
chown -R django-user:django-user /vol
chmod -R 755 /vol

 

  • docker-compose.yml

volumes: 
  - ./app:/app
  - dev-static-data:/vol/web

volumes: 
  dev-db-data:
  dev-static-data:

 

  • settings.py

STATIC_URL = '/static/static/'
MEDIA_URL = '/static/static/'

STATIC_ROOT = '/vol/web/media'
STATIC_ROOT = '/vol/web/static'

 

  • urls.py

from django.conf.urls.static import static
from django.conf import settings

if setting.DEBUG:
  urlpatterns += static(
    settings.MEDIA_URL,
    document_root=settings.MEDIA_ROOT,
  )

 

 

Collect Static

Puts all static files into STATIC_ROOT.

python manage.py collectstatic

 

 

Model

  • Test Case

from unittest.mock import patch

# ---------------------------------------------------------------------
@patch(
'uuid.uuid4') def test_recipe_file_name_uuid(self, mock_uuid): """Test generating image path."""
uuid = 'test-uuid'mock_uuid.return_value = uuid
file_path
= models.recipe_image_file_path(None, 'example.jpg') self.assertEqual(file_path, f'uploads/recipe/{uuid}.jpg')

 

  •  Model

上传文件,文件名再自定义 by uuid.

def recipe_image_file_path(instance, filename):
    """Generate file path for new recipe image."""
ext = os.path.splitext(filename)[1] filename = f'{uuid.uuid4()}{ext}' return os.path.join('uploads', 'recipe', filename)
class Recipe(models.Model): """Recipe object.""" ... ... image = models.ImageField(null=True, upload_to=recipe_image_file_path) def __str__(self): return self.title

 

 

View

  • Test Case

test_recipe_api.py

import tempfile
import os

from PIL import Image

 

Test methods.

class ImageUploadTests(TestCase):
    """Tests for the image upload API."""

    def setUp(self):
        self.client = APIClient()
        self.user = get_user_model().objects.create_user(
            'user@example.com',
            'password123',
        )
        self.client.force_authenticate(self.user)
        self.recipe = create_recipe(user=self.user)

    def tearDown(self):
        self.recipe.image.delete()

# ----------------------------------------------------------------
def test_upload_image(self):
"""Test uploading an image to a recipe."""
url = image_upload_url(self.recipe.id) with tempfile.NamedTemporaryFile(suffix='.jpg') as image_file: img = Image.new('RGB', (10, 10)) img.save(image_file, format='JPEG') image_file.seek(0) # 创建一个假图片用于测试
payload
= {'image': image_file} res = self.client.post(url, payload, format='multipart') self.recipe.refresh_from_db() self.assertEqual(res.status_code, status.HTTP_200_OK) self.assertIn('image', res.data) self.assertTrue(os.path.exists(self.recipe.image.path))
def test_upload_image_bad_request(self): """Test uploading an invalid image."""

url = image_upload_url(self.recipe.id) payload = {'image': 'notanimage'} res = self.client.post(url, payload, format='multipart') self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)

 

  • Serializers

class RecipeImageSerializer(serializers.ModelSerializer):
    """Serializer for uploading images to recipes."""

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

 

  • View

class RecipeViewSet(viewsets.ModelViewSet):

... ...
def get_serializer_class(self): """Return the serializer class for request.""" if self.action == 'list': return serializers.RecipeSerializer elif self.action == 'upload_image': return serializers.RecipeImageSerializer return self.serializer_class
@action(methods
=['POST'], detail=True, url_path='upload-image') def upload_image(self, request, pk=None): """Upload an image to recipe.""" recipe = self.get_object() serializer = self.get_serializer(recipe, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

 

Settings

SPECTACULAR_SETTINGS ='COMPONENT_SPLIT_REQUEST': True,
}

 

End.

posted @ 2022-07-04 10:51  郝壹贰叁  阅读(51)  评论(0编辑  收藏  举报