본문 바로가기

django

Django Admin 커스텀

Django Admin 커스텀 매뉴얼

목적


간단한 요약


장고 관리자 커스텀 관련 내용

장고 관리자 커스텀 관련 전반적인 내용이 담겨있습니다.

  • 문서 내용 목록
    • Admin에 모델 등록하기
      • 관리자 페이지에 모델을 등록하는 방법
    • 커스터마이징 옵션
      • 관리자 페이지의 커스텀 옵션별 정리
    • 커스터마이징 메소드
      • 관리자 페이지의 메소드별 사용 방법
    • CSS, JavaScript 재정의
      • 관리자 페이지의 CSS, JavaScript 사용자 지정 추가
    • 필드에 커스텀 위젯 적용
      • 관리자 페이지에서 사용하는 필드(fields)의 커스텀 위젯 적용 방법
    • Admin Temlate 재정의
      • 관리자 페이지 사용자 지정 Template 사용 방법
    • Admin 목록 페이지(ListView Page) 재정의 후 새로운 로직(경로) 추가
      • 관리자 목록 페이지 커스텀 후 새로운 로직 추가하는 방법
    • Admin 메인 페이지(index.html) 재정의
      • 관리자의 메인페이지 커스텀
    • Admin 페이지 사이드바 커스텀
      • 관리자의 사이드바 커스텀
    • Admin 목록 페이지의 데이터 노출 개수 사용자 지정 커스텀
      • 관리자의 목록 페이지의 데이터 노출 개수 커스텀
    • Admin 페이지 구조도
      • 관리자 페이지의 Template 관계도(구조도)
    • Django Admin URL 설정 시 참고
      • 관리자 페이지 URL 정리

📌 장고 관리자 커스텀 관련 상세 내용을 작성한 문서입니다.

상세내용


