지금까지 만든 웹사이트에는 사실 한 가지 결함이 있다. 위와 같이 게시물의 번호가 페이지에 무관하게 1부터 시작한다.  정상적인 게시물의 번호는 1페이지의 맨 위의 게시물의 번호가 게시물의 총 갯수와 같으며 그 이후로 1씩 줄어야 한다.

그렇다면 게시물의 번호는 <전체 페이지 수> - <시작 인덱스> - <게시물 인덱스> + 1 이 된다. 여기서 시작 인덱스는

해당 페이지의 첫 게시물의 번호이다.  1페이지라면 시작 인덱스는 1,  2페이지라면 11이 된다.   

 

1.  템플릿 필터 작성

 게시물 번호 공식을 템플릿 코드에 적용하기 위해서는 페이징 영역의 페이지 표시범위를 전후 5 페이지 까지로

제한했을 때 처럼 | add:5 와 같은 템플릿 필터를 사용해야한다.  문제는 add 는 값으로 변수를 넘겨줄 수 없다는 것이다.

먼저 원하는 값 만큼을 뺄 수 있는 템플릿 코드를 직접 작성해보자.

 

먼저 pybo 앱 디렉터리 하위에 템플릿 필터 파일을 저장할 templatetags 디렉터리를 생성한다.

 

생성한 디렉터리에 pybo_filter.py 파일을 생성한 뒤 위와 같이 작성한다.  데코레이터 @register.filter 를 추가하는 것으로

sub 함수를 필터로 추가할 수 있다.

 

 

2. 템플릿 필터 적용

이제 question_list.html 에서 위와 같이 pybo_filter 파일을 추가하고 sub 태그와 add 태그를 사용하여 게시물 번호를

미리 구해둔 공식에 따라 계산해준다. 

 

사이트에 접속해보면 정상적으로 게시물 번호 오류가 수정된 것을 확인할 수 있다.

'개인 프로젝트 > Django-mysite' 카테고리의 다른 글

#18 로그인/로그아웃  (0) 2021.09.30
#17 답글 갯수 표시  (0) 2021.09.28
#15 페이징  (0) 2021.09.24
#14 내비게이션 바  (0) 2021.09.24
#13 데이터 저장 2  (0) 2021.09.23

1. 큐(Queue)

 큐는 대기열이라는 이름 그대로 먼저 들어온 데이터가 먼저 추출되는 선입선출(FIFO)의 선형 자료구조이다.

스택이 top 에서만 삽입/삭제가 이루어졌던 것에 비해 큐의 경우 삭제만 이루어지는 front 와 삽입만 이루어지는

rear 로 이루어진다.  주요 연산은 다음과 같다.

  • 데이터 삽입
    • enqueue(e) : 데이터 e를 큐의 맨 뒤인 rear 에 삽입한다.
  • 데이터 삭제
    • dequeue() : 큐의 맨 앞인 front 의 데이터를 제거하고 제거된 데이터를 반환한다.

 

2.  큐의 활용

  • 그래프의 너비 우선 탐색에 활용된다.

  • OS의 스케줄링에서 프로세스들의 요청을 저장하거나 인터넷/전화 회선상에서 고객의 요청을 저장하는 등
    요청이 쌓이는 속도가 처리속도보다 빠른 경우라면 거의 대부분 유용하게 사용된다.

 

3. 파생 자료구조

  • 데크(Dequeue)
    • 일반적인 큐와 달리 front 와 rear 양쪽에서 삽입/삭제가 모두 가능한 자료구조

    • 스택과 큐의 기능을 합쳐놓은 듯한 자료구조이다.
  • 우선순위 큐
    • 들어간 순서가 나오는 순서가 되는 일반적인 큐와 달리 나중에 들어갔어도 지정된 우선순위가 보다
      높은 데이터가 먼저 추출되는 큐
    • 배열이나 리스트로 구현할 경우 우선순위에 맞는 위치에 삽입하기 위해 O(N)의 시간복잡도를 보이기 때문에
      O(logN)의 시간복잡도로 삽입 가능하며 항상 최솟값 혹은 최댓값이 먼저 추출되도록 할 수 있는 heap 을 
      사용하여 구현된다.  이에 대해서는 heap 을 설명할 때 자세히 다루기로 한다.
  • 원형 큐
    • 큐의 front 와 rear가 맞닿아있는 형태의 큐

    • 원형 연결 리스트와 거의 동일하며 실제로 원형 연결 리스트를 그대로 원형 큐처럼 사용할 수 있다.

'CS > 자료구조' 카테고리의 다른 글

