Skip to content

Commit

Permalink
Merge pull request #53 from Team-UmbrellaFriend/feat/#47-회원탈퇴구현
Browse files Browse the repository at this point in the history
[Feat] #47 회원탈퇴구현
  • Loading branch information
dpfls0922 authored May 12, 2024
2 parents ae8f8b6 + c0e514b commit 96820df
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 17 deletions.
8 changes: 7 additions & 1 deletion UmbrellaFriend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,8 @@


CRONJOBS = [
('0 0 * * *', 'home.cron.send_return_reminder_email', '>> /tmp/scheduled_job.log'),
('0 0 * * *', 'home.cron.send_return_reminders', '>> /tmp/email_push.log 2>&1'),
('0 0 * * *', 'home.cron.delete_withdrawal_records', '>> /tmp/delete_withdrawal_record.log 2>&1'),
]


Expand Down Expand Up @@ -215,5 +216,10 @@
'level': 'INFO',
'propagate': False,
},
'users.views': {
'handlers': ['file'],
'level': 'INFO',
'propagate': False,
},
},
}
7 changes: 5 additions & 2 deletions home/cron.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from django.core.management import call_command

def send_return_reminder_email():
call_command('send_return_reminders')
def send_return_reminders():
call_command('send_return_reminders')

def delete_withdrawal_records():
call_command('delete_expired_withdrawal_records')
17 changes: 17 additions & 0 deletions home/management/commands/delete_expired_withdrawal_records.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from users.models import WithdrawalRecord
from datetime import datetime


class Command(BaseCommand):
help = '만료된 회원탈퇴 기록 지우기'

def handle(self, *args, **options):
print("[", datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "]")
try:
expired_records = WithdrawalRecord.objects.filter(expiration_date__lte=timezone.now())
num_deleted, _ = expired_records.delete()
print(f'{num_deleted} 개의 만료된 회원탈퇴 기록이 삭제되었습니다.')
except Exception as e:
print(f'회원탈퇴 기록 삭제 중 에러 발생: {e}')
21 changes: 21 additions & 0 deletions mypage/migrations/0004_alter_umbrellareport_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Generated by Django 5.0.1 on 2024-05-12 23:42

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('mypage', '0003_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AlterField(
model_name='umbrellareport',
name='user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
14 changes: 10 additions & 4 deletions mypage/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ class UmbrellaReport(models.Model):
)

umbrella = models.ForeignKey(Umbrella, on_delete = models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete = models.CASCADE, null = True)
report_reason = models.CharField(max_length = 10, choices = REPORT_CHOICES)
description = models.TextField(max_length = 200, blank = True, null = True)
is_done = models.BooleanField(default = False)

def __str__(self):
if self.is_done:
return f'[{self.user.username}] 우산{self.umbrella.number} {self.report_reason} - 해결완료(O)'
if self.user:
if self.is_done:
return f'[{self.user.username}] 우산{self.umbrella.number} {self.report_reason} - 해결완료(O)'
else:
return f'[{self.user.username}] 우산{self.umbrella.number} {self.report_reason} - 해결안됨(X)'
else:
return f'[{self.user.username}] 우산{self.umbrella.number} {self.report_reason} - 해결안됨(X)'
if self.is_done:
return f'[탈퇴한 회원] 우산{self.umbrella.number} {self.report_reason} - 해결완료(O)'
else:
return f'[탈퇴한 회원] 우산{self.umbrella.number} {self.report_reason} - 해결안됨(X)'
19 changes: 19 additions & 0 deletions umbrella/migrations/0004_alter_rent_return_due_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.1 on 2024-04-22 22:12

import datetime
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('umbrella', '0003_alter_rent_return_due_date'),
]

operations = [
migrations.AlterField(
model_name='rent',
name='return_due_date',
field=models.DateTimeField(default=datetime.datetime(2024, 4, 25, 22, 12, 37, 10220)),
),
]
19 changes: 19 additions & 0 deletions umbrella/migrations/0005_alter_rent_return_due_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.1 on 2024-05-12 23:42

import datetime
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('umbrella', '0004_alter_rent_return_due_date'),
]

operations = [
migrations.AlterField(
model_name='rent',
name='return_due_date',
field=models.DateTimeField(default=datetime.datetime(2024, 5, 15, 23, 42, 54, 658653)),
),
]
5 changes: 3 additions & 2 deletions users/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#users/admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from .models import CustomUser, Profile
from .models import CustomUser, Profile, WithdrawalRecord

