10 changed files with 343 additions and 63 deletions
-
57apps/account/migrations/0008_auto_20250316_1247.py
-
31apps/account/migrations/0009_auto_20250316_1319.py
-
18apps/account/migrations/0010_loginhistory_timezone.py
-
15apps/account/models/notification.py
-
77apps/account/models/user.py
-
6apps/account/serializers/notification.py
-
38apps/account/serializers/user.py
-
1apps/account/urls.py
-
58apps/account/views/notification.py
-
105apps/account/views/user.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)), |
|||
], |
|||
), |
|||
] |
|||
@ -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', |
|||
), |
|||
] |
|||
@ -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), |
|||
), |
|||
] |
|||
@ -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 == "[email protected]": |
|||
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) |
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue