본문 바로가기
개발/AWS

Django 앱 만들기 - admin 패널 꾸미기, QuerySet, UserManager

by ny0011 2021. 1. 21.
반응형

django.contrib의 admin.ModelAdmin을 상속 받아서 사용할 수 있는 변수들을 알아보자

 

* list_display

admin 패널에 보여줄 변수 적기

 

* list_filter

필터링 기준이 될 변수 적기

 

* search_fields

admin 패널에 search 바를 만듦. 검색할 필드를 지정할 수 있음

필드 이름 앞에 prefix를 지정해줄 수 있는데 아무것도 없으면 대소문자 구분없이 검색어를 포함하는 내용으로 찾아줌

= : 완전히 같아야 함(대소문자 구분 안함)

^ : 이 단어로 시작해야 함

 

* filter_horizontal

ManyToManyField 관계에 있는 것들을 검색할 수 있는 필터를 만듦

RoomAdmin 패널에서 Room을 추가할 때 amenities 등 ManyToManyField 관계인 것들은 filter가 추가됨

 

 

꿀팁) foreignkey변수를 타고 다른 모델의 변수를 가져오고 싶을 때 : foreignkey__변수이름 

_를 두번 써주자

from django.contrib import admin
from . import models

@admin.register(models.Room)
class RoomAdmin(admin.ModelAdmin):

    """ Room Admin Definition """

    list_display = (
        "name",
        "country",
        "city",
        "price",
        "address",
        "guest",
        "beds",
        "bedrooms",
        "baths",
        "check_in",
        "check_out",
        "instant_book",
    )
    list_filter = (
        "instant_book", "city", "country"
    )
    search_fields = ("=city", "^host__username")
    filter_horizontal = ("amenities", "facilities", "house_rules")

 

* fieldsets

admin 패널의 add, change 페이지를 변경할 수 있음

two-tuple로 감싸주는데 (name, field_option) 이렇게 구성되어야 함.

- name : fieldset의 제목

- field_options : dictionary 타입으로 fieldset에 들어가는 field를 정의함

   - fields : field 이름

   - classes : CSS 스타일. collapse(페이지 접어두기), wide(수평으로 여분의 공간 더 주기)옵션 등을 줄 수 있다

 

    fieldsets = (
        (
            "Basic Info",
            {"fields": ("name", "description", "country", "address", "price")},
        ),
        ("Times", {"fields": ("check_in", "check_out", "instant_book")}),
        ("Spaces", {"fields": ("guests", "beds", "bedrooms", "baths")}),
        (
            "More About the Space",
            {
                "classes": ("collapse",),
                "fields": ("amenities", "facilities", "house_rules"),
            },
        ),
        ("Last Details", {"fields": ("host",)}),
    )

 

* admin에서 함수 만들기

list_display에 ManyToManyField 변수를 넣으면 에러가 난다

django.core.management.base.SystemCheckError: SystemCheckError: System check identified some issues:

ERRORS:
<class 'rooms.admin.RoomAdmin'>: (admin.E109) The value of 'list_display[12]' must not be a ManyToManyField.

-> ManyToManyField를 다른 형식으로 만들어서 넣어주기!

-> admin에 함수를 만들어보자

list_display에 함수 이름을 적으면 된다. admin 패널은 얘가 함수인 걸 알아서 ordering을 지원하지 않음

<함수이름>.short_description 을 정의하면 admin 패널의 제목을 함수이름이 아니라 다른 걸로 변경 가능

class RoomAdmin(admin.ModelAdmin):
	list_display = (
        "count_amenities",
    )
    def count_amenities(self, obj):
        return obj.amenities.count()

    count_amenities.short_description = "amenities"


class ItemAdmin(admin.ModelAdmin):

    """ Item Admin Definition """
    list_display = (
        "name",
        "used_by",
    )

    def used_by(self, obj):
        return obj.rooms.count()

- admin에서 함수를 만들면 

첫번째 인자 : self : 이 함수가 있는 admin class

두번째 인자 : obj : 해당 admin class의 Model class

used_by에서 obj.rooms의 rooms는 room_set임

 

* QuerySet API : django.db.models.Model을 상속받았으면 QuerySet API들이 내장돼있다

- QuerySet : DB로부터 온 Object의 리스트.

all() 함수 : 현재 QuerySet의 복사본을 리턴함

 

    def count_amenities(self, obj):
        print(obj)

# obj(room)의 __str__함수가 리턴됨
testaaa
test_ubuntu

    def count_amenities(self, obj):
        print(obj.amenities)

