-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
07b0ba8
commit 788ee86
Showing
16 changed files
with
286 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from rest_framework import status | ||
from rest_framework.exceptions import APIException | ||
|
||
|
||
class ValidationError(APIException): | ||
status_code = status.HTTP_400_BAD_REQUEST | ||
default_detail = "Invalid value." | ||
default_code = "invalid" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,9 @@ | ||
# Create your models here. | ||
from django.db import models | ||
|
||
|
||
class BaseModel(models.Model): | ||
created_at = models.DateTimeField(auto_now_add=True) | ||
updated_at = models.DateTimeField(auto_now=True) | ||
|
||
class Meta: | ||
abstract = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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("username is required") | ||
|
||
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Generated by Django 4.2.7 on 2023-11-09 15:25 | ||
|
||
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')), | ||
('password', models.CharField(max_length=128, verbose_name='password')), | ||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | ||
('username', models.CharField(max_length=16, unique=True)), | ||
('is_active', models.BooleanField(default=True)), | ||
('is_admin', models.BooleanField(default=False)), | ||
('created_at', models.DateTimeField(auto_now_add=True)), | ||
('updated_at', models.DateTimeField(auto_now=True)), | ||
], | ||
options={ | ||
'db_table': 'users', | ||
}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,25 @@ | ||
# Create your models here. | ||
from django.contrib.auth.models import AbstractBaseUser | ||
from django.db import models | ||
|
||
from budget_management.common.models import BaseModel | ||
from budget_management.users.managers import UserManager | ||
|
||
|
||
class User(AbstractBaseUser, BaseModel): | ||
username = models.CharField(max_length=16, unique=True) | ||
is_active = models.BooleanField(default=True) | ||
is_admin = models.BooleanField(default=False) | ||
|
||
objects: UserManager = UserManager() | ||
|
||
USERNAME_FIELD = "username" | ||
|
||
class Meta: | ||
db_table = "users" | ||
|
||
def __str__(self): | ||
return self.username | ||
|
||
@property | ||
def is_staff(self): | ||
return self.is_admin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from typing import Optional | ||
|
||
from budget_management.users.models import User | ||
|
||
|
||
def get_user_by_username(username: str) -> Optional[User]: | ||
return User.objects.filter(username=username).first() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from django.db import transaction | ||
|
||
from budget_management.common.exceptions import ValidationError | ||
from budget_management.users.models import User | ||
from budget_management.users.selectors import get_user_by_username | ||
|
||
|
||
class UserService: | ||
def validate_unique_usernmae(self, username: str): | ||
if username: | ||
username_exists = get_user_by_username(username) | ||
if username_exists: | ||
raise ValidationError("Username already exists.") | ||
|
||
def validate_password(self, password: str): | ||
if password: | ||
if len(password) < 8: | ||
raise ValidationError("Password must be at least 8 characters long.") | ||
|
||
@transaction.atomic | ||
def create(self, username: str, password: str) -> User: | ||
self.validate_unique_usernmae(username) | ||
self.validate_password(password) | ||
user = User.objects.create_user(username=username, password=password) | ||
return user |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import json | ||
|
||
from django.urls import reverse | ||
from rest_framework.test import APITestCase | ||
|
||
from budget_management.users.models import User | ||
|
||
|
||
class SingupViewTest(APITestCase): | ||
@classmethod | ||
def setUpTestData(self): | ||
self.user = User.objects.create_user(username="test2", password="test1234!") | ||
|
||
def test_signup_success(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "test", "password": "test1234!"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 201) | ||
|
||
def test_signup_fail_unique_username(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "test2", "password": "test1234!"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) | ||
|
||
def test_signup_fail_password_validation(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "test", "password": "test"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) | ||
|
||
def test_signup_fail_blank_username(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "", "password": "test1234!"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) | ||
|
||
def test_signup_fail_blank_password(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "test", "password": ""}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) | ||
|
||
def test_signup_fail_required_username(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"password": "test1234!"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) | ||
|
||
def test_signup_fail_required_password(self): | ||
response = self.client.post( | ||
path=reverse("signup"), | ||
data=json.dumps({"username": "test"}), | ||
content_type="application/json", | ||
) | ||
self.assertEqual(response.status_code, 400) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from django.test import TestCase | ||
|
||
from budget_management.users.models import User | ||
from budget_management.users.selectors import get_user_by_username | ||
|
||
|
||
class UserSelectorsTest(TestCase): | ||
def setUp(self): | ||
User.objects.create_user(username="test", password="test1234!") | ||
self.user = get_user_by_username("test") | ||
|
||
def test_get_user_by_username(self): | ||
self.assertEqual(self.user.username, "test") | ||
|
||
def test_get_user_by_username_not_found(self): | ||
user = get_user_by_username("test2") | ||
self.assertIsNone(user) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from django.test import TestCase | ||
|
||
from budget_management.users.services import UserService | ||
|
||
|
||
class UserServicesTest(TestCase): | ||
def setUp(self): | ||
self.user_service = UserService() | ||
|
||
def test_create(self): | ||
user = self.user_service.create(username="test", password="test1234!") | ||
self.assertEqual(user.username, "test") | ||
self.assertTrue(user.check_password("test1234!")) | ||
|
||
def test_create_with_duplicated_username(self): | ||
self.user_service.create(username="test", password="test1234!") | ||
with self.assertRaises(Exception): | ||
self.user_service.create(username="test", password="test1234!") | ||
|
||
def test_create_with_short_password(self): | ||
with self.assertRaises(Exception): | ||
self.user_service.create(username="test", password="test") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from django.urls import path | ||
|
||
from budget_management.users.views import SignupView | ||
|
||
urlpatterns = [ | ||
path("signup", SignupView.as_view(), name="signup"), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from drf_yasg.utils import swagger_auto_schema | ||
from rest_framework import serializers, status | ||
from rest_framework.permissions import AllowAny | ||
from rest_framework.response import Response | ||
from rest_framework.views import APIView | ||
|
||
from budget_management.users.services import UserService | ||
|
||
|
||
class SignupView(APIView): | ||
permission_classes = [AllowAny] | ||
|
||
class InputSerializer(serializers.Serializer): | ||
username = serializers.CharField() | ||
password = serializers.CharField() | ||
|
||
class OuputSerializer(serializers.Serializer): | ||
username = serializers.CharField() | ||
|
||
@swagger_auto_schema( | ||
request_body=InputSerializer, | ||
responses={ | ||
status.HTTP_201_CREATED: OuputSerializer, | ||
}, | ||
) | ||
def post(self, request): | ||
""" | ||
계정명과 비밀번호를 받아서 새로운 유저를 생성합니다. | ||
url: /api/users/signup | ||
""" | ||
user_service = UserService() | ||
serializer = self.InputSerializer(data=request.data) | ||
serializer.is_valid(raise_exception=True) | ||
user = user_service.create(**serializer.validated_data) | ||
return Response(self.OuputSerializer(user).data, status=status.HTTP_201_CREATED) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters