작정하고 Django(4)

·2023년 9월 10일

Django

목록 보기
4/8

📍 최종 목적 : 실제 가상 서버를 빌려서 그 위에다가 우리가 만든 docker 시스템을 얹는거고 docker 시스템을 만들기 위해서는 4가지 컨테이너를 구축해야함 (지금까지는 그 중에서도 장고 부분 구축하고 있었음, 장고 컨테이너 내부의 여러가지 앱들 중에서도 accountapp 만들고 있음)

accountapp 구축

Create view

view.py -> url.py ->create.html 작성

실습결과)

Login/Logout view

: 바로 urls.py에서 작성 가능

urls.py

path('login/', LoginView.as_view(template_name='accountapp/login.html'), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),

login, logout 구현 완료
장고에서 기본 제공해주는 것 사용

login.html


   <div style="text-align: center">
       <div>
           <h4>Login</h4>
       </div>
       <div>
          <form action="" method="post">
              {% csrf_token %}
              {{ form }}
              <input type ="submit" class="btn btn-primary">
          </form>
       </div>
   </div>

{% endblock %}

http://127.0.0.1:8000/accounts/create/
에서 이름이랑 비번 정한 후
http://127.0.0.1:8000/accounts/login/
을 하면
http://127.0.0.1:8000/accounts/profile/
로 감
=> login을 하고 어디로 가라는 지칭을 안했기 때문에 기본적으로 /profile/로 가게 됨

  • login, logout view에서 Redirect 하는 경로 찾는 방법 (mechanism)
  1. get/post 파라미터 중에 next value가 있다면 next로 이동

  2. next가 없으면 setting.py 안에 있는 LOGIN_REDIRECT_URL, LOGOUT 경로가 있다면 이동

  3. 1번, 2번 둘 다 없다면 default 경로로 이동

    next

<div>
      <span>nav1</span>
      <span>nav2</span>
      <span>nav3</span>
        {% if not user.is_authenticated %}
        <!--이 유저가 로그인이 돼 있지 않다면 로그인을 보여주고 나머지 로그인 돼 있다면 로그아웃을 보여줄 것-->
        <a href="{% url 'accountapp:login' %}?next={{ request.path }}">
            <!--next=~ 써서 get 방식으로 값을 넘겨줌으로써 지금 있는 페이지 어디서든 그 url을 next 인자로 넘겨줌으로써 로그인을 하고 나서 원래 자리로 돌아올 수 있게됨 -->
            <span>login</span>
        </a>
        {% else %}
        <a href="{% url 'accountapp:logout' %}?next={{ request.path }}">
            <span>logout</span>
        </a>
        {% endif %}

    </div>

login으로 next 인자 없이 직접 로그인 경로로 들어가면 여전히 default 값으로 감
방지하기 위해 login_redirect_url 사용

LOGIN_REDIRECT_URL

setting.py에서

LOGIN_REDIRECT_URL=reverse_lazy('accountapp:hello_world')
LOGOUT_REDIRECT_URL = reverse_lazy('accountapp:login')

BOOTSTRAP으로 FORM 꾸미기

bootstrap 설치 및 사용

  1. bootstrap4 설치 pip install django-bootstrap4
  2. settings.py에 INSTALLED_APPS에 bootstrap4 추가
    이제 사용 가능
    사용 방법 ) login.html 상단에 {% load bootstrap4 %} 추가
    form 대신 {% bootstrap_form form%} 추가하면 bootstrap이 적용된 폼을 사용 가능

font 파일

  1. 원하는 글꼴 다운로드
  2. static에 font 파일 만들기
  3. font 파일에 저장한 글꼴 폰트 파일 넣기
  4. head에다가 파일 불러오는 작업
    style 태그 열고 코드 작성
@font-face {
             font-family: 'NanumSquareRoundR';
             src: local('NanumSquareRoundR'),
             url("{% static 'fonts/NanumRoundR.otf' %}") format("opentype");
        }

=> 프로젝트 내부의 어떤 경로에서든 폰트들 사용 가능
5. 적용하기 (html 전체에서 base 폰트로 지정)
base.html에서 코드 작성
<body style="font-family: 'NanumSquareR';">

Read View (Detail View)

장고에서 제공해주는 이름: Detail View (게인페이지 생성)

  1. views.py에 코드 작성
class AccountDetailView(DetailView):
    model = User
    template_name = 'accountapp/detail.html'
  1. detail.html 파일 작성
{% extends 'base.html' %}