Django Admin의 특징

  • 모델에 대한 CRUD를 UI로 제공

  • 개발에 편한 인터페이스

  • 자유로운 커스터마이징

  • 1. Admin에 모델 등록하기

    Admin에 모델을 커스텀하지 않고 등록하는 방법은 아래와 같습니다.

    방법 1

    # 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으로 분할하는 것이 좋습니다.

      fieldsets 적용 방법

      # admin.py
      class QuestionAdmin(admin.ModelAdmin):
          fieldsets = [
              (None, {'fields':['question_text']}),
              ('Date information', {'fields': ['pub_date']})
          ]
      • fieldsets의 각 튜플의 첫 번째 요소는 fieldset의 제목 입니다.

      결과

    • list_display

      list_display는 Admin 사이트에서 목록페이지에 보여질 필드를 정의하는 옵션입니다.

      • list_display 옵션을 따로 지정하지 않으면 각 object의 'str()'값을 보여줍니다.

      • 외래키가 설정되어 있다면, 관련 object의 'str()'값을 보여줍니다.

      • ManyToManyField는 지원하지 않습니다.

      • 모델 인스턴스 필드명/속성명/함수명 뿐만 아니라,

        ModelAdmin내 멤버함수도 지정 가능합니다.

      list_display 적용 방법

      # admin.py
      class EmployeesAdmin(admin.ModelAdmin):
          list_display = ['emp_no', 'first_name', 'last_name', 'gender',
       'birth_date', 'hire_date']

      멤버 함수 적용 방법

      #admin.py
      @admin.register(Post)
      class PostAdmin(admin.ModelAdmin):
          list_display = ['id', 'title', 'content_size', 'created_at', 'updated_at' ]
      
          def content_size(self, post):
              return mark_safe(f'<u>{len(post.content)}</u>글자')
          content_size.short_description = '글자수'
      
      # content_size.short_description 옵션은 
      # 커스텀한 필드의 컬럼이름을 지정하는 코드입니다.
      • 위 코드를 보면 알 수 있듯이 content_size라는 멤버함수를 새롭게 정의해서사용할 수 있습니다.
      • 멤버함수 새롭게 정의 후 list_display에 해당 함수 이름을 등록하게 되면 해당 함수의 return값이 적용된 화면을 볼 수 있습니다.

      Tip.

      • def content_size(self, post) ← post 경우 model의 객체 이름으로 마음대로 지정해서 사용할 수있습니다.
        • 통상적으로 “obj” 라는 이름으로 사용합니다.
      • 여기서 중요한 포인트는 ModelAdmin 으로 지정한 모델 “위코드에서는 Post 모델”의 객체가 담긴다는 점 입니다.
      • 이처럼 노출되는 데이터의 속성이나 이름 스타일 등등 을 변경 시킬 수 있습니다.

      결과

  • list_display_links

    list_display_links 옵션은 링크 기능을 추가할 필드를 지정하는 옵션입니다.

    • 옵션을 지정하지 않으면 첫 번째에 위치하는 필드만 링크(클릭)가 가능합니다.
    • list_display_links에 등록된 필드에 링크가 걸리게 되는 옵션입니다.

    list_display_links 적용 방법

    # admin.py
    class EmployeesAdmin(admin.ModelAdmin):
        list_display = ['emp_no', 'first_name', 'last_name', 'gender',
     'birth_date', 'hire_date']
        list_display_links = ['emp_no', 'first_name']
  • list_filter, SimpleListFilter

    list_filter

    list_filter는 Admin사이트에 필터를 활성화할 항목을 설정하는 옵션입니다.

    list_filter 적용 방법

    # admin.py
    class EmployeesAdmin(admin.ModelAdmin):
        list_display = ['emp_no', 'first_name', 'last_name', 'gender',
     'birth_date', 'hire_date']
        list_filter = ('gender', 'hire_date', 'birth_date',) # 튜플
    # list_filter = ['gender', 'hire_date'] # 리스트
    
    # list_filter 의 옵션 적용 방법은 리스트 or 튜플로 가능합니다.

    list_filter는 위 사진과 같이 관리자의 변경 목록페이지 오른쪽 사이드바에서 필터를 활성화 하도록 도와줍니다.

    Tip.

    • list_filter는 __조회를 사용하여 관계를 확장할 수도 있습니다.

      ex) - list_filter = [’company__name’, ]

    admin.SimpleListFilter

    list_filter말고도 admin.SimpleListFilter을 이용하여 filter의 요소를 추가 할 수있습니다.

    title, parameter_name 변수와 lookups, queryset 메소드를 재정의 해야 합니다.

    • title - 필터의 이름
    • parameter_name - 필터에 사용될 컬럼의 이름(데이터베이스 상에서 존재하는 이름) URL쿼리에 사용될 컬럼 입니다.
    • lookups - 필터 창에서 보이게되는 것을 구현한 메소드
    • queryset - 필터 창에서 특정 필터를 선택(클릭)했을때의 반환되는 쿼리셋을 구현한 메소드

    admin.SimpleListFilter 적용 방법

    # 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

    • 필터의 이름으로 오른쪽 사이드바에 나타내는 필터의 이름을 표시하게 됩니다.

    5.jpg

    2. parameter_name

    • URL 쿼리에 사용될 필터에 대한 매개 변수입니다.

    6.jpg

    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) 입니다.
    # 두번째 요소의 값은 사이드바에 나타날 옵션의 이름입니다. 

    6.jpg

    첫번째 요소는 위 사진을 보면 알 수 있듯이 검색 시 사용되는 URL 코드 해당 사진에서는 code.pk의 값인 29 입니다.

    5.jpg

    두번째 요소는 위 사진을 보면 알 수 있들이 code.name의 값인 A,B,C,F 로 표시됩니다.

    만약 두번째 요소를 다른 필드로 지정 시 해당 필드로 값이 노출됩니다.

    4. queryset

    • 값을 기준으로 필터링된 쿼리 집합(셋)을 반환합니다.
    • ‘self.value()’를 기준으로 필터링 됩니다.
    # admin.py
    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(score__pk=self.value())
        return queryset
    

    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의 저자의 정보까지 목록페이지에서 표시해주려고 하면 다음과 같이 구현할 수 있습니다.

    # admin.py
    class BookAdmin(admin.ModelAdmin):
        list_display = ['id', 'title', 'author_name']
    
        def author_name(self, obj):
            return obj.author.name
    
        author_name.shor_Description = '저자명'

    일반적으로 위와 같은 코드로 구현이 되는데, Book 모델이 복잡해져서 author_name과 같은 형태의 메소드가 많이 생기게 되면 Admin페이지가 느려지는 것을 경험 할 수 있습니다.

    느려지는 문제가 발생 되는 이유

    • Book 모델을 전부 가져온 다음에 각 Book 모델에 대해 author_name이 실행되느 때문에 Book 모델의 데이터가 많아지면 많아질 수 록 실행 되는 쿼리의 수가 늘어나기 때문입니다.
    • 즉, Book이 100개의 row(데이터)를 가지고 있다면, obj.author.name 코드를 통해 DB에서 obj에 대항하는 author의 name을 찾는 쿼리가 100번 실행 됩니다 (N+1 문제)

    list_select_related 적용 방법

    # admin.py
    class BookAdmin(admin.ModelAdmin):
        list_display = ['id', 'title', 'author_name']
        list_select_related = ['author', ]
    
        def author_name(self, obj):
            return obj.author.name
        author_name.shor_Description = '저자명'

    ModelAdmin 내부에 list_select_related 옵션에 select_related를 하고 싶은 필드를 추가해주기만 하면 됩니다.

    Tip.

    • list_select_related의 기본값은 False 입니다.
    • 또한, list_select_related에 적용시킬 값은 bool, list, tuple만 가능 합니다.
  • list_per_page

    admin 목록 페이지(list)에서 데이터의 최대 노출 개수를 설정하는 옵션입니다.

    기본값은 100이며, 해당 옵션값을 변경하면 목록 페이지에서 노출 되는 데이터의 최대 개수가 변경됩니다.

    list_per_page 적용 방법

    # admin.py
    @admin.register(HomeWork)
    class HomeWorkAdmin(admin.ModelAdmin):
        list_display = ['class_room', 'student', 'title', 'homework_date', 'score']
        search_fields = ['id', 'title', 'class_room__name', 'student__name', 'score']
        raw_id_fields = ['class_room', 'student']
        list_filter = [ScoreFilter]
        list_editable = ['title']
            list_per_page = 5
    
    # list_per_page은 int형의 데이터를 받으며
    # list_per_page의 값을 5로 설정하면 최대 데이터 노출 개수가 5개로 변경 됩니다.

    결과

  • list_max_show_all

    list_max_show_all은 목록페이지의 “모두 표시”버튼을 제어하는 옵션입니다.

    기본값은 200이며, 데이터의 개수가 200이하 일때만 해당 버튼이 노출됩니다.

    list_max_show_all 적용 방법

    # admin.py
    @admin.register(HomeWork)
    class HomeWorkAdmin(admin.ModelAdmin):
        list_display = ['class_room', 'student', 'title', 'homework_date', 'score']
        search_fields = ['id', 'title', 'class_room__name', 'student__name', 'score']
        raw_id_fields = ['class_room', 'student']
        list_filter = [ScoreFilter]
        list_editable = ['title']
            list_max_show_all = 300
    
    # list_max_show_all 은 int형의 데이터 를 받으며 
    # 예시와 같이 300으로 설정하면 데이터의 개수가 300이하 일때 해당 버튼
    # 이 노출됩니다.

    Tip.

    • “모두 표시” 의 버튼을 없애고 싶을때는 list_max_show_all의 값을 False로 지정하면 됩니다.

      ex) - list_max_show_all = False

  • list_editable

    admin 목록 페이지(list)에서 상세 페이지로 이동하지 않고 바로 수정가능하게 해주는 옵션입니다.

    Tip.

    • list_display_links와 list_editable은 특정 컬럼을 중복해서 사용할 수 없습니다.
    • 수정할 수도 있으며 이동할 수도 있다는 거 자체가 말이 안됩니다.

    list_editable 적용 방법

    # admin.py
    @admin.register(HomeWork)
    class HomeWorkAdmin(admin.ModelAdmin):
        list_display = ['class_room', 'student', 'title', 'homework_date', 'score']
        search_fields = ['id', 'title', 'class_room__name', 'student__name', 'score']
        raw_id_fields = ['class_room', 'student']
        list_filter = [ScoreFilter]
        list_editable = ['title']

    결과

 list_editable에 title 필드의 값을 설정해 준 것 만으로도 디테일 페이지로 넘어가지 않고 목록 페이지에서도 수정할 수 있을 것을 확인할 수 있습니다.
  • search_fields

    search_fields는 사용자가 입력하는 검색어를 찾을 필드를 지정하는 옵션입니다.

    검색 필드 지정 시, 상단에 검색 바가 활성화 되며, 지정된 필드에 사용자 입력 단어가 포함된 행을 보여줍니다.

    search_fields 적용 방법

    # admin.py
    @admin.register(HomeWork)
    class HomeWorkAdmin(admin.ModelAdmin):
        list_display = ['class_room', 'student', 'title', 'homework_date', 'styled_score']
        search_fields = ['id', 'title', 'class_room__name', 'student__name', 'score']

    search_fields 적용 전

    search_fields 적용 후

    search_fields를 적용하게 되면 검색창이 생성된것을 확인할 수 있습니다.

    • 데이터 검색에 용이하게 구성됩니다.
  • date_hierarchy

    date_hierarchy는 날짜를 필터하기위해 사용하는 옵션입니다. date_hierarchy 옵션에 원하는 날짜필드를 지정하면 목록페이지에서 해당하는 필드로 날짜 기반 드릴 다운 탐색모음이 나타납니다.

    date_hierarchy 적용 방법

    # admin.py
    class BookAdmin(admin.ModelAdmin):
        date_hierarchy = 'created_date'

    결과

    Tip.

    • 기본값은 False 입니다.

    • date_hierarchy 옵션은 하나의 날짜 필드만 사용해 계층을 만들 수 있기 때문에 튜플이 아닌 문자열을 입력합니다.

    • __조회를 사용하여 관련 모델의 필드를 지정할 수도 있습니다.

      ex) -

      dete_hierarchy = 'author__pub_date'
  • save_as

    save_as는 관리자 디테일 페이지에서 “새로 저장”기능을 활성화 하도록 설정하는 옵션입니다.

    일반적으로 디테일 페이지에서 개체에는 3가지 저장 옵션이 있습니다.

    • “저장”, “저장혹 편집 계속”, “저장 및 다른 항목 추가”

    save_as를 Ture로 설정할 경우 “저장 및 다른 항목 추가”는 기존 개체를 업데이트 하는 대신 새로운 개체(새로운 ID값으로)를 만드는 “새로 저장” 버튼으로 변경 됩니다.

    save_as 적용 방법

    # admin.py
    class BookAdmin(admin.ModelAdmin):
            save_as = True

    save_as 적용 전

    save_as 적용 후

    Tip.

    • 기본값은 False 입니다.
  • save_on_top

    save_on_top은 관리자 디테일 페이지에서 상단에 저장 버튼을 추가하는 옵션 입니다.

    save_on_top 적용 방법

    # admin.py
    class BookAdmin(admin.ModelAdmin):
            save_on_top = True

    save_on_top 적용 전

    save_on_top 적용 후

    Tip.

    • 기본값은 False 입니다.
  • raw_id_field

    장고는 기본적으로 FK Field에 위와 같은 셀렉트 박스(select box)를 제공합니다.

    그러나 때때로 너무 많은 선택지가 있을 경우 드랍 다운식의 셀렉트 박스는 이용하기에 불편할 수 있습니다.

    • raw_id_field는 FK, ManyToManyField에 대해 더 편리한 input 위젯을 제공합니다.

    raw_id_field 적용 방법

    # admin.py
    @admin.register(HomeWork)
    class HomeWorkAdmin(admin.ModelAdmin):
        list_display = ['class_room', 'student', 'title', 'homework_date', 'score']
        search_fields = ['id', 'title', 'class_room__name', 'student__name', 'score']
        raw_id_fields = ['class_room', 'student'] # raw_id_fields 적용

    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()함수를 이용하여 어떠한 처리를 하는 로직입니다.

    chagelist_view 적용 방법 (2)

    def changelist_view(self, request, extra_context=None):
            extra_context = {'title': '사용자 목록'}
        return super().changelist_view(request, extra_context)
  • 4. CSS, JavaScript 재정의

    CSS, JavaScript 재정의에는 2가지 방법이 존재 합니다.

      1. 관리자 템플릿 재정의 후 확장
      1. 메타 클래스를 통한 확장

