6. 장고 모델과 파이썬 문법을 활용하여 투표 목록 보여주기

등록일 : 2020.12.15. 19:29



지난 시간 돌아보기

이전 파트2-2에서는 장고 모델을 활용하여 투표 앱의 질문과 선택지가 될 Question과 Choice를 모델로 만들고, 이를 실제로 DB에 적용하기 위한 작업을 했다. 그리고 장고의 막강한 Admin 페이지로 이동하여 생성한 Superuser로 로그인하여 Question, Choice를 admin에서 관리할 수 있도록 설정했다. admin에서 +add 버튼을 통해 실제 질문과 답변까지 등록해 보았다.

투표 목록 가져오기 위한 views.py 수정하기

이제 투표 목록을 볼 수 있는 화면을 한번 만들어 볼까 한다. 현재 입력된 투표 질문 하나지만, 시간이 가면 다양한 내용의 투표가 다양해질 것을 대비해 가정하고 처리해보자.

사용자의 요청을 받아서 처리해주는 곳이 view.py였다. 처음 우리는 view.py의 index 함수를 통해 "Hello, world. You're at the polls index." 텍스트를 출력했었다. 해당 문장 대신 Question의 text를 뿌려 주려면 어떻게 해야 할까? 쉽다. 출력된 텍스트 대신 Question의 question_text를 출력해주면 되겠다.

view.py를 다음과 같이 수정해보자. views.py 수정

from django.http import HttpResponse
from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

그리고 http://127.0.0.1:8000/polls/ 에 접속해 본다.

우리가 등록한 question_text가 출력되었다. 이제 한 줄 씩 살펴보자.

위의 코드에서 두번째 줄의 from .models import Question을 통해 우리는 model.py의 Question을 가져다 쓰기 위해 불러 온 것을 확인할 수 있다.

자세히 살펴보면 url.py 파일에서 url과 'views.어떤함수'로 보통 연결을 해준다고 지난 시간에 배웠다. 따라서 해당 url.py → views.py의 index 함수에 도착한다. 그다음 함수안의 첫 번째 문장인 latest_question_list = Question.objects.order_by('-pub_date')[:5]에 도달하고 이를 수행한다.

다음의 예시를 잠깐 보고 넘어가자.

# ez2django의 한글 이름 정의하기
easytodjango_kor_name = '이지 투 장고'

# ez2django의 버전 지정하기
easytodjango_version = 1

이렇게 종류에 따라 이름을 지정할 수 있다. ''기호로 묶인 부분은 문자열을 나타낸다. ""으로 묶어도 동일하다. 정답은 없지만 본 예제에서는 ''로 처리한다. 아무것도 없이 ''만 있다면 빈 문자열로 생각하면 된다.

두번째는 숫자형식이다. 1의 경우는 숫자도 될 수 있고 문자도 될 수 있다. '1'로 사용하지 않는 이상 숫자형이 기본으로 된다. 숫자는 수학적인 계산이 가능하고, 문자열일 경우는 사용 방법이 달라진다.

그외에도 많은 구조가 있지만 ,일단은 두 개만 알고 가자.

위의 예제에서 = 기호를 기준으로 오른쪽은 값이나 처리 결과를, 왼쪽은 이름을 붙이는 경우가 일반적이다. 프로그램을 제작할 때 필요한 값들을 이름을 붙여주는 일이 프로그래밍의 시작이며 많은 비중을 차지한다. 내가 사용할 데이터들이 무엇인지 정의하는 활동이다. 예를 들어보자. 이러한 이름 부여는 개발자가 필요에 의해 정의하여 사용한다.

다음의 예시를 보자.

# 예시 1
lastest_q_list = Question.objects.order_by('-pub_date')[:5]

# 예시 2
latest_question_list = Question.objects.order_by('-pub_date')[:5]

# 예시 3
my_questions = Question.objects.order_by('-pub_date')[:5]

