전산.통계/웹프로그래밍

파이썬 장고 프로그래밍 기초

방윤화 2022. 2. 11. 11:41

1. 기본 설치 프로그램

1) 파이썬(python)

2) 데이터베이스(database)

3) 파이참 설치(pycharm)

 

2. Bookmark 만들기

1) application  설계

- 화면 UI 설계

. 전체 리스트 화면(bookmark_list.html)

. 상세 화면(bookmark_detail.html)

 

- 테이블 설계

필드명 타입 제약조건 설명
id
title
url
Integer
CharField(100)
URLField
PK, Auto Increment
Blank
Unique
기본키(Primary Key)
북마크 제목
북마크 URL

 

- 로직 설계 : URL → view → template 간의 처리 흐름 설계

  URL view template
시작  →  전체 리스트 출력 /bookmark/          → BookmarkLV.as_view     → bookmark_list.html
1개 선택  →  상세 출력 /bookmark/99/      → BookmarkDV.as_view    → bookmark_detail.html

 

- URL  설계 : 3요소간 매핑

URL  패턴 view 이름 template 파일 이름
/bookmark/
/bookmark/99/
/admin/
BookmarkLV(ListView)
BookmarkDV(DetailView)
(장고 제공기능)
bookmark_list.html
bookmark_detail.html
북마크 URL

 

- 코딩 순서

작업순서 관련 명령/파일 필요한 작업내용
2) 뼈대 만들기 startproject
settings.py
migrate
createsuperuser
startapp
settings.py
프로젝트 생성
프로젝트 설정 항목 변경
User/Group 테이블 생성
프로젝트관리자 생성
앱 생성
앱 등록
3) model 코딩 models.py
admin.py
makemigrations
migrate
모델(테이블) 정의
모델 등록
모델 변경사항 추출
변경사항 데이터베이스 반영
4) URLconf 코딩 urls.py URL 매핑
5) view 코딩 views.py view 로직 작성
6) template 코딩 templates directory template  파일(출력 화면) 작성

 

2) 뼈대 만들기 : 파이참 실행

- 프로젝트 생성 : 프로젝트와 가상환경이 만들어짐

django-admin startproject mysite

 

(pycharm)

- 메뉴 File(left_click) - New Project(left_click) - (input) ProjectName - Create((left_click)

- 장고 설치 - 하단 Terminal 에서 작업(입력 후 enter)

pip install Django

django-admin startproject config .

 

- 프로젝트 설정 항목 변경 : config/settings.py

① ALLOWED_HOSTS = [ '192.168.56.101', 'localhost', '127.0.0.1' ]

    DEBUG=True 면 개발 모드(지정하지 않으면 첫번째 것을 제외한 주소로 자동 지정됨), False 면 운영 모드(서버의 IP 또는 도메인 지정)

② INSTALLED_APPS = [ 'bookmark.apps.BookmarkConfig', ]

    프로젝트에 포함된 모든 앱 등록

③ TEMPLATES = [ 'DIRS' : [ os.path.join(BASE_DIR, 'templates')],]  # DIRS 외는 그대로 사용. template 파일이 위치할 디렉토리 지정

④ DATABASES = { 'default': { 'ENGINE' : 'django.db.backends.mysql', } } # MariaDB는 mysql로 등록

⑤ TIMEZONE = 'Asia/Seoul'

⑥ STATIC_URL = '/static/'  # 변경없이 그대로

    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] # static 파일이 위치할 디렉토리 지정

⑦ MEDIA_URL = '/media/'

    MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 파일 업로드시 시용. 파일이 위치할 디렉토리 지정

- pip install mysqlclient

- 기본 테이블 생성 - 하단 Terminal 에서 작업(입력 후 enter)

python manage.py migrate

 

- superuser(관리자) 생성

python manage.py createsuperuser # (input)Username(admin)/Email/Password/Password(again)

 

- 앱 생성

python manage.py startapp bookmark

 

- 앱 등록 config/settings.py

INSTALLED_APPS = [ 'bookmark.apps.BookmarkConfig', ]

. 간단히 앱 명칭인 'bookmark'만 등록해도 되지만, 앱설정 클래스로 등록하는게 더 정확.  앱설정 클래스는 startapp bookmark 실행시 자동 생성된 apps.py 파일에 class 명칭이 'BookmarkConfig'로 되어 있어 경로('bookmark.apps.'를 앞에 붙여 'bookmark.apps.BookmarkConfig'로 등록함

 

3) model 코딩

- bookmark/models.py 테이블 정의 

from django.db import models

class Bookmark(models.Model):   # django.db.models.Model을 상속받음
    title = models.CharField('TITLE', max_length=100, blank=True)   # 공백 허용
    url = models.URLField('URL', unique=True)   # 'URL'은 컬럼의 별칭(verbose_name). Admin 사이트에서 보임 

    def __str__(self): # 객체를 문자열로 표현할때 사용하는 레코드명 함수. model class의 객체는 테이블의 레코드 1개를 의미.
        return self.title   # 레코드명은 title 컬럼을 사용

데이터베이스의 테이블 1개를 class 1개로 정의, 테이블 컬럼은 class의 변수로 매핑. 변수 타입은 장고에서 미리 정의해둔 필드 클래스를 사용.

 

