如何在用户配置文件模型中上传图像,同时与 django rest 框架中的用户模型具有一对一关系?

How to upload image in user profile model while has one to one relationship with user model in django rest framework?

我有一个 UserProfile 模型,它与 User 模型有一对一的关系。

I am facing problem in uploading image by rest-framework API endpoint to nested model.

Problem is: Though I am explicitly using @action() decorator, then also it is calling inbuilt create() method for saving image by POST.

那我已经在urls.py中明确提到要调用哪个方法了。现在它显示错误。

There are errors in the implementation in serializer.py and views.py.

So if possible then please correct my mistakes and direct me in the correct direction. Its more than two days but still I am struck.

models.py:

def user_image_upload_file_path(instance, filename):
    """Generates file path for uploading user images"""
    extension = filename.split('.')[-1]
    file_name = f'{uuid.uuid4()}.{extension}'
    date = datetime.date.today()
    initial_path = f'pictures/uploads/user/{date.year}/{date.month}/{date.day}/'
    full_path = os.path.join(initial_path, file_name)

    return full_path

class UserManager(BaseUserManager):

    def create_user(self, email, password, username, **extra_kwargs):
        """Creates and saves a new user"""

        if not email:
            raise ValueError(_('Email cannot be empty'))

        user = self.model(email=self.normalize_email(email), **extra_kwargs)
        user.set_password(password)
        user.save(using=self._db)

        return user

    def create_superuser(self, email, password, username, **extra_kwargs):
        """Creates and saves a new user with superuser permission"""
        user = self.create_user(
            email, password, username, **extra_kwargs)
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)

        return user


class User(AbstractBaseUser, PermissionsMixin):
    """Creates user model that supports using email as username"""
    email = models.EmailField(_('Email'), max_length=255, unique=True)
    created_date = models.DateTimeField(
        _('Created Date'), default=timezone.now, editable=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'

    def __str__(self):
        """String representation of user model"""
        return self.email


class UserProfile(models.Model, Languages):
    """Creates user profile model"""
    user = models.OneToOneField(
        'User',
        related_name='profile',
        on_delete=models.CASCADE
    )
    first_name = models.CharField(
        _('First Name'), max_length=255, blank=True)
    last_name = models.CharField(
        _('Last Name'), max_length=255, blank=True)
    image = models.ImageField(
        _('Image'),
        upload_to=user_image_upload_file_path,
        null=True,
        blank=True,
        max_length=1024
    )


@receiver(post_save, sender=User)
def user_is_created(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)
    else:
        instance.profile.save()

serializer.py:

class UserSerializer(serializers.ModelSerializer):
    """Minimal serializer for supporting user image upload field"""

    class Meta:
        model = get_user_model()
        fields = ('username', )


class UserImageUploadSerializer(serializers.ModelSerializer):
    """Serializer for user profile"""
    user = UserSerializer(read_only=True)

    class Meta:
        model = UserProfile
        fields = ('id', 'user', 'image', )
        read_only_fields = ('id', 'user', )

views.py:

class UserImageUploadView(viewsets.ModelViewSet):
    serializer_class = serializer.UserImageUploadSerializer
    authentication_classes = [authentication.TokenAuthentication, ]
    permission_classes = [permissions.IsAuthenticated, ]
    queryset = get_user_model().objects.all()

    def get_queryset(self):
        """Return object for only authenticated user"""
        return self.queryset.filter(id=self.request.user)


    @action(detail=True, methods=['POST'], url_path='user-upload-image')
    def image_upload(self, pk=None):
        """Save the uploaded picture and profile data"""
        user = self.get_object()
        profile = user.profile
        data = {'user': user, 'id': profile.pk, 'data': request.data}
        serializer_ = serializer.UserImageUploadSerializer(data=data)

        if serializer_.is_valid():
            serializer_.save()
            return Response(serializer_.data, status=status.HTTP_200_OK)
        else:
            return Response(serializer_.errors, status=status.HTTP_400_BAD_REQUEST)

urls.py:

urlpatterns = [
    path('<int:pk>/upload-image/', views.UserImageUploadView.as_view(
        {'get': 'list', 'post': 'image_upload', }), name='user-image-upload')
]

为了完成这个任务,我使用 APIView 手动创建了图像保存功能。

Please update if any efficient code exists

在我的 urls.py 文件中:

urlpatterns = [
    path('upload-image/', views.UserImageUploadView.as_view(), name='user-image-upload'),
]

views.py:

from rest_framework.parsers import FormParser, MultiPartParser, JSONParser
from rest_framework.views import APIView

from . import serializer
from core import models

class UserImageUploadView(APIView):
    """View to upload or view image for user"""
    serializer_class = serializer.TempSerializer
    authentication_classes = [authentication.TokenAuthentication, ]
    permission_classes = [permissions.IsAuthenticated, ]
    parser_classes = [JSONParser, MultiPartParser]

    def get(self, request, format=None):
        """To get user profile picture"""
        user = get_user_model().objects.get(email=request.user)
        user_profile = models.UserProfile.objects.get(user=user)

        # Preparing the data manually as per our serializer
        data = {'user': {'username': user.username},
                'image': user_profile.image or None}

        # Serializing our prepared data
        ser = serializer.TempSerializer(
            user_profile, data=data, context={"request": request})

        # Returning appropriate response
        if ser.is_valid():
            return_ser_data = {'id': ser.data.get('id'),
                               'image': ser.data.get('image')}
            return Response(return_ser_data, status=status.HTTP_200_OK)
        else:
            return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)

    def post(self, request, format=None):
        """To save the profile picture"""
        user = get_user_model().objects.get(email=request.user)
        user_profile = models.UserProfile.objects.get(user=user)

        # Formatting the data to as per our defined serializer
        data = {'user': {'username': user.username},
                'image': request.data.get('image')}

        # Serializing our data
        ser = serializer.TempSerializer(
            user_profile, data=data, context={"request": request})

        if ser.is_valid():
            if ser.validated_data:
                # Deleting the old image before uploading new image
                if user_profile.image:
                    user_profile.image.delete()

                # Saving the model
                ser.save(user=user)
            return_ser_data = {'id': ser.data.get('id'),
                               'image': ser.data.get('image')}
            return Response(return_ser_data, status=status.HTTP_200_OK)
        else:
            return Response(ser.errors, status=status.HTTP_400_BAD_REQUEST)

serializer.py:

class UserSerializer(serializers.ModelSerializer):
    """Minimal serializer for supporting user image upload field"""

    class Meta:
        model = get_user_model()
        fields = ('username', )

class TempSerializer(serializers.ModelSerializer):
    """Serializer for user image upload"""
    user = UserSerializer(read_only=True)
    image = serializers.ImageField(allow_null=True, use_url=True)

    class Meta:
        model = UserProfile
        fields = ('id', 'user', 'image')
        read_only_fields = ('id', 'user')