ABOUT ME

IT와 컴퓨터 관련 팁, 파이썬 등과 아파트 정보, 일상적인 경험 등의 생활 정보를 정리해서 올리는 개인 블로그

  • [python] 장고(django)로 게시판 만들어보기
    코딩 연습/코딩배우기 2021. 10. 11. 21:12

     

    [python] 장고(django)로 게시판 만들어보기

    MariaDB와 파이썬 장고(django) 프레임워크를 이용한 간단한 게시판 만들기

     

    ◆ 작업할 디렉토리 만들기

    - 탐색기에서 장고 게시판 코딩을 할 디렉토리(djangoBoard)를 만들고,
    - VS Code를 실행한 후 '폴더열기'로 해당 폴더를 연다.

    vs code 폴더열기

     

    디렉토리 선택

     

    good4me.co.kr

     

    ◆ 가상환경 설정 및 장고 설치

    ## 가상환경 설정
    C:\Users\xxxxxx\django\djangoBoard>python -m venv venv
    
    ## 파이썬 버전 확인
    C:\Users\xxxxxx\django\djangoBoard>python -V
    Python 3.8.8
    
    ## 가상환경 실행
    C:\Users\xxxxxx\django\djangoBoard>cd venv/Scripts
    (venv) C:\Users\xxxxxx\django\djangoBoard\venv\Scripts>activate
    (venv) C:\Users\xxxxxx\django\djangoBoard\venv\Scripts>cd..
    (venv) C:\Users\xxxxxx\django\djangoBoard\venv>cd..
    
    ## 장고 버전 확인
    (venv) C:\Users\xxxxxx\django\djangoBoard>django-admin --version
    3.1
    
    ## 장고 설치 (버전 3.2)
    (venv) C:\Users\xxxxxx\django\djangoBoard>pip install django==3.2
    Collecting django==3.2
      Using cached Django-3.2-py3-none-any.whl (7.9 MB)
    Collecting pytz
      Using cached pytz-2021.1-py2.py3-none-any.whl (510 kB)
    Collecting sqlparse>=0.2.2
      Using cached sqlparse-0.4.2-py3-none-any.whl (42 kB)
    Collecting asgiref<4,>=3.3.2
      Using cached asgiref-3.4.1-py3-none-any.whl (25 kB)
    Installing collected packages: pytz, sqlparse, asgiref, django
    Successfully installed asgiref-3.4.1 django-3.2 pytz-2021.1 sqlparse-0.4.2
    WARNING: You are using pip version 20.2.3; however, version 21.2.4 is available.
    You should consider upgrading via the 'c:\users\xxxxxx\django\djangoboard\venv\scripts\python.exe -m pip install --upgrade pip' command.
    
    ## pip 업그레이드
    (venv) C:\Users\xxxxxx\django\djangoBoard>python -m pip install --upgrade pip
    Collecting pip
      Using cached pip-21.2.4-py3-none-any.whl (1.6 MB)
    Installing collected packages: pip
      Attempting uninstall: pip
        Found existing installation: pip 20.2.3
        Uninstalling pip-20.2.3:
          Successfully uninstalled pip-20.2.3
    Successfully installed pip-21.2.4
    
    (venv) C:\Users\xxxxxx\django\djangoBoard>pip list
    Package    Version
    ---------- -------
    asgiref    3.4.1
    Django     3.2
    pip        21.2.4
    pytz       2021.1
    setuptools 49.2.1
    sqlparse   0.4.2
    
    ## 장고 프로젝트 생성
    (venv) C:\Users\xxxxxx\django\djangoBoard>django-admin startproject config .

     

     

    ◆ MariaDB 설치

    sqlite3 대신 MariaDB(MySQL과 동일한 엔진 사용)를 사용하기 위해 공식사이트에서 Windows 64비트 버전을 다운로드 한 후 설치한다. https://mariadb.org/

     

    MariaDB Foundation - MariaDB.org

    … Continue reading "MariaDB Foundation"

    mariadb.org

    - MariaDB Server Version 10.6.4 (52MB)

    - MariaDb 설치 시 HeidiSQL DB 관리 툴이 기본적으로 설치되기 때문에 DB 관리가 편리하다.(설치 시 root 비번은 꼭 기억할 것)

     

     

    ※ 윈도우10에서 MariaDB 설치

     

    Next 클릭

     

     

    Next 클릭

    설치 위치 확인 (Browser 버튼으로 설치 위치 변경 가능)

     

     

    Next 클릭

    비밀번호를 입력하고, DB character set (Encoding)은 UTF-8 사용으로 체크 Next 클릭

     

     

    Next 클릭

     

    Service Name 등 그대로 두고 Next, Install 클릭

     

     

     

     

    설치 완료 후 윈도우 시작 버튼을 눌러 MariaDB 설치 내용을 확인해본다. 

     

     

    (HeidiSQL 설치 확인 및 클릭)

     

     

    ※ HeidiSQL 실행

     

    HeidiSQL

    - 세션 이름은 원하는 이름으로 입력(입력 안해도 되지만..)
    - 암호 넣고 '열기' 클릭

     

    초기화면

     

     

    - 장고 프로젝트에서 사용할 database 생성 (세션이름 마우스 우클릭 후 새로생성 > 데이터베이스)

     

     

    - 장고 settings.py에서 접속할 데이터베이스 이름임(borameboard)

     

     

    데이터베이스 생성 확인

     

    ◆ 장고와 MariaDB 연동을 위해 라이브러리(mysqlclient)를 설치한다.

    (venv) C:\Users\xxxxxx\django\djangoBoard>pip install mysqlclient
    Collecting mysqlclient
      Downloading mysqlclient-2.0.3-cp38-cp38-win_amd64.whl (179 kB)
         |████████████████████████████████| 179 kB 6.4 MB/s
    Installing collected packages: mysqlclient
    Successfully installed mysqlclient-2.0.3

    - 이 패키지 설치를 안하면 database 연결이 안됨

     

     

    ◆ 장고 sqlite3 대신 사용할 db 접속 정보 설정하기 - settings.py

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }
    
    #위 코드를 아래처럼 수정한다.
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'borameboard',
            'USER': 'root',
            'PASSWORD': '**********',
            'HOST': 'localhost',
            'PORT': '3306'
        }
    }

     

     

     

    ● django 연동 기본 DB 초기화 (migrate 명령) 및 테스트 서버 구동(runserver)
    - HeidiSQL에서 borameboard database가 필히 생성되어있어야 함

    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py migrate       
    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
      Applying admin.0002_logentry_remove_auto_add... OK
      Applying admin.0003_logentry_add_action_flag_choices... OK
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying auth.0007_alter_validators_add_error_messages... OK
      Applying auth.0008_alter_user_username_max_length... OK
      Applying auth.0009_alter_user_last_name_max_length... OK
      Applying auth.0010_alter_group_name_max_length... OK
      Applying auth.0011_update_proxy_permissions... OK
      Applying auth.0012_alter_user_first_name_max_length... OK
      Applying sessions.0001_initial... OK
    
    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py run server

    - admin, auth, board, contenttypes, sessions 관련 모델(db)이 생성됨

     

     

    ※ 브라우저에서 127.0.0.1:8000 또는 localhost:8000 으로 접속

    장고 테스트 서버 초기 화면

     

     

     

    ◆ 사용자(관리자) 계정 설정

    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py createsuperuser
    사용자 이름 (leave blank to use 'xxxxxx'): admin
    이메일 주소: 
    Password: 
    Password (again):
    Superuser created successfully.

     

    - 관리자 계정으로 접속

    admin 페이지

     

     

    게시판 앱(borameboard) 생성 및 앱 등록

    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py startapp borameboard

     

    - borameboard 생성 후 settings.py에 앱 등록

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'borameboard',
    ]

     

     

    ◆ URL Conf 설정

    # config>urls.py
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('board/', include('borameboard.urls')),
    ]

    - 127.0.0.1:8000/board 접속 시 borameboard 앱의 urls.py로 보내도록 경로 지정

     

     

    - borameboard 앱에 urls.py를 만들고,  
    User Interface인 html 파일을 만들 templates 폴더 / borameboard  폴더를 만든다.

    # borameboard>urls.py
    from django.urls import path
    from . import views
    
    app_name = 'borame_board'
    
    urlpatterns = [
        path('', views.index, name='index'),
    ]

     

     

     

    ◆ views.py 작성

    from django.shortcuts import render
    
    def index(request):
        context = {'board_list': '테스트입니다.'}
        return render(request, 'borameboard/index.html', context)

     

    - templates / borameboard 밑에 index.html 템플릿 파일을 생성하고, views.py의 index() 반환값이 출력되는지 확인한다.

    # index.html

    <html>
    <body>
        {{board_list}}
    </body>
    </html>

     

     

    - 127.0.01:8000/board 로 사이트 접속 

    index.html 접속 확인

     

     

    ◆ 모델 (DB) 생성 - modes.py

    - 장고의 ORM 기능인 models를 통해 게시판 글을 관리하는 DB를 생성한다.

     

    - 장고에서 모든 database table은 모델(Model)로 관리할 수 있다.

     

    - 장고에서는 primary key를 관리하는 id 속성이 자동으로 지정되어 관리된다.
    # id = models.AutoField(primary_key=True)

     

    - 다른 컬럼(필드)에 primary key를 부여하고 싶으면 해당 필드에 primary_key=True 옵션을 준다.
    # models.CharField는 문자열 필드, 
    # models.TextField는 문장을 입력할 수 있는 필드(textarea)
    # DateTimeField는 날짜 필드를 의미한다. 

    from django.db import models
    
    class Board(models.Model):
        title = models.CharField(max_length=200, verbose_name='글 제목', help_text='* 제목은 최대 100자 이내')
        author = models.CharField(max_length=100, verbose_name='글쓴이')
        content = models.TextField(verbose_name='글 내용')
        published_date = models.DateTimeField(auto_now=True, verbose_name='등록(수정)일')
    
        def __str__(self):
            return self.title

    - __str__() 함수는 객체가 생성되면 객체를 문자열로 반환해주는 함수인데, title(제목)을 반환하도록 지정함

    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py makemigrations
    Migrations for 'borameboard':
      borameboard\migrations\0001_initial.py
        - Create model Board
    (venv) C:\Users\xxxxxx\django\djangoBoard>python manage.py migrate       
    Operations to perform:
      Apply all migrations: admin, auth, borameboard, contenttypes, sessions
    Running migrations:
      Applying borameboard.0001_initial... OK

    - makemigrations 명령으로 생성된 모델을 마이그레이션 객체로 변환, migrate 명령으로 데이터베이스에 table 생성

     

     

    HeidiSQL로 확인

     

    ◆ admin 페이지에 등록

    from django.contrib import admin
    from .models import Board
    
    @admin.register(Board)
    class BoardAdmin(admin.ModelAdmin):
        list_display = ['id', 'title', 'author', 'published_date']
        list_display_links = ['id', 'title']
        list_per_page = 10

    - list_display : Admin 목록에 보여질 필드 목록
    - list_display_links : 목록 내에서 링크로 지정할 필드 목록
    - list_per_page : 페이지 별로 보여질 개수 (디폴트 100)

     

     

    ◆ models 마이그레이트 후 어드민 계정 접속

    등록한 Board Model 확인

     

    - Boards 클릭

     

     

     - BOARD 추가 버튼 클릭하여 테스트 데이터 입력 

     

     

     

     

    ◆ Django Template 상속

    - 템플릿 파일의 중복 내용을 상속을 통해 중복되지 않도록 할 수 있다. 
    - 전체 레이아웃(부모 템플릿) 내에  {% block 블럭 영역 명칭 %} {% endblock %}으로 정의 후 각 템플릿(자식 템플릿)에서 다음과 같이 재정의하여 사용

    {% extends '부모 템플릿(경로)' %}
    
    
    {% block 블럭 영역 명칭 %}
    
    {% endblock %}

    - djangoBoard 내 하위 폴더 layout을 만들고 장고 전체에서 사용할 base.html 템플릿 파일을 생성한다. 

    # base.html 템플릿 파일 경로 설정 : settings.py에 디렉토리 경로 지정

    TEMPLATES = [
    {
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [ ],
    에서 

    'DIRS': [ ], 부분을
    'DIRS': [os.path.join(BASE_DIR, 'layout')], 처럼 수정한다.

    # import os 필요함

    - base.html 파일을 전체 레이아웃으로 사용하기 위해 bootstrap을 적용한다.

    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet">
        <title>Borame Board</title>
    </head>
    <body>
        <div class="container-fluid">
            <nav class="navbar navbar-expand-lg navbar-light bg-light">
                <div class="container-fluid">
                    <a class="navbar-brand" href="#">LOGO</a>
                    <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
                        data-bs-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false"
                        aria-label="Toggle navigation">
                        <span class="navbar-toggler-icon"></span>
                    </button>
                    <div class="collapse navbar-collapse" id="navbarNavDropdown">
                        <ul class="navbar-nav">
                            <li class="nav-item">
                                <a class="nav-link active" aria-current="page" href="#">Home</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#">Menu1</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#">Menu2</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#">borameBoard</a>
                            </li>                        
                        </ul>
                    </div>
                </div>
            </nav>
        </div>
        <div class="container">
            {% block content %}
    
            {% endblock %}
        </div>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
    </body>
    </html>

    - Navbar 종류 중 하나를 선택하여 복사한 후 <div class="container-fluid"> 밑에 붙여넣기

    - 부트스트랩 사이트(https://getbootstrap.com/docs/5.1/getting-started/introduction/)에서 css와 js 링크를 복사하여 붙여 넣고 사용함

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet">
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>

     

     

    ◆ views.py 내용 수정

    from django.shortcuts import render
    from .models import Board
    
    def index(request):
        board_list = Board.objects.all().order_by('-id')
        context = {'board_list': board_list}
        return render(request, 'borameboard/index.html', context)

    - Board 객체 매니저인 objects를 사용하여 해당 db table의 데이터 추출 QuerySet을 변수 board_list에 저장한다.
    # 데이터 전체(all())를 id 역순으로 정렬(order_by('-id'))하는 QuerySet
    - 추출한 쿼리셋 데이터를 index.html로 렌더링하여 브라우저로 보여준다.

     

     

    ◆ index.html 내용 수정

     {% extends 'base.html' %}
    {% block content %}
    
    <div class="container">
        <div class="row pt-4 my-3">
            <div class="col-md-12">
                <h3>게시판</h3>
            </div>
        </div>
        <div class="row my-3">
            <table class="table table-hover table-bordered">
                <thead>
                    <th>No</th>
                    <th>제목</th>
                    <th>작성자</th>
                    <th>내용</th>
                    <th>발행일</th>
                    <th>비고</th>
                </thead>
                <tbody>
                    {% for post in board_list %}
                    <tr>
                        <td>{{ forloop.revcounter }}</td>
                        <td><a href="#">{{ post.title }}</a></td>
                        <td>{{ post.author }}</td>
                        <td>{{ post.content|truncatewords:5 }}</td>
                        <td>{{ post.published_date|date:"Y-m-d" }}</td>
                        <td></td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
    
    {% endblock %}

     

     

     

    ◆ 127.0.0.1:8000 으로 접속 시 index.html로 바로 접속하도록 path 설정

    - borameboard의 urls.py에서 path를 받아 views.py에서 redirect() 함수로 처리해도 되지만,
    - config / urls.py 에서 아래처럼 path('', lambda r: redirect('borame_board:index')), 추가하면 바로 redirect 처리할 수도 있다.
    * from django.shortcuts import redirect 추가하고, lambda 함수와 redirect 함수 이용

    from django.contrib import admin
    from django.urls import path, include
    from django.shortcuts import redirect
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', lambda r: redirect('borame_board:index')),
        path('board/', include('borameboard.urls')),
    ]

     

     

    ◆ 글 등록을 위해 urls.py 수정

    - path('regist/', views.regist, name='regist'), 추가

     

     

    ◆ index.html 템플릿 파일 하단에 '글 등록' 버튼 추가

    <div style="padding: 2px 20px; float:right;">
        <a href="{% url 'borame_board:regist' %}" class="btn btn-primary">글 등록</a>
    </div>

     

     

    ◆ 글 등록은 장고 폼(forms.ModelForm)을 사용해본다.

    - borameboard 앱에 forms.py 파일을 생성한다.

    from django import forms
    from django.forms import widgets
    from .models import Board
    
    class RegistForm(forms.ModelForm):
        class Meta:
            model = Board
            fields = ['title', 'author', 'content']  # '__all__'
            widgets = {
                'title': forms.TextInput(attrs={'class': 'form-control'}),
                'author': forms.TextInput(attrs={'class': 'form-control'}),
                'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
            }

    - Model 클래스와 유사하게 Form 클래스 정의 (HTML Form 요소를 파이썬 클래스화)
    - 장고 models 에 대한 form을 생성하기 위해 모델명을 import하고, 보여 줄 컬럼(필드)를 지정한다.('__all__'인 경우 전체 지정됨)
    - 템플릿에서 보여질 html의 요소(Element)를 지정하기 위해 widgets을 import 하고 설정한다.

     

     

     

    ◆ 등록 폼 템플릿 추가 

    - borameboard / templates / borameboard / regist_form.html

    {% extends 'base.html' %}
    {% block content %}
    
    <div class="container">
        <div class="row pt-4 my-3">
            <div class="col-md-12">
                <h3>글 등록</h3>
            </div>
        </div>
        <div class="row my-3"></div>
            <form action="" method="post">
                {% csrf_token %}
                {{ form.as_p }}
                <div style="padding: 5px 50px; float:right;">
                    <input class="btn btn-primary" type="submit" value="글 등록">
                    <a href="#" class="btn btn-primary">글 목록</a>
                </div>
            </form>
        </div>
    </div>
    
    {% endblock %}

    - csrf_token : (Cross Site Request Forgeries) 사이트 간 요청 위조 공격 막기 위함 

     

     

    글 등록 폼

     

     

    ◆ 입력된 글 등록(처리)를 위해 views.py 에 regist() 추가

    from borameboard.models import Board
    from django.shortcuts import render,redirect
    from .models import Board
    from .forms import RegistForm
    
    def index(request):
        board_list = Board.objects.all().order_by('-id')
        context = {'board_list': board_list}
        return render(request, 'borameboard/index.html', context)
    
    
    def regist(request):
        if request.method == 'POST':
            form = RegistForm(request.POST)
            if form.is_valid():
                post = form.save()
                return redirect('borame_board:index')
        else:
            form = RegistForm()
        context = {'form': form,}
        return render(request, 'borameboard/regist_form.html', context)
    ​

     

     

     

    ◆ 글 내용 보기

    - borameboard / urls.py 에 path('detail/<int:pk>/', views.detail, name='detail'), 추가
    - 글 목록(리스트)에서 글 제목을 클릭하면 글의 내용을 볼 수 있도록 하기 위해 

    index.html의 <td><a href="#">{{ post.title }}</a></td> 부분을 
    <td><a href="{% url 'borame_board:detail' post.id %}">{{ post.title }}</a></td> 로 수정한다.

     

     

    ◆ views.py 에 detail() 추가

    def detail(request, pk):
        board_list = get_object_or_404(Board, id=pk)
        context = {'board_list': board_list}
        return render(request, 'borameboard/detail.html', context)

    - views 내에서 특정 조건에 맞는 모델 인스턴스를 가져오려고 할 때는 항상 get_object_or_404() 를 쓴다. 
    * 글 삭제 또는 글이 없어서 발생하는 오류 코드(status code)가 500이 아닌 404로 되도록 하기 위함
    *  get_object_or_404 추가 (from django.shortcuts import render, redirect, get_object_or_404)


    - 글 세부 내용 보기 템플릿 - borameboard / templates / borameboard / detail.html

    {% extends 'base.html' %}
    {% block content %}
    
    <div class="container">
        <div class="row pt-4 my-3">
            <div class="col-md-12">
                <h3>글 세부 내용</h3>
            </div>
        </div>
        <div class="row my-3">
            <table class="table table-bordered">
                <tr>
                    <td>[글 제목] : {{ board_list.title }}</td>
                </tr>
                <tr>
                    <td>
                        <p>[글 내용]</p>
                        {{ board_list.content|linebreaks }}
                        <p><small style="color:silver;text-align:right;">[{{ board_list.published_date|date:"Y-m-d H:i:s" }}]</small></p>
                    </td>
                </tr>
            </table>
        </div>
        <div style="padding: 2px 20px; float:right;">
            <a href="{% url 'borame_board:index' %}" class="btn btn-primary">글 목록</a>
        </div>
    </div>
    
    {% endblock %}

     

     

     

    ◆ 글 수정 기능

    - 글 수정을 위해 borameboard / urls.py 에 
    path('edit/<int:pk>/', views.edit, name='edit'), 추가

    - 글 목록에서 '비고' 내의 '수정' 링크(버튼)를 클릭하면, 수정페이지로 전환되고 수정할 글의 id를 urls에 전달되도록 위해 index.html의 비고 부분에 <a href="{% url 'borame_board:edit' post.id %}" class="btn btn-outline-info btn-sm">수정</a> 를 추가한다.

     

    views.py 에 edit() 추가

    def edit(request, pk):
        post = get_object_or_404(Board, id=pk)
        if request.method == 'POST':
            form = RegistForm(request.POST, instance=post)
            if form.is_valid():
                post = form.save()
                return redirect('borame_board:index')
        else:
            form = RegistForm(instance=post)
        context = {'form': form,}
        return render(request, 'borameboard/edit_form.html', context)

    - id=pk인 모델 인스턴스를 get_object_or_404()로 가져와서 post 변수에 저장하고, 이 인스턴스 변수를 기준으로 폼을 생성한다. ( instance=post)

     

    - 수정 폼은 등록 폼을 그대로 활용하도록 작성한다.

    {% extends 'base.html' %}
    {% block content %}
    
    <div class="container">
        <div class="row pt-4 my-3">
            <div class="col-md-12">
                <h3>글 수정</h3>
            </div>
        </div>
        <div class="row my-3"></div>
            <form action="" method="post">
                {% csrf_token %}
                {{ form.as_p }}
                <div style="padding: 5px 50px; float:right;">
                    <input class="btn btn-primary" type="submit" value="글 수정">
                    <a href="{% url 'borame_board:index' %}" class="btn btn-primary">글 목록</a>
                </div>
            </form>
        </div>
    </div>
    
    {% endblock %}
    ​

     

     

     

    ◆ 글 삭제 기능

    - 글 삭제 기능도 글 목록의 '비고' 내에 '삭제' 링크(버튼)를 만들어서 처리한다.

    * index.html의 비고 부분에 <a href="{% url 'borame_board:delete' post.id %}" onclick="if(!confirm('정말 삭제하시겠습니까?')){return false;}" class="btn btn-outline-danger btn-sm">삭제</a>추가

    - urls.py 에 path('delete/<int:pk>/', views.delete, name='delete'), 추가

     

     

    views.py 에 delete() 추가

    def delete(request, pk):
        post = get_object_or_404(Board, id=pk)
        post.delete()
        return redirect('borame_board:index')

     

     

    게시판 목록(리스트)

     

    ◆ base.html 상단 메뉴의 Home과 borameboard에 게시판 리스트 페이지를 링크한다.

    <a class="nav-link active" aria-current="page" href="#">Home</a>
    <a class="nav-link" href="#">borameBoard</a>
    
    아래 처럼 수정
    
    <a class="nav-link active" aria-current="page" href="{% url 'borame_board:index' %}">Home</a>
    <a class="nav-link" href="{% url 'borame_board:index' %}">borameBoard</a>

     

    글 상세 내용 보기

     

     

    글 수정 페이지

     

     

     

    글 삭제 버튼 클릭

     

     

    ◆ 페이징 처리

    - 사전 작업 : 장고 쉘(shell)을 이용한 데이터 입력

    (venv) C:\Users\xxxxxxdjango\djangoBoard>python manage.py shell
    Python 3.8.8 (default, Apr 13 2021, 15:08:03) [MSC v.1916 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    >>> 
    >>> from django.utils import timezone
    >>> from borameboard.models import Board
    >>> query = Board(title='페이징 처리', author='2000', content='페이징 처리 데이터 입력', published_date=timezone.now())
    >>> query.save()
    >>> Board.objects.all()
    <QuerySet [<Board: 김장용품>, <Board: 반려견 애정템>, <Board: 가성비 좋은 노트북>, <Board: 크리스마스 선물>, <Board: 맥북 
    프로 16인치>, <Board: 갤럭시 워치4>, <Board: 페이징 처리>]>
    >>> for i in range(60):
    ...     query = Board(title='페이징 처리-'+str(i), author='2000', content='페이징 처리 데이터 입력_'+str(i), published_date=timezone.now())
    ...     query.save()
    ...
    >>> Board.objects.all()
    <QuerySet [<Board: 김장용품>, <Board: 반려견 애정템>, <Board: 가성비 좋은 노트북>, <Board: 크리스마스 선물>, <Board: 맥북 
    프로 16인치>, <Board: 갤럭시 워치4>, <Board: 페이징 처리>, <Board: 페이징 처리-0>, <Board: 페이징 처리-1>, <Board: 페이징  
    처리-2>, <Board: 페이징 처리-3>, <Board: 페이징 처리-4>, <Board: 페이징 처리-5>, <Board: 페이징 처리-6>, <Board: 페이징 처 
    리-7>, <Board: 페이징 처리-8>, <Board: 페이징 처리-9>, <Board: 페이징 처리-10>, <Board: 페이징 처리-11>, <Board: 페이징 처 
    리-12>, '...(remaining elements truncated)...']>
    >>>

    # 장고에서는 파이썬 쉘이 아닌 장고 쉘을 이용한다. (python manage.py shell 로 쉘에 진입 후 작업 진행)

     

     

    데이터 입력 후

     

     

    ※ 장고의 ORM 관련 메서드 간략하게 알아보기

    all() 
    해당 table의 데이타 전체 가져오기
    데이터 전체를 QuerySet 타입으로 반환
    dict 타입 사용 방법으로 개별 row를 출력할 수 있다.
    rs = Board.objects.all()
    print(rs[0]['title'])

     

    get()
    row 1개만 가져오기
    QuerySet이 아닌 단일 행(모델 타입) 반환
    반환 값(row 수)이 1개 이상일 경우 에러 발생함
    rs = Board.objects.get(id=1)
    print(rs.title)


    filter()
    특정 조건에 맞는 row들을 가져오기
    조건에 맞는 여러 행(row) 반환(QuerySet 타입)
    rs = Board.objects.filter(title='갤럭시') 
    또는 Board.objects.all().filter(title='갤럭시') 와 동일, all() 생략 가능
    print(rs[0]['title'])

     

    order_by(key)
    인자(키)에 따라 데이터 정렬하며, 정렬 키는 나열 가능
    기본 정렬은 오름차순이고, 키 앞에 -가 붙으면 내림차순
    Board.objects.order_by('id')

     


    value()
    특정 컬럼만을 대상으로 값을 반환할 때 사용
    QuerySet 타입 반환
    Board.objects.value('title')

     

    exclude()
    특정 조건을 제외한 나머지 row들을 가져오기
    Board.objects.exclude(title='갤럭시')

     

    distinct()
    중복된 값은 하나로만 표시하기(SELECT DISTINCT 와 같은 효과)
    Board.objects.distinct('title')

     

    first()
    데이타들 중 맨 처음에 있는 row만 가져오기
    Board.objects.order_by('title').first()

     

    last()
    데이타들 중 맨 마지막에 있는 row만 가져오기 
    Board.objects.filter(title='갤럭시').order_by('-id').first()

     

    count()
    데이타의 갯수(row 수) count
    Board.objects.count()

     

    Data INSERT 하기
    테이블에 해당하는 모델(model)로부터 객체 생성 후, 그 객체의 save() 메서드를 호출한다.
    q = Board(title='페이징 처리', author='2000', content='페이징 처리 데이터 입력', published_date=timezone.now())
    q.save()

     

    Data UPDATE 하기
    업데이트 할 row 객체를 생성하고 변경할 컬럼(필드)를 수정한 후, save() 메서드를 호출한다.
    q = Board.objects.get(id=1)
    q.title = '갤럭시 워치 4 싸게 구매하기'
    q.save()

     

    Date DELETE 하기
    데이타를 삭제할 row 객체를 생성하고 delete() 메서드를 호출한다.
    q = Board.objects.get(id=2)
    q.delete()

     


     

    - 페이징 처리를 위해 views.py 에서 index() 함수 수정하기
    from django.core.paginator import Paginator 추가

    def index(request):
        page = request.GET.get('page', '1')  # 페이지 파라미터 얻기, 없으면 1
        board_list = Board.objects.order_by('-id')
    
        # 페이징 처리
        paginator = Paginator(board_list, 10)  # 페이지당 10개씩 보여주기
        page_obj = paginator.get_page(page)  # page에 해당하는 페이징 객체 생성
    
        context = {'board_list': page_obj}   # 페이징 객체(page_obj) 전달
        return render(request, 'borameboard/index.html', context)


    # 페이징 객체(page_obj) 속성
    count : 총 객체 수 
    paginator.count : 전체 게시물 개수
    paginator.per_page : 페이지당 보여줄 게시물 개수
    paginator.page_range : 페이지 범위
    number : 현재 페이지 번호
    get_page : 페이지 가져오기
    page_range : 페이지 리스트(1~)
    num_pages: 총 페이지 수
    page(n) : n 번째 페이지
    previous_page_number : 이전 페이지 번호
    next_page_number : 다음 페이지 번호
    has_previous : 이전 페이지 유무(boolean)
    has_next : 다음 페이지 유무(boolean)
    start_index : 현재 페이지 시작 인덱스(1부터 시작)
    end_index : 현재 페이지의 끝 인덱스(1부터 시작)

     

     

    - index.html 수정

    {% extends 'base.html' %}
    {% block content %}
    
    <div class="container">
        <div class="row pt-4 my-3">
            <div class="col-md-12">
                <h3>게시판</h3>
            </div>
        </div>
        <div class="row my-3">
            <table class="table table-hover table-bordered">
                <thead>
                    <th>No</th>
                    <th>제목</th>
                    <th>작성자</th>
                    <th>내용</th>
                    <th>발행일</th>
                    <th>비고</th>
                </thead>
                <tbody>
                    {% for post in board_list %}
                    <tr>
                        <td>{{ forloop.counter }}</td>
                        <td><a href="{% url 'borame_board:detail' post.id %}">{{ post.title }}</a></td>
                        <td width="100">{{ post.author }}</td>
                        <td>{{ post.content|truncatewords:5 }}</td>
                        <td>{{ post.published_date|date:"Y-m-d" }}</td>
                        <td width="120">
                            <a href="{% url 'borame_board:edit' post.id %}" class="btn btn-outline-info btn-sm">수정</a> 
                            <a href="{% url 'borame_board:delete' post.id %}" onclick="if(!confirm('정말 삭제하시겠습니까?')){return false;}" 
                            class="btn btn-outline-danger btn-sm">삭제</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
        <div class="row">
            <div class="col-md-12 ">
                <a href="{% url 'borame_board:regist' %}" class="btn btn-outline-secondary">글 등록</a>
            </div>
        </div>
    
        <!-- 페이징 처리 --->
        <div class="row my-3">
            <div class="col-md-12 text-center">
                {% if board_list.has_previous %}
                    <a href="?page=1">처음</a>
                    <a href="?page={{ board_list.previous_page_number }}">이전</a>
                {% endif %}
                    <!-- 페이지리스트 -->
                    <span style="color:red;font-weight:bold;">{{ board_list.number }}</span> 
                    <span> / </span>
                    <span style="font-weight:bold;">{{ board_list.paginator.num_pages }}</span>
                {% if board_list.has_next %}
                    <a href="?page={{ board_list.next_page_number }}">다음</a>
                    <a href="?page={{ board_list.paginator.num_pages }}"></a>
                {% endif %}            
    
            </div>
        </div>
    </div>
    
    {% endblock %}

     

     

    페이징 처리 후 index 페이지

     

Designed by goodthings4me.