diff --git a/.gitignore b/.gitignore index 4579756..1652d34 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules +web/dist +web/build web/static/ Pip* Pipfile* @@ -31,3 +33,4 @@ modules.txt postgresql-*.tgz db.sqlite3 +*.spec diff --git a/web/actions/choices.py b/web/actions/choices.py index baa7a49..5ea8db6 100644 --- a/web/actions/choices.py +++ b/web/actions/choices.py @@ -1,3 +1,5 @@ +from enum import Enum + from django.db.models import IntegerChoices, TextChoices from django.utils.translation import gettext_lazy as _ @@ -18,9 +20,9 @@ class LikeIconStatus(IntegerChoices): EMPTY = 0 -class FollowIconStatus(TextChoices): - FOLLOW = ('Follow', _('Follow')) - UNFOLLOW = ('Unfollow', _('Unfollow')) +class FollowStatus(Enum): + FOLLOW = True + UNFOLLOW = False class UserActionsChoice(IntegerChoices): diff --git a/web/actions/serializers.py b/web/actions/serializers.py index 9157875..a373676 100644 --- a/web/actions/serializers.py +++ b/web/actions/serializers.py @@ -1,9 +1,7 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from .choices import FollowIconStatus from .models import Action, LikeDislike -from .services import ActionsService User = get_user_model() @@ -14,24 +12,6 @@ class Meta: fields = ('vote', 'user', 'date') -class UserFollowSerializer(serializers.ModelSerializer): - """For list of user following and followers""" - - profile_url = serializers.URLField(source='get_absolute_url') - follow = serializers.SerializerMethodField('get_follow_status') - - def get_follow_status(self, obj) -> str: - user = self.context['request'].user - if user == obj: - return None - is_follow = ActionsService.is_user_followed(user, obj.id) - return FollowIconStatus.UNFOLLOW if is_follow else FollowIconStatus.FOLLOW - - class Meta: - model = User - fields = ('id', 'full_name', 'avatar', 'profile_url', 'follow') - - class ActionListSerializer(serializers.ModelSerializer): class Meta: model = Action diff --git a/web/actions/static/actions/js/follow.js b/web/actions/static/actions/js/follow.js index 8882e67..c37ee4b 100644 --- a/web/actions/static/actions/js/follow.js +++ b/web/actions/static/actions/js/follow.js @@ -3,19 +3,21 @@ $(function () { }); function followMe() { - console.log('click') let button = $(this) let data = { 'user_id': button.data('id') } - $.ajax({ - url: button.data('href'), + url: '/api/v1/actions/follow', type: 'post', data: data, success: function (data) { console.log(data, "success") - button.text(data.status) + button.text(getButtonText(data.status)) } }) } + +function getButtonText(followStatus) { + return followStatus === true ? 'Unfollow' : 'Follow' +} diff --git a/web/api/v1/actions/serializers.py b/web/api/v1/actions/serializers.py index 7739f66..0605d5e 100644 --- a/web/api/v1/actions/serializers.py +++ b/web/api/v1/actions/serializers.py @@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from actions.choices import FollowIconStatus, LikeObjChoice, LikeStatus +from actions.choices import FollowStatus, LikeObjChoice, LikeStatus from api.v1.actions.services import FollowService if TYPE_CHECKING: @@ -30,12 +30,12 @@ class UserFollowSerializer(serializers.ModelSerializer): follow = serializers.SerializerMethodField('get_follow_status') - def get_follow_status(self, obj) -> Optional[FollowIconStatus]: + def get_follow_status(self, obj) -> Optional[FollowStatus]: user = self.context['request'].user if user == obj: return None is_follow = FollowService(user=user, user_id=obj.id).is_user_subscribed() - return FollowIconStatus.UNFOLLOW if is_follow else FollowIconStatus.FOLLOW + return FollowStatus.UNFOLLOW if is_follow else FollowStatus.FOLLOW class Meta: model = User diff --git a/web/api/v1/actions/services.py b/web/api/v1/actions/services.py index eea418b..91089d9 100644 --- a/web/api/v1/actions/services.py +++ b/web/api/v1/actions/services.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import NotFound -from actions.choices import FollowIconStatus, LikeIconStatus, LikeObjChoice, LikeStatus +from actions.choices import FollowStatus, LikeIconStatus, LikeObjChoice, LikeStatus from actions.models import Follower, LikeDislike from blog.models import Article, Comment @@ -99,16 +99,14 @@ def subscribe_to_user(self) -> Follower: def unfollow_user(self): return Follower.objects.filter(subscriber=self.user, to_user_id=self.to_user_id).delete() - def subscribe(self) -> dict: + def subscribe(self) -> bool: if not self.is_user_subscribed(): self.subscribe_to_user() - follow_status = FollowIconStatus.UNFOLLOW + follow_status = FollowStatus.FOLLOW else: self.unfollow_user() - follow_status = FollowIconStatus.FOLLOW - return { - 'status': follow_status, - } + follow_status = FollowStatus.UNFOLLOW + return follow_status.value class FollowersQueryService: diff --git a/web/api/v1/actions/views.py b/web/api/v1/actions/views.py index 6398481..0ee21b4 100644 --- a/web/api/v1/actions/views.py +++ b/web/api/v1/actions/views.py @@ -36,8 +36,8 @@ def post(self, request): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) service = FollowService(user=request.user, user_id=serializer.data['user_id']) - response_data = service.subscribe() - return Response(response_data, status.HTTP_200_OK) + follow_status = service.subscribe() + return Response({'status': follow_status}, status.HTTP_200_OK) class UserFollowersView(ListModelMixin, GenericViewSet): diff --git a/web/api/v1/auth_app/services.py b/web/api/v1/auth_app/services.py index 79c1c45..7f3c9f1 100644 --- a/web/api/v1/auth_app/services.py +++ b/web/api/v1/auth_app/services.py @@ -116,11 +116,11 @@ def user(self) -> User: return self._user def validate(self): - self._user = self._get_user_by_uid_or_exception(self._uid) + self._user = self._get_user_by_uid(self._uid) self._validate_token() @staticmethod - def _get_user_by_uid_or_exception(uid: str) -> User: + def _get_user_by_uid(uid: str) -> User: try: uid = force_str(urlsafe_base64_decode(uid)) return User.objects.get(id=uid) diff --git a/web/api/v1/blog/serializers.py b/web/api/v1/blog/serializers.py index dfad87c..48cb1d5 100644 --- a/web/api/v1/blog/serializers.py +++ b/web/api/v1/blog/serializers.py @@ -59,23 +59,28 @@ class Meta(ArticleSerializer.Meta): fields = ArticleSerializer.Meta.fields + ('votes',) -class CreateArticleSerializer(TaggitSerializer, serializers.ModelSerializer): - tags = TagListSerializerField() +class CreateArticleSerializer(serializers.ModelSerializer): # TaggitSerializer, + # tags = TagListSerializerField() class Meta: model = Article - fields = ('title', 'category', 'image', 'content', 'tags') - - @transaction.atomic() - def create(self, validated_data): - validated_data['author'] = self.context.get('request').user - return super().create(validated_data) + fields = ( + 'title', + 'category', + 'image', + 'content', + ) # 'tags' def validate_title(self, title: str): if BlogService.is_article_slug_exist(title): - raise serializers.ValidationError("This title already exists") + raise serializers.ValidationError('This title already exists') return title + @transaction.atomic() + def create(self, validated_data: dict): + validated_data['author'] = self.context['request'].user + return super().create(validated_data) + class ParentCommentSerializer(serializers.ModelSerializer): user = ShortUserSerializer(read_only=True) diff --git a/web/api/v1/profile/serializers.py b/web/api/v1/profile/serializers.py index bdbde60..1ce71e0 100644 --- a/web/api/v1/profile/serializers.py +++ b/web/api/v1/profile/serializers.py @@ -4,8 +4,6 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from actions.choices import FollowIconStatus -from api.v1.actions.services import FollowService User = get_user_model() @@ -88,12 +86,7 @@ def update(self, instance: User, validated_data: dict): class UserListSerializer(serializers.ModelSerializer): - follow = serializers.SerializerMethodField('get_follow_status') - - def get_follow_status(self, obj) -> str: - user = self.context['request'].user - is_follow = FollowService(user, obj.id).is_user_subscribed() - return FollowIconStatus.UNFOLLOW if is_follow else FollowIconStatus.FOLLOW + follow = serializers.BooleanField() class Meta: model = User diff --git a/web/api/v1/profile/services.py b/web/api/v1/profile/services.py index 11b99e3..f5fccd6 100644 --- a/web/api/v1/profile/services.py +++ b/web/api/v1/profile/services.py @@ -1,8 +1,9 @@ from typing import TYPE_CHECKING, Optional from django.contrib.auth import get_user_model -from django.db.models import Count, Q +from django.db.models import Count, Exists, OuterRef, Q +from actions.models import Follower from blog.choices import ArticleStatus from main.decorators import except_shell @@ -33,6 +34,11 @@ def user_profile_queryset(self) -> 'QuerySet[User]': user_likes = Count('likes') return self.get_queryset(is_active=True).annotate(user_posts=user_articles, user_likes=user_likes) + def user_list_queryset(self, current_user: User) -> 'QuerySet[User]': + return self.user_profile_queryset().annotate( + follow=Exists(Follower.objects.filter(subscriber=current_user, to_user_id=OuterRef('pk'))) + ) + @except_shell((User.DoesNotExist,), raise_404=True) def get_user_profile(self, user_id: int) -> User: return self.user_profile_queryset().get(id=user_id) diff --git a/web/api/v1/profile/views.py b/web/api/v1/profile/views.py index 84c2644..8870a9b 100644 --- a/web/api/v1/profile/views.py +++ b/web/api/v1/profile/views.py @@ -63,4 +63,4 @@ class UserListView(ListAPIView): serializer_class = serializers.UserListSerializer def get_queryset(self): - return UserQueryService.get_queryset(is_active=True) + return UserQueryService().user_list_queryset(current_user=self.request.user) diff --git a/web/blog/urls.py b/web/blog/urls.py index 36a14a2..90120b6 100644 --- a/web/blog/urls.py +++ b/web/blog/urls.py @@ -1,19 +1,12 @@ from django.urls import path -from rest_framework.routers import DefaultRouter from . import views from main.views import TemplateAPIView app_name = 'blog' -router = DefaultRouter() -router.register('comment', views.CommentViewSet, basename='comment') - urlpatterns = [ path('blog/', TemplateAPIView.as_view(template_name='blog/post_list.html'), name='blog-list'), path('blog/', TemplateAPIView.as_view(template_name='blog/post_detail.html'), name='blog-detail'), - path('comment//', views.CommentViewSet.as_view({'get': 'list'}), name='article_comments'), path('posts/new/', views.CreateArticleTemplateView.as_view(), name='new_post'), ] - -urlpatterns += router.urls diff --git a/web/blog/views.py b/web/blog/views.py index 4d06b7d..db37a25 100644 --- a/web/blog/views.py +++ b/web/blog/views.py @@ -22,47 +22,6 @@ class ViewSet(ModelViewSet): pagination_class = BasePageNumberPagination -class CommentViewSet(mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericViewSet): - permission_classes = (AllowAny,) - http_method_names = ('get', 'post', 'put', 'delete') - pagination_class = BasePageNumberPagination - - def get_serializer_class(self): - if self.action == 'update' or self.action == 'destroy': - return serializers.UpdateDestroyCommentSerializer - return serializers.CommentSerializer - - def get_queryset(self): - return BlogService.get_comments_queryset() - - def get_object(self): - return BlogService.get_article_comments(article_id=self.kwargs.get('article_id')) - - def list(self, request, article_id): - queryset = self.filter_queryset(self.get_object()) - - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) - - def update(self, request, **kwargs): - serializer = self.get_serializer(self.get_object(), data=request.data) - serializer.is_valid(raise_exception=True) - serializer.save() - return Response(serializer.data) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data) - serializer.is_valid(raise_exception=True) - instance.delete() - return Response(status=status.HTTP_204_NO_CONTENT) - - class CreateArticleTemplateView(TemplateAPIView): template_name = 'blog/post_create.html' diff --git a/web/user_profile/serializers.py b/web/user_profile/serializers.py index e17ea92..b95fb17 100644 --- a/web/user_profile/serializers.py +++ b/web/user_profile/serializers.py @@ -1,9 +1,6 @@ from django.contrib.auth import get_user_model from rest_framework import serializers -from actions.choices import FollowIconStatus -from api.v1.actions.services import FollowService - from main.models import GenderChoice User = get_user_model() @@ -43,16 +40,3 @@ class UpdateUserProfileSerializer(serializers.ModelSerializer): class Meta: model = User fields = ('id', 'first_name', 'last_name', 'birthday', 'gender') - - -class UserListSerializer(serializers.ModelSerializer): - follow = serializers.SerializerMethodField('get_follow_status') - - def get_follow_status(self, obj) -> str: - user = self.context['request'].user - is_follow = FollowService(user, obj.id).is_user_subscribed() - return FollowIconStatus.UNFOLLOW if is_follow else FollowIconStatus.FOLLOW - - class Meta: - model = User - fields = ('id', 'full_name', 'avatar', 'follow') diff --git a/web/user_profile/static/user_profile/js/user_list.js b/web/user_profile/static/user_profile/js/user_list.js new file mode 100644 index 0000000..6b4fd1a --- /dev/null +++ b/web/user_profile/static/user_profile/js/user_list.js @@ -0,0 +1,40 @@ +$(function () { + userList() +}); + + +function userList() { + $.ajax({ + type: 'GET', + url: '/api/v1/user/', + success: userListHandler, + }) +} + +function userListTemplate(user) { + const followButtonText = getButtonText(user.follow) + return ` +
  • +
    + Avatar +
    + ${followButtonText} +

    + ${ user.full_name} +

    + RG: 99384877 +
    +
    +
  • + ` +} + +function userListHandler(data) { + const userList = $('#userList'); + const result = data.results.map((user) => userListTemplate(user)).join(''); + userList.empty(); + userList.append(result); + $(".followMe").click(followMe); +} diff --git a/web/user_profile/templates/user_profile/user_list.html b/web/user_profile/templates/user_profile/user_list.html index 85c6709..4ee3067 100644 --- a/web/user_profile/templates/user_profile/user_list.html +++ b/web/user_profile/templates/user_profile/user_list.html @@ -4,24 +4,7 @@ {% block container %}
    -
    @@ -29,4 +12,5 @@

    {% block jquery %} $.getScript('{% static 'actions/js/follow.js' %}'); +$.getScript('{% static 'user_profile/js/user_list.js' %}'); {% endblock %} diff --git a/web/user_profile/urls.py b/web/user_profile/urls.py index 450d8f4..47320a4 100644 --- a/web/user_profile/urls.py +++ b/web/user_profile/urls.py @@ -12,7 +12,7 @@ urlpatterns = [ path('profile/', profile, name='profile'), - path('user/list/', views.UserListView.as_view(), name='user_list'), + path('user/list/', TemplateAPIView.as_view(template_name='user_profile/user_list.html'), name='user_list'), path('user//', views.UserProfileByIdView.as_view(), name='user_by_id'), ] diff --git a/web/user_profile/views.py b/web/user_profile/views.py index 080d9ab..4e517d1 100644 --- a/web/user_profile/views.py +++ b/web/user_profile/views.py @@ -46,20 +46,6 @@ def update(self, request): return Response(serializer.data, status=status.HTTP_200_OK) -class UserListView(GenericAPIView): - serializer_class = serializers.UserListSerializer - template_name = 'user_profile/user_list.html' - renderer_classes = (JSONRenderer, TemplateHTMLRenderer) - - def get_queryset(self): - return UserProfileService.user_queryset().exclude(id=self.request.user.id) - - def get(self, request): - serializer = self.get_serializer(self.get_queryset(), many=True) - data = {'user_list': serializer.data} - return Response(data, status=status.HTTP_200_OK, template_name=self.template_name) - - class UserProfileByIdView(GenericAPIView): serializer_class = serializers.UserSerializer template_name = 'user_profile/profile_read_only.html'