- bookmark/admin.py Admin 사이트에 반영 

from django.contrib import admin
from bookmark.models import Bookmark

@admin.register(Bookmark)   # Admin 사이트에 등록. @데코레이션 대신에 맨 아랫줄과 같이 할수도 있음.
class BookmarkAdmin(admin.ModelAdmin) :   
      list_display = ('id', 'title','url')  # Admin 사이트에서 보여줄 내용 명시, 'id' 는 자동 생성

# admin.site.register(Bookmark, BookmarkAdmin)

 

- 데이터베이스 변경사항 반영

python manage.py makemigrations bookmark   # bookmark/migrations 디렉토리 하위에 마이그레이션 파일을 만듬

python manage.py migrate   # 위의 마이그레이션 파일을 이용해 데이터베이스에 테이블을 만듬.

 

- 테이블 모습 확인

python manage.py runserver

. 브라우저에서 주소창에 http://127.0.0.1:8000/admin/을 치면 로그인 화면이 나옴.

. superuser로 로그인하면 User, Group 테이블과 추가한 Bookmark 테이블이 나옴.

* 앱 명칭(대문자), 테이블명(첫자만 대문자, 복수)

. Add 버튼을 클릭하면 필드의 명칭이 나옴. (null=False, blank=False 인 경우는 고딕체로 나옴). Meta 내부 class 속성

 

4) URLconf 코딩

- config/urls.py, bookmark/urls.py

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

from bookmark.views import BookmarkLV, BookmarkDV   # URLconf에서 호출할 view 모듈 class명 
# from bookmark.views import *   # 위 대신 사용가능하나, 불필요한 view 포함 호출. 이름 충돌 우려 

urlpatterns = [
    path('admin/', admin.site.urls),   # admin 사이트 사용

    # class based views
    path('bookmark/', BookmarkLV.as_view(), name='index'),   # URL /bookmark/ 요청을 처리할 view를 BookmarkLV로 지정
    path('bookmark/<int:pk>/', BookmarkDV.as_view(), name='detail'),   # URL /bookmark/99/ 요청은 BookmarkDV로 지정
]

. path()  함수는 route, view 2개는 필수인자, kwargs, name은 선택인자. name은 template에서 사용

 

- class형 view가 간단한 경우 views.py를 코딩 안하고, urls.py에 파라미터로 지정 가능

. 즉 아래와 같이 urls.py에 views.py를 합쳐 놓은 형태. 확장성 등을 고려하여 위의 방식을 권장

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

from django.views.generic import ListView, DetailView
from bookmark.models import Bookmark  # 추가

urlpatterns = [
    path('admin/', admin.site.urls),

    # urls with view definition
    path('bookmark/', ListView.as_view(model=Bookmark), name='index'),  # model=Bookmark 추가
    path('bookmark/<int:pk>/', DetailView.as_view(model=Bookmark), name='detail'),  # model=Bookmark 추가
]

 

5) Views 코딩 

- 고려사항 : 어떤 제네릭 뷰를 사용할 것인가?

. 테이블에서 여러개의 레코드를 가져오는 로직인 경우 : ListView

. 테이블에서 1개의 레코드를 가져오는 로직인 경우 : DetailView

 

- bookmark/views.py

from django.views.generic import ListView, DetailView # 제네릭 뷰 사용 정의
from bookmark.models import Bookmark

class BookmarkLV(ListView): # Bookmark 테이블의 레코드 list를 보여주는 view
    model = Bookmark
class BookmarkDV(DetailView): # Bookmark 테이블의 특정 레코드의 상세 내용을 보여주는 view
    model = Bookmark

* ListView : 객체가 들어있는 list를 구성해서 이를 context변수(object_list)로 template로 넘김. 모든 레코드를 넘길때는 모델 class명만 지정해줌.

* 디폴트로 지정해 주는 속성 2가지.

. context 변수명 : object_list,

. template 파일명 : 모델명소문자_list.html(bookmark/bookmark_list.html)

* detailView : 특정 객체 1개 레코드를 context변수(object)로 template로 넘김. PrimaryKey로 조회한 경우는 모델 class명만 지정해줌.

* 디폴트로 지정해 주는 속성 2가지.

. context 변수명 : object,

. template 파일명 : 모델명소문자_detail.html(bookmark/bookmark_detail.html)

 

6) Template 코딩 bookmark/templates/bookmark 디렉토리에 .html 파일로 저장

- bookmark_list.html

<!DOCTYPE html>
<html>
<head>
<title>Django Bookmark</title>
</head>
<body>
<div id="content">


<h1>Bookmark List</h1>
<ul>
        {% for bookmark in object_list %} 
            <li><a href="{% url 'detail' bookmark.id %}">{{ bookmark }}</a></li>
        {% endfor %}
</ul>
</div>

</body>
</html>

. object_list는 class형 view에서 넘겨주는 context. object_list 객체를 순회하면서 순서없는 리스트로 보여 줌

. <a href="{% url 'detail' bookmark.id %}">는 urls.py의 'detail' 패턴으로 URL 링크를 연결함

  path('bookmark/<int:pk>/', Bookmark.DV.as_view(), name='detail'), # /bookmark/99/ 