#6 트리(Tree) 1 - 트리의 개념/이진 트리  (0) 2021.10.04
#5 그래프(Graph)  (0) 2021.10.01
#3 스택 (Stack)  (0) 2021.09.28
#2 양방향 연결 리스트(Doubly Linked List) 구현  (0) 2021.09.27
#1 리스트(List)  (0) 2021.09.27

1. 스택(Stack)

 스택은 나중에 들어온 데이터가 먼저 나가게되는 후입선출(LIFO)의 선형 자료구조이다.   스택에 삽입된 데이터들은

스택의 끝 부분인 스택 탑(stack top)에 쌓이듯이 저장되며 데이터 하나를 추출할 때도 현재 스택 탑에 있는 데이터가

가장 먼저 추출된다. 주요 연산은 다음과 같다.

 

  • 데이터 삽입
    • push(e) : 데이터 e를 스택 탑에 삽입
  • 데이터 삭제
    • pop() : 스택 탑의 데이터를 스택에서 제거하고 제거한 데이터를 반환

 

2. 스택의 활용

  • 후위표기법의 계산 및 괄호의 매칭에 활용된다. 

  • 자료구조의 성질상 이전 작업 흐름으로 복귀하는 기능의 구현에 편리하다.  예를들어 현재 진행중인 작업을 잠시
    중단하고 다른 작업을 해야할 경우, 스택에 진행중이던 작업 정보를 넣어두고 다른 작업을 진행한 뒤 스택에 넣어둔
    정보를 pop 하여 다시 작업을 진행할 수 있다.

  • 위의 특징을 활용하여 그래프의 깊이우선 탐색에 스택의 원리가 사용되며 프로그램에서 함수를 호출할 때 함수의
    정보를 저장하는 데에도 사용된다. 

 

'CS > 자료구조' 카테고리의 다른 글

#5 그래프(Graph)  (0) 2021.10.01
#4 큐(Queue)  (0) 2021.09.28
#2 양방향 연결 리스트(Doubly Linked List) 구현  (0) 2021.09.27
#1 리스트(List)  (0) 2021.09.27
#0 ADT(Abstract Data Type)  (0) 2021.09.27

1. ADT 작성 

Datas:
    head : 첫 노드를 가리키는 포인터
    tail : 마지막 노드를 가리키는 포인터
    it : 이터레이션을 위한 포인터
    size : 리스트의 크기 = 리스트에 들어있는 노드의 갯수
    
Operations:
    insert(e, i) : 데이터 e를 리스트의 i번째 위치에 삽입. i == -1이라면 마지막에 삽입
    delete_at(i) : i 번째 데이터를 삭제하고 삭제한 데이터를 반환
    delete(e) : 데이터 e를 찾아 삭제하고 결과를 반환
    search_at(i) : i 번째 데이터를 반환. 유효하지 않은 인덱스라면 에러 발생
    search(e) : 데이터 e가 처음으로 나타나는 인덱스를 찾아 반환

 

2. 구현

