본문 바로가기

Web + APP/Django

Managers와 QuerySets의 이해

반응형
SMALL

Django에서는 DB를 SQL 문으로 직접 조작하지 않습니다. Django ORM(Object Relational Mapping)을 사용하죠.

(물론 하는 경우도 있습니다. 제가 지금 하는 업무에서는 ORM으로 하기 보다는 SQL Developer로 직접 조작을 하죠, 하지만 이 경우에도 사용자 딴에서 Data를 보여줄 땐 Django ORM이 필수적으로 사용된답니다)

예를 들어서 obj.amenities.count() / obj.amenities.all() 이런 것들이 Django ORM Query가 되겠죠.

 

콘솔로 들어가서 확인을 해볼까요?

 

User라는 클래스를 만들었다고 가정합시다.

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):  # AbstractUser를 상속하면, User id, admin 여부 등 다양한 것을 사용할 수 있다.

    """ Custom User Model """

    ...
    ...

 여기서 우리가 주목해야하는 부분은 AbstractUser에서 상속을 받았다는 겁니다. 이 점을 기억하고 아래를 보도록 하겠습니다.

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

User가 클래스인 것을 확인했죠? 그러면 여기서 사용가능한 메소드, 인자가 무엇인지 봅시다.

>>> 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_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(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')), '_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 0x042FCFB8>, 'gender': <django.db.models.query_utils.DeferredAttribute object at 0x042FCFE8>, 'get_gender_display': functools.partialmethod(<function Model._get_FIELD_display at 0x03FDDE80>, , field=<django.db.models.fields.CharField: gender>), 'bio': <django.db.models.query_utils.DeferredAttribute object at 0x042FCE98>, 'birthdate': <django.db.models.query_utils.DeferredAttribute object at 0x042FCDC0>, 'language': <django.db.models.query_utils.DeferredAttribute object at 0x04306B50>, 'get_language_display': functools.partialmethod(<function Model._get_FIELD_display at 0x03FDDE80>, , field=<django.db.models.fields.CharField: language>), 'currency': <django.db.models.query_utils.DeferredAttribute object at 0x04306BB0>, 'get_currency_display': functools.partialmethod(<function Model._get_FIELD_display at 0x03FDDE80>, , field=<django.db.models.fields.CharField: currency>), 'superhost': <django.db.models.query_utils.DeferredAttribute object at 0x04306C10>, 'get_next_by_date_joined': functools.partialmethod(<function Model._get_next_or_previous_by_FIELD at 0x03FDDEC8>, , 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 0x03FDDEC8>, , field=<django.db.models.fields.DateTimeField: date_joined>, is_next=False), 'groups': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x043100E8>, 'user_permissions': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x04310598>, 'id': <django.db.models.query_utils.DeferredAttribute object at 0x04310718>, 'logentry_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x043107F0>, 'room_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x045D67A8>, 'review_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x045DD2C8>, 'reservation_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x045DDA30>, 'list_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x045DDC28>, 'conversation_set': <django.db.models.fields.related_descriptors.ManyToManyDescriptor object at 0x045E8610>, 'message_set': <django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor object at 0x045E8B80>})

vars는 __dict__, dictionary 또는 클래스 리스트안의 나타내는 것을 리턴합니다. dir는 클래스 안의 names 리스트들을 리턴합니다.

 

뭐가 엄청 많죠? 저는 이 것을 생성하지 않았지만, AbstractUser, AbstractBasicUser를 상속받았기에 사용할 수 있습니다. 그 중 하나의 기능에 Manager가 있습니다.

 

한 번 확인해봅시다.

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

UserManager가 있죠? manager는 데이터베이스로부터 elements를 들고올 수 있게 해주는 애입니다. SQL을 안 쓰고 Python으로 Data를 가져올 수 있는 이유입니다.

 

만약 User 모두를 가지고 오고 싶다면, 이 Manager한테 말하면 됩니다. 마치 API 사용하는 거 같네요.

>>> User.objects.all()
<QuerySet [<User: ddong.han>]>

manager한테 User의 모두를 가져오라고 했습니다. 그런데 QuerySet을 리턴 받았습니다. QuerySet이 뭘까요? Object 리스트입니다. 단순한 리스트가 아니고 DataBase로부터 온 장고 Ojbects 리스트입니다. 매우 똑똑한 리스트죠.

 

그래서 이런 것도 가능합니다.

>>> all_user = User.objects.all()
>>> all_user.filter(superhost=False)
<QuerySet [<User: ddong.han>]>

확인 했듯이 리스트는 다양한 기능을 가지고 있습니다.

>>> dir(all_user)
['__and__', '__bool__', '__class__', '__deepcopy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_add_hints', '_batched_insert', '_chain', '_clone', '_combinator_query', '_create_object_from_params', '_db', '_earliest', '_extract_model_params', '_fetch_all', '_fields', '_filter_or_exclude', '_for_write', '_has_filters', '_hints', '_insert', 
'_iterable_class', '_iterator', '_known_related_objects', '_merge_known_related_objects', '_merge_sanity_check', '_next_is_sticky', '_populate_pk_values', '_prefetch_done', '_prefetch_related_lookups', '_prefetch_related_objects', '_raw_delete', '_result_cache', '_sticky_filter', '_update', '_validate_values_are_expressions', '_values', 'aggregate', 'all', 'annotate', 'as_manager', 'bulk_create', 'bulk_update', 'complex_filter', 'count', 'create', 'dates', 'datetimes', 
'db', 'defer', 'delete', 'difference', 'distinct', 'earliest', 'exclude', 'exists', 'explain', 'extra', 'filter', 'first', 'get', 'get_or_create', 'in_bulk', 
'intersection', 'iterator', 'last', 'latest', 'model', 'none', 'only', 'order_by', 'ordered', 'prefetch_related', 'query', 'raw', 'resolve_expression', 'reverse', 'select_for_update', 'select_related', 'union', 'update', 'update_or_create', 'using', 'values', 'values_list']

그쵸?

# 원하는 정보를 가지고 오고 싶을 때
>>> ddong_han = User.objects.get(username="ddong.han")
>>> print(ddong_han)
ddong.han
# 만약 없는거 가져온다면?
>>> User.objects.get(username="gg")
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\Users\HAN\.virtualenvs\airbnb-clone-azKDMdi5\lib\site-packages\django\db\models\manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "C:\Users\HAN\.virtualenvs\airbnb-clone-azKDMdi5\lib\site-packages\django\db\models\query.py", line 406, in get
    raise self.model.DoesNotExist(
users.models.User.DoesNotExist: User matching query does not exist.

보다 자세한 내용은 Django Documentation QuerySet 파트에서 볼 수 있습니다.

 

Django의 대단한 점은 user에 연결된 foreign key set도 다 가져온다는 점입니다.

>>> dir(ddong_han)
['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', '_state', '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']

dir 배열 안을 보면 'room_set', 'message_set', 'conversation_set', 'list_set' 이런 것들을 찾을 수 있습니다. 즉, user를 통해서 room 데이터에 접근할 수 있다는 점이 Django의 대단한 점입니다.

 

'room_set'으로 접근하기 싫을 땐 related_name을 포함해서 원하는 이름으로 바꿀 수 있습니다.

>>> ddong_han.room_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x04623088>
>>> ddong_han.room_set.all()
<QuerySet [<Room: Ddong.han's House>]>

즉, _set은 ddong_han 같은 element가 그들의 foreign keys에 접근할 수 있는 방법입니다. User model엔 set을 만들지 않았음에도 말이죠 !

 

대박

이걸 왜 쓸까요? 인스타그램을 생각해봅시다. 글(사진)을 model로 삼고 user를 model로 삼을게 분명한데, 사용자 프로필로가서, 사용자에 의해 생성된 모든 글(사진)을 가져오게끔 할 때 위와 같은 방법을 쓸 수 있겠죠 !


이상 Managers와 QuerySets의 이해였습니다. ^_^

반응형
LIST

'Web + APP > Django' 카테고리의 다른 글

Get, Post 요청을 모두 알아서 처리하는 FormView !  (4) 2020.05.30
Class Based View  (0) 2020.05.10
추상 모델  (0) 2020.04.29
커스텀 유저 모델  (0) 2020.04.29
Django Setting 하기 !  (0) 2020.04.18