중에 어떤 게 더 알아보기 쉬울까? 첫 번째 이름이 더 간결하긴 하지만 두 번째 이름이 가장 의미를 잘 나타내 주고 있다. 짧게 쓰는 것이 좋아 보일 수 있지만, 너무 짧으면 알아보기가 어려울 수도 있다. 너무 길어도 장황해진다. 정답은 없지만 우리는 '가독성'관점에서 생각해야 한다. 나중에 만든 코드를 다시 보았을 때 본인이 만든 코드임에도 본인도 알아보기 어렵고 이해하기 힘든 경우가 많다. 실제 코드를 작성한 후 수정하는 시간보다 이것을 다시 이해하는데 많은 시간이 소요되고 낭비된다. 어렵게 짠 코드일수록 이는 더욱 문제가 된다. 하루아침에 되는 것은 아니지만 깔끔하게 작성하도록 노력해보자.

조금만 더 설명을 추가하자면 이런 이름들은 규칙이 있다. 파이썬(.py)의 규칙으로는 latest-question-listlatest question list 처럼 하이픈이나 공백을 사용할 수 없다. 하이픈은 -연산자(operator)로 사용되기로 이미 약속이 되어 있기 때문이고 공백은 보통 구분을 위해 사용되기 때문에 이름 지정에서는 허용되지 않는다. Python 3 버전에서 변수명은 영문 대소문자, 숫자, 한글 _(언더스코어) 등이 가능하다. 주로 사용자가 정의하는 이름이나 함수는 영문 소문자,숫자,언더스코어(_)만 사용하는 경우가 많다. 함수이름을 정의 할 때도 동일하게 쓰인다. 영문 대문자,숫자,언더스코어(_) 조합은 고정된 값을 의미하는 용도로 쓰인다. 예외적인 경우도 물론 있지만 특별한 이유가 없는 경우 이를 따르도록 하자.

Python에서 변수 이름과 함수 이름에 쓰이는 이름 정의 규칙을 snake_case라고 한다.

Question.objects에 대해 알아보기

Question.objects.order_by('-pub_date')[:5]

위의 문장을 살펴보자. 위에서는 Question의 경우는 대문자이다. 일반적인 숫자, 문자, 결과 등을 명명하기 위한 부분은 소문자로 쓰이지만 좀 더 상위 개념인 클래스 개념이 있다. model.py에서 정의한 Qustion을 찾아보자. 앞에 class로 시작하는 부분이 보일 것이다. 이전에 함수는 def로 시작한다는 것을 알았다. class로 정의 한 부분은 def안에 선언한 것처럼 class내부에 여러 이름이 모여 있을 수도 있고, class는 def를 포함할 수 있다. 이런 것들을 묶어 하나의 큰 개념으로 사용하는 것으로 = 로 정의한 이름, def 로 정의한 이름과 구분하기 위해 선언할 때 단어의 첫 글자를 대문자로 쓰고 _를 사용하지 않는다. 따라서 대문자/소문자만 봐도 어떤 개념인지 구분할 수 있게 돕는 것이다.

Python에서 클래스 이름에 쓰이는 이름 정의 규칙을 CamelCase라고 한다.

model.py에서 각각의 모델을 만들고 makemigrations 명령어와 migrate를 통해 DB에 적용하고, admin에서 실제 데이터를 입력했다. 그러면 이를 다시 가져와 views.py에서 사용해야 할 것이다. 장고에서 DB와 연결하여 관리해주는 매니저가 존재한다. 이를 objects라고 부른다. 우리가 정의한 Question.objects, Choice.objects로 호출하여 사용할 수 있다. 여기서의 object용어는 프로그래밍에서 여러가지 의미로 쓰이지만 여기서의 모델 뒤에 오는 object는 모델의 실제 데이터를 가져와 주는 매니저 정도로 생각하자. 우리는 이제 모델 매니저를 통해 데이터를 가져올 수 있게 되었다.

Question.objects.order_by()에 대해 알아보기

objects 뒤에 붙은 order_by()에 대해 알아보자. 데이터가 두개 이상일 경우는 어떤 순서로 먼저 보여줄지 정리해야 한다. order_by()는 순서를 정렬할 때 쓰인다. 보통 여러분이 웹이나 앱에서 게시판을 볼 때 날짜 순(최신 순), 조회수 순, 좋아요 순으로 표시되는 부분을 보았을 것이다. 정렬 규칙이 없어 컴퓨터가 임의로 매번 데이터를 뒤죽박죽 보여준다면 매우 불편한 앱이 될 것이다.

우리는 DB Question 테이블에 question_text와 pub_date 필드를 생성해두었다. 이를 활용하여 날짜 순으로 정렬해보겠다. 현재는 투표 질문이 하나밖에 없지만 추후에 여러 투표 질문들을 생성해보고 order_by() 안의 내용을 변경해 보면 알 수 있을 것이다.

