Skip to content

Commit

Permalink
재 설정
Browse files Browse the repository at this point in the history
- ✨Feat : 회원가입 기능 추가 I-deul-of-zoo#3
  (계정, 이메일, 비밀번호로 회원 가입)
  - ✨Feat :회원가입 제약조건 2이상 추가 I-deul-of-zoo#3
    구현한 제약 조건
      - 숫자, 문자, 특수문자 중 2가지 이상을 포함
      - 3회 이상 연속되는 문자 사용 불가
      - 개인 정보와 비밀번호가 유사한지 체크
      - 비밀 번호가 최소 10자 이상이 되어야한다
      - 흔한 비밀번호는 사용할 수 없다
      - 전부 숫자인 비밀번호를 사용하지 못하게 한다.
- ✨Feat : 로그인 기능 추가 I-deul-of-zoo#5
  • Loading branch information
airhac committed Oct 27, 2023
1 parent 5d64c66 commit 5d41058
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 53 deletions.
56 changes: 56 additions & 0 deletions accounts/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _
from datetime import date
import string
import random

STRING_SEQUENCE = string.ascii_uppercase + string.digits # 새로운 인증 코드 생성

class CustomUserManager(BaseUserManager):
"""
Custom user model manager where email is the unique identifiers
for authentication instead of usernames.
"""
#일반 유저 생성
def create_user(self,username, email, password, **extra_fields):
"""
Create and save a User with the given email and password.
"""
if not email:
raise ValueError(_('The Email must be set'))
if not username:
raise ValueError("username은 필수 영역입니다.")
#email 형태를 동일하게 만들기 위한 함수
user = self.model(
username=username,
email=self.normalize_email(email),
**extra_fields)

user.auth_code = self.create_auth_code()
user.set_password(password)
user.save(using=self._db)
return user

@classmethod
def create_auth_code(self):
auth_code = ""
for _ in range(6):
auth_code += random.choice(STRING_SEQUENCE)
return auth_code


#관리자 유저 생성
def create_superuser(self,username, email, password, **extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)

if extra_fields.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if extra_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))

return self.create_user(username, email, password, **extra_fields)
41 changes: 41 additions & 0 deletions accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.2.6 on 2023-10-27 17:52

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


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
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')),
('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, verbose_name='email address')),
('username', models.CharField(max_length=30, unique=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('auth_code', models.CharField(blank=True, max_length=6, null=True)),
('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')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
),
]
48 changes: 15 additions & 33 deletions accounts/models.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,26 @@
import string
import random

from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager


STRING_SEQUENCE = string.ascii_uppercase + string.digits


class UserManager(BaseUserManager):
def create_user(self, username, password=None, **extra_fields):
if not username:
raise ValueError("username은 필수 영역입니다.")
user = self.model(username=username, **extra_fields)
user.set_password(password)
user.auth_code = UserManager.create_auth_code()
user.save(using=self._db)
return user

def create_superuser(self, username, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True)
return self.create_user(username, password, **extra_fields)

@classmethod
def create_auth_code(cls):
auth_code = ""
for _ in range(6):
auth_code += random.choice(STRING_SEQUENCE)

return auth_code
from django.contrib.auth.models import AbstractUser

from .managers import CustomUserManager

class User(AbstractUser):
objects = UserManager()
auth_code = models.CharField(null=True, blank=True)
objects = CustomUserManager()

email = models.EmailField(verbose_name='email address')
username = models.CharField(max_length=30, unique=True)
updated_at = models.DateTimeField(auto_now=True)
auth_code = models.CharField(max_length=6, null=True, blank=True)


##user model에서 각 row를 식별해줄 key를 설정
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = []

#파이썬에서 어떤값(또는 객체)을 문자열로 변환하는데 사용하는 str()
#내장 함수가 아닌 파이썬 내장 클래스
def __str__(self):
return self.email

def get_hashtag(self):
return f"#{self.username}"

Expand Down
20 changes: 20 additions & 0 deletions accounts/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from django.contrib.auth import get_user_model
from dj_rest_auth.registration.serializers import RegisterSerializer
from rest_framework import serializers
from dj_rest_auth.serializers import LoginSerializer
##연습용 Serializer
from rest_framework.serializers import ModelSerializer
from datetime import datetime

class CustomRegisterSerializer(RegisterSerializer):
class Meta:
model = get_user_model()
fields = [
"username",
"email",
"password",
]

class CustomLoginSerializer(LoginSerializer):
# email 필드를 제거
email = None
11 changes: 7 additions & 4 deletions accounts/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from django.urls import path
from accounts import views
from django.urls import include, path
from . import views
from .views import CustomLoginView

app_name = "accounts"
app_name = "auth"
# base_url: v1/accounts/

urlpatterns = [
#
path('login/', CustomLoginView.as_view(), name='custom-login'),
path('', include('dj_rest_auth.urls'), name='dj_rest_auth'),
path('registration/', include('dj_rest_auth.registration.urls'), name='registration'),
]
32 changes: 32 additions & 0 deletions accounts/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _


# 숫자, 문자, 특수문자 중 2가지 이상을 포함하는지 확인
class CharacterClassesValidator:
def validate(self, password, user=None):
character_classes = 0
if any(char.isdigit() for char in password):
character_classes += 1
if any(char.isalpha() for char in password):
character_classes += 1
if not character_classes >= 2:
raise ValidationError(
_("비밀번호에 숫자, 문자, 특수문자 중 2가지 이상을 포함해야합니다"),
code="password_classes_not_met",
)

# 3회 이상 연속되는 문자 사용을 방지
class NoConsecutiveCharactersValidator:
def validate(self, password, user=None):
consecutive_count = 0
for i in range(1, len(password)):
if ord(password[i]) == ord(password[i - 1]):
consecutive_count += 1
if consecutive_count >= 3:
raise ValidationError(
_("비밀번호에 2회 이상 연속 되는 문자는 사용이 불가 합니다."),
code="consecutive_characters",
)
else:
consecutive_count = 0
12 changes: 10 additions & 2 deletions accounts/views.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from django.shortcuts import render
from dj_rest_auth.views import LoginView
from rest_framework.response import Response
from rest_framework import status
from .serializers import CustomLoginSerializer # 커스텀 시리얼라이저를 가져옴

# Create your views here.
class CustomLoginView(LoginView):
serializer_class = CustomLoginSerializer # 커스텀 시리얼라이저를 사용

def post(self, request, *args, **kwargs):
# 로그인 로직을 그대로 유지
return super(CustomLoginView, self).post(request, *args, **kwargs)
2 changes: 1 addition & 1 deletion config/root_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@

# [end-point]
path('admin/', admin.site.urls),
path('v1/accounts/', include('accounts.urls')),
path('v1/auth/', include('accounts.urls')),
path('v1/posts/', include('posts.urls')),
]
Loading

0 comments on commit 5d41058

Please sign in to comment.