장고 코드리뷰

yoodeit·2022년 6월 23일
0

아무래도 처음부터 하나하나 설명하면서 만들어나가고, 개념이나 기능에 대한 예제 느낌으로 강의를 진행하다 보니 프로젝트를 만들었다가 사라졌다가 하느라 아무래도 헷갈리는 점이 많았던 것 같습니다.
그래서 좀 전체적으로 볼 수 있게 총정리를 해봤습니다.
양도 너무 많고 저도 모르는 부분도 있고 해서
아주 세세한 부분을 다 적을 수는 없고, 간단히 어디에서 어디로 이어지는지, 코드가 어떤 역할을 하는지 정도만 정리해 보았습니다.
최종적인 개념이 다 포함되어 있었던 대나무숲 프로젝트를 기준으로 정리를 진행하였습니다. 급하게 만드느라 기억나는건 적어넣긴 했는데, 대나무숲 프로젝트에 사용되지 않은 개념들이 빠져있을 수 있다는 점 양해 부탁드립니다.

그럼 지금부터 코드리뷰를 시작하겠습니다.

프로젝트명이 devsns였다고 칩시다.
기본적으로 프로젝트를 시작하면 만들어지는 devsns를 봅시다.

여기서 건드려야 할 것은 2가지. settings.py랑 urls.py입니다.

1) settings.py

(1) 일단 INSTALLED_APPS = [ ]

app을 새로 만들때마다 여기에 추가해줘야 합니다.

(2) static 관련 설정

기본적으로 웹 서비스 내부 데이터에는 Static과 Media가 있음.
Media가 사용자가 업로드한 데이터인 것에 반해 Static은 미리 준비된 데이터를 의미함.

  • STATICFILES_DIRS
    static 파일들이 내 개발환경 중 어디에 있는지를 알려주는 역할로, 그 경로를 작성하는 부분.
    위의 사진에서는 최상단의 디렉토리인 BASE_DIR 바로 밑에 static이라는 이름의 폴더에 static 파일들이 담겨 있다는 뜻임.

  • STATIC_ROOT
    static 파일들을 복사하여 모아 높을 경로를 의미함. 실제로 웹서비스를 배포할 때는 특정 폴더에 모조리 static 파일들을 복사해서 넣어둬야 함. 그때 복사한 파일들을 넣어둘 경로를 의마한다.
    아래처럼 쓰면 staticfiles라는 폴더 안에 넣어두게 됨. 그리고 python manage.py collectstatic 명령어를 쓰면 이제 ROOT에 적어둔 디렉토리로 다 복사가 된다. 물론 그냥 개발하면서 runserver할때는 절대로 필요없고 웹서비스 배포할 때나 필요하니 개발중엔 신경쓸 필요가 없다.

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
  • STATIC_URL
    static 파일을 제공할 url. 브라우저에서 static file에 접근할 때 사용할 Url이라는데 당장 개발하는데는 크게 안중요하다.

실제로 static 폴더 안에는 이렇게 다양한 것들이 들어감.

보면 css, img, js 등등 다양한 것들이 들어간다.
Static 파일들을 Html 문서에 쓰고 싶을때는 장고의 템플릿 언어를 사용한다.
기본적으로는 {% 코ㅡ드 %} 이런 모양이다.
Html 최상단에 {% load static %}을 박아두고,
css static file을 예로 들자면,
원래는 css 를 입힐 때, href='css 파일명'
이런식으로 입히던거를
href="{% static 'css 파일명' %}"
요렇게 입혀주면 static 폴더에 있는 css를 입힐 수 있게 됨.

<link href="{% static 'vendor/fontawesome-free/css/all.min.css' %}" rel="stylesheet" type="text/css">

js도 이런식으로 src="{% static 'js 파일명' %}"

<script src="{% static 'vendor/jquery/jquery.min.js' %}"></script>

(3) DATABASES = {}

외부 데이터베이스를 연동할 때는 이부분을 만져줘야 한다.
기본적으로는 sqlite3로 설정되어 있는데, 다른걸로 바꾸고 싶을 수도 있으니까 그때는 이부분을 수정하게 된다.