order_by() 안에는 DB 테이블 필드 이름을 넣으면 이를 기준으로 기본 오름차순으로 정렬해준다. order_by('pub_date')로 정렬 기준을 pub_date로 지정했으므로 해당 필드의 값들을 오름차순으로 정렬된다. 상황에 따라 최신 데이터, 즉 최신 배포 순으로 보고 싶을 수 있다. 이 경우 정렬을 내림차순으로 보여주면 된다. order_by('-pub_date') 로 칼럼 값앞에 -가 붙이면 내림차순 정렬이다. 가장 먼 최근(또는 미래) 날짜가 맨 첫 번째로 오도록 하고 그다음 순서대로 정렬하여 목록을 가져와 준다.

Question.objects.order_by('-pub_date')만 본다면 날짜 내림차순으로 [ 최신 Question, 그다음 Question, ..... ] 순서로 정렬된 목록을 가져올 것이다.

Question.objects.order_by('-pub_date')[:5]에 대해 알아보기

Question에 등록된 질문이 쌓여 10개가 되었다고 생각해 보자. 데이터가 너무 많으니 최신 글만 보여주고 싶다. 예로 10개의 데이터가 있고 이를 order_by('-pub_date')로 최신 순서대로 가져온 값이 [ Quesiton 10, 9, 8, 7, ... 1 ] 이라고 치자.

Question.objects.order_by('-pub_date') 뒤에 [:5] 를 붙이면 앞에서부터 5번째까지만 사용한다. 결과는[ Quesiton 10, 9, 8, 7, 6 ] 까지만 가져오게 되겠다. 다양한 홈페이지들이나 게시판을 보면 최신 글 5개 정도만 보여주고, 나머지는 더보기를 통해 불러오는 경우를 볼 수 있다. 투표 갯수가 수없이 많아진다면 한 화면에 다 보여주기에 시간도 오래 걸리고 보기도 좋지 않을 수 있다. [:5] 는 파이썬의 문법으로 전체 중에 일부를 잘라주는(슬라이싱) 역할을 한다.

따라서 Question.objects.order_by('-pub_date')[:5] 는 pub_date기준의 최신 5개의 Question 만 가져오도록 하는 것이다. 숫자를 3으로 바꾸면 최신 Question을 3개만 가져온다.

Question.objects.order_by('-pub_date')[:5]의 이름 정의하기

latest_question_list = Question.objects.order_by('-pub_date')[:5]# latest_question_list는 pub_date 기준으로 내림차순 정렬하여 최신 5개의 question만 가리킨다

그렇게 꺼내온 5개의 투표 질문을 latest_question_list 라는 이름을 붙여 정의했다. 파이썬에서는 = equal 기호가 이렇게 활용되는 것이다. 보통 이런 문장을 선언문이라고 한다. 프로그래밍 언어마다 다른 경우도 있겠지만 많은 언어가 = 기호는 선언문으로 쓰인다. 결국은 여기서의 latest_question_list를 우리가 새로 정의 해 주었기 때문에 DB에서 날짜순으로 정렬된 최근 5개 Question을 말하는 것이다.

latest_question_list에서 반복문을 통해 question_text만 가져오기

그다음 문장을 보자.

output = ', '.join([q.question_text for q in latest_question_list])

조금 어렵고 함축된 문장으로 많은 분석이 필요한 부분이다.

우선 latest_question_list를 정의하여 최신 5개의 투표 질문을 가져왔지만, 이것은 각 Question에 대한 모든 정보를 가리키고 있다. 이 안에는 각각 개별 Question text나 pub_date가 모두 포함되어있고 그중에 우리는 질문 제목 리스트만 가져오면 된다. 그러므로 Question에 대해 이번엔 제목만 가져와 보자. latest_question_list는 5개의 질문 목록을 가져왔으므로 이를 하나씩 처리할 필요가 있다.

# 예시
1 최신 투표에서 투표 제목 출력하기
2 최신 투표에서 투표 제목 출력하기
3 최신 투표에서 투표 제목 출력하기
# ... 중략
# 투표가 100개라면..?

위는 실제 코드는 아니지만, 사람이 코드를 작성하기 전 이런식으로 짜겠다라는 것을 가정하고 작성한 것이다.

