07. Querysets

조재훈·2022년 7월 19일
0

Clone_Airbnb

목록 보기
14/31
post-thumbnail

amenities의 갯수를 확인하는 메소드로 변경

    def count_aminities(self, obj):
        return obj.amenities.count()

1) DB에서 object 가져오기

상황 : 2명의 사용자가 있다면 이 정보를 어디서 얻어오는가?
콘솔에서 장고 셸을 켜보자.

python manage.py shell

그리고 파이썬 코드로 User 클래스 내부를 살펴보자

>>> from users.models import User
>>> User
<class 'users.models.User'>

여기서 세부 속성을 보려 하는데 dir와 vars가 있다.
vars는 모듈, 클래스, 인스턴스, 또는 _dict__속성을 가진 다른 오브젝트들의 _dict__ 성분을 반환한다.

>>> vars(User)
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': 'kr', 
    'LANGUAGE_CHOICES': (('en', 'English'), ('kr', 'Korean')), 
    'CURRENCY_USD': 'usd', 
    'CURRENCY_KRW': 'krw', 
    'CURRENCY_CHOICES': (('usd', 'USD'), ('krw', 'KRW')), 
    '__str__': <function User.__str__ at 0x000002A41CFC91F0>, 
    '_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 0x000002A41CFDFD60>, 
    'gender': <django.db.models.query_utils.DeferredAttribute object at 
0x000002A41CFDFD00>, 
    'get_gender_display': functools.partialmethod(
        <function Model._get_FIELD_display at 0x000002A41C9C9B80>, , 
        field=<django.db.models.fields.CharField: gender>
        ), 
    'bio': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDFF40>, 
    'birthdate': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDFEE0>, 
    'language': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDFA90>, 
    'get_language_display': functools.partialmethod(
        <function Model._get_FIELD_display at 0x000002A41C9C9B80>, 
        , 
        field=<django.db.models.fields.CharField: language>
        ), 
    'currency': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF9D0>, 
    'get_currency_display': functools.partialmethod(
        <function Model._get_FIELD_display at 0x000002A41C9C9B80>, 
        , 
        field=<django.db.models.fields.CharField: currency>
        ), 
    'superhost': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF910>, 
    'password': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF7F0>, 
    'last_login': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF760>, 
    'is_superuser': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF6D0>, 
    'username': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF640>, 
    'first_name': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF5B0>, 
    'last_name': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF520>, 
    'email': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF490>, 
    'is_staff': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF400>, 
    'is_active': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF370>, 
    'date_joined': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFDF2E0>, 
    'get_next_by_date_joined': functools.partialmethod(
        <function Model._get_next_or_previous_by_FIELD at 0x000002A41C9C9C10>, 
        , 
        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 0x000002A41C9C9C10>, 
        , 
        field=<django.db.models.fields.DateTimeField: date_joined>, 
        is_next=False
        ), 
    'groups': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x000002A41CFE1820>, 
    'user_permissions': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x000002A41CFE14F0>, 
    'id': <django.db.models.query_utils.DeferredAttribute object at 0x000002A41CFE1AF0>, 
    'logentry_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41CFE10A0>, 
    'room_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41D967E20>, 
    'review_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41D07A760>, 
    'reservation_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41D9511C0>, 
    'list_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41D942520>, 
    'conversation_set': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x000002A41D01C7F0>, 
    'message_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x000002A41D081940>})

dir는 속성들의 이름을 리스트로 반환한다.

>>> dir(User)
['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_default_pk', '_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_expr_references', '_get_field_value_map', 
'_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', 
'_prepare_related_fields_for_save', '_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_constraints', '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_user_permissions', 
'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_constraints', 'validate_unique']

뭐가 되게 많이 나오는데 우리가 생성한 것 말고도 상속받은 클래스들에 정의되어있던 것들도 많다.

>>> User.objects
<django.contrib.auth.models.UserManager object at 0x000002A41CFE1160>