# amenities는 __str__가 없으니 이렇게 나옴
rooms.Amenity.None
rooms.Amenity.None


    def count_amenities(self, obj):
        print(obj.amenities.all())
        
# 현재 object의 값을 보여줌
<QuerySet [<Amenity: Shower>]>
<QuerySet [<Amenity: Wifi>]>

 

- Django의 설정을 갖고 python에서 이것저것 해보고 싶을 때

python manage.py shell

- dir() : 인자 없을 때는 현재 local scope의 이름 출력, 인자가 있으면 그 인자의 객체가 갖고 있는 변수와 메소드 확인

['CURRENCY_CHOICES', 'CURRENCY_KRW', 'CURRENCY_USD', 'DoesNotExist', 'EMAIL_FIELD', 'GENDER_CHOICES', 
'GENDER_FEMALE', 'GENDER_MALE', 'GENDER_OTHER', 'LANGUAGE_CHOICES', 'LANGUAGE_ENGLISH', 
'LANGUAGE_KOREAN', 'Meta', 'MultipleObjectsReturned', 'REQUIRED_FIELDS', 'USERNAME_FIELD', 
'__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', 
'__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', 
'__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 
'_check_column_name_clashes', '_check_constraints', '_check_field_name_clashes', '_check_fields'
, '_check_id_field', '_check_index_together', '_check_indexes', '_check_local_fields', 
'_check_long_column_names', '_check_m2m_through_same_relationship', '_check_managers', 
'_check_model', '_check_model_name_db_lookup_clashes', '_check_ordering', 
'_check_property_name_related_field_accessor_clashes', '_check_single_primary_key',
'_check_swappable', '_check_unique_together', '_do_insert', '_do_update', '_get_FIELD_display', 
'_get_next_or_previous_by_FIELD', '_get_next_or_previous_in_order', '_get_pk_val',
'_get_unique_checks', '_meta', '_password', '_perform_date_checks', '_perform_unique_checks',
'_save_parents', '_save_table', '_set_pk_val', 'avatar', 'bio', 'birthdate', 'check',
'check_password', 'clean', 'clean_fields', 'conversation_set', 'currency', 'date_error_message',
'date_joined', 'delete', 'email', 'email_user', 'first_name', 'from_db', 'full_clean', 'gender',
'get_all_permissions', 'get_currency_display', 'get_deferred_fields', 'get_email_field_name', 
'get_full_name', 'get_gender_display', 'get_group_permissions', 'get_language_display', 
'get_next_by_date_joined', 'get_previous_by_date_joined', 'get_session_auth_hash', 
'get_short_name', 'get_username', 'groups', 'has_module_perms', 'has_perm', 'has_perms', 
'has_usable_password', 'id', 'is_active', 'is_anonymous', 'is_authenticated', 'is_staff', 
'is_superuser', 'language', 'last_login', 'last_name', 'list_set', 'logentry_set', 'message_set'
, 'natural_key', 'normalize_username', 'objects', 'password', 'pk', 'prepare_database_save', 
'refresh_from_db', 'reservation_set', 'review_set', 'room_set', 'save', 'save_base', 
'serializable_value', 'set_password', 'set_unusable_password', 'superhost', 
'unique_error_message', 'user_permissions', 'username', 'username_validator', 'validate_unique']

- vars() : __dict__ 를 리턴. dict타입으로 변수 이름과 변수에 할당된 값을 확인할 수 있음

짱 많당...

