django-chelseru 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
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.
- django_chelseru-1.0.3.dist-info/METADATA +315 -0
- django_chelseru-1.0.3.dist-info/RECORD +27 -0
- drfchelseru/admin.py +7 -2
- drfchelseru/consumers.py +82 -0
- drfchelseru/middlewares.py +37 -1
- drfchelseru/migrations/0004_rename_message_message_sms.py +17 -0
- drfchelseru/migrations/0005_rename_message_sms_messagesms_chatroom_messagechat.py +37 -0
- drfchelseru/migrations/0006_alter_chatroom_user_1_alter_chatroom_user_2_and_more.py +36 -0
- drfchelseru/models.py +25 -2
- drfchelseru/routing.py +6 -0
- drfchelseru/serializers.py +23 -3
- drfchelseru/services.py +2 -2
- drfchelseru/settings.py +33 -20
- drfchelseru/urls.py +8 -3
- drfchelseru/views.py +55 -3
- django_chelseru-1.0.1.dist-info/METADATA +0 -58
- django_chelseru-1.0.1.dist-info/RECORD +0 -22
- {django_chelseru-1.0.1.dist-info → django_chelseru-1.0.3.dist-info}/WHEEL +0 -0
- {django_chelseru-1.0.1.dist-info → django_chelseru-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {django_chelseru-1.0.1.dist-info → django_chelseru-1.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-chelseru
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Authentication system, online and real-time chat, SMS system for Iranian SMS services.
|
|
5
|
+
Home-page: https://pipdjango.chelseru.com
|
|
6
|
+
Author: Sobhan Bahman|Rashnu
|
|
7
|
+
Author-email: bahmanrashnu@gmail.com
|
|
8
|
+
Project-URL: Documentation, https://github.com/Chelseru/django-chelseru-lour/
|
|
9
|
+
Project-URL: Telegram Group, https://t.me/bahmanpy
|
|
10
|
+
Project-URL: Telegram Channel, https://t.me/ChelseruCom
|
|
11
|
+
Keywords: djangochelseruchat djangochat drfchat online-chat online real-time chat iran chelseru lor lur bahman rashnu lour sms djangoauth auth ywt otpauth otp authentication djangootp djangoiransms iransms djangosms djangokavenegar djangomelipayamak sobhan چت سبحان بهمن رشنو چلسرو جنگو پایتون لر لور آنلاین ریل تایم
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Framework :: Django
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: Django>=5.1.6
|
|
20
|
+
Requires-Dist: djangorestframework==3.15.2
|
|
21
|
+
Requires-Dist: djangorestframework_simplejwt==5.5.0
|
|
22
|
+
Requires-Dist: channels==4.2.2
|
|
23
|
+
Requires-Dist: channels_redis==4.2.1
|
|
24
|
+
Requires-Dist: daphne==4.1.2
|
|
25
|
+
Requires-Dist: zeep==4.3.1
|
|
26
|
+
Requires-Dist: user-agents==2.2.0
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: keywords
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: project-url
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
39
|
+
|
|
40
|
+
django-chelseru
|
|
41
|
+
یک بسته جنگویی برای گپزنی همزمان، راستیآزمایی پیامکی و فرستادن پیامک با یاریدهندههای ایرانی.
|
|
42
|
+
|
|
43
|
+
نویسنده
|
|
44
|
+
Sobhan Bahman Rashnu
|
|
45
|
+
|
|
46
|
+
🚀 ویژگیها
|
|
47
|
+
📱 راستیآزمایی پیامکی (رمز یکبارمصرف): راستیآزمایی امن کاربران با یاریگیری از رمزهای یکبارمصرف که از راه پیامک فرستاده میشوند.
|
|
48
|
+
|
|
49
|
+
💬 گپزنی همزمان: کارکرد پیامرسانی همزمان بر پایه WebSocket.
|
|
50
|
+
|
|
51
|
+
✉️ سامانههای پیامکی: فرستادن پیامک از راه یاریدهندههای نامور پیامکی ایرانی.
|
|
52
|
+
|
|
53
|
+
⚙️ نصب
|
|
54
|
+
بسته را با یاریگیری از pip نصب کنید:
|
|
55
|
+
|
|
56
|
+
pip install django-chelseru
|
|
57
|
+
|
|
58
|
+
'drfchelseru' را به INSTALLED_APPS در پرونده settings.py خود بیفزایید:
|
|
59
|
+
|
|
60
|
+
INSTALLED_APPS = [
|
|
61
|
+
...
|
|
62
|
+
'drfchelseru',
|
|
63
|
+
'rest_framework',
|
|
64
|
+
'rest_framework_simplejwt',
|
|
65
|
+
...
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
🛠️ پیکربندی
|
|
69
|
+
برای پیکربندی بسته، واژهنامه DJANGO_CHELSERU را به پرونده settings.py خود بیفزایید. این واژهنامه به شما پروانه میدهد تا چیدمانهای راستیآزمایی و پیامک را خودساخته نمایید.
|
|
70
|
+
|
|
71
|
+
# settings.py
|
|
72
|
+
|
|
73
|
+
DJANGO_CHELSERU = {
|
|
74
|
+
'AUTH': {
|
|
75
|
+
'AUTH_METHOD' : 'OTP', # روشهای پشتیبانی شده: OTP, PASSWD
|
|
76
|
+
'AUTH_SERVICE' : 'rest_framework_simplejwt', # سرویسهای پشتیبانی شده: rest_framework_simplejwt
|
|
77
|
+
'OPTIONS': {
|
|
78
|
+
'OTP_LENGTH' : 8, # پیشفرض: 8
|
|
79
|
+
'OTP_EXPIRE_PER_MINUTES': 4, # پیشفرض: 4
|
|
80
|
+
'OTP_SMS_TEMPLATE_ID' : 1, # شناسه قالب پیامکی برای رمز یکبارمصرف
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
'SMS': {
|
|
84
|
+
'SMS_SERVICE': 'PARSIAN_WEBCO_IR', # یاریدهندههای پشتیبانی شده: PARSIAN_WEBCO_IR, MELI_PAYAMAK_COM, KAVENEGAR_COM
|
|
85
|
+
'SETTINGS': {
|
|
86
|
+
'PARSIAN_WEBCO_IR_API_KEY' : '',
|
|
87
|
+
'MELI_PAYAMAK_COM_USERNAME' : '',
|
|
88
|
+
'MELI_PAYAMAK_COM_PASSWORD' : '',
|
|
89
|
+
'MELI_PAYAMAK_COM_FROM' : '',
|
|
90
|
+
'KAVENEGAR_COM_API_KEY' : 'YOUR_KAVENEGAR_API_KEY',
|
|
91
|
+
'KAVENEGAR_COM_FROM' : 'YOUR_KAVENEGAR_FROM_NUMBER',
|
|
92
|
+
},
|
|
93
|
+
'TEMPLATES': {
|
|
94
|
+
'T1': 1,
|
|
95
|
+
'T2': 2,
|
|
96
|
+
...
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
AUTH_METHOD: روش راستیآزمایی را روشن میسازد. برای راستیآزمایی پیامکی، از 'OTP' بهره بگیرید.
|
|
102
|
+
|
|
103
|
+
OTP_LENGTH: درازای رمز یکبارمصرف.
|
|
104
|
+
|
|
105
|
+
OTP_EXPIRE_PER_MINUTES: زمان پایانیافتن رمز یکبارمصرف بر پایه دقیقه.
|
|
106
|
+
|
|
107
|
+
OTP_SMS_TEMPLATE_ID: شناسه قالب پیامکی که برای فرستادن رمز یکبارمصرف بهره گرفته میشود.
|
|
108
|
+
|
|
109
|
+
SMS_SERVICE: یاریدهنده پیامکی دلخواه خود را برگزینید.
|
|
110
|
+
|
|
111
|
+
SETTINGS: آگاهیهای نیاز برای یاریدهنده پیامکی برگزیدهتان را فراهم آورید.
|
|
112
|
+
|
|
113
|
+
TEMPLATES: شناسههای قالب پیامکی خود را روشن سازید.
|
|
114
|
+
|
|
115
|
+
🔌 نقطههای پایانی
|
|
116
|
+
برای به کارگیری کارکردهای این بسته، URLهای زیر را به پرونده urls.py خود بیفزایید.
|
|
117
|
+
|
|
118
|
+
# urls.py
|
|
119
|
+
|
|
120
|
+
from django.urls import path, include
|
|
121
|
+
|
|
122
|
+
urlpatterns = [
|
|
123
|
+
...
|
|
124
|
+
path('api/', include('drfchelseru.urls')),
|
|
125
|
+
...
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
این بسته نقطههای پایانی API زیر را فراهم میآورد:
|
|
129
|
+
|
|
130
|
+
نقطه پایانی
|
|
131
|
+
|
|
132
|
+
شرح
|
|
133
|
+
|
|
134
|
+
روش
|
|
135
|
+
|
|
136
|
+
/api/otp/send/
|
|
137
|
+
|
|
138
|
+
یک رمز یکبارمصرف به شماره همراه گفتهشده میفرستد.
|
|
139
|
+
|
|
140
|
+
POST
|
|
141
|
+
|
|
142
|
+
/api/authenticate/
|
|
143
|
+
|
|
144
|
+
کاربری را با رمز یکبارمصرف دریافتشده، راستیآزمایی میکند.
|
|
145
|
+
|
|
146
|
+
POST
|
|
147
|
+
|
|
148
|
+
/api/sessions/
|
|
149
|
+
|
|
150
|
+
نشستهای فعال کاربر را فهرست کرده و درایوری میکند.
|
|
151
|
+
|
|
152
|
+
GET
|
|
153
|
+
|
|
154
|
+
/api/message/send/
|
|
155
|
+
|
|
156
|
+
یک پیامک با یاریدهنده پیکربندیشده میفرستد.
|
|
157
|
+
|
|
158
|
+
POST
|
|
159
|
+
|
|
160
|
+
به کارگیری نقطههای پایانی
|
|
161
|
+
1. فرستادن رمز یکبارمصرف (/api/otp/send/)
|
|
162
|
+
روش: POST شرح: یک رمز یکبارمصرف به شماره همراه کاربر میفرستد.
|
|
163
|
+
|
|
164
|
+
دادهنماهای نیاز:
|
|
165
|
+
|
|
166
|
+
دادهنما
|
|
167
|
+
|
|
168
|
+
گونه
|
|
169
|
+
|
|
170
|
+
شرح
|
|
171
|
+
|
|
172
|
+
نمونه
|
|
173
|
+
|
|
174
|
+
mobile_number
|
|
175
|
+
|
|
176
|
+
str
|
|
177
|
+
|
|
178
|
+
شماره همراه کاربر.
|
|
179
|
+
|
|
180
|
+
09121234567
|
|
181
|
+
|
|
182
|
+
پاسخها:
|
|
183
|
+
|
|
184
|
+
HTTP 200 OK: رمز یکبارمصرف با کامیابی فرستاده شد.
|
|
185
|
+
|
|
186
|
+
{"details": "The OTP code was sent correctly."}
|
|
187
|
+
|
|
188
|
+
HTTP 400 BAD REQUEST: ساختار نادرست mobile_number.
|
|
189
|
+
|
|
190
|
+
HTTP 409 CONFLICT: یک رمز یکبارمصرف پیشتر فرستاده شده و هنوز روایی دارد.
|
|
191
|
+
|
|
192
|
+
{"details": "An OTP code has already been sent. Please wait X seconds before trying again."}
|
|
193
|
+
|
|
194
|
+
HTTP 500 INTERNAL SERVER ERROR: یک دشواری در کارگذار پیش آمده است.
|
|
195
|
+
|
|
196
|
+
2. راستیآزمایی (/api/authenticate/)
|
|
197
|
+
روش: POST شرح: کاربر را با رمز یکبارمصرف فراهم شده، راستیآزمایی میکند. اگر با کامیابی انجام شود، توکنهای JWT (access و refresh) را بازمیگرداند.
|
|
198
|
+
|
|
199
|
+
دادهنماهای نیاز:
|
|
200
|
+
|
|
201
|
+
دادهنما
|
|
202
|
+
|
|
203
|
+
گونه
|
|
204
|
+
|
|
205
|
+
شرح
|
|
206
|
+
|
|
207
|
+
نمونه
|
|
208
|
+
|
|
209
|
+
mobile_number
|
|
210
|
+
|
|
211
|
+
str
|
|
212
|
+
|
|
213
|
+
شماره همراه کاربر.
|
|
214
|
+
|
|
215
|
+
09121234567
|
|
216
|
+
|
|
217
|
+
code
|
|
218
|
+
|
|
219
|
+
str
|
|
220
|
+
|
|
221
|
+
رمز یکبارمصرف دریافت شده از راه پیامک.
|
|
222
|
+
|
|
223
|
+
12345678
|
|
224
|
+
|
|
225
|
+
group
|
|
226
|
+
|
|
227
|
+
int
|
|
228
|
+
|
|
229
|
+
اختیاری: یک شناسه دسته برای کاربر.
|
|
230
|
+
|
|
231
|
+
1
|
|
232
|
+
|
|
233
|
+
پاسخها:
|
|
234
|
+
|
|
235
|
+
HTTP 200 OK: راستیآزمایی با کامیابی انجام شد.
|
|
236
|
+
|
|
237
|
+
{
|
|
238
|
+
"access": "...",
|
|
239
|
+
"refresh": "..."
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
HTTP 401 UNAUTHORIZED: رمز یکبارمصرف ناروا یا پایانیافته.
|
|
243
|
+
|
|
244
|
+
{"error": "The code sent to this mobile number was not found."}
|
|
245
|
+
|
|
246
|
+
HTTP 400 BAD REQUEST: دادهنماهای نیاز ناپیدا یا ساختار نادرست.
|
|
247
|
+
|
|
248
|
+
HTTP 500 INTERNAL SERVER ERROR: یک دشواری در کارگذار پیش آمده است.
|
|
249
|
+
|
|
250
|
+
3. فرستادن پیامک (/api/message/send/)
|
|
251
|
+
روش: POST شرح: یک پیامک خودساخته را با یاریدهنده پیکربندیشده میفرستد.
|
|
252
|
+
|
|
253
|
+
دادهنماهای نیاز:
|
|
254
|
+
|
|
255
|
+
دادهنما
|
|
256
|
+
|
|
257
|
+
گونه
|
|
258
|
+
|
|
259
|
+
شرح
|
|
260
|
+
|
|
261
|
+
نمونه
|
|
262
|
+
|
|
263
|
+
mobile_number
|
|
264
|
+
|
|
265
|
+
str
|
|
266
|
+
|
|
267
|
+
شماره همراه گیرنده.
|
|
268
|
+
|
|
269
|
+
09121234567
|
|
270
|
+
|
|
271
|
+
message_text
|
|
272
|
+
|
|
273
|
+
str
|
|
274
|
+
|
|
275
|
+
نوشتار پیام. (بیشینه ۲۹۰ نویسه)
|
|
276
|
+
|
|
277
|
+
Hello, World!
|
|
278
|
+
|
|
279
|
+
template_id
|
|
280
|
+
|
|
281
|
+
int
|
|
282
|
+
|
|
283
|
+
برای برخی یاریدهندهها (برای نمونه پارسیان) نیاز است.
|
|
284
|
+
|
|
285
|
+
1
|
|
286
|
+
|
|
287
|
+
پاسخها:
|
|
288
|
+
|
|
289
|
+
HTTP 200 OK: پیام با کامیابی فرستاده شد.
|
|
290
|
+
|
|
291
|
+
{"details": "The Message was sent correctly."}
|
|
292
|
+
|
|
293
|
+
HTTP 400 BAD REQUEST: دشواریهای درستسنجی برای دادهنماها.
|
|
294
|
+
|
|
295
|
+
HTTP 401 UNAUTHORIZED: راستیآزمایی انجام نشد.
|
|
296
|
+
|
|
297
|
+
HTTP 500 INTERNAL SERVER ERROR: یک دشواری در کارگذار پیش آمده است.
|
|
298
|
+
|
|
299
|
+
HTTP 502 BAD GATEWAY: یاریدهنده پیامکی یک دشواری را بازگرداند.
|
|
300
|
+
|
|
301
|
+
4. فهرست نشستها (/api/sessions/)
|
|
302
|
+
روش: GET شرح: همه نشستهای فعال کاربران را فهرست میکند. نیاز به راستیآزمایی (IsAuthenticated) دارد.
|
|
303
|
+
|
|
304
|
+
سربرگهای نیاز:
|
|
305
|
+
|
|
306
|
+
سربرگ
|
|
307
|
+
|
|
308
|
+
ارزش
|
|
309
|
+
|
|
310
|
+
Authorization
|
|
311
|
+
|
|
312
|
+
Bearer <your_access_token>
|
|
313
|
+
|
|
314
|
+
💡 مدلها
|
|
315
|
+
این بسته یک مدل Session برای درایوری نشستهای فعال کاربران دارد. میتوانید به این نشستها از راه نقطه پایانی /api/sessions/ دسترسی یافته و آنها را درایوری کنید.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
django_chelseru-1.0.3.dist-info/licenses/LICENSE,sha256=VupU5KV4NteHaNQb-WH31G_WZWezxXoomjiCIAHoQJo,1089
|
|
2
|
+
drfchelseru/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
drfchelseru/admin.py,sha256=sHmAevggh4O6sQJI1oFB_0vaDfXQ6vx7lP42GeJ6gSg,836
|
|
4
|
+
drfchelseru/apps.py,sha256=hOTTzFGLXiTPZeN8p_LLcSECLtsR2Q0SUo8zJzgM-qQ,211
|
|
5
|
+
drfchelseru/consumers.py,sha256=3qP7nR56ozH2tMvhz4Wi7WsBg4qIS-Z4hwQ2A5r1HnA,2534
|
|
6
|
+
drfchelseru/middlewares.py,sha256=adrhoJE7uo4WISfSIXK41XMQQzMaBshq3r61RblMPAQ,2570
|
|
7
|
+
drfchelseru/models.py,sha256=w_UMGSjOQUIrtg5iGmK5UTaC85lJfQoA-iEdkNHHwNw,3730
|
|
8
|
+
drfchelseru/routing.py,sha256=SEWMBmRFZs1NkKLuppXpmvRmyWiTY3KVIHmA4EpUyyI,167
|
|
9
|
+
drfchelseru/serializers.py,sha256=tM7dN362Vdx0UXq6OfNmYh6rTN1bwFoEgOn1MG4CZIY,1299
|
|
10
|
+
drfchelseru/services.py,sha256=V8mdeeHjmqUFAQLAyaEm3JTk-E8V8SfQMQhXoYdjt_c,10134
|
|
11
|
+
drfchelseru/settings.py,sha256=pvGW6aMCrkW9kph_Zw6B_9qYeWb7wuIrUM1Lnu8kjhU,9055
|
|
12
|
+
drfchelseru/signals.py,sha256=2jISUF_jQVZD06tOqHasXtneZKVCrpeIJQeHo9BjLc0,1428
|
|
13
|
+
drfchelseru/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
|
|
14
|
+
drfchelseru/urls.py,sha256=2RllsBPgojGLxLkyaBsSGk7fU1q44nuA7I_VpeHCmbs,702
|
|
15
|
+
drfchelseru/validators.py,sha256=jAJASfG4kPcqrGxxIcoR8VjS4L4n_EzZuxJtQ1g9qmo,480
|
|
16
|
+
drfchelseru/views.py,sha256=9m0lNPQBBbw5cl_WLmWL_lf3DHRvp9v45AmiUYLviSE,12620
|
|
17
|
+
drfchelseru/migrations/0001_initial.py,sha256=LfkWAeQuPXNW3_igM-w_pyKafCeHQF3aReYgMXfMzEE,1034
|
|
18
|
+
drfchelseru/migrations/0002_otpcode_session_user.py,sha256=c3oyBTQo2kjsKr5r_XQrqaq-66F7pui-4H4mEyAXgJk,3126
|
|
19
|
+
drfchelseru/migrations/0003_rename_mobile_otpcode_mobile_number.py,sha256=eiD7t6etg6nOeT7BhJrSfEcR1yTPePXtATgtyu30520,377
|
|
20
|
+
drfchelseru/migrations/0004_rename_message_message_sms.py,sha256=FxsPAtgioB-Ei6cZFj7B0E1DpBDiYJvqKtom9YiXknM,357
|
|
21
|
+
drfchelseru/migrations/0005_rename_message_sms_messagesms_chatroom_messagechat.py,sha256=ZJfFmeF4pXuhDSdz8ilOxXEBujEhnFQCAv564Dh4CGc,1586
|
|
22
|
+
drfchelseru/migrations/0006_alter_chatroom_user_1_alter_chatroom_user_2_and_more.py,sha256=m7ew_V1jXVPOVkKZO7WoWS473hMRZU_NwfGDnU_ZN4o,1394
|
|
23
|
+
drfchelseru/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
django_chelseru-1.0.3.dist-info/METADATA,sha256=Y8drr6tJnUk04Z84_hhFm_2cACWE_n5bI2vDMHrSxls,9655
|
|
25
|
+
django_chelseru-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
django_chelseru-1.0.3.dist-info/top_level.txt,sha256=fsaO1F03W3j4AYi0TfDGv5Cjb_Qrh6RSkwkWqfqaMns,12
|
|
27
|
+
django_chelseru-1.0.3.dist-info/RECORD,,
|
drfchelseru/admin.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
|
-
from .models import User, OTPCode, Session,
|
|
2
|
+
from .models import User, OTPCode, Session, MessageSMS, ChatRoom, MessageChat
|
|
3
|
+
|
|
3
4
|
|
|
4
5
|
@admin.register(User)
|
|
5
6
|
class MobileAdmin(admin.ModelAdmin):
|
|
@@ -17,8 +18,12 @@ class SessionAdmin(admin.ModelAdmin):
|
|
|
17
18
|
list_display = ['id', 'user__id', 'user__username', 'ip_address', 'last_seen']
|
|
18
19
|
ordering = ('-created_at', )
|
|
19
20
|
|
|
20
|
-
@admin.register(
|
|
21
|
+
@admin.register(MessageSMS)
|
|
21
22
|
class MessageAdmin(admin.ModelAdmin):
|
|
22
23
|
list_display = ['id', 'mobile_number', 'message_text', 'status']
|
|
23
24
|
ordering = ('-created_at', )
|
|
24
25
|
|
|
26
|
+
|
|
27
|
+
admin.site.register(ChatRoom)
|
|
28
|
+
admin.site.register(MessageChat)
|
|
29
|
+
|
drfchelseru/consumers.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from channels.generic.websocket import AsyncWebsocketConsumer
|
|
3
|
+
from .models import ChatRoom, MessageChat
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from asgiref.sync import sync_to_async
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
User = get_user_model()
|
|
9
|
+
|
|
10
|
+
class ChatConsumer(AsyncWebsocketConsumer):
|
|
11
|
+
@sync_to_async
|
|
12
|
+
def is_user_in_chat_room(self, user, chat_room):
|
|
13
|
+
return user == chat_room.user_1 or user == chat_room.user_2
|
|
14
|
+
|
|
15
|
+
async def connect(self):
|
|
16
|
+
user = self.scope["user"]
|
|
17
|
+
if user.is_authenticated:
|
|
18
|
+
self.user = user
|
|
19
|
+
self.chat_room_id = self.scope['url_route']['kwargs']['chat_room_id']
|
|
20
|
+
self.chat_room = await sync_to_async(ChatRoom.objects.get)(id=self.chat_room_id)
|
|
21
|
+
|
|
22
|
+
if not await self.is_user_in_chat_room(user, self.chat_room):
|
|
23
|
+
await self.close()
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
self.room_group_name = f"chat_{self.chat_room.id}"
|
|
27
|
+
|
|
28
|
+
# Join room group
|
|
29
|
+
await self.channel_layer.group_add(
|
|
30
|
+
self.room_group_name,
|
|
31
|
+
self.channel_name
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
await self.accept()
|
|
35
|
+
else:
|
|
36
|
+
await self.close()
|
|
37
|
+
|
|
38
|
+
async def disconnect(self, close_code):
|
|
39
|
+
# Leave room group
|
|
40
|
+
await self.channel_layer.group_discard(
|
|
41
|
+
self.room_group_name,
|
|
42
|
+
self.channel_name
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def receive(self, text_data):
|
|
46
|
+
user = self.scope["user"]
|
|
47
|
+
if not user.is_authenticated:
|
|
48
|
+
await self.close()
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
text_data_json = json.loads(text_data)
|
|
52
|
+
message = text_data_json['message']
|
|
53
|
+
sender_id = self.scope['user'].id
|
|
54
|
+
# sender_id = text_data_json['sender_id']
|
|
55
|
+
sender = await sync_to_async(User.objects.get)(id=sender_id)
|
|
56
|
+
|
|
57
|
+
# Save message to database
|
|
58
|
+
chat_message = await sync_to_async(MessageChat.objects.create)(
|
|
59
|
+
chat_room=self.chat_room,
|
|
60
|
+
sender=sender,
|
|
61
|
+
text=message
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Send message to room group
|
|
65
|
+
await self.channel_layer.group_send(
|
|
66
|
+
self.room_group_name,
|
|
67
|
+
{
|
|
68
|
+
'type': 'chat_message',
|
|
69
|
+
'message': chat_message.text,
|
|
70
|
+
'sender': sender.username
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
async def chat_message(self, event):
|
|
75
|
+
message = event['message']
|
|
76
|
+
sender = event['sender']
|
|
77
|
+
|
|
78
|
+
# Send message to WebSocket
|
|
79
|
+
await self.send(text_data=json.dumps({
|
|
80
|
+
'message': message,
|
|
81
|
+
'sender': sender
|
|
82
|
+
}))
|
drfchelseru/middlewares.py
CHANGED
|
@@ -2,6 +2,13 @@ from django.utils.timezone import datetime
|
|
|
2
2
|
from .models import Session
|
|
3
3
|
import user_agents
|
|
4
4
|
|
|
5
|
+
from urllib.parse import parse_qs
|
|
6
|
+
from channels.middleware import BaseMiddleware
|
|
7
|
+
from django.contrib.auth.models import AnonymousUser
|
|
8
|
+
from rest_framework_simplejwt.tokens import AccessToken
|
|
9
|
+
from django.contrib.auth import get_user_model
|
|
10
|
+
from asgiref.sync import sync_to_async
|
|
11
|
+
|
|
5
12
|
|
|
6
13
|
class TakeUserSessionMiddlaware:
|
|
7
14
|
def __init__(self, get_response):
|
|
@@ -41,4 +48,33 @@ class TakeUserSessionMiddlaware:
|
|
|
41
48
|
x_forwarded_for = request.META.get('X_FORWARDED_FOR')
|
|
42
49
|
if x_forwarded_for:
|
|
43
50
|
return x_forwarded_for.split(',')[0]
|
|
44
|
-
return request.META.get('REMOTE_ADDR')
|
|
51
|
+
return request.META.get('REMOTE_ADDR')
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
User = get_user_model()
|
|
56
|
+
|
|
57
|
+
@sync_to_async
|
|
58
|
+
def get_user(validated_token):
|
|
59
|
+
try:
|
|
60
|
+
user_id = validated_token["user_id"]
|
|
61
|
+
return User.objects.get(id=user_id)
|
|
62
|
+
except User.DoesNotExist:
|
|
63
|
+
return AnonymousUser()
|
|
64
|
+
|
|
65
|
+
class JWTAuthMiddleware(BaseMiddleware):
|
|
66
|
+
async def __call__(self, scope, receive, send):
|
|
67
|
+
query_string = scope.get("query_string", b"").decode()
|
|
68
|
+
query_params = parse_qs(query_string)
|
|
69
|
+
token = query_params.get("token")
|
|
70
|
+
|
|
71
|
+
if token:
|
|
72
|
+
try:
|
|
73
|
+
access_token = AccessToken(token[0])
|
|
74
|
+
scope["user"] = await get_user(access_token)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
scope["user"] = AnonymousUser()
|
|
77
|
+
else:
|
|
78
|
+
scope["user"] = AnonymousUser()
|
|
79
|
+
|
|
80
|
+
return await super().__call__(scope, receive, send)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-08-14 19:00
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('drfchelseru', '0003_rename_mobile_otpcode_mobile_number'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.RenameModel(
|
|
14
|
+
old_name='Message',
|
|
15
|
+
new_name='Message_SMS',
|
|
16
|
+
),
|
|
17
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-08-14 19:01
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('drfchelseru', '0004_rename_message_message_sms'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.RenameModel(
|
|
15
|
+
old_name='Message_SMS',
|
|
16
|
+
new_name='MessageSMS',
|
|
17
|
+
),
|
|
18
|
+
migrations.CreateModel(
|
|
19
|
+
name='ChatRoom',
|
|
20
|
+
fields=[
|
|
21
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
22
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
23
|
+
('user_1', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user1_chats', to='drfchelseru.user')),
|
|
24
|
+
('user_2', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user2_chats', to='drfchelseru.user')),
|
|
25
|
+
],
|
|
26
|
+
),
|
|
27
|
+
migrations.CreateModel(
|
|
28
|
+
name='MessageChat',
|
|
29
|
+
fields=[
|
|
30
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
31
|
+
('text', models.TextField()),
|
|
32
|
+
('timestamp', models.DateTimeField(auto_now_add=True)),
|
|
33
|
+
('chat_room', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='drfchelseru.chatroom')),
|
|
34
|
+
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='drfchelseru.user')),
|
|
35
|
+
],
|
|
36
|
+
),
|
|
37
|
+
]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated by Django 5.1.6 on 2025-08-14 19:36
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
|
|
10
|
+
dependencies = [
|
|
11
|
+
('drfchelseru', '0005_rename_message_sms_messagesms_chatroom_messagechat'),
|
|
12
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
operations = [
|
|
16
|
+
migrations.AlterField(
|
|
17
|
+
model_name='chatroom',
|
|
18
|
+
name='user_1',
|
|
19
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user1_chats_drf_chelseru', to=settings.AUTH_USER_MODEL),
|
|
20
|
+
),
|
|
21
|
+
migrations.AlterField(
|
|
22
|
+
model_name='chatroom',
|
|
23
|
+
name='user_2',
|
|
24
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user2_chats_drf_chelseru', to=settings.AUTH_USER_MODEL),
|
|
25
|
+
),
|
|
26
|
+
migrations.AlterField(
|
|
27
|
+
model_name='messagechat',
|
|
28
|
+
name='chat_room',
|
|
29
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages_chat_drf_chelseru', to='drfchelseru.chatroom'),
|
|
30
|
+
),
|
|
31
|
+
migrations.AlterField(
|
|
32
|
+
model_name='messagechat',
|
|
33
|
+
name='sender',
|
|
34
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
|
35
|
+
),
|
|
36
|
+
]
|
drfchelseru/models.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from django.db import models
|
|
2
|
+
from django.contrib.auth import get_user_model
|
|
2
3
|
from django.contrib.auth.models import User as default_user
|
|
3
4
|
from django.utils.timezone import now, timedelta
|
|
4
5
|
from random import randint
|
|
5
6
|
from .settings import auth_init_check
|
|
6
7
|
|
|
8
|
+
UserGet = get_user_model()
|
|
7
9
|
|
|
8
10
|
class User(models.Model):
|
|
9
11
|
user = models.OneToOneField(default_user, on_delete=models.CASCADE, related_name='mobile_drf_chelseru')
|
|
@@ -64,7 +66,7 @@ class Session(models.Model):
|
|
|
64
66
|
return f'{self.user} - {self.ip_address}'
|
|
65
67
|
|
|
66
68
|
|
|
67
|
-
class
|
|
69
|
+
class MessageSMS(models.Model):
|
|
68
70
|
message_text = models.TextField()
|
|
69
71
|
mobile_number = models.CharField(max_length=20)
|
|
70
72
|
_from = models.CharField(max_length=20, blank=True, null=True)
|
|
@@ -74,4 +76,25 @@ class Message(models.Model):
|
|
|
74
76
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
75
77
|
|
|
76
78
|
def __str__(self):
|
|
77
|
-
return f'to: {self.mobile_number} , at: {self.created_at}'
|
|
79
|
+
return f'to: {self.mobile_number} , at: {self.created_at}'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class ChatRoom(models.Model):
|
|
84
|
+
user_1 = models.ForeignKey(UserGet, on_delete=models.CASCADE, related_name='user1_chats_drf_chelseru')
|
|
85
|
+
user_2 = models.ForeignKey(UserGet, on_delete=models.CASCADE, related_name='user2_chats_drf_chelseru')
|
|
86
|
+
|
|
87
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
88
|
+
|
|
89
|
+
def __str__(self):
|
|
90
|
+
return f"ID: {self.id} | Chat between {self.user_1.username} and {self.user_2.username}"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class MessageChat(models.Model):
|
|
94
|
+
chat_room = models.ForeignKey(ChatRoom, on_delete=models.CASCADE, related_name='messages_chat_drf_chelseru')
|
|
95
|
+
sender = models.ForeignKey(UserGet, on_delete=models.CASCADE)
|
|
96
|
+
text = models.TextField()
|
|
97
|
+
timestamp = models.DateTimeField(auto_now_add=True)
|
|
98
|
+
|
|
99
|
+
def __str__(self):
|
|
100
|
+
return f"iD: {self.id} | Message from {self.sender.username} at {self.timestamp} | Chatroom ID: {self.chat_room.id}"
|
drfchelseru/routing.py
ADDED
drfchelseru/serializers.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from rest_framework.serializers import ModelSerializer
|
|
2
2
|
from django.contrib.auth.models import User
|
|
3
|
-
from .models import User as mobile, OTPCode, Session,
|
|
3
|
+
from .models import User as mobile, OTPCode, Session, MessageSMS, ChatRoom, MessageChat
|
|
4
4
|
|
|
5
|
+
# from django.contrib.auth import get_user_model
|
|
6
|
+
|
|
7
|
+
# UserGet = get_user_model()
|
|
5
8
|
|
|
6
9
|
class DefaultUserSerializer(ModelSerializer):
|
|
7
10
|
class Meta:
|
|
@@ -30,5 +33,22 @@ class SessionSerializer(ModelSerializer):
|
|
|
30
33
|
|
|
31
34
|
class MessageSerializer(ModelSerializer):
|
|
32
35
|
class Meta:
|
|
33
|
-
model =
|
|
34
|
-
fields = '__all__'
|
|
36
|
+
model = MessageSMS
|
|
37
|
+
fields = '__all__'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ChatRoomSerializer(ModelSerializer):
|
|
41
|
+
user_1, user_2 = DefaultUserSerializer(read_only=True), DefaultUserSerializer(read_only=True)
|
|
42
|
+
class Meta:
|
|
43
|
+
model = ChatRoom
|
|
44
|
+
fields = '__all__'
|
|
45
|
+
depth = 1
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MessageChatSerializer(ModelSerializer):
|
|
49
|
+
sender = DefaultUserSerializer(read_only=True)
|
|
50
|
+
|
|
51
|
+
class Meta:
|
|
52
|
+
model = MessageChat
|
|
53
|
+
fields = '__all__'
|
|
54
|
+
depth = 1
|
drfchelseru/services.py
CHANGED
|
@@ -155,7 +155,7 @@ class KavenegarCom:
|
|
|
155
155
|
def send_message(mobile_number, message_text, data, template_id=None):
|
|
156
156
|
try:
|
|
157
157
|
icheck = sms_init_check()
|
|
158
|
-
if not (icheck and isinstance(icheck, dict) and 'SMS_SERVICE' in icheck and '
|
|
158
|
+
if not (icheck and isinstance(icheck, dict) and 'SMS_SERVICE' in icheck and 'SETTINGS' in icheck):
|
|
159
159
|
raise 'SMS service settings are not configured correctly.'
|
|
160
160
|
|
|
161
161
|
except ImproperlyConfigured as e:
|
|
@@ -163,7 +163,7 @@ def send_message(mobile_number, message_text, data, template_id=None):
|
|
|
163
163
|
raise
|
|
164
164
|
|
|
165
165
|
sms_service = icheck['SMS_SERVICE']
|
|
166
|
-
options = icheck['
|
|
166
|
+
options = icheck['SETTINGS']
|
|
167
167
|
response_data = None
|
|
168
168
|
response_status_code = HTTP_500_INTERNAL_SERVER_ERROR
|
|
169
169
|
response_bool = False
|
drfchelseru/settings.py
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
"""
|
|
2
2
|
DJANGO_CHELSERU = {
|
|
3
3
|
'AUTH': {
|
|
4
|
-
'AUTH_METHOD'
|
|
5
|
-
'AUTH_SERVICE'
|
|
4
|
+
'AUTH_METHOD' : 'OTP', # OTP, PASSWD
|
|
5
|
+
'AUTH_SERVICE' : 'rest_framework_simplejwt', # rest_framework_simplejwt
|
|
6
6
|
'OPTIONS': {
|
|
7
7
|
'OTP_LENGTH' : 8, # DEFAULT 8
|
|
8
8
|
'OTP_EXPIRE_PER_MINUTES': 4, # DEFAULT 4
|
|
9
|
-
'
|
|
9
|
+
'OTP_SMS_TEMPLATE_ID' : 1,
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
'SMS': {
|
|
13
13
|
'SMS_SERVICE': 'PARSIAN_WEBCO_IR', # PARSIAN_WEBCO_IR , MELI_PAYAMAK_COM , KAVENEGAR_COM
|
|
14
|
-
'
|
|
14
|
+
'SETTINGS': {
|
|
15
15
|
'PARSIAN_WEBCO_IR_API_KEY' : '',
|
|
16
16
|
'MELI_PAYAMAK_COM_USERNAME' : '',
|
|
17
17
|
'MELI_PAYAMAK_COM_PASSWORD' : '',
|
|
18
18
|
'MELI_PAYAMAK_COM_FROM' : '',
|
|
19
19
|
'KAVENEGAR_COM_API_KEY' : '656F6635756C485658666F6A52307562456C4F5043714769597A58434D2B527974434534672B50445736553D',
|
|
20
20
|
'KAVENEGAR_COM_FROM' : '2000660110',
|
|
21
|
+
},
|
|
22
|
+
'TEMPLATES': {
|
|
23
|
+
'T1': 1,
|
|
24
|
+
'T2': 2,
|
|
25
|
+
'T3': 3,
|
|
26
|
+
'T4': 4,
|
|
27
|
+
'T5': 5,
|
|
28
|
+
'T6': 6,
|
|
29
|
+
'T7': 7,
|
|
30
|
+
'T8': 8,
|
|
31
|
+
'T9': 9,
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
}
|
|
@@ -51,7 +62,7 @@ def auth_init_check():
|
|
|
51
62
|
_auth_service = _auth.get('AUTH_SERVICE')
|
|
52
63
|
_opt_len = _auth.get('OPTIONS').get('OTP_LENGTH', 6)
|
|
53
64
|
_opt_exp_time = _auth.get('OPTIONS').get('OTP_EXPIRE_PER_MINUTES', 4)
|
|
54
|
-
_otp_sms_template = _auth.get('OPTIONS').get('
|
|
65
|
+
_otp_sms_template = _auth.get('OPTIONS').get('OTP_SMS_TEMPLATE_ID', 0)
|
|
55
66
|
|
|
56
67
|
if _auth_mode:
|
|
57
68
|
if _auth_mode in list(map(lambda x: x[1], AUTH_METHOD)):
|
|
@@ -95,6 +106,7 @@ def sms_init_check():
|
|
|
95
106
|
try:
|
|
96
107
|
sms_service = None
|
|
97
108
|
options = {}
|
|
109
|
+
templates = {}
|
|
98
110
|
if not hasattr(settings, SERVICE_NAME):
|
|
99
111
|
raise ImproperlyConfigured(f'{SERVICE_NAME} must be defined in settings.py.')
|
|
100
112
|
|
|
@@ -103,6 +115,7 @@ def sms_init_check():
|
|
|
103
115
|
raise ImproperlyConfigured(f'SMS key must be defined in {SERVICE_NAME}')
|
|
104
116
|
|
|
105
117
|
else:
|
|
118
|
+
templates = getattr(settings, SERVICE_NAME).get('SMS').get('TEMPLATES', {})
|
|
106
119
|
sms_service = getattr(settings, SERVICE_NAME).get('SMS').get('SMS_SERVICE')
|
|
107
120
|
if not sms_service:
|
|
108
121
|
raise ImproperlyConfigured(f'SMS_SERVICE key must be defined in {SERVICE_NAME}: SMS .')
|
|
@@ -112,58 +125,58 @@ def sms_init_check():
|
|
|
112
125
|
raise ImproperlyConfigured(f'SMS_SERVICE must be choice between {list(map(lambda x: x[1], SMS_SERVICES))}.')
|
|
113
126
|
|
|
114
127
|
else:
|
|
115
|
-
if not getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
116
|
-
raise ImproperlyConfigured(f'
|
|
128
|
+
if not getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS'):
|
|
129
|
+
raise ImproperlyConfigured(f'SETTINGS key must be defined in {SERVICE_NAME}: SMS .')
|
|
117
130
|
|
|
118
131
|
else:
|
|
119
132
|
if sms_service == 'PARSIAN_WEBCO_IR':
|
|
120
|
-
api_key = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
133
|
+
api_key = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('PARSIAN_WEBCO_IR_API_KEY')
|
|
121
134
|
if not api_key:
|
|
122
|
-
raise ImproperlyConfigured(f'PARSIAN_WEBCO_IR_API_KEY
|
|
135
|
+
raise ImproperlyConfigured(f'PARSIAN_WEBCO_IR_API_KEY must be defined in {SERVICE_NAME}: SMS: SETTINGS, To access the SMS service API, you need to have API keys.')
|
|
123
136
|
|
|
124
137
|
else:
|
|
125
138
|
options['api_key'] = api_key
|
|
126
139
|
|
|
127
140
|
# -------------------------------------
|
|
128
141
|
elif sms_service == 'MELI_PAYAMAK_COM':
|
|
129
|
-
username = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
142
|
+
username = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('MELI_PAYAMAK_COM_USERNAME')
|
|
130
143
|
if not username:
|
|
131
|
-
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_USERNAME
|
|
144
|
+
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_USERNAME must be defined in {SERVICE_NAME}: SMS: SETTINGS, To access the SMS service API, you need to have API keys.')
|
|
132
145
|
|
|
133
146
|
else:
|
|
134
147
|
options['username'] = username
|
|
135
148
|
|
|
136
|
-
password = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
149
|
+
password = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('MELI_PAYAMAK_COM_PASSWORD')
|
|
137
150
|
if not password:
|
|
138
|
-
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_PASSWORD
|
|
151
|
+
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_PASSWORD must be defined in {SERVICE_NAME}: SMS: SETTINGS, To access the SMS service API, you need to have API keys.')
|
|
139
152
|
|
|
140
153
|
else:
|
|
141
154
|
options['password'] = password
|
|
142
155
|
|
|
143
|
-
_from = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
156
|
+
_from = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('MELI_PAYAMAK_COM_FROM')
|
|
144
157
|
if not _from:
|
|
145
|
-
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_FROM
|
|
158
|
+
raise ImproperlyConfigured(f'MELI_PAYAMAK_COM_FROM must be defined in {SERVICE_NAME}: SMS: SETTINGS, To send an SMS, the sender`s number is required.')
|
|
146
159
|
|
|
147
160
|
else:
|
|
148
161
|
options['from'] = _from
|
|
149
162
|
|
|
150
163
|
# -------------------------------------
|
|
151
164
|
elif sms_service == 'KAVENEGAR_COM':
|
|
152
|
-
api_key = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
165
|
+
api_key = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('KAVENEGAR_COM_API_KEY')
|
|
153
166
|
if not api_key:
|
|
154
|
-
raise ImproperlyConfigured(f'KAVENEGAR_COM_API_KEY
|
|
167
|
+
raise ImproperlyConfigured(f'KAVENEGAR_COM_API_KEY must be defined in {SERVICE_NAME}: SMS: SETTINGS, To access the SMS service API, you need to have API keys.')
|
|
155
168
|
|
|
156
169
|
else:
|
|
157
170
|
options['api_key'] = api_key
|
|
158
171
|
|
|
159
|
-
_from = getattr(settings, SERVICE_NAME).get('SMS').get('
|
|
172
|
+
_from = getattr(settings, SERVICE_NAME).get('SMS').get('SETTINGS').get('KAVENEGAR_COM_FROM')
|
|
160
173
|
if not _from:
|
|
161
|
-
raise ImproperlyConfigured(f'KAVENEGAR_COM_FROM
|
|
174
|
+
raise ImproperlyConfigured(f'KAVENEGAR_COM_FROM must be defined in {SERVICE_NAME}: SMS: SETTINGS, To send an SMS, the sender`s number is required.')
|
|
162
175
|
|
|
163
176
|
else:
|
|
164
177
|
options['from'] = _from
|
|
165
178
|
|
|
166
|
-
return {'SMS_SERVICE': sms_service, '
|
|
179
|
+
return {'SMS_SERVICE': sms_service, 'SETTINGS': options, 'TEMPLATES': templates}
|
|
167
180
|
except ImproperlyConfigured as e:
|
|
168
181
|
print(f"Configuration Error: {e}")
|
|
169
182
|
raise
|
drfchelseru/urls.py
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
from django.urls import path
|
|
2
|
-
from .
|
|
1
|
+
from django.urls import path, include
|
|
2
|
+
from rest_framework.routers import DefaultRouter
|
|
3
|
+
from .views import MessageSend, OTPCodeSend ,Authentication, SessionList, MessageViewSet, ChatRoomViewSet
|
|
3
4
|
|
|
4
5
|
app_name = 'drfchelseru'
|
|
5
6
|
|
|
7
|
+
router = DefaultRouter()
|
|
8
|
+
router.register(r'chatrooms', ChatRoomViewSet, basename='chatroom')
|
|
9
|
+
router.register(r'messages', MessageViewSet, basename='messages')
|
|
10
|
+
|
|
6
11
|
urlpatterns = [
|
|
7
12
|
path('message/send/', MessageSend.as_view(), name='message-send'),
|
|
8
13
|
path('otp/send/', OTPCodeSend.as_view(), name='otp-send'),
|
|
9
14
|
path('authenticate/', Authentication.as_view(), name='auth'),
|
|
10
15
|
path('sessions/', SessionList.as_view(), name='sessions'),
|
|
11
|
-
]
|
|
16
|
+
] + [path('chat/', include(router.urls)),]
|
drfchelseru/views.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
+
from rest_framework.viewsets import ModelViewSet
|
|
1
2
|
from rest_framework.views import APIView
|
|
2
3
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|
3
4
|
from rest_framework.generics import ListAPIView
|
|
4
5
|
from rest_framework.response import Response
|
|
5
6
|
from rest_framework.status import HTTP_200_OK, HTTP_204_NO_CONTENT, HTTP_500_INTERNAL_SERVER_ERROR, HTTP_502_BAD_GATEWAY, HTTP_401_UNAUTHORIZED, HTTP_400_BAD_REQUEST, HTTP_409_CONFLICT
|
|
7
|
+
from rest_framework.exceptions import NotFound, ValidationError
|
|
8
|
+
from django.contrib.auth.models import User as UserDefault
|
|
9
|
+
|
|
6
10
|
from .services import send_message
|
|
7
11
|
from .settings import sms_init_check, auth_init_check
|
|
8
12
|
from .validators import mobile_number as mobile_validator
|
|
9
|
-
from .serializers import MessageSerializer, OTPCodeSerializer, SessionSerializer
|
|
10
|
-
from .models import User
|
|
13
|
+
from .serializers import MessageSerializer, OTPCodeSerializer, SessionSerializer, ChatRoomSerializer, MessageChatSerializer
|
|
14
|
+
from .models import User, ChatRoom
|
|
11
15
|
from django.utils.timezone import now, timedelta
|
|
12
16
|
from django.db import transaction
|
|
13
17
|
|
|
@@ -210,4 +214,52 @@ class Authentication(APIView):
|
|
|
210
214
|
class SessionList(ListAPIView):
|
|
211
215
|
permission_classes = (IsAuthenticated, )
|
|
212
216
|
serializer_class = SessionSerializer
|
|
213
|
-
queryset = serializer_class.Meta.model.objects.all()
|
|
217
|
+
queryset = serializer_class.Meta.model.objects.all()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ChatRoomViewSet(ModelViewSet):
|
|
221
|
+
serializer_class = ChatRoomSerializer
|
|
222
|
+
permission_classes = [IsAuthenticated]
|
|
223
|
+
model = serializer_class.Meta.model
|
|
224
|
+
|
|
225
|
+
def get_queryset(self):
|
|
226
|
+
return self.model.objects.filter(user_1=self.request.user) | self.model.objects.filter(user_2=self.request.user)
|
|
227
|
+
|
|
228
|
+
def perform_create(self, serializer):
|
|
229
|
+
user = self.request.user
|
|
230
|
+
|
|
231
|
+
user_id = self.request.data.get('user', None)
|
|
232
|
+
user_2 = UserDefault.objects.filter(id=user_id).first()
|
|
233
|
+
if not user_2:
|
|
234
|
+
raise NotFound("کاربر مورد نظر با آی دی فرستاده شده یافت نشد.")
|
|
235
|
+
|
|
236
|
+
chat_room = serializer.save(user_1=user, user_2=user_2)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class MessageViewSet(ModelViewSet):
|
|
240
|
+
serializer_class = MessageChatSerializer
|
|
241
|
+
permission_classes = [IsAuthenticated]
|
|
242
|
+
|
|
243
|
+
def get_queryset(self):
|
|
244
|
+
user = self.request.user
|
|
245
|
+
|
|
246
|
+
queryset = self.serializer_class.Meta.model.objects.all()
|
|
247
|
+
chat_room_id = self.request.query_params.get('chat_room')
|
|
248
|
+
if chat_room_id:
|
|
249
|
+
queryset = queryset.filter(chat_room_id=chat_room_id)
|
|
250
|
+
return queryset
|
|
251
|
+
|
|
252
|
+
def perform_create(self, serializer):
|
|
253
|
+
chat_room_id = self.request.data.get('chat_room')
|
|
254
|
+
if not chat_room_id:
|
|
255
|
+
raise ValidationError("فیلد chat_room اجباریه.")
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
chat = ChatRoom.objects.get(id=chat_room_id)
|
|
259
|
+
except ChatRoom.DoesNotExist:
|
|
260
|
+
raise NotFound("چتروم پیدا نشد.")
|
|
261
|
+
|
|
262
|
+
message = serializer.save(sender=self.request.user, chat_room=chat)
|
|
263
|
+
chat = message.chat_room
|
|
264
|
+
chat.save()
|
|
265
|
+
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: django-chelseru
|
|
3
|
-
Version: 1.0.1
|
|
4
|
-
Summary: Authentication system, online and real-time chat, SMS system for Iranian SMS services.
|
|
5
|
-
Home-page: https://pip-django.chelseru.com
|
|
6
|
-
Author: Sobhan Bahman|Rashnu
|
|
7
|
-
Author-email: bahmanrashnu@gmail.com
|
|
8
|
-
Project-URL: Documentation, https://github.com/Chelseru/django-chelseru-lour/
|
|
9
|
-
Project-URL: Telegram Group, https://t.me/bahmanpy
|
|
10
|
-
Project-URL: Telegram Channel, https://t.me/ChelseruCom
|
|
11
|
-
Keywords: djangochelseruchat djangochat drfchat online-chat online real-time chat iran chelseru lor lur bahman rashnu lour sms djangoauth auth ywt otpauth otp authentication djangootp djangoiransms iransms djangosms djangokavenegar djangomelipayamak sobhan چت سبحان بهمن رشنو چلسرو جنگو پایتون لر لور آنلاین ریل تایم
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Framework :: Django
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
-
Classifier: Operating System :: OS Independent
|
|
16
|
-
Requires-Python: >=3.11
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
License-File: LICENSE
|
|
19
|
-
Requires-Dist: Django>=5.1.6
|
|
20
|
-
Requires-Dist: djangorestframework==3.15.2
|
|
21
|
-
Requires-Dist: djangorestframework_simplejwt==5.5.0
|
|
22
|
-
Requires-Dist: channels==4.2.2
|
|
23
|
-
Requires-Dist: channels_redis==4.2.1
|
|
24
|
-
Requires-Dist: daphne==4.1.2
|
|
25
|
-
Requires-Dist: zeep==4.3.1
|
|
26
|
-
Requires-Dist: user-agents==2.2.0
|
|
27
|
-
Dynamic: author
|
|
28
|
-
Dynamic: author-email
|
|
29
|
-
Dynamic: classifier
|
|
30
|
-
Dynamic: description
|
|
31
|
-
Dynamic: description-content-type
|
|
32
|
-
Dynamic: home-page
|
|
33
|
-
Dynamic: keywords
|
|
34
|
-
Dynamic: license-file
|
|
35
|
-
Dynamic: project-url
|
|
36
|
-
Dynamic: requires-dist
|
|
37
|
-
Dynamic: requires-python
|
|
38
|
-
Dynamic: summary
|
|
39
|
-
|
|
40
|
-
# Django Chelseru
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Installation
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
pip install django-chelseru
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
---
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
## License
|
|
54
|
-
|
|
55
|
-
MIT License
|
|
56
|
-
|
|
57
|
-
Sobhan Bahman | Rashnu
|
|
58
|
-
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
django_chelseru-1.0.1.dist-info/licenses/LICENSE,sha256=VupU5KV4NteHaNQb-WH31G_WZWezxXoomjiCIAHoQJo,1089
|
|
2
|
-
drfchelseru/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
drfchelseru/admin.py,sha256=PmHIgD7XSObOTd9xx6_lgEfAr4Z_2bvkrnrHby1ZdK8,741
|
|
4
|
-
drfchelseru/apps.py,sha256=hOTTzFGLXiTPZeN8p_LLcSECLtsR2Q0SUo8zJzgM-qQ,211
|
|
5
|
-
drfchelseru/middlewares.py,sha256=ONzRCiiHrUicDXFVds2aTROb6QBIcWZyW_jXQgrAQAc,1450
|
|
6
|
-
drfchelseru/models.py,sha256=ffTiDI7a1rVMjMFeeZqBqbkCsciwnKCowf2u8vj6VRs,2774
|
|
7
|
-
drfchelseru/serializers.py,sha256=wYbvFmZuQzGEgi9S6B-kPuIAXJDcir6qKQuV6RgE3J4,769
|
|
8
|
-
drfchelseru/services.py,sha256=pseSSP92GvXH-WzPzAyk17FKZOWe8ogFOYnwctpgqLY,10132
|
|
9
|
-
drfchelseru/settings.py,sha256=w5pPbPOyziyYXwQaQpMAVEECQVFt6BiPr37zXdKJEBo,8573
|
|
10
|
-
drfchelseru/signals.py,sha256=2jISUF_jQVZD06tOqHasXtneZKVCrpeIJQeHo9BjLc0,1428
|
|
11
|
-
drfchelseru/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
|
|
12
|
-
drfchelseru/urls.py,sha256=SbSVU75NXNoH6YWvJrJbuo3kc6jgremOS8g0CLmk52w,410
|
|
13
|
-
drfchelseru/validators.py,sha256=jAJASfG4kPcqrGxxIcoR8VjS4L4n_EzZuxJtQ1g9qmo,480
|
|
14
|
-
drfchelseru/views.py,sha256=L_KcoHe_5SAJ-2xaRcYYL_V1Jgk1PwKJztV4wXpMa0s,10726
|
|
15
|
-
drfchelseru/migrations/0001_initial.py,sha256=LfkWAeQuPXNW3_igM-w_pyKafCeHQF3aReYgMXfMzEE,1034
|
|
16
|
-
drfchelseru/migrations/0002_otpcode_session_user.py,sha256=c3oyBTQo2kjsKr5r_XQrqaq-66F7pui-4H4mEyAXgJk,3126
|
|
17
|
-
drfchelseru/migrations/0003_rename_mobile_otpcode_mobile_number.py,sha256=eiD7t6etg6nOeT7BhJrSfEcR1yTPePXtATgtyu30520,377
|
|
18
|
-
drfchelseru/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
django_chelseru-1.0.1.dist-info/METADATA,sha256=dWyRINBWk76PZf2ikSqhaKZWVCdAUrvVbVz9isLVMZw,1758
|
|
20
|
-
django_chelseru-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
-
django_chelseru-1.0.1.dist-info/top_level.txt,sha256=fsaO1F03W3j4AYi0TfDGv5Cjb_Qrh6RSkwkWqfqaMns,12
|
|
22
|
-
django_chelseru-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|