Notification api lari chiqarildi

This commit is contained in:
2025-11-27 00:40:27 +05:00
parent 900f23e5f6
commit e8e900c393
14 changed files with 265 additions and 4 deletions

View File

@@ -1,7 +1,7 @@
from django.contrib import admin from django.contrib import admin
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from core.apps.accounts.models import SearchHistory, UserLike from core.apps.accounts.models import SearchHistory, UserLike, UserNotification, Notification
@admin.register(SearchHistory) @admin.register(SearchHistory)
@@ -18,3 +18,19 @@ class UserLikeAdmin(ModelAdmin):
"id", "id",
"__str__", "__str__",
) )
@admin.register(UserNotification)
class UserNotificationAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
)
@admin.register(Notification)
class NotificationAdmin(ModelAdmin):
list_display = (
"id",
"__str__",
)

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.2.7 on 2025-11-26 12:24
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_rename_address_name_business_address_and_more'),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('title', models.CharField(max_length=255, verbose_name='Title')),
('description', models.TextField(verbose_name='Description')),
('notification_type', models.CharField(choices=[('System', 'System'), ('Another', 'Another')], max_length=255, verbose_name='Type')),
('long', models.FloatField(verbose_name='Long')),
('lat', models.FloatField(verbose_name='Lat')),
],
options={
'verbose_name': 'Notification',
'verbose_name_plural': 'Notifications',
'db_table': 'notification',
},
),
migrations.CreateModel(
name='UserNotification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_read', models.BooleanField(default=False, verbose_name='Read')),
('notification', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.notification', verbose_name='Notification')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'User Notification',
'verbose_name_plural': 'User Notifications',
'db_table': 'user_notification',
},
),
]

View File

@@ -5,3 +5,4 @@ from .address import * # noqa
from .business import * # noqa from .business import * # noqa
from .user_like import * # noqa from .user_like import * # noqa
from .search_history import * # noqa from .search_history import * # noqa
from .notification import * # noqa

View File

@@ -3,6 +3,7 @@ from django_core.models.base import AbstractBaseModel
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.apps.accounts.choices import NotificationType from core.apps.accounts.choices import NotificationType
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from model_bakery import baker
class Notification(AbstractBaseModel): class Notification(AbstractBaseModel):
@@ -12,6 +13,10 @@ class Notification(AbstractBaseModel):
long = models.FloatField(verbose_name=_("Long")) long = models.FloatField(verbose_name=_("Long"))
lat = models.FloatField(verbose_name=_("Lat")) lat = models.FloatField(verbose_name=_("Lat"))
@classmethod
def _baker(cls):
return baker.make(cls)
def __str__(self): def __str__(self):
return str(self.pk) return str(self.pk)
@@ -26,6 +31,10 @@ class UserNotification(AbstractBaseModel):
notification = models.ForeignKey(Notification, verbose_name=_("Notification"), on_delete=models.CASCADE) notification = models.ForeignKey(Notification, verbose_name=_("Notification"), on_delete=models.CASCADE)
is_read = models.BooleanField(verbose_name=_("Read"), default=False) is_read = models.BooleanField(verbose_name=_("Read"), default=False)
@classmethod
def _baker(cls):
return baker.make(cls)
def __str__(self): def __str__(self):
return str(self.pk) return str(self.pk)

View File

@@ -2,3 +2,4 @@ from .category import * # noqa
from .search import * # noqa from .search import * # noqa
from .ad import * # noqa from .ad import * # noqa
from .user import * # noqa from .user import * # noqa
from .notification import * # noqa

View File

@@ -0,0 +1 @@
from .natification import * # noqa

View File

@@ -0,0 +1,49 @@
from rest_framework import serializers
from core.apps.accounts.models import UserNotification, Notification
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
"id",
"title",
"description",
"long",
"lat"
]
class BaseUserNotificationSerializer(serializers.ModelSerializer):
notification = NotificationSerializer(many=False, read_only=True)
class Meta:
model = UserNotification
fields = [
"id",
"is_read",
"notification",
"created_at",
]
class ListUserNotificationSerializer(BaseUserNotificationSerializer):
class Meta(BaseUserNotificationSerializer.Meta): ...
class RetrieveUserNotificationSerializer(BaseUserNotificationSerializer):
class Meta(BaseUserNotificationSerializer.Meta): ...
class CreateUserNotificationSerializer(BaseUserNotificationSerializer):
class Meta(BaseUserNotificationSerializer.Meta): ...
class UpdateUserNotificationSerializer(BaseUserNotificationSerializer):
class Meta(BaseUserNotificationSerializer.Meta):
fields = [
"is_read"
]

View File

@@ -1 +1,2 @@
from .test_user_like import * # noqa from .test_user_like import * # noqa
from .test_user_notification import * # noqa

View File

