Django Admin-2 : 내용 커스텀하기(어드민과 모델)

Ho Kim·2022년 10월 31일
1

기본 앱을 만들었다면 이제 더 추가하고 변경하고 싶은 사항들이 있을 것이다. 처음에는 수정 할 수 있는 항목이 거의 없는 것처럼 느껴졌는데, 지금은 django의 자유도가 생각보다 크다는 것을 알게 되어 공유해보려고 한다.

여기서 말하는 커스텀이란?

예를 들어 간단한 로그인 데이터베이스가 있다고 치자.
로그인 데이터베이스에는 최근에 로그인한 날짜가 있는데 최근 10일에 로그인했는지 여부를 change_list에서 보고 싶다면 어떻게 해야할까?

이런 식으로 데이터베이스의 필드는 아니지만 사용자가 보고싶어 하는 항목을 추가로 작성해서 보여주는 것을 커스텀이라고 할 것이다.

간단하게 구성한 데이터베이스를 보자.

# adminpage\models.py

from django.db import models

class User(models.Model):
	id = models.CharField(primary_key=True, verbose_name="아이디",max_length=200)
	user_name = models.CharField(verbose_name="이름",max_length=200)
	user_pass = models.CharField(verbose_name="비밀번호",max_length=200)
	recent_login = models.DateTimeField( verbose_name="최근 로그인", )
	updated_at = models.DateTimeField(auto_now=True, verbose_name="수정일", )
	created_at = models.DateTimeField(auto_now_add=True, verbose_name="생성일", )
 
	def __str__(self):
		return f"{self.id} ({self.user_name})"
        
	class Meta:
		managed = True
		db_table = 'user' 
		verbose_name_plural = '유저'

짤막팁

created_at은 어드민에서 인스턴스가 새로 생성된 경우 당시의 시간이 저장 되어야 한다.
이 경우 모델에서 필드를 만들 때 auto_now_add=True 옵션을 주면 새로 생성한 경우 생성 시간이 자동으로 저장 된다.

updated_at은 업데이트, 즉 어드민에서 저장될 때마다 당시의 시간으로 업데이트 되어야 한다.
이 경우 모델에서 필드를 만들 때 auto_now=True 옵션을 주면 어드민에서 값을 저장할 때 마다 당시의 시간이 업데이트 된다.

두 경우 주의사항이 있는데, auto_now_add나 auto_now를 True로 만들면 수정이 불가해야 하므로 change_form에서 보고자 할 때(fields나 fieldset에 추가한 경우) readonly_fields에 추가해야 오류가 나지 않는다.

이제 두가지 선택지가 있다.

첫번째는 커스텀 할 항목을 모델에 작성하는 것이다.
해당 항목를 여러번 재사용 할 경우는 모델에 작성하는 것이 좋다.

두번째는 커스텀 할 항목을 어드민에 작성하는 것이다.
모델에 커스텀 항목이 늘어나면 모델 파일이 지저분해지므로 단 한번만 사용 될 항목의 경우는 어드민에 작성하는 것이 좋다.

1. 모델에 작성하기

1) 모델 내부에 함수 형식으로 원하는 항목을 작성한다.

# adminpage\models.py

from django.db import models
from django.utils import timezone
from datetime import timedelta

class User(models.Model):
	id = models.CharField(primary_key=True, verbose_name="아이디",max_length=200)
	user_name = models.CharField(verbose_name="이름",max_length=200)
	user_pass = models.CharField(verbose_name="비밀번호",max_length=200)
	recent_login = models.DateTimeField( verbose_name="최근 로그인", )
	updated_at = models.DateTimeField(auto_now=True, verbose_name="수정일", )
	created_at = models.DateTimeField(auto_now_add=True, verbose_name="생성일", )
 
	def __str__(self):
		return f"{self.id} ({self.user_name})"

	def isRecentlyLogined(self):
		recent = timezone.now() - timedelta(days=10)
		return self.recent_login > recent
		
	class Meta:
		managed = True
		db_table = 'user' 
		verbose_name_plural = '유저' 

isRecentlyLogined 라는 함수를 새로 작성했다.

timezone.now() 로 데이터베이스의 현재 시간을 가져온다.
datetime.now() 를 쓸 수 도 있지만 datetime은 컴퓨터 시간을 가져오기 때문에 데이터베이스의 시간을 비교해야한다면 timezone.now()를 쓰는것이 좋다.