. {{ bookmark }}는 특정 레코드 1개 의미. 해당 객체의 __str__() 메소드 호출(models.py)하여 return self.title에 따라 bookmark.title을 출력.

. {% url %}은 URL 패턴에서 URL 스트링을 추출하라는 의미.

 

- bookmark_detail.html

<!DOCTYPE html>
<html>
<head>
<title>Django Bookmark Detail</title>
</head>
<body>
<div id="content">


<h1>{{ object.title }}</h1>
<ul>
        <li>URL: <a href="{{ object.url }}">{{ object.url }}</a></li>
</ul>
</div>

</body>
</html>

. {{ object.title }}, {{ object.url }}은 models.py의 bookmark.title, bookmark.url을 의미

 

7) 완성 작업 확인

- python manage.py runserver

 

- 브라우저에서 http://127.0.0.1:8000/admin/

- +Add 데이터 입력

 

- 브라우저에서 http://127.0.0.1:8000/bookmark/

. 각 항목의 url 확인 및 링크 접속 확인

 

3. 블로그 만들기

0) 애플리케이션 설계

- 테이블 설계

필그명 타입 제약조건 설명
id
title
slug
description
content
create_date
modify_date
Integer
CharField(50)
SlugField(50)
CharField(100)
TextField
DateTimeField
DateTimeField
PK, Auto Increment

Unique
Blank

aoto_now_add
auto_now
기본 키
포스트 제목
제목 별칭
포스트 내용 한줄 설명
포스트 내용
생성 날자
수정 날자

- URL 설계

URL 패턴 View 이름 Template  파일 명
/blog/
/blog/post/
/blog/post/django-example/
/blog/archive/
/blog/archive/2019/
/blog/archive/2019/nov/
/blog/archive/2019/nov/10/
/blog/archive/today/
/admin/
PostLV(ListView)
PostLV(ListView)
PostDV(DEtailView)
PostAV(ArchiveIndexView)
PostYAV(YearArchiveView)
PostMAV(MonthArchiveView)
PostDAV(DayArchiveView)
PostTAV(TodayArchiveView)
(장고 제공 기능)
post_all.html
post_all.html
post_detaill.html
post_archive.html
post_archive_year.html
post_archive_month.html
post_archive_day.html
post_archive_day.html

- 작업 순서

작업순서 관련 명령/파일 작업 내용
뼈대 만들기 startproject
settings.py
migrate
createsuperuser
startapp
settings.py
앞에서 했으므로 불필요



blog 앱 생성
앱 등록
model 코딩 models.py
admin.py
model(table) 정의
admin 사이트에 model 등록
url 코딩 urls.py url 정의
view  코딩 views.py view 로직 작성
template 코딩 .html template 파일 작성

 

1) 뼈대 만들기

- python manage.py startapp blog

 

- 앱 등록 config/settings.py

INSTALLED_APPS = [ 'blog.apps.BlogConfig', ]  # 추가

 

2) Model 코딩 models.py

from django.db import models
from django.urls import reverse

class Post(models.Model):
    title = models.CharField('TITLE', max_length=50)
    slug = models.SlugField('SLUG', unique=True, allow_unicode=True, help_text='one word for title alias.')
    description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple description text.')
    content = models.TextField('CONTENT')
    create_dt = models.DateTimeField('CREATE DATE', auto_now_add=True)
    modify_dt = models.DateTimeField('MODIFY DATE', auto_now=True)

    class Meta:
        verbose_name = 'post'
        verbose_name_plural = 'posts'
        db_table  = 'blog_posts'
        ordering  = ('-modify_date',)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse('blog:post_detail', args=(self.slug,))

    def get_previous(self):
        return self.get_previous_by_modify_dt()

    def get_next(self):
        return self.get_next_by_modify_dt()

. reverse() 함수는 URL 패턴을 만들어주는 내장함수.

예 : urls.py의 패턴 '/polls/<int:pk>/results/' 로 뷰함수 매핑(/polls/3/results/)으로 views.results() 를 호출하는데,
reverse() 함수는 URL을 만들어 줌(reverse('polls:results', arg=(3,)))은 /polls/3/results/으로 URL을 만들어 줌

. title : 한줄로 입력(CharField), 별칭('TITLE')- form 화면, admin 사이트에서 확인 가능

slug란?
페이지나 포스트를 설명하는 핵심단어의 집합으로 중요한 단어만 이용해 제목을 작성. 제목에서 조사, 전치사, 부호를 빼고 하이픈으로 연결
콘텐츠의 고유주소로 이용하며, URL에서 pk 대신 사용됨. 검색엔진에 유용. 해당 필드에 인덱스가 디폴트로 생성됨.
unique=True 는 검색할때 기본키로 사용. allow_unicode=True는 한글처리 가능. help_text는 form화면에 나타남.

. content : 여러줄 입력 가능(TextField)

. auto_now_add : 객체 처음 생성시각을 자동 기록

. auto_now : 객체가 데이터베이스에 저장될때 시각을 자동 기록(변경시각)

. meta 내부 class : 필드 속성이외에 필요한 파라미터를 정의

. verbose_name, verbose_name_plural : 테이블의 별칭 단수, 복수

. db_table : 데이터베이스에 저장되는 테이블명. 없으면 앱명_모델클래스명(blog_post)

