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:
2025-01-20 20:59:16 +05:00
parent 4d0eb86478
commit f6f77bb0e5
19 changed files with 513 additions and 108 deletions

View File

@ -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",

View 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),
),
]

View File

@ -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)

View File

@ -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__"

View File

@ -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)

View File

@ -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",
),
]

View File

@ -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)