{% block content %}}

  <div>
      <div style="text-align: center; max-width: 500px; margin: 4rem auto;">
          <p>
              {{ user.date_joined }}
          </P>
          <h2>
              {{ user.username }}
          </h2>
      </div>
  </div>
{% endblock %}
  1. urls.py 경로 지정
path('detail/<int:pk>', AccountDetailView.as_view(), name='detail'),

<int:pk> 특정 유저 객체에 부여된 고유한 키, pk라는 이름의 integer 정보를 /뒤에 받겠다. (몇 번 유저에 접근할 것인지)

  1. views.py에서 코드 추가
 context_object_name = 'target_user'
  1. detail.html에도 target_user로 수정 (2개 다)
target_user.date_joined

=> 4번, 5번의 경우 다른 계정의 myPage를 들어갔을때 로그인한 유저의 Page가 아니라 선택한 그 계정의 Page가 보일 수 있도록 (다른 사람이 내 페이지에 오더라고 내 페이지의 정보 보여줌)

Update View

  1. view.py에 updateview 생성
class AccountUpdateView(UpdateView):
    model = User
    form_class = UserCreationForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'
  1. urls.py에서 경로 지정
path('update/<int:pk>', AccountUpdateView.as_view(), name='update'),
  1. update.html 생성 (create랑 다를 거 없음, create 기본으로 코드 수정)
  2. 마이페이지 들어갔을 때 페이지 주인이 본인 개인정보를 수정할 수 있는 페이지로 갈 수 있는 링크 생성 => detail.html에서 생성
{% if target_user == user %}
          <a href="{% url 'accountapp:update' pk=user.pk %}">
              <p>
                  Change Info
              </p>
          </a>
          {% endif %}

target_user가 지금 접속한 유저와 같다면 링크 보여주게 함
위의 단계까지 했다면 아이디까지 바꿀 수 있게 됨 => change info를 들어갔을 때 user name 칸을 비활성화 시켜주는 작업 필요
5. accountapp에 파이썬 파일 새로 만듦 (forms.py)

class AccountUpdateForm(UserCreationForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields['username'].disabled = True
        # username이 아이디값 속성 중에서 disabled 값 True로 바꿈
        # usercreationformdmf 상속받아 사용하는데, 마지막 줄이 없다면 usercreationform이랑 accountupdateform이랑 전혀 다르지 않은 폼
        # 한 줄이 추가됨으로써 초기화 이후에 값을 하나 바꿔줌 username칸을 비활성화 시켜줌

브라우저 창에서(개발자 도구)에서 아이디 바꿔서 보낼 수 있음 (비활성화 돼있다하더라도)
=> 그래도 바뀌지 않음!

Delete View

  1. views.py에서 delete view 생성
class AccountDeleteView(DeleteView):
    model = User
    success_url = reverse_lazy('accountapp:login')
    template_name = 'accountapp/delete.html'
  1. urls.py
path('delete/<int:pk>', AccountDeleteView.as_view(), name='delete'),
  1. delete.html 만들기
  2. detail.html
<a href="{% url 'accountapp:delete' pk=user.pk %}">
              <p>
                  Quit
              </p>
          </a>
  1. 추가적으로 header 파일에서 signup 기능 추가 해줌
 <a href="{% url 'accountapp:create' %}">
            <span>SignUp</span>
        </a>

+) bug fix
views.py에서 update랑 delete view에 context_object_name = 'target_user' 추가
delete랑 update html 파일 pk=target_user.pk로 수정(account로 돼있음)

Authentication

Account App 만든 이유 ) 처음에 만든 Hello World에 아무나 접근해서 글을 쓸 수 있다는 것이 문제 -> 중간에 '인증' 과정을 넣기 위해 account app 만듦

인증 과정 만들기

  1. view에서 hello_world에다가 인증 넣기
    사용자가 로그인했으면 정상적 작동, 아니면 로그인 창으로 되돌림
if request.user.is_authenticated:
        # request안에 user객체 있고 그 안에 is_authenticated라는 메소드 존재
    if request.method == "POST":

        temp = request.POST.get('hello_world_input')

        new_hello_world = HelloWorld()
        new_hello_world.text = temp
        new_hello_world.save()
        # DB에 helloworld 객체 저장하게 됨
        
        return HttpResponseRedirect(reverse('accountapp:hello_world'))
    else:
        hello_world_list = HelloWorld.objects.all()
        # HelloWorld에 모든 데이터를 긁어올 수 있음
        return render(request, 'accountapp/hello_world.html', context={'hello_world_list': hello_world_list})
