카테고리 없음

내일배움캠프 19일차

barryjung 2023. 4. 11. 21:14

[오늘 한일]

  • 팀 프로젝트 진행

 

[오늘 배운점]

 

<view함수에 _view를 붙여줘야 하는 이유>

에러 메세지의 모습이다.

로그인 기능을 테스트하는데 이런 에러가 나왔다.

login() takes 1 positional argument but 2 were given

로그인 함수는 1개의 위치 인자를 취하는데, 2개가 주어졌다라고 한다.

 

 

def signup(request):
(중략)
    form = UserForm(request.POST)
    if form.is_valid():
        form.save()
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(request, username=username, password=password)
        login(request, user)
        return redirect('/user/login')

에러가 난 코드는 이렇다.

지금까지 실습하면서 login에는 항상 저렇게 두개의 인자를 줬는데,

왜 에러가 나는 걸까 당황스러웠다.


인자를 하나만 넣어보기도 했는데 안됬다.

(user만 남겨봤는데 'User' object has no attribute 'method' 에러가 나온다.

method인 POST가 넘어와야 하는데 못왔다는 뜻 같다.

request를 주는 이유가 method 때문인거 같다.)

 

views.py의 전체 코드를 천천히 돌아보다보니 문제가 될만한 부분을 찾게 되었다.

팀원분께서 아래쪽에 login기능에 함수이름을 login으로 작성하신 것이었다.

 

어제 해설 강의에서 나는 함수명 뒤에 _view를 왜 붙이시는 건지 질문했었다.

튜터님께서는 변수 덮어쓰기가 일어날 가능성을 피하기 위해 작성한다고 답해주셨다.

기능 명을 직관적으로 지었을 경우 함수 안에서 해당 이름을 동작시켜서,

생각지 못한 에러가 날수 있는 것이다.

login, logout 함수이름을 보니 이 부분이 떠올랐다.

 

나는 혹시 모른다는 마음으로 이부분을 수정해봤는데, 에러가 나지 않았다!

(views.py의 함수명과 그걸 참조하는 urls.py의 함수 부분을 수정했다.)

변수는 아니었지만, 해당 이름이 문제를 일으키고 있었던 것이다.

지금 생각해보니, django.contrib.auth에서 import해오는 login과,

우리가 만든 login함수가 이름이 같으니, login을 실행할때 인자를 하나만 달라고 했던것이다.

 

def login(request):

def login_view(request):

우리가 만들었던 login함수의 모습. 입력 받는 인자가 하나이다.

 

문제를 인지하고 아래와 같이 수정해줬다.

결론, view함수이름을 독특하게 지을게 아니라면, _view를 꼭 붙여주자.

 


<현재 로그인한 사용자를 참조키에 입력하기>

어떤 데이터를 생성하면서,

그 외래키 필드에 현재 로그인한 사용자를 입력하는 방법은 무엇일까?

 

지금까지 수업이나 개인 과제에서는 같은 케이스가 없었어서 방법을 고민해야 했다.

방법이 간단할 것이라고 생각했는데 생각지 못한 난관이 있었다.

 

해당하는 view함수는 이렇다.

def create_tweet(request):
(중략)
    user = request.user

    tweet_form = TweetForm(request.POST)
    tweet_form.user = user
    tweet_form.save()

우선 현재 로그인한 유저 정보를 얻는 것은 간단하다.

request.user하면 된다.

기능이 잘 안되니까 여러 시행착오를 겪었는데, user 또한 다각도로 살펴봤었다.

 

저 user를 print해본다면 그냥 username이 프린트 된다.

(그래서 username이 아닌 user 데이터로 만들려고 user모델을 바꿔 보기도 했다.)

하지만 알아보니 이부분은 알맞게 작성한 코드이다.

혹시 검사가 필요하다면 대신 type을 프린트 하면 된다.