class ProfileInline(admin.StackedInline):
model = Profile
Expand All @@ -11,4 +11,5 @@ class ProfileInline(admin.StackedInline):
class CustomUserAdmin(BaseUserAdmin):
inlines = (ProfileInline, )

admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(WithdrawalRecord)
26 changes: 26 additions & 0 deletions users/migrations/0003_withdrawalrecord.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 5.0.1 on 2024-05-12 23:42

import datetime
import django.utils.timezone
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('users', '0002_profile_fcm_token'),
]

operations = [
migrations.CreateModel(
name='WithdrawalRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('studentID', models.IntegerField(default=0, unique=True)),
('withdrawal_reason', models.CharField(choices=[('수량', '우산 수량이 적어서 사용을 잘 안해요.'), ('관리', '우산 관리가 잘 안되어 사용할 수 없어요.'), ('새계정', '새 계정을 만들고 싶어요.'), ('기타', '기타사항 (직접 입력)')], max_length=10)),
('description', models.TextField(blank=True, max_length=200, null=True)),
('withdrawal_date', models.DateTimeField(default=django.utils.timezone.now)),
('expiration_date', models.DateTimeField(default=datetime.datetime(2024, 5, 19, 23, 42, 54, 660042))),
],
),
]
22 changes: 20 additions & 2 deletions users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from umbrella.models import Umbrella
from django.contrib.auth.models import AbstractUser
from django.conf import settings

from django.utils import timezone

class CustomUser(AbstractUser):
username = models.CharField(max_length = 10, unique = False)
Expand All @@ -22,4 +22,22 @@ class Profile(models.Model):
studentCard = models.ImageField(upload_to = f'profile')
phoneNumber = models.CharField(max_length = 20)
umbrella = models.OneToOneField(Umbrella, related_name = 'user', null = True, blank = True, on_delete = models.SET_NULL)
fcm_token = models.CharField(max_length = 200, blank = True, null = True)
fcm_token = models.CharField(max_length = 200, blank = True, null = True)


class WithdrawalRecord(models.Model):
REPORT_CHOICES = (
('수량', '우산 수량이 적어서 사용을 잘 안해요.'),
('관리', '우산 관리가 잘 안되어 사용할 수 없어요.'),
('새계정', '새 계정을 만들고 싶어요.'),
('기타', '기타사항 (직접 입력)'),
)

studentID = models.IntegerField(default = 0, unique = True)
withdrawal_reason = models.CharField(max_length = 10, choices = REPORT_CHOICES)
description = models.TextField(max_length = 200, blank = True, null = True)
withdrawal_date = models.DateTimeField(default = timezone.now)
expiration_date = models.DateTimeField(default = lambda: timezone.now() + timezone.timedelta(days = 7)) # 만료 일자

def __str__(self):
return f'[{self.studentID}] {self.withdrawal_reason}'
12 changes: 10 additions & 2 deletions users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from django.contrib.auth import authenticate # DefautlAuthBackend인 TokenAuth 방식으로 유저 인증
from django.contrib.auth.models import update_last_login
from .models import Profile, CustomUser
from .models import Profile, CustomUser, WithdrawalRecord

# 회원가입
class SignUpProfileSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -136,4 +136,12 @@ def update(self, instance, validated_data):
if profile_data:
profile.phoneNumber = profile_data.get('phoneNumber', profile.phoneNumber)
profile.save()
return instance
return instance



# 회원탈퇴
class RecordSerializer(serializers.ModelSerializer):
class Meta:
model = WithdrawalRecord
fields = ('studentID', 'withdrawal_reason', 'description', 'withdrawal_date')
3 changes: 2 additions & 1 deletion users/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#users/urls.py
from django.urls import path
from .views import SignUpView, LoginView, LogoutView, ProfileView
from .views import SignUpView, LoginView, LogoutView, ProfileView, DeleteAccountView

