mirror of
https://github.com/i701/sarlink-portal-api.git
synced 2025-04-19 17:36:53 +00:00
Enhance User model: add email field with unique constraint, update id_card field to allow null values, and include verified field. Update UserAdmin to display verified field. Improve device listing to filter by logged-in user.
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 2m39s
All checks were successful
Build and Push Docker Images / Build and Push Docker Images (push) Successful in 2m39s
This commit is contained in:
parent
ddfbeba2f4
commit
43f9b7ef7c
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@ -1,3 +1,12 @@
|
||||
{
|
||||
"djlint.showInstallError": false
|
||||
"djlint.showInstallError": false,
|
||||
"python.testing.unittestArgs": [
|
||||
"-v",
|
||||
"-s",
|
||||
"./api",
|
||||
"-p",
|
||||
"*test.py"
|
||||
],
|
||||
"python.testing.pytestEnabled": false,
|
||||
"python.testing.unittestEnabled": true
|
||||
}
|
@ -11,6 +11,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"verified",
|
||||
"is_active",
|
||||
"is_staff",
|
||||
"mobile",
|
||||
@ -36,6 +37,7 @@ class UserAdmin(BaseUserAdmin):
|
||||
"email",
|
||||
"mobile",
|
||||
"address",
|
||||
"verified",
|
||||
"wallet_balance",
|
||||
"acc_no",
|
||||
"id_card",
|
||||
|
@ -1,20 +1,23 @@
|
||||
from django.contrib.auth.models import BaseUserManager
|
||||
from typing import Optional
|
||||
from django.contrib.auth.models import UserManager as BaseUserManager
|
||||
|
||||
|
||||
class CustomUserManager(BaseUserManager):
|
||||
def create_user(self, username, password=None, **extra_fields):
|
||||
def create_user(
|
||||
self, username, email=None, password: Optional[str] = None, **extra_fields
|
||||
):
|
||||
"""Create and return a user with an email and password."""
|
||||
if not username:
|
||||
raise ValueError("The Username field must be set")
|
||||
|
||||
user = self.model(username=username, **extra_fields)
|
||||
user = self.model(username=username, email=email, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, password=None, **extra_fields):
|
||||
def create_superuser(self, username, email=None, password=None, **extra_fields):
|
||||
"""Create and return a superuser with an email and password."""
|
||||
extra_fields.setdefault('is_staff', True)
|
||||
extra_fields.setdefault('is_superuser', True)
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
|
||||
return self.create_user(username, password, **extra_fields)
|
||||
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.1.2 on 2025-03-26 23:55
|
||||
|
||||
import api.managers
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0012_alter_user_id_card"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelManagers(
|
||||
name="user",
|
||||
managers=[
|
||||
("objects", api.managers.CustomUserManager()),
|
||||
],
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="id_card",
|
||||
field=models.CharField(blank=True, max_length=255, null=True, unique=True),
|
||||
),
|
||||
]
|
17
api/migrations/0014_alter_user_email.py
Normal file
17
api/migrations/0014_alter_user_email.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.2 on 2025-03-28 16:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("api", "0013_alter_user_managers_alter_user_id_card"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="user",
|
||||
name="email",
|
||||
field=models.EmailField(blank=True, max_length=254, null=True, unique=True),
|
||||
),
|
||||
]
|
@ -10,10 +10,11 @@ from django.utils import timezone
|
||||
|
||||
class User(AbstractUser):
|
||||
address = models.CharField(max_length=255, blank=True)
|
||||
email = models.EmailField(blank=True, null=True, unique=True)
|
||||
mobile = models.CharField(max_length=255, blank=True, unique=True, null=True)
|
||||
designation = models.CharField(max_length=255, blank=True)
|
||||
acc_no = models.CharField(max_length=255, blank=True)
|
||||
id_card = models.CharField(max_length=255, blank=True, unique=True)
|
||||
id_card = models.CharField(max_length=255, blank=True, unique=True, null=True)
|
||||
verified = models.BooleanField(default=False)
|
||||
dob = models.DateField(blank=True, null=True)
|
||||
terms_accepted = models.BooleanField(default=False)
|
||||
|
116
api/views.py
116
api/views.py
@ -32,6 +32,17 @@ from .serializers import (
|
||||
CustomReadOnlyUserByIDCardSerializer,
|
||||
)
|
||||
|
||||
ID_CARD_PATTERN = r"^[A-Z]{1,2}[0-9]{6,7}$"
|
||||
MOBILE_PATTERN = r"^[7|9][0-9]{6}$"
|
||||
ACCOUNT_NUMBER_PATTERN = r"^(7\d{12}|9\d{16})$"
|
||||
|
||||
|
||||
class ErrorMessages:
|
||||
USERNAME_EXISTS = "Username already exists."
|
||||
INVALID_ID_CARD = "Please enter a valid ID card number."
|
||||
INVALID_MOBILE = "Please enter a valid mobile number."
|
||||
INVALID_ACCOUNT = "Please enter a valid account number."
|
||||
|
||||
|
||||
class CreateUserView(generics.CreateAPIView):
|
||||
# Create user API view
|
||||
@ -42,65 +53,39 @@ class CreateUserView(generics.CreateAPIView):
|
||||
def post(self, request, *args, **kwargs):
|
||||
# Extract required fields from request data
|
||||
password = request.data.get("password")
|
||||
username = request.data.get("username") # This can be None
|
||||
username = request.data.get("username")
|
||||
address = request.data.get("address")
|
||||
mobile = request.data.get("mobile")
|
||||
acc_no = request.data.get("acc_no")
|
||||
id_card = request.data.get("id_card")
|
||||
dob = request.data.get("dob")
|
||||
atoll_id = request.data.get("atoll") # Get the atoll ID
|
||||
island_id = request.data.get("island") # Get the island ID
|
||||
atoll_id = request.data.get("atoll")
|
||||
island_id = request.data.get("island")
|
||||
terms_accepted = request.data.get("terms_accepted")
|
||||
policy_accepted = request.data.get("policy_accepted")
|
||||
firstname = request.data.get("firstname")
|
||||
lastname = request.data.get("lastname")
|
||||
# Validate required fields
|
||||
|
||||
existing_username = User.objects.filter(username=username).first()
|
||||
if existing_username:
|
||||
return Response({"message": "Username already exists."}, status=400)
|
||||
# Validate required fields first
|
||||
validation_error = self.validate_required_fields(request.data)
|
||||
if validation_error:
|
||||
return validation_error
|
||||
|
||||
if not firstname:
|
||||
return Response({"message": "firstname is required."}, status=400)
|
||||
if not lastname:
|
||||
return Response({"message": "lastname is required."}, status=400)
|
||||
if not password:
|
||||
return Response({"message": "Password is required."}, status=400)
|
||||
if not username:
|
||||
return Response({"message": "Username is required."}, status=400)
|
||||
if not address:
|
||||
return Response({"message": "Address is required."}, status=400)
|
||||
if not mobile:
|
||||
return Response({"message": "Mobile number is required."}, status=400)
|
||||
if not acc_no:
|
||||
return Response({"message": "Account number is required."}, status=400)
|
||||
if not id_card:
|
||||
return Response({"message": "ID card is required."}, status=400)
|
||||
if not dob:
|
||||
return Response({"message": "Date of birth is required."}, status=400)
|
||||
if not atoll_id:
|
||||
return Response({"message": "Atoll is required."}, status=400)
|
||||
if not island_id:
|
||||
return Response({"message": "Island is required."}, status=400)
|
||||
if terms_accepted is None:
|
||||
return Response({"message": "Terms acceptance is required."}, status=400)
|
||||
if policy_accepted is None:
|
||||
return Response({"message": "Policy acceptance is required."}, status=400)
|
||||
# Check username uniqueness after validation
|
||||
if User.objects.filter(username=username).exists():
|
||||
return Response({"message": ErrorMessages.USERNAME_EXISTS}, 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
|
||||
)
|
||||
if id_card and not re.match(ID_CARD_PATTERN, id_card):
|
||||
return Response({"message": ErrorMessages.INVALID_ID_CARD}, status=400)
|
||||
|
||||
if not re.match(r"^[7|9][0-9]{6}$", mobile):
|
||||
return Response(
|
||||
{"message": "Please enter a valid mobile number."}, status=400
|
||||
)
|
||||
if User.objects.filter(id_card=id_card).exists():
|
||||
return Response({"message": "ID card already exists."}, 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
|
||||
)
|
||||
if mobile is None or not re.match(MOBILE_PATTERN, mobile):
|
||||
return Response({"message": ErrorMessages.INVALID_MOBILE}, status=400)
|
||||
|
||||
if acc_no is None or not re.match(ACCOUNT_NUMBER_PATTERN, acc_no):
|
||||
return Response({"message": ErrorMessages.INVALID_ACCOUNT}, status=400)
|
||||
|
||||
# Fetch Atoll and Island instances
|
||||
try:
|
||||
@ -111,19 +96,20 @@ class CreateUserView(generics.CreateAPIView):
|
||||
except Island.DoesNotExist:
|
||||
return Response({"message": "Island not found."}, status=404)
|
||||
|
||||
# Create user without email
|
||||
# Create user
|
||||
user = User.objects.create_user(
|
||||
first_name=firstname,
|
||||
last_name=lastname,
|
||||
username=username,
|
||||
username=str(username),
|
||||
password=password,
|
||||
email=None,
|
||||
address=address,
|
||||
mobile=mobile,
|
||||
acc_no=acc_no,
|
||||
id_card=id_card,
|
||||
dob=dob,
|
||||
atoll=atoll, # Assign the Atoll instance
|
||||
island=island, # Assign the Island instance
|
||||
atoll=atoll,
|
||||
island=island,
|
||||
terms_accepted=terms_accepted,
|
||||
policy_accepted=policy_accepted,
|
||||
)
|
||||
@ -133,6 +119,32 @@ class CreateUserView(generics.CreateAPIView):
|
||||
serializer.data, status=status.HTTP_201_CREATED, headers=headers
|
||||
)
|
||||
|
||||
def validate_required_fields(self, data):
|
||||
required_fields = {
|
||||
"firstname": "First name",
|
||||
"lastname": "Last name",
|
||||
"password": "Password",
|
||||
"username": "Username",
|
||||
"address": "Address",
|
||||
"mobile": "Mobile number",
|
||||
"acc_no": "Account number",
|
||||
"id_card": "ID card",
|
||||
"dob": "Date of birth",
|
||||
"atoll": "Atoll",
|
||||
"island": "Island",
|
||||
}
|
||||
|
||||
for field, label in required_fields.items():
|
||||
if not data.get(field):
|
||||
return Response({"message": f"{label} is required."}, status=400)
|
||||
|
||||
if data.get("terms_accepted") is None:
|
||||
return Response({"message": "Terms acceptance is required."}, status=400)
|
||||
if data.get("policy_accepted") is None:
|
||||
return Response({"message": "Policy acceptance is required."}, status=400)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class LoginView(KnoxLoginView):
|
||||
# login view extending KnoxLoginView
|
||||
@ -216,7 +228,11 @@ def filter_user(request):
|
||||
print(f"Querying with filters: {filters}")
|
||||
print(f"Found user: {user}")
|
||||
|
||||
return Response({"ok": True if user else False})
|
||||
return Response(
|
||||
{"ok": True, "verified": user.verified}
|
||||
if user
|
||||
else {"ok": False, "verified": False}
|
||||
)
|
||||
|
||||
|
||||
class ListUserByIDCardView(generics.ListAPIView):
|
||||
|
@ -22,6 +22,21 @@ class DeviceListCreateAPIView(
|
||||
filterset_fields = "__all__"
|
||||
filterset_class = DeviceFilter
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
|
||||
# Filter devices by the logged-in user unless the user is a superuser
|
||||
if not request.user.is_superuser:
|
||||
queryset = queryset.filter(user=request.user)
|
||||
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
def get_serializer_class(self) -> type:
|
||||
if self.request.method == "POST":
|
||||
return CreateDeviceSerializer
|
||||
|
@ -44,7 +44,6 @@ def authenticate_by_token(callback_token):
|
||||
|
||||
|
||||
def create_callback_token_for_user(user, token_type):
|
||||
|
||||
token = None
|
||||
token_type = token_type.upper()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user