Notification api lari chiqarildi
This commit is contained in:
@@ -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__",
|
||||||
|
)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
1
core/apps/api/serializers/notification/__init__.py
Normal file
1
core/apps/api/serializers/notification/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .natification import * # noqa
|
||||||
49
core/apps/api/serializers/notification/natification.py
Normal file
49
core/apps/api/serializers/notification/natification.py
Normal 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"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -1 +1,2 @@
|
|||||||
from .test_user_like import * # noqa
|
from .test_user_like import * # noqa
|
||||||
|
from .test_user_notification import * # noqa
|
||||||
|
|||||||
@@ -46,11 +46,10 @@ def test_list(data):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_create(data,ad):
|
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
|
||||||
|
|
||||||
|
|||||||
78
core/apps/api/tests/user/test_user_notification.py
Normal file
78
core/apps/api/tests/user/test_user_notification.py
Normal 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
|
||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
1
core/apps/api/views/notification/__init__.py
Normal file
1
core/apps/api/views/notification/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .notification import * # noqa
|
||||||
54
core/apps/api/views/notification/notification.py
Normal file
54
core/apps/api/views/notification/notification.py
Normal 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)
|
||||||
Reference in New Issue
Block a user