mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-02-22 08:32:01 +00:00
Add wallet balance to User model and implement Atoll/Island management
- Added `wallet_balance` field to the User model. - Updated UserAdmin to include `wallet_balance` in the admin interface. - Created serializers and views for Atoll and Island management. - Implemented endpoints for listing, creating, and updating Atolls and Islands. - Enhanced payment processing with UUIDs for Payment and Topup models. - Added migration files for new fields and constraints. - Improved error handling and validation in various views. - Updated email templates for better responsiveness and SEO.
This commit is contained in:
parent
4d0eb86478
commit
f6f77bb0e5
@ -15,6 +15,7 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
"is_staff",
|
"is_staff",
|
||||||
"mobile",
|
"mobile",
|
||||||
"address",
|
"address",
|
||||||
|
"wallet_balance",
|
||||||
"acc_no",
|
"acc_no",
|
||||||
"id_card",
|
"id_card",
|
||||||
"dob",
|
"dob",
|
||||||
@ -35,6 +36,7 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
"email",
|
"email",
|
||||||
"mobile",
|
"mobile",
|
||||||
"address",
|
"address",
|
||||||
|
"wallet_balance",
|
||||||
"acc_no",
|
"acc_no",
|
||||||
"id_card",
|
"id_card",
|
||||||
"dob",
|
"dob",
|
||||||
|
22
api/migrations/0005_alter_atoll_name_alter_island_name.py
Normal file
22
api/migrations/0005_alter_atoll_name_alter_island_name.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-01-20 14:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("api", "0004_alter_atoll_id_alter_island_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="atoll",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(max_length=255, unique=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="island",
|
||||||
|
name="name",
|
||||||
|
field=models.CharField(max_length=255, unique=True),
|
||||||
|
),
|
||||||
|
]
|
@ -7,6 +7,7 @@ from django.db import models
|
|||||||
from .managers import CustomUserManager
|
from .managers import CustomUserManager
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
email = models.EmailField(unique=True, blank=True, null=True)
|
email = models.EmailField(unique=True, blank=True, null=True)
|
||||||
address = models.CharField(max_length=255, blank=True)
|
address = models.CharField(max_length=255, blank=True)
|
||||||
@ -20,8 +21,13 @@ class User(AbstractUser):
|
|||||||
policy_accepted = models.BooleanField(default=False)
|
policy_accepted = models.BooleanField(default=False)
|
||||||
wallet_balance = models.FloatField(default=0.0)
|
wallet_balance = models.FloatField(default=0.0)
|
||||||
ninja_user_id = models.CharField(max_length=255, blank=True)
|
ninja_user_id = models.CharField(max_length=255, blank=True)
|
||||||
atoll = models.ForeignKey('Atoll', on_delete=models.SET_NULL, null=True, blank=True, related_name='users')
|
atoll = models.ForeignKey(
|
||||||
island = models.ForeignKey('Island', on_delete=models.SET_NULL, null=True, blank=True, related_name='users')
|
"Atoll", on_delete=models.SET_NULL, null=True, blank=True, related_name="users"
|
||||||
|
)
|
||||||
|
island = models.ForeignKey(
|
||||||
|
"Island", on_delete=models.SET_NULL, null=True, blank=True, related_name="users"
|
||||||
|
)
|
||||||
|
|
||||||
def get_all_fields(self, instance):
|
def get_all_fields(self, instance):
|
||||||
return [field.name for field in instance.get_fields()]
|
return [field.name for field in instance.get_fields()]
|
||||||
|
|
||||||
@ -29,16 +35,17 @@ class User(AbstractUser):
|
|||||||
|
|
||||||
|
|
||||||
class Atoll(models.Model):
|
class Atoll(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
created_at = models.DateTimeField(default=timezone.now)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Island(models.Model):
|
class Island(models.Model):
|
||||||
atoll = models.ForeignKey(Atoll, on_delete=models.CASCADE, related_name='islands')
|
atoll = models.ForeignKey(Atoll, on_delete=models.CASCADE, related_name="islands")
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255, unique=True)
|
||||||
created_at = models.DateTimeField(default=timezone.now)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from knox.models import AuthToken
|
from knox.models import AuthToken
|
||||||
from django.contrib.auth import authenticate
|
from django.contrib.auth import authenticate
|
||||||
from api.models import User
|
from api.models import User, Atoll, Island
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
@ -46,7 +46,6 @@ class CustomReadOnlyUserSerializer(serializers.ModelSerializer):
|
|||||||
"username",
|
"username",
|
||||||
"mobile",
|
"mobile",
|
||||||
"address",
|
"address",
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -92,3 +91,15 @@ class KnoxTokenSerializer(serializers.ModelSerializer):
|
|||||||
class Meta: # type: ignore
|
class Meta: # type: ignore
|
||||||
model = AuthToken
|
model = AuthToken
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class AtollSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta: # type: ignore
|
||||||
|
model = Atoll
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class IslandSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta: # type: ignore
|
||||||
|
model = Island
|
||||||
|
fields = "__all__"
|
||||||
|
@ -7,17 +7,23 @@ from django.db.models.signals import post_save
|
|||||||
from api.models import User
|
from api.models import User
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=User)
|
@receiver(post_save, sender=User)
|
||||||
def assign_device_permissions(sender, instance, created, **kwargs):
|
def assign_device_permissions(sender, instance, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
# Assign all permissions for devices and read permission for atoll and island
|
# Assign all permissions for devices and read permission for atoll and island
|
||||||
device_permissions = Permission.objects.filter(content_type__model='device')
|
device_permissions = Permission.objects.filter(content_type__model="device")
|
||||||
atoll_read_permission = Permission.objects.get(codename='view_atoll')
|
atoll_read_permission = Permission.objects.get(codename="view_atoll")
|
||||||
island_read_permission = Permission.objects.get(codename='view_island')
|
island_read_permission = Permission.objects.get(codename="view_island")
|
||||||
|
payment_permissions = Permission.objects.filter(
|
||||||
|
content_type__model="payment"
|
||||||
|
).exclude(codename="delete_payment")
|
||||||
|
|
||||||
for permission in device_permissions:
|
for permission in device_permissions:
|
||||||
instance.user_permissions.add(permission)
|
instance.user_permissions.add(permission)
|
||||||
instance.user_permissions.add(atoll_read_permission, island_read_permission)
|
instance.user_permissions.add(atoll_read_permission, island_read_permission)
|
||||||
|
for permission in payment_permissions:
|
||||||
|
instance.user_permissions.add(permission)
|
||||||
|
|
||||||
|
|
||||||
@receiver(reset_password_token_created)
|
@receiver(reset_password_token_created)
|
||||||
|
17
api/urls.py
17
api/urls.py
@ -11,6 +11,10 @@ from .views import (
|
|||||||
UserDetailAPIView,
|
UserDetailAPIView,
|
||||||
healthcheck,
|
healthcheck,
|
||||||
test_email,
|
test_email,
|
||||||
|
ListCreateAtollView,
|
||||||
|
RetrieveUpdateDestroyAtollView,
|
||||||
|
ListCreateIslandView,
|
||||||
|
RetrieveUpdateDestroyIslandView,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -26,5 +30,16 @@ urlpatterns = [
|
|||||||
path("users/<int:pk>/", UserDetailAPIView.as_view(), name="user-detail"),
|
path("users/<int:pk>/", UserDetailAPIView.as_view(), name="user-detail"),
|
||||||
path("healthcheck/", healthcheck, name="healthcheck"),
|
path("healthcheck/", healthcheck, name="healthcheck"),
|
||||||
path("test/", test_email, name="testemail"),
|
path("test/", test_email, name="testemail"),
|
||||||
|
path("atolls/", ListCreateAtollView.as_view(), name="atolls"),
|
||||||
|
path(
|
||||||
|
"atolls/<int:pk>/",
|
||||||
|
RetrieveUpdateDestroyAtollView.as_view(),
|
||||||
|
name="atoll-detail",
|
||||||
|
),
|
||||||
|
path("islands/", ListCreateIslandView.as_view(), name="islands"),
|
||||||
|
path(
|
||||||
|
"islands/<int:pk>/",
|
||||||
|
RetrieveUpdateDestroyIslandView.as_view(),
|
||||||
|
name="island-detail",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
76
api/views.py
76
api/views.py
@ -11,6 +11,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.exceptions import ValidationError
|
from rest_framework.exceptions import ValidationError
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from api.serializers import AtollSerializer, IslandSerializer
|
||||||
|
|
||||||
# knox imports
|
# knox imports
|
||||||
from knox.views import LoginView as KnoxLoginView
|
from knox.views import LoginView as KnoxLoginView
|
||||||
@ -85,13 +86,19 @@ class CreateUserView(generics.CreateAPIView):
|
|||||||
return Response({"message": "Policy acceptance is required."}, status=400)
|
return Response({"message": "Policy acceptance is required."}, status=400)
|
||||||
|
|
||||||
if not re.match(r"^[A-Z]{1,2}[0-9]{6,7}$", id_card):
|
if not re.match(r"^[A-Z]{1,2}[0-9]{6,7}$", id_card):
|
||||||
return Response({"message": "Please enter a valid ID card number."}, status=400)
|
return Response(
|
||||||
|
{"message": "Please enter a valid ID card number."}, status=400
|
||||||
|
)
|
||||||
|
|
||||||
if not re.match(r"^[7|9][0-9]{6}$", mobile):
|
if not re.match(r"^[7|9][0-9]{6}$", mobile):
|
||||||
return Response({"message": "Please enter a valid mobile number."}, status=400)
|
return Response(
|
||||||
|
{"message": "Please enter a valid mobile number."}, status=400
|
||||||
|
)
|
||||||
|
|
||||||
if not re.match(r"^(7\d{12}|9\d{16})$", acc_no):
|
if not re.match(r"^(7\d{12}|9\d{16})$", acc_no):
|
||||||
return Response({"message": "Please enter a valid account number."}, status=400)
|
return Response(
|
||||||
|
{"message": "Please enter a valid account number."}, status=400
|
||||||
|
)
|
||||||
|
|
||||||
# Fetch Atoll and Island instances
|
# Fetch Atoll and Island instances
|
||||||
try:
|
try:
|
||||||
@ -109,7 +116,7 @@ class CreateUserView(generics.CreateAPIView):
|
|||||||
username=username,
|
username=username,
|
||||||
password=password,
|
password=password,
|
||||||
address=address,
|
address=address,
|
||||||
mobile=str("+960") + str(mobile),
|
mobile=mobile,
|
||||||
acc_no=acc_no,
|
acc_no=acc_no,
|
||||||
id_card=id_card,
|
id_card=id_card,
|
||||||
dob=dob,
|
dob=dob,
|
||||||
@ -219,3 +226,64 @@ def test_email(request):
|
|||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
)
|
)
|
||||||
return Response({"status": "ok"}, status=status.HTTP_200_OK)
|
return Response({"status": "ok"}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCreateAtollView(StaffEditorPermissionMixin, generics.ListCreateAPIView):
|
||||||
|
serializer_class = AtollSerializer
|
||||||
|
queryset = Atoll.objects.all()
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
name = serializer.validated_data.get("name")
|
||||||
|
print(name)
|
||||||
|
if Atoll.objects.filter(name=name).exists():
|
||||||
|
return Response({"message": "Atoll name already exists."}, status=400)
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDestroyAtollView(
|
||||||
|
StaffEditorPermissionMixin, generics.RetrieveUpdateDestroyAPIView
|
||||||
|
):
|
||||||
|
serializer_class = AtollSerializer
|
||||||
|
queryset = Atoll.objects.all()
|
||||||
|
lookup_field = "pk"
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
name = serializer.validated_data.get("name")
|
||||||
|
if name and Atoll.objects.filter(name=name).exclude(pk=instance.pk).exists():
|
||||||
|
return Response({"message": "Atoll name already exists."}, status=400)
|
||||||
|
return super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCreateIslandView(StaffEditorPermissionMixin, generics.ListCreateAPIView):
|
||||||
|
serializer_class = IslandSerializer
|
||||||
|
queryset = Island.objects.all()
|
||||||
|
|
||||||
|
def create(self, request, *args, **kwargs):
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
name = serializer.validated_data.get("name")
|
||||||
|
if Island.objects.filter(name=name).exists():
|
||||||
|
return Response({"message": "Island name already exists."}, status=400)
|
||||||
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class RetrieveUpdateDestroyIslandView(
|
||||||
|
StaffEditorPermissionMixin, generics.RetrieveUpdateDestroyAPIView
|
||||||
|
):
|
||||||
|
serializer_class = IslandSerializer
|
||||||
|
queryset = Island.objects.all()
|
||||||
|
lookup_field = "pk"
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
instance = self.get_object()
|
||||||
|
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
name = serializer.validated_data.get("name")
|
||||||
|
if name and Island.objects.filter(name=name).exclude(pk=instance.pk).exists():
|
||||||
|
return Response({"message": "Island name already exists."}, status=400)
|
||||||
|
return super().update(request, *args, **kwargs)
|
||||||
|
@ -36,7 +36,8 @@ urlpatterns = [
|
|||||||
path("api/auth/", include("api.urls")),
|
path("api/auth/", include("api.urls")),
|
||||||
# Devices
|
# Devices
|
||||||
path("api/devices/", include("devices.urls")),
|
path("api/devices/", include("devices.urls")),
|
||||||
|
# Billing
|
||||||
|
path("api/billing/", include("billing.urls")),
|
||||||
]
|
]
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from .models import Payment, BillFormula, Topup
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "user", "amount", "paid", "paid_at", "method")
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Payment, PaymentAdmin)
|
||||||
|
admin.site.register(BillFormula)
|
||||||
|
admin.site.register(Topup)
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-01-20 14:58
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("billing", "0002_billformula_payment_topup_delete_device"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="billformula",
|
||||||
|
name="id",
|
||||||
|
field=models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="payment",
|
||||||
|
name="id",
|
||||||
|
field=models.UUIDField(
|
||||||
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
20
billing/migrations/0004_alter_topup_id.py
Normal file
20
billing/migrations/0004_alter_topup_id.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-01-20 15:00
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("billing", "0003_alter_billformula_id_alter_payment_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="topup",
|
||||||
|
name="id",
|
||||||
|
field=models.UUIDField(
|
||||||
|
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1,35 +1,37 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from api.models import User
|
from api.models import User
|
||||||
|
import uuid
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
from devices.models import Device
|
from devices.models import Device
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
|
|
||||||
class Payment(models.Model):
|
class Payment(models.Model):
|
||||||
PAYMENT_TYPES = [
|
PAYMENT_TYPES = [
|
||||||
('WALLET', 'Wallet'),
|
("WALLET", "Wallet"),
|
||||||
('TRANSFER', 'Transfer'),
|
("TRANSFER", "Transfer"),
|
||||||
]
|
]
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
id = models.CharField(primary_key=True, max_length=255)
|
|
||||||
number_of_months = models.IntegerField()
|
number_of_months = models.IntegerField()
|
||||||
amount = models.FloatField()
|
amount = models.FloatField()
|
||||||
paid = models.BooleanField(default=False)
|
paid = models.BooleanField(default=False)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='payments')
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="payments")
|
||||||
paid_at = models.DateTimeField(null=True, blank=True)
|
paid_at = models.DateTimeField(null=True, blank=True)
|
||||||
method = models.CharField(max_length=255, choices=PAYMENT_TYPES, default='TRANSFER')
|
method = models.CharField(max_length=255, choices=PAYMENT_TYPES, default="TRANSFER")
|
||||||
expires_at = models.DateTimeField(null=True, blank=True)
|
expires_at = models.DateTimeField(null=True, blank=True)
|
||||||
created_at = models.DateTimeField(default=timezone.now)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
devices = models.ManyToManyField(Device, related_name='payments')
|
devices = models.ManyToManyField(Device, related_name="payments")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Payment by {self.user}"
|
return f"Payment by {self.user}"
|
||||||
|
|
||||||
|
|
||||||
class BillFormula(models.Model):
|
class BillFormula(models.Model):
|
||||||
id = models.CharField(primary_key=True, max_length=255)
|
|
||||||
formula = models.CharField(max_length=255)
|
formula = models.CharField(max_length=255)
|
||||||
base_amount = models.FloatField()
|
base_amount = models.FloatField()
|
||||||
discount_percentage = models.FloatField()
|
discount_percentage = models.FloatField()
|
||||||
@ -39,10 +41,11 @@ class BillFormula(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.formula
|
return self.formula
|
||||||
|
|
||||||
|
|
||||||
class Topup(models.Model):
|
class Topup(models.Model):
|
||||||
id = models.CharField(primary_key=True, max_length=255)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
amount = models.FloatField()
|
amount = models.FloatField()
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topups')
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="topups")
|
||||||
paid = models.BooleanField(default=False)
|
paid = models.BooleanField(default=False)
|
||||||
created_at = models.DateTimeField(default=timezone.now)
|
created_at = models.DateTimeField(default=timezone.now)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
11
billing/serializers.py
Normal file
11
billing/serializers.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Payment
|
||||||
|
from devices.serializers import DeviceSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentSerializer(serializers.ModelSerializer):
|
||||||
|
devices = DeviceSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Payment
|
||||||
|
fields = "__all__"
|
8
billing/urls.py
Normal file
8
billing/urls.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# billing/urls.py
|
||||||
|
from django.urls import path
|
||||||
|
from .views import CreatePaymentView, VerifyPaymentView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("create-payment/", CreatePaymentView.as_view(), name="create-payment"),
|
||||||
|
path("verify-payment/", VerifyPaymentView.as_view(), name="verify-payment"),
|
||||||
|
]
|
141
billing/views.py
141
billing/views.py
@ -1,2 +1,141 @@
|
|||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
# billing/views.py
|
||||||
|
from .models import Payment, Device
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework import status
|
||||||
|
from decouple import config
|
||||||
|
from .serializers import PaymentSerializer
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from api.mixins import StaffEditorPermissionMixin
|
||||||
|
from rest_framework import generics
|
||||||
|
|
||||||
|
|
||||||
|
class InsufficientFundsError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePaymentView(StaffEditorPermissionMixin, generics.CreateAPIView):
|
||||||
|
serializer_class = PaymentSerializer
|
||||||
|
queryset = Payment.objects.all()
|
||||||
|
|
||||||
|
def create(self, request):
|
||||||
|
data = request.data
|
||||||
|
user = request.user
|
||||||
|
amount = data.get("amount")
|
||||||
|
number_of_months = data.get("number_of_months")
|
||||||
|
device_ids = data.get("device_ids", [])
|
||||||
|
print(amount, number_of_months, device_ids)
|
||||||
|
|
||||||
|
if not amount or not number_of_months:
|
||||||
|
return Response(
|
||||||
|
{"message": "amount and number_of_months are required."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
if not device_ids:
|
||||||
|
return Response(
|
||||||
|
{"message": "device_ids are required."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
# Create payment
|
||||||
|
payment = Payment.objects.create(
|
||||||
|
amount=amount,
|
||||||
|
number_of_months=number_of_months,
|
||||||
|
paid=data.get("paid", False),
|
||||||
|
user=user,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Connect devices to payment
|
||||||
|
devices = Device.objects.filter(id__in=device_ids, user=user)
|
||||||
|
payment.devices.set(devices)
|
||||||
|
|
||||||
|
serializer = PaymentSerializer(payment)
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
|
|
||||||
|
class VerifyPaymentView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
||||||
|
serializer_class = PaymentSerializer
|
||||||
|
queryset = Payment.objects.all()
|
||||||
|
|
||||||
|
def update(self, request, *args, **kwargs):
|
||||||
|
data = request.data
|
||||||
|
user = request.user
|
||||||
|
print(user)
|
||||||
|
method = data.get("method")
|
||||||
|
payment_id = data.get("payment_id")
|
||||||
|
abs_amount = data.get("abs_amount")
|
||||||
|
if not method:
|
||||||
|
return Response(
|
||||||
|
{"message": "method is required. 'WALLET' or 'TRANSFER'"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
if not payment_id:
|
||||||
|
return Response(
|
||||||
|
{"message": "payment_id is required."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
if not abs_amount:
|
||||||
|
return Response(
|
||||||
|
{"message": "abs_amount is required."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
payment = Payment.objects.get(id=payment_id)
|
||||||
|
devices = payment.devices.all()
|
||||||
|
|
||||||
|
if data["type"] == "WALLET":
|
||||||
|
print("processing WALLET payment")
|
||||||
|
self.process_wallet_payment(user, payment, float(data["abs_amount"]))
|
||||||
|
elif data["type"] == "TRANSFER":
|
||||||
|
self.verify_external_payment(data, payment)
|
||||||
|
|
||||||
|
# Update devices
|
||||||
|
expiry_date = datetime.now() + timedelta(days=30 * payment.number_of_months)
|
||||||
|
devices.update(is_active=True, expiry_date=expiry_date)
|
||||||
|
|
||||||
|
return Response({"message": "Payment verified successfully."})
|
||||||
|
|
||||||
|
except Payment.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"message": "Payment not found."}, status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
except InsufficientFundsError:
|
||||||
|
return Response(
|
||||||
|
{"message": "Insufficient funds in wallet."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
return Response(
|
||||||
|
{"message": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_wallet_payment(self, user, payment, amount):
|
||||||
|
print("processing wallet payment")
|
||||||
|
print(user, amount)
|
||||||
|
if user.wallet_balance < amount:
|
||||||
|
return Response(
|
||||||
|
{"message": "Insufficient funds in wallet."},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
payment.paid = True
|
||||||
|
payment.paid_at = datetime.now()
|
||||||
|
payment.method = "WALLET"
|
||||||
|
payment.save()
|
||||||
|
|
||||||
|
user.wallet_balance -= amount
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def verify_external_payment(self, data, payment):
|
||||||
|
response = requests.post(
|
||||||
|
f"{config('PAYMENT_VERIFY_BASE_URL')}/verify-payment",
|
||||||
|
json=data,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
print(response.json())
|
||||||
|
if not response.json().get("success"):
|
||||||
|
raise Exception("Payment verification failed.")
|
||||||
|
@ -2,7 +2,11 @@ from rest_framework import generics, status
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from .models import Device
|
from .models import Device
|
||||||
from .serializers import CreateDeviceSerializer, DeviceSerializer, ReadOnlyDeviceSerializer
|
from .serializers import (
|
||||||
|
CreateDeviceSerializer,
|
||||||
|
DeviceSerializer,
|
||||||
|
ReadOnlyDeviceSerializer,
|
||||||
|
)
|
||||||
from api.mixins import StaffEditorPermissionMixin
|
from api.mixins import StaffEditorPermissionMixin
|
||||||
from .filters import DeviceFilter
|
from .filters import DeviceFilter
|
||||||
import re
|
import re
|
||||||
@ -27,16 +31,17 @@ class DeviceListCreateAPIView(
|
|||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
mac = request.data.get("mac", None)
|
mac = request.data.get("mac", None)
|
||||||
if not re.match(r"^([0-9A-Fa-f]{2}([.:-]?)){5}[0-9A-Fa-f]{2}$", mac):
|
if not re.match(r"^([0-9A-Fa-f]{2}([.:-]?)){5}[0-9A-Fa-f]{2}$", mac):
|
||||||
return Response({"error": "Invalid mac address"}, status=400)
|
return Response({"message": "Invalid mac address."}, status=400)
|
||||||
if Device.objects.filter(mac=mac).exists():
|
if Device.objects.filter(mac=mac).exists():
|
||||||
return Response({"error": "Device with this mac already exists"}, status=400)
|
return Response(
|
||||||
|
{"message": "Device with this mac address already exists."}, status=400
|
||||||
|
)
|
||||||
return super().create(request, *args, **kwargs)
|
return super().create(request, *args, **kwargs)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
serializer.save(user=self.request.user)
|
serializer.save(user=self.request.user)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
|
class DeviceDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
|
||||||
queryset = Device.objects.select_related("user").all()
|
queryset = Device.objects.select_related("user").all()
|
||||||
serializer_class = ReadOnlyDeviceSerializer
|
serializer_class = ReadOnlyDeviceSerializer
|
||||||
@ -52,6 +57,9 @@ class DeviceUpdateAPIView(StaffEditorPermissionMixin, generics.UpdateAPIView):
|
|||||||
# Pass 'partial=True' to allow partial updates
|
# Pass 'partial=True' to allow partial updates
|
||||||
partial = kwargs.pop("partial", True)
|
partial = kwargs.pop("partial", True)
|
||||||
instance = self.get_object()
|
instance = self.get_object()
|
||||||
|
mac = request.data.get("mac", None)
|
||||||
|
if not re.match(r"^([0-9A-Fa-f]{2}([.:-]?)){5}[0-9A-Fa-f]{2}$", mac):
|
||||||
|
return Response({"message": "Invalid mac address"}, status=400)
|
||||||
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
self.perform_update(serializer)
|
self.perform_update(serializer)
|
||||||
@ -73,4 +81,3 @@ class DeviceDestroyAPIView(StaffEditorPermissionMixin, generics.DestroyAPIView):
|
|||||||
{"message": f"Device '{device_name}' deleted."},
|
{"message": f"Device '{device_name}' deleted."},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="Your login token">
|
||||||
|
<meta name="keywords" content="login, token, SARLink Portal">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Your Login Token</title>
|
<title>Your Login Token</title>
|
||||||
@ -62,6 +66,7 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
@ -85,4 +90,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,10 +1,16 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="Your verification token">
|
||||||
|
<meta name="keywords" content="verification, token, sarlink">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<title>Your Verification Token</title>
|
<title>Your Verification Token</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2>Use this verification code: {{ callback_token }}</h2>
|
<h2>Use this verification code: {{ callback_token }}</h2>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
# import os
|
# import os
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
@ -7,10 +8,12 @@ from django.template import loader
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from djangopasswordlessknox.models import CallbackToken
|
from djangopasswordlessknox.models import CallbackToken
|
||||||
from djangopasswordlessknox.settings import api_settings
|
from djangopasswordlessknox.settings import api_settings
|
||||||
|
|
||||||
# from twilio.rest import Client
|
# from twilio.rest import Client
|
||||||
from decouple import config
|
from decouple import config
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@ -27,9 +30,13 @@ def authenticate_by_token(callback_token):
|
|||||||
return token.user
|
return token.user
|
||||||
|
|
||||||
except CallbackToken.DoesNotExist:
|
except CallbackToken.DoesNotExist:
|
||||||
logger.debug("djangopasswordlessknox: Challenged with a callback token that doesn't exist.")
|
logger.debug(
|
||||||
|
"djangopasswordlessknox: Challenged with a callback token that doesn't exist."
|
||||||
|
)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
logger.debug("djangopasswordlessknox: Authenticated user somehow doesn't exist.")
|
logger.debug(
|
||||||
|
"djangopasswordlessknox: Authenticated user somehow doesn't exist."
|
||||||
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
logger.debug("djangopasswordlessknox: Permission denied while authenticating.")
|
logger.debug("djangopasswordlessknox: Permission denied while authenticating.")
|
||||||
|
|
||||||
@ -41,15 +48,19 @@ def create_callback_token_for_user(user, token_type):
|
|||||||
token = None
|
token = None
|
||||||
token_type = token_type.upper()
|
token_type = token_type.upper()
|
||||||
|
|
||||||
if token_type == 'EMAIL':
|
if token_type == "EMAIL":
|
||||||
token = CallbackToken.objects.create(user=user,
|
token = CallbackToken.objects.create(
|
||||||
to_alias_type=token_type,
|
user=user,
|
||||||
to_alias=getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME))
|
to_alias_type=token_type,
|
||||||
|
to_alias=getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME),
|
||||||
|
)
|
||||||
|
|
||||||
elif token_type == 'MOBILE':
|
elif token_type == "MOBILE":
|
||||||
token = CallbackToken.objects.create(user=user,
|
token = CallbackToken.objects.create(
|
||||||
to_alias_type=token_type,
|
user=user,
|
||||||
to_alias=getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME))
|
to_alias_type=token_type,
|
||||||
|
to_alias=getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME),
|
||||||
|
)
|
||||||
|
|
||||||
if token is not None:
|
if token is not None:
|
||||||
return token
|
return token
|
||||||
@ -83,12 +94,20 @@ def verify_user_alias(user, token):
|
|||||||
"""
|
"""
|
||||||
Marks a user's contact point as verified depending on accepted token type.
|
Marks a user's contact point as verified depending on accepted token type.
|
||||||
"""
|
"""
|
||||||
if token.to_alias_type == 'EMAIL':
|
if token.to_alias_type == "EMAIL":
|
||||||
if token.to_alias == getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME):
|
if token.to_alias == getattr(
|
||||||
setattr(user, api_settings.PASSWORDLESS_USER_EMAIL_VERIFIED_FIELD_NAME, True)
|
user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME
|
||||||
elif token.to_alias_type == 'MOBILE':
|
):
|
||||||
if token.to_alias == getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME):
|
setattr(
|
||||||
setattr(user, api_settings.PASSWORDLESS_USER_MOBILE_VERIFIED_FIELD_NAME, True)
|
user, api_settings.PASSWORDLESS_USER_EMAIL_VERIFIED_FIELD_NAME, True
|
||||||
|
)
|
||||||
|
elif token.to_alias_type == "MOBILE":
|
||||||
|
if token.to_alias == getattr(
|
||||||
|
user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME
|
||||||
|
):
|
||||||
|
setattr(
|
||||||
|
user, api_settings.PASSWORDLESS_USER_MOBILE_VERIFIED_FIELD_NAME, True
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
user.save()
|
user.save()
|
||||||
@ -116,32 +135,47 @@ def send_email_with_callback_token(user, email_token, **kwargs):
|
|||||||
# Make sure we have a sending address before sending.
|
# Make sure we have a sending address before sending.
|
||||||
|
|
||||||
# Get email subject and message
|
# Get email subject and message
|
||||||
email_subject = kwargs.get('email_subject',
|
email_subject = kwargs.get(
|
||||||
api_settings.PASSWORDLESS_EMAIL_SUBJECT)
|
"email_subject", api_settings.PASSWORDLESS_EMAIL_SUBJECT
|
||||||
email_plaintext = kwargs.get('email_plaintext',
|
)
|
||||||
api_settings.PASSWORDLESS_EMAIL_PLAINTEXT_MESSAGE)
|
email_plaintext = kwargs.get(
|
||||||
email_html = kwargs.get('email_html',
|
"email_plaintext", api_settings.PASSWORDLESS_EMAIL_PLAINTEXT_MESSAGE
|
||||||
api_settings.PASSWORDLESS_EMAIL_TOKEN_HTML_TEMPLATE_NAME)
|
)
|
||||||
|
email_html = kwargs.get(
|
||||||
|
"email_html", api_settings.PASSWORDLESS_EMAIL_TOKEN_HTML_TEMPLATE_NAME
|
||||||
|
)
|
||||||
# Inject context if user specifies.
|
# Inject context if user specifies.
|
||||||
context = inject_template_context({'callback_token': email_token.key, })
|
context = inject_template_context(
|
||||||
html_message = loader.render_to_string(email_html, context,)
|
{
|
||||||
|
"callback_token": email_token.key,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
html_message = loader.render_to_string(
|
||||||
|
email_html,
|
||||||
|
context,
|
||||||
|
)
|
||||||
send_mail(
|
send_mail(
|
||||||
email_subject,
|
email_subject,
|
||||||
email_plaintext % email_token.key,
|
email_plaintext % email_token.key,
|
||||||
api_settings.PASSWORDLESS_EMAIL_NOREPLY_ADDRESS,
|
api_settings.PASSWORDLESS_EMAIL_NOREPLY_ADDRESS,
|
||||||
[getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME)],
|
[getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME)],
|
||||||
fail_silently=False,
|
fail_silently=False,
|
||||||
html_message=html_message,)
|
html_message=html_message,
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug("Failed to send token email. Missing PASSWORDLESS_EMAIL_NOREPLY_ADDRESS.")
|
logger.debug(
|
||||||
|
"Failed to send token email. Missing PASSWORDLESS_EMAIL_NOREPLY_ADDRESS."
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("Failed to send token email to user: %d."
|
logger.debug(
|
||||||
"Possibly no email on user object. Email entered was %s" %
|
"Failed to send token email to user: %d."
|
||||||
(user.id, getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME)))
|
"Possibly no email on user object. Email entered was %s"
|
||||||
|
% (user.id, getattr(user, api_settings.PASSWORDLESS_USER_EMAIL_FIELD_NAME))
|
||||||
|
)
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -152,60 +186,62 @@ def send_sms_with_callback_token(user, mobile_token, **kwargs):
|
|||||||
|
|
||||||
Passes silently without sending in test environment.
|
Passes silently without sending in test environment.
|
||||||
"""
|
"""
|
||||||
base_string = kwargs.get('mobile_message', api_settings.PASSWORDLESS_MOBILE_MESSAGE)
|
base_string = kwargs.get("mobile_message", api_settings.PASSWORDLESS_MOBILE_MESSAGE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if api_settings.PASSWORDLESS_MOBILE_NOREPLY_NUMBER:
|
print("Sending SMS")
|
||||||
print("Sending SMS")
|
# We need a sending number to send properly
|
||||||
# We need a sending number to send properly
|
if api_settings.PASSWORDLESS_TEST_SUPPRESSION is True:
|
||||||
if api_settings.PASSWORDLESS_TEST_SUPPRESSION is True:
|
# we assume success to prevent spamming SMS during testing.
|
||||||
# we assume success to prevent spamming SMS during testing.
|
return True
|
||||||
return True
|
to_number = getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME)
|
||||||
to_number = getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME)
|
if to_number.__class__.__name__ == "PhoneNumber":
|
||||||
if to_number.__class__.__name__ == 'PhoneNumber':
|
to_number = to_number.__str__()
|
||||||
to_number = to_number.__str__()
|
|
||||||
|
|
||||||
# user_withh_mobile_exists = User.objects.filter(mobile=to_number).exists()
|
# user_withh_mobile_exists = User.objects.filter(mobile=to_number).exists()
|
||||||
# if not user_withh_mobile_exists:
|
# if not user_withh_mobile_exists:
|
||||||
# print("User with mobile number does not exist.")
|
# print("User with mobile number does not exist.")
|
||||||
# logger.debug("User with mobile number does not exist.")
|
# logger.debug("User with mobile number does not exist.")
|
||||||
# return False
|
# return False
|
||||||
|
|
||||||
|
api_url = config("SMS_API_URL")
|
||||||
api_url = config("SMS_API_URL")
|
api_key = config("SMS_API_KEY")
|
||||||
api_key = config("SMS_API_KEY")
|
if not api_url or not api_key:
|
||||||
if not api_url or not api_key:
|
logger.debug("Failed to send SMS. Missing SMS_API_URL or SMS_API_KEY.")
|
||||||
logger.debug("Failed to send SMS. Missing SMS_API_URL or SMS_API_KEY.")
|
|
||||||
return False
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Authorization": f"Bearer {api_key}"
|
|
||||||
}
|
|
||||||
data = {
|
|
||||||
"number": to_number,
|
|
||||||
"message": base_string % mobile_token.key,
|
|
||||||
"check_delivery": False
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(api_url, headers=headers, data=json.dumps(data))
|
|
||||||
if response.status_code == 200:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.debug(f"Failed to send SMS. Status code: {response.status_code}")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.debug("Failed to send token sms. Missing PASSWORDLESS_MOBILE_NOREPLY_NUMBER.")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {api_key}",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"number": to_number,
|
||||||
|
"message": base_string % mobile_token.key,
|
||||||
|
"check_delivery": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(api_url, headers=headers, data=json.dumps(data))
|
||||||
|
if response.status_code == 200:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.debug(f"Failed to send SMS. Status code: {response.status_code}")
|
||||||
|
return False
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.debug("Couldn't import Twilio client. Is twilio installed?")
|
logger.debug("Couldn't import Twilio client. Is twilio installed?")
|
||||||
return False
|
return False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.debug("Couldn't send SMS."
|
logger.debug(
|
||||||
"Did you set your Twilio account tokens and specify a PASSWORDLESS_MOBILE_NOREPLY_NUMBER?")
|
"Couldn't send SMS."
|
||||||
|
"Did you set your Twilio account tokens and specify a PASSWORDLESS_MOBILE_NOREPLY_NUMBER?"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug("Failed to send token SMS to user: {}. "
|
logger.debug(
|
||||||
"Possibly no mobile number on user object or the twilio package isn't set up yet. "
|
"Failed to send token SMS to user: {}. "
|
||||||
"Number entered was {}".format(user.id, getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME)))
|
"Possibly no mobile number on user object or the twilio package isn't set up yet. "
|
||||||
|
"Number entered was {}".format(
|
||||||
|
user.id, getattr(user, api_settings.PASSWORDLESS_USER_MOBILE_FIELD_NAME)
|
||||||
|
)
|
||||||
|
)
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
return False
|
return False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user