Browse Source

fix guest user token

master
alireza 1 year ago
parent
commit
aa5613ed12
  1. 57
      apps/account/migrations/0008_auto_20250316_1247.py
  2. 31
      apps/account/migrations/0009_auto_20250316_1319.py
  3. 18
      apps/account/migrations/0010_loginhistory_timezone.py
  4. 15
      apps/account/models/notification.py
  5. 77
      apps/account/models/user.py
  6. 6
      apps/account/serializers/notification.py
  7. 38
      apps/account/serializers/user.py
  8. 1
      apps/account/urls.py
  9. 58
      apps/account/views/notification.py
  10. 105
      apps/account/views/user.py

57
apps/account/migrations/0008_auto_20250316_1247.py

@ -0,0 +1,57 @@
# Generated by Django 3.2.7 on 2025-03-16 12:47
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('account', '0007_notification'),
]
operations = [
migrations.AddField(
model_name='notification',
name='service',
field=models.CharField(choices=[('imam-javad', 'Imam Javad'), ('doboodi', 'Doboodi')], default='imam-javad', max_length=20, verbose_name='service'),
),
migrations.AddField(
model_name='user',
name='device_os',
field=models.CharField(choices=[('android', 'android'), ('apple', 'apple')], max_length=16, null=True),
),
migrations.AddField(
model_name='user',
name='username',
field=models.CharField(blank=True, max_length=150, null=True, unique=True),
),
migrations.AlterField(
model_name='user',
name='email',
field=models.EmailField(blank=True, help_text="Enter the user's email address.", max_length=254, null=True, unique=True, verbose_name='Email Address'),
),
migrations.AlterField(
model_name='user',
name='fullname',
field=models.CharField(blank=True, help_text='Enter the full name of the user.', max_length=255, null=True, verbose_name='Full Name'),
),
migrations.AlterUniqueTogether(
name='user',
unique_together={('email', 'device_id')},
),
migrations.CreateModel(
name='LocationHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lat', models.FloatField(blank=True, null=True, verbose_name='lat')),
('lon', models.FloatField(blank=True, null=True, verbose_name='lon')),
('country', models.CharField(blank=True, max_length=255, null=True, verbose_name='country')),
('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='city')),
('ip', models.CharField(max_length=255, null=True)),
('at_time', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='location_history', to=settings.AUTH_USER_MODEL)),
],
),
]

31
apps/account/migrations/0009_auto_20250316_1319.py

@ -0,0 +1,31 @@
# Generated by Django 3.2.7 on 2025-03-16 13:19
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('account', '0008_auto_20250316_1247'),
]
operations = [
migrations.CreateModel(
name='LoginHistory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lat', models.FloatField(blank=True, null=True, verbose_name='lat')),
('lon', models.FloatField(blank=True, null=True, verbose_name='lon')),
('country', models.CharField(blank=True, max_length=255, null=True, verbose_name='country')),
('city', models.CharField(blank=True, max_length=255, null=True, verbose_name='city')),
('ip', models.CharField(max_length=255, null=True)),
('at_time', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_history', to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='LocationHistory',
),
]

18
apps/account/migrations/0010_loginhistory_timezone.py

@ -0,0 +1,18 @@
# Generated by Django 3.2.7 on 2025-03-16 13:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0009_auto_20250316_1319'),
]
operations = [
migrations.AddField(
model_name='loginhistory',
name='timezone',
field=models.CharField(blank=True, max_length=100, null=True),
),
]

15
apps/account/models/notification.py

@ -2,17 +2,24 @@ from django.db import models
from django.utils.translation import gettext as _
class Notification(models.Model):
class ServiceChoices(models.TextChoices):
IMAM_JAVAD = 'imam-javad', 'Imam Javad'
DOBOODI = 'doboodi', 'Doboodi'
title = models.CharField(max_length=255, verbose_name=_('title'))
message = models.TextField(max_length=512, verbose_name=_('message'))
user = models.ForeignKey("account.User", on_delete=models.CASCADE, verbose_name=_('user'), related_name='notifications')
is_read = models.BooleanField(default=False, verbose_name=_('is read'))
service = models.CharField(
max_length=20,
choices=ServiceChoices.choices,
default=ServiceChoices.IMAM_JAVAD,
verbose_name=_('service')
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at'), null=True)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at'), null=True)
def __str__(self):
return self.title

77
apps/account/models/user.py

