ForeignKeyManyToManyField 그리고 OneToOneField 들을 제외한,

각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다.

 

verbose name 이 주어지지 않았을때는,

Django 는 자동으로 field 의 속성명의 언더스코어를 공백으로 전환하여 verbose name 을 생성합니다.

 

verbose name 은

사용자가 사용하는 자세한 이름을 의미합니다

 

verbose name 을 지정하지 않으면,

어드민에 표시되는 이름이 이상하다는것을 느낄수 있을겁니다.


아래 예시에서, verbose name 은 “person’s first name” 입니다

first_name = models.CharField("person's first name", max_length=30)​
 
 
아래 예시에서는, verbose name 이 “first_name” 입니다.

필드 타입의 첫번째 인자로, verbose name 이 명시되지 않았기 때문에,

필드명 first_name 에서, 언더스코어를 공백으로 전환하여 verbose name 을 표시합니다.

first_name = models.CharField(max_length=30)​
 
verbose_name 을 정의한 모델의 예시입니다.
class Members(models.Model):
    name = models.CharField(max_length=50, verbose_name='이름')
    nick_name = models.CharField(max_length=50, verbose_name="닉네임")
    birth_date = models.DateField(verbose_name="생년월일", null=True)

 

위 Members 모델에서는, name, nick_name, birth_date 이

각각 한글로 표시된 verbose_name 들을 어드민에 표시할것입니다.


1. 외래키 모델들

아직 살펴보지는 않았지만, ForeignKeyManyToManyField 그리고 OneToOneField 들은

첫번째 인자로 모델 클래스가 옵니다.

 

따라서, verbose_name을 키워드 인자로 사용해줍니다.

poll = models.ForeignKey(Poll, on_delete=models.CASCADE, verbose_name="the related poll")
# poll 은 ForeignKey (외래키) 로 Poll 이라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

sites = models.ManyToManyField(Site, verbose_name="list of sites")
# sites 는 ManyToManyField 로 Site 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌 

place = models.OneToOneField(Place, on_delete=models.CASCADE, verbose_name='related place')
# place 는 OneToOneField 로 Place 라는 클래스를 첫번째 인자로 받음 
# verbose_name 은 키워드 인자로 넣어줌​

 

위의 예시에서는, 단지 ForeignKeyManyToManyField 그리고 OneToOneField 를 사용할때에,

verbose_name 이 어떻게 키워드 인자로 들어가는지에 대한 코딩 스타일만 보여주고 있습니다.

 

관례적으로,

verbose_name 의 첫번째 글자는 대문자로 써주지 않습니다.

Django 가 필요할때 자동으로 첫번째 글자를 대문자화 해줍니다.


2. 마치며..

verbose_name 에 대해서 알아보았습니다.
verbose_name 을 사용하면, 어떤것이 달라지는지 완벽한 예시가 있으면 좋았을텐데,

현재까지는, verbose_name 을 사용할경우,

해당 필드가 어드민에 표시되는 바가 달라지게 된다는점을 알고 넘어가면 될것 같습니다.

 

지난 포스팅에서 Runner 모델을 작성했었는데,

아래와 같이, name 필드에 verbose_name 을 추가해주었습니다. “러너 이름”

 

class Runner(models.Model):
    MedalType = models.TextChoices('MedalType', 'GOLD SILVER BRONZE')
    name = models.CharField("러너이름", max_length=60)
    medal = models.CharField(blank=True, choices=MedalType.choices, max_length=10)
    
    def __str__(self):
        return self.name + " " + self.medal​

 

위와같이 verbose_name 을 모델 필드에 추가하면,

래처럼 어드민에 “러너이름” 이라고 표시가 됩니다.

 


1. 정참조와 역참조 객체 서로 호출하기

데이터베이스에서 두 테이블이 참조 관계에 있는 경우를 생각해보자.

예를 들어, User 테이블과 사용자의 직업인 Occupation 테이블이 있다.

 

두 테이블은 N:1 관계에 있으며, User 객체가 Occupation 객체를 참조하고 있다. 

User 가 Occupation 을 선택하여 입사 원서를 작성한다고 가정해보자.

class User(models.Model):
    name	= models.CharField(max_length = 50)
    job		= models.ForeignKey('Occupation', on_delete = models.CASCADE)
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

User 객체는 Occupation 객체를 정참조 하고 있으므로,

속성 이름으로 바로 접근 할 수 있다. User1을 선택하여, 그 사람의 job을 찾아보자.

user1 = User.objects.get(id = 1)
user1.job.name
>>> 'Developer'

 

그러나 Occupation 객체는 User 객체를 역참조 하고 있으므로 바로 접근이 불가능하다. 

developer 이라는 Occupation을 가지고 있는 유저를 모두 찾아보자.

job1   = Occupation.objects.get(name = 'developer')
people = job1.user.all() # 이게 될까?

 

❌ 안 됨 ❌

Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'Occupation' object has no attribute 'user'

 

그렇다고 절대로 사용하지 못하는 것은 절대 아니니 걱정하지말자.

역참조 관계에 있을 때는 [classname]_set 이라는 속성을 사용하여 접근해야한다.

job1   = Occupation.objects.get(id = 1)
people = job1.user_set.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

이 때, user_set 대신 사용할 수 있는 것이 related_name이다. 역참조 대상인 user 객체를 부를 이름.

 

즉, User 클래스를 정의할 때,

정참조 하고 있는 Occupation 클래스의 인스턴스에서 어떤 명칭으로 거꾸로 호출당할 지 정해주는 이름인 것이다.


