Django-mysite만들기9 게시판 조회수 중복 증가 쿠키 처리

|

django mini project

cafe24신입사원 교육과정 - django 수업 내용 정리


전체 코드 보기


쿠키를 이용해서 조회수 중복 증가 막기

이전에 작성한 포스팅에서 글 상세보기를 눌렀을 때, 조회수 증가 처리를 아래의 코드와 같이 간단하게 +1처리만 했었는데,

def view(request, no=0):
    if no == 0: return HttpResponseRedirect('list')

    board = Board.objects.filter(id=no)
    board.update(hit=F('hit')+1)
    data = {
        'board':board[0]
    }
    return render(request, 'board/view.html', data)

이 코드의 경우 F5를 누르면 계속해서 조회수가 +1씩 증가하게 된다.

이를 위해서 쿠키를 이용해서 조회수 중복 증가를 조금이나마 막는 코드로 수정했다.


[ 쿠키 만들기 ]

set_cookie(name, value, max_age)

name : 쿠키 이름

value : 저장하는 값

max_age : 쿠키 유지 시간

위의 코드로 쿠키를 만들어 심을 수 있다.

[ 쿠키 읽기]

request.COOKIES.get(쿠키이름) : 쿠키 데이터를 문자열로 반환해준다.

[ 쿠키 삭제]

response = render(request, 해당 template, data)

response.delete_cookie(쿠키이름)


조회수 코드 수정하기

로그인 한 경우 쿠키 이름을 hit:로그인유저id 로, 로그인 안한 경우 hit로 설정할 예정이다. 각각의 쿠키의 값으로는 게시글 번호를 |와 함께 값을 이어 붙일 예정이다.

쿠키는 매일 자정 12시에 reset 시킬 것이다.

ex) ‘쿠키이름=쿠키값(방문 게시글 목록)’

35번 회원이 로그인 한 경우 : hit:35=57|56|54|47|32|58|59

로그인 안한 경우 : hit=57|56|54|47|32|58|59

Python 쿠키 특정 정해놓은 시간에 삭제하는 법

쿠키는 매일 자정 12시에 reset 시킬 것이다.

tomorrow = datetime.datetime.replace(datetime.datetime.now(), hour=23, minute=59, second=0)
expires = datetime.datetime.strftime(tomorrow, "%a, %d-%b-%Y %H:%M:%S GMT")
...
response.set_cookie(쿠키이름, 쿠키값, expires =expires)

코드

def view(request, no=0):
    # 존재하는 게시글이 없을 경우 return
    if no == 0:
        return HttpResponseRedirect('list')
    # template에 전달할 데이터
    board = Board.objects.filter(id=no)
    data = {
        'board':board[0]
    }
    
    response = render(request, 'board/view.html', data)
    # [1] 로그인 확인
    if request.session.get('authUser') is None:
        cookie_name = 'hit'
    else:
        cookie_name = f'hit:{request.session["authUser"]["id"]}'

    # [2] 그 날 당일 밤 12시에 쿠키 삭제
    tomorrow = datetime.datetime.replace(datetime.datetime.now(), hour=23, minute=59, second=0)
    expires = datetime.datetime.strftime(tomorrow, "%a, %d-%b-%Y %H:%M:%S GMT")

    # [3] hit를 check하는 쿠키가 있는 경우
    if request.COOKIES.get(cookie_name) is not None:
        cookies = request.COOKIES.get(cookie_name)
        cookies_list = cookies.split('|')
        if str(no) not in cookies_list:
            response.set_cookie(cookie_name, cookies + f'|{no}', expires =expires)
            board.update(hit=F('hit') + 1)
            return response
    # [4] hit를 check하는 쿠키가 없는 경우
    else:
        response.set_cookie(cookie_name, no, expires =expires)
        board.update(hit=F('hit') + 1)
        return response

    return render(request, 'board/view.html', data)

해당하는 쿠키(str)에 |를 기준으로 계속 이어 붙여주는 형식으로 작성했다.

NetWork 탭에서 확인해보면 아래의 그림과 같이 쿠키가 담기는 것을 확인할 수 있다.

views

하지만 이 코드 역시 브라우저의 쿠키 삭제 후 다시 게시글에 방문하면 조회수가 증가하게 된다…


CSS를 적용하는 다양한 방법

|

CSS 란?