# 양방향 연결리스트 클래스
class DoublyLinkedList:
    # 노드 클래스
    class __Node:
        # 노드 생성자
        def __init__(self, e):
            # 데이터
            self.data = e

            # 다음 노드
            self.next = None

            # 이전 노드
            self.prev = None

    # 리스트 생성자
    def __init__(self):
        # head
        self.__head = None

        # tail
        self.__tail = None

        # 현재 노드 (iterator 구현을 위한 필드)
        self.__it = self.__Node(0)

        # 리스트 크기
        self.__size = 0

    # 삽입 메소드
    def insert(self, e, i=-1):
        # 삽입할 노드 생성
        new_node = self.__Node(e)

        # 인덱스가 생략됐거나 마지막 인덱스인 경우 맨 끝에 삽입
        if i < 0 or i == self.__size:
            # 리스트가 비어있다면 head = tail = new_node
            if not self.__head:
                self.__head = self.__tail = new_node

            # 리스트가 비어있지 않다면 마지막 노드 뒤에 삽입
            else:
                self.__tail.next = new_node
                new_node.prev = self.__tail
                self.__tail = new_node

        # 마지막 노드가 아닌 인덱스가 주어졌을 경우
        else:
            # 유효하지 않은 인덱스라면 IndexError 발생
            if i > self.__size:
                raise IndexError

            # 인덱스가 0이라면 첫 노드의 앞 부분에 삽입
            if i == 0:
                self.__head.prev = new_node
                new_node.next = self.__head
                self.__head = new_node

            # 그렇지 않다면 해당 인덱스의 노드 앞 부분에 삽입
            else:
                cur = self.__head
                for _ in range(i):
                    cur = cur.next
                cur.prev.next = new_node
                new_node.prev = cur.prev
                cur.prev = new_node
                new_node.next = cur

        # 리스트의 크기 1 증가
        self.__size += 1

    # 탐색 메소드 - 인덱스
    def search_at(self, i):
        # 유효하지 않은 인덱스라면 IndexError 발생
        if i < 0 or i >= self.__size:
            raise IndexError

        # head 부터 i만큼 다음 노드로 이동
        cur = self.__head
        for _ in range(i):
            cur = cur.next

        # 노드의 데이터 반환
        return cur.data

    # 탐색 메소드 - 데이터
    def search(self, e):
        cur = self.__head
        idx = 0
        # 데이터 값이 e인 노드가 나올 때 까지 탐색
        while cur and cur.data != e:
            cur = cur.next
            idx += 1

        # 데이터 값이 e인 노드가 없을 경우 ValueError 발생
        if not cur:
            raise ValueError

        # 데이터 값이 e인 노드가 처음으로 나타난 인덱스 반환
        return idx

    # i 번째 노드 삭제 메소드
    def delete_at(self, i):
        # 유효하지 않은 인덱스라면 IndexError 발생
        if i < 0 or i >= self.__size:
            raise IndexError

        # head 부터 i 만큼 다음 노드로 이동
        cur = self.__head
        for _ in range(i):
            cur = cur.next

        # 반환할 노드의 데이터를 저장
        res = cur.data

        # 앞, 뒤 노드가 서로 연결되도록 조정
        # head, tail 을 삭제한 경우 head, tail 재지정
        if cur.prev:
            cur.prev.next = cur.next
        else:
            self.__head = cur.next

        if cur.next:
            cur.next.prev = cur.prev
        else:
            self.__tail = cur.prev

        # 리스트 크기 1 감소
        self.__size -= 1

        # 삭제한 데이터 반환
        return res

    # 처음으로 나타나는 데이터 e 삭제 메소드
    def delete(self, e):
        # 데이터 삭제 시도
        try:
            # search 메소드를 호출하여 데이터의 인덱스를 구함
            idx = self.search(e)

            # delete_at 메소드를 호출하여 데이터 삭제
            self.delete_at(idx)

            # 삭제성공. True 반환
            return True

        # 존재하지 않는 데이터라면 삭제에 실패. False 반환
        except ValueError:
            return False

    # len 함수의 결과로 sef.__size 반환
    def __len__(self):
        return self.__size

    # 이터레이터 구현
    def __iter__(self):
        # 이터레이션을 위한 self.__it 객체의 next 를 self.__head 로 하고 self 를 반환
        self.__it.next = self.__head
        return self

    def __next__(self):
        # 다음 노드가 있다면 self.__it = self.__it.next 후 self.__it 의 데이터값 반환
        if self.__it.next:
            self.__it = self.__it.next
            return self.__it.data
        # 없다면 이터레이션 종료
        else:
            raise StopIteration


# 테스트
def main():
    # 연결 리스트 생성
    dlist = DoublyLinkedList()
    
    # 데이터 삽입
    dlist.insert(3)
    dlist.insert(1)
    dlist.insert(2)
    dlist.insert(4)
    dlist.insert(0)
    dlist.insert(6)
    dlist.insert(7)
    dlist.insert(9)

    # 데이터 4 삭제
    dlist.delete(4)

    # 첫 번째 데이터(3) 삭제
    dlist.delete_at(0)

    # 3 번째 데이터(6) 삭제
    dlist.delete_at(3)

    # Python 의 list 로 변환하여 출력
    print(list(dlist))
  • ADT에서 언급한대로 리스트의 기능을 구현
  • __len__(self) 를 구현하여 len 함수를 호출하면 리스트의 크기를 반환하도록 함
  • __iter__(self) 와 __next__(self) 를 구현하여 이터레이터로서 동작하도록 구현.  for 문이나 list() 에 사용 가능.

 

단방향 연결 리스트나 원형 연결 리스트의 경우 양방향 연결 리스트의 코드를 조금 수정하는 것 만으로 구현할 수

있기 때문에 따로 구현하지 않는다.

'CS > 자료구조' 카테고리의 다른 글

#5 그래프(Graph)  (0) 2021.10.01
#4 큐(Queue)  (0) 2021.09.28
#3 스택 (Stack)  (0) 2021.09.28
#1 리스트(List)  (0) 2021.09.27
#0 ADT(Abstract Data Type)  (0) 2021.09.27

1. 리스트(List)

 리스트는 입력받은 데이터를 순차적으로 저장하는 가장 간단한 형태의 선형 자료구조이다.  리스트는 일반적으로

