1번째 줄: |
1번째 줄: |
| [[분류:장고 기능구현(초급)]] | | [[분류:장고 기능구현(초급)]] |
− | 활성 플래그 : 사용자 활성 상태 여부 - 꺼지면 로그인도 할 수 없다.
| + | ==개요== |
| + | 기본적으로 제공되는 유저모델은 제한적이다. 학교에서 운영한다면 학번 등의 정보를 저정해야 하는데, 이처럼 제공하지 않는 기능들을 확장하는 과정이 필요하다. |
| | | |
− | 스태프 플래그 : 사용자가 관리 인터페이스에 로그인할 수 있는지 여부를 결정한다. 일반 사용자와 관리자를 구분할 때 사용한다.
| + | 장고에서 기본적으로 제공해주는 항목을 넘어 개별항목을 지정하는 등의 작업을 하기 위해 처음부터 커스텀유저를 사용하길 권장한다. |
| + | {| class="wikitable" |
| + | |+유저 기능을 확장하는 방법<ref>https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html</ref> |
| + | !방법 |
| + | !설명 |
| + | |- |
| + | |Proxy. 대리모델 사용하기. |
| + | | 장고 내에서가 아닌, 다른 사이트에서 사용되는 유저권한 등을 위임받아와 사용한다. |
| + | |- |
| + | | Profile. 프로필 사용. |
| + | |단순히 프로필에 들어갈 내용을 더 확장하려면 새로운 모델을 만들어 OneToOneField를 사용해 프로필을 늘리면 되는데, 굳이 어려운 커스텀모델을 다시 짜는 이유는 아이디 대신 이메일로 로그인한다든가, 권한을 부여하는 등 다양한 편의기능을 구현하기 위함이다. 게다가 이런 방식으로 만들면 또 다른 DB를 불러와야 한다는 단점도 있다. |
| + | 굳이 편의기능이 필요하지 않다면 Admin창에 InlineModelAdmin 등으로 모델을 연결하여 관리하면 된다. 그러나, 공부를 위해, 이후 추가될 기능들을 위해 미리 익혀두면 좋으리라. |
| + | |- |
| + | |AbstractBaseUser. |
| + | |완전 새로운 모델을 사용하기 위함.(모델을 상속해서 만들어진다.) DB에 큰 영향을 미치기 때문에 프로젝트 시작 전에 구성되어야 한다. |
| + | id 대신 이메일 로그인을 구현한다든가 특수한 기능이 필요할 때 사용한다.(장고에서 제공하는 권한이 프로젝트와 맞지 않은 경우) |
| | | |
− | 슈퍼 유저 플래그 : 사용자에게 관리자 인터페이스에서 항목을 추가, 작성, 삭제같은 접근을 할 수 있는 권한을 결정한다. 이 플래그가 켜지면 모든 일반 사용 권한이 무시된다.
| + | 기본 속성으로 id(기본키), password, last_login 필드만 들어있다. |
| + | |- |
| + | |AbstractUser. |
| + | |존재하는 기존 필드들을 사용한다.(위 모델의 확장판이라 보면 된다.) 이 모델을 사용한다 해서 이메일 로그인을 구현하지 못하는 것은 아니다. 마찬가지로 프로젝트 시작 전에 구성되어야 한다. |
| + | 사용하기 위해선 settings.py의 설정이 필요하다. |
| + | |
| + | 기본 속성으로 id(기본키), password, last_login, is_superuser, username, first_name, last_name, email, is_staff, is_active, date_joined 필드가 들어있다. |
| + | |}일반적으로 본격적인 프로젝트를 시작하기 전에 만들기가 권장된다. 프로젝트 도중에 모델을 바꾸는 것은 DB의 왜래키나 다대다 관계에 영향을 미치는데, 이를 다시 고치는 일은 꽤 어려운 일이기 때문이다.(중간에 커스텀모델을 추가하는 경우엔 DB자체를 지우고, 다시 구성한다.) 장고의 의존성 특성 때문에 최초의 DB에 생성되어 포함되어야 한다. |
| + | ===사용하는 것은 AbstractBaseUser=== |
| + | AbstractUser는 크게 변경하거나 확장하는 내용이 많지 않다.(그냥 Profile 방식을 사용해도 되지 않을까 싶을 정도로..) 하여, 여기에선 조금 어렵지만, 더 자유로운 BaseUser에 대해 다뤄보고자 한다. |
| + | ==사전작업== |
| + | ===settings.py에 만들 모델 추가=== |
| + | 보통은 앱이름은 account, 모델이름은 MyUser, Account, CustomUser, User 따위가 된다.<syntaxhighlight lang="python"> |
| + | AUTH_USER_MODEL = '앱이름.모델이름' # 관리유저로 사용할 모델을 설정한다. |
| + | |
| + | </syntaxhighlight> |
| + | ==모델 작성== |
| + | <syntaxhighlight lang="python"> |
| + | from django.db import models |
| + | from django.contrib.auth.models import AbstractBaseUser, BaseUserManager |
| + | |
| + | |
| + | class User(AbstractBaseUser): |
| + | # 계정관련 |
| + | identifier = models.CharField(max_length=20, unique=True) # 로그인 할 때 사용할 식별자. |
| + | email = models.EmailField(max_length=60) |
| + | # 유저정보 관련 |
| + | nickname = models.CharField(max_length=10, unique=True) # 드러날 식별자. 변경 가능하지만, 유일하다. |
| + | date_joined = models.DateTimeField(auto_now_add=True) |
| + | last_login = models.DateTimeField(auto_now=True) # 마지막 로그인 시간을 저장하기. |
| + | # 유저권한 관련 |
| + | is_active = models.BooleanField(default=False) # 장고 필수필드 |
| + | is_staff = models.BooleanField(default=False) # 어드민에 접속할 권한. |
| + | is_admin = models.BooleanField(default=False) # 장고 필수필드 |
| + | is_superuser = models.BooleanField(default=False) # 딱히 지정하지 않고 모든 권한을 줄 때 |
| + | |
| + | REQUIRED_FIELDS = ['email', ] # 입력할 때 필수입력 필드 지정. |
| + | USERNAME_FIELD = 'identifier' # 어떤 것을 로그인 때의 식별자로 사용할 것인가. |
| + | |
| + | def __str__(self): |
| + | return self.nickname |
| + | |
| + | def has_perm(self, perm, obj=None): # 이건 뭐람;;? |
| + | return self.is_admin |
| + | |
| + | def has_module_perms(self, app_label): |
| + | return True |
| + | |
| + | def email_send(self, subject, message, from_email=None, **kwargs): # 이메일 발송을 위해. |
| + | pass |
| + | |
| + | objects = AccountManager() # 회원가입을 다룰 클래스(이후 작성) |
| + | </syntaxhighlight>username와 같이 장고에서 기본적으로 구현해 주는 필드에 적용하려면, AbstractBaseUser을 상속한 다음 USERNAME_FIELD 등 특수한 변수에 등록해야 한다. |
| + | ===DB에 반영=== |
| + | 다른 앱의 모델에서 유저를 참조하는 경우, 참조테이블로 User가 들어가 있는데, 이 때문에 'auth.User', which has been swapped out. 라는 에러가 뜬다. |
| + | |
| + | 이것과 from django.contrib.auth.models import User를 지우고, from django.conf import settings 후에 |
| + | |
| + | User 대신 settings.AUTH_USER_MODEL를 넣어주면 된다. |
| + | ===관리자 계정 작성=== |
| + | manage.py createsuperuser 를 실행해 관리자계정을 만들려 하면 |
| + | |
| + | AttributeError: 'Manager' object has no attribute 'get_by_natural_key' 에러가 뜬다. 이는 메니저가 구현되어있지 않기 때문. 다음과 같이 유저메니저를 만들어주기만 하자.<syntaxhighlight lang="python"> |
| + | class AccountManager(BaseUserManager): |
| + | pass |
| + | </syntaxhighlight>그리고 위 커스텀유저모델 안에 objects = AccountManager() 변수를 추가해준다.(AccountManager를 참조하기 위해 AccountManager는 커스텀유저 위에 작성해주어야 한다.) |
| + | |
| + | 다시 시도해보면 이 에러가 뜬다. object has no attribute 'create_superuser' |
| + | |
| + | 내용이 안채워졌기 때문인데, 내용을 채워가 보자.<syntaxhighlight lang="python"> |
| + | class AccountManager(BaseUserManager): # 계정을 만드는 데 쓰인다. |
| + | use_in_migrations = True |
| + | |
| + | def create_user(self, identifier, password, email, **extra_fields): # 모든 유저를 생성할 때 거치는 함수. |
| + | if not email: |
| + | raise ValueError("email 주소가 있어야 합니다.") |
| + | if not identifier: |
| + | raise ValueError("아이디는 입력하셔야죠;") |
| + | email = self.normalize_email(email) |
| + | user = self.model(email=email, identifier=identifier, **extra_fields) # 커스텀모델에 각종 인자 저장. |
| + | user.set_password(password) |
| + | user.save(using=self._db) |
| + | return user |
| + | |
| + | def create_superuser(self, identifier, email, password, **extra_fields): # 관리자계성 생성. 인수는 필요에 따라.(아래 유저모델에서 설정한 필수입력 필드를 포함해야 한다. 그 필드대로 묻는다.) |
| + | user = self.create_user( |
| + | email=self.normalize_email(email), |
| + | password=password, |
| + | identifier=identifier, # 이름대신 아이디. |
| + | ) |
| + | user.is_active = True |
| + | user.is_admin = True # 어드민과 슈퍼유저는 뭐가 다른걸까;;? |
| + | user.is_staff = True |
| + | user.is_superuser = True |
| + | user.save(using=self._db) |
| + | return user |
| + | </syntaxhighlight>사람마다 짜는 방식이 달라서, 그리고 일일이 주석을 달아두지 않기 때문에 처음 하는 사람들은 해석하는 데 적잖이 애먹는다. |
| + | |
| + | ===관리자 화면에 등록=== |
| + | admin.py를 작성한다.<syntaxhighlight lang="python"> |
| + | from django.contrib import admin |
| + | from .models import User # 직접 등록한 모델 |
| + | #from django.contrib.auth import get_user_model은 안되나?? 확인해보자. |
| + | |
| + | |
| + | @admin.register(User) |
| + | class UserAdmin(admin.ModelAdmin): |
| + | list_display = ('identifier', 'email','nickname', 'joined_at', 'last_login_at') |
| + | list_display_links = ('identifier', 'email') |
| + | exclude = ('password',) # 사용자 상세 정보에서 비밀번호 필드를 노출하지 않음 |
| + | |
| + | def joined_at(self, obj): |
| + | return obj.date_joined.strftime("%Y-%m-%d") |
| + | |
| + | def last_login_at(self, obj): |
| + | if not obj.last_login: |
| + | return '' |
| + | return obj.last_login.strftime("%Y-%m-%d %H:%M") |
| + | |
| + | joined_at.admin_order_field = '-date_joined' # 가장 최근에 가입한 사람부터 리스팅 |
| + | joined_at.short_description = '가입일' |
| + | last_login_at.admin_order_field = 'last_login_at' |
| + | last_login_at.short_description = '최근로그인' |
| + | </syntaxhighlight>그럼 이제 이 모델로 계정관리가 가능해진다. |
| + | ==폼 작성== |
| + | 기본 폼은 password 따위를 암호화하지 않고 그대로 저장한다. 폼을 밑바닥부터 만들려면 알아야 할 것도 많은데, UserCreationForm을 이용해 폼을 다뤄보자. |
| + | |
| + | changeform은 굳이 사용하지 않아도 된다. 회원관리 참고..<syntaxhighlight lang="python"> |
| + | from django import forms |
| + | from django.contrib.auth.forms import UserCreationForm, UserChangeForm |
| + | from django.contrib.auth import get_user_model # 직접 불러올 수도 있지만, settings 파일에서 설정된 모델을 자동으로 불러오는 편이 수정에 유용하지. |
| + | |
| + | class UserCreateForm(UserCreationForm): |
| + | class Meta: |
| + | model = get_user_model # 설정된 커스텀모델 |
| + | fields = ["identifier","email"] # 패스워드는 자동추가. |
| + | |
| + | class UserUpdateForm(UserChangeForm): |
| + | class Meta: |
| + | model = get_user_model |
| + | fields = ["username", "password1", "password2", "email"] |
| + | </syntaxhighlight>굳이 많은 기능을 추가하지 않는다면 뷰에서 바로 장고 제공 폼을 사용하기도 한다. |
| + | |
| + | password부분은 UserCreationForm에 정의된 것들이라 오버라이드 해줘야 한다. |
| + | |
| + | 비밀번호 조건은 settings.py의 AUTH_PASSWORD_VALIDATORS 안에 있는 조건들을 거쳐야 한다. |
| + | |
| + | |
| + | ==모델== |
| + | 커스텀유저를 외래키로 사용하고 싶다면 |
| + | |
| + | author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,) |
| + | |
| + | 위와 같은 형태로 사용하면 된다. |
| + | |
| + | [의문. 글쓰기 뷰에서 저자를 불러올 때에도 settings.AUTH_USER_MODEL이면 될까/?] |
| + | =각주= |
| + | <references /> |