개발자 블로그

[django] 오늘의 공부 정리 0610 본문

Django/오늘의 공부

[django] 오늘의 공부 정리 0610

hayongwoon 2022. 6. 10. 22:13

1. settings.py에 비밀번호나 깃허브에 노출이 되면 안되는 것들 관리는 환경변수로 설정하면 좋다. 파일을 따로 만들어 깃이그노어하는 경우도 있긴한데 리눅스는 보편적으로 환경변수에다 설정을 하는 방법으로 한다고 한다. -> 추후에 리눅스 환경에서 적용해보자! 

 

2. 장고의 middleware 사용자와 서버가 통신을 주고받을때 이 레이어를 거쳐서 정보를 주고 받는다. 이 미들웨어의 다양한 기능들이 있어 데이터의 정보를 주고 받을 해당 웨어를 거쳐 정보가 전달된다고 한다. 

 

3. Model.object.get 과 filter의 차이 

get은 객체 반환(1개), filter는 객체의 집단 리스트의 형태로 반환 0개 이상의 객체들을 리스트로 반환. get의 경우 다수 또는 객체가 없는 것을 쿼리를 보내면 에러가 뜬다.

 

4. 데이터 추가, 조회, 삭제, 수정하기

# 추가1
model = Model(
	field1="value1",
  field2="value2"
)
model.save()

# 추가2
Model.object.create(
  field1="value1",
  field2="value2"
)

# 조회
Model.object.all()

# 수정1
model = Model.object.get(id=obj_id)
model.field = value
model.save()

# 수정2
Model.object.filter(field__contains=value).update(
    field1="value1",
    field2="value2"
)

# 삭제
Model.object.filter(field="value").delete()

 

5. 리스폰스할 때, 상태 코드를 함께 반환하는데, 에러를 핸들링하는 방법!

 5-1) status=400 일반적인 사용

 5-2) status= status.HTTP_400_BAD_REQUEST~~ -> DRF에서 임포트해서 지원함 가독성이 좋고 관리하기 편함

 

6. 자주 사용하는 패턴 모음

# objects.get에서 객체가 존재하지 않을 경우 DoesNotExist Exception 발생
try:
    Model.objects.get(id=obj_id)
except Model.DoesNotExist:
    # some event
    return Response("존재하지 않는 오브젝트입니다.")

# -join_date처럼 "-"를 붙이면 역순으로 정렬
# .order_by("?")사용시 무작위 셔플
Model.objects.all().order_by("join_date") 

# queryset에서 첫번째 object를 가져옴. all()[0]과 동일
Model.objects.all().first()

# 입력한 object가 존재 할 경우 해당 object를 가져오고, 존재하지 않을 경우 새로 생성
# model 변수에는 생성된거나 기존에 있던 객체가 담겨지고, created에는 bool값 기존 객체가 없으면 false 있으면 true
model, created = Model.objects.get_or_create(
    field1="value1",
    field2="value2",
)

if created: # created event
else: # already exist event

 

7. 회원 탈퇴기능은 디비에서 계정을 삭제하는 것보다 is_active를 false로 비활성화하는 거임. 그럼 탈퇴한 회원이 다시 로그인할 때 서버는 디비에서 찾아 is_active false인 것을 확인하고 '탈퇴한 회원입니다' 같은 문구를 말함! 또한, 유저를 참조한 테이블이 엄청 많아서 후처리를 해줘야하는데 이게 꽤 위험한 작업이다. 때문에 이런 문제로 유저를 디비에서 삭제를 하기보단 is_active를 false로 한다.

 

8. 커스텀 유저 생성

  • 일반 user model은 필드가 고정되어 있어 커스텀이 어려움
  • custom user model 생성 시 필드들을 자유롭게 커스텀 가능
  • AbstractBaseUser과 AbstractUser의 상속 차이 : 더 기능이 추가 된, 때문에 커스텀하기 어려워짐 -> 기능 타고들어가서 봐보자.