@ -11,6 +11,10 @@ from apps.account.manager import UserManager
class User(AbstractUser):
class DeviceOs(models.TextChoices):
android = 'android', 'android'
apple = 'apple', 'apple'
class UserType(models.TextChoices):
PROFESSOR = 'professor', 'Professor'
CLIENT = 'client', 'Client'
@ -21,33 +25,29 @@ class User(AbstractUser):
class GenderChoices(models.TextChoices):
MALE = 'male', 'Male'
FEMALE = 'female', 'Female'
email = models.EmailField(unique=True, verbose_name="Email Address", help_text="Enter the user's email address.")
fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the full name of the user.")
last_name = None
first_name = None
username = models.CharField(unique=True, null=True, blank=True, max_length=150)
email = models.EmailField(unique=True, verbose_name="Email Address", help_text="Enter the user's email address.", null=True, blank=True)
fullname = models.CharField(max_length=255, verbose_name="Full Name", help_text="Enter the full name of the user.", null=True, blank=True)
birthdate = models.DateField(verbose_name=_('birthdate'), null=True, blank=True)
avatar = models.ImageField(null=True, blank=True, upload_to='users/avatars/%Y/%m/')
phone_number = PhoneNumberField(unique=True, validators=[validate_possible_number], null=True, blank=True, verbose_name=_('phone'))
language = LanguageField(null=True)
username = None
last_name = None
first_name = None
gender = models.CharField(
max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender."
)
user_type = models.CharField(
max_length=20,
choices=UserType.choices,
default=UserType.CLIENT,
verbose_name="User Type",
help_text="Type of the user."
)
gender = models.CharField(max_length=20, choices=GenderChoices.choices, null=True, blank=True, verbose_name=_('Gender'), help_text="Select the user's gender.")
user_type = models.CharField(max_length=20, choices=UserType.choices, default=UserType.CLIENT, verbose_name="User Type", help_text="Type of the user.")
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="Date Joined", help_text="The date and time the user registered.")
city = models.CharField(verbose_name=_('City'), max_length=255, null=True, blank=True)
country = models.CharField(max_length=255, verbose_name=_('country'), null=True, blank=True)
device_id = models.CharField(verbose_name=_('device id'), max_length=255, null=True, blank=True)
device_os = models.CharField(choices=DeviceOs.choices, null=True, max_length=16)
fcm = models.CharField(max_length=512, null=True, blank=True)
date_joined = models.DateTimeField(auto_now_add=True, verbose_name="Date Joined", help_text="The date and time the user registered.")
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True, verbose_name="Active", help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")
deleted_at = models.DateTimeField(null=True, blank=True)
@ -57,31 +57,36 @@ class User(AbstractUser):
EMAIL_FIELD = "email"
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["fullname", ]
USERNAME_FIELD = "username"
REQUIRED_FIELDS = []
def __str__(self):
username = self.email or self.fullname or self.device_id
return f"{username}-({self.user_type})"
def soft_delete(self):
self.deleted_at = timezone.now()
self.is_active = False
self.fullname = f'{self.fullname}:deleted'
number = str(random.randint(1000000000, 9999999999)) # ایجاد یک عدد رندوم 10 رقمی
number = str(random.randint(1000000000, 9999999999))
self.phone_number = f'{self.phone_number}:deleted{number}'
self.email = f'{self.email}:deleted{number}' if self.email else None
self.save()
# def clean(self):
# super().clean()
# if self.email == "":
# # fix db uniqueness error bcz of django charfield null to empty string conversion
# self.email = None
def __str__(self):
return f"{self.email} - {self.get_full_name()} - ({self.user_type})"
def save(self, *args, **kwargs):
self.username = self.email
if User.objects.filter(username=self.email).count():
self.username = f'{self.email}:{self.id}'
return super().save(*args, **kwargs)
def get_full_name(self):
return self.fullname
@property
def is_guest(self):
return self.email is None
@property
def user_type_based_on_groups(self):
@ -97,3 +102,17 @@ class User(AbstractUser):
ordering = ("-id",)
verbose_name = "All Users"
verbose_name_plural = "All Users"
unique_together = (
'email', 'device_id'
)
class LoginHistory(models.Model):
user = models.ForeignKey("account.User", on_delete=models.CASCADE, related_name='login_history')
lat = models.FloatField(verbose_name=_('lat'), null=True, blank=True)
lon = models.FloatField(verbose_name=_('lon'), null=True, blank=True)
country = models.CharField(max_length=255, verbose_name=_('country'), null=True, blank=True)
city = models.CharField(max_length=255, verbose_name=_('city'), null=True, blank=True)
ip = models.CharField(max_length=255, null=True)
timezone = models.CharField(max_length=100, null=True, blank=True)
at_time = models.DateTimeField(auto_now_add=True)