. ordering : 객체 리스트 출력시 modify_dt 기준 내림차순임.

. get_absolute_url :  이 메소드가 정의된 객체를 지칭하는 URL 을 반환. reverse()를 호출

. reverse('blog:post_detail', args=(self.slug,))은 /blog/post객체.slug/post_detail/ URL을 반환

. get_previous_post : modify_dt()를 기준한 최신 포스트 반환

. get_next_post : modify_dt()를 기준한 예전 포스트 반환

. 위 3개의 get메소드는 template에서 사용함

 

- admin.py 등록

from django.contrib import admin
from blog.models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):   # Post class가 Admin site에서 보여질 형태를 정의
    list_display  = ('id', 'title', 'modify_date')
    list_filter   = ('modify_dt',)
    search_fields = ('title', 'content')
    prepopulated_fields = {'slug': ('title',)}

. list_filter : 필터 사이드바에 사용할 컬럼

. search_fields : 검색박스에 사용할 해당 컬럼

. prepopulated_fields : slug 필드는 title 필드를 사용해 미리 채워줌

 

- 테이블 변경사항을 데이터베이스에 반영

python manage.py makemigrations

python manage.py migrate

 

- 변경사항 확인

python manage.py runserver

http://192.168.56.101:8000/admin/

3) URLconf 코딩

- urls.py 를 config/urls.py와 앱별로 구부한 urls.py로 나눔

- config/urls.py

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

urlpatterns = [
    path('admin/', admin.site.urls),
    path('bookmark/', include('bookmark.urls')),
    path('blog/', include('blog.urls')),
]

. include() : APP_URLCONF로 처리를 위임.

 

- bookmark/urls.py를 수정

from django.urls import path
from bookmark.views import BookmarkLV, BookmarkDV

app_name = 'bookmark' # namespace
urlpatterns = [
    path('', BookmarkLV.as_view(), name='index'),
    path('<int:pk>/', BookmarkDV.as_view(), name='detail'),
]

. app_name = 'bookmark' : namespace를 bookmark로 지정

. path : URL 부분에 /bookmark/ 가 없어짐. url 패턴의 이름은namespace를 포함해 'bookmark:index' 가 됨. URL 패턴의 이름이 변경되었으므로 template에서 변경해 줘야 함

 

- blog/urls.py 작성

from django.urls import path, re_path   # re_path : 한글 슬러그를 위해 사용
from blog import views

app_name = 'blog'
urlpatterns = [    
    path('', views.PostLV.as_view(), name='index'),    # 예시 URL : /blog/. /blog/요청할 view는 PostLV로 지정. 패턴 이름은 namespace 포함 'blog:index'가 됨
    path('post/', views.PostLV.as_view(), name='post_list'), # Example: /blog/post/ (same as /blog/) # PostLV view는 /blog/와 /blog/post/ 두개 패턴을 처리 함   
    re_path(r'^post/(?P<slug>[-\w]+)/$', views.PostDV.as_view(), name='post_detail'), # Example: /blog/post/django-example/. /blog/post/slug/ 요청을 처리할 view는 PostDV.
    # 다음 path('post/<slug:slug>/', views.PostDV.as_view(), name='post_detail'),의 경우는 한글 처리 안됨. <slug> 컨버터는 '[-a-zA-Z0-9]+'만 인식하기때문.
    path('archive/', views.PostAV.as_view(), name='post_archive'),  # Example: /blog/archive/    
    path('archive/<int:year>/', views.PostYAV.as_view(), name='post_year_archive'),  # Example: /blog/archive/2019/    
    path('archive/<int:year>/<str:month>/', views.PostMAV.as_view(), name='post_month_archive'), # Example: /blog/archive/2019/nov    
    path('archive/<int:year>/<str:month>/<int:day>/', views.PostDAV.as_view(), name='post_day_archive'), # Example: /blog/archive/2019/nov/10/. 년/월/일 부분을 '/blog/archive/4자리숫자/3자리소문자/한두자리숫자/'로 제한하려면
    # re_path(r'^archive/(?<year>\d{4})/(?<month>[a-z]{3})/(?<day>\d{1,2})/$', views.PostDAV.as_view(), name='post_day_archive'),
    path('archive/today/', views.PostTAV.as_view(), name='post_today_archive'),  # Example: /blog/archive/today/
]

 

4) View 코딩

- blog/views.py

from django.views.generic import ListView, DetailView
from django.views.generic.dates import ArchiveIndexView, YearArchiveView, MonthArchiveView
from django.views.generic.dates import DayArchiveView, TodayArchiveView

from blog.models import Post

# Create your views here.

#--- ListView
class PostLV(ListView) : # ListView 제네릭뷰를 상속받아 PostLV view를 정의. ListView는 테이블로 부터 object_list를 가져와 출력
    model = Post # 대상 테이블은 Post
    template_name = 'blog/post_all.html'  # 지정하지 않으면 'blog/post_list.html'가 됨.
    context_object_name = 'posts' # template 파일로 넘겨주는 context 변수명 지정. 디폴트 object_list명 항상 사용가능
    paginate_by = 2 # 한 페이지에 보여주는 객체 수. 화면 하단에 버튼을 만들어 페이징 기능 사용 가능

