diff --git a/Pipfile b/Pipfile index 352b63c..3ac0bc2 100644 --- a/Pipfile +++ b/Pipfile @@ -17,6 +17,8 @@ django-model-utils = "*" mysqlclient = "*" django-mysql = "*" python-decouple = "*" +kavenegar = "*" +celery = "*" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 96bcd9d..b4cf6ea 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "22ecb352f2ea0c572bef44b919bf830243068a64c19f8f13c4d8ce21df36b3da" + "sha256": "ac15ca54fa13ba02229053fc213eb914b17a1e63a5613810f124f0e6f766b8aa" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,10 @@ "default": { "asgiref": { "hashes": [ - "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", - "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5" + "sha256:3e4192eaec0758b99722f0b0666d5fbfaa713054d92e8de5b58ba84ec5ce696f", + "sha256:c8f49dd3b42edcc51d09dd2eea8a92b3cfc987ff7e6486be734b4d0cbfd5d315" ], - "version": "==3.2.3" + "version": "==3.2.5" }, "certifi": { "hashes": [ @@ -39,19 +39,19 @@ }, "django": { "hashes": [ - "sha256:6f857bd4e574442ba35a7172f1397b303167dae964cf18e53db5e85fe248d000", - "sha256:d98c9b6e5eed147bc51f47c014ff6826bd1ab50b166956776ee13db5a58804ae" + "sha256:50b781f6cbeb98f673aa76ed8e572a019a45e52bdd4ad09001072dfd91ab07c8", + "sha256:89e451bfbb815280b137e33e454ddd56481fdaa6334054e6e031041ee1eda360" ], "index": "pypi", - "version": "==3.0" + "version": "==3.0.4" }, "django-cors-middleware": { "hashes": [ - "sha256:85904a3401e7bc0c86502ff2b01d726917af3aaa7dafb77799b27ace637e8c92", - "sha256:bca8888ed33a94ba5472bde37ed71ec3d08231d6817fd4d799296b016073da95" + "sha256:5bbdea85e22909d596e26f6e0dbc174d5521429fa3943ae02a2c6c48e76c88c7", + "sha256:856dbe4d7aae65844ccc68acb49c6da7dbf7cbacaf5bcf37019f4c0c60b3be84" ], "index": "pypi", - "version": "==1.4.0" + "version": "==1.5.0" }, "django-filter": { "hashes": [ @@ -63,49 +63,57 @@ }, "django-model-utils": { "hashes": [ - "sha256:3f130a262e45d73e0950d2be76af4bf4ee86804dd60e5f90afc5cd948fcfe760", - "sha256:682f58c1de330cedcda58cc85d5232c5b47a9e2cb67bef4541fb43fdaeb18e96" + "sha256:9cf882e5b604421b62dbe57ad2b18464dc9c8f963fc3f9831badccae66c1139c", + "sha256:adf09e5be15122a7f4e372cb5a6dd512bbf8d78a23a90770ad0983ee9d909061" ], "index": "pypi", - "version": "==3.2.0" + "version": "==4.0.0" }, "django-mysql": { "hashes": [ - "sha256:5ed13e3c4e4dc1671255bfc5a9f943320079af626a0c4d1a7f8baf276f5b9de2", - "sha256:6a6d3b2b4b094418d67cc064b110d8ac31ca98a67b535568cf829bf052c718db" + "sha256:b984dfe75bedeb01bfcb974fe8400c224a6b55677b98a799f1c96274d96c5a72", + "sha256:ceaf6d93396b7f7b3798ac5f9a819dd70d5f0e1112850f2a34c57f062fa19398" ], "index": "pypi", - "version": "==3.2.0" + "version": "==3.3.0" }, "django-oauth-toolkit": { "hashes": [ - "sha256:ad1b76275950ebbff708222cec57bbdb879f89bac7df6b9dee0f4b9db485c264" + "sha256:2c1e27f6c9b67782c86acbe38954b102e0593ba799cc69782f78448154127b6e", + "sha256:3c5233eaa3abdbc40c5a04c0eadea77cce098d3f91195272ab151bf68e6496b6" ], "index": "pypi", - "version": "==1.2.0" + "version": "==1.3.0" }, "djangorestframework": { "hashes": [ - "sha256:5488aed8f8df5ec1d70f04b2114abc52ae6729748a176c453313834a9ee179c8", - "sha256:dc81cbf9775c6898a580f6f1f387c4777d12bd87abf0f5406018d32ccae71090" + "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4", + "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f" ], "index": "pypi", - "version": "==3.10.3" + "version": "==3.11.0" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" + }, + "kavenegar": { + "hashes": [ + "sha256:37992560e93535b904ee908e26819713c5df28532edfc897767251f527b76f03" + ], + "index": "pypi", + "version": "==1.1.2" }, "markdown": { "hashes": [ - "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", - "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c" + "sha256:90fee683eeabe1a92e149f7ba74e5ccdc81cd397bd6c516d93a8da0ef90b6902", + "sha256:e4795399163109457d4c5af2183fbe6b60326c17cfdf25ce6e7474c6624f725d" ], "index": "pypi", - "version": "==3.1.1" + "version": "==3.2.1" }, "mysqlclient": { "hashes": [ @@ -126,39 +134,31 @@ }, "pillow": { "hashes": [ - "sha256:047d9473cf68af50ac85f8ee5d5f21a60f849bc17d348da7fc85711287a75031", - "sha256:0f66dc6c8a3cc319561a633b6aa82c44107f12594643efa37210d8c924fc1c71", - "sha256:12c9169c4e8fe0a7329e8658c7e488001f6b4c8e88740e76292c2b857af2e94c", - "sha256:248cffc168896982f125f5c13e9317c059f74fffdb4152893339f3be62a01340", - "sha256:27faf0552bf8c260a5cee21a76e031acaea68babb64daf7e8f2e2540745082aa", - "sha256:285edafad9bc60d96978ed24d77cdc0b91dace88e5da8c548ba5937c425bca8b", - "sha256:384b12c9aa8ef95558abdcb50aada56d74bc7cc131dd62d28c2d0e4d3aadd573", - "sha256:38950b3a707f6cef09cd3cbb142474357ad1a985ceb44d921bdf7b4647b3e13e", - "sha256:4aad1b88933fd6dc2846552b89ad0c74ddbba2f0884e2c162aa368374bf5abab", - "sha256:4ac6148008c169603070c092e81f88738f1a0c511e07bd2bb0f9ef542d375da9", - "sha256:4deb1d2a45861ae6f0b12ea0a786a03d19d29edcc7e05775b85ec2877cb54c5e", - "sha256:59aa2c124df72cc75ed72c8d6005c442d4685691a30c55321e00ed915ad1a291", - "sha256:5a47d2123a9ec86660fe0e8d0ebf0aa6bc6a17edc63f338b73ea20ba11713f12", - "sha256:5cc901c2ab9409b4b7ac7b5bcc3e86ac14548627062463da0af3b6b7c555a871", - "sha256:6c1db03e8dff7b9f955a0fb9907eb9ca5da75b5ce056c0c93d33100a35050281", - "sha256:7ce80c0a65a6ea90ef9c1f63c8593fcd2929448613fc8da0adf3e6bfad669d08", - "sha256:809c19241c14433c5d6135e1b6c72da4e3b56d5c865ad5736ab99af8896b8f41", - "sha256:83792cb4e0b5af480588601467c0764242b9a483caea71ef12d22a0d0d6bdce2", - "sha256:846fa202bd7ee0f6215c897a1d33238ef071b50766339186687bd9b7a6d26ac5", - "sha256:9f5529fc02009f96ba95bea48870173426879dc19eec49ca8e08cd63ecd82ddb", - "sha256:a423c2ea001c6265ed28700df056f75e26215fd28c001e93ef4380b0f05f9547", - "sha256:ac4428094b42907aba5879c7c000d01c8278d451a3b7cccd2103e21f6397ea75", - "sha256:b1ae48d87f10d1384e5beecd169c77502fcc04a2c00a4c02b85f0a94b419e5f9", - "sha256:bf4e972a88f8841d8fdc6db1a75e0f8d763e66e3754b03006cbc3854d89f1cb1", - "sha256:c6414f6aad598364aaf81068cabb077894eb88fed99c6a65e6e8217bab62ae7a", - "sha256:c710fcb7ee32f67baf25aa9ffede4795fd5d93b163ce95fdc724383e38c9df96", - "sha256:c7be4b8a09852291c3c48d3c25d1b876d2494a0a674980089ac9d5e0d78bd132", - "sha256:c9e5ffb910b14f090ac9c38599063e354887a5f6d7e6d26795e916b4514f2c1a", - "sha256:e0697b826da6c2472bb6488db4c0a7fa8af0d52fa08833ceb3681358914b14e5", - "sha256:e9a3edd5f714229d41057d56ac0f39ad9bdba6767e8c888c951869f0bdd129b0" + "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be", + "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946", + "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837", + "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f", + "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00", + "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d", + "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533", + "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a", + "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358", + "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda", + "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435", + "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2", + "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313", + "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff", + "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317", + "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2", + "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614", + "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0", + "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386", + "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9", + "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636", + "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865" ], "index": "pypi", - "version": "==6.2.1" + "version": "==7.0.0" }, "python-decouple": { "hashes": [ @@ -176,24 +176,31 @@ }, "requests": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==2.22.0" + "version": "==2.23.0" }, "sqlparse": { "hashes": [ - "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", - "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e", + "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548" ], - "version": "==0.3.0" + "version": "==0.3.1" }, "urllib3": { "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" + "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", + "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" ], - "version": "==1.25.7" + "version": "==1.25.8" + }, + "uuid": { + "hashes": [ + "sha256:1f87cc004ac5120466f36c5beae48b4c48cc411968eed0eaecd3da82aa96193f" + ], + "index": "pypi", + "version": "==1.30" } }, "develop": {} diff --git a/apps/notifications/__init__.py b/apps/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notifications/admin.py b/apps/notifications/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/notifications/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/notifications/apps.py b/apps/notifications/apps.py new file mode 100644 index 0000000..a25babf --- /dev/null +++ b/apps/notifications/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class NotificationsConfig(AppConfig): + name = 'apps.notifications' diff --git a/apps/notifications/migrations/0001_initial.py b/apps/notifications/migrations/0001_initial.py new file mode 100644 index 0000000..7b7f2e8 --- /dev/null +++ b/apps/notifications/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 3.0.4 on 2020-03-21 02:33 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('uuid', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('state', models.IntegerField(choices=[(1, 'created'), (2, 'delayed'), (3, 'pending'), (4, 'failed'), (5, 'done')], default=1)), + ('phone_number', models.CharField(blank=True, db_index=True, max_length=30, null=True)), + ('body', models.TextField(max_length=200)), + ('application_client_id', models.CharField(db_index=True, max_length=100, null=True)), + ('user_uuid', models.UUIDField(db_index=True, null=True)), + ('create', models.DateTimeField(auto_now=True, null=True, verbose_name='create')), + ('last_update', models.DateTimeField(auto_now=True, null=True, verbose_name='last update')), + ], + ), + ] diff --git a/apps/notifications/migrations/0002_auto_20200321_0854.py b/apps/notifications/migrations/0002_auto_20200321_0854.py new file mode 100644 index 0000000..3a023a8 --- /dev/null +++ b/apps/notifications/migrations/0002_auto_20200321_0854.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.4 on 2020-03-21 08:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='create', + field=models.DateTimeField(auto_now_add=True, null=True, verbose_name='create'), + ), + ] diff --git a/apps/notifications/migrations/__init__.py b/apps/notifications/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/notifications/models.py b/apps/notifications/models.py new file mode 100644 index 0000000..3b7452c --- /dev/null +++ b/apps/notifications/models.py @@ -0,0 +1,28 @@ +import uuid + +from django.db import models +from django.utils.translation import gettext_lazy as _ +from model_utils import Choices + + +class Notification(models.Model): + STATE_CHOICES = Choices( + (1, 'created', 'created'), + (2, 'delayed', 'delayed'), + (3, 'pending', 'pending'), + (4, 'failed', 'failed'), + (5, 'done', 'done'), + ) + uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4, unique=True, + db_index=True) + state = models.IntegerField(choices=STATE_CHOICES, default=STATE_CHOICES.created) + phone_number = models.CharField(max_length=30, db_index=True, blank=True, null=True) + body = models.TextField(max_length=200) + application_client_id = models.CharField(max_length=100, db_index=True, null=True) + user_uuid = models.UUIDField(db_index=True, null=True) + create = models.DateTimeField(_('create'), blank=True, null=True, auto_now_add=True) + last_update = models.DateTimeField(_('last update'), blank=True, null=True, auto_now=True) + + def send(self): + from .tasks import send_sms + send_sms.delay(self.uuid) \ No newline at end of file diff --git a/apps/notifications/tasks.py b/apps/notifications/tasks.py new file mode 100644 index 0000000..5697c01 --- /dev/null +++ b/apps/notifications/tasks.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import, unicode_literals + +from celery import shared_task +from apps.notifications.models import Notification +from django.conf import settings +from gooyal_accounts.celery import app +from kavenegar import * +api = KavenegarAPI(settings.SMS_KAVENEGAR_TOKEN) + +@shared_task +def send_sms(notification_uuid): + notification = Notification.objects.get(uuid=notification_uuid) + params = { + 'sender': settings.SMS_KAVENEGAR_SENDER, + 'receptor': notification.phone_number, + 'message': notification.body + } + response = api.sms_send(params) + mock_response = [ + {'messageid': 1088480346, + 'message': 'Gooyal OTP: 12345', + 'status': 1, + 'statustext': 'در صف ارسال', + 'sender': '1000596446', + 'receptor': '09106853582', + 'date': 1584797465, + 'cost': 402 + } + ] + # notification.state = + + print(response) diff --git a/apps/notifications/tests.py b/apps/notifications/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/notifications/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/notifications/views.py b/apps/notifications/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/apps/notifications/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/apps/users/models.py b/apps/users/models.py index 2c9a355..6e63324 100644 --- a/apps/users/models.py +++ b/apps/users/models.py @@ -165,8 +165,17 @@ class User(AbstractUser): return result def send_otp(self): - # TODO: some action to sent OTP - return True + sms_body = f'Gooyal OTP: {self.otp}' + self.notify(sms_body) + + def notify(self, body, notification_type='sms'): + from apps.notifications.models import Notification + notification = Notification.objects.create( + user_uuid=self.uuid, + phone_number=self.phone_number, + body=body + ) + notification.send() def __str__(self): return f"{self.pk} - {self.username}" diff --git a/gooyal_accounts/__init__.py b/gooyal_accounts/__init__.py index e69de29..d128d39 100644 --- a/gooyal_accounts/__init__.py +++ b/gooyal_accounts/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) \ No newline at end of file diff --git a/gooyal_accounts/celery.py b/gooyal_accounts/celery.py new file mode 100644 index 0000000..e5d85f8 --- /dev/null +++ b/gooyal_accounts/celery.py @@ -0,0 +1,24 @@ +from __future__ import absolute_import, unicode_literals + +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'gooyal_accounts.settings') + +app = Celery('gooyal_accounts') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + + +# @app.task(bind=True) +# def debug_task(self): +# print('Request: {0!r}'.format(self.request) \ No newline at end of file diff --git a/gooyal_accounts/settings.py b/gooyal_accounts/settings.py index 0939e1d..5fd7474 100644 --- a/gooyal_accounts/settings.py +++ b/gooyal_accounts/settings.py @@ -42,6 +42,7 @@ INSTALLED_APPS = [ 'corsheaders', 'apps.users', 'apps.transactions', + 'apps.notifications', ] MIDDLEWARE = [ @@ -153,3 +154,6 @@ MEDIA_URL = "/media/" STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATIC_URL = '/static/' + +SMS_KAVENEGAR_TOKEN = '6A795A6F69463662444E64507052345A41746E5344513D3D' +SMS_KAVENEGAR_SENDER = '1000596446' \ No newline at end of file