Cascading Style Sheet - “계단형”의 의미로 스타일 적용에 특정도, 우선순위가 있다. 우선순위가 정해지는 것이 계단식 스타일 시트라는 의미이다.

필요한 이유 :
1. HTML에 직접 스타일을 적용함으로써 생기는 HTML문서 자체의 무거움을 줄일 수 있다.
2. 하나의 스타일로 다수 의 페이지에 같은 속성을 적용 함으로써 작업시간 단축
3. 웹 표준의  원칙  :  HTML 마크업을 통해 구조를 잡고,  CSS로 디자인을 입힘

CSS를 적용하는 다양한 방법

[1] 인라인 방식

태그에 직접 스타일을 적용하기 때문에 바로 확인이 가능하다.

바로 스타일 적용이 확인 되기 때문에 테스트 용도또는 웹메일을 전송할 때만 사용해야하고 그 외에는 절대 사용하지 말 것 !!!!!!!!!

ex ) <p style="color:red"> 내용 </p>


[2] 임베디드 방식

-태그에 직접 스타일을 지정하지 않고 <head>와 </head> 사이에 스타일을 지정하는 방식

-HTML 문서 내부에 따로 스타일을 지정

-스타일 형식을 지정 해주고 미디어 타입을 지정

-스타일 형식 ‘text/css’ 는 고정

-media타입은 화면에 보여줄 때 screen, 프린트 출력에는 print 그리고 두 가지 모두에 적용되는 공통 스타일이면 all 등이 올 수 있다.

-CSS 코드가 길어지면 관리하기가 힘들어 진다.

ex)

...
<head>
...
<style type=text/css media=screen>
	p { color:#ddd }	
</style>  
...
</head>
<body>

	<p> 내용 </p>

</body>

[3] link방식

-임베디드 방식과 마찬가지로 <head> 와 </head> 사이에 특정 CSS 파일을 불러오도록 지정한다.

-link방식을 선호 => 구버전 브라우저에서 @import를 인식 못하며, 속도 측면에서 link방식이 조금 빠르다고 한다.

main.css

body {
   padding!: 0 0 0 0;
   padding: 0;
   margin: 10px;
   font-size: 12px;
   font-family: Arial, Helvertica, sans-serif;
   color: red;
} 

html

<link href='main.css' rel='stylesheet' type='text/css'>
<body>
    
   ...
    
</body>
views
html에 css가 먹혔는 지 확인

[4] @import 방식

<style type=‘text/css’ media=‘screen’>
   @import url(main.css)	
</style>

-하나의 CSS파일 내부에서 다른 CSS 파일을 불러올 수 있다.

ex)

main.css

* {
	margin: 0;
	padding: 0
}

body {
	font: 0.75em "맑은 고딕", 돋움, 굴림;
	text-align: center
}

ex.css

@import "mysite.css";

웹 표준, HTML 기본 이해

|

웹 표준이란 ?

HTML을 시작하기 전, 웹 표준에 대해 알고가자!

하나의 브라우저에 최적화 되어서 다른 브라우저에서 깨지는 웹 페이지는 이제 없다.

특정 웹 브라우저에 종속되는 웹 페이지는 더 이상 설 자리가 없다.

★ ==> 웹 페이지 제작 기술에 표준의 필요성이 대두

특정 브라우저에서만 사용하는 비표준화된 기술은 배제

특정 브라우저에서만 사용하는 비표준화된 기술은 배제

웹문서의 구조(HTML)와 표현(CSS) 그리고 동작(JavaScript)을 구분해서 사용(제작)

[ HTML과 CSS를 웹의 표준으로 사용하자 ]
- 웹 표준에 따라 웹 페이지를 작성하게 되면 표준을 따르는 브라우저에서는 모두 같은 정보를 얻게 되므로 사용자가 가장 큰 혜택을 보게 된다. 
- IE, 파이어폭스, 크롬, 사파리등 서로 다른 브라우저라도 같은 화면을 보여 주는 것, 즉 브라우저의 벽을 허물어 주는 개념을 ‘크로스 브라우징’ 이라 한다.


[ HTML은 구조를 잡고, CSS는 디자인을 담당하도록 분리하자 ]
- 정보의 구조는 HTML이 담당하고  CSS는 레이아웃과 디자인을 담당하도록 분리
- 하나의 콘텐츠 소스는 다양한 장비와 브라우저에서 사용할 수 있다는 의미


HTML이란 ?