@@ -50,7 +50,6 @@ def test_create(data,ad):
urls, client, instance = data urls, client, instance = data
response = client.post(urls["list"], data={"ad": ad.pk}) response = client.post(urls["list"], data={"ad": ad.pk})
data_resp = response.json() data_resp = response.json()
print(data_resp)
assert response.status_code == 201 assert response.status_code == 201
assert data_resp["status"] is True assert data_resp["status"] is True

View File

@@ -0,0 +1,78 @@
import pytest
from django.urls import reverse
from rest_framework.test import APIClient
from core.apps.accounts.models import UserNotification
@pytest.fixture
def instance(db):
return UserNotification._baker()
@pytest.fixture
def api_client(instance):
client = APIClient()
client.force_authenticate(user=instance.user)
return client, instance
@pytest.fixture
def data(api_client):
client, instance = api_client
return (
{
"list": reverse("notification-list"),
"retrieve": reverse("notification-detail", kwargs={"pk": instance.pk}),
"retrieve-not-found": reverse("notification-detail", kwargs={"pk": 1000}),
"notification-read": reverse("notification-notification-read", kwargs={"pk": instance.pk}),
"all-read": reverse("notification-all-read"),
},
client,
instance,
)
@pytest.mark.django_db
def test_list(data):
urls, client, _ = data
response = client.get(urls["list"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve(data):
urls, client, _ = data
response = client.get(urls["retrieve"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_retrieve_not_found(data):
urls, client, _ = data
response = client.get(urls["retrieve-not-found"])
data_resp = response.json()
assert response.status_code == 404
assert data_resp["status"] is False
@pytest.mark.django_db
def test_notification_reads(data):
urls, client, _ = data
response = client.post(urls["notification-read"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True
@pytest.mark.django_db
def test_all_read(data):
urls, client, _ = data
response = client.post(urls["all-read"])
data_resp = response.json()
assert response.status_code == 200
assert data_resp["status"] is True

View File

@@ -2,9 +2,10 @@ from django.urls import include, path
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from core.apps.api.views import CategoryHomeApiViewSet, CategoryViewSet, HomeAdApiView, SearchHistoryViewSet, \ from core.apps.api.views import CategoryHomeApiViewSet, CategoryViewSet, HomeAdApiView, SearchHistoryViewSet, \
UserLikeViewSet UserLikeViewSet, NotificationViewSet
router = DefaultRouter() router = DefaultRouter()
router.register("notification", NotificationViewSet, basename="notification")
router.register("user-like", UserLikeViewSet, basename="user-like") router.register("user-like", UserLikeViewSet, basename="user-like")
router.register("category", CategoryViewSet, basename="category") router.register("category", CategoryViewSet, basename="category")
router.register("category-home", CategoryHomeApiViewSet, basename="category-home") router.register("category-home", CategoryHomeApiViewSet, basename="category-home")

View File

@@ -2,3 +2,4 @@ from .category import * # noqa
from .search import * # noqa from .search import * # noqa
from .ad import * # noqa from .ad import * # noqa
from .user import * # noqa from .user import * # noqa
from .notification import * # noqa

View File

@@ -0,0 +1 @@
from .notification import * # noqa

View File

@@ -0,0 +1,54 @@
from xmlrpc.client import Fault
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ReadOnlyModelViewSet
from django_core.mixins.base import BaseViewSetMixin
from drf_spectacular.utils import extend_schema
from core.apps.accounts.models import UserNotification
from rest_framework.decorators import action
from django_filters.rest_framework import DjangoFilterBackend
from django.utils.translation import gettext_lazy as _
from core.apps.api.serializers.notification import (
ListUserNotificationSerializer,
CreateUserNotificationSerializer,
RetrieveUserNotificationSerializer,
UpdateUserNotificationSerializer
)
@extend_schema(tags=["Notification"])
class NotificationViewSet(BaseViewSetMixin, ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated]
serializer_class = ListUserNotificationSerializer
filter_backends = [DjangoFilterBackend]
action_permission_classes = {}
action_serializer_class = {
"list": ListUserNotificationSerializer,
"retrieve": RetrieveUserNotificationSerializer,
"create": CreateUserNotificationSerializer,
}
def get_queryset(self):
qs = UserNotification.objects.filter(user=self.request.user).order_by("-created_at")
return qs
@action(detail=True, methods=["post"])
def notification_read(self, request, pk=None):
notification = self.get_object()
serializer = UpdateUserNotificationSerializer(
notification,
data={"is_read": True},
partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data)
@action(detail=False, methods=["post"])
def all_read(self, request):
user = request.user
UserNotification.objects.filter(user=user, is_read=False).update(is_read=True)
return Response({"detail": _("Barcha xabarlar o'qilgan qilindi!")}, status=status.HTTP_200_OK)