Skip to content

Commit

Permalink
Нотификации, эндпоинты Users
Browse files Browse the repository at this point in the history
  • Loading branch information
Reagent992 committed Jan 26, 2024
1 parent 6b7d8a5 commit ba0f150
Show file tree
Hide file tree
Showing 12 changed files with 244 additions and 46 deletions.
1 change: 0 additions & 1 deletion .envexample
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ USE_POSTGRESQL=False
DB_NAME=IPR
DB_HOST=db
DB_PORT=5432
USE_POSTGRESQL=True
POSTGRES_DB=ipr
POSTGRES_USER=ipr_user
POSTGRES_PASSWORD=ipr_password
17 changes: 17 additions & 0 deletions api/v1/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.contrib.auth import get_user_model
from django_filters.rest_framework import BooleanFilter, FilterSet

User = get_user_model()


class CustomFilter(FilterSet):
no_ipr = BooleanFilter(method="filter_no_ipr", label="No IPR")

class Meta:
model = User
fields = ("team",)

def filter_no_ipr(self, queryset, name, value):
if value:
return queryset.filter(ipr=None)
return queryset
21 changes: 18 additions & 3 deletions api/v1/serializers/api/notifications_serializer.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
from notifications.models import Notification
from rest_framework import serializers

from api.v1.serializers.api.users_serializer import CustomUserSerializer


class NotificationSerializer(serializers.ModelSerializer):
"""Сериализатор уведомлений."""

recipient = CustomUserSerializer(read_only=True, many=False)
actor = CustomUserSerializer(read_only=True, many=False)

class Meta:
model = Notification
fields = (
"id",
"recipient_id",
"actor_object_id",
"verb",
"unread",
"timestamp",
# "actor_content_type_model",
"recipient",
"actor",
)

# TODO: Добавить ссылку на объект уведомления.
# url = reverse('your_nested_detail_view_name',
# kwargs={'content_type': contentType, 'pk': objectId})
# >>> reverse("users-detail", args=[user.id])
# >>> '/api/v1/users/5/'
# request.build_absolute_uri('/some-relative-path/')
# т.е. всего 3 варианта, можно if-ами пройти.
# if isinstance(notification.target, IPR):
19 changes: 17 additions & 2 deletions api/v1/serializers/api/users_serializer.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
from django.contrib.auth import get_user_model
from djoser.serializers import UserSerializer
from rest_framework import serializers
from rest_framework.serializers import ModelSerializer

from users.models import User
from users.models import Position

User = get_user_model()


class PositionsSerializer(ModelSerializer):
"""Сериализатор должностей."""

class Meta:
model = Position
fields = ("name",)


class CustomUserSerializer(UserSerializer):
"""Сериализатор пользователей."""

position = serializers.CharField(source="position.name", read_only=True)

class Meta:
model = User
fields = (
Expand All @@ -20,5 +35,5 @@ class Meta:
"date_joined",
"last_login",
"userpic",
"get_team_id",
"team",
)
5 changes: 5 additions & 0 deletions api/v1/serializers/internal/dummy_serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from rest_framework import serializers


class DummySerializer(serializers.Serializer):
pass
42 changes: 34 additions & 8 deletions api/v1/views/notifications_view.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from notifications.models import Notification
from rest_framework import viewsets
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response

from api.v1.serializers.api.notifications_serializer import (
NotificationSerializer,
)
from api.v1.serializers.internal.dummy_serializer import DummySerializer


@extend_schema(tags=["Уведомления"])
@extend_schema_view(
list=extend_schema(
summary="Получить список уведомлений",
summary="Список уведомлений",
description=(
"Список уведомлений, "
"уведомления именно для пользователя делающего запрос."
),
),
retrieve=extend_schema(
summary="Получить уведомление",
summary="Уведомление",
),
partial_update=extend_schema(
summary="Отметить уведомление как прочитанное",
Expand All @@ -25,14 +28,37 @@
"нужно изменить значение unread."
),
),
mark_all_as_read=extend_schema(
methods=["get"],
summary="Отметить все уведомления пользователя как прочтенные",
responses={status.HTTP_200_OK: DummySerializer},
),
)
class NotificationViewSet(viewsets.ModelViewSet):
serializer_class = NotificationSerializer
http_method_names = ["get", "patch", "head", "options"]
queryset = Notification.objects.all()
# TODO: Отчистка уведомлений.

def partial_update(self, request, pk):
"""Отметить уведомление как прочитанное."""
notification = self.get_object()
if request.user != notification.recipient:
return Response(
{"message": "Уведомление не принадлежит пользователю."},
status=status.HTTP_403_FORBIDDEN,
)
notification.mark_as_read()
serializer = self.get_serializer(notification)
return Response(serializer.data)

def get_queryset(self):
"""Персонализированная выдача списка уведомлений."""
user = self.request.user
return Notification.objects.filter(recipient=user)
return self.request.user.notifications.unread()

@action(methods=["get"], detail=False)
def mark_all_as_read(self, request):
"""Отметить все уведомления пользователя как прочтенные."""
request.user.notifications.mark_all_as_read()
return Response(
{"message": "Уведомления отмечены как прочтенные."},
status=status.HTTP_200_OK,
)
88 changes: 81 additions & 7 deletions api/v1/views/users_view.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,90 @@
from django.conf import settings
from django.db.models import CharField, ExpressionWrapper, F, Value
from django_filters.rest_framework import DjangoFilterBackend
from djoser.views import UserViewSet as UserViewSetFromDjoser
from drf_spectacular.utils import extend_schema
from rest_framework.pagination import PageNumberPagination
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import filters
from rest_framework.decorators import action
from rest_framework.filters import OrderingFilter
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response