HTML (Hypertext Markup Language,하이퍼텍스트 마크업 언어)는 우리가 보는 웹페이지가 어떻게 구조화되어 있는지 브라우저로 하여금 알 수 있도록 하는 마크업 언어이다.

HTML은 elements로 구성되어 있으며, 이들은 적절한 방법으로 나타내고 실행하기 위해 각 컨텐츠의 여러 부분들을 감싸고 마크업한다.

tags는 웹 상의 다른 페이지로 이동하게 하는 하이퍼링크 내용들을 생성하거나, 단어를 강조하는 등의 역할을 한다.

태그

< > 사이에 오는 단어나 문자

브라우저에게 작성한 텍스트의 구조와 의미에 관해 알려준다.

  • 웹 페이지의 구조를 브라우저가 알 수 있게 하기 위해서 태그들의 쌍을 사용
  • <h1> Hello </h1>
    • 시작(opening)태그 + 콘텐츠(내용) + 종료(closing) = 엘리먼트라고 한다.

하이퍼 텍스트

  • 웹 전체의 기초가 되는 것

  • 단일 페이지에서 벗어나 다른 페이지와 연결할 수 있게 해주는 것

  • <a href="#"></a> 와 같은 모양으로 링크가 걸린다.

    href속성은 목적지를 명시

시맨틱

시맨틱 검색

'’시맨틱검색”“은 검색로봇이 검색어 의미를 스스로 분석하고 추리해 원하는 정보를 더 정교하게 찾아주는 검색방식

시맨틱 마크업

“시맨팁 마크업”이란 HTML의 태그를 사용하여 문서 안의 내용이 담고 있는 의미가 무엇인지 표현할 수 있도록 구조를 작성하는 것을 말함

내용이 담고 있는 의미가 무엇인지 표현할 수 있도록 작성해야 한다.
HTML과 CSS를 사용하여 ‘구조’와 ‘표현’을 구분해야 한다.
엘리먼트는 HTML문서의 개별적인 구성요소이다.

기본 마크업 태그 확인하기


블록레벨

  • 브라우저 안에 블록처럼 쌓여가며 줄 바꿈을 하여 표시한다.
  • 항상 새로운 줄로 줄 바꿈하여 표시 된다.
  • 너비 값은 브라우저 화면에 100%로 꽉 차게 표시된다.

ex)

<p></p> 태그, <h1></h1> 태그

views

인라인 레벨

  • 이미 표시된 엘리먼트에 이어 같은 줄 안에 표시되고 너비는 내용만큼 차지한다.
  • 같은 줄 안에 이어서 표시된다.
  • 너비 값은 요소가 가지고 있는 값 자체로 표시된다.

ex)

views

띄어쓰기 해보기

1, &nbsp;

views

2, padding값 주기

views

기본 마크업 태그 h

[ 모든 페이지에는 h1 태그가 한개씩만 존재해야 한다 ]

<h1>은 가장 중요한 헤더정보에
<h6>은 가장 낮은 중요도의 헤더정보를 정의
<h1>첫번째로 중요한 제목</h1>
<h2>두번째로 중요한 내용</h2>
<h2>두번째로 중요한 내용</h2>
<h3>세번째로 중요한 내용</h3>
views

기본 마크업 태그 address

문서 저자나 소유자를 위한 연락처 정보 정의


기본 마크업 태그 div

css 스타일 적용해 디자인을 입힐 때 많이 사용

블록레벨 엘리먼트들을 그룹으로 묶음


기본 마크업 태그 span

html문서에서 인라인 레벨 엘리먼트들을 그룹으로 묶을 때 사용



DOCTYPE

- DOCTYPE은 html의 태그는 아니지만 웹페이지에서 맨 처음에 선언되어 어떤 종류의 html을 사용할지 웹브라우저에게 알려줄 목적으로 사용
- 브라우저는 DOCTYPE 선언을 확인하고 브라우저 모드를 결정한다.
- 브라우저 모드는 표준 호환 모드,  비표준 호환 모드
- 웹 표준에서는 웹 페이지를 제대로 표현하기 위해서는 올바른 문서 형태를 정의 해주어야 한다.
- 가장 많이 사용되고 있는 HTML 버전은 HTML4.01  과  XHTML1.0
- 최신 버전인 HTML5를 문서형식으로 사용하는 웹사이트도 증가 추세

W3C의 doctype추천 페이지

HTML Validator (문서 유효성 검증 )

