Initial commit

This commit is contained in:
2025-01-20 14:33:03 +05:00
commit 4d0eb86478
84 changed files with 4436 additions and 0 deletions

0
api/__init__.py Normal file
View File

71
api/admin.py Normal file
View File

@ -0,0 +1,71 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from api.models import User, Atoll, Island
from django.contrib.auth.models import Permission
# Define a new User admin
class UserAdmin(BaseUserAdmin):
list_display = (
"username",
"email",
"first_name",
"last_name",
"is_active",
"is_staff",
"mobile",
"address",
"acc_no",
"id_card",
"dob",
"atoll",
"island",
"terms_accepted",
"policy_accepted",
) # Add custom fields here
fieldsets = (
(None, {"fields": ("username", "password")}),
(
"Personal info",
{
"fields": (
"first_name",
"last_name",
"email",
"mobile",
"address",
"acc_no",
"id_card",
"dob",
"atoll",
"island",
"terms_accepted",
"policy_accepted",
)
},
), # Add custom fields here
(
"Permissions",
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
)
},
),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
# Re-register UserAdmin
admin.site.register(User, UserAdmin)
admin.site.register(Permission)
admin.site.register(Atoll)
admin.site.register(Island)
# TokenAdmin.raw_id_fields = ["user"]

9
api/apps.py Normal file
View File

@ -0,0 +1,9 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "api"
def ready(self):
import api.signals # Add this line to import the signals.py

5
api/authentication.py Normal file
View File

@ -0,0 +1,5 @@
from rest_framework.authentication import TokenAuthentication as BaseTokenAuth
class TokenAuthentication(BaseTokenAuth):
keyword = "Token"

17
api/backends.py Normal file
View File

@ -0,0 +1,17 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
if not password:
return None
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None

18
api/exceptions.py Normal file
View File

@ -0,0 +1,18 @@
from rest_framework.views import exception_handler
from rest_framework.exceptions import Throttled
def custom_exception_handler(exc, context):
# Call REST framework's default exception handler first,
# to get the standard error response.
response = exception_handler(exc, context)
if isinstance(exc, Throttled): # check that a Throttled exception is raised
custom_response_data = { # prepare custom response data
"message": "Too many attemps. Please Try again in %d seconds." % exc.wait,
}
response.data = (
custom_response_data # set the custom response data on response object
)
return response

16
api/filters.py Normal file
View File

@ -0,0 +1,16 @@
import django_filters
from api.models import User
class UserFilter(django_filters.FilterSet):
last_name = django_filters.CharFilter(lookup_expr="icontains")
first_name = django_filters.CharFilter(lookup_expr="icontains")
email = django_filters.CharFilter(lookup_expr="icontains")
class Meta:
model = User
fields = [
"username",
"last_name",
"first_name",
]

20
api/managers.py Normal file
View File

@ -0,0 +1,20 @@
from django.contrib.auth.models import BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, username, password=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.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, 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)
return self.create_user(username, password, **extra_fields)

View File

@ -0,0 +1,190 @@
# Generated by Django 5.1.2 on 2025-01-20 03:29
import api.managers
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
name="Atoll",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("name", models.CharField(max_length=255)),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel(
name="Island",
fields=[
(
"id",
models.CharField(max_length=255, primary_key=True, serialize=False),
),
("name", models.CharField(max_length=255)),
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
("updated_at", models.DateTimeField(auto_now=True)),
(
"atoll",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="islands",
to="api.atoll",
),
),
],
),
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("email", models.EmailField(max_length=254, unique=True)),
("address", models.CharField(blank=True, max_length=255)),
("mobile", models.CharField(blank=True, max_length=255)),
("designation", models.CharField(blank=True, max_length=255)),
("acc_no", models.CharField(blank=True, max_length=255)),
("id_card", models.CharField(blank=True, max_length=255)),
("verified", models.BooleanField(default=False)),
("dob", models.DateField(blank=True, null=True)),
("terms_accepted", models.BooleanField(default=False)),
("policy_accepted", models.BooleanField(default=False)),
("wallet_balance", models.FloatField(default=0.0)),
("ninja_user_id", models.CharField(blank=True, max_length=255)),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.permission",
verbose_name="user permissions",
),
),
(
"atoll",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="users",
to="api.atoll",
),
),
(
"island",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="users",
to="api.island",
),
),
],
options={
"verbose_name": "user",
"verbose_name_plural": "users",
"abstract": False,
},
managers=[
("objects", api.managers.CustomUserManager()),
],
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.1.2 on 2025-01-20 03:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("api", "0001_initial"),
]
operations = [
migrations.AlterField(
model_name="user",
name="email",
field=models.EmailField(blank=True, max_length=254, null=True, unique=True),
),
]

View File

