auth 초기구성
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -122,6 +122,8 @@ celerybeat.pid
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Environments
 | 
					# Environments
 | 
				
			||||||
.env
 | 
					.env
 | 
				
			||||||
 | 
					.env.dev
 | 
				
			||||||
 | 
					.env.prd
 | 
				
			||||||
.venv
 | 
					.venv
 | 
				
			||||||
env/
 | 
					env/
 | 
				
			||||||
venv/
 | 
					venv/
 | 
				
			||||||
@ -159,4 +161,4 @@ cython_debug/
 | 
				
			|||||||
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
 | 
					#  and can be added to the global gitignore or merged into this file.  For a more nuclear
 | 
				
			||||||
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 | 
					#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 | 
				
			||||||
#.idea/
 | 
					#.idea/
 | 
				
			||||||
 | 
					_media/cluster/
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										0
									
								
								auth_prj/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								auth_prj/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										16
									
								
								auth_prj/asgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								auth_prj/asgi.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					ASGI config for auth_prj project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It exposes the ASGI callable as a module-level variable named ``application``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.core.asgi import get_asgi_application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'auth_prj.settings')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					application = get_asgi_application()
 | 
				
			||||||
							
								
								
									
										170
									
								
								auth_prj/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								auth_prj/settings.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					Django settings for auth_prj project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Generated by 'django-admin startproject' using Django 4.2.14.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/4.2/topics/settings/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For the full list of settings and their values, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/4.2/ref/settings/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from dotenv import load_dotenv
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build paths inside the project like this: BASE_DIR / 'subdir'.
 | 
				
			||||||
 | 
					BASE_DIR = Path(__file__).resolve().parent.parent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 우선순위: .env.dev > .env.prd > .env
 | 
				
			||||||
 | 
					if os.path.exists(os.path.join(BASE_DIR, '.env.dev')):
 | 
				
			||||||
 | 
					    print("Read Environment File > Used : .env.dev")
 | 
				
			||||||
 | 
					    load_dotenv(os.path.join(BASE_DIR, '.env.dev'))
 | 
				
			||||||
 | 
					elif os.path.exists(os.path.join(BASE_DIR, '.env.prd')):
 | 
				
			||||||
 | 
					    print("Read Environment File > Used : .env.prd")
 | 
				
			||||||
 | 
					    load_dotenv(os.path.join(BASE_DIR, '.env.prd'))
 | 
				
			||||||
 | 
					else:
 | 
				
			||||||
 | 
					    print("None Environment File > Used : local_env")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					# Quick-start development settings - unsuitable for production
 | 
				
			||||||
 | 
					# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SECURITY WARNING: keep the secret key used in production secret!
 | 
				
			||||||
 | 
					SECRET_KEY = 'django-insecure-*kh6e0376o-0m5n*xz^2a2t^fa^77c1=))f$3egn7!w7axaj-l'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SECURITY WARNING: don't run with debug turned on in production!
 | 
				
			||||||
 | 
					DEBUG = int(os.environ.get('DEBUG', 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ALLOWED_HOSTS = ["*"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Application definition
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					INSTALLED_APPS = [
 | 
				
			||||||
 | 
					    'django.contrib.admin',
 | 
				
			||||||
 | 
					    'django.contrib.auth',
 | 
				
			||||||
 | 
					    'django.contrib.contenttypes',
 | 
				
			||||||
 | 
					    'django.contrib.sessions',
 | 
				
			||||||
 | 
					    'django.contrib.messages',
 | 
				
			||||||
 | 
					    'django.contrib.staticfiles',
 | 
				
			||||||
 | 
					    # by.sdjo 2025-04-22
 | 
				
			||||||
 | 
					    'rest_framework',
 | 
				
			||||||
 | 
					    'rest_framework_simplejwt',
 | 
				
			||||||
 | 
					    'drf_yasg',
 | 
				
			||||||
 | 
					    'corsheaders',
 | 
				
			||||||
 | 
					    # create by.sdjo 2025-04-22
 | 
				
			||||||
 | 
					    'users', # 2025-04-22 custom app create
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTH_USER_MODEL = 'users.CustomUser'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MIDDLEWARE = [
 | 
				
			||||||
 | 
					    'corsheaders.middleware.CorsMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.security.SecurityMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.sessions.middleware.SessionMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.common.CommonMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.csrf.CsrfViewMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.auth.middleware.AuthenticationMiddleware',
 | 
				
			||||||
 | 
					    'django.contrib.messages.middleware.MessageMiddleware',
 | 
				
			||||||
 | 
					    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# by.sdjo 2025-04-22
 | 
				
			||||||
 | 
					CORS_ALLOWED_ORIGINS = [
 | 
				
			||||||
 | 
					    "http://localhost:3000",
 | 
				
			||||||
 | 
					    "http://127.0.0.1:3000",
 | 
				
			||||||
 | 
					    "http://192.168.0.100:3000",
 | 
				
			||||||
 | 
					    "https://demo.test",
 | 
				
			||||||
 | 
					    "http://sample.test",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# by.sdjo 2025-04-22
 | 
				
			||||||
 | 
					REST_FRAMEWORK = {
 | 
				
			||||||
 | 
					    'DEFAULT_AUTHENTICATION_CLASSES': (
 | 
				
			||||||
 | 
					        'rest_framework_simplejwt.authentication.JWTAuthentication',
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ROOT_URLCONF = 'auth_prj.urls'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEMPLATES = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'BACKEND': 'django.template.backends.django.DjangoTemplates',
 | 
				
			||||||
 | 
					        'DIRS': [],
 | 
				
			||||||
 | 
					        'APP_DIRS': True,
 | 
				
			||||||
 | 
					        'OPTIONS': {
 | 
				
			||||||
 | 
					            'context_processors': [
 | 
				
			||||||
 | 
					                'django.template.context_processors.debug',
 | 
				
			||||||
 | 
					                'django.template.context_processors.request',
 | 
				
			||||||
 | 
					                'django.contrib.auth.context_processors.auth',
 | 
				
			||||||
 | 
					                'django.contrib.messages.context_processors.messages',
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WSGI_APPLICATION = 'auth_prj.wsgi.application'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Database
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# DATABASES = {
 | 
				
			||||||
 | 
					#     'default': {
 | 
				
			||||||
 | 
					#         'ENGINE': 'django.db.backends.sqlite3',
 | 
				
			||||||
 | 
					#         'NAME': BASE_DIR / 'db.sqlite3',
 | 
				
			||||||
 | 
					#     }
 | 
				
			||||||
 | 
					# }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DATABASES = {
 | 
				
			||||||
 | 
					    "default": {
 | 
				
			||||||
 | 
					        'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.sqlite3'),
 | 
				
			||||||
 | 
					        'NAME': os.environ.get('SQL_DATABASE', BASE_DIR / 'db.sqlite3'),
 | 
				
			||||||
 | 
					        'USER': os.environ.get('SQL_USER', 'user'),
 | 
				
			||||||
 | 
					        'PASSWORD': os.environ.get('SQL_PASSWORD', 'password'),
 | 
				
			||||||
 | 
					        'HOST': os.environ.get('SQL_HOST', 'localhost'),
 | 
				
			||||||
 | 
					        'PORT': os.environ.get('SQL_PORT', '3306'),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Password validation
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTH_PASSWORD_VALIDATORS = [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Internationalization
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/4.2/topics/i18n/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LANGUAGE_CODE = 'en-us'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TIME_ZONE = 'UTC'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USE_I18N = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USE_TZ = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Static files (CSS, JavaScript, Images)
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/4.2/howto/static-files/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					STATIC_URL = 'static/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Default primary key field type
 | 
				
			||||||
 | 
					# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
 | 
				
			||||||
							
								
								
									
										23
									
								
								auth_prj/urls copy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								auth_prj/urls copy.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					from django.urls import path, include, re_path
 | 
				
			||||||
 | 
					from rest_framework import permissions
 | 
				
			||||||
 | 
					from drf_yasg.views import get_schema_view
 | 
				
			||||||
 | 
					from drf_yasg import openapi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					schema_view = get_schema_view(
 | 
				
			||||||
 | 
					   openapi.Info(
 | 
				
			||||||
 | 
					      title="msa-django-auth API",
 | 
				
			||||||
 | 
					      default_version='v1',
 | 
				
			||||||
 | 
					      description="인증 서비스용 JWT API 문서",
 | 
				
			||||||
 | 
					   ),
 | 
				
			||||||
 | 
					   public=True,
 | 
				
			||||||
 | 
					   permission_classes=(permissions.AllowAny,),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('admin/', admin.site.urls),
 | 
				
			||||||
 | 
					    path('api/auth/', include('users.urls')),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
 | 
				
			||||||
 | 
					    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
 | 
				
			||||||
 | 
					    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										41
									
								
								auth_prj/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								auth_prj/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					URL configuration for auth_prj project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The `urlpatterns` list routes URLs to views. For more information please see:
 | 
				
			||||||
 | 
					    https://docs.djangoproject.com/en/4.2/topics/http/urls/
 | 
				
			||||||
 | 
					Examples:
 | 
				
			||||||
 | 
					Function views
 | 
				
			||||||
 | 
					    1. Add an import:  from my_app import views
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('', views.home, name='home')
 | 
				
			||||||
 | 
					Class-based views
 | 
				
			||||||
 | 
					    1. Add an import:  from other_app.views import Home
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
 | 
				
			||||||
 | 
					Including another URLconf
 | 
				
			||||||
 | 
					    1. Import the include() function: from django.urls import include, path
 | 
				
			||||||
 | 
					    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.urls import path, include, re_path
 | 
				
			||||||
 | 
					from rest_framework import permissions
 | 
				
			||||||
 | 
					from drf_yasg.views import get_schema_view
 | 
				
			||||||
 | 
					from drf_yasg import openapi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					schema_view = get_schema_view(
 | 
				
			||||||
 | 
					   openapi.Info(
 | 
				
			||||||
 | 
					      title="msa-django-auth API",
 | 
				
			||||||
 | 
					      default_version='v1',
 | 
				
			||||||
 | 
					      description="인증 서비스용 JWT API 문서",
 | 
				
			||||||
 | 
					   ),
 | 
				
			||||||
 | 
					   public=True,
 | 
				
			||||||
 | 
					   permission_classes=(permissions.AllowAny,),
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('admin/', admin.site.urls),
 | 
				
			||||||
 | 
					    path('api/auth/', include('users.urls')),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # ✅ Swagger & Redoc
 | 
				
			||||||
 | 
					    re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
 | 
				
			||||||
 | 
					    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
 | 
				
			||||||
 | 
					    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										16
									
								
								auth_prj/wsgi.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								auth_prj/wsgi.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					"""
 | 
				
			||||||
 | 
					WSGI config for auth_prj project.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It exposes the WSGI callable as a module-level variable named ``application``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					For more information on this file, see
 | 
				
			||||||
 | 
					https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.core.wsgi import get_wsgi_application
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'auth_prj.settings')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					application = get_wsgi_application()
 | 
				
			||||||
							
								
								
									
										22
									
								
								manage.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								manage.py
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					"""Django's command-line utility for administrative tasks."""
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
					    """Run administrative tasks."""
 | 
				
			||||||
 | 
					    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'auth_prj.settings')
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from django.core.management import execute_from_command_line
 | 
				
			||||||
 | 
					    except ImportError as exc:
 | 
				
			||||||
 | 
					        raise ImportError(
 | 
				
			||||||
 | 
					            "Couldn't import Django. Are you sure it's installed and "
 | 
				
			||||||
 | 
					            "available on your PYTHONPATH environment variable? Did you "
 | 
				
			||||||
 | 
					            "forget to activate a virtual environment?"
 | 
				
			||||||
 | 
					        ) from exc
 | 
				
			||||||
 | 
					    execute_from_command_line(sys.argv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    main()
 | 
				
			||||||
							
								
								
									
										0
									
								
								requirementes.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								requirementes.txt
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								users/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								users/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										30
									
								
								users/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								users/admin.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from django.contrib.auth.admin import UserAdmin
 | 
				
			||||||
 | 
					from .models import CustomUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomUserAdmin(UserAdmin):
 | 
				
			||||||
 | 
					    model = CustomUser
 | 
				
			||||||
 | 
					    list_display = ('email', 'name', 'grade', 'is_active', 'is_staff')
 | 
				
			||||||
 | 
					    list_filter = ('grade', 'is_active', 'is_staff')
 | 
				
			||||||
 | 
					    search_fields = ('email', 'name')
 | 
				
			||||||
 | 
					    ordering = ('email',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    readonly_fields = ('created_at',)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fieldsets = (
 | 
				
			||||||
 | 
					        (None, {'fields': ('email', 'password')}),
 | 
				
			||||||
 | 
					        ('Personal Info', {'fields': ('name', 'grade')}),
 | 
				
			||||||
 | 
					        ('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions')}),
 | 
				
			||||||
 | 
					        ('Important dates', {'fields': ('last_login', 'created_at')}),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    add_fieldsets = (
 | 
				
			||||||
 | 
					        (None, {
 | 
				
			||||||
 | 
					            'classes': ('wide',),
 | 
				
			||||||
 | 
					            'fields': ('email', 'name', 'grade', 'password1', 'password2', 'is_active', 'is_staff', 'is_superuser')}
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					admin.site.register(CustomUser, CustomUserAdmin)
 | 
				
			||||||
							
								
								
									
										6
									
								
								users/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								users/apps.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					from django.apps import AppConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UsersConfig(AppConfig):
 | 
				
			||||||
 | 
					    default_auto_field = 'django.db.models.BigAutoField'
 | 
				
			||||||
 | 
					    name = 'users'
 | 
				
			||||||
							
								
								
									
										35
									
								
								users/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								users/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					# Generated by Django 4.2.14 on 2025-04-22 04:33
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from django.db import migrations, models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Migration(migrations.Migration):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    initial = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dependencies = [
 | 
				
			||||||
 | 
					        ('auth', '0012_alter_user_first_name_max_length'),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    operations = [
 | 
				
			||||||
 | 
					        migrations.CreateModel(
 | 
				
			||||||
 | 
					            name='CustomUser',
 | 
				
			||||||
 | 
					            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')),
 | 
				
			||||||
 | 
					                ('email', models.EmailField(max_length=254, unique=True)),
 | 
				
			||||||
 | 
					                ('name', models.CharField(max_length=255)),
 | 
				
			||||||
 | 
					                ('grade', models.CharField(choices=[('admin', '관리자'), ('manager', '매니저'), ('user', '일반 사용자')], default='user', max_length=20)),
 | 
				
			||||||
 | 
					                ('is_active', models.BooleanField(default=True)),
 | 
				
			||||||
 | 
					                ('is_staff', models.BooleanField(default=False)),
 | 
				
			||||||
 | 
					                ('created_at', models.DateTimeField(auto_now_add=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={
 | 
				
			||||||
 | 
					                'abstract': False,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
							
								
								
									
										0
									
								
								users/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								users/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										48
									
								
								users/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								users/models.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					from django.db import models
 | 
				
			||||||
 | 
					from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomUserManager(BaseUserManager):
 | 
				
			||||||
 | 
					    def create_user(self, email, password=None, **extra_fields):
 | 
				
			||||||
 | 
					        if not email:
 | 
				
			||||||
 | 
					            raise ValueError("The Email must be set")
 | 
				
			||||||
 | 
					        email = self.normalize_email(email)
 | 
				
			||||||
 | 
					        user = self.model(email=email, **extra_fields)
 | 
				
			||||||
 | 
					        user.set_password(password)
 | 
				
			||||||
 | 
					        user.save(using=self._db)
 | 
				
			||||||
 | 
					        return user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create_superuser(self, email, password=None, **extra_fields):
 | 
				
			||||||
 | 
					        extra_fields.setdefault("is_staff", True)
 | 
				
			||||||
 | 
					        extra_fields.setdefault("is_superuser", True)
 | 
				
			||||||
 | 
					        extra_fields.setdefault("grade", "admin")  # 슈퍼유저는 기본 admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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(email, password, **extra_fields)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CustomUser(AbstractBaseUser, PermissionsMixin):
 | 
				
			||||||
 | 
					    GRADE_CHOICES = (
 | 
				
			||||||
 | 
					        ('admin', '관리자'),
 | 
				
			||||||
 | 
					        ('manager', '매니저'),
 | 
				
			||||||
 | 
					        ('user', '일반 사용자'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    email = models.EmailField(unique=True)
 | 
				
			||||||
 | 
					    name = models.CharField(max_length=255)
 | 
				
			||||||
 | 
					    grade = models.CharField(max_length=20, choices=GRADE_CHOICES, default='user')
 | 
				
			||||||
 | 
					    is_active = models.BooleanField(default=True)
 | 
				
			||||||
 | 
					    is_staff = models.BooleanField(default=False)
 | 
				
			||||||
 | 
					    created_at = models.DateTimeField(auto_now_add=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    objects = CustomUserManager()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    USERNAME_FIELD = 'email'
 | 
				
			||||||
 | 
					    REQUIRED_FIELDS = ['name']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return self.email
 | 
				
			||||||
							
								
								
									
										17
									
								
								users/serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								users/serializers.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					from rest_framework import serializers
 | 
				
			||||||
 | 
					from .models import CustomUser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RegisterSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
					    password = serializers.CharField(write_only=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = CustomUser
 | 
				
			||||||
 | 
					        fields = ('email', 'name', 'password', 'grade')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def create(self, validated_data):
 | 
				
			||||||
 | 
					        password = validated_data.pop('password')
 | 
				
			||||||
 | 
					        user = CustomUser(**validated_data)
 | 
				
			||||||
 | 
					        user.set_password(password)
 | 
				
			||||||
 | 
					        user.save()
 | 
				
			||||||
 | 
					        return user
 | 
				
			||||||
							
								
								
									
										3
									
								
								users/tests.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								users/tests.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					from django.test import TestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Create your tests here.
 | 
				
			||||||
							
								
								
									
										10
									
								
								users/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								users/urls.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					from django.urls import path
 | 
				
			||||||
 | 
					from .views import RegisterView, MeView
 | 
				
			||||||
 | 
					from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					urlpatterns = [
 | 
				
			||||||
 | 
					    path('register/', RegisterView.as_view(), name='register'),
 | 
				
			||||||
 | 
					    path('login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
 | 
				
			||||||
 | 
					    path('refresh/', TokenRefreshView.as_view(), name='token_refresh'),
 | 
				
			||||||
 | 
					    path('me/', MeView.as_view(), name='me'),
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
							
								
								
									
										23
									
								
								users/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								users/views.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					from rest_framework.views import APIView
 | 
				
			||||||
 | 
					from rest_framework.response import Response
 | 
				
			||||||
 | 
					from rest_framework import status
 | 
				
			||||||
 | 
					from .serializers import RegisterSerializer
 | 
				
			||||||
 | 
					from rest_framework.permissions import IsAuthenticated
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RegisterView(APIView):
 | 
				
			||||||
 | 
					    def post(self, request):
 | 
				
			||||||
 | 
					        serializer = RegisterSerializer(data=request.data)
 | 
				
			||||||
 | 
					        if serializer.is_valid():
 | 
				
			||||||
 | 
					            user = serializer.save()
 | 
				
			||||||
 | 
					            return Response({"message": "User registered successfully."}, status=status.HTTP_201_CREATED)
 | 
				
			||||||
 | 
					        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class MeView(APIView):
 | 
				
			||||||
 | 
					    permission_classes = [IsAuthenticated]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request):
 | 
				
			||||||
 | 
					        user = request.user
 | 
				
			||||||
 | 
					        serializer = RegisterSerializer(user)
 | 
				
			||||||
 | 
					        return Response(serializer.data)
 | 
				
			||||||
		Reference in New Issue
	
	Block a user