다음과 같은 기능을 가진다.

  • 데이터 삽입
    • insert(e) : 데이터 e를 맨 뒤에 삽입 
    • insert(e, i) : 데이터 e를 인덱스 i에 삽입
  • 데이터 삭제
    • delete(i) : 인덱스 i의 데이터 삭제
    • delete(e) : 데이터 e를 찾아 삭제
  • 데이터 탐색
    • search(i) : 인덱스 i의 데이터 반환
    • search(e) : 데이터 e를 찾아 인덱스를 반환

이 외에도 구현자의 의도에 따라 리스트의 모든 요소를 제거, 리스트의 요소를 정렬, 리스트가 비어있는지 확인 등

다양한 기능이 들어가기도 하지만 가장 기본적인것은 위의 세 가지이다.

 

 

2. 리스트의 특징

  • 구현이 간단하며 대부분의 프로그래밍 언어에서 기본 자료형 또는 내장 라이브러리의 형태로 지원
  • 다수의 데이터를 순서를 유지한채로 그룹화하여 관리하기 좋음
  • 특정 데이터를 탐색하는데 O(N) 의 시간복잡도를 보임 

 

3. 리스트의 구현 방식에 따른 특징

  • 배열 리스트(Array List) : 배열을 기반으로 구현된 리스트
    • 인덱스를 활용하여 특정 순번의 데이터에 대해 O(1) 의 시간복잡도로 빠른 탐색이 가능
    • 삽입, 삭제 연산 실행 시 데이터들의 인덱스를 조정해야하기 때문에 O(N)의 시간복잡도를 보임
    • 배열의 크기에 제한이 있어 최초에 할당했던 크기 이상의 데이터를 삽입할 경우 배열을 확장하는
      과정을 거쳐야함
  • 연결 리스트(Linked List) : 데이터를 하나의 노드로 구성하여 서로 연결시키는 것으로 구현된 리스트
    • n 번째 데이터에 접근하기 위해서는 링크를 타고 넘어가는 과정을 n번 거칠 필요가 있어 특정 순번의
      데이터를 탐색하는데 O(N)의 시간복잡도를 보임
    • 어느 위치에서 삽입/삭제 연산 실행 하더라도 새로운 노드를 만들어 전후의 연결관계만을 조정하면
      되기 때문에 O(1) 의 시간복잡도로 처리 가능
    • 데이터가 추가될 때마다 노드를 생성하기 때문에 데이터가 늘어나더라도 별도의 확장과정이 필요없음

 

4. 연결 리스트의 종류

  • 단방향 연결 리스트
    • 각 노드가 데이터다음 노드의 주소로 이루어진 연결 리스트
    • 현재 노드에서 이전 노드를 참조할 수 없음
  • 양방향 연결 리스트
    • 각 노드가 데이터와 이전 노드의 주소, 다음 노드의 주소로 이루어진 연결 리스트
    • 현재 노드의 이전 노드와 다음 노드를 모두 참조할 수 있음
  • 원형 연결 리스트
    • 양방향 연결리스트의 첫 노드와 끝 노드를 서로 연결시킨 연결 리스트
    • 현재 탐색중인 노드를 가리키는 포인터 하나만으로 head와 tail을 모두 참조할 수 있는 것과 마찬가지
    • 스트림 버퍼나 큐를 구현하는데에도 사용 가능하며 데이터를 순환탐색하기에도 편리하다.

'CS > 자료구조' 카테고리의 다른 글

#5 그래프(Graph)  (0) 2021.10.01
#4 큐(Queue)  (0) 2021.09.28
#3 스택 (Stack)  (0) 2021.09.28
#2 양방향 연결 리스트(Doubly Linked List) 구현  (0) 2021.09.27
#0 ADT(Abstract Data Type)  (0) 2021.09.27

1. ADT(Abstract Data Type) 란?

 ADT, 혹은 추상자료형이란 어떤 대상에 대한 데이터와 그 데이터들에 대한 연산을 정의하는 것으로 대상을

추상화하여 표현한 것이다.  

 

2. ADT의 예시

 예를들어 추상화의 대상이 동전지갑이라고 가정해보자.  동전지갑은 동전을 하나 또는 여러개씩 넣을 수 있으며

마찬가지로 하나 또는 여러개씩 꺼낼 수 있다.  이것을 추상화 한다면 다음과 같이 표현할 수 있을 것이다.

Datas:
    coin_10 : 10원짜리 동전의 갯수
    coin_50 : 50원짜리 동전의 갯수
    coin_100 : 100원짜리 동전의 갯수
    coin_500 : 500원짜리 동전의 갯수
   
Operations:
    insert(coin_type, cnt) : coin_type 에 해당하는 동전의 갯수를 cnt 만큼 증가시킨다.
    delete(coin_type, cnt) : coin_type 에 해당하는 동전의 갯수를 cnt 만큼 감소시킨다.

