Django 기본 학습

solarrrrr·2023년 10월 25일
0

Today I Learned

목록 보기
67/74

django로 개인 프로젝트를 진행하기에 앞서
django 프레임워크를 사용 안 해본 지 오래되어서
기본 개념을 되새길 겸 간단하게 정리했다.


Django의 MTV 아키텍처

모델, 템플릿, 뷰로 이루어져 있다.

모델은 데이터를 정의하고 관리하는 부분이며
연결할 데이터베이스의 정보가 들어가는 부분이다.

템플릿은 사용자에게 보여지는 부분을 정의하는 데 사용된다.

뷰는 실제 처리 로직이 들어가는데,
사용자의 요청을 처리하고 모델과 템플릿을 연결해 결과를 반환한다.

ORM

파이썬 코드로 DB를 조작하게 해 주는 역할을 한다.
SQL 쿼리를 직접 날리지 않고 파이썬 코드 형태로 할 수 있기 때문에
사용이 쉽고 개발 생산성이 올라간다고 한다.
또 DB가 달라져도 사용 가능하다는 장점이 있다.
이걸 데이터베이스 독립성이라고 표현하는 것 같다.

다만 쿼리가 복잡해질 경우 사용하기 어렵다는 단점이 있는데,
잘못 사용하면 성능 문제가 발생할 수 있다.

개인적으로 FLASK로 실무를 하면서 ORM보다는 row query를 주로 썼는데
쿼리를 잘 하는 건 아니지만 row query도 나름 쓸 만하고 좋았던 기억이 있다.

미들웨어

사용자 요청과 응답 사이에 위치하여 특정 작업을 수행하는 컴포넌트이다.
인증 작업이나 로깅, 압축 등의 작업을 수행한다.
settings.py에 미들웨어 설정하는 부분이 있는데
그곳에 추가해서 사용하게 되며 추가한 순서대로 실행된다.

Django의 캐싱

캐싱 하면 보통 레디스가 많이 떠오르기는 하는데
장고에서 제공하는 캐싱 기능이 있다.
메모리 캐시, 데이터베이스 캐시, 파일 캐시 등이 존재한다.
필요한 캐시를 설정하고 캐시 키와 값, 만료 시간 등을 설정해서
캐시를 저장하고 불러올 수 있다.
또 @cache_page 데코레이터를 사용하면 특정 뷰의 결과를 캐싱하는 것도 가능하고
{% cache %} 태그를 사용하면 템플릿에서 특정 부분을 캐싱하는 것도 가능하다.

CSRF 보호 기능

CSRF는 크로스 사이트 요청 위조로 웹사이트의 취약점을 이용해서
공격자의 요청이 사용자의 요청인 것처럼 속이는 공격 기법이다.

보통 데이터 값을 변경하는 요청을 대상으로 한다.
정보의 탈취보다는 특정 작업을 무단으로 진행하기 위한 목적이 많다.
그렇다고 정보 유출이 없다는 건 아니다.

CSRF와 함께 많이 등장하는 XSS라는 공격 기법도 있다.
크로스 사이트 스크립팅이라는 건데,
둘 다 사용자의 브라우저를 대상으로 한다는 공통점이 있다.

다만 XSS는 사용자의 인증된 세션을 악용하는 공격 방식인 반면
CSRF는 인증된 세션 없이도 공격할 수 있다는 차이점이 있다.

또 두 공격의 목적 또한 다른데,
XSS는 사용자 PC에서 스크립트를 실행해
사용자의 정보를 탈취하는 게 목적이라면,
CSRF는 요청을 위조함으로써 송금, 제품 구입 등의
특정 행위를 수행하는 걸 목적으로 한다.

Django에서는 CSRF 공격 방지를 위해 CSRF 토큰을 이용한다.
기본적으로 모든 POST 요청에 대해 CSRF 토큰을 검증하며
이를 통해 요청의 유효성을 확보한다.

이 기능을 활성화하려면 settings.py의 MIDDLEWARE에 다음의 미들웨어를 추가한다.

django.middleware.csrf.CsrfViewMiddleware            

그런 다음 웹페이지의 폼을 생성할 때
{% csrf_token %} 태그를 사용해서
폼 안에 CSRF 토큰을 포함한다.

POST 요청하는 뷰에서는 Django가 자동으로 CSRF 토큰을 검증하기 때문에 개발자가 별도로 작업할 필요는 없다.

만약 CSRF 토큰을 포함하지 않는 API 요청 같은 경우는
@csrf_exempt 데코레이터를 사용하면 된다.

Django에서 데이터베이스 마이그레이션 관리

Django는 마이그레이션 시스템을 이용해
데이터베이스 스키마 변경을 관리한다.
모델의 변경사항을 감지하여 마이그레이션 파일을 생성하고
이를 데이터베이스에 적용해 스키마를 변경한다.

models.py의 코드상에서 스키마에 조작이 발생하면
makemigrations와 migrate 명령을 통해 적용할 수 있다.

RESTful API 구현

DRF라는 확장 프레임워크를 사용하면
RESTful API를 구현할 수 있는 여러 기능들을 제공해
쉽게 구현할 수 있다.
또는 클래스 기반 뷰를 사용해 API를 구현할 수도 있다.

템플릿 상속하는 법과 이점