#--- DetailView
class PostDV(DetailView) : # 테이블의 특정 객체 조회를 위한 키는 기본 키 대신 slug 속성 사용. slug 파라미터는 urls.py에서 추출하여 view로 넘겨 줌
    model = Post

#--- ArchiveView 날자 제네릭뷰
class PostAV(ArchiveIndexView) : # ArchiveIndexView 제네릭뷰는 Post테이블로 부터 객체리스트를 가져와 modify_dt날자 기준 최신 객체를 먼저 출력 
    model = Post
    date_field = 'modify_dt'  # 기준 날자 지정

class PostYAV(YearArchiveView) : # YearArchiveView 제네릭뷰는 Post테이블로 부터 modify_dt날자의 년도기준 객체를 가져와 그 객체들이 속한 월을 리스트로 출력. 연도 파라미터를 urls.py에서 추출하여 view로 넘겨 줌
    model = Post
    date_field = 'modify_dt' # 날자가 YYYY인 포스트를 검색해 그list들의 변경월을 출력
    make_object_list = True # 요청 년도에 해당하는 객체로만 object_list로 만들어 template로 넘겨중. False면 안 만듬.

class PostMAV(MonthArchiveView) : # 년월기준 객체를 가져와 그 객체들이 속한 리스트로 출력.
    model = Post
    date_field = 'modify_dt'

class PostDAV(DayArchiveView) : # 년월일기준 객체를 가져와 그 객체들이 속한 리스트로 출력.
    model = Post
    date_field = 'modify_dt'

class PostTAV(TodayArchiveView) : # # 오늘 기준 객체를 가져와 그 객체들이 속한 리스트로 출력. PostDAV와 유사
    model = Post
    date_field = 'modify_dt'

 

5) Template 코딩

- blog/templates/blog/post_all.html

* 별도로 HTML 태그<html> 지정이 없으면 장고는 <body>영역으로 간주 함.

<h1>Blog List</h1>
<br>

{% for post in posts %}   <!-- posts(object_list) 객체들을 순회 -->
    <h3><a href='{{ post.get_absolute_url }}'>{{ post.title }}</a></h3>  <!-- URL /blog/post/slug단어/ 형식 -->
    {{ post.modify_dt|date:"N d, Y" }}  <!-- date:"N d, Y" 포맷은 'July 05, 2020' 같은 형식임 -->
    <p>{{ post.description }}</p>
{% endfor %}
<br/> <!-- 공백 줄 뗌 -->

<div>
    <span> <!-- span 태그 내의 요소들은 같은 줄에 배치.페이징 기능을 위한 줄 -->
        {% if page_obj.has_previous %}  <!-- page_obj는 장고 Page 객체가 들어 있는 context 변수. 현page기준 앞 page 확인
            <a href="?page={{ page_obj.previous_page_number }}">PreviousPage</a> 
            <!-- page_obj.previous_page_number는 앞 page 번호. URL은 '?page=3' 같은 형식 -->
        {% endif %}
      
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
        <!-- page_obj.number는 현 page번호, page_obj.paginator.num_pages는 총page수
      
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">NextPage</a>
        {% endif %}
    </span>
</div>

* template 에서 URL 추출 함수(아래 두 문장은 동일)

<a href='{{ post.get_absolute_url }}'>{{ post.title }}</a> # model.py에 메소드로 정의되어 있어야 가능

def get_absolute_url(self):
        return reverse('blog:post_detail', args=(self.slug,))
mdels.py에 있는 위의 정의에 따라 <a href="{% url 'blog:post_detail' post.slug %}">{{ post.title }}</a>과 같음

 

- blog/templates/blog/post_detail.html

<h2>{{ object.title }}</h2>  <!-- object 객체는 PostDV 클래스형 뷰에서 context변수로 넘겨주는 Post 특정 객체 1개 -->

<p>
    {% if object.get_previous %} <-- 이전날자 객체 반환. 변경 날자가 현재 객체보다 더 최신 객체가 있는지 확인? -->
    <a href="{{ object.get_previous.get_absolute_url }}" title="View previous post">&laquo;--{{ object.get_previous }}
    <!-- get_previous는 이전 객체, get_previous.get_absolute_url는 이전 객체를 지칭하는 URL 패턴 /blog/post/slug/ -->
    <!-- &laquo;는 HTML  특수문자 "<<"를 의미
    </a>
    {% endif %}

    {% if object.get_next %} <-- 다음날자 객체 반환. 변경 날자가 현재 객체보다 더 오래된 객체가 있는지 확인? -->
    | <a href="{{ object.get_next.get_absolute_url }}" title="View next post">{{ object.get_next }}--&raquo;</a>
    {% endif %}
</p>

<p>{{ object.modify_dt|date:"j F Y" }}</p>  <!-- date:"j F Y"는 포맷. '12, July 2020' -->
<br/>

<div>
    {{ object.content|linebreaks }} <!-- 객체의 content를 출력. linebreaks는 \n(new line)을 인식하게 함 -->
</div>

 

- blog/templates/blog/post_archive.html

* template 파일명은 view에서 template_name으로 지정. 지정하지 않으면 model의 class소문자_archive.html, model의class소문자_archive_year.html로 자동 지정됨

