diff --git a/solution/pulse/api/countries/views.py b/solution/pulse/api/countries/views.py index 2f50ac4..187a87c 100644 --- a/solution/pulse/api/countries/views.py +++ b/solution/pulse/api/countries/views.py @@ -11,20 +11,22 @@ class CountryListApiView(ListAPIView): serializer_class = CountrySerializer def filter_queryset(self, queryset): - regions = self.request.query_params.get("region") + regions = self.request.query_params.getlist("region") + + if regions == [""]: + return queryset + if regions: - regions_list = regions.split(",") invalid_regions = [ - region - for region in regions_list - if region not in settings.REGIONS + region for region in regions if region not in settings.REGIONS ] if invalid_regions: invalid_regions_str = ", ".join(invalid_regions) error_message = f"Invalid region(s): {invalid_regions_str}" raise ValidationError(error_message) - queryset = queryset.filter(region__in=regions_list) + queryset = queryset.filter(region__in=regions) + return queryset diff --git a/solution/pulse/api/posts/permissions.py b/solution/pulse/api/posts/permissions.py index e5cd177..ea24e3b 100644 --- a/solution/pulse/api/posts/permissions.py +++ b/solution/pulse/api/posts/permissions.py @@ -1,11 +1,14 @@ from rest_framework import status +from rest_framework.exceptions import APIException from rest_framework.permissions import BasePermission -class CanAccessPost(BasePermission): - message = "You do not have permission to access this post." +class CustomForbidden(APIException): status_code = status.HTTP_404_NOT_FOUND + default_detail = "You dont have access to view this post." + +class CanAccessPost(BasePermission): def has_object_permission(self, request, view, obj): if ( obj.author.isPublic @@ -14,7 +17,7 @@ class CanAccessPost(BasePermission): ): return True - return False + raise CustomForbidden class CanAccessFeed(BasePermission): @@ -29,4 +32,4 @@ class CanAccessFeed(BasePermission): ): return True - return False + raise CustomForbidden diff --git a/solution/pulse/api/posts/serializers.py b/solution/pulse/api/posts/serializers.py index 8de68f2..0b51d3d 100644 --- a/solution/pulse/api/posts/serializers.py +++ b/solution/pulse/api/posts/serializers.py @@ -6,7 +6,7 @@ from api.posts.models import Post class PostSerializer(serializers.ModelSerializer): # ruff: noqa: N815 - author = serializers.ReadOnlyField(source="author.username") + author = serializers.SerializerMethodField() likesCount = serializers.SerializerMethodField() dislikesCount = serializers.SerializerMethodField() @@ -30,7 +30,14 @@ class PostSerializer(serializers.ModelSerializer): def get_dislikesCount(self, obj): return obj.dislikes.count() + def get_author(self, obj): + return obj.author.login + def validate_tags(self, value): + if not isinstance(value, list): + error = "Tags must be provided as a list." + raise serializers.ValidationError(error) + for tag in value: if len(tag) > settings.MAX_TAG_LENGTH: error = "Each tag must be 20 characters or fewer." diff --git a/solution/pulse/api/posts/urls.py b/solution/pulse/api/posts/urls.py index 2a63025..fac3a0b 100644 --- a/solution/pulse/api/posts/urls.py +++ b/solution/pulse/api/posts/urls.py @@ -4,12 +4,12 @@ import api.posts.views urlpatterns = [ path( - "/create", + "/new", api.posts.views.CreatePostApiView.as_view(), name="create-post", ), path( - "/", + "/", api.posts.views.PostDetailApiView.as_view(), name="post-detail", ), diff --git a/solution/pulse/api/posts/views.py b/solution/pulse/api/posts/views.py index bc8d575..7fa1ed4 100644 --- a/solution/pulse/api/posts/views.py +++ b/solution/pulse/api/posts/views.py @@ -18,7 +18,7 @@ class CreatePostApiView(APIView): serializer = PostSerializer(data=request.data) if serializer.is_valid(): serializer.save(author=request.user) - return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.data, status=status.HTTP_200_OK) raise ValidationError(serializer.errors) @@ -52,7 +52,9 @@ class MyFeedListApiView(ListAPIView): limit = serializer.validated_data.get("limit") offset = serializer.validated_data.get("offset") - return self.request.user.posts.all()[offset: offset + limit] + return self.request.user.posts.order_by("-createdAt").all()[ + offset: offset + limit + ] class UserFeedListApiView(ListAPIView): @@ -81,11 +83,11 @@ class UserFeedListApiView(ListAPIView): limit = serializer.validated_data.get("limit") offset = serializer.validated_data.get("offset") - return user.posts.all()[offset: offset + limit] + return user.posts.order_by("-createdAt").all()[offset : offset + limit] class LikePostApiView(APIView): - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, CanAccessPost] def post(self, request, post_id): try: @@ -103,7 +105,7 @@ class LikePostApiView(APIView): class DislikePostApiView(APIView): - permission_classes = [IsAuthenticated] + permission_classes = [IsAuthenticated, CanAccessPost] def post(self, request, post_id): try: diff --git a/solution/pulse/api/users/migrations/0001_initial.py b/solution/pulse/api/users/migrations/0001_initial.py index 6d27659..f8f4a3d 100644 --- a/solution/pulse/api/users/migrations/0001_initial.py +++ b/solution/pulse/api/users/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-03-03 15:01 +# Generated by Django 4.2.10 on 2024-03-04 19:15 import api.users.validators import django.core.validators @@ -30,7 +30,7 @@ class Migration(migrations.Migration): ('password', models.CharField(max_length=100, validators=[django.core.validators.RegexValidator('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{6,100}$')])), ('countryCode', models.CharField(max_length=2, validators=[django.core.validators.RegexValidator('[a-zA-Z]{2}'), api.users.validators.CountryCodeValidator()])), ('isPublic', models.BooleanField()), - ('phone', models.CharField(max_length=20, null=True, validators=[django.core.validators.MaxLengthValidator(20), django.core.validators.RegexValidator('\\+[\\d]+')])), + ('phone', models.CharField(max_length=20, null=True, validators=[django.core.validators.MaxLengthValidator(20), django.core.validators.RegexValidator('^\\+\\d+')])), ('image', models.URLField(null=True)), ('friends', models.ManyToManyField(blank=True, through='users.Friendship', to='users.profile')), ], diff --git a/solution/pulse/api/users/models.py b/solution/pulse/api/users/models.py index 339dd65..a061b40 100644 --- a/solution/pulse/api/users/models.py +++ b/solution/pulse/api/users/models.py @@ -27,7 +27,7 @@ class Profile(models.Model): isPublic = models.BooleanField() phone = models.CharField( max_length=20, - validators=[MaxLengthValidator(20), RegexValidator(r"\+[\d]+")], + validators=[MaxLengthValidator(20), RegexValidator(r"^\+\d+")], null=True, ) image = models.URLField(max_length=200, null=True) @@ -56,7 +56,6 @@ class Profile(models.Model): return self.liked_posts.add(post) def dislike_post(self, post): - print(self, post) self.liked_posts.remove(post) return self.disliked_posts.add(post) @@ -82,7 +81,7 @@ class Profile(models.Model): cls.objects.filter(phone=validated_data.get("phone")) .exclude(id=user_id) .exists() - ): + ) and validated_data.get("phone") is not None: errors["phone"] = {"User with this phone already exists"} return errors diff --git a/solution/pulse/api/users/serializers.py b/solution/pulse/api/users/serializers.py index 6d383b8..88c9281 100644 --- a/solution/pulse/api/users/serializers.py +++ b/solution/pulse/api/users/serializers.py @@ -22,8 +22,6 @@ class UpdateProfileSerializer(serializers.ModelSerializer): class Meta: model = Profile fields = [ - "login", - "email", "countryCode", "isPublic", "phone", diff --git a/solution/pulse/api/users/views.py b/solution/pulse/api/users/views.py index c3ddc51..052a920 100644 --- a/solution/pulse/api/users/views.py +++ b/solution/pulse/api/users/views.py @@ -116,7 +116,7 @@ class ProfileMeApiView(APIView): ) serializer.save() - return Response(serializer.data) + return Response(self._get_profile_data(user)) raise ValidationError(serializer.errors) @@ -202,9 +202,9 @@ class FriendsListApiView(ListAPIView): limit = serializer.validated_data.get("limit") offset = serializer.validated_data.get("offset") - return Friendship.objects.filter(from_profile=self.request.user)[ - offset: offset + limit - ] + return Friendship.objects.order_by("-addedAt").filter( + from_profile=self.request.user + )[offset: offset + limit] class PasswordChangeApiView(APIView):