종류별 동전의 갯수를 data로 가지며 각 동전을 cnt개 만큼 넣었다 뺐다 할 수 있는 지갑을 ADT로 나타낸 것이다.

 

3. 자료구조의 인터페이스, ADT

 위의 예시처럼 ADT는 데이터와 연산의 기능에 대해 설명할 뿐, 연산의 내부 구현이 어떻게 이루어져있는지 등에 대해

언급하지 않는다. 그렇기에 ADT는 자료구조의 인터페이스와도 같다.  사용자는 ADT만을 보고도 자료구조를 사용할 수

있지만 실제로 각 연산들이 어떻게 구현되어있는지는 알 수 없으며 알 필요도 없다.  이렇게 인터페이스와 구현을

구분하는 것으로 내부 구현이 바뀌더라도 사용자는 여전히 같은 방식으로 자료구조를 사용할 수 있으며 개발 측에서는

필요 이상의 정보를 공개하지 않고도 사용자에게 기능을 제공할 수 있다.  앞으로 알아볼 자료구조들을 직접 구현해보는

경우에도 먼저 간단하게 ADT를 정의하고 이를 구현하는 흐름으로 진행할 것이다. 

'CS > 자료구조' 카테고리의 다른 글

#5 그래프(Graph)  (0) 2021.10.01
#4 큐(Queue)  (0) 2021.09.28
#3 스택 (Stack)  (0) 2021.09.28
#2 양방향 연결 리스트(Doubly Linked List) 구현  (0) 2021.09.27
#1 리스트(List)  (0) 2021.09.27

지금까지 만든 웹사이트는 아직 페이징 처리가 되어있지 않아 모든 게시글을 한페이지에 전부 표시하도록 되어있다.

이번에는 페이징을 적용하여 한 페이지에 표시할 수 있는 게시글의 수를 제한할 수 있도록 한다.

 

1. 테스트 데이터 생성

페이징이 제대로 이루어지는지 확인하려면 다수의 게시글이 필요하다. 장고 쉘로 테스트데이터를 생성해보자.

python manage.py shell 을 실행하여 장고 쉘에 진입한 뒤 Question 모델과 timezone 모듈을 import 하여

반복문으로 300개의 Question 인스턴스를 생성, 저장한다. 

 

다시 사이트에 접속해보면 추가된 300개의 데이터가 모두 한 페이지에 표시되는 것을 볼 수 있다.

 

 

2. Paginator

장고에서는 Paginator 라는 클래스를 사용하여 페이징을 할 수 있다.

 

pybo/views.py 의 index 함수를 위와 같이 수정하여 페이징을 적용한다. 

 

템플릿에서도 페이징을 적용하기 위해 question_list.html 을 위와 같이 수정한다. 이전이나 다음 페이지가 있다면

이전과 다음 링크가 활성화되게 하고 페이지 리스트중 현재 페이지는 강조되도록 하였다.

 

사이트에 다시 접속하면 정상적으로 페이징 처리가 된 것을 볼 수 있다.  문제는 페이징 영역에 존재하는 모든 페이지가

표시된다는 것이다.

 

템플릿을 위와 같이 조금 수정한다. |add:-5 나 |add:5 는 템플릿 필터로 question_number 값에 각각 -5, 5를 더해준다는

의미이다. 즉, 페이지영역의 표시범위를 현재페이지로부터 이전 5개붙어 다음 5개까지로 제한하는 코드이다.

 

사이트에 다시 접속해보면 페이지지가 현재페이지의 전후로 5페이지까지만 표시되는 것을 볼 수 있다.

 

 

3. 처음과 끝 추가

강좌에 따로 방법이 소개되어있진 않지만 이번에 배운것을 응용하여 첫 페이지와 끝 페이지로 바로 이동하는 기능을

추가해보자.

 

먼저 이전 페이지 링크 위에 1 페이지가 페이지목록에 보이지 않는 경우, 즉 현재 페이지가 7 페이지 이상인 경우

첫 페이지로 이동하는 링크가 나타나도록 한다.

 

그리고 다음 페이지 링크 아래에 마지막 페이지가 목록에 보이지 않는 경우, 즉 현재 페이지가 마지막페이지수 - 5 보다

작은 경우 끝 페이지로 이동하는 링크가 나타나도록 한다. 총 페이지 수는 <page오브젝트>.paginator.num_pages 로 

얻을 수 있다. 

 

 

4. 이전, 다음 페이지 수정

강좌에서는 이전이나 다음 페이지가 없더라도 링크를 비활성화할 뿐 표시는 하도록 구현하였지만 실제 게시판의

상당수가 이전이나 다음 페이지가 없다면 링크 자체가 나타나지 않도록 구현되어있다. 

 