2. What related names do

앞의 예시를 다시 보자.

아무래도 Occupation의 입장에서는 입사 지원자들을 appliers라고 부르는 것이 더 직관적이고 편할 것 같다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    created_at	= models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

[Key Point] 를 눈여겨 보자.
User객체를 정의할 때, job이라는 속성에 Occupation객체가 연결되어 정참조하고 있다.

 

Occupation객체의 인스턴스와 연결되어 있는 User 객체를 거꾸로 불러올 때, 

appliers 라는 이름으로 부르기 위해 job 속성에 related_names = 'appliers'를 함께 지정해주었다.

job1   = Occupation.objects.get(id = 1)
people = job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>, <Object User Object(2)>]>

 

잘 동작하는 것을 알 수 있다.

모든 Foreign Key에 related_name을 붙여줄 필요는 없다.

때에 따라, 참조하고 있는 객체 이름에 _set을 붙이는 것이 더 직관적인 경우가 굉장히 많기 때문이다.


3. Related name이 필수인 경우가 있다.

바로 한 클래스에서 서로 다른 두 컬럼(속성)이 같은 테이블(클래스)를 참조하는 경우이다.
앞서 설명한 상황에서, 지원자가 필수로 신청한 occupation외에, 2지망인 occupation도 받는다고 가정해보자.

class User(models.Model):
    name       = models.CharField(max_length = 50)
    job	       = models.ForeignKey('Occupation', on_delete = models.CASCADE)
[*] choice_2nd = models.ForeignKey('Occupation', on_delete = models.CASCADE, null = True)
    created_at = models.DateTimeField(auto_now_add = True)
    	
class Occupation(models.Model):
    name = models.CharField(max_length = 50)

 

참고로 위와 같은 선언은 애초에 마이그레이션이 되지 않는다.

related_name 지정하라는 문구만 뜸.

 

User객체에서 Occupation객체를 정참조 하는 속성이 두 개이다.

다시 말해 developer이라는 Occupations객체의 인스턴스를 1지망으로 선택한 지원자와 2지망으로 선택한 지원자가

따로 구별되어있다는 뜻이 된다. 아래 두 인스턴스를 보자.

user1 = User.objects.create(name = 'Nick', job_id = 1) #developer
user2 = User.objects.create(name = 'Sue', job_id = 2, choice_2nd_id = 1)

user1은 1지망은 job으로 id가 1번인 developer이다.
user2의 1지망은 2번 job이고, 2지망이 developer이다.

job1 = Occupation.objects.get(id = 1)
job1.user_set.all()

의 결과가 생성될 수 있을까?
❌ 안 됨 ❌

 

Occupation객체를 정참조 하고 있는 컬럼이 job과choice_2nd두 개이므로,

그저 user_set이라는 속성만으로는 자신을 바라보고 있는 두 User 객체 가운데

어떤 속성에 접근해야할 지 알 수가 없기 때문이다.

 

즉, developer을

1지망으로 고른 사람들의 목록(Nick)을 가져와야할 지,

2지망으로 고른 사람들의 목록(Sue)을 가져와야할 지 알 수가 없기 때문이다.

class User(models.Model):
    name = models.CharField(max_length = 50)
    job	 = models.ForeignKey( 
            'Occupation',
            on_delete    = models.CASCADE,
            related_name = 'appliers' ------------------------- [Key Point !]
        )
    choice_2nd  = models.ForeignKey(
            'Occupation',
            on_delete    = models.CASCADE,
            null         = True
            related_name = 'second_appliers'
        )
    created_at	= models.DateTimeField(auto_now_add = True)

 

이제는 developer을 1지망으로 지원한 Nick과 2지망으로 지원한 Sue를 구분하여 호출할 수 있다.

job1 = Occupation.objects.get(id = 1)

job1.appliers.all()
>>> <QuerySet[<Object User Object(1)>]> # ---> Nick

job1.second_appliers.all()
>>> <QuerySet[<Object User Object(2)>]> # ---> Sue

ManyToMany 관계에 있을 때에도 related_name은 같은 원리로 동작한다. 헷갈리지 않게 주의하자.

 


4. 마치며

웹의 구조나 서비스가 복잡해질 수록, 클래스 사이의 참조가 많아진다.

일대다는 물론이고, 다대다 관계도 계속 늘어난다.

그럴 때일 수록 related name이 중요하다고 생각한다.

 

어떻게든 migration만 되면 되지. 라는 생각으로 related_name을 마음대로 설정하다 보면,

나중엔 변수 이름을 아무렇게나 정했을 때만큼이나 의미를 알수 없는 코드를 양산하게 되기 때문이다.

오늘도 많이 배웠다.

 


출처

https://djangojeng-e.github.io/2020/08/02/Django-Models-6%ED%8E%B8-Fields-verbose-field-names/

 

Django Models 6편 - Fields (verbose field names)

Models - Fields (verbose field names) ForeignKey, ManyToManyField 그리고 OneToOneField 들을 제외한, 각 필드 타입은 선택적으로 첫번째 위치인자로, verbose name 을 받습니다. verbose name 이 주어지지 않았을때는, Django

djangojeng-e.github.io

https://velog.io/@brighten_the_way/Django%EC%99%80-Reverse-relations%EA%B3%BC-Relatedname

 

Django와 Reverse relations과 Related_name

그래서 결국, 너의 이름은

velog.io

 

+ Recent posts