mappingproxy(
{'__module__': 'users.models', '__doc__': ' Custom User Model ', 
'GENDER_MALE': 'male', 'GENDER_FEMALE': 'female', 'GENDER_OTHER': 'other', 
'GENDER_CHOICES': (('male', 'Male'), ('female', 'Female'), ('other', 'Other')), 
'LANGUAGE_ENGLISH': 'en', 'LANGUAGE_KOREAN': 'ko', 
'LANGUAGE_CHOICES': (('en', 'English'), ('ko', 'Korean')), 
'CURRENCY_USD': 'usd', 'CURRENCY_KRW': 'krw', 
'CURRENCY_CHOICES': (('usd', 'USD'), ('krw', 'KRW')), 
'_meta': <Options for User>, 'DoesNotExist': <class 'users.models.User.DoesNotExist'>, 
'MultipleObjectsReturned': <class 'users.models.User.MultipleObjectsReturned'>, 
'avatar': <django.db.models.fields.files.ImageFileDescriptor object at 0x7fe5fe439be0>, 
'gender': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe439d60>, 
'get_gender_display': 
functools.partialmethod(<function Model._get_FIELD_display at 0x7fe5fdd9eaf0>, , field=<django.db.models.fields.CharField: gender>), 
'bio': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe439a00>, 
'birthdate': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe4399a0>,
'language': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe439940>, 
'get_language_display': 
functools.partialmethod(<function Model._get_FIELD_display at 0x7fe5fdd9eaf0>, , field=<django.db.models.fields.CharField: language>), 
'currency': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe439880>, 
'get_currency_display': 
functools.partialmethod(<function Model._get_FIELD_display at 0x7fe5fdd9eaf0>, , field=<django.db.models.fields.CharField: currency>), 
'superhost': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe4397c0>, 
'get_next_by_date_joined': 
functools.partialmethod(<function Model._get_next_or_previous_by_FIELD at 0x7fe5fdd9eb80>, , field=<django.db.models.fields.DateTimeField: date_joined>, is_next=True), 
'get_previous_by_date_joined': 
functools.partialmethod(<function Model._get_next_or_previous_by_FIELD at 0x7fe5fdd9eb80>, , field=<django.db.models.fields.DateTimeField: date_joined>, is_next=False), 
'groups': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x7fe5fe4349a0>,
'user_permissions': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x7fe5fe434b50>, 
'id': <django.db.models.query_utils.DeferredAttribute object at 0x7fe5fe443130>,
'logentry_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fe443310>, 
'room_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fd324520>, 
'review_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fd31ed60>, 
'reservation_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fd324910>, 
'list_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fd31ef40>, 
'conversation_set': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x7fe5fd3245b0>, 
'message_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x7fe5fd319280>})

 

이중에 User.objects를 찍어보면 UserManager 타입인 것을 알 수 있다

-> DB에 서 데이터를 가져올 수 있음. 

data model을 만들면 DB에서 CURD를 할 수 있게 Django가 자동으로 database-abstraction API를 준다!

-> 그 API가 QuerySet인듯. python에서 object를 다루는 방법을 위 링크에서 알려줌

 

- all()에서 리턴한 QuerySet을 저장해두고 filter 등 함수를 사용해서 가공할 수 있다

>>> User.objects
<django.contrib.auth.models.UserManager object at 0x7fe5fe443250>
>>> all_user = User.objects.all()
>>> all_user
<QuerySet [<User: ubuntu>, <User: test111>]>
>>> all_user.filter(superhost=True)
<QuerySet [<User: ubuntu>]>

dir(all_user)를 해보면 _set이라고 붙은 게 있다

review_set, list_set, message_set

-> user를 foreignkey, ManyToManyFields 로 지정해둔 model이 있으면

user도 이 key들을 통해서 model(review, list, message)에 접근 가능

>>> ubu = User.objects.get(username="ubuntu")
>>> ubu.room_set.all()
<QuerySet [<Room: test_ubuntu>]>

-> 기본으로 class이름_set으로 되는데 변경하고 싶으면 foreignkey 등록한 변수에 related_name으로 지정가능

    host = models.ForeignKey(
        "users.User", related_name="rooms", on_delete=models.CASCADE)
        
>>> ubu.rooms
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x7fc100100850>

이렇게 하면 user가 room_set으로 찾지 않고 rooms로 찾게 됨

- models.py가 변경 되었으니 migrate 해주고 python 다시 시작하면 검색 됨.

 

object에서 필드 변수값으로 찾을 수 있지만 id나 pk(primary key)로 찾을 수 있다

Django에서는 id와 pk가 같음

filter로 데이터를 찾을 수 있고 변수 옵션으로 startswith, ~로 시작하는 변수를 찾을 수 있음

>>> from rooms.models import Room
>>> room = Room.objects.get(id=1)
>>> room = Room.objects.get(pk=1)
>>> room.review_set.all()
<QuerySet [<Review: tttt - test_ubuntu>]>
>>> room.amenities.all()
<QuerySet [<Amenity: Wifi>]>

>>> startswith=User.objects.filter(username__startswith="ubu")
>>> startswith
<QuerySet [<User: ubuntu>]>

 

더 알아보기

Django ORM : Object Relational Mapping

django-orm-cookbook-ko.readthedocs.io/en/latest/

chrisjune-13837.medium.com/django-%EB%8B%B9%EC%8B%A0%EC%9D%B4-%EB%AA%B0%EB%9E%90%EB%8D%98-orm-%EA%B8%B0%EC%B4%88%EC%99%80-%EC%8B%AC%ED%99%94-592a6017b5f5

tutorial.djangogirls.org/ko/django_orm/

docs.djangoproject.com/en/3.1/ref/contrib/admin/

댓글