# user.models.py
# custom user model 사용 시 UserManager 클래스와 create_user, create_superuser 함수가 정의되어 있어야 함
class UserManager(BaseUserManager):
    def create_user(self, username, password=None):
        if not username:
            raise ValueError('Users must have an username')
        user = self.model(
            username=username,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user
    
    # python manage.py createsuperuser 사용 시 해당 함수가 사용됨
    def create_superuser(self, username, password):
        user = self.create_user(
            username=username,
            password=password
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class User(AbstractBaseUser):
    username = models.CharField("사용자 계정", max_length=20, unique=True)
    email = models.EmailField("이메일 주소", max_length=100)
    password = models.CharField("비밀번호", max_length=128
    fullname = models.CharField("이름", max_length=20)
    join_date = models.DateTimeField("가입일", auto_now_add=True)

		# is_active가 False일 경우 계정이 비활성화됨
    is_active = models.BooleanField(default=True) 

    # is_staff에서 해당 값 사용
    is_admin = models.BooleanField(default=False)
    
    # id로 사용 할 필드 지정.
    # 로그인 시 USERNAME_FIELD에 설정 된 필드와 password가 사용된다.
    USERNAME_FIELD = 'username'

    # user를 생성할 때 입력받은 필드 지정
    REQUIRED_FIELDS = []
    
    objects = UserManager() # custom user 생성 시 필요
    
    def __str__(self):
        return self.username

    # 로그인 사용자의 특정 테이블의 crud 권한을 설정, perm table의 crud 권한이 들어간다.
    # admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
    def has_perm(self, perm, obj=None):
        return True
    
    # 로그인 사용자의 특정 app에 접근 가능 여부를 설정, app_label에는 app 이름이 들어간다.
    # admin일 경우 항상 True, 비활성 사용자(is_active=False)의 경우 항상 False
    def has_module_perms(self, app_label): 
        return True
    
    # admin 권한 설정
    @property
    def is_staff(self): 
        return self.is_admin

 

 

10. 역참조

해당 테이블에 누가 나를 참조하고 있는지, 나를 참조하고 있는 테이블에 접근하는 것!

#views.py
def get(self, request):
        user = User.objects.get(id=5)
        hobbys = list(user.userprofile.hobby.all())
        for hobby in hobbys:
            # exclde : 매칭 된 쿼리만 제외, filter와 반대
            # annotate : 필드 이름을 변경해주기 위해 사용, 이외에도 원하는 필드를 추가하는 등 다양하게 활용 가능
            # values / values_list : 지정한 필드만 리턴 할 수 있음. values는 dict로 return, values_list는 tuple로 ruturn
            # F() : 객체에 해당되는 쿼리를 생성함
            hobby_members = hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)
            print(str(hobby_members.query))
            # print(hobby.userprofile_set)
            # print(hobby.userprofile_set.all())
            # print(hobby.userprofile_set.exclude(user=user))
            # print(hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')))
            # print(hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True))
            # print(f"hobby : {hobby.name} / hobby members : {hobby_members}")

        return Response({'message': 'get method!!'})

hobby_members = hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)

이 코드를 뜯어보자!

 

**해당 유저의 하비의 갯수 만큼 쿼리셋이 반환되겠지만 하나의 결과만 보고 비교해보자!

 

10-1) hobby.userprofile_set.all()

파이썬 내장 함수인 dir()로 hobby와 연관된 메소드나 변수를 참조할 수 있는데, hobby테이블과 다대다로 참조된 userprofile 테이블은 쿼리셋을 불러올 때 hobby.userprofile'_set'이 붙는 것이다. 반면 참조하고 있는 테이블이 1개인 일대일의 경우는 그냥 '_set'을 안 붙임

 

그리고 .all()을 안 붙일 때와 붙일 때의 print된 결과값이다.

all() 유무 차이

10-2) hobby.userprofile_set.exclude(user=user)

위랑 비교해보면 exclued(user=user) -> 조건에 해당하는 유저 즉, 자기 자신을 제외한 모든 객체를 반환!

 

10-3) hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username'))

annotate 사실 이 부분은 없어도 된다. 하지만 default로 hobby로 역참조된 유저의 네임을 갖고오기 위해선 user__username으로 필드네임이 붙는다. 이러한 부분의 필드 이름을 as로 다시 붙여준거라 생각하면 된다. 

 

추가 설명- annotate는 주석을 달다라는 영어인데, 장고에서는 필드와 annotate(메소드) 메소드를 반환한 값이 딕셔너리 형태로 반환된다. 참고: annotate의 사전적 의미는 '주석을 달다'인데요. Django에서는 주석 대신 '필드'를 추가한다고 생각하시면 되겠습니다. (엑셀이라면 컬럼을 추가하는 셈이겠고요.) https://docs.djangoproject.com/en/4.0/topics/db/aggregation/

 

참고로 이부분은 위 10-2 사진과 결과는 동일하다. annotate 특성상 사용자의 편의상 필드네임을 수정한 것 뿐이기 때문!

 

10-4) hobby.userprofile_set.exclude(user=user).annotate(username=F('user__username')).values_list('username', flat=True)

value_list('username', flat =True) -> 유저네임에 해당하는 필드의 정보만 가져와라! 실제 쿼리문을 보면 SELECT 'USER.USER'.'USERNAME' AS 'USERNAME' FROM ~~~ 에 해당하는 부분이다. 

flat은 디폴트가 False인데, 결과가 튜플형태로 나온다. Treu이면 스트링으로 나오는 것을 볼 수 있다.

 

실제 쿼리문과 결과값

사진이 작아서 잘 안보이지만 프린트로 찍어보면 해당 부분들이 어떤 기능인지 알 수 있다.

 

*추가로 F() 객체는 해당하는 필드 이름을 쿼리문으로 바꿔주어 우리가 원하는 필드를 가져올 수 있다.

 

11. dir()과 eval()함수를 통한 결과값 도출

def get(self, request):
        user = User.objects.get(id=5)
        hobbys = list(user.userprofile.hobby.all())
        for command in dir(user):
            try:
                print(f'user.{command} :', eval(f'user.{command}'))
            except:
                pass

해당하는 user와 관련된 메소드의 결과값을 프린트 찍어볼수 있음! 

짜잔! 결과가 나온 걸 볼 수 있다. 무지 많지만 예시만 보자!