(4) 배포 시 관리해야 할 사항

배포를 할 때는 settings.py에서 secret key랑
외부데이터베이스 연동했으면 그 데이터베이스 Root password
이렇게 두가지는 가리고 배포를 하든 깃허브에 올리든 해야 한다.
Mysettings.py 같은걸 만들어서 거기에 담아두고 settings.py에서는 그냥 import 해오는 식으로 설계해두고 배포나 업로드할 때에는 Mysettings.py 파일만 빼고 업로드하면 된다.

2) Urls.py

기본적으로는 웹 서비스에 쓰일 url들을 싹 다 정리한 파일이라고 보면 됩니다. 특정 기능을 구현하는 새로운 창을 만들 때마다 url path가 추가된다고 보면 됩니다. 다시 말하면 Url 목록이 거의 기능 단위라는 거지요. 그러니 위의 사진에 있는 path 목록별로 하나씩 원리를 파헤치고 코드 리뷰도 해보려고 합니다.
urls.py 자체에서 알아야 할 기능은 include 하나밖에 없습니다.
나중에 url이 엄청 많아지게 될 것 같으면, django.urls에서 include를 import해준 다음에 include를 사용해서 묶어주면 됩니다.

(1)admin

장고에서 지원하는 admin 기능으로 들어가는 url입니다.

path('admin/', admin.site.urls),

말 나온김에 admin.py도 보고 갑시다.

admin을 활용하려면 일단 관리자계정을 만들었어야 합니다.

python3 manage.py createsuperuser

이걸 입력하면 됩니다.


이렇게 생겼습니다. models.py에 정의해준 클래스들을 모두 임포트하고 모두 등록해주면 됩니다.(Models.py는 나중에 다룰 일이 있으니 일단은 넘어갑시다) 그러면 이렇게 admin 사이트에서 관리할 수 있게 됩니다. 글 올리고 삭제하고 뭐 다 됩니다.

(2) home

제일 기본적인 화면입니다.

글을 업로드하고 나면 다시 메인 화면으로 돌아 와야겠죠? 바로 그 메인 화면이라고 보면 됩니다.
대충 기본적으로 메인화면에서 구현해야 하는 주요 기능은 다음과 같습니다.
첫째, 작성한 글의 목록 보여주기
둘째, 작성한 글의 제목을 클릭하면 상세페이지로 들어가게 하기
셋째, 글쓰기 버튼 만들기
넷째, 페이지네이션(페이지 나누고 옮겨다닐 수 있게 하기)
home은 home 함수를 실행시켜 index.html을 render하게 되니까 기능구현이 어떻게 되었나를 보려면
views.py의 home함수, 그리고 index.html을 보면 됩니다.

일단 url path부터 보면,

path('', views.home, name='home'),

우선 괄호 안의 인자를 순서대로 설명하자면.

첫째, ''는 http://127.0.0.1:8000 뒤에 아무것도 안붙는 것이 home의 url임을 나타냅니다.

둘째, views.home은 views.py에 있는 home함수를 실행시킴을 의미합니다.

그래야 home함수가 url에 맞는 html을 화면에 렌더링해줄테니까요.
예를 들면, home 함수는 이렇게 정의합니다.

posts = Post.objects.filter().order_by('-date')

posts 변수는 객체들의 목록을 담는 곳입니다.
블로그의 모든 글들을 대상으로는 하지만 날짜순으로 정렬해서 데리고 와서 posts변수에 담자는 뜻입니다.
그렇게 담은 posts는 render의 3번째 인자로 index.html에 넘겨줍니다.

그리고 띄워야 할 index.html은 이렇게 생겼습니다.

일단 눈에 띄는 템플릿 언어들부터 정리하자면
{% extends 'base.html' %}
{% block content %}
{% endblock %}
위의 세가지 템플릿 언어는 html 상속기능을 이용하느라 그렇습니다.
index.html은 base.html을 상속한다는 뜻이고
block content부터 endblock까지는 base.html과 다른, 추가적인 부분을 의미합니다.
그밖에 html에서도 if 조건문이나 for 반복문을 사용하고 싶을 때도 템플릿 언어를 사용할 수 있습니다.
index.html 내에서 몇가지를 살펴보자면