6
apps/account/serializers/notification.py

@ -9,10 +9,11 @@ from apps.account.models import User
class NotificationSerializer(serializers.ModelSerializer):
user_type = serializers.ChoiceField(choices=[('user', 'User'), ('merchant', 'Merchant')], default='user')
service = serializers.ChoiceField(choices=Notification.ServiceChoices.choices, default=Notification.ServiceChoices.IMAM_JAVAD)
class Meta:
model = Notification
fields = ['id', 'title', 'message', 'is_read', 'user_type', 'created_at', 'updated_at']
fields = ['id', 'title', 'message', 'is_read', 'user_type', 'service', 'created_at', 'updated_at']
@ -21,4 +22,5 @@ class NotificationSendSerializer(serializers.Serializer):
body = serializers.CharField()
data = serializers.DictField(required=False)
account_id = serializers.CharField(required=True)
user_type = serializers.CharField(required=True)
user_type = serializers.CharField(required=True)
service = serializers.ChoiceField(choices=Notification.ServiceChoices.choices, default=Notification.ServiceChoices.IMAM_JAVAD)

38
apps/account/serializers/user.py

@ -18,10 +18,11 @@ class UserProfileSerializer(serializers.ModelSerializer):
required=False,
help_text="Select the user's gender."
)
fcm = serializers.CharField(required=False, help_text="Firebase Cloud Messaging token.")
class Meta:
model = User
fields = ['id', 'fullname', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill', 'city', 'country', 'birthdate', 'gender']
read_only_fields = ['email', 'info', 'skill']
fields = ['id', 'device_id', 'fcm', 'fullname', 'avatar', 'email', 'phone_number', 'password', 'info', 'skill', 'city', 'country', 'birthdate', 'gender']
read_only_fields = ['email', 'info', 'skill', 'device_id']
# def validate_email(self, value):
# if User.objects.filter(email=value).exists():
@ -44,7 +45,7 @@ class UserProfileSerializer(serializers.ModelSerializer):
class UserRegisterSerializer(serializers.ModelSerializer):
password_confirmation = serializers.CharField(write_only=True)
fcm = serializers.CharField(required=False)
device_id = serializers.CharField(required=False)
device_id = serializers.CharField(required=True)
email = serializers.EmailField()
class Meta:
@ -55,6 +56,7 @@ class UserRegisterSerializer(serializers.ModelSerializer):
'email': {'required': True,},
'password': {'required': True,},
'password_confirmation': {'required': True,},
'device_id': {'required': True,},
}
def validate_email(self, value):
@ -75,8 +77,6 @@ class UserRegisterSerializer(serializers.ModelSerializer):
# If there are any errors, raise ValidationError
data.pop('password_confirmation', None)
data.pop('fcm', None)
data.pop('device_id', None)
return data
@ -102,11 +102,12 @@ class UserLoginSerializer(serializers.ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'}, trim_whitespace=False)
fcm = serializers.CharField(required=False)
device_id = serializers.CharField(required=False)
timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
class Meta:
model = User
fields = ['id', 'phone_number', 'password', 'fullname', 'avatar', 'email', 'token', 'fcm', 'device_id']
fields = ['id', 'phone_number', 'password', 'fullname', 'avatar', 'email', 'token', 'fcm', 'device_id', 'timezone']
def get_token(self, obj):
token, created = Token.objects.get_or_create(user=obj)
@ -142,8 +143,8 @@ class UserResetPasswordSerializer(serializers.ModelSerializer):
'password': {'required': True,},
'password_confirmation': {'required': True,},
}
def validate(self, data):
password = data.get('password')
password_confirmation = data.get('password_confirmation')
@ -158,4 +159,23 @@ class UserResetPasswordSerializer(serializers.ModelSerializer):
data.pop('password_confirmation', None)
return data
class UserGuestSerializer(serializers.ModelSerializer):
lat = serializers.CharField(max_length=255, allow_null=True, allow_blank=True, required=False)
lon = serializers.CharField(max_length=255, allow_null=True, allow_blank=True, required=False)
fcm = serializers.CharField(required=False)
device_id = serializers.CharField(required=False)
device_os = serializers.ChoiceField(choices=User.DeviceOs.choices, required=False)
timezone = serializers.CharField(required=False, allow_null=True, allow_blank=True)
class Meta:
model = User
fields = ['device_id', 'fcm', 'device_os', 'lat', 'lon', 'timezone']
def validate(self, data):
# Make sure at least device_id is provided
if not data.get('device_id'):
raise serializers.ValidationError({"device_id": "Device ID is required for guest users."})
return data