이전, 다음 페이지가 없다면 링크를 표시하지 않도록 question_list.html을 위와 같이 수정한다.

 

이제 이전 페이지나 다음 페이지가 없다면 링크가 표시되지 않는다.

'개인 프로젝트 > Django-mysite' 카테고리의 다른 글

#17 답글 갯수 표시  (0) 2021.09.28
#16 게시물 번호 오류  (0) 2021.09.28
#14 내비게이션 바  (0) 2021.09.24
#13 데이터 저장 2  (0) 2021.09.23
#12 템플릿 상속  (0) 2021.09.23

 이번에는 지금까지 만든 웹사이트에 내비게이션 바를 추가할 차례이다.  내비게이션 바는 웹사이트의

여러 페이지들에 접근할 수 있는 하이퍼텍스트 링크를 포함하고있는 영역이다.  일반적으로

웹사이트의 모든 페이지에 나타나며 시각적으로도 기능적으로도 웹사이트의 주요 디자인 요소 중

하나이다.

 

1. 템플릿 수정

내비게이션 바가 모든 웹페이지에 공통적으로 나타나도록 하기 위해 base.html 템플릿을 다음과 같이 수정해준다.

로그인 기능은 추후에 구현할 예정이지만 미리 내비게이션 바에 위치시켜두었다. button 태그로 작성한 것은

창의 크기가 일정 수준 작아지면 나타나는 id 가 navbarNav 인 영역의 메뉴들을 숨겼다 나타냈다 할 수 있는 토글

메뉴 버튼이다.

 

평소에는 메인페이지로 이동하기 위한 Pybo 링크와 로그인 링크가 표시된다.

 

창의 크기를 줄이면 로그인 메뉴가 사라지고 우측에 메뉴 버튼이 나타난 것을 확인할 수 있다.

 

 

2. 메뉴버튼 구현 

현재 상태로는 메뉴 버튼을 눌러도 숨겨진 로그인 버튼이 나타나지 않는다.  이 버튼이 작동하도록 하려면

부트스트랩 자바 스크립트 파일이 추가되어야한다. 

 

부트스트랩을 설치할때 다운받았던 압축파일 내의 bootstrap.min.js 파일과 jquery 다운로드 페이지에서

"Download the compressed, production jQuery <version>" 링크를 다른이름으로 저장하여 받을 수 있는

jquery-<version>.min.js 파일을 다음과 같이 static 디렉토리에 위치시킨다.

 

base.html 의 본문 영역 이후에 script 태그를 사용하여 js 파일들을 추가해준다.

 

이제 메뉴 버튼을 누르면 정상적으로 숨겨진 로그인 링크가 나타나는 것을 확인할 수 있다.

 

 

3. include

내비게이션 바의 html 코드가 다른 템플릿에서 재사용될 경우를 고려해서 별도의 파일로 분리하고 base.html에서

include 하는 방식으로 바꿔보자. 

 

먼저 templates 디렉토리에 navbar.html을 생성한다. 내용은 내비게이션 바를 만들기 위해 base.html에 삽입했던

코드와 동일하다.

 

다음으로 base.html 에서 내비게이션 바의 html 코드가 있던 자리에 navbar.html 을 include 해주면 이전과 동일하게

작동한다. 편리한 유지보수를 위해 독립적인 기능으로 볼 수 있는 부분들은 파일을 분리해주는 습관을 들이는 것이 좋다.

'개인 프로젝트 > Django-mysite' 카테고리의 다른 글

#16 게시물 번호 오류  (0) 2021.09.28
#15 페이징  (0) 2021.09.24
#13 데이터 저장 2  (0) 2021.09.23
#12 템플릿 상속  (0) 2021.09.23
#11 부트스트랩(Bootstrap)  (0) 2021.09.22

 json이란 JavaScript Object Notation의 줄임말로, JavaScript 에서 객체를 표현하는데 사용되는

방식에서 파생된 경량의 데이터 교환 형식이다.  데이터를 직렬화(Serialization)하는데 자주 사용되는

형식이다.  Python 에서는 json을 다루기 위한 내장모듈 json을 지원한다. 

 

 

json 모듈의 가장 기본적인 네 가지 메소드와 사용 예시는 다음과 같다.

메소드 기능
load json 형식으로 저장된 파일을 Python 객체로 파싱
loads json 형식의 문자열을 Python 객체로 파싱
dump Python 객체를 json 형식으로 파싱하여 파일에 쓰기
dumps Python 객체를 json 형식의 문자열로 파싱하여 반환

 

import json

# 예시용 오브젝트
sample_object = {
    "json_string": "string_example",
    "json_number": 100,
    "json_array": [1, 2, 3, 4, 5],
    "json_object": { "name":"John", "age":30},
    "json_bool": True
}

