Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-3: 사용자 회원가입 API 구현 #5

Merged
merged 4 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
1 change: 1 addition & 0 deletions budget_management/users/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Register your models here.
6 changes: 6 additions & 0 deletions budget_management/users/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "budget_management.users"
16 changes: 16 additions & 0 deletions budget_management/users/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
def create_user(self, username, password, **extra_fields):
if not username:
raise ValueError("계정명은 반드시 필요합니다!")

user = self.model(username=username, **extra_fields)
user.set_password(password)
user.save()
return user

def create_superuser(self, username, password, **extra_fields):
extra_fields.setdefault("is_admin", True)
return self.create_user(username, password, **extra_fields)
30 changes: 30 additions & 0 deletions budget_management/users/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.7 on 2023-11-10 10:05

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('username', models.CharField(max_length=32, unique=True)),
('password', models.CharField(max_length=128)),
('is_admin', models.BooleanField(default=False)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'users',
},
),
]
Empty file.
27 changes: 27 additions & 0 deletions budget_management/users/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from django.contrib.auth.models import AbstractBaseUser
from django.db import models

from budget_management.users.managers import UserManager


class User(AbstractBaseUser):
username = models.CharField(max_length=32, unique=True)
password = models.CharField(max_length=128)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

objects: models.Manager = UserManager()

USERNAME_FIELD = "username"

class Meta:
db_table = "users"

def __str__(self):
return self.username

@property
def is_staff(self):
return self.is_admin
23 changes: 23 additions & 0 deletions budget_management/users/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import serializers

from budget_management.users.models import User


class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "created_at", "updated_at")


class UserSignupSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "password")
extra_kwargs = {"password": {"write_only": True, "required": True}}

def create(self, validated_data):
user = User.objects.create_user(
username=validated_data["username"],
password=validated_data["password"],
)
return user
39 changes: 39 additions & 0 deletions budget_management/users/tests/views/test_user_signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import json

from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase

User = get_user_model()


class RegistrationTestCase(APITestCase):
def setUp(self):
self.user = User.objects.create_user(username="testuser1", password="testpassword")

def test_post_signup_success(self):
response = self.client.post(
path=reverse("signup"),
data=json.dumps({"username": "testuser2", "password": "testpassword"}),
content_type="application/json",
)
response_data = json.loads(response.content)
self.assertEqual(response_data["username"], "testuser2")
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

def test_post_signup_fail_invalid_password(self):
response = self.client.post(
path=reverse("signup"),
data={"username": "testuser3", "password": "1010"},
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_password_similarity_validator(self):
response = self.client.post(
path=reverse("signup"),
data={"username": "testuser3", "password": "testuser312"},
content_type="application/json",
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
9 changes: 9 additions & 0 deletions budget_management/users/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import List

from django.urls import URLPattern, path

from budget_management.users.views import SignupView

urlpatterns: List[URLPattern] = [
path("signup/", SignupView.as_view(), name="signup"),
]
32 changes: 32 additions & 0 deletions budget_management/users/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from drf_yasg.utils import swagger_auto_schema
from rest_framework import permissions, status
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from budget_management.users.serializers import UserSerializer, UserSignupSerializer


class SignupView(APIView):
permission_classes = [permissions.AllowAny]

@swagger_auto_schema(
operation_summary="유저 회원가입",
request_body=UserSignupSerializer,
responses={status.HTTP_201_CREATED: UserSerializer},
)
def post(self, request: Request) -> Response:
"""
사용자 이름(username)과 비밀번호(password)를 받아 새로운 사용자 계정을 생성합니다.

Args:
username (str): 사용자 계정 이름.
password (str): 사용자 계정 비밀번호.

Returns:
User: 생성된 사용자 객체.
"""
serializer = UserSignupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
9 changes: 5 additions & 4 deletions config/root_urls.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path
from django.urls import path, include
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions

schema_view = get_schema_view(
openapi.Info(
title="Django 스켈레톤 프로젝트",
title="예산 관리 어플리케이션",
default_version="v1",
description="원티드 프리온보딩 코스 Django 스켈레톤 프로젝트",
contact=openapi.Contact(email=""),
description="원티드 프리온보딩 Mission3. 개인 프로젝트",
contact=openapi.Contact(email="happysseul627@gmail.com"),
),
public=True,
permission_classes=[permissions.AllowAny],
Expand All @@ -21,6 +21,7 @@
# Admin
path("admin/", admin.site.urls),
# API
path("api/users/", include("budget_management.users.urls")),
# Swagger
path("swagger/docs/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"),
]
Expand Down
6 changes: 4 additions & 2 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"drf_yasg",
]

LOCAL_APPS = []
LOCAL_APPS = [
"budget_management.users.apps.UsersConfig",
]

INSTALLED_APPS = [
"django.contrib.admin",
Expand Down Expand Up @@ -117,7 +119,7 @@
# Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# AUTH_USER_MODEL = "users.User" -> users 앱이 없어서 주석처리
AUTH_USER_MODEL = "users.User"

# Swagger settings
SWAGGER_SETTINGS = {"SECURITY_DEFINITIONS": {"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}}}
Expand Down