@ -0,0 +1,16 @@
# Generated by Django 5.1.2 on 2025-01-20 04:33
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("api", "0002_alter_user_email"),
]
operations = [
migrations.AlterModelManagers(
name="user",
managers=[],
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 5.1.2 on 2025-01-20 04:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("api", "0003_alter_user_managers"),
]
operations = [
migrations.AlterField(
model_name="atoll",
name="id",
field=models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
migrations.AlterField(
model_name="island",
name="id",
field=models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
]

View File

14
api/mixins.py Normal file
View File

@ -0,0 +1,14 @@
from rest_framework import permissions
from .permissions import IsStaffEditorPermission
# from knox.auth import TokenAuthentication
class StaffEditorPermissionMixin:
permission_classes = [
# permissions.IsAdminUser,
permissions.IsAuthenticated,
IsStaffEditorPermission,
# TokenAuthentication,
]

46
api/models.py Normal file
View File

@ -0,0 +1,46 @@
"""
This is the models module for api.
"""
from django.contrib.auth.models import AbstractUser
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)
mobile = models.CharField(max_length=255, blank=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)
verified = models.BooleanField(default=False)
dob = models.DateField(blank=True, null=True)
terms_accepted = models.BooleanField(default=False)
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')
def get_all_fields(self, instance):
return [field.name for field in instance.get_fields()]
objects = CustomUserManager()
class Atoll(models.Model):
name = models.CharField(max_length=255)
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)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name

21
api/pagination.py Normal file
View File

@ -0,0 +1,21 @@
from rest_framework import pagination
from rest_framework.response import Response
class CustomPagination(pagination.LimitOffsetPagination):
def get_paginated_response(self, data):
return Response(
{
"meta": {
"total": self.count,
"per_page": self.limit,
"current_page": int(self.offset / self.limit) + 1,
"last_page": int((self.count - 1) / self.limit) + 1,
},
"links": {
"next_page": self.get_next_link(),
"previous_page": self.get_previous_link(),
},
"data": data,
}
)

32
api/permissions.py Normal file
View File

@ -0,0 +1,32 @@
from rest_framework import permissions
class IsStaffEditorPermission(permissions.DjangoModelPermissions):
perms_map = {
"GET": ["%(app_label)s.view_%(model_name)s"],
"OPTIONS": [],
"HEAD": [],
"POST": ["%(app_label)s.add_%(model_name)s"],
"PUT": ["%(app_label)s.change_%(model_name)s"],
"PATCH": ["%(app_label)s.change_%(model_name)s"],
"DELETE": ["%(app_label)s.delete_%(model_name)s"],
}
message = {
"message": "You do not have permission to perform this action.",
}
def has_permission(self, request, view):
# Ensure the user is authenticated
if not request.user.is_authenticated:
return False
# Get the model name from the view
model_name = view.queryset.model._meta.model_name
app_label = view.queryset.model._meta.app_label
# Check permissions based on the request method
perms = self.perms_map.get(request.method, [])
perms = [perm % {'app_label': app_label, 'model_name': model_name} for perm in perms]
return request.user.has_perms(perms)

94
api/serializers.py Normal file
View File

@ -0,0 +1,94 @@
from knox.models import AuthToken
from django.contrib.auth import authenticate
from api.models import User
from rest_framework import serializers
class CustomUserSerializer(serializers.ModelSerializer):
"""serializer for the user object"""
user_permissions = serializers.SerializerMethodField()
def get_user_permissions(self, instance):
permission_ids = instance.user_permissions.all()
return [
{"id": permission.id, "name": permission.name}
for permission in permission_ids
]
class Meta: # type: ignore
model = User
fields = (
"id",
"username",
"email",
"user_permissions",
"first_name",
"last_name",
"last_login",
"date_joined",
"is_superuser",
)
class CustomReadOnlyUserSerializer(serializers.ModelSerializer):
"""serializer for the user object"""
class Meta: # type: ignore
model = User
# fields = "__all__"
fields = (
"id",
"email",
"first_name",
"last_name",
"username",
"mobile",
"address",
)
class UserSerializer(serializers.ModelSerializer):
"""serializer for the user object"""
class Meta: # type: ignore
model = User
fields = ("username", "password")
extra_kwargs = {"password": {"write_only": True, "min_length": 5}}
def create(self, validated_data):
return User.objects.create_user(**validated_data)
class AuthSerializer(serializers.Serializer):
"""serializer for the user authentication object"""
username = serializers.CharField()
password = serializers.CharField(
style={"input_type": "password"}, trim_whitespace=False
)
def validate(self, attrs):
username = attrs.get("username")
password = attrs.get("password")
user = authenticate(
request=self.context.get("request"), username=username, password=password
)
if not user:
msg = "Unable to authenticate with provided credentials"
raise serializers.ValidationError(msg, code="authentication")
attrs["user"] = user
return user
class KnoxTokenSerializer(serializers.ModelSerializer):
"""serializer for the auth token object"""
class Meta: # type: ignore
model = AuthToken
fields = "__all__"

61
api/signals.py Normal file
View File

@ -0,0 +1,61 @@
from django.core.mail import EmailMultiAlternatives
from django.dispatch import receiver
from django.template.loader import render_to_string
from decouple import config
from django_rest_passwordreset.signals import reset_password_token_created
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')
for permission in device_permissions:
instance.user_permissions.add(permission)
instance.user_permissions.add(atoll_read_permission, island_read_permission)
@receiver(reset_password_token_created)
def password_reset_token_created(
sender, instance, reset_password_token, *args, **kwargs
):
"""
Handles password reset tokens
When a token is created, an e-mail needs to be sent to the user
:param sender: View Class that sent the signal
:param instance: View Instance that sent the signal
:param reset_password_token: Token Model Object
:param args:
:param kwargs:
:return:
"""
context = {
"current_user": reset_password_token.user,
"username": reset_password_token.user.username,
"email": reset_password_token.user.email,
"reset_password_url": f"{config('FRONTEND_URL')}/auth/reset-password-confirm/?token={reset_password_token.key}",
}
# render email text
email_html_message = render_to_string("email/password_reset_email.html", context)
email_plaintext_message = (
f"Here is your password reset link: {context['reset_password_url']}"
)
msg = EmailMultiAlternatives(
# title:
"Password Reset for {title}".format(title="Sarlink Portal"),
# message:
email_plaintext_message, # This is the plaintext version
# from:
"noreply@sarlink.net",
# to:
[reset_password_token.user.email],
)
msg.attach_alternative(email_html_message, "text/html")
msg.send()

View File

@ -0,0 +1,102 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Instructions to reset your password." />
<meta name="keywords" content="password, reset, email, instructions" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Password Reset Email</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f5f5f5;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
border-radius: 8px;
padding: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.header {
margin-bottom: 30px;
}
.logo {
color: #2c3e50;
font-size: 24px;
font-weight: bold;
}
.message {
color: #6c757d;
font-size: 16px;
line-height: 1.5;
margin-top: 20px;
}
.footer {
margin-top: 30px;
color: #6c757d;
font-size: 14px;
}
.button {
display: inline-block;
padding: 10px 20px;
background-color: #007bff;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 20px 0;
}
.button:hover {
background-color: #0056b3;
}
a {
color: #007bff;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">Password Reset Instructions</div>
</div>
<p class="message">
Hello {{ username }},
</p>
<p class="message">
We received a request to reset your password. Click the button below to create a new password:
</p>
<a href="{{ reset_password_url }}" class="button">Reset Password</a>
<p class="message">
If the button doesn't work, you can copy and paste this link into your browser:
<br>
<a href="{{ reset_password_url }}">{{ reset_password_url }}</a>
</p>
<p class="message">
If you did not request this password reset, you can safely ignore
this email.
</p>
<p class="footer">Best regards,<br>SARLink</p>
</div>
</body>
</html>

1
api/tests.py Normal file
View File

@ -0,0 +1 @@
# Create your tests here.

5
api/throttle.py Normal file
View File

@ -0,0 +1,5 @@
from rest_framework import throttling
class BurstRateThrottle(throttling.UserRateThrottle):
scope = "burst"

30
api/urls.py Normal file
View File

@ -0,0 +1,30 @@
from django.urls import path
from knox import views as knox_views
from .views import (
LoginView,
CreateUserView,
ManageUserView,
KnoxTokenListApiView,
ListUserView,
UserDetailAPIView,
healthcheck,
test_email,
)
urlpatterns = [
path("create/", CreateUserView.as_view(), name="create"),
path("profile/", ManageUserView.as_view(), name="profile"),
path("login/", LoginView.as_view(), name="knox_login"),
path("logout/", knox_views.LogoutView.as_view(), name="knox_logout"),
path("logoutall/", knox_views.LogoutAllView.as_view(), name="knox_logoutall"),
path("tokens/", KnoxTokenListApiView.as_view(), name="knox_tokens"),
# path("auth/", CustomAuthToken.as_view()),
path("users/", ListUserView.as_view(), name="users"),
path("users/<int:pk>/", UserDetailAPIView.as_view(), name="user-detail"),
path("healthcheck/", healthcheck, name="healthcheck"),
path("test/", test_email, name="testemail"),
]

29
api/utils.py Normal file
View File

@ -0,0 +1,29 @@
def reverse_dhivehi_string(input_str):
"""
Reverses a Dhivehi string while preserving character composition.
Args:
input_str (str): The Dhivehi string to be reversed
Returns:
str: The reversed Dhivehi string
"""
# Reverse the string and then normalize the character order
reversed_str = input_str[::-1]
# List to store the corrected characters
corrected_chars = []
# Iterate through the reversed string
i = 0
while i < len(reversed_str):
# Check if current character is a combining character
if i + 1 < len(reversed_str) and "\u0300" <= reversed_str[i + 1] <= "\u036F":
# If next character is a combining mark, add it before the base character
corrected_chars.append(reversed_str[i + 1] + reversed_str[i])
i += 2
else:
corrected_chars.append(reversed_str[i])
i += 1
return "".join(corrected_chars)

221
api/views.py Normal file
View File

@ -0,0 +1,221 @@
# django imports
from django.contrib.auth import login
# rest_framework imports
from rest_framework import generics, permissions
from rest_framework.authtoken.serializers import AuthTokenSerializer
from api.filters import UserFilter
from api.mixins import StaffEditorPermissionMixin
from api.models import User, Atoll, Island
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
# knox imports
from knox.views import LoginView as KnoxLoginView
from knox.models import AuthToken
from django_filters.rest_framework import DjangoFilterBackend
import re
from typing import cast, Dict, Any
from django.core.mail import send_mail
# local apps import
from .serializers import (
KnoxTokenSerializer,
UserSerializer,
AuthSerializer,
CustomUserSerializer,
CustomReadOnlyUserSerializer,
)
class CreateUserView(generics.CreateAPIView):
# Create user API view
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny,)
queryset = User.objects.all()
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
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
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)
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)
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 not re.match(r"^[7|9][0-9]{6}$", mobile):
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)
# Fetch Atoll and Island instances
try:
atoll = Atoll.objects.get(id=atoll_id)
island = Island.objects.get(id=island_id)
except Atoll.DoesNotExist:
return Response({"message": "Atoll not found."}, status=404)
except Island.DoesNotExist:
return Response({"message": "Island not found."}, status=404)
# Create user without email
user = User.objects.create_user(
first_name=firstname,
last_name=lastname,
username=username,
password=password,
address=address,
mobile=str("+960") + str(mobile),
acc_no=acc_no,
id_card=id_card,
dob=dob,
atoll=atoll, # Assign the Atoll instance
island=island, # Assign the Island instance
terms_accepted=terms_accepted,
policy_accepted=policy_accepted,
)
serializer = self.get_serializer(user)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED, headers=headers
)
class LoginView(KnoxLoginView):
# login view extending KnoxLoginView
serializer_class = AuthSerializer
permission_classes = (permissions.AllowAny,)
throttle_scope = "login"
def post(self, request, format=None):
try:
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = cast(Dict[str, Any], serializer.validated_data)["user"]
login(request, user)
response = super(LoginView, self).post(request, format=None)
return response
except ValidationError as e:
message = "Unable to log in with provided credentials."
if (
hasattr(e, "detail")
and isinstance(e.detail, list)
and len(e.detail) > 0
):
message = e.detail[0]
return Response({"message": message}, status=status.HTTP_400_BAD_REQUEST)
class ManageUserView(generics.RetrieveUpdateAPIView):
"""Manage the authenticated user"""
serializer_class = CustomUserSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_object(self):
"""Retrieve and return authenticated user"""
return self.request.user
class KnoxTokenListApiView(
StaffEditorPermissionMixin,
generics.ListAPIView,
):
# Create user API view
serializer_class = KnoxTokenSerializer
permission_classes = (permissions.IsAuthenticated,)
queryset = AuthToken.objects.all()
def get(self, request, *args, **kwargs):
user_id = getattr(request.user, "id", None)
if user_id is None:
return Response({"error": "User ID not found"}, status=400)
queryset = AuthToken.objects.filter(user_id=user_id)
data = KnoxTokenSerializer(queryset, many=True).data
return Response({"data": data})
class ListUserView(StaffEditorPermissionMixin, generics.ListAPIView):
# Create user API view
serializer_class = CustomReadOnlyUserSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = "__all__"
filterset_class = UserFilter
queryset = User.objects.all()
class UserDetailAPIView(StaffEditorPermissionMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = CustomReadOnlyUserSerializer
lookup_field = "pk"
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = serializer.data
# Customize the response format
return Response(data)
@api_view(["GET"])
def healthcheck(request):
return Response({"status": "ok"}, status=status.HTTP_200_OK)
@api_view(["POST"])
@permission_classes((permissions.AllowAny,))
def test_email(request):
send_mail(
"Subject here",
"Here is the message.",
"noreply@sarlink.net",
["shihaam@shihaam.me"],
fail_silently=False,
)
return Response({"status": "ok"}, status=status.HTTP_200_OK)