mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-06-28 05:26:07 +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:
@ -15,6 +15,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
"is_staff",
|
||||
"mobile",
|
||||
"address",
|
||||
"wallet_balance",
|
||||
"acc_no",
|
||||
"id_card",
|
||||
"dob",
|
||||
@ -35,6 +36,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
"email",
|
||||
"mobile",
|
||||
"address",
|
||||
"wallet_balance",
|
||||
"acc_no",
|
||||
"id_card",
|
||||
"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 django.utils import timezone
|
||||
|
||||
|
||||
class User(AbstractUser):
|
||||
email = models.EmailField(unique=True, blank=True, null=True)
|
||||
address = models.CharField(max_length=255, blank=True)
|
||||
@ -20,8 +21,13 @@ class User(AbstractUser):
|
||||
policy_accepted = models.BooleanField(default=False)
|
||||
wallet_balance = models.FloatField(default=0.0)
|
||||
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')
|
||||
island = models.ForeignKey('Island', on_delete=models.SET_NULL, null=True, blank=True, related_name='users')
|
||||
atoll = models.ForeignKey(
|
||||
"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):
|
||||
return [field.name for field in instance.get_fields()]
|
||||
|
||||
@ -29,16 +35,17 @@ class User(AbstractUser):
|
||||
|
||||
|
||||
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)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Island(models.Model):
|
||||
atoll = models.ForeignKey(Atoll, on_delete=models.CASCADE, related_name='islands')
|
||||
name = models.CharField(max_length=255)
|
||||
atoll = models.ForeignKey(Atoll, on_delete=models.CASCADE, related_name="islands")
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from knox.models import AuthToken
|
||||
from django.contrib.auth import authenticate
|
||||
from api.models import User
|
||||
from api.models import User, Atoll, Island
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
@ -46,7 +46,6 @@ class CustomReadOnlyUserSerializer(serializers.ModelSerializer):
|
||||
"username",
|
||||
"mobile",
|
||||
"address",
|
||||
|
||||
)
|
||||
|
||||
|
||||
@ -92,3 +91,15 @@ class KnoxTokenSerializer(serializers.ModelSerializer):
|
||||
class Meta: # type: ignore
|
||||
model = AuthToken
|
||||
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 django.contrib.auth.models import Permission
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def assign_device_permissions(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
# Assign all permissions for devices and read permission for atoll and island
|
||||
device_permissions = Permission.objects.filter(content_type__model='device')
|
||||
atoll_read_permission = Permission.objects.get(codename='view_atoll')
|
||||
island_read_permission = Permission.objects.get(codename='view_island')
|
||||
device_permissions = Permission.objects.filter(content_type__model="device")
|
||||
atoll_read_permission = Permission.objects.get(codename="view_atoll")
|
||||
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:
|
||||
instance.user_permissions.add(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)
|
||||
|
17
api/urls.py
17
api/urls.py
@ -11,6 +11,10 @@ from .views import (
|
||||
UserDetailAPIView,
|
||||
healthcheck,
|
||||
test_email,
|
||||
ListCreateAtollView,
|
||||
RetrieveUpdateDestroyAtollView,
|
||||
ListCreateIslandView,
|
||||
RetrieveUpdateDestroyIslandView,
|
||||
)
|
||||
|
||||
|
||||
@ -26,5 +30,16 @@ urlpatterns = [
|
||||
path("users/<int:pk>/", UserDetailAPIView.as_view(), name="user-detail"),
|
||||
path("healthcheck/", healthcheck, name="healthcheck"),
|
||||
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.exceptions import ValidationError
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from api.serializers import AtollSerializer, IslandSerializer
|
||||
|
||||
# knox imports
|
||||
from knox.views import LoginView as KnoxLoginView
|
||||
@ -85,13 +86,19 @@ class CreateUserView(generics.CreateAPIView):
|
||||
return Response({"message": "Policy acceptance is required."}, status=400)
|
||||
|
||||
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):
|
||||
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):
|
||||
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
|
||||
try:
|
||||
@ -109,7 +116,7 @@ class CreateUserView(generics.CreateAPIView):
|
||||
username=username,
|
||||
password=password,
|
||||
address=address,
|
||||
mobile=str("+960") + str(mobile),
|
||||
mobile=mobile,
|
||||
acc_no=acc_no,
|
||||
id_card=id_card,
|
||||
dob=dob,
|
||||
@ -219,3 +226,64 @@ def test_email(request):
|
||||
fail_silently=False,
|
||||
)
|
||||
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)
|
||||
|
Reference in New Issue
Block a user