amenities의 갯수를 확인하는 메소드로 변경
def count_aminities(self, obj):
return obj.amenities.count()
상황 : 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
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가 생성되었다.