여기서 템플릿언어를 통해 for반복문을 사용합니다.
아까 views.py에 있던 home함수에서
render의 3번째 인자로 넘겨준 posts에서 하나씩 꺼내 post로 넣고 이하의 코드를 실행하는 모습입니다. 그 객체에 있는 제목, 그 객체에 있는 날짜 등을 띄우는 겁니다.
참고로 td태그는 table 태그 중 하나입니다.

그리고 여기서는 템플릿언어로 if 조건문을 사용합니다.
로그인이 된 상태면 글쓰기 버튼이 생기고, 아니면 글쓰기 버튼이 없도록 조건문을 작성한 것입니다.

셋째, name='home'은 지어준 별명 같은겁니다.

나중에 html에서 a태그를 통해 다른 url로 이동해야 할 때 템플릿 언어와 name으로 지정된 것을 활용하면 됩니다.

아까 썼던 사진을 다시 보면, a태그에 name='detail'로 지정되어 있는 url이 사용되는 것을 볼 수 있습니다.

<a href="{% url 'detail' post.id %}">{{ post.title }}</a>

*익명게시판 말고 자유게시판의 메인페이지는 freehome으로 별 차이 없습니다.

path('freehome/', views.freehome, name='freehome'),

(3) postcreate

사용자가 글쓰기 하는 기능/업로드 하는 기능.

path('postcreate', views.postcreate, name='postcreate'),

보면 http://127.0.0.1:8000/postcreate 라는 url에 들어가면 views.py에 있는 postcreate 함수가 실행됨을 알 수 있다.

[index.html]

메인 화면에서 멀뚱멀뚱 있는다고 글쓰기가 되는 건 아닐테니까, 써놓은 글의 목록을 띄워두는 메인화면 말고, 그 밑에 글쓰기 버튼을 구현해줘야 합니다. 메인화면에 글쓰기 버튼을 구현해야 하니까 Index.html에서 코딩합니다.


index.html에 구현된 글쓰기 버튼을 보면 a태그로 감싸져 있으며,

이 버튼을 누르면

이 화면이 보이게끔

http://127.0.0.1:8000/postcreate 로 이동할 수 있도록 설계되어 있다.

[views.py]

그리고 postrcreate url에서 실행될 postcreate함수는 어떻게 생겼는지를 보면 이렇게 생겼다.

참고로 사용자에게 입력을 받는 방법에는 3가지가 있었다.
1) ???
2)???
3)???
근데 그중에서 3번째 방법, 그러니까 ModelForm을 사용하고 있을 뿐이다. 왜냐하면 3번째 방법에서는 한 함수가 get요청과 post요청을 모두 다룰 수 있기 때문이다.

그래서 실제로 보면 조건문을 이용해 2가지 기능을 분리해뒀다.

만약 get요청이 들어온다면, render를 통해 post_form.html을 띄워주라는 코든데, render의 세 번째 인자에 주목해야 한다.

우선적으로

form = PostForm()

이렇게 form이라는 변수에 PostForm()을 담아준다.

[forms.py]

근데 PostForm()이 뭐냐. 그건 forms.py에서 만든 클래스다.

forms.py는 사용자로부터 어떤 종류의 데이터를 입력받을 것인지를 정해둘 뿐 아니라, 실제로 입력받았을 때 데이터베이스에 전달하는 일종의 분류칸이 있는 통 혹은 캐리어쯤 된다.

PostForm은 다음과 같이 클래스로 정의해준 바 있다.

여기서 주목해야 할 것은 class Meta까지만이다.
그 밑은 Bootstrap을 적용하기 위해서는 init을 활용한 생성자를 만드는 과정이다.

이쯤에서 장고에서 form을 어떻게 동작시키는지 그 원리를 잠깐 살펴보고자 한다.