템플릿 재정의 후 확장

  # 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가 존재해도 일괄적으로 적용됩니다.
      class PostAdmin(admin.ModelAdmin):
          **formfield_overrides** = {
              models.CharField: {'widget': TextInput(attrs={'size':'20'})},
              models.TextField: {'widget': Textarea(attrs={'rows':4, 'cols':40})},
          }
      • 커스텀 위젯을 설정하는 것 말고도 필드의 사이즈나 높이를 재정의 할 수도 있습니다.
    • 특정 필드에만 위젯 적용

      때에 따라 content 필드에만 커스텀 위젯을 적용하고 description 필드에는 디폴트 TextArea를 사용하고자 하는 경우도 있습니다.

      forms.py 재정의

      # forms.py 
      from django.conf import settings
      from .widgets import SimpleMDEWidget
      
      class PostAdminForm(form.ModelForm):
          class Meta:
              model = Post
              exclude = []
              options = getattr(settings, "SIMPLEMDE_OPTIONS", '')
              widgets = {
                  'content': SimpleMDEWidget(wrapper_class='simplemde-box-admin',
                  options=options),
              }
      • widget 변수 딕셔너리에 명시적으로 적용하고자 하는 필드 이름을 지정합니다.
      • exclude 또는 fields 속성으로 입력 가능 필드를 명시적으로 지정해야 합니다. 그렇지 않으면 에러가 발생합니다.

      admin.py

      # admin.py
      
      class PostAdmin(admin.ModelAdmin):
              form = PostAdminForm
      
              class Media:
                  css = {
                          'all': ('css/simplemde.css',)
                  }
      • 관리자에서 사용할 From을 커스텀한 Form으로 변경 합니다.
      • 또한, 관리자 화면에서 사용할 스타일 시트도 불러 옵니다.
  • 6. Admin 템플릿 재정의

    Admin의 목록 페이지 또는 상세 페이지에 버튼을 추가 하는 등 새로운 커스텀을 하고 싶을때가 있습니다. 그럴때 사용하는 방법이 Admin 템플릿의 재정의 입니다.

    Admin 템플릿 재정의 적용 방법

    Admin Template files 위치

    venv → Lib → django → contrib → admin → templates → admin

    • Admin 목록 페이지 재정의
    • admin/change_list.html 을 오버라이딩 해서 재정의 합니다.
    # admin_custom_list.html
    {% extends "admin/change_list.html" %}
    {% load i18n admin_urls static admin_modify %}
    {% block object-tools %}
        .....
    {% endblock %}
    • 먼저 “{% 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 상세 페이지 재정의

    • admin/chage_form.html 을 오버라이딩 해서 재정의 합니다.

    {% extends "admin/change_form.html" %}
    {% load i18n admin_urls static admin_modify %}
    {% block object-tools %}
        .....
    {% endblock %}
    {% block content %}
        .....    
    {% endblock %}

    앱 또는 모델별로 재정의될 수 있는 템플릿 목록

    • actions.html
    • app_index.html
    • chage_form.html
    • chage_form_object_tools.html
    • change_list.html
    • change_list_object_tools.html
    • change_list_results.html
    • date_hierarchy.html
    • delete_confirmation.html
    • object_history.html
    • pagination.html
    • popup_response.html
    • prepopulated_fields_js.html
    • search_form.html
    • submit_line.html
  • 7. Admin 목록 페이지 재정의 후 새로운 로직(경로) 추가

    Admin의 목록 페이지에 새로운 버튼을 추가한 후 클릭했을 때 커스텀한 form 페이지로 이동 하는 방법

    1. Admin 목록 페이지 재정의

    # payment_change_list.html
    {% extends "admin/change_list.html" %}
    {% load i18n admin_urls static admin_modify %}
    {% block object-tools %}
        <ul class="object-tools">
            <li>
                <a href="{% url 'admin:payment_payment_add' %}" class="addlink">
                    결제 추가
                </a>
            </li>
            <li>
                <a href="{% url 'admin:create_payment_in_class' %}" class="addlink">
                    반에 결제 추가
                </a>
            </li>            
        </ul>
    {% endblock %}
    • 가장 먼저 Admin 목록 페이지를 재정의 합니다.

    • 버튼을 추가할때 원하는 위치 를 지정 하고 “block” 구문을 이용하여 원하는 위치에 추가합니다.

    • {% block object-tools %} 같은 경우

      위 사진의 위치를 나타냅니다.

    • URL 설정 같은 경우 “8. Django Admin URL 설정 시 참고” 참고 해주세요.

    2. Admin 디테일 페이지 재정의

    # create_payment_in_class.html
    {% extends "admin/change_form.html" %}
    {% load i18n admin_urls static admin_modify %}
    {% block extrahead %}
        {{ block.super }}
        <script src="{% static 'admin/js/vendor/jquery/jquery.js' %}"></script>
        <script src="{% static 'admin/js/calendar.js' %}"></script>
        <script src="{% static 'admin/js/jquery.init.js' %}"></script>
        <script src="{% static 'admin/js/admin/DateTimeShortcuts.js' %}"></script>
        <script src="{% static 'admin/js/core.js' %}"></script>
        <script src="{% static 'admin/js/inlines.js' %}"></script>
        <script src="{% static 'admin/js/admin/RelatedObjectLookups.js' %}"></script>
        <script src="{% static 'admin/js/actions.js' %}"></script>
        <script src="{% static 'admin/js/urlify.js' %}"></script>
        <script src="{% static 'admin/js/prepopulate.js' %}"></script>
        <script src="{% static 'admin/js/vendor/xregexp/xregexp.js' %}"></script>
    {% endblock %} 
    # 디테일 페이지에서 사용할 admin에 정의된 js 파일을 불러옵니다. 
    
    {% block breadcrumbs %}
        ...
        # 디테일 페이지에서 사용할 브레드크럼을 정의 합니다.
    {% endblock %}
    {% block content %}
        ...
      # 디테일 페이지에서 사용할 content를 정의합니다.
    {% endblock %}

    • {% extends "admin/change_form.html" %}를 상속받은 후 원하는 페이지 화면을 재정의 합니다.

    3. 재정의 HTML을 Admin에 지정

    class PaymentAdmin(admin.ModelAdmin):
        change_list_template = 'admin/payment_change_list.html'
    • 새롭게 정의한 목록 페이지(html)을 ModelAdmin을 상속받은 class에 지정해 줍니다.

    4. get_urls 설정

    class PaymentAdmin(admin.ModelAdmin):
        change_list_template = 'admin/payment_change_list.html'
    
        def get_urls(self):
            urls = super().get_urls()
            payment_urls = [
                    path('add/class/', self.admin_site.admin_view(self.create_payment),
                            name='create_payment_in_class')
            ]
        return payment_urls + urls
    
        def create_payment(self, request):
            if reuqest.method == "POST":
                    .....
                    context = dict(
                        self.admin_site.each_context(request),
                    )
                    return TemplateResponse(request, 'admin/create_payment_in_class.html', context)
            return redirect('/admin/payment/payment/') # 어드민의 목록 페이지 URL
    • get_urls 메소드를 오버라이딩 한 후 원하는 URL 이름과 admin_view() 함수 안에 새로운 페이지에서 처리할 로직을 정의 합니다.
    • context 에 “self.admin_site.each_context(request),”를 정의하는 이유는 기존에 있는 관리자 템플릿을 사용하기 위함 입니다.
    • “return TemplateResponse” 에 정의 한 “create_payment_in_class.html” 를 새롭게 생성합니다.
    • create_payment() 함수 같은 경우 새롭게 정의한 디테일 페이지의 로직을 처리하는 함수 입니다.
  • 8. Admin 메인 페이지(index.html) 재정의

    Django 관리자 템플릿 파일은 앞서 말했다시피 contrib → admin → templates → admin 에 정의 되어 있습니다.

    • 그 중 하나 이상을 재정의하려면 먼저 “templates” 파일 안에 admin 디렉터리를 만듭니다.
    • settins.py → TEMPLATES 변수의 설정이 먼저 프로젝트에 맞게 설정 되어 있어야 합니다.
    • 옵션을 사용자 정의한 경우에 관리자에 포함된 템플릿보다 먼저 템플릿 로드 시스템에서 사용자 정의 템플릿을 찾아서 정의 합니다.

    1. TEMPLATES 변수 정의

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [
                os.path.join(BASE_DIR, 'templates'), # 중요 ! 
            ],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

    2. templates → admin 디렉터리 생성

  • admin 디렉터리를 새로 생성 후 index.html 으로 html 파일을 생성합니다.

  • 기존에 정의되어있는 것(기본 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 를 상속받은 후 원하는 곳의 커스텀을 진행하면 됩니다.

  • 4. index 페이지 상단에 버튼 및 새로운 로직 추가*

    {% extends "admin/index.html" %}
    {% load i18n admin_urls static admin_modify %}
    
    {% block content_title %}
       <div class="layout-between">
           {% if title %}
               <h1>{{ title }}</h1>
           {% endif %} # 
    
           <a class="custom-btn" href="{% url 'direct_send' %}"
              style="margin: 0 0 20px; color: #ffffff;">문자 발송</a>
       </div>
       <link rel="stylesheet" href="{% static 'css/custom_admin.css' %}">
    {% endblock %}
    
    {% block content %}
       <div id="content-main">
           <div class="module">
               <table>
                   <caption>
                       <div class="section">문자발송</div>
                   </caption>
                   <tbody>
                   <tr class="model-attachment">
                       <th scope="row">
                           <a href="{% url 'direct_send' %}">직접 문자발송</a>
                       </th>
                       <td></td>
                   </tr>
                   <tr class="model-attachment">
                       <th scope="row">
                           <a href="/admin/class_rooms/classroom/">자동 문자발송</a>
                       </th>
                       <td></td>
                   </tr>
                   </tbody>
               </table>
           </div>
           {% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
       </div>
    {% endblock %}
  • {% block content_title %} 같은 경우 관리자 페이지 상단의 제목 부분을 나타냅니다. 상단에 버튼을 추가 하기 위해 해당 부분을 override 해서 커스텀을 진행 합니다.

  • {{ title }}

    ” 같은 경우 기존에 정의되어 있는 상단의 제목으로 제목 옆에 버튼을 새롭게 생성하기 위해 override 한후 버튼을 추가해 줍니다.

  • {% block content %} 같은 경우 메인페이지의 내용 부분을 나타냅니다. 모델 별로 목록이 나타나는 곳으로 상단에 임의의 로직을 추가하기 위해 override 해서 새로운 색션을 추가해 줍니다.

  • 5. 결과*

  • 관리자 메인 페이지 제목 옆에 버튼과 내용 부분에 임의의 색션이 추가된 것을 확인할 수 있습니다.

  • 6. 커스텀 버튼에 url 설정*

    새롭게 정의한 버튼에 로직을 설정하기 위해 views.py를 설정해 줍니다.

    # config -> urls.py
    
    urlpatterns = [
       path('admin/direct/sms/', DirectSendSMSFormView.as_view(), name='direct_send'),
       path('admin/', admin.site.urls),
       ...
    ]
  • 프로젝트 루트 디렉토리 안에 있는 urls.py 에서 url을 추가해 줍니다.

  • url을 추가할때는 기존에 설정되어 있는 “path('admin/', admin.site.urls)” 위에 새롭게 추가할 admin url을 추가해 줍니다.

    • 만약 “path('admin/', admin.site.urls)” 다음에 추가 할 경우 에러 발생
    • “path('admin/', admin.site.urls)” 해당 구문으로 django admin에서 url을 정의합니다. 그전에 새롭게 추가하고 정의해야 정상적으로 작동 됩니다.
  • 7. 커스텀 버튼에 views.py 설정*

    class DirectSendSMSFormView(FormView):
       template_name = 'admin/my_custom_page.html'
       form_class = MyCustomForm
       success_url = reverse_lazy('admin:index')
    
       def post(self, request, *args, **kwargs):
           .....
    
       def get_context_data(self, **kwargs):
           context = super().get_context_data(**kwargs)
           context.update(admin.site.each_context(self.request))
           context.update(form=self.form_class)
           return context
  • View 같은 경우 원하는 기능의 코드를 작성하면 됩니다.

  • get_context_data 함수에서 context.update(admin.site.each_context(self.request)) 해당 부분은 기존의 어드민 template를 사용하기 위함 입니다.

  • context.update(form=self.form_class) 같은 경우 새로운 form을 사용하기 위함 입니다.

  • 9. Admin 페이지 사이드바 커스텀

    Admin 페이지의 사이드바에 임의의 새로운 로직을 추가하고 싶을 수 있습니다.

    사이드바에 새로운 로직을 추가 하는 방법은 다음과 같습니다.

    1. 사이드바 html 생성

    # change_nav_sidebar.html
    
    {% load i18n admin_urls static admin_modify %}
    <button class="sticky toggle-nav-sidebar" id="toggle-nav-sidebar"
            aria-label="{% translate 'Toggle navigation' %}"></button>
    <nav class="sticky" id="nav-sidebar">
        <div class="module">
            <table>
                <caption>
                    <div class="section">문자발송</div>
                </caption>
                <tbody>
                <tr class="model-attachment">
                    <th scope="row">
                        <a href="{% url 'direct_send' %}">직접 문자발송</a>
                    </th>
                    <td></td>
                </tr>
                </tbody>
            </table>
        </div>
        {% include 'admin/app_list.html' with app_list=available_apps show_changelinks=False %}
    </nav>
    • 사이드바에 새로운 색션을 임의로 추가 하는것은 기존에 정의 되어 있는 곳에 추가를 하는 것입니다. 그렇기 때문에 기존에 있는 코드를 새롭게 정의한 html에 그대로 가지고 옵니다.
    • 그 후 , 원하는 위치에 코드를 추가해 줍니다.
    • {% include 'admin/app_list.html' with app_list=available_apps show_changelinks=False %} → 같은 경우 기존에 정의되어 있는 코드 입니다.

    2. Admin template base.html 재정의

    • sidebar.html 같은 경우 base.html에 정의되어 있기 때문에 base.html을 재정의 해야 합니다.
    ....
    
    {% if not is_popup and is_nav_sidebar_enabled %}
        {% block nav-sidebar %}
          {% include "admin/change_nav_sidebar.html" %}
        {% endblock %}
    {% endif %}
    
    .....
    • 기존의 base.html 코드를 전체 복사 한 뒤 새롭게 생성한 base.html에 그대로 가져 옵니다.
    • 그 후, sidebar를 include하는 코드에 새롭게 정의한 sidebar.html으로 설정해 줍니다.

    3. 새롭게 정의한 admin 관련 html 파일 위치

    23.jpg

    4. 결과

    Untitled

  • 10. Admin 목록 페이지의 데이터 노출 개수 사용자 지정 커스텀

    Admin 목록페이지에서 원하는 데이터 노출 개수를 사용자가 직접 정의 하게 커스텀 하는 방법을 정의 합니다.

    • 목록페이지의 데이터 노출 개수의 기본값은 200 입니다.
    • list_per_page 값을 정의 하면 목록페이지의 데이터 노출 개수를 임의로 지정할 수 있습니다.

    1. list_per_page의 기본값 지정

    # settings -> base.py
    
    LIST_PER_PAGE = 20
    • settings 파일의 base.py에서 변수로 list_per_page를 설정 합니다.
    • base.py 에서 list_per_page를 정의 하는 이유는 관리에 용이하기 때문입니다.

    2. js 파일 지정 및 chagelist_view 커스텀

    class CustomBaseModelAdmin(admin.ModelAdmin):
    
        class Media:
            js = (
                "js/axios0.21.js",
                "js/admin/paginator_dropdown.js",
        )
        css = {
            'all': ('css/custom_admin.css',),
        }
    
        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)
    
        def get_list_per_selector(self, data_value: str):
            """list_per_page를 변경하는 함수 입니다."""
            if data_value:
                data: Dict = {}
                change_list_per_page = int(data_value)
                self.list_per_page = change_list_per_page
                if self.list_per_page == change_list_per_page:
                    data['is_ok'] = True
                    return JsonResponse(data=data, status=200)
                else:
                    data['detail'] = '데이터가 올바르지 않습니다.'
                    return JsonResponse(data=data, status=400)
    • 드롭박스를 생성하고 처리하는 과정을 axios를 사용하여 처리합니다.
      • axios랑 js코드를 지정해 줍니다.
    • axios로 get 요청으로 해당 로직을 구현하기 위해 chagelist_view를 오버라이딩 해서 get요청일때의 처리를 해줍니다.
    • self.list_per_page 의 값을 변경해야하기 때문에 해당 부분은 함수로 따로 빼서 작성해서 사용합니다.

    3. js 코드 구현

    window.addEventListener("load", function () {
    
        const paginator = document.querySelector(".paginator");
        const pageConfirm = document.querySelector(".this-page");
        const listDataset = document.querySelector(".action-counter");
    
        if (pageConfirm) {
            const listPerPage = `<select class="list_per_get_selector" id="list_per_get_selector" name="list_per_get_selector">
            <option value="20">20</option><option value="50">50</option><option value="100">100</option>
            <option value="200">200</option></select>`
            paginator.insertAdjacentHTML('beforeend', listPerPage);
        }
        if (listDataset) {
            resetSelected(listDataset.dataset['actionsIcnt']);
        }
    })
    
    window.onload = function () {
        const listSelector = document.getElementById("list_per_get_selector")
        if (listSelector) {
            listSelector.addEventListener("change", function (e) {
                let listPerPageSelector = document.getElementById("list_per_get_selector");
                let selectedValue = listPerPageSelector.options[listPerPageSelector.selectedIndex].value;
                let currentURL = window.location.href
                const url = `${currentURL}?list_per_get_selector=${selectedValue}`;
                const config = {
                    headers: {
                        "Content-Type": "application/json"
                    }
                }
                axios.get(url, config)
                    .then(function (response) {
                        const responseStatus = response.status
                        console.log(response)
                        switch (responseStatus) {
                            case 200:
                                window.location.reload()
                                break;
                            case 400:
                                console.log(response.data['detail'])
                            default :
                                console.log(response);
                                break;
                        }
                    })
                    .catch(function (error) {
                        console.log(error);
                    })
            })
        }
    }
    
    function resetSelected(value) {
        let listPerPageSelector = document.getElementById("list_per_get_selector");
        if (listPerPageSelector) {
            for (let i = 0; i < listPerPageSelector.children.length; i++) {
                if (listPerPageSelector[i].value === value) {
                    listPerPageSelector[i].selected = true;
                }
            }
        }
    }
    • 드롭 박스로 페이지 데이터를 커스텀 하기 위한 js 코드를 작성합니다.

    4. 커스텀한 BaseAdmin 상속

    class HallOfFameAdmin(CustomBaseModelAdmin):
        ....
        list_max_show_all = False
      list_per_page = settings.LIST_PER_PAGE
        ....
    • CusTomBaseAdmin을 상속받아 줍니다.
    • base.py 에서 지정한 list_per_page를 불러서 변수에 대입 합니다.
    • list_max_show_all = False 로 한 이유는 전체 보기 기능을 사용하지 않기 위함 입니다.
      • 사용하지 않는 이유는 데이터의 양이 1000 이상 넘어갔을때 과부화가 올 수 있기 때문입니다. 또한, “전체 보기” 같은 경우 n개 이상일때만 노출 하는 커스텀 또한 가능 합니다.

    5. 결과

  • 어드민 목록 페이지에서 데이터 개수를 사용자 지정에 맞춰 노출할 수 있는 드롭박스가 생성되고 작동하는것을 확인할 수 있습니다.

'django' 카테고리의 다른 글

Django ORM  (0) 2022.03.09
Django와 Axios를 이용한 좋아요 구현하기  (0) 2022.01.15
Django CBV 기반 CRUD 만들기  (0) 2022.01.09
Django 프로젝트 구조  (0) 2021.12.07
Django Deploy  (0) 2021.12.07