DRF 기반으로 게시판 서비스를 만들고, React.js와 연동해보자❗ 2탄
회원 관련 기능
- 회원 프로필 관리(닉네임, 소개, 프로필 사진 등)
- 회원가입
- 로그인
- 프로필 수정
회원가입, 로그인 기능은 Django의 기본 User 모델을 사용해 구현했었다.
하지만 이 프로젝트에서의 프로필 관리 기능을 추가하려면 부족하다.
필요한 유저 모델을 정의하면 다음과 같다.
username
: 아이디(CharField, primary=True)
password
: 비밀번호(CharField)
nickname
: 닉네임(CharField)
image
: 프로필 사진(ImageField)
subjects
: 관심사(CharField)
position
: 직종(CharField)
username, password, email 필드는 Django의 기본 User 모델이 가지고 있지만, 이외 필드들을 추가하기 위해서는 User 모델을 확장해야 한다.
확장 방법은 총 4가지이다.
기본 User 모델을 상속받는 방법.
기능을 추가하거나 동작을 변경할 수 있다.
가장 간단하지만, 기존 User 모델의 스키마를 변경하지 않기 때문에 정작 필요한 필드를 추가할 수 없다.
기본 User 모델에 일대일로 연결되는 새로운 모델을 만드는 방법.
관계형 데이터베이스의 1:1 관계와 같다.
User 모델을 직접 건드리지 않으면서도 필드를 추가할 수 있어, 프로젝트 중간에 추가 사항이 생겨도 언제든 추가할 수 있어 편리하다.
이 방법을 고른다면, Profile 모델을 하나 만들어 추가하고 싶은 속성들(nickname, image, subjects, postion)을 추가하고, Profile 모델에 User 모델을 OneToOneField
로 연결하면 된다.
그러나 두 개의 모델을 연결해서 사용하면 하나의 모델을 사용하는 것보다 느리다는 단점이 존재한다.
User 모델을 추상화한 AbstractBaseUser 모델을 상속받아 아예 새로운 모델을 만드는 정석 방법.
AbstractBaseUser는 User 모델의 간소화 버전으로 정말 기본 요소만 있어 일일이 구현해야 하기에 가장 자유도와 난이도가 높다.
유저 모델을 아예 새로 만들기 때문에 프로젝트 진행 중에는 선택하기 어렵다.
프로젝트 시작 전에 충분히 계획해서 시작해야 하며, 추가적으로 구현하거나 수정할 기능이 많을 때 선택해야 한다.
AbstractBaseUser보다는 많은 것을 이미 갖고 있는 AbstractUser를 상속받아 새로운 유저 모델을 만드는 방법.
사실상 기본 User 모델을 그대로 가져와 필요한 내용만 수정하거나 추가할 수 있어 가장 많이 사용되는 편리한 방식이다.
AbstractBaseUser처럼 유저 모델을 새로 만들어야 하므로 프로젝트 초기에 적용하는 것이 좋다.
그럼 가장 직관적인 1:1 Model 방법으로 User 모델을 확장해보자.
models.py
에 Profile 모델을 작성한다.
OneToOneField
를 사용한다.on_delete
옵션을 models.CASCADE
로 설정해 모델에서 데이터가 삭제될 때 연결된 데이터도 함께 삭제되도록 한다.from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
# Create your models here.
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
nickname = models.CharField(max_length=128)
position = models.CharField(max_length=128)
subjects = models.CharField(max_length=128)
image = models.ImageField(upload_to='profile/', default='default.png')
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver
는 post_save
신호를 수신하는 create_user_profile
함수를 등록한다.
유저가 생성될 때 이 함수가 호출되어, 해당 유저에 대한 프로필을 자동으로 생성한다.
@receiver
: Django에서 신호를 처리하기 위해 사용하는 데코레이터post_save
: 객체가 저장된 후에 발생하는 신호를 수신sender=User
: sender 매개변수를 통해 User 모델의 신호 수신을 결정create_user_profile()
함수는 instance와 created를 통해 새로 생성된 유저 정보를 사용할 수 있다이후 마이그레이션을 하면 에러가 발생한다.
에러 메시지가 시키는 대로 Pillow를 설치하자. Pillow는 앱의 형태가 아니므로 INSTALLED_APPS
에 등록해줄 필요는 없다.
다음으로 미디어 파일들에 대한 경로도 지정해주어야 한다.
/media/
디렉토리를 미디어 파일들의 루트로 설정하여, 이후 미디어 파일들에 대한 경로를 작성할 때 상대 경로로 간편하게 작성할 수 있도록 하자.
프로젝트/settings.py
를 수정한다.
import os
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
프로젝트의 최상위(루트)에 media 폴더를 생성하고, 그 안에 profile 폴더와 default.png 파일을 넣어준다.
마지막으로 미디어 파일 경로를 프로젝트 URL에 매칭시킨다.
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
까먹지 말고 마이그레이션해준다.
이젠 EZ한 시리얼라이저
from .models import Profile
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("nickname", "position", "subjects", "image")
프로필 관련 기능은 프로필을 조회하는 기능과 수정하는 기능으로, generics.RetrieveUpdateAPIView
로 구현할 수 있다.
from .models import Profile
from .serializers import RegisterSerializer, LoginSerializer, ProfileSerializer
class ProfileView(generics.RetrieveUpdateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
그러나 프로필 조회는 누구나 할 수 있게 하고, 수정은 로그인한 해당 유저만 가능하도록 해야 한다는 문제가 있다.
이처럼 어떤 API에 특정한 권한이 필요한 상황에는
permission_classes
필드를 설정하면 된다!
API마다 필요한 권한이 다른 경우, 권한이 미리 조합된 클래스를 활용하거나, 직접 권한 클래스를 만들어 설정할 수 있다.
그렇다면 안전한 메소드(GET) 외의 요청이 들어온 경우 프로필을 수정하려는 해당 유저만을 허가해 주는 권한을 직접 만들어보자.
rest_framework.permissions
에 다양한 기본 권한 클래스들이 미리 선언되어 있다.
AllowAny
: 모든 요청을 통과시키며, 어떠한 인증도 필요 없다.
IsAuthenticated
: 인증된 경우에만 통과된다. 즉 직접 선언한 인증 방법으로 인증을 통과한 요청만 가능한 권한이다.
IsAdminUser
: 관리자인 경우에만 통과된다.
앱/permissions.py
파일을 생성한다.
from rest_framework import permissions
class CustomReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
permissions.BasePermission
클래스를 상속받아 작성한다.has_object_permission
메소드를 가져와서 작성한다.permissions.SAFE_METHODS
: 데이터에 영향을 미치지 않는 메소드, 즉 GET 메소드를 의미한다.개별 프로필에 대한 URL을 설정한다.
from django.urls import path
from .views import RegisterView, LoginView, ProfileView
urlpatterns = [
path('register/', RegisterView.as_view()),
path('login/', LoginView.as_view()),
path('profile/<int:pk>/', ProfileView.as_view()),
]
User 모델과 Profile 모델을 관리자 페이지에 따로 등록하게 되면 관리자 페이지에서 볼 수 있지만, User 테이블과 Profile 테이블이 분리되어 보인다.
다행히 admin.py
에 다음과 같이 작성하면 두 모델이 같은 모델인 것처럼 함께 볼 수 있다.
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from .models import Profile
# Register your models here.
class ProfileInline(admin.StackedInline):
model = Profile
can_delete = False
verbose_name_plural = "profile"
class UserAdmin(BaseUserAdmin):
inlines = (ProfileInline, )
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
우선 프로필 조회 기능을 확인해보자.
http://127.0.0.1:8000/users/profile/<int:pk> 로 GET 요청을 보내기 전에,
이전에 테스트 유저를 만들었을 때는 자동으로 프로필을 생성해주는 프로필 모델이 없었기 때문에 테스트 유저를 새로 만들어야 한다.
GET 요청은 설정한 대로 별도의 권한 없이도 잘 동작한다.
프로필 수정 기능을 테스트하려면 토큰이 있어야 하므로 로그인 요청을 보내서 토큰을 얻어온다.
이제 Profile에 PUT 요청을 보내 프로필을 수정해보자.
Body에서 Form Data를 선택하면 요청 양식을 Multipart로 보낼 수 있다.
헤더에 토큰 값을 꼭 넣어준다. 좌측에는 Authorization, 우측에는 토큰 값을 넣으면 된다.
200 OK 메시지가 뜨며 성공!
관리자 페이지에 들어가보면, 수정한 내용이 잘 반영되어 있다.
사진 링크를 클릭하면 사진도 잘 보이는 것을 확인할 수 있다.