1
apps/account/urls.py

@ -13,6 +13,7 @@ urlpatterns = [
path('register/', views.UserRegisterView.as_view(), name='user-register'),
path('verify/', views.UserVerifyView.as_view(), name='user-verify'),
path('login/', views.UserLoginView.as_view(), name='user-login'),
path('guest/', views.UserGuestView.as_view(), name='user-guest'),
# path('notif/', views.NotificationListView.as_view(), name='user-notif'),

58
apps/account/views/notification.py

@ -17,7 +17,17 @@ class NotificationListView(generics.ListAPIView):
@swagger_auto_schema(
operation_description="Retrieve a list of notifications for the authenticated user or merchant account.",
tags=['Notifications']
tags=['Notifications'],
manual_parameters=[
openapi.Parameter(
'service',
openapi.IN_QUERY,
description="Filter notifications by service (imam-javad or doboodi)",
type=openapi.TYPE_STRING,
enum=['imam-javad', 'doboodi'],
required=False
)
]
)
def get(self, request, *args, **kwargs):
"""
@ -27,36 +37,68 @@ class NotificationListView(generics.ListAPIView):
- **Method**: GET
- **URL**: /api/notifications/
- **Response**: Includes details of notifications such as title, message, is read status, creation date, and update date.
- **Query Parameters**:
- `service`: Optional. Filter notifications by service ('imam-javad' or 'doboodi')
- **Response**: Includes details of notifications such as title, message, is read status, service, creation date, and update date.
- **Headers**: `Authorization: Bearer <token>` for authentication.
"""
return super().get(request, *args, **kwargs)
def get_queryset(self):
user = self.request.user
return Notification.objects.filter(user=user).order_by('-created_at')
user = self.request.user
queryset = Notification.objects.filter(user=user)
# Filter by service if provided in query params
service = self.request.query_params.get('service', None)
if service:
queryset = queryset.filter(service=service)
return queryset.order_by('-created_at')
class NotificationReadAllView(generics.GenericAPIView):
permission_classes = [IsAuthenticated,]
queryset = Notification.objects.all()
permission_classes = [IsAuthenticated,]
queryset = Notification.objects.all()
@swagger_auto_schema(
operation_description="Mark all notifications as read for the authenticated user or merchant account.",
tags=['Notifications'],
manual_parameters=[
openapi.Parameter(
'service',
openapi.IN_QUERY,
description="Filter notifications to mark as read by service (imam-javad or doboodi)",
type=openapi.TYPE_STRING,
enum=['imam-javad', 'doboodi'],
required=False
)
],
responses={
200: "All notifications marked as read",
403: "Forbidden",
}
)
def get(self, request, *args, **kwargs):
user = request.user
service = request.query_params.get('service', None)
# Get base queryset for user's notifications
notifications = Notification.objects.filter(user=user)
# Apply service filtering based on query parameter
if service == 'doboodi':
# If service is doboodi, only mark doboodi notifications as read
notifications = notifications.filter(service=Notification.ServiceChoices.DOBOODI)
status_message = 'all doboodi notifications marked as read'
else:
# Default: mark all imam-javad notifications as read (exclude doboodi)
notifications = notifications.exclude(service=Notification.ServiceChoices.DOBOODI)
status_message = 'all imam-javad notifications marked as read'
# Update the filtered notifications
notifications.update(is_read=True)
return Response({'status': 'all notifications marked as read'}, status=status.HTTP_200_OK)
return Response({'status': status_message}, status=status.HTTP_200_OK)

105
apps/account/views/user.py

@ -5,6 +5,7 @@ from rest_framework.generics import CreateAPIView, RetrieveUpdateAPIView, Generi
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.db.models import Q
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.authtoken.models import Token
from rest_framework.exceptions import AuthenticationFailed
@ -22,7 +23,7 @@ from rest_framework.exceptions import ValidationError
from utils.exceptions import InvaliedCodeVrify, ExpiredCodeException, ServiceUnavailableException
from apps.account.models import User
from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer
from apps.account.serializers import UserRegisterSerializer, UserProfileSerializer, UserVerifySerializer, UserLoginSerializer, UserRecoverPasswordSerializer, UserResetPasswordSerializer, UserGuestSerializer
from utils.redis import RedisManager
from utils.exceptions import AppAPIException
from utils import send_email, is_valid_email
@ -34,8 +35,73 @@ logger = logging.getLogger(__name__)
class UserGuestView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserGuestSerializer
@swagger_auto_schema(
operation_description="Create a guest user account with device information",
request_body=UserGuestSerializer,
)
def post(self, request, *args, **kwargs):
logger.info(f'GuestAuthView--> {request.data}')
return super().post(request, *args, **kwargs)
@staticmethod
def generate_login_token(user):
token, created = Token.objects.update_or_create(user=user)
return token.key
def get_client_ip(self):
request = self.request
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
return Response({
'token': self.generate_login_token(user),
}, status=200)
def perform_create(self, serializer):
device_id = serializer.validated_data.get('device_id')
device_os = serializer.validated_data.get('device_os')
fcm = serializer.validated_data.get('fcm')
lat, lon = serializer.validated_data.get('lat'), serializer.validated_data.get('lon')
user_timezone = serializer.validated_data.pop('timezone', None)
serializer_data = dict(serializer.validated_data)
obj = User.objects.select_for_update().filter(Q(device_id=device_id)).first()
if not obj:
obj, created = User.objects.select_for_update().get_or_create(
device_id=device_id,
defaults=serializer_data
)
if created:
logger.info(f'Guest-(created)->: {obj.device_id}')
obj.last_login = timezone.now()
obj.save()
login_history_obj = obj.login_history.create(
lat=lat,
lon=lon,
ip=self.get_client_ip(),
timezone=user_timezone,
)
return obj
class UserRegisterView(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserRegisterSerializer
@ -52,7 +118,7 @@ class UserRegisterView(CreateAPIView):
code = RedisManager.generate_otp_code()
logger.info(f"phone= {data['email']}")
print(f' send {code}/{data["email"]}')
print(f'send {code}/{data["email"]}')
phone_number = RedisManager().add_to_redis(code, **data)
send_email([data['email']], code)
@ -99,8 +165,8 @@ class UserVerifyView(CreateAPIView):
user = self.perform_create(
email=serializer.data['email'],**verify_data
)
Token.objects.filter(user=user).delete()
token = Token.objects.create(user=user)
# Token.objects.filter(user=user).delete()
token = Token.objects.get_or_create(user=user)
return Response(data={
'token': str(token),
'user_id': user.id,
@ -112,7 +178,6 @@ class UserVerifyView(CreateAPIView):
def valied_code(self, current_code, save_code):
if (current_code and save_code) and ( current_code != save_code):
# raise InvaliedCodeVrify()
raise ValidationError({"code": "code notfound"})
return current_code
@ -128,7 +193,10 @@ class UserVerifyView(CreateAPIView):
user.set_password(kwargs['password'])
user.save()
else:
user = User.objects.create(**kwargs)
user = User.objects.flter(device_id=kwargs['device_id']).first()
if not user:
user = User.objects.create(**kwargs)
user.set_password(kwargs['password'])
user.last_login = timezone.now()
user.is_active = True
@ -148,7 +216,16 @@ class UserLoginView(CreateAPIView):
def post(self, request, *args, **kwargs):
return super().post(request, *args, **kwargs)
@staticmethod
def get_client_ip(self):
request = self.request
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
@ -156,13 +233,18 @@ class UserLoginView(CreateAPIView):
user = authenticate(request, username=request.data['email'], password=data['password'])
if not user:
raise ValidationError({"email": "Unable to log in with provided credentials."})
user_timezone = serializer.validated_data.pop('timezone', None)
user.last_login = timezone.now()
user.is_active = True
user.save
token, created = Token.objects.get_or_create(user=user)
serializer_data = serializer.data
serializer_data['token'] = token.key
login_history_obj = obj.login_history.create(
ip=self.get_client_ip(),
timezone=user_timezone,
)
return Response({
"id": user.id,
"fullname": user.fullname,
@ -173,6 +255,7 @@ class UserLoginView(CreateAPIView):
}, status=status.HTTP_201_CREATED)
class UserProfileView(RetrieveAPIView):
serializer_class = UserProfileSerializer
permission_classes = [IsAuthenticated, IsActiveUser]
@ -256,12 +339,12 @@ class UserDeleteView(APIView):
if user.email == "admin@gmail.com":
raise AppAPIException({"message": "Unable to log in with provided credentials."}, status_code=status.HTTP_204_NO_CONTENT)
user.soft_delete()
user.soft_delete()
if t := Token.objects.filter(user=user).first():
t.delete()
return Response({"detail": "Your account has been deleted."}, status=status.HTTP_204_NO_CONTENT)
except Exception:
# پیام خطای ثابت برای سایر خطاهای غیرمنتظره
return Response({"detail": "User does not exist."}, status=status.HTTP_404_NOT_FOUND)

Loading…
Cancel
Save