카테고리 없음

스파르타 AI-8기 TIL(12/14)-Django

kimjunki-8 2024. 12. 14. 21:56
여러가지 HTTP 요청 메서드
지금까지 배운것
render
redirect
이것말고도 더 있다.
get_object_or_404() - get을 호출한 후 객체가 없다면 404 에러를 raise하여 404 페이지로 이동시킵니다.
get_list_or_404() - filter를 호출한 후 빈 리스트라면 404 에러를 raise하여 404페이지로 이동합니다.

400번대 코드, 즉 403, 404와 같은 코드라면 클라이언트의 요청에 문제가 있음을 나타내고
500번대 코드는 서버 내부에 문제가 생겨 요청을 처리할 수 없다는 것을 나타냅니다.
존재하지 않는 게시물을 조회하려고 했기 때문에 클라이언트의 요청에 문제가 있음을 나타내는 400번대 상태코드(404 Not Fount)가 더 적절하다.
저 숫자를 상태 코드라고 한다.

100번
200번
300번
400번
500번 이렇게 있다. 400은 클라이언트의 잘못, 500은 서버의 잘못으로 오류가 난다.

그래서 get_object_or_404() 이것을 쓰는 것이다.
get_object_or_404(where, condition)
where- 어디서 찾을 껀지(데이터를(views, templates. etc)
condition - 어떻게 찾을 것인지(어떤 조건)
만약 못 찾았다면 404 에러를 낸다 

그런데
그런데 전에 설명했듯이, if request.method == 'POST'는 POST만 처리하도록 했습니다. 그런데 만약에 GET도 필요하다면, 게다가 POST와 GET만 있지 않습니다. 그렇기 위해 데코레이터가 필요합니다.
여기서 else를 통해 다른 방식도 받도록 했습니다.

데코레이터
데코레이터는 함수를 감싸줌 
@example
def example():
....​

이런 상태에서 안에 추가적인 기능을 더 넣어주는 것(이것이 꾸며준다라고 할 수 있다)(참고로 이것도 함수라고 할 수 있다) 

그래서 장고는 대표적으로 2개 지원
require_http_methods()
view 함수를 특정한 method 요청에 대해서만 허용
만약 인자로 리스트로 "GET", "POST" 넣으면, GET, POST가 아닌 호출은 차단한다

그런데 만약 하나만 허용하고 싶다면

require_POST() ->(GET, Delete..._)
POST 요청만 허용
from django.views.decorators.http import require_POST로 통해 가져오고 

이렇게 쓰면 끝이다.
원래 if....로 했는데 이렇게만 하면 더 쉽고 간편하다.


Template with Auth
Auth 기능을 Templates에서 활용
그런데 templats는 views에서 넘겨준 context만 사용이 가능하지 않나?
여기서 숨겨진 사실이 있습니다. 
바로 사실 context로 넘겨지는 정보가 없더라도, 시스템상 자동으로 넘겨오는(우리는 안 보이는) 정보들이 있습니다.
request.user 도 그 중에 하나로 템플릿을 랜더링할때 현재 로그인한 사용자를 나타내는 auth.User 클래스의 인스턴스 또는 AnonymousUser 인스턴스(로그인하지 않은 유저)를 request.user로 접근할 수 있습니다.

예를 들어 로그인을 하면 로그인 버튼이 사라지고, 로그인을 안 했으면 반대로 로그 아웃이 안 보이는 것

일단 보자

이렇게 할 경우

이렇게 AnonymousUser가 보인다.
이렇게 쓸 수 있다는 것이다.
로그인하면 Admin으로 바뀐다 참고로 request 안 적고 그대로 user라고해도 괜찮다.

접근제한
is_authenticated 속성 사용하기
@login_required 사용하기

.is_authenticated
위에 .is_authenticated는 만약 로그인이 되었다는 라는 뜻이다.
즉, 로그인이 되었으면 로그아웃을 보여주고, 아니면 안 보여주는 코드인것이다.

그런데 솔직히 저 if .is_authenticated를 전부 하나하나 붙여줄 것인가? 그것도 힘들다 그래서 데코레이터를 쓰자.

@login_required(데코레이터)(참고로 데코레이터 두개 가능)
함수 위에 작성하면, 오로지 로그인이 되었을때만 함수가 작동한다.

만약 로그인이 되어있지 않은 상태에서 접근하면 settings.LOGIN_URL 에 설정된 경로로 이동시킵니다.
from django.contrib.auth.decorators import login_required에서 쓴다

그리고 login_required는 또 한가지의 장점이 있습니다. 바로 이전 링크를 기억해주는 겁니다.
만약 로그인을 안 한 상태로 어떠한 작업을 하는 링크로 들어가면 바로 로그인 화면으로 바로 뛰어집니다. 그리고 로그인을 하면 다시 그 작업을 하는 링크로 들어가야 합니다.하지만 login_required는 그럴필요가 없이 그 상태에서 이전 링크를 기억하고 있다가 로그인을 하면 바로 가도록 우리가 코드를 지정하면 되는 것이다.

여기서 ?next=가 그 역할이다. 우리는 그저 저 next를 받아서 바로 그 다음 링크로 향하게 할지, 아니면 그대로 놨둘지 코드만 작성하면 된다. (POST 처리하는 곳에서 해야한다 예를 들어 next가 있으면 처리하고 없으면 없는대로)

이렇게 만약 next가 있으면 그대로 그 링크로 이동하고 없으면 first_page로 가라라는 뜻의 코드이다

그런데 이것이 끝인가? 아니다 templates도 손봐야 한다. 왜냐하면 action에서 우리가 또 다른 링크로 지정했기 때문입니다. 그러면 어떻게 적느냐? action자리를 비워두면 된다. 그러면 request 자리가 들어온 그 링크 그대로 다시 데이터를 보낸다.

회원가입
바로바로 UserCreationForm
username과 password 로 새로운 user를 생성하는 ModelForm
username, password1, password2를 가짐

from django.contrib.auth.forms import UserCreationForm여기서 가져와서 쓴다.

자, 여기서 중요한 설명이 필요합니다.
먼저, 

이렇게 화면이 뜨는데, 사실 저기에 뭘 적어도 아무런 변화가 없습니다. 왜냐하면 그 정보를 담을 그릇도, 옮길 방법도 아직은 구현이 안 되었기 때문입니다.
게다가 잘 보면 action에 signup으로 다시 돌아가는 형태로 있습니다. 이것이 무엇이냐면, 만약 method가 POST라면, 저 Username하고 Password를 적은 상태로 회원가입을 누르면 그 적은 정보를 가지고 다시 def signup을 쓰게 되는데, 그런데 아직 form = UserCreationForm() 이렇게 ()안에 아무것도 없습니다. 즉, 이 데이터를 뭐 지지고 볶고 할 방법을 안 적은 것입니다. 그렇기에 전에 했었던 방법처럼,
form = AuthenticationForm(data = request.POST) 이렇게 하면 templates에서 버튼을 눌러 다시 함수를 실행할 때, 적은 정보를 받아서 다시 작동을 하는 원리란 소리 입니다.

쉽게 설명하자면, 만약 버튼을 눌러서 다시 돌아왔다고 해도, 만약 데이터가 있으면 즉, POST로 정보가 전달 되었으면, if를 실행시키고, 만약 없으면 빈 form 즉, else를 실행시킨다 라는 뜻입니다. 그렇기에 위에도 마찬가지로 만약 신규 아이디와 비밀번호를 안 치면 정보가 없을 뿐더러, 그것을 적었다 하더라도, 그 정보를 담을 그릇이 없다는 뜻입니다.

그리고 저기에 return redirect는 만약 저 정보들을 받은 상태로 다시 돌아간다는 뜻이지, 회원가입에서도 정보를 받았다고 치면, 그 받은 정보를 들고 redirect 시킨다는 뜻입니다.

이렇게 바꿨습니다.
자, 그런데 겉보기에는 method='POST'로 이미 명시했기 때문에 다른 방식의 요청(method)을 받을 일이 없어 보이는데, 왜 백엔드 코드에서 다시 한 번 request.POST를 사용하는지 의문이 들 수 있습니다.

HTML에서 method="POST"는 클라이언트(브라우저)가 서버에 데이터를 전송할 때 POST 방식을 사용하도록 명시하는 것입니다.

이는 클라이언트와 서버 간의 의사소통 방식을 지정한 것이며, 서버로 요청이 들어오면 실제로는 다양한 요청이 전달될 가능성을 항상 고려해야 합니다. 즉, HTML에서 POST 방식으로 제한한다고 하더라도, 사용자가 의도적으로 다른 요청을 보낼 가능성을 배제할 수는 없습니다.


서버에서 request.POST로 데이터를 처리하는 이유
Django에서 request.POST는 HTTP POST 요청으로 전송된 데이터를 접근하는 방식입니다. 이 부분은 보안과 코드의 명확성을 보장하기 위해 꼭 필요합니다. 그 이유는 다음과 같습니다.

1. 보안
서버는 클라이언트가 반드시 신뢰할 수 있다고 가정할 수 없습니다.
예를 들어, 악의적인 사용자가 HTML을 수정하거나, 심지어 API 호출을 조작하여 GET 또는 다른 HTTP 메서드로 요청을 보내는 상황도 있을 수 있습니다.
POST가 아닌 GET으로 데이터를 보낸다면, 서버에서 별도의 검증 없이 처리를 시도할 수 있습니다. 이 경우 민감한 사용자 데이터가 URL에 노출되거나, 예상치 못한 에러가 발생할 가능성이 있습니다.

따라서 서버는 POST 요청으로 전송된 데이터만 처리하도록 제한하는 것이 중요합니다.

2. 코드의 명확성
request.method로 어떤 요청이 들어왔는지 확인하거나 request.POST로 POST 데이터를 처리하면, 코드를 읽는 사람들에게 다음과 같은 의도를 명확히 전달할 수 있습니다:
이 함수는 POST 요청만을 처리한다.
POST 데이터를 검증하고 저장한다.
이는 가독성을 높이고, 유지보수하는 개발자에게 의도를 명확히 전달합니다.

GET 요청을 처리하는 이유
else 부분에서 form = UserCreationForm()을 실행하는 이유는, 이 뷰가 GET 요청에도 대응할 수 있도록 설계되었기 때문입니다.
GET 요청이 오면 새로운 빈 폼을 반환해 사용자에게 회원가입 페이지를 보여줍니다.
POST 요청이 오면 폼 데이터가 전송되었으므로, 이를 처리합니다.
따라서 if request.method == "POST"를 명시적으로 처리하면 다른 요청(GET, PUT, DELETE 등)을 명확히 구분할 수 있습니다. 그런데 갑자기 GET? 뭔 소리지 하실 수 있는데, 처음 유저가 회원가입을 하기 위해 회원가입 페이지로 이동하는데, 이걸 GET이라고 할 수 있습니다. 왜? 그냥 회원가입만 하러 가기 때문에 빈 폼을 보여줘야 하기 때문입니다.

바인딩?
1. 바인딩 폼이란?
폼 바인딩은 사용자가 제출한 데이터(예: HTML 폼에서 입력한 값)를 Django의 폼 클래스에 전달하여 폼과 데이터를 "연결"하는 것을 의미합니다.

2. 언제 바인딩이 이루어지는가?
Django에서 폼 바인딩은 주로 두 상황에서 발생합니다:
2.1 사용자가 데이터를 제출했을 때
폼 데이터를 request.POST 또는 request.GET에서 가져와 폼 클래스에 전달하여 바인딩합니다.
form = UserCreationForm(request.POST)  # 바인딩 폼


2.2 빈 폼을 생성할 때
폼에 데이터를 전달하지 않고 초기 상태로 생성하면 비바인딩 폼이 됩니다.

form = UserCreationForm()  # 비바인딩 폼


3. 바인딩의 주요 과정
폼 바인딩은 다음 단계를 거칩니다:
1. 데이터 연결
사용자가 제출한 데이터를 폼 필드와 연결합니다.
예를 들어, username 필드에 사용자가 입력한 데이터가 전달됩니다.

2.  검증 (Validation)
바인딩된 데이터가 유효한지 확인합니다. 이 과정에서:
각 필드에 정의된 조건(예: 최대 길이, 필수 입력 여부 등)이 적용됩니다.
is_valid() 메서드를 호출하면 데이터 검증이 수행됩니다.

if form.is_valid():  # 폼 검증
    form.save()  # 데이터 저장


3. 에러 처리
데이터가 유효하지 않은 경우, 폼 객체는 form.errors 속성을 통해 필드별 에러 메시지를 제공합니다.
이를 통해 사용자는 잘못된 입력을 수정할 수 있습니다.

예시:

from django.shortcuts import render, redirect
from django.contrib.auth.forms import UserCreationForm

def signup(request):
    if request.method == "POST":
        # 바인딩 폼: POST 데이터와 폼을 연결
        form = UserCreationForm(request.POST)
        if form.is_valid():  # 데이터 검증
            form.save()  # 데이터 저장
            return redirect('login')  # 성공 시 리다이렉트
    else:
        # 비바인딩 폼: 빈 폼 생성
        form = UserCreationForm()
    return render(request, 'signup.html', {'form': form})

회원탈퇴
DB에서 데이터 제거가 회원탈퇴이다.
request.user -> request 객체 안에 쿠키가 들어있는데, 장고가 알아서 쿠키에서 세션ID를 빼서, 세션 Table에 가서 비교하는 작업을 수행해서 실제로 유저 테이블에 있는 그 유저의 인스턴스를 request객체 안에서 user로 접근할 수 있게 한다. 즉, 나에게 요청이 들어온 유저가 누군지 알고 싶으면 request.user하면 된다.

즉, 저 user.delete도 그 안에 세션ID, 유저에 관한 데이터가 있기 때문에 바로 .delete를 붙이면 된다.

그리고 만약 세션까지 제거하고 싶다면?(log_out)까지 해주면 된다.
중요
절대경로, 상대주소
절대경로
절대 경로 (Absolute Path) 정의 절대 경로는 파일이나 리소스의 전체 경로를 명확히 지정하는 방식입니다. 기준이 되는 루트(root) 디렉토리부터 시작하여 파일의 위치를 명확히 정의합니다. 항상 동일한 위치를 가리킵니다.
즉, 코드에서

이렇게 되어있다고 하자. 그러면 이것은 저 main_page로 이동하는게 아니라, 처음부터 링크를 적어버리는 것이다.
즉, 어딘가 이동시키는게 아니라

그냥 여기서부터 시작한다는 뜻이다.(참고로 /main_page/에서 처음 /을 붙이면 절대경로가 된다.)

2. 상대 경로 (Relative Path)
정의
상대 경로는 현재 위치를 기준으로 파일이나 리소스의 위치를 지정하는 방식입니다.
파일이나 디렉토리의 위치를 기준점으로부터 상대적으로 표현합니다.
기준점은 현재 디렉토리(.) 또는 상위 디렉토리(..)일 수 있습니다.

예를 들어
redirect('posts/list/)
이렇게 쓰는것이 상대 경로입니다.

회원 정보 수정
이것도 장고가 지원한다!
UserChangeForm!

자, 여기서 중요한 점은 저렇게 Form을 가져다가 쓸 때, form이 어떤 역할을 하는지도 생각해야 합니다.
예를 들어 UserChangeForm은 값을 변경하는 클래스입니다. 즉, 값을 받아야만 바인딩을 할 수 있는데, 그 바인딩을 하기 위해선 알맞은 값을 클래스와 연결시켜야 합니다. 그렇게 생각하면 과연 뭘 가져와야 수정을 하는지 알아야 합니다. 맞습니다 request에 있는 user데이터를 가져와야 합니다. 그렇게 request.user 데이터를 넣어주면 됩니다.

그런데 생각해보면 이미 존재하는 값을 수정할려면, 이미 존재하는 값을 보여줘야 합니다. 즉, 값을 받기 보다는 어떠한 인스턴스에 넣어 줘야 한다는 뜻입니다.

이렇게 말입니다.

instance=request.user는 Django의 ModelForm이나 Form 객체에서 제공하는 기능 중 하나로, 기존 데이터를 기반으로 Form을 생성하기 위해 사용됩니다.

instance=request.user의 역할
기존 데이터를 Form에 채워넣기:
UserChangeForm은 주로 사용자의 정보를 수정할 때 사용됩니다.
instance=request.user를 설정하면, 현재 로그인한 사용자의 데이터를 UserChangeForm에 미리 채워줍니다.
즉, 사용자 정보를 보여주고, 필요하면 수정할 수 있도록 합니다.
그래서, request.user는 instance로 전달됩니다.
폼에 사용자의 기존 데이터가 채워지고, 수정 후 저장하면 해당 사용자 객체가 업데이트됩니다.

수정 작업을 위한 초기화:
수정 폼에서 기존 데이터를 불러오지 않으면, 폼이 비어 있게 됩니다. 이 상태에서 데이터를 제출하면 새로운 객체를 생성하려는 동작으로 처리될 수 있습니다.
instance=request.user를 지정하면 해당 사용자 객체와 연결된 폼이 생성되며, 이후 폼이 제출될 경우 기존 데이터를 수정하는 동작으로 처리됩니다.

왜 그냥 request.user를 넣을 수 없을까?
UserChangeForm의 instance 매개변수는 ModelForm에서 사용하는 특별한 기능입니다.
단순히 request.user를 form = UserChangeForm(request.user)로 전달하면, request.user는 instance로 전달되는 것이 아니라 폼의 첫 번째 필드 데이터로 인식됩니다. 이로 인해 폼 생성 시 에러가 발생합니다.

내부적으로 어떻게 동작하는가?
UserChangeForm은 Django의 ModelForm을 기반으로 합니다.
ModelForm은 instance 키워드 인수를 사용하여 수정 대상 객체를 지정합니다.
UserChangeForm(request.user)는 올바른 키워드 인수를 전달하지 않았기 때문에 request.user를 instance가 아닌 폼 필드의 값으로 해석하려고 시도합니다.

여기서 폼 필드의 값이란?
"폼 필드의 값"이란 HTML Form에 해당하는 각 필드에 들어가는 데이터를 의미합니다.
예를 들어 다음과 같은 폼이 있다고 가정해 봅시다:

class UserChangeForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['username', 'email']

이때 UserChangeForm은 username과 email 필드를 가진 폼을 생성합니다.

 폼(form)? 폼 필드 값?
폼(Form)은 우리가 컴퓨터에 정보를 넣는 상자(입력칸)들입니다. 예를 들어:
이름을 입력하는 상자
이메일을 입력하는 상자
비밀번호를 입력하는 상자
이런 것들이 폼 안에 들어있고, 사용자가 이 상자들에 정보를 넣고 저장하면, 컴퓨터가 그 정보를 받아서 기억하거나 고칠 수 있습니다.
그러면

form = UserChangeForm(request.user)​
이 코드가 왜 문제일까요?
폼은 첫 번째 값으로 정보를 넣으라고 기다리고 있습니다.
예를 들어: "이름"은 "Tom"이고, "이메일"은 "tom@email.com"입니다.
그런데 request.user는 "정보"가 아니며, request.user는 "사용자" 그 자체입니다.
예를 들어, "Tom이라는 사람" 그 자체지, "이름"과 "이메일" 같은 구체적인 정보가 아닙니다.
폼은 구체적인 정보가 필요하며, 이렇게 통째로 값을 주면, 에러가 납니다.

request.user는 하나의 "객체"이며
객체는 사람이라고 생각하면, 예를 들어, "Tom"이라는 사람이 있으면,
이 사람에게는 여러 가지 정보(속성)가 있습니다:
이름: Tom
이메일: tom@email.com
나이: 25
"필드"는 그 정보들을 담는 작은 상자입니다.
이름 상자에는 request.user.username이 들어가고,
이메일 상자에는 request.user.email이 들어가고,
나이 상자에는 request.user.age가 들어갑니다.

request.user는 하나의 "객체"이며
객체는 사람이라고 생각하면, 예를 들어, "Tom"이라는 사람이 있으면,
이 사람에게는 여러 가지 정보(속성)가 있습니다:
이름: Tom
이메일: tom@email.com
나이: 25
"필드"는 그 정보들을 담는 작은 상자입니다.
이름 상자에는 request.user.username이 들어가고,
이메일 상자에는 request.user.email이 들어가고,
나이 상자에는 request.user.age가 들어갑니다.

그렇기에
instance는 단순한 이름이 아니라 Django에서 특별한 기능을 가지고 있는 키워드 인자며, 
instance를 넘기면 폼이 업데이트 모드로 동작합니다.
즉, 폼은 두 가지 모드 중 하나로 작동하는데:
새로운 데이터를 추가하는 모드 (Create)
instance를 전달하지 않으면 새로운 객체를 만들어서 저장하며,
이때는 빈 폼을 사용하거나 request.POST 데이터를 넣어서 저장합니다.

기존 데이터를 수정하는 모드 (Update)
instance=request.user처럼 instance를 넘기면,
폼은 기존 모델 객체를 수정하려고 동작합니다.
그리고 form.save()를 호출하면, request.user 객체가 업데이트 됩니다.

화면에서 보듯이 굳이 이렇게까지 많은 것을 바꿔줘야 할까? 아니다.

그러면 뭐다? 그 클래스로 가서 보면 field가 있다. 그렇기에 그냥 저 클래스를 상속받아서 값만 바꿔주면 된다.

forms.py를 만들어서 그 안에 적어주자. 그리고 바꿀 부분인 Meta만 적는다!
그런데 model에 User를 적어도 되지만, 다른 방식도 선호한다.

장고는 이 User를 한번에 여러개 쓸 수 있게 해준다. 그렇기에 App별로 User가 다를 수 있다. 즉, 현재 활성화되어서 쓰여지고 있는 User를 항상 접근을 가능하게 하는 함수를 지원한다.
그것이 바로 get_user_model()이다.


그리고 잘 보면

이 get_user_model은 settings에 있는 AUTH_USER_MODEL을 참조하는데, 사실 이건 settings에 존재하지 않는다. 왜냐하면 저것을 따로 만들어줘야 하는데, 안 만들면 디폴트 값으로 장고의 기본 User를 가르킨다.

예를 들어 users에 있는 Myuser를 쓰겠다고 get_users를 하면 이 User가 쓰인다는 뜻이다.

아무튼 돌아와서 저렇게 쓰면 된다는 뜻이다.

그리고 이제 fields를 쓸 차례이다.
fields에는 가져올 데이터만 쓰면된다.

그리고 이제 UserChangeForm을 쓰는게 아니라 CustomUserUpdate을 쓰면된다.

만약 저 밑에 password를 지우고 싶다? 

이건 저 클래스의 form이기 때문에, 우리가 따로 meta를 설정해도, 저 password는 전체적 form에 들어가 있기 때문에 보여진다. 그래서 그냥 password = None으로 바꿔주면 된다.

이렇게 말이다.

그러면 더 깔끔하게 변한다.

오늘은 여기까지...