Search code examples
djangodjango-rest-frameworkdjango-viewsdjango-rest-viewsets

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


I have a UserProfile model which has one to one relationship with User model.

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.

Then I have explicitly mentioned which method to call in urls.py. Now it shows errors.

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.

In 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()

In 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', )

In 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)

In urls.py:

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

Solution

  • To achieve this task I have manually created the image saving feature using APIView.

    Please update if any efficient code exists

    In my urls.py file:

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

    In 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)
    

    In 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')