티스토리 뷰

--> [Django] AbstractUser로 User모델 커스터마이징 해보기

 

[Django] AbstractUser로 User모델 커스터마이징 해보기

--> 호밀밭의 파수꾼 - Django가 기본적으로 제공하는 User모델 [Django] Django가 기본적으로 제공하는 User모델 이용자와 관리자가 없는 웹사이트는 무용지물에 가깝습니다. 그렇기 때문에 어느 웹사이

tcitr-antoliny.tistory.com

이전 포스팅에서는

AbstractUser를 통해 장고가 기본적으로 제공하는 User모델을 커스터마이징 해보았습니다.

 

하지만 저번 포스팅에서도 봤듯이 막상 User모델을 만들고

admin페이지를 통해 확인해보니

(왼) 모델 메인 페이지 (오) 모델 객체 생성 페이지

모델 메인 페이지는 User모델 객체의 username필드만 보이고 모델 객체 생성 페이지에는

Last login, Groups, User permissions같은 User모델에서 기본적으로 제공되는 것들이 먼저 보이고

필자가 생성한 필드들은 하단에 위치합니다.

(Height, Weight, Gender)

이러한 부분들이 마음에 든다면 상관없지만

필자같이 까탈스러운 사람에겐 어느정도 내 입맛에 맞게 수정하고 싶은 욕구가 끓습니다.

 

이번 시간에는 UserAdmin클래스를 오버라이딩하여 Admin페이지도 내가 커스터마이징 한

User모델에 맞게 수정해보도록 하겠습니다.

 

 

UserAdmin 코드 파악하기

 

일단 UserAdmin을 사용하려면

가장 먼저 admin.py에

admin.site.register() 함수에 두 번째 인자로 UserAdmin을 추가하면 됩니다.

admin.site.register(User, UserAdmin)

그리고 다시 runserver를 해서 admin페이지에 들어가면

벌써 조금 뭔가가 달라진 걸 느낄 수 있습니다.

(왼) 추가하기 전 (오) UserAdmin을 추가하고 난 뒤

기존에는 UserName필드만 보였지만 이제는

UserName필드 외에도 Email, First Name, Last Name, Staff Status필드가 보이는 걸 확인할 수 있습니다.

 

지금 필자의 웹사이트는 유저가 한 명 밖에 없지만

만약 유저들이 많아졌을 때 각 유저들의 중요한 필드들을 메인 페이지에 보이게 할 수 있다면

해당 유저를 클릭해서 상세정보를 보지 않아도 어느 정도 해당 유저가

어떠한 데이터를 갖고 있는지 확인할 수 있습니다.

 

이외에도 위에 돋보기도 보이고 오른쪽에는 Filter가 추가된 걸 확인할 수 있습니다.

--> Django Auth.Admin Source Code(UserAdmin)

 

GitHub - django/django: The Web framework for perfectionists with deadlines.

The Web framework for perfectionists with deadlines. - GitHub - django/django: The Web framework for perfectionists with deadlines.

github.com

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    ....
    list_display = ("username", "email", "first_name", "last_name", "is_staff")
    list_filter = ("is_staff", "is_superuser", "is_active", "groups")
    search_fields = ("username", "first_name", "last_name", "email")
    ordering = ("username",)
    filter_horizontal = (
        "groups",
        "user_permissions",
    )
    ....

Github를 통해 실제 UserAdmin코드를 보면

list_display, list_filter, search_fields, ordering, filter_horizontal 등 다양한 클래스 변수들이 있습니다.

 

전부 다 튜플 형태의 값을 받는데

튜플에 있는 값은 문자열로 된 필드명입니다.

 

 

 

list_display

 

먼저 list_display부터 소개하자면

list_display는 User모델 admin 메인 페이지에서 보이는 각 객체들(해당 모델)의 필드를 설정할 수 있습니다.

class UserAdmin(UserAdmin):
    list_display = ("username", "email", "weight", "height")

필자 사이트의 User객체는 first_name, last_name 필드는 사실 필요 없기 때문에

list_display에서 해당 필드들을 제외하고 필자가 추가한 필드들인 weight과 height을 추가했습니다.

(dltlehfld 유저를 추가했습니다.)

유저들이 있는 테이블에서 1행 3, 4열 값들을 확인하면 필자가 설정한 값들로 바뀐 걸 확인할 수 있습니다.

 

 

list_filter

 

다음으로 list_filter같은 경우는

admin 모델 메인 페이지에서 오른쪽에 보이는 FILTER와 연관되어 있습니다.(라이언이 있는)

FILTER를 보면 By staff status, By supseruser status, By active라고 되어있는 걸 확인할 수 있는데

해당 페이지에 있는 모든 유저 객체들을 특정 기준에 따라 빠르게 필터링할 수 있습니다.

 

예시로 생성한 dltlehfld유저의 superuser status를 끄고

(is_superuser --> False)

필터링 부분에 있는 By superuser status에서 Yes를 누르면