그럴 경우 <class 'django.utils.functional.SimpleLazyObject'>가 출력되어 알수 있다.

 

그리고 나는 당연히 POST받은 내용으로 생성한 TweetForm을,

변수에 담고 그 중 user필드에 user정보를 넣은다음,

저장하면 완료라고 생각하고 저렇게 작성했다.

 

그런데 계속 잘 되지 않고 에러가 나왔다.

NOT NULL constraint failed: tweet_tweetmodel.user_id 이런 메세지가 나온다.

에러 메세지의 모습이다. 알려주는 정보가 자세해서 좋다.

 

30분정도 혼자 알아보다가, 팀원분이 해결방법을 아셨다고 해서 배우게 됬다.

해결 방법은 이렇다.

    user = request.user
    tweet_form = TweetForm(request.POST)
    tweet_form_post = tweet_form.save(commit=False)
    tweet_form_post.user = user
    tweet_form_post.save()

처음에는 바뀐 부분이 왜 필요할까 의문을 가졌지만 알아보니 딱 알맞은 동작하는 방법이다.

tweet_form을 한번 save를 하는데 commit=False 옵션을 주어 임시저장만 수행한다.

그 임시저장한 내용을 변수에 담고 그 중 user에 user를 대입하고 저장하는 것이다.

 

참고하자면 우리의 user모델은 아래와 같다.

    user = models.ForeignKey(
        User, on_delete=models.CASCADE, verbose_name="이름", related_name='usermodel')

사용자 데이터인 User 모델을 참고하는 외래키이다.

그래서 request.user를 여기에 대입해주면 딱 맞다.

 

그러면 왜 바로 대입할때는 대입이 안되었고,

임시저장을 하고나서 되었을까?

 

그 이유는 TweetForm에는 user라는 칸이 없기 때문이다!!!

user는 입력받아야 하는 값이 아니고 자동으로 작성되야 하는 칸이다.

그래서 작성한 form에는 user가 없다.

즉, form에 user가 없으니 TweetForm으로 만든 인스턴스에는

(request.POST에도 user칸이 없는 것은 물론이고) user라는 칸이 없는 것이다.

 

그래서, request.user를 대입시키지 못하고,

저장 할때는 입력된 user가 없으니 빈칸인 null이 넘어간다.

그래서 입력되야하는 user_id 필드가 빈값으로 입력되었으니 위와 같은 에러가 난것이다.

NOT NULL constraint failed: tweet_tweetmodel.user_id

null될수 없는 필드가, null이 입력되어서 NOT NULL을 위반했다는 뜻이다.

 

그렇기 때문에, TweetForm을 request내용으로 임시저장을 한번 하는 것이다.

그럼 임시저장을 한 데이터 인스턴스에는 user필드가 생긴다.

그리고나서 여기에 로그인 사용자를 그대로 대입해 주는 것이다.

이렇게 해서 해결할 수 있었다.

 

마침 user필드가 외래키여서 NOT NULL속성을 기본적으로 가졌던것 같다.

결론, form을 사용하며 form에서 이용하지 않은 값을 가공할때는 이런방법이 적절하다.

 


<Form작성법 한가지 규칙>

class UserForm(UserCreationForm)
    class Meta:
        model = User
        fields = ['username', 'password1', 'password2', 'bio']

Form을 작성할때 기본적인 형태가 이렇다.

 

어제 배운점 내용처럼, UserCreationForm, AuthenticationForm등은,

view에서 바로 써도 되고, Form에서 상속한다음 이름만 바꿔줘도 된다.

 

연결할 모델에 대한 정의가 필요하다면, class Meta:를 적어준다.

model = 데이터모델명을 적는다.

그리고 fields = 와 리스트로 된 내용까지가 한쌍이다.

 

만약 fields를 아예 생략한다면 (혹은 안적는 다면)

django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form UserForm needs updating.

이런 에러가 나온다.

fields는 꼭 필요하다는 내용으로 보인다.