안녕하세요 장고 is_valid의 동작방식이 궁금합니다.

조회수 2960회
if self.is_valid():
    return redircet('/')

위와 같은 코드에서 is_valid()가 내부적으로 해당 폼의 clean()을 호출해서 유효성 검사를 하는거라고 알고있는데 clean() 같은경우는 return 값이 cleaned_date 라는 dict자료 던데요,

is_valid는 어떻게 True False 를 반환할수 있는거죠? clean() 안에서 add.error 라던지 errors 의 dict 값에 요소가 추가되면 False를 반환하도록 되어있는건가요?

아시는분 답변 부탁드립니다. 이해 안가고 너무 궁금하네요 정중히 부탁드리겠습니다 . 읽어주셔서 감사합니다. 남은시간도 좋은하루 보내시길 바라겠습니다.

  • (•́ ✖ •̀)
    알 수 없는 사용자

1 답변

  • 머.. 너무 당황치 마시고 소스를 차근차근 보면서 같이 알아보시죠. 일단 is_valid()입니다.

    def is_valid(self):
        return self.is_bound and not self.errors
    

    사실 이 구문 자체는 무조건 bool이 돌아옵니다. 왜냐하면 and 연산자가 쓰이고 있기 때문이지요. 뭔진 몰라도 self.is_boundTrue여야 하고 self.errors는 Falsy해야 유효한 폼이 되는 거네요.


    그럼 self.errors가 뭐길래?

    @property
    def errors(self):
        if self._errors is None:
            self.full_clean()
        return self._errors
    

    self.errors는 기본적으로 self._errors를 반환하는 프로퍼티군요. (self._errors가 하필 None일 때는 self.full_clean()을 한번 돌리긴 하지만, 그 결과를 반환하는 건 아니고요.)

    이쯤에서 일단 질문자님의 의문 ― "self.clean()dict를 반환하는데 어떻게 is_valid()bool을 반환하지?" ― 은 어느 정도 해결이 되었으리라고 생각됩니다. is_valid()는 그 안에서 무슨 난리굿판이 벌어지든 결국에는 True 아니면 False가 나오는 메소드입니다.


    그러면 이제 추가 의혹 ― "clean() 안에서 add.error 라던지 errorsdict 값에 요소가 추가되면 False를 반환하는 건가? 뭐지?" ― 을 확인해 보도록 하지요. 지금까지 본것에 따르면 is_valid()는 결국 self._errors가 뭔가 값이 없지 않을 때 False가 되는 건데요.

    그러면 이제 질문은 self._errors가 뭐냐 하는 문제가 됩니다.

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                     initial=None, error_class=ErrorList, label_suffix=None,
                     empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
        # 중략
        self._errors = None  # clean() 이 호출된 뒤에 에러들을 여기 저장한다.
    

    영어 주석을 하나 번역했습니다. self._errors에 에러들을 저장한다는군요. 어떻게? 검색을 해보죠.

    def add_error(self, field, error):
        # 중략
        for field, error_list in error.items():
            if field not in self.errors:
                # 중략
                if field == NON_FIELD_ERRORS:
                    self._errors[field] = self.error_class(error_class='nonfield') # <-- 이런거
                else:
                    self._errors[field] = self.error_class() # <-- 이런거
            self._errors[field].extend(error_list)
            if field in self.cleaned_data:
                del self.cleaned_data[field] # <-- 이런거
    

    알고 계신 대로 self._errors는 오류들의 리스트입니다. self._errors[field] 구문을 보면 명백하죠.

    그러면 self.add_error()는 언제 호출될까요?

    def _clean_fields(self):
        for name, field in self.fields.items():
            # 중략
            try:
                # 중략
            except ValidationError as e:
                self.add_error(name, e) # <-- 여기
    

    그리고 self._clean_fields()는 언제 호출되는가 하면...

    def full_clean(self): # --> 음? 이거 어디서 본 적 있는 메소드 같은데?
        # 중략
        if self.empty_permitted and not self.has_changed():
            return
    
        self._clean_fields() # <-- 여기
        self._clean_form()
        self._post_clean()
    

    아! 이게 이렇게 도는 것이군요. 여기서부터는 django form의 validation 처리 순서도를 그리실 수 있을 것이라 생각됩니다.


    요컨대 django forms의 유효성 검사도 대다수 validators들의 방법을 따르는 것 같습니다. 오류의 목록을 저장/업데이트하는 변수/속성이 있고, 그 목록이 비어 있는지에 따라 유효/무효를 판별해 주는 변수/속성이 또 따로 있는 것이죠.

    왜 그렇게 해야 하느냐? 그냥 아래 코드처럼 심플하게 하면 되지 않느냐? 하신다면...

    if (무효조건1) { // 얼핏 보면 아무 문제 없는 구조이지만 실제로는 문제가 있다.
        alert(경고1);
        return false;
    }
    if (무효조건2) { // ex) 무효조건2 구문이 그 자체로 오류가 있거나 무조건 참이 나오거나 코드 인젝션 가능한 버그 코드라면 이 구간은 그야말로 대책 없다.
        alert(경고2);
        return false;
    }
    if (무효조건3) { // ex) 무효조건1~3을 모두 위반한 사용자는, 받아야 할 모든 경고를 받기 위해, 최소 3번의 재시도를 해야 한다. 사용자는 3배 이상 화딱지가 나게 된다.
        alert(경고3);
        return false;
    }
    return true; // 그래서 이 방식으로 유효성 판별하는 코드들은 사실 재설계해야 한다.
    

    답변이 도움이 되었을지 모르겠네요.

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)