from api.v1.serializers.api.users_serializer import CustomUserSerializer
from api.v1.filters import CustomFilter
from api.v1.serializers.api.users_serializer import (
CustomUserSerializer,
PositionsSerializer,
)
from users.models import Position


@extend_schema(
responses=CustomUserSerializer,
description="Пользователи.",
@extend_schema(tags=["Пользователи"])
@extend_schema_view(
list=extend_schema(
summary=("Список пользователей."),
description=(
"<ul><h3>Поддерживается:</h3><li>Сортировка по имени "
"<code>./?ordering=full_name</code> "
"и должности <code>./?ordering=-position_name</code></li>"
"<li>Поиск по ФИО и должности <code>./?search=Мирон</code></li>"
"<li>Ограничение pagination <code>./?limit=5</code>.</li>"
"<li>Фильтр по id команды <code>./?team=1</code></li>"
"<li>Фильтр по id команды и отсутствию ИПР "
"<code>./?team=1&no_ipr=true</code></li></ul>"
),
),
retrieve=extend_schema(summary="Профиль пользователя"),
me=extend_schema(summary="Текущий пользователь"),
)
class UserViewSet(UserViewSetFromDjoser):
"""Пользователи."""

filterset_class = CustomFilter
filter_backends = (
DjangoFilterBackend,
filters.SearchFilter,
OrderingFilter,
)
if settings.USE_POSTGRESQL: # TODO: Проверить.
search_fields = (
"@last_name",
"@first_name",
"@patronymic",
"@position__name",
)
else:
search_fields = (
"last_name",
"first_name",
"patronymic",
"position__name",
)
serializer_class = CustomUserSerializer
pagination_class = PageNumberPagination
pagination_class = LimitOffsetPagination
http_method_names = ["get", "head", "options"]
ordering_fields = ("full_name", "position_name")

def get_queryset(self):
queryset = super().get_queryset()

return queryset.annotate(
full_name=ExpressionWrapper(
F("last_name") + F("first_name") + F("patronymic"),
output_field=CharField(),
),
position_name=ExpressionWrapper(
F("position__name") if F("position__name") else Value(""),
output_field=CharField(),
),
)

@extend_schema(
summary="Список должностей",
)
@action(
methods=["get"], detail=False, serializer_class=PositionsSerializer
)
def positions(self, request):
"""Список должностей."""
queryset = Position.objects.all()
serializer = PositionsSerializer(queryset, many=True)
return Response(serializer.data)
14 changes: 5 additions & 9 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,40 +123,35 @@

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# -------------------------------------------------------------CUSTOM SETTINGS

MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")

AUTH_USER_MODEL = "users.User"
# -----------------------------------------------------------------DRF SETTINGS

REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
"PAGE_SIZE": 6,
"PAGE_SIZE": 10,
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

# Выключено предупреждение об отсутствие DEFAULT_PAGINATION_CLASS
SILENCED_SYSTEM_CHECKS = ["rest_framework.W001"]
# Время жизни токена увеличено, для упрощения тестирования.
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(days=1),
"AUTH_HEADER_TYPES": ("Bearer",),
}

# --------------------------------------------------------------DJOSER SETTINGS

DJOSER = {
"LOGIN_FIELD": "email",
# TODO: "PERMISSIONS": {},
"SERIALIZERS": {
"current_user": "api.v1.serializers.api.users_serializer.CustomUserSerializer",
},
"HIDE_USERS": False,
}

# -------------------------------------------------------------ALTER USER MODEL
SPECTACULAR_SETTINGS = {
"TITLE": "IPR API",
Expand All @@ -166,6 +161,7 @@
),
"VERSION": "0.1.0",
"SCHEMA_PATH_PREFIX": "/api/v1/",
"SERVE_INCLUDE_SCHEMA": False,
}
# --------------------------------------------------------------------CONSTANTS
EMAIL_LENGTH = 254
Expand Down
33 changes: 28 additions & 5 deletions core/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,20 @@ def created_ipr_notification(sender, instance: IPR, created, **kwargs):
"""Создание уведомления о новом ИПР."""
if created:
text = "Вам назначен новый ИПР"
notify.send(sender=instance, recipient=instance.executor, verb=text)
notify.send(
sender=instance.creator,
recipient=instance.executor,
verb=text,
target=instance,
)
else:
text = "Изменение в ИПР."
notify.send(sender=instance, recipient=instance.executor, verb=text)
notify.send(
sender=instance.creator,
recipient=instance.executor,
verb=text,
target=instance,
)


@receiver(post_save, sender=Task)
Expand All @@ -24,10 +34,20 @@ def created_task_notification(sender, instance: Task, created, **kwargs):

if created:
text = "Вам добавлена новая задача"
notify.send(sender=instance, recipient=instance.executor, verb=text)
notify.send(
sender=instance.creator,
recipient=instance.executor,
verb=text,
target=instance,
)
else:
text = "Изменение в задаче."
notify.send(sender=instance, recipient=instance.executor, verb=text)
notify.send(
sender=instance.creator,
recipient=instance.executor,
verb=text,
target=instance,
)


@receiver(post_save, sender=Comment)
Expand All @@ -36,5 +56,8 @@ def created_comment_notification(sender, instance: Comment, created, **kwargs):
if created:
text = "Новый комментарий"
notify.send(
sender=instance, recipient=instance.task.executor, verb=text
sender=instance.author,
recipient=instance.ipr.executor,
verb=text,
target=instance,
)
Loading

0 comments on commit ba0f150

Please sign in to comment.