이런식으로 하나씩 반복적으로 사용되는 경우 개수만큼 프로그래밍 문장이 늘어날 것이다. 엑셀 시트에 각 세로로 번호를 매길 때 1 엔터, 2 엔터, 3 엔터... 이렇게 수동으로 입력 하는 경우 매우 불편함을 알 수 있다. 프로그래밍 언어의 핵심이자 기초 중에 기초인 '반복문'을 활용하면 이런 귀차니즘을 쉽게 해결할 수 있다.

장고 쉘 접속하여 DB데이터를 반복문으로 순환하기

장고 쉘을 활용하여 실제 DB에서 데이터를 가져와 보고 웹에 출력하기 전에 터미널에서 출력해보면 흐름을 알기가 편하다. 장고 쉘은 다음과 같이 접속할 수 있다. 파이참 터미널에 + 를 눌러 새로운 터미널을 추가하자. 터미널에 python manage.py shell명령어를 입력해 장고 쉘에 접속해보자.

(venv) $ python manage.py shell
Python 3.7.7 (default, Apr 29 2020, 09:50:19) 
[Clang 11.0.0 (clang-1100.0.33.12)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

쉘에 진입하면 >>> 표시가 나타난다. 첫 시간 python 버전을 확인하기 위해 접속했었던 파이썬 인터프리터 화면과 같은 모습일 것이다. 여기에 우리가 작성한 코드를 한 줄씩 입력해보고 출력도 해보자.

>>> from polls.models import Question
>>> latest_question_list = Question.objects.order_by('-pub_date')[:5]
>>> latest_question_list
<QuerySet [<Question: Question object (1)>]>

현재까지는 Question에 하나가 있었지만 이제 여러 투표 목록을 보여주기 위해 Question을 추가하자.

Django admin 페이지에 접속해 만들어 둔 슈퍼유저로 로그인하고 Question메뉴를 진입해 Add버튼을 통해 등록해보자. 현재 하나의 투표가 등록되어 있으므로 이는 그대로 두고, 4개의 최신 투표를 더 추가한다. 이름은 다음과 같이 순서대로 총 4개를 최신 투표 2, 최신 투표 3, 최신 투표 4, 최신 투표 5로 등록해보자. Date published의 Date / Time도 Today 버튼과 now 버튼을 각각 클릭하여 입력한다.

그리고 다시 다음과 같이 한 줄씩 다시 입력해보자.

>>> latest_question_list = Question.objects.order_by('-pub_date')[:5]
>>> latest_question_list
<QuerySet [<Question: Question object (5)>, <Question: Question object (4)>, <Question: Question object (3)>, <Question: Question object (2)>, <Question: Question object (1)>]>
>>>

Python의 for문으로 반복하는 일 시키기

위에 보이는 QuerySet은 DB에서 가져온 object 묶음이다. 이 안에는 우리가 정의한 필드의 정보와 값들을 가지고 있다. 이를 하나씩 반복하여 접근할 필요가 있다. 프로그래밍에서는 반복작업을 해주는 반복문이라는 것이 존재한다. python에서는 다음과 같이 사용한다.

위의 쉘 입력에 이어서 입력

>>> for q in latest_question_list:
...     q
... 
<Question: Question object (5)>
<Question: Question object (4)>
<Question: Question object (3)>
<Question: Question object (2)>
<Question: Question object (1)>
>>>

위의 입력에 이어서 화면에 입력해 보자. for문 첫 문장 입력 후 엔터를 치자. 두 번째 줄... 다음에는 앞에 4칸의 공백을 입력해야 한다. 이는 파이썬 언어의 입력 규칙이며 for문 안에 포함되는 반복할 내용이라는 뜻이다. 스페이스바로 4칸을 띄우고 q를 입력해주면 된다. 세 번째 줄 ... 상태에서는 그냥 엔터를 누른다. for문의 반복할 내용이 끝임을 알려준다.

for q in latest_question_list 첫 번째 문장의 의미는 DB에서 가져온 latest_question_listQuestion object를 하나씩 순환하겠다는 뜻이다. 우리는 위의 쉘에서 확인한 그대로 latest_question_list에 5개의 오브젝트를 순차적으로 접근했다. for문 안의 문장은 조건에 따라 반복하게 된다. 처음 q는 Question object (5)를 가리키게 될 것이고 문장이 모두 수행되면, 그 다음 latest_question_list의 오브젝트를 의미하게 된다. 그리고 리스트의 마지막까지 탐색 후 종료될 것이다. 이렇게 순차적으로 Question object에 접근했지만 우리는 이중에 question_text만 출력하기를 원한다. Question object에 .필드이름을 붙이면 접근할 수 있다.

>>> for q in latest_question_list:
...     q.question_text
... 
'최신 투표 5'
'최신 투표 4'
'최신 투표 3'
'최신 투표 2'
'선호하는 떡볶이 맵기 강도는?'
>>>

그러면 question 오브젝트의 각 제목을 순차적으로 가져오게 된다. q.question_text 문장은 한 줄이지만, q는 latest_question_list의 마지막 오브젝트에 도달할 때까지 각 오브젝트를 순회하게 된다.

>>> latest_question_list = Question.objects.order_by('-pub_date')[:5]
>>> [q.question_text for q in latest_question_list]
['최신 투표 5', '최신 투표 4', '최신 투표 3', '최신 투표 2', '선호하는 떡볶이 맵기 강도는?']

그다음 예제를 보자. for문 안에 있던 q.question_text가 for문 앞에 왔다. 그리고 []기호로 감싸져 있다. 우리는 이전에 polls/urls.py의 urlpatterns = []와 mysite/settings.py의 INSTALLED_APPS = []를 변경해본 적이 있다. 이 이름들의 =다음에 []로 쌓여 있는 것을 알 수 있고 그안의 항목은 ,로 구분되고 있다. []안에 내용을 추가할 때 맨 마지막에 ,를 잘 입력해달라고 강조했었다. 눈썰미가 좋은 독자라면 눈치챘을 것이다. 이를 파이썬에서는 리스트라고 하며 한 개 이상의 데이터를 한번에 다룰 때 주로 사용한다.

[] 안에 q.question_text for q in latest_question_list 등의 표현식이 사용되면 for문 앞의 표현식으로 리스트를 만들어 준다 정도로 생각하자. 당장은 깊게 이해하지 않아도 된다.

>>> ''.join([q.question_text for q in latest_question_list])
'최신 투표 5최신 투표 4최신 투표 3최신 투표 2선호하는 떡볶이 맵기 강도는?'
>>> ', '.join([q.question_text for q in latest_question_list])
'최신 투표 5, 최신 투표 4, 최신 투표 3, 최신 투표 2, 선호하는 떡볶이 맵기 강도는?'

위의 두개의 예시를 직접 입력 해보자. 차이를 금방 알 수 있을 것이다. ''.join() 안에 리스트를 넣었다. ''는 위에서 빈 문자열이 된다고 살짝 이야기했었다. 빈 문자열에 모든 question_text를 합쳤더니 구별이 되지 않는다. 따라서 콤마와 공백을 사이사이 추가했다. ', '.join() 으로 인해 각 question_text가 보기 좋게 출력되었다. 리스트에서는 각각이 논리적으로 개별 데이터이다. 여기서는 이를 한 번에 한 문장으로 보여주기 위해 하나의 데이터로 합친 것이다. 모두 완료되었으면 exit() 명령어로 빠져나온다.

다시 원래의 코드를 보자.

polls/views.py

from django.http import HttpResponse
from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

join()으로 취합한 데이터를 output이라는 이름으로 붙여줬고 이를 HttpResponse(output)으로 응답해 주었다.

그리고 http://127.0.0.1:8000/polls/ 에 다시 접속해 브라우저를 새로고침 해보자. 우리가 원하는 투표 목록이 한 줄로 잘 출력 되었을 것이다.

이 부분은 파이썬 지식이 없으면 다소 어려울 수 있다. 나중에 알고 나면 정말 쉬우니 포기하지 말자. 이 부분을 몰라도 다음 진행을 하는데 어려움이 없다. 추후에 복습하면서 다시 익히자.

이번 장에서 views.py에서 DB 테이블의 특정 데이터 목록을 가져오고, 정렬하고, 일부만 잘라보았다. 그리고 파이썬의 선언문, 반복문을 활용해 데이터를 리스트로 만들고 문자열로 합쳐 출력해보는 방법을 알아보았다. 조금 쉬었다가 다음 장으로 가도록 하자.