notification-kit 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notification_kit-1.0.0/PKG-INFO +132 -0
- notification_kit-1.0.0/README.md +105 -0
- notification_kit-1.0.0/notification_kit/__init__.py +5 -0
- notification_kit-1.0.0/notification_kit/admin.py +137 -0
- notification_kit-1.0.0/notification_kit/api/__init__.py +0 -0
- notification_kit-1.0.0/notification_kit/api/permissions.py +16 -0
- notification_kit-1.0.0/notification_kit/api/serializers.py +12 -0
- notification_kit-1.0.0/notification_kit/api/urls.py +16 -0
- notification_kit-1.0.0/notification_kit/api/views.py +85 -0
- notification_kit-1.0.0/notification_kit/apps.py +7 -0
- notification_kit-1.0.0/notification_kit/backends/__init__.py +0 -0
- notification_kit-1.0.0/notification_kit/backends/base.py +136 -0
- notification_kit-1.0.0/notification_kit/backends/custom.py +21 -0
- notification_kit-1.0.0/notification_kit/backends/email.py +102 -0
- notification_kit-1.0.0/notification_kit/backends/push.py +51 -0
- notification_kit-1.0.0/notification_kit/backends/sms.py +45 -0
- notification_kit-1.0.0/notification_kit/exceptions.py +168 -0
- notification_kit-1.0.0/notification_kit/middleware.py +29 -0
- notification_kit-1.0.0/notification_kit/models/__init__.py +26 -0
- notification_kit-1.0.0/notification_kit/models/abstract.py +287 -0
- notification_kit-1.0.0/notification_kit/models/concrete.py +59 -0
- notification_kit-1.0.0/notification_kit/models/mixins.py +86 -0
- notification_kit-1.0.0/notification_kit/services/__init__.py +0 -0
- notification_kit-1.0.0/notification_kit/services/base.py +97 -0
- notification_kit-1.0.0/notification_kit/services/custom_service.py +19 -0
- notification_kit-1.0.0/notification_kit/settings.py +218 -0
- notification_kit-1.0.0/notification_kit/signals.py +118 -0
- notification_kit-1.0.0/notification_kit/tasks/__init__.py +0 -0
- notification_kit-1.0.0/notification_kit/tasks/base.py +138 -0
- notification_kit-1.0.0/notification_kit/utils/__init__.py +0 -0
- notification_kit-1.0.0/notification_kit/utils/template.py +60 -0
- notification_kit-1.0.0/notification_kit/utils/validators.py +61 -0
- notification_kit-1.0.0/pyproject.toml +47 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: notification-kit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Reusable, extensible Django notification framework
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: django,notifications,email,sms,push,celery,django-app
|
|
7
|
+
Author: Sercan Tatar
|
|
8
|
+
Author-email: sercan@everionconsulting.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Framework :: Django
|
|
11
|
+
Classifier: Framework :: Django :: 4.2
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Requires-Dist: celery (>=5.3,<6.0)
|
|
21
|
+
Requires-Dist: django (>=4.2)
|
|
22
|
+
Requires-Dist: djangorestframework (>=3.14,<4.0)
|
|
23
|
+
Project-URL: Documentation, https://github.com/gulergokce/django-notification-kit
|
|
24
|
+
Project-URL: Homepage, https://github.com/gulergokce/django-notification-kit
|
|
25
|
+
Project-URL: Repository, https://github.com/gulergokce/django-notification-kit
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
🚀 Django Notification Kit
|
|
29
|
+
Django Notification Kit, Django projeleri için tasarlanmış; çok kanallı (Email / SMS / Push) bildirim gönderimini yöneten, modüler ve genişletilebilir bir altyapıdır. Sistemin temel amacı; bildirim gönderim süreçlerini API – Service – Backend – Task katmanlarına ayırarak yüksek test edilebilirlik ve sürdürülebilirlik sağlamaktır.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
🎯 Kapsam (v1.0.0 – MVP)
|
|
34
|
+
Bu versiyon, profesyonel bir bildirim sistemi için gerekli olan şu temel bileşenleri sunar:
|
|
35
|
+
|
|
36
|
+
✅ Abstract Model Yapısı: Genişletilmeye hazır taban modeller.
|
|
37
|
+
|
|
38
|
+
✅ Concrete Modeller: Notification, Template, Preference ve Log yapıları.
|
|
39
|
+
|
|
40
|
+
✅ SMTP Email Backend: Standart e-posta gönderim desteği.
|
|
41
|
+
|
|
42
|
+
✅ Base Notification Service: İş mantığını yöneten merkezi servis.
|
|
43
|
+
|
|
44
|
+
✅ Celery Task Factory: Ölçeklenebilir, asenkron gönderim desteği.
|
|
45
|
+
|
|
46
|
+
✅ DRF API: Standartlara uygun API uçları ve Serializer'lar.
|
|
47
|
+
|
|
48
|
+
✅ Merkezi Ayar Yönetimi: NOTIFICATION_KIT üzerinden tam kontrol.
|
|
49
|
+
|
|
50
|
+
✅ Full Testing: Unit, API ve Performans testleri.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
🏛 Mimari Genel Bakış
|
|
55
|
+
Sistem, sorumlulukların net ayrıştırıldığı Layered Architecture (Katmanlı Mimari) prensibiyle tasarlanmıştır.Katmanların SorumluluklarıKatmanSorumluluk AlanıAPIRequest validation, permission kontrolü, throttling.Serviceİş kuralları, kanal seçimi, retry logic yönetimi.BackendKanal spesifik (Email, SMS vb.) düşük seviyeli implementasyon.TaskAsenkron (Async) ve toplu (Batch) işlem yönetimi (Celery).ModelsVeri şeması; Notification, Template ve Kullanıcı tercihleri.SettingsMerkezi ve projeye özel override edilebilir konfigürasyon.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
⚙️ Ayar Yönetimi
|
|
60
|
+
Paket içi ayarlar notification_kit/settings.py dosyasında tanımlıdır. Proje bazlı özelleştirmeler için ana settings.py dosyanızda NOTIFICATION_KIT sözlüğünü kullanabilirsiniz.
|
|
61
|
+
|
|
62
|
+
Python
|
|
63
|
+
# project/settings.py
|
|
64
|
+
|
|
65
|
+
NOTIFICATION_KIT = {
|
|
66
|
+
"ASYNC": True,
|
|
67
|
+
"MAX_RETRIES": 5,
|
|
68
|
+
"EMAIL": {
|
|
69
|
+
"FROM_EMAIL": "noreply@example.com",
|
|
70
|
+
"RATE_LIMIT": 200,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
Not: Hassas veriler (API Key, Şifre vb.) her zaman çevre değişkenleri (environment variables) üzerinden yönetilmelidir.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
🛠 Teknik Implementasyon
|
|
78
|
+
1. Backend Yapısı
|
|
79
|
+
Her kanal (SMS, Email, Push) için bir backend sınıfı bulunur. Yeni bir sağlayıcı eklemek oldukça basittir:
|
|
80
|
+
|
|
81
|
+
Python
|
|
82
|
+
class BaseBackend:
|
|
83
|
+
def send(self, notification):
|
|
84
|
+
raise NotImplementedError("Bu metod implemente edilmelidir.")
|
|
85
|
+
2. Service Katmanı
|
|
86
|
+
İş mantığının kalbidir. API'den gelen isteği alır, hangi kanalın kullanılacağına karar verir ve gerekirse işi Celery'ye devreder.
|
|
87
|
+
|
|
88
|
+
Kanal Seçimi: Template üzerinden dinamik kanal yönetimi.
|
|
89
|
+
|
|
90
|
+
Soyutlama: Backend implementasyonundan bağımsız çalışma.
|
|
91
|
+
|
|
92
|
+
3. API Katmanı
|
|
93
|
+
Yalnızca bildirim oluşturma ve tetikleme sorumluluğunu taşır.
|
|
94
|
+
|
|
95
|
+
Validation: Serializer'lar ile veri doğruluğu garanti edilir.
|
|
96
|
+
|
|
97
|
+
Security: Permission sınıfları ile yetkisiz erişim engellenir.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
🔒 Güvenlik Yaklaşımı
|
|
102
|
+
Veri Gizliliği: Kişisel veriler (PII) kesinlikle loglanmaz.
|
|
103
|
+
|
|
104
|
+
Hız Sınırı: API düzeyinde Throttling ile brute-force ve aşırı yüklenme engellenir.
|
|
105
|
+
|
|
106
|
+
Denetim: Tüm gönderim denemeleri ve sonuçları NotificationLog üzerinden izlenebilir.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
🧪 Test Stratejisi
|
|
111
|
+
Sistem, Pytest altyapısı kullanılarak aşağıdaki seviyelerde test edilmektedir:
|
|
112
|
+
|
|
113
|
+
Model: Veri bütünlüğü ve ilişki testleri.
|
|
114
|
+
|
|
115
|
+
Service: İş mantığı ve kanal yönlendirme testleri.
|
|
116
|
+
|
|
117
|
+
Backend: Provider entegrasyon (mock) testleri.
|
|
118
|
+
|
|
119
|
+
API: Endpoint erişim ve veri formatı testleri.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
🤝 Katkıda Bulunma
|
|
124
|
+
Projeyi fork edin.
|
|
125
|
+
|
|
126
|
+
Yeni bir feature branch oluşturun (git checkout -b feature/yeniOzellik).
|
|
127
|
+
|
|
128
|
+
Değişikliklerinizi commit edin (git commit -m 'Ekle: Yeni SMS Backend').
|
|
129
|
+
|
|
130
|
+
Branch'inizi push edin (git push origin feature/yeniOzellik).
|
|
131
|
+
|
|
132
|
+
Bir Pull Request açın.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
🚀 Django Notification Kit
|
|
2
|
+
Django Notification Kit, Django projeleri için tasarlanmış; çok kanallı (Email / SMS / Push) bildirim gönderimini yöneten, modüler ve genişletilebilir bir altyapıdır. Sistemin temel amacı; bildirim gönderim süreçlerini API – Service – Backend – Task katmanlarına ayırarak yüksek test edilebilirlik ve sürdürülebilirlik sağlamaktır.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
🎯 Kapsam (v1.0.0 – MVP)
|
|
7
|
+
Bu versiyon, profesyonel bir bildirim sistemi için gerekli olan şu temel bileşenleri sunar:
|
|
8
|
+
|
|
9
|
+
✅ Abstract Model Yapısı: Genişletilmeye hazır taban modeller.
|
|
10
|
+
|
|
11
|
+
✅ Concrete Modeller: Notification, Template, Preference ve Log yapıları.
|
|
12
|
+
|
|
13
|
+
✅ SMTP Email Backend: Standart e-posta gönderim desteği.
|
|
14
|
+
|
|
15
|
+
✅ Base Notification Service: İş mantığını yöneten merkezi servis.
|
|
16
|
+
|
|
17
|
+
✅ Celery Task Factory: Ölçeklenebilir, asenkron gönderim desteği.
|
|
18
|
+
|
|
19
|
+
✅ DRF API: Standartlara uygun API uçları ve Serializer'lar.
|
|
20
|
+
|
|
21
|
+
✅ Merkezi Ayar Yönetimi: NOTIFICATION_KIT üzerinden tam kontrol.
|
|
22
|
+
|
|
23
|
+
✅ Full Testing: Unit, API ve Performans testleri.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
🏛 Mimari Genel Bakış
|
|
28
|
+
Sistem, sorumlulukların net ayrıştırıldığı Layered Architecture (Katmanlı Mimari) prensibiyle tasarlanmıştır.Katmanların SorumluluklarıKatmanSorumluluk AlanıAPIRequest validation, permission kontrolü, throttling.Serviceİş kuralları, kanal seçimi, retry logic yönetimi.BackendKanal spesifik (Email, SMS vb.) düşük seviyeli implementasyon.TaskAsenkron (Async) ve toplu (Batch) işlem yönetimi (Celery).ModelsVeri şeması; Notification, Template ve Kullanıcı tercihleri.SettingsMerkezi ve projeye özel override edilebilir konfigürasyon.
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
⚙️ Ayar Yönetimi
|
|
33
|
+
Paket içi ayarlar notification_kit/settings.py dosyasında tanımlıdır. Proje bazlı özelleştirmeler için ana settings.py dosyanızda NOTIFICATION_KIT sözlüğünü kullanabilirsiniz.
|
|
34
|
+
|
|
35
|
+
Python
|
|
36
|
+
# project/settings.py
|
|
37
|
+
|
|
38
|
+
NOTIFICATION_KIT = {
|
|
39
|
+
"ASYNC": True,
|
|
40
|
+
"MAX_RETRIES": 5,
|
|
41
|
+
"EMAIL": {
|
|
42
|
+
"FROM_EMAIL": "noreply@example.com",
|
|
43
|
+
"RATE_LIMIT": 200,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
Not: Hassas veriler (API Key, Şifre vb.) her zaman çevre değişkenleri (environment variables) üzerinden yönetilmelidir.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
🛠 Teknik Implementasyon
|
|
51
|
+
1. Backend Yapısı
|
|
52
|
+
Her kanal (SMS, Email, Push) için bir backend sınıfı bulunur. Yeni bir sağlayıcı eklemek oldukça basittir:
|
|
53
|
+
|
|
54
|
+
Python
|
|
55
|
+
class BaseBackend:
|
|
56
|
+
def send(self, notification):
|
|
57
|
+
raise NotImplementedError("Bu metod implemente edilmelidir.")
|
|
58
|
+
2. Service Katmanı
|
|
59
|
+
İş mantığının kalbidir. API'den gelen isteği alır, hangi kanalın kullanılacağına karar verir ve gerekirse işi Celery'ye devreder.
|
|
60
|
+
|
|
61
|
+
Kanal Seçimi: Template üzerinden dinamik kanal yönetimi.
|
|
62
|
+
|
|
63
|
+
Soyutlama: Backend implementasyonundan bağımsız çalışma.
|
|
64
|
+
|
|
65
|
+
3. API Katmanı
|
|
66
|
+
Yalnızca bildirim oluşturma ve tetikleme sorumluluğunu taşır.
|
|
67
|
+
|
|
68
|
+
Validation: Serializer'lar ile veri doğruluğu garanti edilir.
|
|
69
|
+
|
|
70
|
+
Security: Permission sınıfları ile yetkisiz erişim engellenir.
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
🔒 Güvenlik Yaklaşımı
|
|
75
|
+
Veri Gizliliği: Kişisel veriler (PII) kesinlikle loglanmaz.
|
|
76
|
+
|
|
77
|
+
Hız Sınırı: API düzeyinde Throttling ile brute-force ve aşırı yüklenme engellenir.
|
|
78
|
+
|
|
79
|
+
Denetim: Tüm gönderim denemeleri ve sonuçları NotificationLog üzerinden izlenebilir.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
🧪 Test Stratejisi
|
|
84
|
+
Sistem, Pytest altyapısı kullanılarak aşağıdaki seviyelerde test edilmektedir:
|
|
85
|
+
|
|
86
|
+
Model: Veri bütünlüğü ve ilişki testleri.
|
|
87
|
+
|
|
88
|
+
Service: İş mantığı ve kanal yönlendirme testleri.
|
|
89
|
+
|
|
90
|
+
Backend: Provider entegrasyon (mock) testleri.
|
|
91
|
+
|
|
92
|
+
API: Endpoint erişim ve veri formatı testleri.
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
🤝 Katkıda Bulunma
|
|
97
|
+
Projeyi fork edin.
|
|
98
|
+
|
|
99
|
+
Yeni bir feature branch oluşturun (git checkout -b feature/yeniOzellik).
|
|
100
|
+
|
|
101
|
+
Değişikliklerinizi commit edin (git commit -m 'Ekle: Yeni SMS Backend').
|
|
102
|
+
|
|
103
|
+
Branch'inizi push edin (git push origin feature/yeniOzellik).
|
|
104
|
+
|
|
105
|
+
Bir Pull Request açın.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from notification_kit.models.concrete import (
|
|
4
|
+
Notification,
|
|
5
|
+
NotificationTemplate,
|
|
6
|
+
NotificationPreference,
|
|
7
|
+
NotificationLog,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@admin.register(Notification)
|
|
12
|
+
class NotificationAdmin(admin.ModelAdmin):
|
|
13
|
+
"""
|
|
14
|
+
Ana Notification modeli için admin ayarları
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
list_display = (
|
|
18
|
+
"id",
|
|
19
|
+
"user",
|
|
20
|
+
"channel",
|
|
21
|
+
"status",
|
|
22
|
+
"priority",
|
|
23
|
+
"created_at",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
list_filter = (
|
|
27
|
+
"channel",
|
|
28
|
+
"status",
|
|
29
|
+
"priority",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
search_fields = (
|
|
33
|
+
"title",
|
|
34
|
+
"body",
|
|
35
|
+
"user__username",
|
|
36
|
+
"user__email",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
readonly_fields = (
|
|
40
|
+
"sent_at",
|
|
41
|
+
"read_at",
|
|
42
|
+
"created_at",
|
|
43
|
+
"updated_at",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
ordering = ("-created_at",)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@admin.register(NotificationTemplate)
|
|
50
|
+
class NotificationTemplateAdmin(admin.ModelAdmin):
|
|
51
|
+
"""
|
|
52
|
+
Bildirim şablonları için admin ayarları
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
list_display = (
|
|
56
|
+
"code",
|
|
57
|
+
"name",
|
|
58
|
+
"channel",
|
|
59
|
+
"is_active",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
list_filter = (
|
|
63
|
+
"channel",
|
|
64
|
+
"is_active",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
search_fields = (
|
|
68
|
+
"code",
|
|
69
|
+
"name",
|
|
70
|
+
"description",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
ordering = ("code",)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@admin.register(NotificationPreference)
|
|
77
|
+
class NotificationPreferenceAdmin(admin.ModelAdmin):
|
|
78
|
+
"""
|
|
79
|
+
Kullanıcı bildirim tercihleri admin ayarları
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
list_display = (
|
|
83
|
+
"id",
|
|
84
|
+
"user",
|
|
85
|
+
"channel",
|
|
86
|
+
"is_enabled",
|
|
87
|
+
"frequency",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
list_filter = (
|
|
91
|
+
"channel",
|
|
92
|
+
"is_enabled",
|
|
93
|
+
"frequency",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
search_fields = (
|
|
97
|
+
"user__username",
|
|
98
|
+
"user__email",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@admin.register(NotificationLog)
|
|
103
|
+
class NotificationLogAdmin(admin.ModelAdmin):
|
|
104
|
+
"""
|
|
105
|
+
Bildirim logları admin ayarları
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
list_display = (
|
|
109
|
+
"notification_id",
|
|
110
|
+
"action",
|
|
111
|
+
"old_status",
|
|
112
|
+
"new_status",
|
|
113
|
+
"created_at",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
list_filter = (
|
|
117
|
+
"action",
|
|
118
|
+
"created_at",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
search_fields = (
|
|
122
|
+
"notification_id",
|
|
123
|
+
"action",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
readonly_fields = (
|
|
127
|
+
"notification_id",
|
|
128
|
+
"action",
|
|
129
|
+
"old_status",
|
|
130
|
+
"new_status",
|
|
131
|
+
"details",
|
|
132
|
+
"ip_address",
|
|
133
|
+
"user_agent",
|
|
134
|
+
"created_at",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
ordering = ("-created_at",)
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from rest_framework.permissions import BasePermission, SAFE_METHODS
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IsAdminOrReadOnly(BasePermission):
|
|
5
|
+
"""
|
|
6
|
+
Okuma işlemleri herkese açık olabilir.
|
|
7
|
+
Yazma / silme işlemleri sadece admin (staff) kullanıcıya izinlidir.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def has_permission(self, request, view):
|
|
11
|
+
# GET, HEAD, OPTIONS serbest
|
|
12
|
+
if request.method in SAFE_METHODS:
|
|
13
|
+
return True
|
|
14
|
+
|
|
15
|
+
# Yazma işlemleri için admin şartı
|
|
16
|
+
return bool(request.user and request.user.is_staff)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from ..models.concrete import Notification, NotificationPreference
|
|
3
|
+
|
|
4
|
+
class NotificationSerializer(serializers.ModelSerializer):
|
|
5
|
+
class Meta:
|
|
6
|
+
model = Notification
|
|
7
|
+
fields = "__all__"
|
|
8
|
+
|
|
9
|
+
class NotificationPreferenceSerializer(serializers.ModelSerializer):
|
|
10
|
+
class Meta:
|
|
11
|
+
model = NotificationPreference
|
|
12
|
+
fields = "__all__"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.urls import path, include
|
|
2
|
+
from rest_framework.routers import DefaultRouter
|
|
3
|
+
from .views import NotificationViewSet, NotificationPreferenceViewSet, admin_send_notification
|
|
4
|
+
|
|
5
|
+
router = DefaultRouter()
|
|
6
|
+
router.register(r"notifications", NotificationViewSet, basename="notification")
|
|
7
|
+
router.register(r"preferences", NotificationPreferenceViewSet, basename="preference")
|
|
8
|
+
|
|
9
|
+
urlpatterns = [
|
|
10
|
+
path("api/", include(router.urls)),
|
|
11
|
+
path(
|
|
12
|
+
"admin/notifications/send/",
|
|
13
|
+
admin_send_notification,
|
|
14
|
+
name="admin-notifications-send",
|
|
15
|
+
),
|
|
16
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from rest_framework import viewsets, status
|
|
2
|
+
from rest_framework.decorators import action
|
|
3
|
+
from rest_framework.response import Response
|
|
4
|
+
from .serializers import NotificationSerializer, NotificationPreferenceSerializer
|
|
5
|
+
from ..models.concrete import Notification, NotificationPreference
|
|
6
|
+
from notification_kit.api.permissions import IsAdminOrReadOnly
|
|
7
|
+
from django.contrib.auth import get_user_model
|
|
8
|
+
from rest_framework.decorators import api_view, permission_classes
|
|
9
|
+
from rest_framework.permissions import IsAuthenticated
|
|
10
|
+
|
|
11
|
+
from ..models import Notification # concrete Notification
|
|
12
|
+
|
|
13
|
+
class NotificationViewSet(viewsets.ModelViewSet):
|
|
14
|
+
queryset = Notification.objects.all()
|
|
15
|
+
serializer_class = NotificationSerializer
|
|
16
|
+
permission_classes = [IsAdminOrReadOnly]
|
|
17
|
+
|
|
18
|
+
@action(detail=True, methods=["post"])
|
|
19
|
+
def read(self, request, pk=None):
|
|
20
|
+
notification = self.get_object()
|
|
21
|
+
notification.mark_as_read()
|
|
22
|
+
return Response({"status": "ok"})
|
|
23
|
+
|
|
24
|
+
@action(detail=False, methods=["post"])
|
|
25
|
+
def read_all(self, request):
|
|
26
|
+
notifications = Notification.objects.all() # filtreleme eklenebilir
|
|
27
|
+
for n in notifications:
|
|
28
|
+
n.mark_as_read()
|
|
29
|
+
return Response({"status": "ok"})
|
|
30
|
+
|
|
31
|
+
@action(detail=False, methods=["get"])
|
|
32
|
+
def unread_count(self, request):
|
|
33
|
+
count = Notification.objects.filter(read_at__isnull=True).count()
|
|
34
|
+
return Response({"unread_count": count})
|
|
35
|
+
|
|
36
|
+
class NotificationPreferenceViewSet(viewsets.ModelViewSet):
|
|
37
|
+
queryset = NotificationPreference.objects.all()
|
|
38
|
+
serializer_class = NotificationPreferenceSerializer
|
|
39
|
+
|
|
40
|
+
User = get_user_model()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@api_view(["POST"])
|
|
44
|
+
@permission_classes([IsAuthenticated])
|
|
45
|
+
def admin_send_notification(request):
|
|
46
|
+
"""
|
|
47
|
+
Admin/System endpoint:
|
|
48
|
+
recipient_id + title + body alır, Notification kaydı üretir.
|
|
49
|
+
Testin aradığı url-name: admin-notifications-send
|
|
50
|
+
"""
|
|
51
|
+
recipient_id = request.data.get("recipient_id")
|
|
52
|
+
title = request.data.get("title")
|
|
53
|
+
body = request.data.get("body")
|
|
54
|
+
|
|
55
|
+
if not recipient_id or not title or not body:
|
|
56
|
+
return Response(
|
|
57
|
+
{"detail": "recipient_id, title ve body zorunlu."},
|
|
58
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
user = User.objects.get(id=recipient_id)
|
|
63
|
+
except User.DoesNotExist:
|
|
64
|
+
return Response(
|
|
65
|
+
{"detail": "recipient bulunamadı."},
|
|
66
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
channel = request.data.get("channel", "email")
|
|
70
|
+
priority = request.data.get("priority", getattr(Notification, "PRIORITY_NORMAL", "normal"))
|
|
71
|
+
|
|
72
|
+
n = Notification.objects.create(
|
|
73
|
+
user=user, # <- sizin modelde NOT NULL olan alan bu
|
|
74
|
+
channel=channel,
|
|
75
|
+
status=getattr(Notification, "STATUS_PENDING", "pending"),
|
|
76
|
+
title=title,
|
|
77
|
+
body=body,
|
|
78
|
+
priority=priority,
|
|
79
|
+
metadata=request.data.get("metadata", {}),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return Response(
|
|
83
|
+
{"id": n.id, "status": n.status},
|
|
84
|
+
status=status.HTTP_201_CREATED,
|
|
85
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# notification_kit/backends/base.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
from abc import ABC
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from notification_kit.settings import get_settings
|
|
10
|
+
from notification_kit.utils.validators import mask_pii
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseNotificationBackend(ABC):
|
|
16
|
+
"""
|
|
17
|
+
Tüm bildirim backend'lerinin uyması gereken temel arayüz.
|
|
18
|
+
Email, SMS ve Push backend'leri bu sınıftan türetilmelidir.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name: str = ""
|
|
22
|
+
channel: str = ""
|
|
23
|
+
|
|
24
|
+
def __init__(self) -> None:
|
|
25
|
+
settings = get_settings()
|
|
26
|
+
|
|
27
|
+
self.max_retries: int = settings.get("MAX_RETRIES", 3)
|
|
28
|
+
self.retry_delay: int = settings.get("RETRY_DELAY", 300)
|
|
29
|
+
|
|
30
|
+
channel_settings = settings.get(self.channel.upper(), {})
|
|
31
|
+
self.batch_size: int = channel_settings.get("BATCH_SIZE", 1)
|
|
32
|
+
self.rate_limit: int = channel_settings.get("RATE_LIMIT", 60)
|
|
33
|
+
|
|
34
|
+
# -------------------------------------------------
|
|
35
|
+
# Interface (Varsayılan: override edilmesi beklenir)
|
|
36
|
+
# Not: abstract bırakmıyoruz ki testlerde DummyBackend
|
|
37
|
+
# rahat instantiate olabilsin.
|
|
38
|
+
# -------------------------------------------------
|
|
39
|
+
def send(self, notification: Any) -> bool:
|
|
40
|
+
"""
|
|
41
|
+
Bildirimi gönderir.
|
|
42
|
+
Başarılıysa True, başarısızsa False döner.
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError(f"{self.__class__.__name__}.send() implement edilmedi")
|
|
45
|
+
|
|
46
|
+
def validate_recipient(self, recipient: Any) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
Alıcı bilgisini doğrular.
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError(
|
|
51
|
+
f"{self.__class__.__name__}.validate_recipient() implement edilmedi"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def get_delivery_status(self, notification: Any) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Bildirimin teslim durumunu sorgular.
|
|
57
|
+
"""
|
|
58
|
+
raise NotImplementedError(
|
|
59
|
+
f"{self.__class__.__name__}.get_delivery_status() implement edilmedi"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# --------------------
|
|
63
|
+
# Ortak yardımcı metodlar
|
|
64
|
+
# --------------------
|
|
65
|
+
def format_message(self, template: str, context: dict) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Template ve context kullanarak mesaj içeriğini oluşturur.
|
|
68
|
+
(Bu format, str.format bazlıdır. Django Template değil.)
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
return template.format(**context)
|
|
72
|
+
except KeyError as exc:
|
|
73
|
+
raise ValueError(f"Eksik template değişkeni: {exc}") from exc
|
|
74
|
+
|
|
75
|
+
def sanitize_content(self, content: str) -> str:
|
|
76
|
+
"""
|
|
77
|
+
İçeriği güvenli hale getirir.
|
|
78
|
+
Zararlı karakterler ve gereksiz boşluklar temizlenir.
|
|
79
|
+
"""
|
|
80
|
+
return (content or "").strip()
|
|
81
|
+
|
|
82
|
+
# --------------------
|
|
83
|
+
# Log + Retry helpers
|
|
84
|
+
# --------------------
|
|
85
|
+
def log_send_attempt(self, notification: Any, success: bool, error: Exception | None = None) -> None:
|
|
86
|
+
"""
|
|
87
|
+
Bildirim gönderim denemesini loglar.
|
|
88
|
+
PII veriler maskelenerek yazılır.
|
|
89
|
+
"""
|
|
90
|
+
recipient = ""
|
|
91
|
+
if hasattr(notification, "get_recipient_identifier"):
|
|
92
|
+
try:
|
|
93
|
+
recipient = str(notification.get_recipient_identifier())
|
|
94
|
+
except Exception:
|
|
95
|
+
recipient = ""
|
|
96
|
+
|
|
97
|
+
safe_recipient = mask_pii(recipient) if recipient else ""
|
|
98
|
+
|
|
99
|
+
payload = {
|
|
100
|
+
"backend": self.name or self.__class__.__name__,
|
|
101
|
+
"channel": self.channel,
|
|
102
|
+
"recipient": safe_recipient,
|
|
103
|
+
"success": success,
|
|
104
|
+
"error": str(error) if error else None,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# stdout yerine logger kullanalım (testlerde de temiz)
|
|
108
|
+
logger.info("[NOTIFICATION LOG] %s", payload)
|
|
109
|
+
|
|
110
|
+
def handle_error(self, notification: Any, error: Exception) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Hata durumunda loglama + notification state günceller.
|
|
113
|
+
"""
|
|
114
|
+
self.log_send_attempt(notification, success=False, error=error)
|
|
115
|
+
|
|
116
|
+
if hasattr(notification, "mark_as_failed"):
|
|
117
|
+
try:
|
|
118
|
+
notification.mark_as_failed(str(error))
|
|
119
|
+
except Exception:
|
|
120
|
+
# notification mark_as_failed patlarsa testleri kırmayalım
|
|
121
|
+
logger.exception("mark_as_failed çağrısı hata verdi")
|
|
122
|
+
|
|
123
|
+
def should_retry(self, notification: Any, error: Exception) -> bool:
|
|
124
|
+
"""
|
|
125
|
+
Bildirim tekrar denenmeli mi?
|
|
126
|
+
"""
|
|
127
|
+
retry_count = getattr(notification, "retry_count", None)
|
|
128
|
+
if retry_count is None:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
if retry_count >= self.max_retries:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
# basit backoff (settings.RETRY_DELAY)
|
|
135
|
+
time.sleep(self.retry_delay)
|
|
136
|
+
return True
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .email import BaseSMTPBackend
|
|
2
|
+
from .sms import BaseSMSBackend
|
|
3
|
+
from .push import BasePushBackend
|
|
4
|
+
|
|
5
|
+
class EmailBackend(BaseSMTPBackend):
|
|
6
|
+
def build_email_message(self, notification):
|
|
7
|
+
message = super().build_email_message(notification)
|
|
8
|
+
# Proje özel footer ekle
|
|
9
|
+
message.body += "\n\n-- Proje Footer --"
|
|
10
|
+
return message
|
|
11
|
+
|
|
12
|
+
class SMSBackend(BaseSMSBackend):
|
|
13
|
+
def send_sms(self, phone_number, message):
|
|
14
|
+
# Örnek: Twilio API entegrasyonu
|
|
15
|
+
return super().send_sms(phone_number, message)
|
|
16
|
+
|
|
17
|
+
class PushBackend(BasePushBackend):
|
|
18
|
+
def build_payload(self, notification):
|
|
19
|
+
payload = super().build_payload(notification)
|
|
20
|
+
payload["custom"] = "Proje özel data"
|
|
21
|
+
return payload
|