본문 바로가기

Django

20210206_Django 태그 기능, get_or_create, ManyToManyField, 배포(pythonanywhere)

Django 게시판 기능 보완

  • 글 목록에서 제목 눌러서 글 보기 창 들어가기 (연결)
    • table요소의 열 태그인 tr태그onclick으로 연결해 주었고
    • 추가적으로 포인터가 있으면 좋을 것 같아서 role="button"을 통해서 마우스를 올리면 손가락 표시가 나타나게 하였다.
{% raw %}
{% extends "base.html" %}

{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <table class="table table-light">
            <thead class="thead-light">
                <tr>
                    <th>#</th>
                    <th>제목</th>
                    <th>아이디</th>
                    <th>일시</th>
                </tr>
            </thead>
            <tbody class="text-dark">
                {% for board in boards %}
                <tr role="button" onclick="location.href='/board/detail/{{ board.id }}/'">
                    <th>{{ board.id }}</th>
                    <td>{{ board.title }}</td>
                    <td>{{ board.writer }}</td>
                    <td>{{ board.registered_dttm }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
    </div>
</div>
<div class="row mt-2">
    <div class="col-12">
        <nav>
            <ul class="pagination justify-content-center">
                {% if boards.has_previous %}
            <li class="page-item">
                <a href="?p={{ boards.previous_page_number }}" class="page-link">이전으로</a>
            </li>
                {% else %}
            <li class="page-item disabled">
                <a href="#" class="page-link">이전으로</a>
            </li>
                {% endif %}
            <li class="page-item active">
                <a href="#" class="page-link">{{ boards.number }}/{{ boards.paginator.num_pages}}</a>
            </li>
               {% if boards.has_next %}
            <li class="page-item">
                <a href="?p={{ boards.next_page_number }}" class="page-link">다음으로</a>
            </li>
                {% else %}
            <li class="page-item disabled">
                <a href="#" class="page-link">다음으로</a>
            </li>
                {% endif %}
        </ul>
        </nav>
    </div>
</div>
<div class="row mt-1">
    <div class="col-2">
        <buttton class="btn btn-primary btn-block" onclick="location.href='/board/write/'">글쓰기</buttton>
    </div>
    <div class="col-2">
        <buttton class="btn btn-primary btn-block" onclick="location.href='/'">홈</buttton>
    </div>
</div>

{% endblock %}
{% endraw %}

Django 태그 만들기

  • model에서 게시글 구현을 위해서 user(writer) 와 board의 관계는 1:N 으로 ForeignKey()를 사용하여 관계를 맺었음
  • 태그 기능은 M:N 관계로 하나의 태그가 여러글에 들어갈수 있고 한개의 글에 여러개의 태그가 들어갈 수 있음

태그 app 만들기

  • django-admin startapp 앱이름 or python manage.py startapp 앱이름

태그 model 만들기

from django.db import models

# Create your models here.


class Tag(models.Model):
    name = models.CharField(max_length=32, verbose_name='태그명')
    registered_dttm = models.DateTimeField(
        auto_now_add=True, verbose_name="등록시간")

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'slowbuscamp_tag'
        verbose_name = '슬로우버스캠프 태그'
        verbose_name_plural = '슬로우버스캠프 태그'

태그 admin 만들기

  • model 가져오는것 잊지 말자!
from django.contrib import admin
from .models import Tag # 주의!

# Register your models here.


class TagAdmin(admin.ModelAdmin):
    list_display = ('name', 'registered_dttm')


admin.site.register(Tag, TagAdmin)

settings에 태그 app 추가

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'board',
    'sl_user',
    'tag'
]

게시판 모델(board 모델) 수정하기

  • 게시판 모델에도 태그정보가 들어가기 때문에 사용하기 위해서
  • 다대다 관계 구현을 위한 ManyToManyField를 사용함
from django.db import models

# Create your models here.


class Board(models.Model):
    title = models.CharField(max_length=128, verbose_name='제목')
    contents = models.TextField(verbose_name='내용')
    writer = models.ForeignKey(
        'sl_user.Sl_user', on_delete=models.CASCADE, verbose_name='작성자')
    # on_delete=models.CASCADE 는 models의 key가 삭제 되면 해당 모델도 지우겠다.
    tags = models.ManyToManyField('tag.Tag', verbose_name='태그')
    registered_dttm = models.DateTimeField(
        auto_now_add=True, verbose_name='등록시간')

    def __str__(self):
        return self.title

    class Meta:
        db_table = ' slowbuscamp_board'
        verbose_name = '슬로우버스캠프 게시글'  # 기본적으로 복수형을 지원하기 때문에 s가 자동으로 붙게 되어 있음
        verbose_name_plural = '슬로우버스캠프 게시글'  # 복수형이름을 따로 지정하여 자동으로 붙는 s를 제거 가능

태그 migrations, migrate 하기

  • python manage.py migrations , python manage.py migrate

게시글 보기 화면에 태그 구현

  • board_detail.html
  • 어차피 모델에서 따오면 되기 때문에 view에서 board를 모델로 지칭하고 있기 때문에 board를 사용
  • 대신 태그 필드(ManyToManyField)의 경우 필드 특성상 그냥으로는 안되고 all함수를 이용해야 QuerySet이 된다. 이를 통해 for문과 활용될수 있다.
  • ManyToManyField
  • |join:'문자' 를 통해서 각 요소들을 구분해주는 문자를 join을 통해 지정할수 있다.
{% raw %}
{% extends "base.html" %}

{% block contents %}
<div class="row mt-5">
    <div class="col-12">
        <div class="form-group">
            <label for="title">제목</label>
            <input type="text" class="form-control" id="title" value="{{ board.title }}" readonly>
            <label for="contents">내용</label>
            <textarea class="form-control" readonly>{{ board.contents }}</textarea>
            <label for="tags">태그</label>
            <span class="form-control" id='tags'>
                {{ board.tags.all|join:", " }}
                <!-- {% for tag in board.tags.all %}
                {{ tag.name }}
                {% endfor %} -->
            </span>
        </div>
        <button class="btn btn-primary" onclick="location.href='/board/list/'">목록</button>
    </div>
</div>
{% endblock %}
{% endraw %}

글쓰기 화면에 태그 구현

  • 쭉 쓰면 ,를 구분자로 하여 tag를 인식하고 tag가 기존에 있던것이면 그냥 저장하고, tag가 기존에 없던 것이라면 만들고 저장하게 함

  • board_wrtie.html의 경우 form형식을 사용함

  • form 수정

    • tags로 form필드 추가, 입력은 없어도 있어도 상관 없으므로 required=False설정
from django import forms


# 입력 받을 값은 제목과 내용 두개
class BoardForm(forms.Form):
    title = forms.CharField(error_messages={
        'required': '제목을 입력해 주세요.'
    }, max_length=128, label="제목")
    contents = forms.CharField(error_messages={
        'required': '내용을 입력해 주세요.'
    }, widget=forms.Textarea, label="내용")
    tags = forms.CharField(required=False, label="태그")

  • view 수정
    • 기능 구현을 하기 위해서(있는거 없는거 구분해서)
    • 주의할점은 필드 특성상 태그는 모델이 만들어져서 db에 저장이 되어야 태그데이터를 추가가능하다.
    • get_or_create()함수 : 데이터 중복 방지시 좋음
def board_write(request):
    if not request.session.get('user'):
        return redirect('/sl_user/login/')

    if request.method == "POST":
        form = BoardForm(request.POST)
        if form.is_valid():
            user_id = request.session.get('user')
            sl_user = Sl_user.objects.get(pk=user_id)

            tags = form.cleaned_data['tags'].split(',')
            # 입력 받아 타당하게 저장된 tag 값들을 ','로 구분해서 tags변수에 할당 

            board = Board()
            board.title = form.cleaned_data['title']
            board.contents = form.cleaned_data['contents']
            board.writer = fcuser
            board.save()

            for tag in tags: # tags안에 있는 값들을 하나씩 꺼내서
                if not tag:
                    continue
                # Tag models에 있는 model중 name필드 값이 입력받은 tag와 같은 값을 가져오고, 없다면 모델에 만들어라 (create 데이터는 _을 통해 안받음)
                _tag, _ = Tag.objects.get_or_create(name=tag)
                board.tags.add(_tag) # 해당 태그들을 board 모델의 tags필드를 트리거함

            return redirect('/board/list/')

    else:
        form = BoardForm()

    return render(request, 'board_write.html', {'form': form})



배포

배포 django 설정

  • DEBUG 모드 해제
  • HOST 주소 설정
  • STATIC 설정
    • STATIC_ROOT는 해당 자료가 수집되는 곳을 알려주는 것임
import os
from pathlib import Path

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False  # 화면에 개발할때 필요한 정보들을 보여주는지 설정

# pythonanywhere에서 가입한 id 적어주기 또는 주소, 다른 주소로 접속하는 것을 막는 역할
ALLOWED_HOSTS = [
    'raccooncode96.pythonanywhere.com'
]


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'
# STATICFILES_DIRS = [
#     os.path.join(BASE_DIR, 'static')
# ]
# BASE_DIR  이 프로젝트 폴더를 의미, 프로젝트 폴더의 static 폴더

# 다른 곳에서 받아 올 것이기 때문에
STATIC_ROOT = os.path.join(BASE_DIR, 'static')


Python anywhere 호스팅 설정

  • python anywhere 가입

Python anywhere files

  • 프로젝트 파일을 압축하여 python anywhere에 files들어가서 upload

Python anywhere 콘솔

  • python anwhere의 consoles 창(bash 콘솔임)에가서 ls를 통해서 현재 파일 확인
  • unzip 프로젝트 파일 이름.확장자 명령을 통해 파일 압축 풀기
  • pythonanywhere에 virtualenv가 원래 초기에 install 되어 있기 때문에 그냥
  • virtualenv --python=python버전 가상환경이름 을 통해서 콘솔에 가상환경 만들고, activate 할 것 (bash 콘솔이라서 source를 무조건 써주어야 함)
    • source 가상환경이름/bin/activate -> 활성화 됨
  • pip install django 장고 설치
  • 프로젝트 안에서 static파일들을 수집하는 명령어 수행 python manage.py collectstatic -> yes
  • 기존에 프로젝트 파일에 db.sqlite파일을 안넣었으면 python manage.py makemigrations , python manage.py migrate 통해 db구축 및 연결
  • exit 를 통해서 콘솔 종료

Python anywhere web

  • Python anywhere의 web 에서 add web app 하여 app 추가
    • python web framework 설정에서 manual configuration을 선택 가상환경을 포함하기 때문에
    • python 버전은 알아서 맞추어서(3.x)

  • Code 설정하기
    • Source code 경로 설정 : /home/가입한 아이디/프로젝트이름
    • WSGI 설정 : 옆에 경로의 wsgi.py 눌러 프레임 워크 설정
      • 기본 hello world 설정은 주석처리(ctrl+/)시키고 django 부분 주석 해제해서 active 시킴
      • django 코드의 path를 자신의 프로젝트로 수정(기본 mysite라서)
      • 그리고 하단의 os.environ에서 mysite.settings를 프로젝트이름.settings로 설정
      • save

  • Vitualenv 설정 : 경로 넣기 /home/가입아이디/가상환경이름

  • Static files 설정 : url 은 /static/ directory는 /home/가입아이디/static/ 으로 설정 하는데 directory는 콘솔기준의 static 폴더 경로임

    • static의 경우 Bootstrap에서 가져온 CSS,JS등의 적용을 위해서 필요함
  • 최상단 초록색 Reload버튼 누르면 업데이트됨



배포 확인

  • 가입아이디.pythonanywhere.com 으로 들어가면 됨
  • pythonanywhere의 경우 배포가 무료인데 3개월에 한번씩 로그인해서 리로드해줘야 한다.