Django에서 템플릿 상속은 중복되는 코드를 피하고
템플릿 구조를 재사용하기 위한 기능이다.

기본 템플릿을 정의하고 다른 템플릿에서 이걸 상속해서
필요한 부분을 오버라이딩하거나 확장할 수 있다.

이를 통해 유지보수성과 효율성을 높일 수 있다.


ORM 관련 자주 사용되는 메소드

  • filter()
    특정 조건에 맞는 레코드를 필터링한다.

    Model.objects.filter(name='Kim')
  • exclude()
    특정 조건을 만족하지 않는 레코드를 필터링한다.

    Model.objects.exclude(age__lt=18)
  • get()
    하나의 레코드를 가져온다.
    만약 조건에 맞는 레코드가 없거나 조건에 맞는 레코드가 여러 개일 경우
    MultipleObjectsReturned 혹은 DoesNotExist 예외가 발생한다.

  • order_by()
    레코드를 특정 컬럼을 기준으로 정렬한다.

    Model.objects.order_by('name')
  • annotate()
    쿼리셋에 집계함수를 적용해서 새로운 필드를 추가한다.

    Model.objects.annotate(total=Sum('quantity'))

    quantity 필드의 합을 total이라는 새로운 필드에 추가한 쿼리셋을 반환하게 된다.

  • aggregate()
    쿼리셋에 집계함수를 적용할 때 사용한다.

    Models.objects.aggregate(total=Sum('quantity'))

    quantity 필드의 합을 반환한다.

  • select_related와 prefetch_related
    관련된 객체를 미리 캐싱해서 쿼리 수를 줄일 수 있는 Django ORM의 메소드이다.

    select_related
    1:1, n:1(정참조) 상황에 주로 사용한다.
    쿼리 결과에 포함된 객체의 관련 객체를 함께 가져오는 방법이다.
    DB단에서 SQL JOIN으로 가져온다.
    prefetch_related()와 달리 관련 객체를 별도의 쿼리로 가져오지 않고 쿼리 결과에 포함시켜 가져온다.

    class Owner(models.Model):
       name = models.CharField(max_length=100)
    
    class Dog(models.Model):
       name = models.CharField(max_length=100)
       owner = models.ForeignKey(Owner, on_delete=models.CASCADE)
    
    dogs = Dog.objects.select_related('owner').all()
    
    for dog in dogs:
       print(dog.owner.name)


    prefetch_related
    1:n(역참조), n:m 상황에 주로 사용한다.
    select_related와는 달리 DB단에서 JOIN을 사용하지 않고
    python에서 joining을 진행한다.
    main 쿼리가 실행된 다음 추가로 실행되기 때문에
    prefetch_related보다 select_related가 전반적으로 DB I/O를 줄이는 데 도움이 된다.

    class Owner(models.Model):
      name = models.CharField(max_length=100)
    
    class Dog(models.Model):
      name = models.CharField(max_length=100)
      owner = models.ForeignKey(Owner, on_delete=models.CASCADE)
    owners = Owner.objects.prefetch_related('dog_set').all()
    
    for owner in owners:
       for dog in owner.dog_set.all():
           print(dog.name)

    💡 참고사항
    Django ORM에서는 ForeignKey 필드를 사용할 때 자동으로
    '역참조' 관계 매니저를 생성해 주는데,
    [소문자 모델명]_set을 사용해 접근할 수 있다.

    ex) > owner.dog_set.all()

    또는 related_name을 명시해서 해당 이름을 대신 사용할 수도 있다.

    ex) owner = models.ForeignKey(
           Owner, 
           on_delete=models.CASCADE, 
           related_name='dogs'
       )
    > owner.dogs.all()
  • Lazy Lording
    데이터가 실제로 필요한 시점에 로드되는 디자인 패턴을 말한다.
    만약 특정 객체의 상세 정보를 가져오는 경우,
    그 객체와 연관된 다른 객체들의 정보는 상세 정보가 실제로 필요한 시점에
    데이터베이스에 로드하게 된다.

  • N+1 문제
    ORM 사용 시 나타나는 성능 문제이며, 쿼리 효율성과 관련된 문제이다.
    1은 최초 쿼리를 의미하고 N은 관계된 객체를 가져오기 위한 추가 쿼리를 의미한다.

    owners = Owner.objects.all()
    
    for owner in owners:
    	for dog in owner.dog_set.all():
        	print(dog.name)

    첫 번째 쿼리는 Owner 모델 클래스의 모든 인스턴스를 가져오는 데 사용되고,
    (쿼리셋 생성, Owner 테이블 전체 조회)
    그후 각 인스턴스를 참조하는 Dog 모델 클래스의 데이터를 가져오는 쿼리가 실행된다.
    이렇게 되면 총 쿼리 수는 Owner의 전체 수에 1을 더한 값이 된다.
    불필요하게 쿼리 수가 많아지게 되는 현상이고, Owner 객체가 많을수록
    쿼리 수가 더 늘어나게 될 것이다.
    이 문제를 해결하기 위해 위에 말한 select_related와 prefetch_related를 사용할 수 있다.
    필요한 데이터를 미리 한 번의 쿼리로 가져와 쿼리 수를 줄일 수 있다.

profile
몰입

0개의 댓글