# Python 오브젝트를 json 문자열로 파싱
json_string = json.dumps(sample_object)
print(json_string)

# json 문자열을 Python 오브젝트로 파싱
json_object = json.loads(json_string)
for key in json_object:
    print(key)
# 실행 결과
# {"json_string": "string_example", "json_number": 100, "json_array": [1, 2, 3, 4, 5], "json_object": {"name": "John", "age": 30}, "json_bool": true}
# json_string
# json_number
# json_array
# json_object
# json_bool

dumps 와 loads 를 사용하여 Python 객체를 json 문자열로 파싱하고 다시 Python 객체로 파싱한 코드이다.

 

 

import json

# 예시용 오브젝트
sample_object = {
    "json_string": "string_example",
    "json_number": 100,
    "json_array": [1, 2, 3, 4, 5],
    "json_object": { "name":"John", "age":30},
    "json_bool": True
}

# Python 오브젝트를 json 문자열로 파싱하여 파일에 쓴다
f = open('json_file', 'w')
json_string = json.dump(sample_object, f)

# 저장한 파일을 읽어들여 다시 Python 오브젝트로 파싱한다.
f = open('json_file', 'r')
json_object = json.load(f)
for key in json_object:
    print(key)
# 실행 결과
# json_string
# json_number
# json_array
# json_object
# json_bool

dump 와 load 를 사용하여 Python 객체를 json 파일로 저장하고 그 파일을 읽어들여 다시 Python 객체로 파싱한

코드이다.

 

'언어 > Python' 카테고리의 다른 글

#11 흐름제어 (조건문, 반복문)  (0) 2021.05.27
#10 특수한 매개변수  (0) 2021.05.26
#9 입력을 받는 여러 방법  (0) 2021.04.06
#8 우선순위 큐(heapq)  (0) 2021.04.05
#7 데크(deque)  (0) 2021.04.05

1. 질문 등록

 지금까지 만든 웹사이트는 질문 리스트와 각 질문의 상세내용을 볼 수 있고 각 질문에 대해 답변도 등록할 수 있는

기능을 갖추었다.  이제 질문을 등록할 수 있다면 최소한의 기능을 갖춘 게시판이 완성될 것이다. 

 

먼저 질문등록을 하려면 등록 버튼을 만들어야한다. question_list.html 을 다음과 같이 수정하여 질문 리스트 페이지

하단에 질문 등록 버튼을 생성하자.

 

다음으로 별칭 pybo:question_create 에 해당하는 url 매핑을 추가해야한다.  pybo/urls.py 를 다음과 같이 수정한다.

 

 

2. 폼(Form)

이제 뷰에서 실제로 question_create 함수를 정의해야하는데, 그에 앞서 먼저 폼(form)에 대해 알아보자. 

폼(form)은 웹 페이지 요청시 전달되는 파라미터들을 쉽게 관리하기 위해 사용하는 클래스로, 필수 파라미터들이

누락되지 않았는지, 형식이 적절한지 등을 검증할 수 있으며 HTML을 자동생성하거나 폼에 연결된 모델을 이용하여

데이터를 저장할 수도 있다.  아래 코드는 pybo/forms.py 에 질문 등록에 사용될 QuestionForm 을 구현한 것이다.

장고에서 사용되는 폼은 일반 폼(forms.Form)모델 폼(forms.ModelForm) 두 종류로 나뉜다. 모델 폼의 경우 모델과

연결된 폼으로, 연결된 모델의 데이터를 저장할 수 있다.  모델 폼은 이너 클래스인 Meta 클래스를 반드시 필요로한다.

Meta 클래스에는 연결할 모델과 모델의 속성을 적어야 한다. 

 

QuestionForm 은 Question 모델과 연결되는 모델 폼이며, 필드값으로 subject(제목)와 content(내용)가 있다.

labels 는 각 필드값에 대해 간단히 설명하는 텍스트를 넣어둔 것이다.

 

 

3. 뷰 함수 생성

폼을 만들었으니 이제 pybo/views.py 에 다음과 같이 question_create 함수를 작성한다.

forms.py의 QuestionForm 을 import 한 뒤 폼의 인스턴스를 생성, pybo/question_form.html 템플릿에 질문 등록에

사용될 폼으로 넘겨주어 렌더링한다.

 

 

4. 템플릿

이제 질문 등록을 위한 템플릿 templates/pybo/question_form.html을 작성해야한다.  {{ form.as_p }} 코드로 폼에

정의한 속성들을 입력받는 HTML 코드를 자동으로 생성할 수도 있지만 이 방식은 디자인 측면에서 한계가 명확하고