deltatime은 시간을 숫자 자료형처럼 계산해야 할 때 쓴다. 현재로부터 10일을 뺐으므로 10일 이전의 시간이 된다.
계산된 시간과 비교해 현재 인스턴스의 최근 로그인 시간이 더 크다면 최근 로그인한 적이 있는 것이므로 True가 반환된다.

모델에서는 인스턴스의 값에 접근하기 위해 self를 쓴다. 기억해두자.


2) 어드민에 올린다.


# adminpage\admin.py

class UserAdmin(admin.ModelAdmin):
    list_display = ('id', 'user_name', 'recent_login','isRecentlyLogined')

3) 보기 편하게 수정한다.

이름도 한글화 하고 싶고, 결과도 boolean 값이니까 조금 더 보기 쉽게 만들고 싶다.

해당 변경사항은 admin.display 어노테이션을 통해 간단하게 수정할 수 있다.


# adminpage\models.py

from django.contrib import admin

...

	@admin.display(
		boolean=True,
  		description="최근 로그인 여부"
	)
	def isRecentlyLogined(self):
		recent = timezone.now() - timedelta(days=10)
		return self.recent_login > recent

...
		

+) change_form에서 최근 로그인 여부 보기
해당 항목을 수정 페이지에서도 보고 싶다면 어드민 fields 혹은 fieldsets에 isRecentlyLogined 항목만 추가하면 된다고 생각하기 쉽다.

하지만 그렇게 하면
Unknown field(s) (isRecentlyLogined) specified for User.
오류를 만나게 된다.

이는 커스텀 함수가 수정할 수 없는 항목이기 때문에 발생하는 오류이다.
따라서 커스텀 함수는 항상 readonly_fields에 추가되어야한다.


# adminpage\admin.py

class UserAdmin(admin.ModelAdmin):
    list_display = ('id', 'user_name', 'recent_login','isRecentlyLogined')
    fields =  ('id', 'user_name','user_pass', ('recent_login','isRecentlyLogined'))
    readonly_fields =  ('isRecentlyLogined', )

fields 혹은 fieldsets 튜플 안에 한번 더 튜플을 하면 해당 항목을 한줄로 뽑아준다.
위의 경우 recent_loginisRecentlyLogined가 한줄에 나타난다.

모든 항목이 각 줄에 나타나도록 했더니 체크 아이콘이 너무 크게 나와서 작게 나타나게 하려고 추가했다.

2. 어드민에 작성하기

위와 동일한 항목을 어드민에서 작성하고 가져와 볼 것이다.

# adminpage\admin.py

from django.utils import timezone
from datetime import timedelta

class UserAdmin(admin.ModelAdmin):
    list_display = ('id', 'user_name', 'recent_login','isRecentlyLogined2')
    fields =  ('id', 'user_name','user_pass', ('recent_login','isRecentlyLogined2'))
    readonly_fields =  ('isRecentlyLogined2', )

    @admin.display(
		boolean=True,
  		description="최근 로그인 여부"
	)
    def isRecentlyLogined2(self, obj):
        recent = timezone.now() - timedelta(days=10)
        return obj.recent_login > recent

모델에서 작성한것과 거의 동일하다.
달라진 부분은 함수가 인자로 selfobj를 받고, 모델에서 인스턴스의 값에 접근할 때 self로 접근했던 것과 달리 obj를 사용한다는 것 뿐이다.

모델은 구조이고 그 구조에 값이 채워진 것이 인스턴스 이다.
모델은 청사진이고 그것이 실제로 구현된 것이 인스턴스라고 할 수 있다.
그래서 인스턴스의 타입을 뽑아보면 모델이 나타난다.
따라서 인스턴스 내부에서 함수를 선언하고 함수내부에서 모델 인스턴스의 값에 접근하고자 하면 self를 쓰면 된다.

하지만 어드민에서 모델에 접근하고자 하는 경우에는 self를 쓰면 안된다. 어드민에서 self는 모델이 아니라 어드민이기 때문이다. 따라서 self 가 아닌 어드민이 가지고 있는 현재 인스턴스인 obj를 써서 내부 값에 접근해야한다.

위 코드를 실행시켜보면 모델에 항목을 작성한 것과 정확히 동일한 결과가 나타나는 것을 확인할 수 있다.



상세 코드는 다음 링크를 참조 :

https://github.com/hokim2407/django-admin_study/tree/878475fca8d694e9eacd8829fc02dc73cbd38852

0개의 댓글