기본적으로 글을 쓰든, 댓글을 쓰든 사용자가 form을 이용해야 입력을 받아서 데이터베이스에 저장을 하고 띄워줄 수가 있다.
그러니 form의 개념적인 데이터 흐름은 다음과 같다.
1) 사용자가 form이 있는 페이지를 호출한다.
2) 이게 첫 호출이면 빈 form을 사용자에게 보여준다.
3) 여기서 사용자가 form을 채워서 제출하면
4) 이 데이터를 검증하고, 유효할 경우 데이터베이스에 저장하고 home으로 되돌린다.
5) 유효하지 않으면 에러 메시지를 띄우고 다시 form 입력 페이지로 되돌린다.

[post_form.html]

그럼 이제 화면에 띄워줄 post_form.html이 어떻게 작성되었나 보자.

form태그는 보안 문제때문에 반드시 csrf_token이 있어야 한다. 그리고 table태그로 감싼 그 안에는 postcreate함수에서 render의 세 번째 인자로 함께 넘겨주었던 form을 table 형식으로 나타냄을 의미.
그리고 밑에 제출 버튼을 만들고 폼태그를 닫아주었다.
실제로 나타난 모습은 아래와 같다.

그럼 이제 나타난 양식에 내용을 다 쓰고 제출버튼을 누르면 어떻게 될 것인지, 즉 요청이 POST일 경우를 다시 보자.

요청받은 메서드가 post거나 files면
form이라는 변수에 받은 요청을 PostForm의 형태로 넣어 주고
그 form이 유효하다면 저장. 그리고 home으로 되돌아간다.

(4) detail

지금까지 메인화면에서 글의 목록을 보고, 글쓰기 버튼을 눌러서 글을 쓰는 것까지는 구현이 되었다.
이제 상세보기가 필요하다.

일단 detail쪽 url은 path가 이렇게 생겼다.

path('detail/<int:post_id>', views.detail, name='detail'),

나머지는 비슷하고, 첫 번째 인자가 이상하게 생겼는데 그건 아무래도 상세페이지니까 만들어진 게시물마다 url이 따로 존재해야 하기 때문이다. 즉 게시물들의 각 고윳값을 detail/ 뒤에 적어줌으로써 게시물 수만큼 디테일페이지의 url이 생기게 된다.

[views.py]
여기서 정의된 detail 함수는 다음과 같이 생겼다.

보면 위에서 봤던 Home함수나 postcreate함수는 request만 받으면 됐는데, 얘는 post_id도 함께 받아야 하는 함수다.

post_detail = get_object_or_404(Post, pk=post_id)

를 통해 Post에 담긴 객체들 중에서, pk값이 post_id인 객체가 있으면 그녀석을 post_detail에 담아준다. pk값은 Primary Key로 id를 의미하고, post_id는 함수가 받아온 값으로, 몇 번째로 그 객체가 만들어졌는지를 자동으로 알려주는 그런 역할을 한다.

다음에는

comment_form = CommentForm()

이걸 통해서 comment_form이라는 변수에 CommentForm()을 담아줍니다.
그리고 마지막으로

return render(request, 'detail.html', {'post_detail':post_detail, 'comment_form': comment_form})

이걸로 detail.html을 렌더링해준다. 이때 Render의 3번째 인자로 post_detail, 다시말하면 pk=post_id인 객체와 comment 객체를 detail.html로 넘겨준다.

[forms.py]

아까 comment_form 변수에 담아주었던 CommentForm()은 forms.py에서 정의해준 클래스이다.

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['comment']

    def __init__(self, *args, **kwargs):
        super(CommentForm, self).__init__(*args, **kwargs)

        self.fields['comment'].widget.attrs = {
            'class': 'form-control',
            'placeholder': "댓글을 입력해주세요",
            'rows': 10
        }

[detail.html]

일단 코드는 이렇고

구현된 모습은

이렇다.

보면 디테일페이지는 2가지 기능을 한다.
일단 작성해둔 글의 제목, 날짜, 본문을 띄워주는 기능.

<h2 class="alert alert-primary"> {{ post_detail.title }}</h2>
<span class="badge rounded-pill bg-light">{{ post_detail.date}}</span>
<br/><hr/>
<p>
    {{ post_detail.body }}
</p>
<br/><hr/>