‘HTML Validator’DOCTYPE에 선언된 데로 태그가 올바르게 작성되었는지 여부를 확인하고 인증해주는 서비스 (validator.w3.org)

Django-mysite만들기8 URL 간결화 - namespace(app_name주기)

|

전체 코드 보기


지난포스팅


Django project mysite의 application이 늘어나면서

project의 urls.py가 복잡해졌다.

urlpatterns = [
    path('', main_views.index),

    path('user/joinform', user_views.joinform),
    path('user/joinsuccess', user_views.joinsuccess),
    path('user/join', user_views.join),

    path('user/api/checkemail', user_views.checkemail),

    path('user/loginform', user_views.loginform),
    path('user/login', user_views.login),
    path('user/logout', user_views.logout),

    path('user/updateform', user_views.updateform),
    path('user/update', user_views.update),

    path('guestbook/list', guestbook_views.list),
    path('guestbook/write', guestbook_views.write),
    path('guestbook/delete/<int:no>', guestbook_views.delete),
    path('guestbook/delete', guestbook_views.delete),

    path('board/list/<int:page>', board_views.list),
    path('board/list/', board_views.list),
    path('board/writeform', board_views.writeform),
    path('board/writeform/<int:no>', board_views.writeform),
    path('board/write', board_views.write),
    path('board/<int:no>', board_views.view),
    path('board/modify/<int:no>', board_views.modifyform),
    path('board/modify', board_views.modify),
    path('board/delete/<int:no>', board_views.delete),


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

너무 많은 url 매핑들…!

복잡해진 코드를 해결하기 위해서 각각의 Django App 안에 urls.py 파일을 만들고,

메인 urls.py 파일에서 각 Django App의 urls.py 파일로 URL 매핑을 위탁하게 코드를 수정하자!


[1] 각각의 App안에 urls.py 만들기

views

[2] 각각 App에서 urls.py 매핑해주기

맨 위의 아주 긴~ urls.py를 각각의 app매핑에 따라서 urls.py로 나눠 배치해준다.

ex) board기능

board/urls.py

from django.urls import path
from . import views # 해당 app의 views import!

urlpatterns = [
    # path는 main에서 `board/` url을 위탁시키기에 그 뒤의 주소부터 넣어준다.
    path('list/<int:page>', views.list),
    path('list/', views.list),
    path('writeform', views.writeform),
    path('writeform/<int:no>', views.writeform),
    path('write', views.write),
    path('<int:no>', views.view),
    path('modify/<int:no>', views.modifyform),
    path('modify', views.modify),
    path('delete/<int:no>', views.delete),
]

이렇게 모든 app의 각각의 urls.py를 만들어 코드를 나눠주면 된다.


[3] 메인 urls.py에서 include 해주기

include모듈을 import해준다.

from django.urls import include

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('main.urls')),
    path('user/', include('user.urls')),
    path('guestbook/', include('guestbook.urls')),
    path('board/', include('board.urls')),
    path('admin/', admin.site.urls),
]

main urls.py의 코드가 아주 간결해졌다!


[4] URL 간결화시키기 (app_name 주기)

각각의 app의 urls.py를 분리시킨 뒤,

templates html에서 사용하는 url를 간결화를 위해서

app_name과 각각 url path에 name을 줄 수 있다.

ex) board/urls.py

from django.urls import path
from . import views

app_name = 'board'

urlpatterns = [
    path('list/<int:page>', views.list, name='list_page'),
    path('list/', views.list, name='list'),
    path('writeform', views.writeform, name='writeform'),
    path('writeform/<int:no>', views.writeform, name='reply_writeform'),
    path('write', views.write, name='write'),
    path('<int:no>', views.view, name='view'),
    path('modify/<int:no>', views.modifyform, name='modifyform'),
    path('modify', views.modify, name='modify'),
    path('delete/<int:no>', views.delete, name='delete'),
]

언뜻보면, urls.py 코드가 길어져 더 복잡해 보일 수 있지만,

이렇게 각각의 name을 주고 나면, url 매핑을 할때 하드코딩을 피할 수 있게 된다.

ex) board/write.html

views

파라미터 보내기

위의 urls.py에서,

path('writeform/<int:no>', views.writeform, name='reply_writeform'),

위와 같이 파라미터를 보내는 경우 어떻게 처리해야할까?

views

[5] views에서도 간결한 처리 가능

redirect로 처리할 경우