<h1>Post Archives until {% now "N d, Y" %}</h1> <!-- {% now "N d, Y" %}는 현재 나라와 시간. 'July 18, 2020' -->
<ul>
    {% for date in date_list %}  <!-- date_list는 QuerySet에서 날자 정보만 담은 DateQuerySet의 객체 리스트 -->
    <li style="display: inline;"> <!-- 년도 메뉴를 한줄에 보여주기 위함 -->
        <a href="{% url 'blog:post_year_archive' date|date:'Y' %}">Year-{{ date|date:"Y" }}</a></li>
        <!-- Year-YYYY 형식으로 보여주고 링크 연결 -->
    {% endfor %}
</ul>
<br/>

<div>
    <ul>
        {% for post in object_list %} <!-- object_list는 latest 사용 가능 -->
        <li>{{ post.modify_dt|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp;
        <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
        <!-- 순서없는 리스트로 수정일과 제목을 출력. 사이에 빈칸(&ndsp;) 3개. 포맷은 '2020-05-05', URL 링크
        {% endfor %}
    </ul>
</div>

 

- blog/templates/blog/post_archive_year.html

<h1>Post Archives for {{ year|date:"Y" }}</h1>

<ul>
    {% for date in date_list %}
    <li style="display: inline;">
        <a href="{% url 'blog:post_month_archive' year|date:'Y' date|date:'b' %}">{{ date|date:"F" }}</a></li> <!-- "F" July--> 
    {% endfor %}
</ul>
<br/>

<div>
    <ul>
        {% for post in object_list %}  <!-- object_list는 latest로 사용 불가. ArchiveIndexView에서만 정의 됨 -->
        <li>{{ post.modify_dt|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp;
        <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
        {% endfor %}
    </ul>
</div>

 

- blog/templates/blog/post_archive_month.html

<h1>Post Archives for {{ month|date:"N, Y" }}</h1>

<div>
    <ul>
        {% for post in object_list %}
        <li>{{ post.modify_date|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp;
        <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
        {% endfor %}
    </ul>
</div>

 

- blog/templates/blog/post_archive_day.html

* TodayArchiveView와 DayArchiveView 제네릭뷰는 디폴트 template 파일명이 같음. 

<h1>Post Archives for {{ day|date:"N d, Y" }}</h1>

<div>
    <ul>
        {% for post in object_list %}
        <li>{{ post.modify_date|date:"Y-m-d" }}&nbsp;&nbsp;&nbsp;
        <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
        {% endfor %}
    </ul>
</div>

 

6) 완성 작업 확인

- python manage.py runserver

 

- 브라우저에서 http://192.168.56.101:8000/admin/

- +Add 데이터 입력

. slug 필드는 title 필드로 부터 자동으로 채워짐. admin.py 에서 propopulated_fields 속성으로 정의 됨.

. 필드명이 별칭 으로 표시됨.

. create_dt, modify_dt는 auto_now_add, auto_now 속성이므로 나타나지 않음.

- 브라우저에서 http://192.168.56.101:8000/blog/

. 각 항목의 url 확인 및 페이지 이동 확인

- 브라우저에서 http://192.168.56.101:8000/blog/archive/

 

4. 프로젝트 첫 페이지 만들기

1) config/urls.py 수정

- view class는 'HomeView', URL 패턴명은 'home'으로 정의. 기존 urls.py에 추가

from config.views import HomeView
urlpatterns = [ path('', HomeView.as_view(), name='home'), ]

 

2) config/views.py  코딩

- 단순히 template만 보여 주는 로직이므로 TemplateView를 상속받아 코딩.

from Django.views.generic import TemplateView

class HomeView(TemplateView) : # TemplateView를 사용하는 경우에는 반드시 template_name변수를 오버라이딩 해 줘야 함
      template_name = 'home.html' # template 파일의 위치는 settings.py의 TEMPLATE_DIRS에 지정

 

3) Template 코딩하기

- 부트스트랩 홈페이지 활용(http://getbootstrap.com/)

. Docs(Documentation) > Components > Navbar(Navigation Bar) 에서 sample source와 color schemes를 가져와 사용.

. 화면요소를 부트스트랩 라이브러리에서 가져와야 함. Docs > Getting started > Introduction에서 링크를 가져옴

- home.html에서 다른 페이지에서 사용하는 제목, 메뉴 같은 공통부분은 base.html로 작성

. base.html에는 모든 페이지에서 공통 사용하는 제목, 메뉴, 그리고 상속기능에 맞춰 구성요소들을 배치하는 {% block %} 태그 기능이 있음.

 

- config/templates/base.html

<!DOCTYPE html>
<html lang="ko">

<head>
<title>{% block title %}Django Web Programming{% endblock %}</title>

{% load staticfiles %}
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% static "css/base.css" %}{% endblock %}" />
<link rel="stylesheet" type="text/css" href="{% block extrastyle %}{% endblock %}" />

</head>

<body>

<div id="header">
    <h2 class="maintitle">Easy&amp;Fast Django Web Programming</h2>
    <h4 class="welcome">Welcome, <a href="#">shkim</a> /
                <a href="#">Change Password</a> /
                <a href="#">Logout</a>
    </h4>
</div>

<div id="menu">
    <li><a href="#">Home</a></li>
    <li><a href="#">Bookmark</a></li>
    <li><a href="#">Blog</a></li>
    <li><a href="#">Photo</a></li>

    <li><a href="#">Add&bigtriangledown;</a>
        <ul>
            <li><a href="#">Bookmark</a></li>
            <li><a href="#">Blog</a></li>
    <li><a href="#">Photo</a></li>
        </ul>
    </li>
    <li><a href="#">Change&bigtriangledown;</a>
        <ul>
            <li><a href="#">Bookmark</a></li>
            <li><a href="#">Blog</a></li>
    <li><a href="#">Photo</a></li>
        </ul>
    </li>

    <li><a href="#">Archive</a></li>
    <li><a href="#">Search</a></li>
    <li><a href="#">Admin</a></li>
</div>

{% block content %}{% endblock %}

{% block footer %}{% endblock %}

</body>
</html>

 

- config/templates/home.html

{% extends "base.html" %}

{% block title %}home.html{% endblock %}

{% load staticfiles %}
{% block extrastyle %}{% static "css/home.css" %}{% endblock %}

{% block content %}

<div id="content_home">

    <div id="homeimg">
        <a href="/"><img src="{% static 'img/Ulleungdo.jpg' %}" style="height:256px;"/></a>
        <h4 style="margin: 0;">This is Django powered web site.</h4>
    </div>

    <hr style="margin: 5px 0;">

    <h2>Select Application</h2>

    <table id="applist">
        <tr>
        <td><b><i><a href="#">Bookmark</a></i><b></td>
        <td>You can write your own post and share to others. Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo</td>
        <td id="Edit"><i><a href="#">Add</a></i></td>
        <td id="Edit"><i><a href="#">Change</a></i></td>
        </tr>

        <tr>
        <td><b><i><a href="#">Blog</a></i><b></td>
        <td>You can write your own post and share to others. Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo</td>
        <td id="Edit"><i><a href="#">Add</a></i></td>
        <td id="Edit"><i><a href="#">Change</a></i></td>
        </tr>

        <tr>
        <td><b><i><a href="#">Photo</a></i><b></td>
        <td>You can write your own post and share to others. Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo</td>
        <td id="Edit"><i><a href="#">Add</a></i></td>
        <td id="Edit"><i><a href="#">Change</a></i></td>
        </tr>
    </table>
</div>
{% endblock content %}

{% block footer %}
<div id="footer">
    &copy; shkim 2016
</div>
{% endblock footer %}

 

- config/static/css/base.css

body {
    font-family: "Lucida Grande", Verdana, Arial, sans-serif;
    font-size: 12px;
}

/* PAGE STRUCTURE */
div#header {
    position: absolute;
    top: 0px;
    left: 0px;
    height: 30px;
    width: 100%;
    display: table;
    background: orange;
}

div#menu {
    position: absolute;
    top: 30px;
    left: 0px;
    height: 20px;
    width: 100%;
    display: table;
    table-layout: fixed;
    border-spacing: 40px 0px;
    background: #ffa;
    font-size: 8px;
}