기본적으로 base.html을 상속하고
아까 detail 함수에서 render의 3번째 인자로 넘겨주었던 post_detail을 템플릿 언어로 감싸서 활용한다.
post_detail.title을 h2태그로 감싸서 맨 위에 띄우고
그다음에 span태그로 감싸서 post_detail.date를 띄우고
마지막으로 P태그로 감싸서 post_detail.body를 띄운다.

다음은 댓글 관련 기능인데, 작성된 댓글을 열람할 수 있는 댓글 목록을 구현해야 하고, 댓글을 작성할 수 있는 댓글 입력창을 만들어야 한다.

먼저 작성된 댓글을 열람할 수 있는 댓글 목록은 다음과 같이 구현한다.

{% for comment in post_detail.comment_set.all %}
<span class="badge rounded-pill bg-light">{{ comment.date}}</span>    
{{ comment }}
<hr>
{% endfor %}

post_detail이라는 객체(개별 게시물 하나하나)를 참조하는 댓글 집합 전체를 순회하도록 템플릿 언어를 활용해 for문을 구성하면 된다.
post_detail은 아까 원하는 특정 pk값을 가지고 있는 게시물 객체였다.
post_detail.comment는 그 객체에 종속적인 comment 모델을 의미한다.
post_detail.comment_set 은 그 comment 객체의 집합을 의미하고
post_detail.comment_set.all 은 comment 객체 집합의 전체를 의미한다.
그렇게 for comment in post_detail.comment_set.all 이라는 반복문을 쓰게 되면 post_detail 객체를 참조하는 모든 댓글 객체들이 반복적으로 comment 안에 들어가게 되고 그거 하나하나마다 comment.date와 comment를 띄워주게 되기 때문에 댓글 목록을 구현할 수 있게 된다.

마지막으로 댓글을 입력하는 공간이다.

<form method="POST" action="{% url 'new_comment' post_detail.id  %}">
    {% csrf_token %}
    <div class="form-group">
        {{ comment_form }}
    </div>
    <input type="submit" class="btn btn-primary btn-icon-split btn-sm" value="댓글 입력">
</form>

입력을 받으니까 당연히 form태그를 쓰고, 당연히 csrf_token이 들어가고, 메서드는 당연히 POST이다.
action은 이 form 태그로 받은 post요청을 어디로 넘겨줄지와 관련한 사항이다. new_comment라는 url에 form태그로 받은 post 요청과 함께 post_detail.id를 같이 넘겨주게 될 것이다.
comment_form을 div태그로 감싸서 넣어주고, input태그로 제출버튼을 만들어준 후에 form태그를 닫아주면 끝난다.

이렇게까지 하면 상세페이지 구현도 끝.

(5) new_comment
댓글 저장 기능이다. 아까 detail.html에서 댓글 입력을 받았고, 그 입력받은 댓글을 new_comment라는 url에 넘겨준다고 한 바 있다.
코드는 이렇게 생겼고

댓글 post 요청을 처리하면 되는데,

filled_form = CommentForm(request.POST)

으로 CommentForm 으로부터 post형식으로 받은 요청을 filled_form에 넣어주고

if filled_form.is_valid():
        finished_form = filled_form.save(commit=False)

으로 그게 유효하면 저장하되 filled_form.save(commit=False)를 통해 잠시 저장하지 않고 finished_form이라는 새로운 변수에 담는다.
그리고

finished_form.post = get_object_or_404(Post, pk=post_id)
finished_form.save()

Post객체 중에 pk=post_id인 객체를 finished_form의 외부키. 그러니까 finished_form이 pk=post_id인 객체를 참조할 수 있도록 코드를 짜 주고, 그제서야 finished_form을 저장한다.
마지막으로

return redirect('detail', post_id)

이제 다시 detail url로 이동하는데, 방금까지 작업한 post_id를 갖고 있는 상세페이지로 이동한다.(댓글 잘 써놓고 메인페이지로 튕기면 안되니까. 그 댓글을 단 상세피이지로 이동해야 이치에 맞다)

일단 여기까지가 제일 기본적인 사항인 것 같습니다.
시간관계상 나머지 기능들은 나중에 벨로그에 글로만 게시하게 될 것 같습니다.

profile
Yoodeit

0개의 댓글