모든 유저 모델 객체 중에서 is_superuser필드 값이 True인 유저들만

화면에 보이게 됩니다.

 

이러한 list_filter는 기본값으로 "is_staff", "is_superuser", "is_active", "groups"가 제공되는걸

UserAdmin 소스코드에서 확인할 수 있습니다.

(이 중 groups가 보이지 않는 이유는 필자의 웹 사이트에는 groups모델 객체가 하나도 존재하지 않기 때문)

list_filter = ("is_staff", "is_superuser", "is_active", "groups")

이제 필자가 list_filter값을 오버라이딩해서 weight필드를 추가해보면

오른쪽 FILTER에 By weight이라는 부분이 생긴 걸 확인할 수 있습니다.

다만 필터링되는 기준이 모든 객체들의 몸무게 값이 전부 나열될 수 있기 때문에

(FloatField기 때문에)

웬만하면 list_filter값에쓰일 필드는 BooleanField로 설정하는 게 좋을 거 같습니다.

 

 

search filter

 

search_filter는 이름만 봐도 알 수 있듯이

어피치가 있는 돋보기 부분의 로직에 해당합니다.

 

해당 부분은 말 그대로 search라서 유저 모델 객체들의 필드값을 통해 사용자가 원하는

유저를 빠르게 찾는 역할을 하는 부분입니다.

search_fields = ("username", "first_name", "last_name", "email")

기본값은 list_display와 같고

만약 height이 170인 유저를 admin페이지에서 찾고 싶다면

search_fields에 "height"필드를

추가하기만 하면 됩니다.

 

ordering

 

마지막으로 ordering입니다.

ordering은 처음 User모델 admin메인 페이지에 접속했을 때 나오는

각 User들을 정렬하는 기준이 됩니다.

 

필자의 필력이 부족하여 무슨 말인가 싶을 수도 있기 때문에

간단한 테스트를 통해 보면 이해하기 쉽습니다.

ordering = ("username",)

일단 기본적으로 "username"필드를 기준으로 정렬하는데

필자는 해당 값을 "-height"으로 하여 키가 큰 사람들이 먼저 보일 수 있도록 정렬했습니다.

ordering = ("-height",)

이제 어드민페이지를 통해 확인해보면

왼 ordering = ("username",)          오 ordering = ("-height",)

dltlehfld유저가 antoliny0919유저보다 위에 있는 걸 확인할 수 있습니다.

 

이렇게 ordering은 처음에 보이는 유저들의 순서를 결정합니다.

 

 

add_fieldsets

 

이제 User모델 admin 메인 페이지는 어느정도 커스터마이징 했겠다

한 번 admin페이지가 잘 동작하나 테스트해보기 위해 Add User를 통하여 일반적인 User객체를 생성해보겠습니다.

폼에 값들을 채워서 생성하는 버튼을 눌렀지만 IntegrityError가 발생합니다.

다시 Add User페이지에 가서 유저 생성 폼을 확인해보면

입력할 수 있는 칸이 Username, password, password confirmation밖에 없는 걸 확인할 수 있습니다.

사실 이런 부분을 보면 당연히 오류가 날 수밖에 없다고 생각이 듭니다.

 

왜냐하면 이전 시간에 User모델을 커스터마이징 하면서 User생성 로직인

_create_user부분도 커스터마이징 했기 때문입니다.

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, height, weight, **extra_fields):
        """
        Create and save a user with the given username, email, and password.
        """
        if not username:
            raise ValueError("The given username must be set")
        email = self.normalize_email(email)
        # Lookup the real model class from the global app registry so this
        # manager method can be used in migrations. This is fine because
        # managers are by definition working on the real model.

        user = self.model(username=username,
                        email=email,
                        height = height,
                        weight = weight,
                        **extra_fields)
        user.password = make_password(password)
        user.save(using=self._db)
        return user

필수 입력사항인 height, weight, email은 User를 생성할 때 당연히 보내줘야 하는 값들이기 때문에

_create_user메서드에 height, weight 매개변수를 추가해줬습니다.

(email은 기본값으로 제공)

 

하지만 Add User를 통해 유저 모델 객체를 생성할 때는

height과 weight의 입력칸이 없기 때문에

자동적으로 height과 weight, email의 값을 NULL로 보내서 IntegrityError가 발생한 것입니다.

 

이런 부분을 해결하려면 사실 Add User페이지에 email, height, weight값을

입력할 수 있는 부분이 필요합니다.

 

이러한 부분들은 UserAdmin클래스의 add fieldsets값을 조금 수정해주면 해결할 수 있습니다.

class UserAdmin(admin.ModelAdmin):
    ....
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("username", "password1", "password2"),
            },
        ),
    )
    ....

방법은 간단합니다.

"fields"키 값에 대응하는 튜플 형태의 value에 Add User페이지에서 꼭 입력받아야 하는

필수 입력사항 필드를 추가하면 됩니다.