div#content {
    position: absolute;
    top: 70px;
    left: 50px;
    right: 50px;
}

div#footer {
    position: absolute;
    bottom: 20px;
    left: 50px;
    right: 50px;
    height: 30px;
    border-top:1px solid #ccc;
}

/* HEADER */
.maintitle {
    display: table-cell;
    vertical-align: middle;
    padding-left: 20px;
    color: #ffc;
    font-weight: bold;
    font-size: 16px;
}

.welcome {
    display: table-cell;
    vertical-align: middle;
    text-align: right;
    padding-right: 20px;
    color: #ffc;
    font-weight: normal;
    font-size: 12px;
}

.welcome a:link, .welcome a:visited {
    color: white;
}

/* MENU */
div#menu a:link, div#menu a:visited {
    color: #36c;
}

div#menu > li {
    display: table-cell;
    vertical-align: middle;
    border: 2px solid #bbb;
    border-radius: 25px;
    text-align: center;
    font-weight: bold;
}

/* pulldown menu */
div#menu li  ul {
    display:none;
    position:absolute;
    margin: 0;
    padding:10px 10px 5px 10px;
    list-style:none;
    border-right: 1px solid #ccc;
    border-left: 1px solid #ccc;
    border-bottom: 1px solid #ccc;
    background: white;
    z-index: 1;
}

div#menu li:hover ul {
    display:block;
}

/* LINK */
a:link, a:visited {
    color: #369;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

/* TABLE */
table {
    border-collapse: collapse;
}

td, th {
    line-height: 18px;
    border-bottom: 1px solid #eee;
    vertical-align: top;
    padding: 5px 15px;
    font-family: "Lucida Grande", Verdana, Arial, sans-serif;
}

 

- config/static/css/home.css

div#content_home {
    position: absolute;
    top: 80px;
    left: 110px;
    right: 110px;
}

div#homeimg {
    background: #ddd;
    padding: 5px 0 1px 0;
    text-align: center;
}

 

- config/static/img/Ulleungdo.jpg

 

4) 완성 작업 확인

- python manage.py runserver

- 브라우저에서 http://192.168.56.101:8000/

 

5. 기존 앱 첫 페이지에 맞춰 개선

- base.html template을 상속받아 처리하도록 모든 template를 수정함.

 