urlpatterns = [
path('signup/', SignUpView.as_view()),
path('login/', LoginView.as_view()),
path('logout/', LogoutView.as_view()),
path('profile/<int:user_id>/', ProfileView.as_view()),
path('withdrawal/', DeleteAccountView.as_view()),
]
62 changes: 59 additions & 3 deletions users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,16 @@
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from .serializers import SignUpSerializer, LoginSerializer, UserUpdateSerializer
from .models import CustomUser
from .serializers import SignUpSerializer, LoginSerializer, UserUpdateSerializer, RecordSerializer
from .models import CustomUser, WithdrawalRecord
from umbrella.models import Rent
from mypage.models import UmbrellaReport
import logging


logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger(__name__)


class SignUpView(generics.CreateAPIView):
queryset = CustomUser.objects.all()
Expand All @@ -26,6 +34,7 @@ def post(self, request):
password_error = None

for field, error in errors.items():
logger.error(f"field: [{field}] error: [{error}]")
if field == 'profile':
if not profile_error:
for sub_field, sub_error in error.items():
Expand Down Expand Up @@ -116,4 +125,51 @@ def put(self, request, *args, **kwargs):
return Response({'status': 400, 'message': invalid_error[0], 'data': ''}, status = status.HTTP_400_BAD_REQUEST)
if password_error:
return Response({'status': 400, 'message': password_error[0], 'data': ''}, status = status.HTTP_400_BAD_REQUEST)
return Response({'status': 400, 'message': '유효하지 않은 데이터입니다.', 'data': ''}, status=status.HTTP_400_BAD_REQUEST)
return Response({'status': 400, 'message': '유효하지 않은 데이터입니다.', 'data': ''}, status=status.HTTP_400_BAD_REQUEST)


class DeleteAccountView(generics.GenericAPIView):
permission_classes = [IsAuthenticated]


def delete(self, request):
try:
user = request.user

studentID = user.profile.studentID
withdrawal_reason = request.data.get('withdrawal_reason')
description = request.data.get('description')

valid_choices = [choice[0] for choice in WithdrawalRecord.REPORT_CHOICES] # choice[0] 각 튜플의 첫 번째 요소
if withdrawal_reason not in valid_choices:
return Response({'status': status.HTTP_400_BAD_REQUEST, 'message': '유효하지 않은 탈퇴 사유입니다.', 'data': ''}, status = status.HTTP_400_BAD_REQUEST)

if withdrawal_reason == '기타':
if not description:
return Response({'status': status.HTTP_400_BAD_REQUEST, 'message': '기타 사유를 입력해주세요.', 'data': ''}, status = status.HTTP_400_BAD_REQUEST)

if WithdrawalRecord.objects.filter(studentID = studentID).exists():
return Response({'status': status.HTTP_400_BAD_REQUEST, 'message': '중복된 학생 ID가 있어 회원탈퇴를 진행할 수 없습니다.', 'data': ''}, status=status.HTTP_400_BAD_REQUEST)

# 대여한 우산이 있는지 확인
rented_umbrella = Rent.objects.filter(user = user, return_date = None).first()
if rented_umbrella:
return Response({'status': status.HTTP_400_BAD_REQUEST, 'message': '우산을 반납하지 않아 탈퇴할 수 없습니다.\n반납 후 다시 진행해 주세요!', 'data': ''}, status = status.HTTP_400_BAD_REQUEST)

# 회원과 관련된 Umbrella reports 데이터 조회
umbrella_reports = UmbrellaReport.objects.filter(user = user)
umbrella_reports.update(user = None)

record_data = {'studentID': studentID, 'withdrawal_reason': withdrawal_reason, 'description': description}
record_serializer = RecordSerializer(data = record_data)
if record_serializer.is_valid():
record_serializer.save()

# 회원 탈퇴
user.delete()
return Response({'status': status.HTTP_200_OK, 'message': '회원 탈퇴가 완료되었습니다.\n우산 대여가 필요하면 다시 찾아주세요!', 'data': ''}, status = status.HTTP_200_OK)
else:
return Response({'status': status.HTTP_400_BAD_REQUEST, 'message': '회원탈퇴 실패', 'data': ''}, status = status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f'회원탈퇴 오류: {e}', exc_info = True)
return Response({'status': status.HTTP_500_INTERNAL_SERVER_ERROR, 'message': '회원탈퇴 중 오류가 발생했습니다.\n다시 시도해주세요!', 'data': ''}, status = status.HTTP_500_INTERNAL_SERVER_ERROR)

0 comments on commit 96820df

Please sign in to comment.