(꼭 필수 입력사항 필드만 추가하지 않아도 됩니다.)

 

필자같은 경우에는 "email", "height", "weight"이 필수 입력사항이기 때문에

해당 값들을 추가했습니다.

class UserAdmin(UserAdmin):
    
    add_fieldsets = (
        (
            None,
            {
                "classes": ("wide",),
                "fields": ("username", "password1", "password2", "email", "height", "weight",),
            },
        ),
    )

이제 다시 admin페이지를 켜고 Add User페이지를 확인해보면

 

필자가 추가한 Email, Height, Weight이 잘 적용된 걸 확인할 수 있습니다.

이제 다시 User를 생성해보면

성공적으로 객체가 생성됩니다. 😎

 

이렇게 Add User페이지를 통해 객체를 생성하거나

User 모델 admin 메인 페이지에서 이미 생성된 유저 객체를 수정하려고 클릭했을 때 이동하게 되는 페이지가

Change User페이지입니다.

Change User페이지를 한 번 확인해보면

필자가 생성한 Weight, Height 필드는 보이지도 않고

Groups와 Permission, Last login 등등 장고에서 기본적으로 제공하는 User모델

필드들만 나열되어 있습니다.

 

여기서 우리는 이제 사실상 우리에게 필요 없는 필드들은 지워주고

필요한 필드들만 보이게 나열할 것입니다.

 

 

fieldsets

일단 먼저 Change User 페이지의 형식을 파악해보면

연한 청록색(?) 부분으로 아래에 나열되어 있는 필드들의 집합 타이틀이 해당 공간에 써져있습니다.

(Personal info, Permission)

그리고 그 집합 타이틀 아래에는 해당 집합 타이틀과 관련성이 있는

필드들이 나열되어 있고 해당 User에게 이미 설정되어 있는 값이면 값이 채워져 있고

아니면 비워져 있습니다.

 

(🌼 First name과 Last name은 Email과 달리 색이 연한데 이 부분은 해당 필드가 필수입력사항이 아니란 걸 나타냅니다 🌼)

class UserAdmin(admin.ModelAdmin):
    add_form_template = "admin/auth/user/add_form.html"
    change_user_password_template = None
    fieldsets = (
        (None, {"fields": ("username", "password")}),
        (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
        (
            _("Permissions"),
            {
                "fields": (
                    "is_active",
                    "is_staff",
                    "is_superuser",
                    "groups",
                    "user_permissions",
                ),
            },
        ),
        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
    )

UserAdmin의 fieldsets코드를 봐보면

튜플 형태의 값으로 첫 번째 인덱스에는 소괄호로 묶어진 str값이 있고

두 번째 인덱스는 키값이 "fields"인 딕셔너리가 존재합니다.

 

사실 첫 번째 인덱스의 값만 봐도 바로 감이 오실거라고 생각합니다.

첫번째 있는 값을 나열해보면 익숙한 값이 보입니다.

(None, "Personal info", "Permissions", "Important dates")

Personal info와 Permissions는 아까봤던 연한 청록색부분에 써져있던 내용입니다.

 

Personal info부분의 두번째 인덱스를 확인해보면

value값으로 "first_name", "last_name", "email"이 있습니다.

어쩌면 필자가 아무말 하지 않아도

이제는 이 형식을 보고 바로 이해할 수 있을 거라고 생각합니다.

(
    (
        ("집합 타이틀"), {"fields": "해당 타이틀과 관련된 필드들"}
    ),
)

이제 fieldsets부분을 수정하고 난 뒤

fieldsets = (
    (None, {"fields": ("username", "password", "email")}),
    (
        ("Personal info"),
        {
            "fields": (
                "is_superuser",
                "height",
                "weight",
            ),
        },
    ),
    (("hello world"), {"fields": ("last_login", "date_joined")}),
)

admin 페이지에서 확인해보면

필자가 수정한 부분들이 잘 적용된 걸 확인할 수 있습니다.

 

아쉽게도 필자가 설명할 수 있는 부분은 여기까지 인 거 같습니다.

 

이제 자신이 커스터마이징 한 User모델과 Admin페이지에 일관성이 느껴지나요?

 

만약 느껴진다면 여러분들의 Admin페이지 커스터마이징은 성공이라고 생각합니다. 😎

 

 

사실 이때까지 필자가 설명한 내용들은 admin페이지에서 가장 기본적인 커스터마이징에 해당합니다.

혹시나 더 깊은 수준으로 커스터마이징을 하려고 하는 분들을 위해

필자가 생각하기에 가장 도움이 되는 두 링크를 남겨두겠습니다.

--> Django contrib/auth/admin Source Code

 

GitHub - django/django: The Web framework for perfectionists with deadlines.

The Web framework for perfectionists with deadlines. - GitHub - django/django: The Web framework for perfectionists with deadlines.

github.com

--> Django Admin Site Reference

 

Django

The web framework for perfectionists with deadlines.

docs.djangoproject.com

댓글
공지사항