# admin.py
from django.contrib import admin
form .models import * # 모든 모델을 불러옵니다.
admin.site.register(Departments)
admin.site.register(DeptEmp)
admin.site.register(DeptManager)
admin.site.register(Employees)
admin.site.register(Salaries)
admin.site.register(Titles)
방법 2
form django.contrib import admin
form .models import *
class MyAdmin(admin.ModelAdmin):
...
admin.site.register(DeptEmp, myAdmin)
# admin.site.register(등록할 모델 이름, 등록할 어드민 클래스 이름)
방법 3
form django.contrib import admin
form .models import *
@admin.register(DeptEmp)
class MyAdmin(admin.ModelAdmin):
...
models 에는 등록되어 있는 models들이 존재하고 이를 import 해서 등록시키는 과정 입니다. 총3가지 방법이 있는데 각각에 대해서 간단히 알아보도록 합시다.
방법1은 기본 ModelAdmin을 통해서 admin 페이지에 등록을 합니다.
방법 2는 ModelAdmin을 상속해 이를 커스텀하고 커스텀한 클래스를 통해 등록을 해줍니다.
방법 3은 방법 2와 유사하지만 데코레이터를 통해서 더 간결하게 등록을 합니다.
모델만 등록된 Admin 페이지
2. 커스터마이징 옵션
fields, fieldsets
fields
fields 는 Admin 사이트에서 디테일 페이지에서 보여질 폼 필드를 재정렬하거나 사용할 필드를 재정의 하는 옵션입니다.
fields 적용 방법
# admin.py
class QuestionAdmin(admin.ModelAdmin):
fields = ['pub_date', 'question_text']
결과
수십 개의 필드가 있는 관리 폼의 경우에는 직관적인 순서를 선택하는 것이 사용 편리성의 중요한 부분입니다.
fieldsets
fieldsets 같은 경우도 fields와 동일하나 수십 개의 필드가 있는 폼에 관해서는 폼을 fieldsets으로 분할하는 것이 좋습니다.
# admin.py
class ScoreFilter(SimpleListFilter):
title = _('점수 구분')
parameter_name = 'score'
def lookups(self, request, model_admin):
codes = [code for code in Code.objects.filter(code__value='SCORE')]
return [(code.pk, code.name) for code in codes]
def queryset(self, request, queryset):
if self.value():
return queryset.filter(score__pk=self.value())
return queryset
1. title
필터의 이름으로 오른쪽 사이드바에 나타내는 필터의 이름을 표시하게 됩니다.
2. parameter_name
URL 쿼리에 사용될 필터에 대한 매개 변수입니다.
3. lookups
튜플 목록을 반환 합니다.
첫번째 요소의 값은 URL 쿼리의 value로 나타납니다.
두번째 요소의 값은 오른쪽 사이드바(filter)에 나타날 옵션의 이름입니다.
# admin.py
def lookups(self, request, model_admin):
codes = [code for code in Code.objects.filter(code__value='SCORE')]
return [(code.pk, code.name) for code in codes]
# return 되는 값은 튜플로 구성된 리스트 이며 첫번째 요소는 코드화된
# url쿼리(쿼리스트링의 value) 입니다.
# 두번째 요소의 값은 사이드바에 나타날 옵션의 이름입니다.
첫번째 요소는 위 사진을 보면 알 수 있듯이 검색 시 사용되는 URL 코드 해당 사진에서는 code.pk의 값인 29 입니다.
두번째 요소는 위 사진을 보면 알 수 있들이 code.name의 값인 A,B,C,F 로 표시됩니다.
self.value()가 있다면 filter로 원하는 쿼리셋을 반환하는 코드를 작성할 수 있습니다.
여기서 self.value()는 쿼리스트링으로 넘어온 ‘value’의 값 입니다.
위에서 설명한 코드로 보면 code.pk의 값인 29가 넘어오게 됩니다.
list_select_related
list_select_related는 Admin 사이트에서 목록페이지에 보여질 필드를 정의할때 쿼리의 수를 효율적으로 실행될 수 있도록 설정하는 옵션입니다.
list_select_related 설명
# admin.py
class Author(models.Model):
name = models.CharField(max_length=10)
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
예를 들어 위와 같은 모델이 있다고 가정했을 때 Book Admin에서 Book의 저자의 정보까지 목록페이지에서 표시해주려고 하면 다음과 같이 구현할 수 있습니다.
FK로 정의되어 있는 ‘class_room’, ‘student’ 필드를 raw_id_fields로 적용 시켰습니다.
결과
위 사진을 보면 알 수 있듯이 돋보기 모양의 아이콘이 생기며 해당 아이콘을 클릭하면 검색할 수 있는 창이 새롭게 뜨는것을 확인할 수 있습니다.
이로써 ‘class_room’, ‘student’ 가 너무 많아 졌을 때 필터를 적용하거나 검색하는 것이 가능해 졌습니다.
Tip.
raw_id_field를 사용하지 않으면 FK field 같은 경우 기본적으로 셀렉트 박스(select box)로 노출됩니다.
actions
django admin 같은 경우 기본적으로 리스트 페이지에서 체크박스를 통해 데이터의 “삭제” 기능만 구현되어 있습니다.
삭제 이외의 기능을 구현하고 싶을때 사용하는 것이 actions 입니다.
django admin 에서는 action function를 만들어 원하는 기능을 추가할 수 있습니다.
**Tip.**
- acrtions는 외래키 등 관계를 사용하지 않는 모델에도 당연히 사용가능하지만 외래키 처럼 관계가 있는 모델 및 필드에도 사용 가능 합니다.
**actions 적용 방법**
옵션의 적용 방법은 list_display에서 멤버함수(메소드)를 정의한 후 적용 시킨것과 같습니다.
- actions 함수를 따로 만들어서 해당 actions 함수를 actions 클래스 옵션 값에 해당 함수들을 목록의 값으로 추가해주면 됩니다.
```python
# admin.py
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['id', 'title', 'status', 'created_at', 'updated_at' ]
actions = ['make_published', 'make_draft']
# actions 에 사용할 멤버 함수 지정
# admin action 추가
def make_published(self, request, queryset):
updated_count = queryset.update(status='p') #queryset.update
self.message_user(request, f'{updated_count}건의 포스팅을 Published 상태로 변경')
make_published.short_description = '지정 포스팅을 Published 상태로 변경'
def make_draft(self, request, queryset):
updated_count = queryset.update(status='d') #queryset.update
self.message_user(request, f'{updated_count}건의 포스팅을 draft 상태로 변경')
make_draft.short_description = '지정 포스팅을 draft 상태로 변경'
# 액션함수이름.short_description 같은 경우
# 기능을 수행할 actions의 이름을 지정해주는 코드 입니다.
```
**결과**
inlines
inlines 옵션은 ForienKey로 연결된 다른 Model을 관리자 디테일 페이지에 삽입하는 옵션입니다.
예를 들어 위 사진 처럼 Home Work와 Home Work Content라는 모델이 있고 Hoem Work Content모델이 Home Work 모델을 참조하고 있을때, Home Work 모델의 관리자 디테일 페이지에서 참조하고 있는 Home Work Content의 정보를 같이 보여주고 싶을때 사용하는 옵션입니다.
즉, 어드민 안에 또 다른 어드민을 넣을때 사용합니다.
inLines 옵션의 종류는 2가지가 있습니다.
StackedInline
TabularInline
둘의 차이는 외관적 차이 밖에 없으니 마음에 드는 걸로 사용하면 됩니다.
inlines 옵션을 사용할때는 TabularInline, StackedInline 중 택 1하여 원하는 종류를 상속하여 사용하게 됩니다.
StackedInline 적용 방법
# admin.py
class HomeWorkContentAdmin(admin.StackedInline):
model = HomeWorkContent
extra = 1
class HomeWorkAdmin(admin.ModelAdmin):
inlines = [HomeWorkContentAdmin]
참조하는 모델을 지정해주고 해당 클래스의 이름을 inlines에 리스트, 튜플 타입으로 지정해주면 됩니다.
extra 같은 경우 노출 되는 데이터 행의 기본 값 입니다.
StackedInline 결과
extra를 1로 설정했기 때문에 기본적으로 1개의 데이터 행만 생성되어 있는 것을 확인할 수 있습니다.
지정한 모델의 필드 전체가 생성되게 됩니다.
StackedInline 같은 경우 데이터(필드)의 배치가 세로 입니다.
열 방향
TabularInline 적용 방법
# admin.py
class HomeWorkContentAdmin(admin.TabularInline):
model = HomeWorkContent
extra = 1
class HomeWorkAdmin(admin.ModelAdmin):
inlines = [HomeWorkContentAdmin]
TabularInline 결과
TabularInline 같은 경우 데이터(필드)의 배치가 가로 입니다.
행 방향
Tip.
extra 의 원래 기본값은 3입니다.
3. 커스터마이징 메소드
formfield_for_foreignkey
formfield_for_foreignkey는 외래키 참조로 셀렉트 박스의 내용을 제한하는 기능을 수행하는 메소드입니다.
예를 들어 “스포츠” 게시판에 “축구”, “야구” 등을 분류 하고 있고 “게임” 게시판에 “RPG”, “FPS”등으로 분류하고 있다고 할 때 일반적으로 게시물 모델에서 카테고리를 참조하면 기본적으로 모든 카테고리가 나열 됩니다.
“스포츠” 게시판을 선택했을 때는 “축구”, “야구” 등 종목만 목록에 보이도록하는 것이 사용자 편의성과 데이터의 정합성을 위해 올바릅니다.
즉, 외래키의 참조 필드 내용에 따라 선택적으로 셀렉트 박스 목록을 보여주고 싶다면 formfield_for_foreignkey 메소드를 오버라이딩 하면됩니다.
formfield_for_foreignkey 사용 전
외래키로 참조할 수 있는 모든 카테고리가 노출되는 것을 확인할 수 있습니다.
formfield_for_foreignkey 사용 후
# admin.py
class HomeWorkAdmin(admin.ModelAdmin):
...생략
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'score':
kwargs['queryset'] = Code.objects.filter(code__value='SCORE')
return super().formfield_for_foreignkey(db_field, request, **kwargs)
# formfield_for_foreignkey 오버라이딩 후 원하는 조건값을 설정하면 됩니다.
위 사진을 보면 알 수 있듯이 해당 카테고리에 맞춰서 원하는 셀렉트 박스의 값만 노출되는것을 확인할 수 있습니다.
get_urls
get_urls는 URLconf와 동일한 방식으로 해당 ModelAdmin에 사용될 URL을 리턴하는 메소드입니다. 따라서 해당 메소드를 오버라이딩 하면 확장할 수 있습니다.
일반적으로, get_urls 같은 경우 오버라이딩 해서 다른 페이지로 이동 시키기 위해 or 다른 동작을 적용 시키기 위해 사용합니다.
get_urls 적용 방법
# admin.py
class HomeWorkAdmin(admin.ModelAdmin):
...생략
def get_url(self):
urls = super().get_urls()
data_urls = [
path('url_name/', self.my_view),
]
return data_urls + urls
def my_view(self, request):
...생략
context = dict(
self.admin_site_each_content(request),
key=value,
)
return TemplateResponse(request, "sometemplate.html", context)
# data_urls 같은 경우 임의로 작성된 이름입니다.
# 변수명을 원하는대로 작성하셔도 무방합니다.
# ex) - homework_urls = []
# self.admin_site_each_content(request)
# ex) - 같은경우 관리 템플릿을 렌더링 하기 위한 공통 변수를 포함합니다.
urls = super().get_urls(): 지금 선언한 함수에 기존의 get_urls함수를 사용하겠다는 뜻입니다.
data_urls: path 함수를 사용해서 url주소를 만들어주고 실행할 함수를 담습니다.
return 값에는 urls와 data_urls를 더해서 적용시켜 줍니다.
urls와 data_urls를 더해서 적용 시키는 이유는 기존에 정의되어 있는 관리자 urls과 같이 새로 만든 url을 추가하기 위함입니다.
get_form
get_form은 views.py 에서 사용하는 get_form과 비슷한 기능을 하는 메소드 입니다. 현재 적용되어 있는 form을 불러와서 사용자 권한에 따른 필드 노출을 정의도 할 수 있으며 특정 필드의 커스텀 또한 할 수 있습니니다.
get_form 적용 방법
# 필드의 넓이 변경
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['myfield'].widget.attrs['style'] = 'width: 45em;'
return form
# 사용자 타입에 따른 노출 변경
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
if request.user_type == 'STUDENT':
form.base_fields['myfield'].widget.attrs['style'] = 'display:none;'
return form
return form
# request.user_type == 'STUDENT' 같은 경우
# User의 모델에 user_type이라는 초이스 필드와 값이 있다고 가정하고
# 작성된 코드 입니다.
save_form, save_model
From의 save 버튼을 눌렀을 때 있는 그대로 데이터베이스에 저장되지만 때때로 다른 처리가 필요할 수 있습니다.
이런 경우에는 Django Admin에서 Form을 전송하여 Model로 변환되고 저장되는 프로세스 중간에 개발자의 로직이 들어가야합니다.
Django Admin에서는 save 버튼을 눌러서 저장하게 되면 save_form과 save_model을 거치게 됩니다.
save_form
이 단계에서는 Form으로 넘어온 데이터를 처리할 수 있습니다. 아직 Model의 객체가 생성되기 전이므로 Form데이터에 포함된 필드값에 대한 변경이 가능한 단계 입니다.
Form
Form은 위 사진처럼 구성되어 있으며, Form의 data 속성에는 Admin 페이지에서 입력한 모든 값들이 포함되어 있습니다.
save_form 메서드에서는 이 값들을 수정할 수 있습니다.
- Form의 data 속성은 기본적으로 _mutable이 False로 되어 있습니다. 이것이 의미하는 것은 수정할 수 없다는 것입니다.
**save_form 적용 방법**
따라서 아래와 같은 방법을 사용합니다.
```python
def save_form(self, request, form, change):
data = form.data.copy() # copy()를 사용하면 데이터를 복사하게 되고 수정할 수 있는 data가 리턴 됩니다.
data.update({"contents": "imege1.jpg"})
form.data = data # 수정 후 form의 data 속성을 수정한 query dict으로 바꿔 줍니다.
return super().save_form(request, form, change)
```
- Form의 data는 수정할 수 없으니 .copy() 함수를 사용하여 수정 하는 것 입니다.
**save_model**
이 단계에서는 데이터베이스에 저장될 Model의 객체와 Form 데이터가 함께 오게 됩니다. Model에 어떤 값을 넣어서 저장될 지 직접 로직을 처리할 수 있습니다. 이 단계에서는 Form의 data필드를 수정해도 반영이 되지 않습니다.
save_model 메서드는 Form의 data 속성이 모두 입력된 Model Instance가 데이터베이스에 적용되기 전에 호출되는 메서드 입니다.
위의 공식문서에서 확인할 수 있듯이 이 메서드에는 Model이 저장되기 전, 후에 새로운 로직을 추가할 수 있습니다.
**save_model 적용 방법**
```python
def save_model(self, request, obj, form, change):
data = form.data
html_contents = data.get('contents', None)
obj.contents = html_contents
super().save_model(request, obj, form, change)
data = {
'file_name': 'aaa',
'imge_type': 'jpeg',
'url': 'http://www',
'related_article': obj,
}
ImageTable.objects.create(**data)
return obj
```
super().save_model 메서드를 실행하면 실제로 Model Instance가 데이터베이스에 적용됩니다.
따라서 이 메서드 전에 Model에 저장될 내용을 수정할 수 있습니다.
obj가 저장될 모델 인스턴스이기 때문에 이 모델의 속성값에 값을 부여하거나 수정하면 됩니다.
- save_model 메서드가 호출되기 전에 obj에는 Form의 data 속성값만 적용되어 있습니다.
- pk값은 None으로 표시되어 있는데 아직 데이터베이스에 적용되지 않았기 때문입니다.
- save_model메서드를 수행하면 pk값이 부여됩니다. 데이터베이스 상에는 row값이 보이지 않을 수 있지만 실제로 저장이 된 것입니다.
changelist_view
chagelist_view 메서드는 Admin 목록페이지에서 노출되는 데이터를 커스텀 하기 위해 사용하는 메서드 입니다.
chagelist_view 적용 방법 (1)
def changelist_view(self, request, extra_context=None):
if request.method == 'GET':
list_selector = request.GET.get('list_per_get_selector', '')
self.get_list_per_selector(list_selector)
return super().changelist_view(request, extra_context)
# 해당 코드는 목록페이지에서 list_per_get_selector라는 key 값으로 value를 가지고
# 와서 해당 값으로 get_list_per_selector()함수를 이용하여 어떠한 처리를 하는 로직입니다.
# javascript
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %}
{% block content %}
....
<script src="{% static 'js/admin/my_own_admin.js' %}"></script>
{% block end %}
# 또는
{% extends "admin/change_form.html" %}
{% load i18n admin_urls static admin_modify %}
{% block extrahead %}
{{ block.super }}
...
<script src="{% static 'js/admin/my_own_admin.js' %}"></script>
{% endblock %}
# block extrahead 안에 스크립트 파일 지정 시 페이지가 전체 로드 되기 전에
# 코드가 돌기 때문에 에러가 발생할 수 있습니다.
# 따라서, block extrahead 안에 스크립트 파일 지정 시 해당 스크립트 파일에서
# window.addEventListener("load", MyFunction) 형식으로 로드가 된 다음 실행 시키는게
# 좋습니다.
해당 방법은 “6. Admin 템플릿 재정의”을 본 후 코드를 보면 이해에 도움이 됩니다.
메타 클래스를 통한 확장*
class MyModelAdmin(admin.ModelAdmin):
class Media:
js = (
'js/admin/my_own_admin.js',
.....,
)
css = {
'all':('css/admin/my_own_admin.css',
.....,
)
}
5. 필드에 커스텀 위젯 적용
필드 전체에 위젯 적용
모델 어드민에 존재하는 TextField의 특정 위젯을 일괄 커스텀 하고 싶은 경우 가 있을 수 있습니다. 그럴때 formfield_overrides 변수에 딕셔너리 형식으로 위젯을 일괄 지정할 수 있습니다.
formfield_overrides
from .widgets import SimpleMDEWidget
class PostAdmin(admin.ModelAdmin):
**formfield_overrides** = {
models.TextField: {'widget': SimpleMDEWidget(wrapper_class='simplemde-box-admin',
options=options)},
}
PostAdmin 같이 모델 어드민에 여러 개의 TextField가 존재해도 일괄적으로 적용됩니다.
먼저 “{% extends "admin/change_list.html" %}” extends 구문으로 change_list.html 을 상속받는 html 파일을 새로 정의 합니다.
class MyModelAdmin(admin.ModelAdmin):
change_list_template = 'admin_custom_list.html'
change_form_template = 'admin_custom_form.html'
# change_list_template 같은 경우 관리자의 목록 페이지(ListView)
# change_form_template 같은 경우 관리자의 디테일 페이지(DetailView) 입니다.
그 후 새롭게 정의한 html을 change_list_template 클래스 변수에 값으로 지정해 주면 해당 어드민의 목록 페이지의 template는 커스텀한 template로 변경 됩니다.
기존에 정의되어있는 것(기본 admin template file) 또한 “index.html”으로 새롭게 정의하기 위해 똑같은 이름으로 생성합니다.
옵션을 사용자 정의한 경우에 관리자에 포함된 템플릿보다 먼저 템플릿 로드 시스템에서 사용자 정의 템플릿을 찾아서 정의 합니다.
3. index.html 재정의*
{% extends "admin/index.html" %}
{% load i18n admin_urls static admin_modify %}
# admin_urls, static, admin_modify 를 loda 하는 이유는 어드민에 기존에 정의 되어 있는
# 기능 및 스타일 코드를 사용하기 위함 입니다.
extends 구문을 이용하여 기존에 있는 index.html 를 상속받은 후 원하는 곳의 커스텀을 진행하면 됩니다.