디자인 영역과 서버 개발 영역이 분리되지 않는다는 문제가 발생한다.  여기서는 수동으로 폼을 작성하는 내용만을

다루기로 한다.

 

다음과 같이 질문 등록 템플릿 pybo/question_form.html을 작성한다.

다른 템플릿과 마찬가지로 base.html을 상속하여 content 블록을 구현한다. 절차는 다음과 같다.

① 요청 방식이 post인 form 엘리먼트를 생성하고  답변 등록을 구현할 때 했던 것 처럼 보안을 위해 {% csrf_token %}

    을 최상단에 위치시킨다. 

 

② {% if form.errors %} 블록으로 전달된 폼이 올바른 상태가 아니라면 경고문을 출력하도록 한다.

 

③ from-group 클래스로 제목과 내용을 입력받는 html 코드를 작성한다.

 

④ 폼을 제출할 submit 타입의 버튼을 생성한다. 

 

 

5. 뷰 함수 완성

여기까지의 작업만으로는 질문 등록 페이지에서 아무리 내용을 입력하고 save 버튼을 눌러도 아무 동작도 일어나지

않을 것이다.  뷰 함수에 데이터를 저장하는 코드를 작성하지 않았기 때문이다. 

 

pybo/views.py 의 question_create 함수를 다음과 같이 수정한다. 

question_create 함수가 호출되는 경우는 처음 질문 등록 버튼을 눌렀을 때와 질문 등록 폼을 완성하고 제출했을 때의

두 가지 경우로 나뉜다.  두 경우는 데이터 전송방식(get or post)으로 구분하며, 전자의 경우 빈 QuestionForm 을

생성하여 question_form.html 로 전달하며 후자의 경우, 폼이 정상적으로 제출되었다면 데이터를 저장한 뒤 질문 리스트

페이지로 리다이렉트, 폼이 비정상적인 상태라면 질문 등록 페이지로 되돌아간다.

 

※ question = form.save(commit=False) 에서 commit을 False로 지정한 이유는 폼으로 생성된 question 인스턴스가

   아직 create_date 데이터를 담고있지 않기 때문이다.  우선 인스턴스를 생성한 뒤 create_date를 설정해주고 나서야 

   save를 실행하여 실제로 데이터를 저장하는 것이다.

 

 

6. 답변 등록

이전에 미리 구현해두었던 답변 등록 또한 폼을 적용하여 다시 구현해보자.

 

먼저 답변 등록에 사용할 폼을 forms.py 에 다음과 같이 정의한다.

답변의 경우 제목이 없기 때문에 내용 필드 하나만을 가진다.

 

다음으로 views.py 의 answer_create함수를 다음과 같이 수정한다.

answer_create 함수의 경우 POST 방식으로만 호출되기 때문에 else 문의 부분은 필요가 없지만 구조의 통일을

위해서 위와 같이 작성한다.  

 

마지막으로 필수 항목이 누락된 경우 에러메시지를 표시하기 위해 question_detail.html 의 답변 등록 부분에 

다음과 같이 에러 표현 부분을 추가한다.

{% if form.errors %} 아래로 에러 표시용 코드를 삽입한 것을 확인할 수 있다.

 

 

7. 테스트

여기까치 작업을 마쳤다면 이제 질문과 답변의 등록/조회가 모두 가능하게 되었다. 

 

① 질문 리스트

등록된 질문의 리스트가 정상적으로 표시되며 하단에 질문 등록을 위한 버튼도 확인할 수 있다.

 

 

② 질문 등록

질문 등록 버튼을 클릭하면 질문 등록을 위한 페이지로 연결된다.

 

제목이나 내용이 누락된 경우 에러메시지를 표시한다.

 

정상적으로 제목과 내용을 입력한 뒤 save를 누르면 정상적으로 질문이 등록되어 리스트에 표시되는 것을 볼 수 있다.

 

 

③ 질문 상세 조회

 리스트에서 질문을 클릭할 경우 질문의 내용을 확인할 수 있으며 질문에 대한 답변 또한 작성할 수 있다.

 

④ 답변 작성

답변 내용을 누락한 경우 에러 메시지를 표시한다.

 

정상적으로 답변 내용을 입력할 경우 답변이 등록되어 정상적으로 조회되는 것을 볼 수 있다.

'개인 프로젝트 > Django-mysite' 카테고리의 다른 글

#15 페이징  (0) 2021.09.24
#14 내비게이션 바  (0) 2021.09.24
#12 템플릿 상속  (0) 2021.09.23
#11 부트스트랩(Bootstrap)  (0) 2021.09.22
#10 스타일시트(Style Sheet)  (0) 2021.09.22

+ Recent posts