Skip to content

Commit

Permalink
Chore: ( Documented api)
Browse files Browse the repository at this point in the history
  • Loading branch information
Okemwag committed Jun 13, 2024
1 parent 12f1a2c commit 7b216d5
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 52 deletions.
31 changes: 0 additions & 31 deletions apps/posts/mixins.py

This file was deleted.

35 changes: 35 additions & 0 deletions apps/posts/selectors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Optional
from django.db.models import QuerySet, Q
from .models import Post, LikePost


def get_all_posts() -> QuerySet[Post]:
return Post.objects.all()


def get_user_posts(user_id: int) -> QuerySet[Post]:
return Post.objects.filter(author_id=user_id)


def search_posts(query: str) -> QuerySet[Post]:
return Post.objects.filter(
Q(caption__icontains=query) | Q(author__first_name__icontains(query))
)


def get_trending_posts(limit: int = 5) -> QuerySet[Post]:
return Post.objects.order_by("-date_posted")[:limit]


def get_post_by_id(post_id: int) -> Optional[Post]:
try:
return Post.objects.get(id=post_id)
except Post.DoesNotExist:
return None


def get_like_for_post(user_id: int, post_id: int) -> Optional[LikePost]:
try:
return LikePost.objects.get(user_id=user_id, post_id=post_id)
except LikePost.DoesNotExist:
return None
13 changes: 11 additions & 2 deletions apps/posts/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from rest_framework import routers
from .views import PostViewSet
from .views import PostViewSet, LikePostViewSet


app_name = 'apps.posts'

router = routers.DefaultRouter()
router.register(r'posts', PostViewSet)
# router.register(r"posts", PostViewSet)
# router.register(r"likes", LikePostViewSet)

router.register("posts", PostViewSet, basename="posts")
router.register("likes", LikePostViewSet, basename="likes")


urlpatterns = router.urls
178 changes: 178 additions & 0 deletions apps/posts/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
from rest_framework import viewsets, status, filters
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework.decorators import action
from django_filters.rest_framework import DjangoFilterBackend
from django.core.exceptions import ObjectDoesNotExist
import logging
from typing import Any, Dict

from .models import LikePost
from .serializers import PostSerializer, LikePostSerializer
from .exceptions import PostDoesNotExist
from .selectors import (
get_all_posts,
get_user_posts,
search_posts,
get_trending_posts,

)

logger = logging.getLogger(__name__)


class PostViewSet(viewsets.ModelViewSet):
queryset = get_all_posts()
serializer_class = PostSerializer
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ["author"]
search_fields = ["caption"]

def create(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
data: Dict[str, Any] = request.data
data["author"] = request.user.id
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(f"Error creating post: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

@action(detail=True, methods=["post"], permission_classes=[IsAuthenticated])
def like(self, request: Any, pk: int = None) -> Response:
try:
post = self.get_object()
like, created = LikePost.objects.get_or_create(user=request.user, post=post)
if not created:
like.delete()
return Response({"message": "Unliked"}, status=status.HTTP_200_OK)
return Response({"message": "Liked"}, status=status.HTTP_201_CREATED)
except ObjectDoesNotExist:
logger.error(f"Post with id {pk} does not exist")
raise PostDoesNotExist()
except Exception as e:
logger.error(f"Error liking post: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

@action(detail=False, methods=["get"], permission_classes=[IsAuthenticated])
def my_posts(self, request: Any) -> Response:
try:
queryset = get_user_posts(request.user.id)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Error retrieving my posts: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

@action(detail=False, methods=["get"], permission_classes=[AllowAny])
def search(self, request: Any) -> Response:
try:
query: str = request.query_params.get("q", "")
queryset = search_posts(query)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Error searching posts: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

@action(detail=False, methods=["get"], permission_classes=[AllowAny])
def trending(self, request: Any) -> Response:
try:
queryset = get_trending_posts()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Error retrieving trending posts: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)


class LikePostViewSet(viewsets.ModelViewSet):
queryset = LikePost.objects.all()
serializer_class = LikePostSerializer
permission_classes = [IsAuthenticated]
filter_backends = [DjangoFilterBackend]
filterset_fields = ["user", "post"]

def create(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
data: Dict[str, Any] = request.data
data["user"] = request.user.id
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(f"Error creating like: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

def destroy(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
instance = self.get_object()
if instance.user == request.user:
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
return Response(
{"message": "You are not allowed to delete this"},
status=status.HTTP_403_FORBIDDEN,
)
except ObjectDoesNotExist:
logger.error(f"Like with id {kwargs['pk']} does not exist")
return Response(
{"message": "Like does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
logger.error(f"Error deleting like: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

def list(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
queryset = self.queryset.filter(user=request.user)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"Error listing likes: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

def retrieve(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
instance = self.get_object()
if instance.user == request.user:
serializer = self.get_serializer(instance)
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"message": "You are not allowed to view this"},
status=status.HTTP_403_FORBIDDEN,
)
except ObjectDoesNotExist:
logger.error(f"Like with id {kwargs['pk']} does not exist")
return Response(
{"message": "Like does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
logger.error(f"Error retrieving like: {e}")
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)

def update(self, request: Any, *args: Any, **kwargs: Any) -> Response:
try:
instance = self.get_object()
if instance.user == request.user:
serializer = self.get_serializer(
instance, data=request.data, partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(
{"message": "You are not allowed to update this"},
status=status.HTTP_403_FORBIDDEN,
)
except ObjectDoesNotExist:
logger.error(f"Like with id {kwargs['pk']} does not exist")
return Response(
{"message": "Like does not exist"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e:
logger
3 changes: 2 additions & 1 deletion config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,8 @@

MEDIA_FILE_MAX_AGE = 90

MEDIA_URL = BASE_DIR
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"


# Logging configuration
Expand Down
52 changes: 34 additions & 18 deletions config/urls.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
"""
URL configuration for config project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

...

schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
default_version="v1",
description="Test description",
terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="[email protected]"),
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
path('admin/', admin.site.urls),
path("admin/", admin.site.urls),
path(
"swagger<format>/", schema_view.without_ui(cache_timeout=0), name="schema-json"
),
path(
"swagger/",
schema_view.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
path("api/v1/", include("apps.posts.urls")),
]

admin.site.site_header = "Sharehub Admin"
admin.site.site_title = "Sharehub Admin"
admin.site.index_title = "Sharehub Admin"

0 comments on commit 7b216d5

Please sign in to comment.