여기보면 UserManager 라는게 있는데 데이터베이스에서 elements를 가져오게 해준다. SQL없이 파이썬만을 이용해서 가능하게 해준다.
manager는 이 object안에 존재하고있고 모든 users를 얻고 싶으면 이 manager에게 알려주어야 한다.
(참조 : https://docs.djangoproject.com/en/4.0/ref/models/querysets/#django.db.models.query.QuerySet)

>>> User.objects.all()
<QuerySet [<User: jaewhoon>, <User: tomato>]>

이때 QuerySet이 등장하는데 이것은 Object 리스트를 의미한다. 이것을 활용해서 데이터를 필터링하는 등 여러가지 강력한 기능을 수행할 수 있다.

>>> tomato = User.objects.get(username="tomato")
>>> print(tomato)
tomato
>>> tomato
<User: tomato>
>>> dir(tomato)
{ ~~~~~~~~~~~~~, 'room_set', ~~~~~~]
>>> tomato.room_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x000002A41EC8C2E0>

여기 보면 create_reverse_many_to_one_manager 라는게 있다. user가 room에 ForeignKey로 연결되어있는데 이를 통해 user와 연결된 room 정보에 접근할 수 있다.
room_set 외에도 set가 붙은 것들은 elements가 해당 ForeignKey에 접근할 수 있도록 해주는 방법이다.

>>> tomato.room_set.all()
<QuerySet [<Room: LALALA HOUSE>]>

우리는 user에 어떠한 손도 대지 않았지만 장고가 set을 만들었고 우리는 이를 활용해서 ForeignKey를 user쪽으로 향하게 했다.

>>> tomato.review_set.all()
<QuerySet [<Review: Good - LALALA HOUSE>]>


만약 User를 다른 사람으로 바꾼다면?

>>> tomato.review_set.all()
<QuerySet []>

여기서 ~~_set으로 표현되는 것들은 ForeignKey 필드에 related_name라는걸 입력해서 room_set이 아닌 다른 단어로 불러낼 수 있다.
rooms - models - Room

    host = models.ForeignKey("users.User", related_name="rooms", on_delete=models.CASCADE)

콘솔

>>> from users.models import User
>>> tomato = User.objects.get(username="tomato")
>>> tomato.rooms
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x000001574CF9C700>
>>> tomato.rooms.all()
<QuerySet [<Room: LALALA HOUSE>]>

Room 클래스에 ForeignKey로 연결된 users는 User 클래스에서 참조할 때 room_set이 아닌 rooms 라는 키워드로 타고 들어갈 수 있다. migration을 해주자.

>>> from rooms.models import Room
>>> room = Room.objects.get(id=1)
>>> room.amenities.all()
<QuerySet [<Amenity: Shower>, <Amenity: Wifi>, <Amenity: Washing Machine>]>
>>> startswith = User.objects.filter(username__startswith="tom")
>>> startswith
<QuerySet [<User: tomato>]>
>>> from rooms.models import Amenity
>>> Amenity.objects.all()
<QuerySet [<Amenity: Shower>, <Amenity: Wifi>, <Amenity: Washing Machine>]>
>>> a = Amenity.objects.get(id=1)
>>> a.room_set
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x0000017EA6DB2F70>
>>> a.room_set.all()
<QuerySet [<Room: LALALA HOUSE>]>

Amenity 클래스는 현재 아무것도 없다.

그러나 amenities가 Room 클래스에 ManyToManyField로 연결이 되어있어서 Amenity에 room_set이 생성되었다.
다른 클래스들에도 related_name을 설정해준다.
rooms - models.py - Photo

class Photo(AbstractItem):
    
    """Photo Model Definition"""
    
    caption = models.CharField(max_length=80)
    file = models.ImageField()
    room = models.ForeignKey("Room", related_name="photos", on_delete=models.CASCADE)
    
    ...

rooms - models.py - Room

class Room(models_core.TimeStampedModel):
    
    """Room Model Definition"""
    
    ...
    
    host = models.ForeignKey("users.User", related_name="rooms", on_delete=models.CASCADE)
    room_type = models.ForeignKey("RoomType", related_name="rooms", on_delete=models.SET_NULL, null=True)
    amenities = models.ManyToManyField("Amenity", related_name="rooms", blank=True)
    facilities = models.ManyToManyField("Facility", related_name="rooms", blank=True)
    house_rules = models.ManyToManyField("HouseRule", related_name="rooms", blank=True)
    
    ...

reservations - models.py - Reservation

class Reservation(models_core.TimeStampedModel):
    
    """Reservation Model Definition"""
    
    ...
    
    guest = models.ForeignKey("users.User", related_name="reservations", on_delete=models.CASCADE)
    room = models.ForeignKey("rooms.Room", related_name="reservations", on_delete=models.CASCADE)
    
    ...

reviews - models.py - Review

class Review(models_core.TimeStampedModel):
    
    """Review Model Definition"""
    
    ...
    
    user = models.ForeignKey("users.User", related_name="reviews", on_delete=models.CASCADE)
    room = models.ForeignKey("rooms.Room", related_name="reviews", on_delete=models.CASCADE)
    
    ...

그리고 migration

2) Room Admin 설정

Room 인스턴스를 하나 생성한다.

파일목록에 업로드한 사진이 자동으로 추가되었다.

Admin 패널에서 Room 인스턴스에 등록된 사진 갯수를 세는 메소드를 추가해주자.
rooms - admin.py - RoomAdmin

    list_display = ("count_photos")
    
    def count_photos(self, obj):
        return obj.photos.count()

rooms - admin.py - ItemAdmin

@admin.register(models.RoomType, models.Amenity, models.Facility, models.HouseRule)
class ItemAdmin(admin.ModelAdmin):
    
    """Item Admin Definition"""
    
    list_display = ("name", "used_by")
    
    def used_by(self, obj):
    return obj.rooms.count()

ItemAdmin 클래스의 적용을 받는 모든 클래스들의 admin 화면에 used_by가 생성되었다.



profile
맨땅에 헤딩. 인생은 실전.

0개의 댓글