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