return redirect('board:view', article_id)

이런식으로 처리할 수 있다.

Django-mysite만들기7 계층형 답글 게시판 기능 추가

|

django mini project

cafe24신입사원 교육과정 - django 수업 내용 정리

강사님github


전체 코드 보기


board app 추가 ! python manage.py startapp board

settings.py 에 app 추가

[1] 1(user):N(board) 관계 Model 정의

board/models.py

views
db-schema
from django.db import models

# Create your models here.
from user.models import User


class Board(models.Model):
    title = models.CharField(max_length=100)
    content = models.CharField(max_length=2000)
    hit = models.IntegerField(default=0)
    regdate = models.DateTimeField(auto_now=True)
    groupno = models.IntegerField(default=0)
    orderno = models.IntegerField(default=0)
    depth = models.IntegerField(default=0)
    user = models.ForeignKey(User, to_field='id', on_delete=models.CASCADE)
    # user모델의 id를 fk로, user가 지워지면 같이 삭제

user model정의 보기

계층형 게시판을 위해서,

groupno, orderno, depth 컬럼을 정의해 주었다.

groupno 컬럼 - 첫 게시글과, 그 게시글의 답글들에게 같은 groupno을 주어서 보여주기 위함

orderno 컬럼 - 같은 groupno의 게시글들을 최신순으로 위로 올리기 위함

depth 컬럼 - 답글들을 한 칸씩 밀려서 보이게 하기 위함

views

admin.py등록

from board.models import Board
admin.site.register(Board)

python manage.py makemigrations

python manage.py migrate



[2] urls.py

urlpatterns = [
   ...
    path('board/list/<int:page>', board_views.list),
    path('board/list/', board_views.list),
    path('board/writeform', board_views.writeform),
    path('board/writeform/<int:no>', board_views.writeform),
    path('board/write', board_views.write),
    path('board/<int:no>', board_views.view),
    path('board/modify/<int:no>', board_views.modifyform),
    path('board/modify', board_views.modify),
    path('board/delete/<int:no>', board_views.delete),
	...
]

[3] views.py

게시글 작성

# 글쓰기 폼으로 보내는 
# url에서 no을 확인해 -1인 경우는 새글쓰기, 그외에는 해당no 게시글의 답글쓰기로 판별
def writeform(request, no=-1):
    if no == -1:
        return render(request, 'board/write.html')
    else:
        return render(request, 'board/write.html', {"no":no})

def write(request):
    board = Board()
    board.title = request.POST['title']
    board.content = request.POST['content']
    board.user = User.objects.get(id=request.session['authUser']['id'])

    # 새글 작성일 경우
    if request.POST['no'] == '-1':
        value = Board.objects.aggregate(max_groupno=Max('groupno'))
        board.groupno = value["max_groupno"]+1
        board.save()
    # 답글 작성일 경우 orderno, groupno, depth 설정
    else:
        board2 = Board.objects.get(id=request.POST['no'])
        # 답글이 작성될 경우, 작성되는 답글의 orderno+1보다 크거나 같은 그룹 내의 이전 게시글의 orderno을 +1씩 올려주어야 한다.
        Board.objects.filter(orderno__gte=board2.orderno+1).update(orderno=F('orderno') + 1)
        board.groupno = board2.groupno
        board.orderno = board2.orderno+1
        board.depth = board2.depth+1
        board.save()

    return HttpResponseRedirect('list')

글 상세보기

def view(request, no=0):
    if no == 0: return HttpResponseRedirect('list')

    board = Board.objects.filter(id=no)
    board.update(hit=F('hit')+1)
    data = {
        'board':board[0]
    }
    return render(request, 'board/view.html', data)

수정 및 삭제

def modifyform(request, no=0):
    board = Board.objects.filter(id=no)[0]
    data = {
        'board':board
    }
    return render(request, 'board/modify.html', data)

def modify(request):
    board_id = request.POST['id']
    board = Board.objects.get(id=board_id)
    board.title = request.POST['title']
    board.content = request.POST['content']
    board.save()
    data = {
        'board':board
    }
    return HttpResponseRedirect(board_id, data)

def delete(request, no=0):
    board = Board.objects.get(id=no)
    board.title = '삭제된 글입니다.'
    board.save()
    return HttpResponseRedirect('/board/list')

코드가 정리안되고 복잡한 느낌이 든다. .. . ..

코드를 다시 정리해야겠다.