else:
    return HttpResponseRedirect(reverse('accountapp:login'))
# 요청을 받은 객체 안에서 method를 찾고 post일 경우 기존

=> if와 else문 추가
+) 이는 accountapp에도 적용해야함 (로그인하지도 않았는데 정보 수정 페이지 들어가면 안 됨)
해결해주기 위해 views.py에서 코드 추가 (update view와 delete view 둘 다 추가)

 def get(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            return super().get(*args, **kwargs)
        else:
            return HttpResponseRedirect(reverse('accountapp:login'))
        # 로그인 돼있으면 기존의 방식대로 하되, 아니라면 login 창으로 되돌려 보내주기

    def post(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            return super().get(*args, **kwargs)
        else:
            return HttpResponseRedirect(reverse('accountapp:login'))

+) 이 상태에서 문제는 다른 사람의 정보 수정 페이지에 접근이 가능하고, 그 사람의 탈퇴 페이지까지 들어갈 수 있음 (탈퇴까지 가능)
해결해주기 위해 추가적으로 코드 작성
self.get_object() => self는 자체 뷰를 가리킴, 그 안에서 현재 사용되고 있는 object 중에서도 update view 같은 경우는 pk를 받는데, pk를 가지고 옴 (유저 객체)
그것이 지금 현재 request를 보내고 있는 유저와 같은지 확인 필요
다(update, delete view에) if self.request.user.is_authenticated and self.get_object() == self.request.user: 이 코드로 수정
그리고 else HttpResponseREdirect~가 아니라 HttpResponseForbidden()로 수정 (금지돼 있는 곳에 접근했음을 보여줌

Decorator

: 함수 앞/뒤를 꾸며줌, 함수의 내부를 고치진 않지만 앞/뒤에 붙어서 꾸며줌!(코드 가독성 상승)업로드중..

  • @method_decorator : 일반 function에 사용하는 decorator를 메서드에 사용할 수 있도록 변환해주는 decorator
  1. views.py에서 코드 decorator 사용해서 수정
@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'

@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
class AccountDeleteView(DeleteView):
    model = User
    context_object_name = 'target_user'
    success_url = reverse_lazy('accountapp:login')
    template_name = 'accountapp/delete.html'

이러면 본인인지 확인하는 과정은 삭제됨

  1. custom decorator 만들거임 -> accountapp 내부에서 decorators 파이썬 파일 만들기
from django.contrib.auth.models import User
from django.http import HttpResponseForbidden


def account_ownership_required(func):
    def decorated(request, *args, **kwargs):
        user = User.objects.get(pk=kwargs['pk'])
        if not user == request.user:
            return HttpResponseForbidden()
        return func(request, *args, **kwargs)
    return decorated

= 본인 확인 코드
요청을 받으면서 프라이머리 키로 받은 값을 가지고 있는 user object가 유저가 됨
pk를 확인해서 user object가 실제로 request 보낸 유저랑 같은지 아닌지 확인하고 아니라면 forbidden response 보냄

그리고 나서 decorator 적용 (다시 views.py)

@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
@method_decorator(account_ownership_required, 'get')
@method_decorator(account_ownership_required, 'post')
class AccountUpdateView(UpdateView):
    model = User
    context_object_name = 'target_user'
    form_class = AccountUpdateForm
    success_url = reverse_lazy('accountapp:hello_world')
    template_name = 'accountapp/update.html'

@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
@method_decorator(account_ownership_required, 'get')
@method_decorator(account_ownership_required, 'post')
class AccountDeleteView(DeleteView):
    model = User
    context_object_name = 'target_user'
    success_url = reverse_lazy('accountapp:login')
    template_name = 'accountapp/delete.html'
  • 메서드 decorator 좋은 점
    위에다가
    has_ownership = [account_ownership_required, login_required] 이런 식으로 배열을 만들고 메서드 decorator안에 하나만 넣어주면 배열 내의 있는 decorator들을 모두 확인하고 체크해줌
@method_decorator(has_ownership, 'get')
@method_decorator(has_ownership, 'post')

=> 원래 했던 인증시스템과 똑같이 동작, 훨씬 간결하게 코드 작성 가능

0개의 댓글