ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 41일차 - 장고프로젝트 (5)
    AI 솔루션 개발자과정(Java, Python) 2022. 12. 5. 17:51

    스태프만 포스트를 작성할 수 있게 만들기

    지금까지 만든 웹 사이트는 모든 사용자가 글을 쓸 수 있는 게시판 형태이다.

    그러나 이 과정에서 만드는 웹 사이트는 블로그 형태로 만들고자 한다.

    댓글은 모든 사용자가 자유롭게 작성할 수 있어도, 글은 최고 관리자 혹은 스태프에 해당하는 사람만 작성할 수 있게 만들고자 한다.

     

     

    관리자 페이지에서 Users 메뉴로 들어가보면 사용자의 등급을 일반 사용자와 스태프, 최고 관리자까지 부여할 수 있다. 이 권한 여부에 따라 포스트 작성 페이지에 접근할 수 있는지 없는지를 테스트 코드로 작성한다.

     

    먼저 tests.py에서 obama를 관리자로 권한을 부여하는 코드를 추가한다.

    일반 사용자인 trump는 포스트 작성 페이지에 접근할 수 없기 때문에 상태코드가 200이면 안된다.

    반면 obama는 관리자이기 때문에 페이지가 정상적으로 열려 상태코드가 200이 되어야 한다. 또한 obama가 로그인한 상태에서 포스트를 작성하면 성공해야 한다.

     

     

     

    테스트는 FAILED이다.

    페이지에 접근 가능한 사용자를 제한하지 않았기 때문에 trump가 접속했을 때 상태코드가 200이 나왔다.

     

     

    blog/views.py에서 PostCreate 클래스를 수정하여, UserPassesTestMixin을 인자로 추가하고, test_func() 함수를 추가해 이 페이지에 접근 가능한 사용자를 최고 관리자와 일반 관리자로 제한한다.

    form_calid()에서도 로그인한 사용자가 최고 관리자와 일반 관리자인 경우에만 동작하도록 수정한다.

     

     

    이러면 테스트 결과도 OK이다.

     

     

    이제는 관리자 페이지가 아닌 일반 블로그 페이지에서도 포스트 작성 페이지를 바로 열 수 있는 버튼을 포스트 목록 페이지에 추가한다.

    pos_tlist.html에서 if문을 통해 최고 관리자와 일반 관리자가 버튼을 클릭했을 때 href를 통해 포스트 생성 페이지로 접근하도록 만든다.

     

     

    서버를 실행해보면 정상적으로 포스트 생성버튼이 보이고, 클릭하면 포스트 생성 페이지에 접속이 된다.

     

     


     

     

    포스트 수정 페이지 만들기

    포스트 작성 페이지도 만들었으니 이번에는 만들어진 포스트를 수정하는 페이지도 추가한다.

     

     

    먼저 테스트 코드로 기본 요건을 정의한다.

    앞에서와 마찬가지로 테스트 코드에 우리가 원하는 상태를 test_update_post()함수로 작성한다.

    수정할 포스트는 미리 만든 self.post_003,URL은 self.post_003.pk이다.

     

    일반 사용자인 trump는 접근시 403오류가 발생하고, 작성자인 obama는 접근시 정상적으로 열린다.

     

    페이지가 제대로 열린다면 웹 브라우저의 타이틀이 'Edit Post - Blog'인지, 메인 영역에 Edit Post라는 문구가 있는지 확인한다.

     

    제대로 되어 있다면 title, content, category의 값을 모두 수정한 다음 POST 방식으로 update_post_url에 날린다.

     

    마지막으로 수정한 3가지 항목이 모두 반영되었는지를 확인하는 코드를 추가한다.

     

    이때, ForeignKey인 category는 self.category_music.pk뒤에 pk를 명시했다.

     

     

     

     

     

     

     

     

     

     

     

     

    테스트 결과는 당연히 Fail이다. urls.py와 views.py를 수정하지 않았기 때문이다.

    urls.py와

    views.py를 수정하고 다시 테스트를 진행한다.

     

     

    테스트 결과는 Fail. 포스트 작성자는 obama인데, trump로 로그인해도 정상적인 페이지가 보여서 실패이다.

    원래는 '권한없음'을 의미하는 403오류가 나와야 한다.

     

     

    포스트를 작성자만 수정할 수 있게 구현하기

    포스트를 수정하는 페이지는 작성자 본인만 접근할 수 있어야 한다.

    방문자가 포스트의 작성자와 동일한지 확인하고, 아닌 경우에는 권한이 없다고 오류 메시지를 나타내야 한다.

    이때 CBV에서 제공하는 dispatch() 메서드를 활용한다.

     

    dispatch() 메서드는 방문자가 웹 사이트 서버에 GET 방식으로 요청했는지 POST방식으로 요청했는지 판단하는 기능을 한다. CreateView나 현재 사용하고 있는 UpdateView의 경우 방문자가 서버에 GET방식으로 들어오면, 포스트를 작성할 수 있는 폼 페이지를 보내준다.

     

    반면에 같은 경로로 폼에 내용을 담아 POST방식으로 들어오는 경우에는 폼이 유효한지 확인하고, 문제가 없다면 데이터베이스에 내용을 저장하도록 되어 있다.

    self.get_object()는 UpdateView의 메서드로, Post.objects.get(pk=pk)와 동일한 역할을 한다.

    이렇게 가져온 author 필드가 방문자와 동일한 경우에만 dispatch() 메서드가 원래 역할을 해야 한다.

    만약 이 조건을 만족시키지 못한다면 권한없음을 나타내기 위해 raise PermissionDenied를 실행한다.

    장고는 이러한 경우에 활용할 수 있는 PermissionDenied를 미리 만들어서 제공한다.

    이렇게 되면 권한이 없는 방문자가 타인의 포스트를 수정하려고 할 때 403 오류 메시지를 나타낸다.

     

     

    다시 테스트를 진행하면 Fail이 나온다.

    Edit Post - Blog가 아니라 Create Post - Blog라고 나와서 실패이다.

    PostUpdate는 UpdateView를 상속받아 확장한 클래스이다. 

    CreateView, UpdateView는 모델명 뒤에 _form.html이 붙은 템플릿 파일을 사용하도록 기본 설정되어 있다.

    그래서 PostCreate를 작성할 때 만들어둔 post_form.html을 자동으로 찾아서 사용한 것이다.

     

    CBV로 뷰를 만들 때 template_name을 지정해 원하는 html 파일을 템플릿 파일로 설정할 수 있다.

     

    그리고 post_form.html을 복사하여 같은 위치에 붙여놓은 다음 post_update_form.html로 파일명을 수정하고,

    2개의 Create를 Edit으로 변경한다.

     

    테스트 결과는 OK이다.

     

     

    마지막으로 포스트 상세 페이지에 수정 버튼을 추가한다.

    서버를 실행하여 기능이 잘 구현되었는지 확인한다.

     

     

     

    태그 선택란 추가하기

    지금까지는 CreateView와 UpdateView로 포스트 작성과 포스트 수정 페이지를 만들 때 태그 기능은 뺀 상태로 진행했다.

    임시로 PostCreate 클래스의 fields 리스트에 'tags'를 추가하고, 포스트 작성 페이지를 열어 확인한다.

     

    이 상태에서는 태그를 선택할 순 있지만, 새로운 태그를 생성할 수 없다.

     

    post_form.html 템플릿 파일에 input을 추가하여 텍스트 형식으로 태그를 추가하도록 한다.

    웹 브라우저에서 확인해보면 새로 만든 태그 입력란이 보인다.

     

    앞에서 임시로 추가한 PostCreate에서 fields에 추가했던 tags를 제거한다.

    이제 버튼을 만들어 누르면 작성한 태그가 추가되도록 한다.

     


    먼저 테스트 코드를 작성한다.

    메인 영역에 있는 input요소가 존재하는지 확인하고, 존재한다면 form을 채워 서버에 POST방식으로 전송하기 전에 태그가 몇 개 있는지 확인한다.

    기존에는 title과 content에만 내용을 담았는데, 이번에는 tags_str에 new tag; 한글태그,python을 담아 보낸다. 세미콜론과 쉼표를 모두 사용한 이유는 두 가지 모두 구분자로 잘 작동하는지 확인하기 위함이다.

     

    그럼 기존에 있던 python과 2개의 태그 외에 'new tag'와 '한글태그'가 추가되어서 총 5개여야 한다.

     

     

    테스트 결과는 Fail. tag_str에 담은 내용을 처리하지 못해서 발생한 오류이다.

     

    post_form.html에 추가한 name='tags_str'인 input 요소에 입력된 값을 가져오기 위해서 

    views.py에 form_valid()기능을 추가한다.

    42줄 : 태그와 관련된 작업을 하기 전에 CreateView의 form_valid()함수의 결과값을 변수에 저장한다.

    44줄 : POST방식으로 전달된 정보 중 name='tags_str'인 input의 값을 가져온다.

    45줄 : 값이 빈 칸인 경우 동작할 필요가 없으므로, if문을 이용해서 값이 존재한다면 여러 개의 태그가 들어               오더라도 처리할 수 있도록 세미콜론과 쉼표 모두 구분자로 처리하여 쉼표를 세미콜론으로 변경한 후             세미콜론을 split해서 리스트형태로 tags_list에 담는다.

    52줄 : tags_list에 담겨있는 값은 문자열 형태이기 때문에 Tag모델의 인스턴스로 변환하는 과정이 필요하다.

              먼저 strip()으로 앞뒤의 공백을 제거한다.

    53줄 : 이 값을 name으로 갖는 태그가 있다면 가져오고, 없다면 새로 만든다. get_or_create()는 두 가지                   값을 동시에 return한다. 첫번째는 Tag 모델의 인스턴스이고, 두번째는 이 인스턴스가 새로 생성되었             는지를나타내는 bool 형태의 값이다.

    54줄 : 만약 새로 생성해야 한다면 아직 slug값은 없는 상태이기 때문에 slug값을 생성해야 한다.

              관리자 페이지에서 작업할 때는 자동으로 생성해 주지만, 지금은 get_or_create() 메서드로 작업하기              때문에 장고의 slugify() 라는 함수를 사용한다. slugify()를 사용하기 위해서 views.py에 import한다.

              한글 태그가 입력되더라도 처리할 수 있기 위해서 allow_unicode=True로 설정한다.

    57줄 : 이렇게 처리한 태그를 tags필드에 추가한다.

    59줄 : 작업이 끝나면 새로 만든 포스트 페이지로 이동해야 하기 때문에 response변수에 담아둔                               form_valid() 결과값을 return한다.

     

    테스트 결과는 OK.

     

     

    포스트 수정 페이지에 태그 입력란 추가하기

     

    먼저 테스트 코드를 작성한다.

    메인영역의 input요소를 찾아서 변수에 저장하고, 포스트 수정 페이지에는 기존의 태그가 저장되어 있어야 하므로 input에 post_003의 태그가 들어 있는지 확인한다.

    PostUpdate 클래스가 정상적으로 작동한다면 이 내용은 self.post_003으로 리다이렉트 된다.

     

    테스트 결과는 Fail. id가 id_tag_str인 input을 찾을 수 없다고 한다.

     

     

    post_form.html을 수정했을 때처럼 post_update_form.html에 tags_str을 담기 위한 내용을 추가한다.

     

     

     

    다시 테스트를 진행하니 value가 없다고 한다.

     

     

    tag_str_input의 value에 현재 포스트의 태그가 나열되어 있어야 사용자가 수정하지 않고 <submit>버튼을 눌렀을 때 현재 상태가 유지될 수 있다.

    즉 포스트 수정 페이지를 열었을 때 기존 태그가 자동으로 입력되도록 만들기 위해서는 post_update_form.html에서 Tags를 입력하기 위해 만든 input 요소에 value라는 속성을 추가하면 된다.

     

    tags_str_default는 아직 만들지 않았기 때문에 views.py의 PostUpdate에서 값을 넘길 수 있도록 추가한다.

    CBV로 뷰를 만들 때 템플릿으로 추가 인자를 넘기려면 get_context_data()를 이용한다.

    self.object로 가져온 tags가 존재한다면 이 태그의 이름을 리스트 형태로 담는다.

    그리고 이 리스트의 값들을 세미콜론으로 결합하여 하나의 문자열로 만든다.

    그 결과를 context['tags_str_default']에 담아 리턴하면 템플릿에서 해당 위치를 채운다.

    PostCreate때와 마찬가지로 PostUpdate에도 form_valid()를 추가하고, tags_str로 들어온 값을 처리할 수 있도록 코드를 추가한다.

     

     

    테스트 결과는 OK.

     

     


     

    외부 라이브러리를 블로그에 활용하기

    CreateView와 UpdateView를 사용해 만든 포스트 작성 페이지와 포스트 수정 페이지는 입력 폼이 한쪽에 치우쳐있어서 모양이 예쁘다고는 보기 어렵다.

    django-crispy-forms는 이 문제를 손쉽게 해결해 준다.

     

    장고를 설치할 때처럼 터미널에서 pip install django-crispy-forms로 설치한다.

     

    새로운 모듈을 설치한 후에는 do_it_django-prj/settings.py의 INSTALLED_APPS에 등록을 해야 한다.

    그리고 맨 아래에 crispy_forms의 스타일을 bootstrap4로 하겠다고 지정해야 한다.

     

     

    폼 모양 꾸미기

    이제 post_form.html을 수정하여 crispy_forms를 적용한다.

     

     

    포스트 작성 페이지가 보기 깔끔해졌다.

     

     

    같은 방식으로 post_update_form.html도 수정한다.

     

     

     

     

    마크다운 적용하기

    지금까지 만든 웹 사이트는 어떤 페이지에서도 줄바꿈이 적용되지 않았다는 치명적인 단점이 있다.

    글자 크기를 바꿀 수 없고, 내용 중간에 사진도 넣을 수 없다. 이렇게 되면 가독성 있는 글을 보여줄 수가 없다.

     

    django-markdownx를 설치하면 마크다운 문법을 적용하여 이 문제를 해결할 수 있다.

     

     

    crispy를 설치할 때처럼 터미널에서 다음과 같이 입력한다.

     

    그리고 settings.py를 열어 INSTALLED_APPS에 markdownx를 추가한다.

     

    django-markdownx는 프로젝트 폴더의 urls.py에도 경로를 하나 추가해야 원활하게 작동한다.

     

    이제 models.py에서 Post 모델의 content 필드를 TextField가 아닌 MarkdownxField로 바꾼다.

     

    모델을 수정했으니 마이그레이션을 진행해야 하지만 오류가 발생한다.

    지금 설치되어있는 장고는 최신버전인 장고4이지만 마크다운과 버전충돌이 일어나는것 같다.

    장고를 제거한 후 버전다운을 하여 다시 설치하고 마이그레이션을 진행한다.

     

     

     

    마이그레이션이 완료되면 post_form.html과 post_update_form.html에 {{ form.media }}를 추가한다.

     


    마크다운을 적용한다면 포스트를 작성할 때 실시간으로 아래에 마크다운이 적용되어 렌더링된다.

    하지만 포스트를 작성하거나 수정할 때에만 적용이 되고, 작업을 완료했을 때는 아직 적용되지 않는다.

     

     

     

     

    작성한 내용이 마크다운으로 보이도록 get_content_markdown() 메서드를 만들어 적용한다.

    먼저 models.py의 Post모델에 다음과 같이 메서드를 만든다.

     

     

    post_detail.html에서 content를 가져오던 부분을 다음과 같이 새로만든 메서드를 이용하여 수정한다.

    이렇게 하면 content필드에 마크다운 문법에 따라 저장되어 있는 텍스트를 HTML로 변환한 후 가져온다.

    이때 { | safe }도 함께 입력해 HTML 이스케이핑을 방지하는 필터도 추가한다.

     

    post_list.html도 다음과 같이 수정해 마크다운을 적용한다.

     

     

    HTML 이스케이핑 이란 마크다운 문법으로 작성된 텍스트를 HTML로 변환해 가져올 꺽쇠괄호를 사용한 텍스트를 <div>태그 안에 사용한다고 하면, HTML문법 기준으로는 렌더링하기가 모호하다.

    이런 상황을 방지하기 위해 { | safe }를 붙여준다.

     

     

    이제 관리자 페이지에서도 마크다운을 활용할 수 있도록 다음과 같이 수정한다.

     

     

     

    회원가입과 로그인 기능 추가하기

    요즘 웹 사이트들은 이메일로 회원가입을 하는 방식이 아닌 대부분 구글이나 카카오톡 등 sns계정으로 로그인하는 방식을 많이 사용한다.

    이러한 방식으로 회원가입과 로그인을 손쉽게 활용할 수 있도록 구현한다.

     

     

    먼저 django-allauth를 설치한다.

     

    allauth는 다른 라이브러리와 달리 settings.py의 INSTALLED_APPS에 여러가지를 추가해야 한다.

    다음으로 맨 아래에 AUTHENTICATION_BACKENDS 설정과 SITE_ID = 1을 추가한다.

    마지막으로 회원가입을 할 때 이메일을 반드시 받는 것으로 설정한다.

    그 이메일이 맞는지 검증하는 기능은 작동하지 않도록 설정한다.

     

     

    프로젝트 폴더의 urls.py를 열어 django_allauth가 사용할 수 있는 URL경로를 추가한다.

     

     

    django_allauth를 사용하려면 데이터베이스에도 반영을 해야한다.

    간단히 python manage.py migrate만 입력해도 된다.

     

     

     

    django_allauth는 구글뿐만 아니라 다양한 서비스 계정으로 로그인하는 기능을 제공한다.

    하지만 모든 서비스를 다 구현해 볼 수는 없으니 여기서는 구글 로그인만 구현한다.

     

     

    먼저 구글 계정으로 웹 사이트에서 로그인할 수 있게 하려면 구글에 웹 사이트를 등록해야 한다.

    구글 개발자 콘솔(https://console.cloud.google.com/projectselector2/apis/dashboard?pli=1&supportedpurview=project)에 접속한 후 새 프로젝트 생성 페이지로 이동한다.

     

    프로젝트 이름은 원하는 이름으로 결정하고 만든다.

     

    OAuth 선택화면이 나오면 User Type을 외부로 지정하고 저장한다.

    그 후 다음과 같이 앱 이름을 정하고 이메일 정보를 입력하고 저장한다.

     

    다음은 왼쪽 메뉴에서 [사용자 인증 정보]를 선택한 다음 오른쪽 상단에서 [ + 사용자 인증 정보 만들기]를 클릭하여 [ OAuth 클라이언트 ID ]를 클릭한다.

    애플리케이션 유형은 웹 애플리케이션이고, 설정해둔 이름을 작성하고, URL을 입력한다.

    위에는 웹 사이트의 주소인 https://127.0.0.1:8000을, 아래는 https://127.0.0.1:8000/accounts/google/login/callback/을 입력한다.

     

    이제 만들기를 누르면 클라이언트가 생성되며 클라이언트ID와 보안 비밀번호가 생성된다.

    이 ID와 보안 비밀번호는 따로 저장해둔다.

     

    터미널에서 로컬 서버를 실행하고, 관리자 페이지에 접속한다.

    SITES > Sites로 들어가면 example.com이라는 항목이 있는데, 이 항목을 수정해야 한다.

    현재는 settings.py의 SITE_ID = 1에 해당하는 도메인이 example.com인데, 개발용으로 사용하고 있는 로컬 서버 경로는 https://127.0.0.1:8000이다.

    Domain name: 은 로컬 주소인 127.0.0.1:8000으로, Display name: 은 원하는 대로 정한다.

     

     

    내비게이션 바에서 <Log in> 버튼을 클릭했을 때 모달이 나타나고, 모들의 왼쪽 상단에 있는

    <Log in with Google> 버튼을 클릭하는 것으로 회원가입을 할 수 있어야 한다.

     

     

     

    먼저 navbar.html을 열어 단 두줄만 수정한다.

    {% load socialaccount %}를 맨 윗줄에 추가하고, 모달의 구글로그인 버튼을 수정한다.

     

     

     

    그 다음 관리자 페이지에서 [SOCIAL ACCOUNTS] > [Social applications]로 들어가 오른쪽 위의

    버튼을 클릭하면 새로운 social application을 추가할 수 있는 페이지가 나온다.

    다음과 같이 작성하면 된다.

    Provider는 Google을, Name은 원하는 사용자 이름을, Client id와 Secret key는 앞에서 구글이 부여한 클라이언트 정보를 입력하면 된다.

    Sites에서 왼쪽에 있는 127.0.0.1:8000을 선택해 Chosen sites로 옮긴다.

    저장 후 프로젝트 폴더의 settings.py에서 맨 밑에 경로를 지정한다.

     

    127.0.0.1:8000/accounts/logout/을 입력하면 위에서 설정한 메인 페이지로 이동한다.

     

    현재 내비게이션 바에는 로그인 상태인지 아닌지 알 수 있는 방법이 없다.

    로그인 상태일 때 <Login> 버튼 위치에 username이 들어간 버튼이 나타나고, 그 버튼을 클릭하면 드롭다운 메뉴가 나타나게 만든다. 드롭다운 메뉴에는 <Log Out>버튼을 추가하고, 그 버튼을 클릭하면 /accounts/logout/으로 가도록 경로를 설정한다. 

     

    따라서 내비게이션 바의 <Dropdown link>버튼에 대한 코드를 잘라내어 <Log in>버튼에 대한 코드 바로 위에 붙여 넣는다.

     

     

     

    이메일 로그인과 회원가입 버튼 활성화를 위해 navbar.html의 모달 버튼을 수정한다.

     

    로그인 된 상태일 때 다음과 같이 유저네임이 뜨고, 드롭다운 버튼이 활성화 되어 로그아웃을 할 수 있다.

Designed by Tistory.