1) bookmark/bookmark_list.html

{% extends "base.html" %}

{% block title %}bookmark_list.html{% endblock %}

{% block content %}
<div id="content">
    <h1>Bookmark List</h1>

    <ul>
        {% for bookmark in object_list %}
            <li><a href="{% url 'bookmark:detail' bookmark.id %}">{{ bookmark }}</a></li>
        {% endfor %}
    </ul>
</div>
{% endblock %}

- 'detail' 대신 namespace가 포함된 'bookmark:detail'로 수정

 

2) bookmark/bookmark_detail.html

{% extends "base.html" %}

{% block title %}bookmark_detail.html{% endblock %}

{% block content %}
<div id="content">
    <h1>{{ object.title }}</h1>

    <ul>
        <li>URL: <a href="{{ object.url }}">{{ object.url }}</a></li>
    </ul>
</div>
{% endblock %}

 

3) blog/post_all.html

{% extends "base.html" %}

{% block title %}post_all.html{% endblock %}

{% block content %}
<div id="content">

<h1>Blog List</h1>

{% for post in posts %}
    <h2><a href='{{ post.get_absolute_url }}'>{{ post.title }}</a></h2>
    {{ post.modify_date|date:"N d, Y" }}
    <p>{{ post.description }}</p>
{% endfor %}

<br/>

<div>
    <span>
        {% if page_obj.has_previous %}
            <a href="?page={{ page_obj.previous_page_number }}">PreviousPage</a>
        {% endif %}
      
        Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
      
        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">NextPage</a>
        {% endif %}
    </span>

</div>

</div>
{% endblock %}

 

4) blog/post_detail.html

{% extends "base.html" %}

{% block title %}post_detail.html{% endblock %}

{% block content %}
<div id="content">

<h2>{{ object.title }}</h2>

<p>
    {% if object.get_previous %}
    <a href="{{ object.get_previous.get_absolute_url }}" title="View previous post"><i class="fas-arrow-circle-left">{{ object.get_previous }}</a>
    {% endif %}

    {% if object.get_next %}
    | <a href="{{ object.get_next.get_absolute_url }}" title="View next post"><i class="fas-arrow-circle-left">{{ object.get_next }}</a>
    {% endif %}
</p>

<p class="date">{{ object.modify_date|date:"j F Y" }}</p>
<br/>

<div class="body">
    {{ object.content|linebreaks }}
</div>

</div>
{% endblock %}

 

5) blog/post_archive.html

{% extends "base.html" %}

{% block title %}post_archive.html{% endblock %}

{% block content %}

<h1>Post Archives until {% now "N d, Y" %}</h1>

    {% for date in date_list %}
        <a href="{% url 'blog:post_year_archive' date|date:'Y' %}" class="btn btn-outline-primary btn-sm mx-1>Year-{{ date|date:"Y" }}</a>
    {% endfor %}

<br><br>

<div>
    <ul>
        {% for post in object_list %}
        <li>{{ post.modify_dt|date:"Y-m-d" }}&emsp;
        <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a></li>
        {% endfor %}
    </ul>
</div>

{% endblock %}

 

6) blog/post_archive_year.html

{% extends "base.html" %}

{% block title %}post_archive_year.html{% endblock %}

{% block content %}

<h1>Post Archives for {{ year|date:"Y" }}</h1>

    {% for date in date_list %}
        <a href="{% url 'blog:post_month_archive' year|date:'Y' date|date:'b' %}" class="btn btn-outline-primary btn-sm mx-1>{{ date|date:"F" }}</a>
    {% endfor %}
<br><br>

<div>
    <ul>
        {% for post in object_list %}
        <li class="h5">
             {{ post.modify_date|dt:"Y-m-d" }}&emsp;
            <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a>
        </li>
        {% endfor %}
    </ul>
</div>

{% endblock %}

 

7) blog/post_archive_month.html

{% extends "base.html" %}

{% block title %}post_archive_month.html{% endblock %}

{% block content %}

<h1>Post Archives for {{ month|date:"N, Y" }}</h1>
<br><br>

<div>
    <ul>
        {% for post in object_list %}
        <li class="h5">
              {{ post.modify_dt|date:"Y-m-d" }}&emsp;
              <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a>
        </li>
        {% endfor %}
    </ul>
</div>

{% endblock %}

 

8) blog/post_archive_day.html

{% extends "base.html" %}

{% block title %}post_archive_day.html{% endblock %}

{% block content %}

<h1>Post Archives for {{ day|date:"N d, Y" }}</h1>
<br><br>

<div>
    <ul>
        {% for post in object_list %}
        <li class="h5">
            {{ post.modify_dt|date:"Y-m-d" }}&emsp;
            <a href="{{ post.get_absolute_url }}"><strong>{{ post.title }}</strong></a>
        </li>
        {% endfor %}
    </ul>
</div>

{% endblock %}

 

9) 완성 작업 확인

- python manage.py runserver

- 브라우저에서 http://192.168.56.101:8000/

- 각 메뉴들을 클릭해서 확인

. Bookmark, Blog, util(Admin, Archive)

 

*** 본 내용은 "Django를 활용한 쉽고 빠른 웹 개발 파이썬 웹 프로그래밍(김석훈 지음. 한빛미디어)"을 요약한 것임