반응형

펠리칸(Pelican) 정적사이트 생성기

  파이썬하면 요즘 ML/DL, 데이터분석 등으로 엄청난 인기를 끌고 있는데요. 웹프로그래밍에서도 매우 많은 인기가 있습니다. 우리나라에서는 점유율이 많이 못미치지만, 해외에서 Django, FastAPI등은 매우 좋은 반응을 보이고 있습니다. 오늘 소개할 도구는 Django나 FastAPI와 같은 서버도구는 아닌, 파이썬으로 만들어진 정적사이트 생성기 펠리칸(Pelican)입니다.

 

1. 설치

우선 일반적인 파이썬 프로젝트 시작방법과 마찬가지로 가상환경을 만들어줍니다. (pycharm을 활용하면 쉽게 가상환경이 만들어집니다. VSCode로 진행하면 조금 까다로우니, 다른 포스팅을 참고하세요). 가상환경이 만들어지면 아래의 pip install ~ 로 pelican을 설치합니다. 

pip install pelican

 

2. 추가 라이브러리 설치

github page 배포를 위한 라이브러리 및 마크다운 문서 표시를 위한 추가 라이브러리를 설치합니다.

pip install ghp-import markdown

 

3. 프로젝트 생성

본격적인 프로젝트를 생성합니다. 첫번째 물음에서 [.]으로 되어있으면 현재 사이트에 생성한다는 의미입니다.

pelican-quickstart

생성이 완료되면 아래와 같이 프로젝트 구성이 보입니다.

 

4. 샘플 페이지 작성

content 폴더에 Hello.md파일을 작성합니다.

# hello.md #
Title: Are ‘you’ just inside your skin or is your smartphone part of you?
Date: 2018-02-26
Category: Psychology
Slug: are-you-just-inside-your-skin-or-is-your-smartphone-part-of-you

In November 2017, a gunman entered a church in Sutherland Springs in Texas, where he killed 26 people and wounded 20 others. He escaped in his car, with police and residents in hot pursuit, before losing control of [...]

 

5. 빌드

pelican content

 

6. 실행

특이하게도 실행은 output폴더로 이동해서 수행해야합니다. 이점은 다른 SSG(Static Site Generator)에 비해 조금 불편하네요. 그리고 일반적인 파이썬 프로젝트와 달리 main.py에서 실행하는게 아니고 pelican.server로 실행해야 합니다. 스크립트를 만들면 되긴 할텐데, 파이썬 공부한지가 오래되어 잘 기억이 안나네요. 

cd output
python -m pelican.server

 

<결과>

드디어 결과가 나왔습니다. 기본 구성 치고는 여러 요소들을 포함하고 있어 보이는데, 이뻐보이진 않습니다. 손대야 할 부분이 많아보이네요.

펠리칸(Pelican) 정적사이트 생성기

 

오늘은 간단히 파이썬 정적사이트 생성기인 펠리칸(pelican)에 대해 간단히 알아봤습니다. 파이썬을 좋아하시는 분들이라면 한번 시도해보셔도 좋을 것 같습니다.

반응형
반응형

Django앱을 Gitlab에 올리고, Heroku로 자동 배포하는 방법, 그리고 Freenorm의 무료 DNS를 적용하는 방법을 알아보겠습니다. 순서는 아래와 같습니다.

  1. django 앱 생성 (본문은 Django의 사용법을 다루는 글이 아니므로, 기본적인 앱은 만들어져 있어야 합니다.)
  2. Heroku repo 생성
  3. Gitlab repo 생성
  4. CD/CI설정 (Gitlab 추가설정 및 파일 추가 생성)
  5. 추가 설정 (오류 점검)
  6. 외부DNS(Freenom) 적용

1. Django 앱 생성

기본적인 앱이 구현되어있겠지만, 앱을 배포하기 위해서는 추가적인 라이브러리 설치가 필요합니다. 

pip install gunicorn whitenoise django-herok

# gunicorn: 위에서 설명한 파이썬 HTTP 서버(Python WSGI HTTP Server)입니다.
# whitenoise: 헤로쿠(Heroku)에서 정적 파일(Static files)을 사용하기 위해 필요한 모듈입니다.
# django-heroku: 헤로쿠(Heroku)에 배포하는 django 프로젝트의 각종 설정을 간단하게 해주는 모듈입니다.

 

settings.py에 아래의 내용을 추가해줍니다.

import django_heroku
import dj_database_url

# Add this line
PRODUCTION = os.environ.get('DATABASE_URL') != None

ALLOWED_HOSTS = ['wilky.ga','.herokuapp.com']

MIDDLEWARE = [
    ....중략....
    'whitenoise.middleware.WhiteNoiseMiddleware',
]


if PRODUCTION:
    DATABASES['default'] = dj_database_url.config()

django_heroku.settings(locals())

 

2. Heroku에 앱 생성

터미널에서 아래와 같이 입력하여 heroku에 로그인 후 새로운 앱을 생성합니다. (Heroku CLI는 설치되어 있어야 합니다.)

heroku login # Heroku 로그인
heroku create django-repo  # 앱 생성
heroku git:remote -a django-repo # 저장소를 등록해서 연결합니다.
git remote -v  # 원격 저장소 확인

이렇게 생성 및 연결이 되었다면, 이번엔 Heroku 사이트로 이동하여 몇가지 확인해놓아야 하는 부분을 체크합니다.

(1) App Name 확인: django-repo 라고 만들었습니다.

(2) App URL 확인 : django-repo.herokuapp.com이 됩니다. App의 settings하단에 Domains 섹션에 보시면 URL이 나와있습니다. 또는 Open App으로 직접 이동하여 URL을 확인할 수도 있습니다.

(3) API 키 확인 (내 계정 - Account Settings)

 

 

 

3. Gitlab에 Repository 생성 후 업로드

Gitlab에 Repository를 만들어줍니다. Repository 만드는 방법이나 Git 사용법은 다른 사이트를 참고하시기 바랍니다.

 

git을 이용해 소스코드를 저장해야 하는데, 저장하기전에 .gitignore파일을 아래의 사이트를 이용해 생성해 놓습니다. 

 

그런 다음 git을 시작하고, 코드를 Gitlab에 저장합니다.

# git 시작
git init  

# git 원격저장소 추가
git remote add origin https://gitlab.com/front42/django_blog.git

# 변경/추가/삭제
git add .

# git 커밋
git commit -m "initial commit"

# push
git push origin master
## 요즘은 main으로 되어있는데, 기본값을 설정에서 master로 바꿔서 진행합니다.

 

4. CI/CD를 위한 설정

(1) Gitlab - Heroku 연결

-Settings -> CI/CD에서 Variables를 Expand하여 변수를 추가합니다.

 

만들어야 되는 변수는 HEROKU_APPKEY, HEROKU_APP_HOST, HEROKU_APPNAME입니다. Heroku에서 App생성시 적어두었던 정보를 이용해 변수를 만듭니다. 단, Flags에 Protect variable을 해제해야 실행시 오류가 없습니다.이것으로 Gitlab과 Heroku가 연결이 되었습니다.

 

(2) 추가 파일 생성

라이브러리 설치 및 서비싱을 위한 설정파일들을 생성합니다.

<requirements.txt>

pip freeze > requirements.txt

 

<.gitlab-ci.yml>

stages:
  - test
  - deploy

# Uncomment these lines if you would use tdd using selenium in your project
# UnitTest:
#   image: python:3.6
#   stage: test
#   before_script:
#     - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
#     - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list
#     - pip install -r requirements.txt
#     - python manage.py makemigrations
#     - python manage.py migrate
#     - apt-get update -qq && apt-get install -y -qq unzip
#     - apt-get install -y google-chrome-stable
#     - apt-get install -y xvfb
#     - wget https://chromedriver.storage.googleapis.com/2.32/chromedriver_linux64.zip
#     - unzip chromedriver_linux64.zip
#     - python manage.py collectstatic --no-input
#     - python manage.py runserver 8000 &
#   when: on_success
#   script:
#     - coverage run manage.py test
#     - coverage report -m

Deployment:
  image: ruby:2.6
  stage: deploy
  before_script:
    - gem install dpl
    - wget -qO- https://cli-assets.heroku.com/install-ubuntu.sh | sh
  script:
    - dpl --provider=heroku --app=$HEROKU_APPNAME --api-key=$HEROKU_APIKEY
    - export HEROKU_API_KEY=$HEROKU_APIKEY
    - heroku run --app $HEROKU_APPNAME python manage.py migrate
  environment:
    name: production
    url: $HEROKU_APP_HOST

 

<Procfile>

migrate: bash python manage.py migrate
web: gunicorn my_app_name.wsgi  # my_app자리에 앱 이름을 넣는다.

추가파일을 생성 후 "git push origin master"를 수행하면, 해당 시점부터는 자동으로 Heroku로 배포가 되어, 사이트 확인이 가능해집니다.

 

5. Collectstatic 설정 변경

배포를 하는 과정을 보다보면 collectstatic 부분에서 에러가 나는 모습을 볼 수 있습니다.

이를 해결하기 위해서는 터미널에 아래와 같이 입력해줍니다.

heroku config:set DISABLE_COLLECTSTATIC=1

다른 방법도 여럿 보였지만, 위 방법이 제일 간단하고 정상작동됩니다.

참고로, Database(db.sqlite3)를 못읽어오는 경우도 있었는데, 혹시 다른 폴더 하위에 있다면(예: "db/db.sqlite3") 프로젝트폴더 내 바로 두고 실행하시기 바랍니다.

 

이렇게 하여 Heroku에 직접 소스코드를 push하여 올리지 않고 Gitlab만 관리하더라도 배포가 되는 것을 확인할 수 있습니다.

6. Freenom의 무료 DNS 적용하기

Heroku에서 제공하는 URL이 있긴 하지만, 좀더 우리가 원하는 URL을 적용하기 위해 DNS를 적용하는 방법을 알아보겠습니다. 우선 Freenom으로 가서 원하는 이름의 도메인을 만듭니다. (다른 사이트를 참조하시기 바랍니다.) 우선 만들기만 하고 추가적인 세팅은 잠시 뒤에 하겠습니다.

 

얼마전 Heroku 정책 변경으로 인해 DNS를 변경하기 위해서는 신용카드를 등록해야한다고 합니다. 돈이 나가는 것은 아니고 보안 목적이라고 하니 걱정은 안해도 될 것 같습니다. 우상단의 Account Setting > Billing 메뉴로 진입하여 개인의 신용카드를 등록합니다.

Settings > Domains 섹션에서 "Add domain"버튼을 클릭해줍니다.

그러면 우측에 아래와 같은 화면이 나타나는데, 위의 freenom에서 만들어준 주소를 적어주고, 하단의 Next버튼을 눌러줍니다. 그러면 DNS target이 나타납니다. 이 주소를 메모해서 Freenom에 적용시켜줘야 합니다.

Freenom에 로그인 후 Services > My Domains > Manage Domain > Manage Freenom DNS로 진입한 후,

 

Heroku에서 제공받은 DNS Target의 정보를 붙여넣습니다. 그리고 다른 정보는 아래와 같이 설정합니다.

Name: WWW

Type: CNAME

TTL: 144400

 

이렇게 하면 드디어 내 주소가 반영된 모습을 확인하실 수 있을겁니다.

 

※ 아직 데이터베이스(sqlite3)연결이 잘 안된는 것 같습니다. 블로그/로그인/사용자등록 기능이 제대로 나오지 않고 있는데, 좀 더 공부해봐야 겠습니다.

반응형
반응형

Oracle DB의 테이블을 이용해서 Join하는 방법을 알아보겠습니다.

 

1. 모델 정의

1:N관계에서 N쪽 테이블(TempDataroomHstry클래스)에 ForeignKey로 1쪽 테이블명(Temp래스)을 지정해줍니다. 이때, 1쪽 테이블의 참조하려는 필드가 Primary Key로 지정되어있으면 상관없지만, 없을 경우 필드 정의에(ForeignKey 함수 내부에서) to__field='목표필드' 를 지정해줘야 합니다. 그리고, db_column='참조칼럼명' 에서 해당 테이블에서 참조할 실제 칼럼(필드)명을 지정해줘야 합니다.

 

(*) Oracle DB에서만 이런 문제가 발생하는 것인지.. 아직은 잘 모르겠습니다. 이것 때문에 한참을 헤메었네요..^^;; 또한 이번의 경우 Temp의 emp_field와 TempDataroomHstry의 id는 사실 칼럼명은 동일하게 'emp_#'이었습니다. db_column에 넣어주는 값이 현재 테이블의 칼럼명인지, 목표 테이블의 칼럼명인지 좀 헷갈립니다...

<models.py>

from django.db import models


class Temp(models.Model):
    emp_field = models.CharField(db_column='emp_#', primary_key=True, max_length=7)  # Field renamed to remove unsuitable characters. Field renamed because it ended with '_'.
    emp_x = models.CharField(max_length=2, blank=True, null=True)
    kornm_n = models.CharField(max_length=32, blank=True, null=True)    
    res_1 = models.CharField(db_column='res_#1', max_length=12, blank=True, null=True)  # Field renamed to remove unsuitable characters.    
    sex_n = models.CharField(max_length=2, blank=True, null=True)    
    dept_c = models.CharField(max_length=16, blank=True, null=True)
    
    class Meta:
        managed = False
        db_table = 'TEMP'


class TempDataroomHstry(models.Model):
    seq_field = models.IntegerField(db_column='seq_#',primary_key=True)  # Field renamed to remove unsuitable characters. Field renamed because it ended with '_'.
    id = models.ForeignKey(Temp, to_field='emp_field', db_column='emp_#',on_delete=models.CASCADE, null=True, related_name='id')   #, related_name='tempdataroomhstry'
    in_d = models.DateField(blank=True, null=True)


    class Meta:
        managed = False
        db_table = 'TEMP_DATAROOM_HSTRY'

 

2. 데이터 활용하기

Join을 위해서는 "select_related()"나 "prefetch_related()"를 사용하는데, 이번에는 select_related()만 알아보도록 하겠습니다. select_related는 1:1 또는 1:N 의 경우에 사용할 수 있는 함수입니다. (정방향 참조필드). select_related()의 인자로는 해당 Table의 ForeinKey를 넣어줍니다. 아래의 예제에서는 우선 'id'칼럼을 이용하여 join후 모든 데이터를 불러오고 'in_d'를 기준으로 역정렬(desc)을 하여 list를 만들고, 이를 home.html에 넘겨줍니다.

<views.py>

from django.core.paginator import Paginator
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse

from .models import TempDataroomHstry

#### 초기화면 및 조회함수 ####
def index(request):
    list = {}
    list = TempDataroomHstry.objects.select_related('id').all().order_by('-in_d')

    # HTML에서 인자가 전달될 경우 처리
    q = request.GET.get('q', '')
    if q:
        list = list.filter(id=q)
    # 여기까지
   
    context = {'member_list': list}
    return render(request, 'home.html', context)

A테이블에 B가 조인될 경우 "[A].[조인된A칼럼명].[B칼럼명]"과 같이 사용하면 됩니다. 아래 home.html파일은 실제 Join된 데이터를 불러와 사용하는 예시를 볼 수 있습니다.

<home.html>

....
<tbody>
{% if member_list %}
  {% for member in member_list %}
    <tr>
      <td>{{ member.id.emp_field }}</td>
      <td>{{ member.id.kornm_n }}</td>
      <td>{{ member.id.res_1 }}</td>
      <td>{{ member.id.sex_n }}</td>
      <td>{{ member.id.dept_c }}</td>
      <td>{{ member.in_d }}</td>
    </tr>
  {% endfor %}
{% endif %}
</tbody>

 

~~~끝~~~

반응형
반응형
import pandas as pd
import requests
from bs4 import BeautifulSoup

key = '내 서비스키'
# url='http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?LAWD_CD=11110&DEAL_YMD=201512&serviceKey='+key
url='http://openapi.molit.go.kr:8081/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTrade?serviceKey='+key

rowList=[]  # 전체 행을 저장할 변수
nameList=[] # 열(칼럼) 이름을 저장할 변수
item_content=[] # 각 행별 칼럼값들을 저장할 임시공간

# 지역코드는 11000(서울)~39020(서귀포)
city_list=["26440","26410","26710","26290","26170","26260","26230","26320","26530","26380","26140","26500","26470","26200","26110","26350"]
for city in city_list:
    params ={'LAWD_CD' : city, 'DEAL_YMD' : '202110' } 
    response = requests.get(url, params=params).text #인코딩이 필요할 경우 .encode('utf-8')
    
    soup = BeautifulSoup(response, "lxml-xml")

    item_list = soup.find_all('item')   # 전체 contents를 담은 변수
    
    rowsLen = len(item_list)    # 전체 행 수
    for i in range(rowsLen):
        columns = item_list[i].find_all() # 1번째 행(row)의 모든 요소값들을 칼럼으로 한다.
        columnsLen = len(columns)    # 1번째 행(row)의 요소길이를 열(column) 길이로 한다.

        for j in range(0, columnsLen):
            if i == 0 and city=="26440":    # 첫번째 행 데이터 수집시 컬럼 값 저장
                nameList.append(columns[j].name)    # name 값만 추출한다

            eachColumn = columns[j].text    # 각 행(i)의 각 열(j)의 텍스트만 추출한다.
            item_content.append(eachColumn)   # 각 칼럼값을 append하여 1개 행을 만든다.
        rowList.append(item_content)    # 전체 리스트 공간에 개별 행을 append한다.
        item_content=[] # 다음 row의 값을 입력받기 위해 비워준다.

df = pd.DataFrame(rowList, columns=nameList)
df.head(50)

반응형
반응형

블로그 앱을 추가하도록 하겠습니다. 블로그 앱에서는 메인인 Home.html에서 포스트 클릭 시 포스트를 출력해주는 기능입니다.

1. blog app 추가

블로그 앱을 생성해줍니다.

django-admin startapp blog

 

2. mysite/settings.py 수정

블로그 앱을 등록해줍니다.

INSTALLED_APPS = [
    'blog.apps.BlogConfig',		# 추가
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

 

3. blog/models.py 구성

블로그 앱의 모델을 작성해 줍니다. 1:N의 관계에서 1은 N의 테이블(클래스)에서 ForeignKey로 지정해줍니다. 예를 들어 Category와 Post는 1:N의 관계입니다. 이 경우 Post 테이블에 Category 필드를 지정해주며, ForeignKey 속성을 부여합니다.

on_delete=models.SET_NULL은 해당 필드가 삭제될 경우 null로 세팅하라는 의미이며,

on_delete=model.CASCADE 해당 필드가 삭제될 경우 해당 값을 모두 삭제하라는 의미입니다.

from django.db import models

# Create your models here.
class Post(models.Model):
    category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
    tags = models.ManyToManyField('Tag', blank=True)
    title = models.CharField('TITLE', max_length=50)
    description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple one-line text.')
    image = models.ImageField('IMAGE', upload_to='blog/%Y/%m/', blank=True, null=True)
    content = models.TextField('CONTENT')
    create_dt = models.DateTimeField('CREATE DT', auto_now_add=True)
    update_dt = models.DateTimeField('UPDATE DT', auto_now=True)
    like = models.PositiveSmallIntegerField('LIKE', default=0)

    def __str__(self):
        return self.title

class Category(models.Model):
    name = models.CharField(max_length=50, unique=True)
    description = models.CharField('DESCRIPTION', max_length=100, blank=True, help_text='simple one-line text.')

    def __str__(self):
        return self.name

class Tag(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, blank=True, null=True)
    content = models.TextField('CONTENT')
    create_dt = models.DateTimeField('CREATE DT', auto_now_add=True)
    update_dt = models.DateTimeField('UPDATE DT', auto_now=True)

    @property  # property 문법: 메서드를 필드로 정의
    def short_content(self):
        return self.content[:10]

    def __str__(self):
        return self.short_content

 

4. blog/admin.py 구성

models.py에서 구성한 각 테이블(클래스)를 admin사이트에 등록합니다.

from django.contrib import admin

from blog.models import Post, Category, Tag, Comment


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ('id', 'category', 'tag_list', 'title', 'description', 'image', 'create_dt', 'update_dt', 'like')

    def tag_list(self, obj):
        return ','.join([t.name for t in obj.tags.all()])  # post:tag = 1:N이므로, 태그들을 전부 콤마로 이어붙임

    def get_queryset(self, request):
        return super().get_queryset(request).prefetch_related('tags')   # 관련된 태그 테이블도 같이 가져오도록

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('id', 'name', 'description')

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ('id', 'name')

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ('id', 'post', 'short_content', 'create_dt', 'update_dt')

 

5. Table Migration

model 변경사항을 반영합니다.

Python manage.py makemigrations

python manage.py migrate

 

6. mysite/urls.py 수정

blog url을 가져올 수 있도록 등록합니다. 또한 이미지 파일이 불려와질 수 있도록 urlpatterns 구문을 추가합니다.

from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include

from mysite.views import HomeView


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', HomeView.as_view(), name='home'),
    path('blog/', include('blog.urls')),
]

# 이미지 url 접근 시 이미지 파일이 접근 가능하도록 설정
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

 

7. blog/urls.py 작성

실제 blog의 세부 url을 작성합니다. url에 따라 PostDV클래스를 찾습니다.

from django.urls import path

from blog import views


app_name = 'blog'
urlpatterns = [
    # /blog/post/99/
    path('post/<int:pk>/', views.PostDV.as_view(), name='post_detail'),
]

 

8. blog/views.py 작성

PostDV를 정의합니다. 모델은 Post모델을 사용하며, 사용할 template을 정의합니다.

from django.views.generic import DetailView

from blog.models import Post


class PostDV(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'

 

9. templates/blog/post_detail.html 작성

보여질 post_detail.html파일을 작성합니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>post_detail.html</title>
</head>
<body>
This is post_detail.html...
</body>
</html>

아직은 해당 경로로 이동해도 보여지지 않습니다. 포스트를 생성해야합니다.

10. admin site로 이동하여 포스트 생성

포스트를 몇개 등록합니다.(아래 이미지에서는 첫번째 등록한 포스트가 지워졌습니다.)

 

11. url 입력

위에서 입력한 포스트의 id를 유념하여 url을 입력합니다. 아래와 같이 작성된 post_detail.html 이 나타나면 정상입니다.

 

12. home.html 수정

url을 매번 입력하여 이동할 수는 없으므로, home.html파일의 첫번째 이미지 클릭 시 포스트가 나타나도록 수정하겠습니다.

{% load static %}

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>Home.html</title>
        <!-- Favicon-->
        <link rel="icon" type="image/x-icon" href="{% static 'assets/favicon.ico' %}" />
        <!-- Font Awesome icons (free version)-->
        <script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" crossorigin="anonymous"></script>
        <!-- Google fonts-->
        <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700" rel="stylesheet" type="text/css" />
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="{% static 'css/styles.css' %}" rel="stylesheet" />
    </head>
    <body id="page-top">
        <!-- Navigation-->
        <nav class="navbar navbar-expand-lg navbar-dark fixed-top" id="mainNav">
            <div class="container">
                <div class="navbar-brand">Vue - Django Web Programming</div>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
                    Menu
                    <i class="fas fa-bars ms-1"></i>
                </button>
                <div class="collapse navbar-collapse" id="navbarResponsive">
                    <ul class="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#page-top">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#portfolio">Blog</a></li>
                        <li class="nav-item"><a class="nav-link" href="#team">Iam</a></li>
                        <li class="nav-item"><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Masthead-->
        <header class="masthead">
            <div class="container">
                <div class="masthead-subheading">Welcome To My Blog !</div>
                <div style="margin-bottom: 230px;">일상 생활과 관련사항을 기록하고 있습니다.</div>
            </div>
        </header>

        <!-- Blog Grid-->
        <section class="page-section bg-light" id="portfolio">
            <div class="container">
                <div class="text-center">
                    <h2 class="section-heading text-uppercase">Blot List</h2>
                    <h3 class="section-subheading text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
                </div>
                <div class="row">
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 1-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" href="{% url 'blog:post_detail' 1 %}"> <!--이부분 수정 -->
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/1.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Threads</div>
                                <div class="portfolio-caption-subheading text-muted">Illustration</div>
                            </div>
                        </div>
                    </div>
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 2-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" data-bs-toggle="modal" href="#portfolioModal2">
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/2.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Explore</div>
                                <div class="portfolio-caption-subheading text-muted">Graphic Design</div>
                            </div>
                        </div>
                    </div>
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 3-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" data-bs-toggle="modal" href="#portfolioModal3">
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/3.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Finish</div>
                                <div class="portfolio-caption-subheading text-muted">Identity</div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
        <!-- Iam-->
        <section class="page-section bg-light" id="team">
            <div class="container">
                <div class="text-center">
                    <h2 class="section-heading text-uppercase">홍  길  동</h2>
                    <h3 class="section-subheading text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
                </div>
                <div class="row">
                    <div class="col-lg-4">
                        <div class="team-member">
                            <img class="mx-auto rounded-circle" src="{% static 'assets/img/team/1.jpg' %}" alt="..." />
                            <h4>Creative Company</h4>
                            <p class="text-muted">Python Web Programmer</p>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-twitter"></i></a>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-facebook-f"></i></a>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-linkedin-in"></i></a>
                        </div>
                    </div>
                    <div class="col-lg-8">
                        내 소개 내용...
                    </div>
                </div>
            </div>
        </section>
        <!-- Footer-->
        <footer class="footer py-4">
            <div class="container">
                <div class="row align-items-center">
                    <div class="col-lg-4 text-lg-start">Copyright &copy; Your Website 2021</div>
                    <div class="col-lg-4 my-3 my-lg-0">
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-twitter"></i></a>
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-facebook-f"></i></a>
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <div class="col-lg-4 text-lg-end">
                        <a class="link-dark text-decoration-none me-3" href="#!">Privacy Policy</a>
                        <a class="link-dark text-decoration-none" href="#!">Terms of Use</a>
                    </div>
                </div>
            </div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
        <script src="{% static 'js/scripts.js' %}"></script>
        <!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
        <!-- * *                               SB Forms JS                               * *-->
        <!-- * * Activate your form at https://startbootstrap.com/solution/contact-forms * *-->
        <!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
        <script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
    </body>
</html>

 

반응형
반응형

 

Start Bootstrap에서 템플릿을 받아와서 메인 페이지를 구성하겠습니다.

 

1. Start Bootstrap 템플릿 다운로드

우선 Google에서 start bootstrap으로 검색하여 해당 사이트로 이동합니다.

다음으로 Themes 메뉴를 클릭한 다음, Free 옵션을 선택(Pro 해제) 하여 무료 옵션 중에서 적당한 테마를 고릅니다. 클릭하여 나타나는 상세 페이지에서 Download를 해 줍니다. 그리고 Template에서도 블로그용 template를 받아줍니다.

 

 

다운로드 받은 Theme 파일은 아래와 같이 static폴더와 templates폴더에 나누어 넣어줍니다. index.html 파일은 home.html로 이름을 바꾸어줍니다.

 

2. home.html 파일 수정

파일 링크를 참조하는 "src=" 부분과 "href=" 부분을 "src={% static ..... %}", "href={% static ..... %}"와 같이 변경해 줍니다. visual studio code의 바꾸기 기능 중 정규식을 이용하여

from: src="(.*?)" 

to:    src="{% static '$1' %}"

와 같이 하여 하나씩 확인해가면서 바꾸어줍니다. 그리고 만들려고 하는 목적에 맞게 컨텐츠를 수정해줍니다.

{% load static %}

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <meta name="description" content="" />
        <meta name="author" content="" />
        <title>Home.html</title>
        <!-- Favicon-->
        <link rel="icon" type="image/x-icon" href="{% static 'assets/favicon.ico' %}" />
        <!-- Font Awesome icons (free version)-->
        <script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" crossorigin="anonymous"></script>
        <!-- Google fonts-->
        <link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Roboto+Slab:400,100,300,700" rel="stylesheet" type="text/css" />
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="{% static 'css/styles.css' %}" rel="stylesheet" />
    </head>
    <body id="page-top">
        <!-- Navigation-->
        <nav class="navbar navbar-expand-lg navbar-dark fixed-top" id="mainNav">
            <div class="container">
                <div class="navbar-brand">Vue - Django Web Programming</div>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
                    Menu
                    <i class="fas fa-bars ms-1"></i>
                </button>
                <div class="collapse navbar-collapse" id="navbarResponsive">
                    <ul class="navbar-nav text-uppercase ms-auto py-4 py-lg-0">
                        <li class="nav-item"><a class="nav-link" href="#page-top">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="#portfolio">Blog</a></li>
                        <li class="nav-item"><a class="nav-link" href="#team">Iam</a></li>
                        <li class="nav-item"><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
                    </ul>
                </div>
            </div>
        </nav>
        <!-- Masthead-->
        <header class="masthead">
            <div class="container">
                <div class="masthead-subheading">Welcome To My Blog !</div>
                <div style="margin-bottom: 230px;">일상 생활과 관련사항을 기록하고 있습니다.</div>
            </div>
        </header>

        <!-- Blog Grid-->
        <section class="page-section bg-light" id="portfolio">
            <div class="container">
                <div class="text-center">
                    <h2 class="section-heading text-uppercase">Blot List</h2>
                    <h3 class="section-subheading text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
                </div>
                <div class="row">
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 1-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" data-bs-toggle="modal" href="#portfolioModal1">
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/1.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Threads</div>
                                <div class="portfolio-caption-subheading text-muted">Illustration</div>
                            </div>
                        </div>
                    </div>
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 2-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" data-bs-toggle="modal" href="#portfolioModal2">
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/2.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Explore</div>
                                <div class="portfolio-caption-subheading text-muted">Graphic Design</div>
                            </div>
                        </div>
                    </div>
                    <div class="col-lg-4 col-sm-6 mb-4">
                        <!-- Portfolio item 3-->
                        <div class="portfolio-item">
                            <a class="portfolio-link" data-bs-toggle="modal" href="#portfolioModal3">
                                <div class="portfolio-hover">
                                    <div class="portfolio-hover-content"><i class="fas fa-plus fa-3x"></i></div>
                                </div>
                                <img class="img-fluid" src="{% static 'assets/img/portfolio/3.jpg' %}" alt="..." />
                            </a>
                            <div class="portfolio-caption">
                                <div class="portfolio-caption-heading">Finish</div>
                                <div class="portfolio-caption-subheading text-muted">Identity</div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </section>
        <!-- Iam-->
        <section class="page-section bg-light" id="team">
            <div class="container">
                <div class="text-center">
                    <h2 class="section-heading text-uppercase">홍  길  동</h2>
                    <h3 class="section-subheading text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
                </div>
                <div class="row">
                    <div class="col-lg-4">
                        <div class="team-member">
                            <img class="mx-auto rounded-circle" src="{% static 'assets/img/team/1.jpg' %}" alt="..." />
                            <h4>Creative Company</h4>
                            <p class="text-muted">Python Web Programmer</p>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-twitter"></i></a>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-facebook-f"></i></a>
                            <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-linkedin-in"></i></a>
                        </div>
                    </div>
                    <div class="col-lg-8">
                        내 소개 내용...
                    </div>
                </div>
            </div>
        </section>
        <!-- Footer-->
        <footer class="footer py-4">
            <div class="container">
                <div class="row align-items-center">
                    <div class="col-lg-4 text-lg-start">Copyright &copy; Your Website 2021</div>
                    <div class="col-lg-4 my-3 my-lg-0">
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-twitter"></i></a>
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-facebook-f"></i></a>
                        <a class="btn btn-dark btn-social mx-2" href="#!"><i class="fab fa-linkedin-in"></i></a>
                    </div>
                    <div class="col-lg-4 text-lg-end">
                        <a class="link-dark text-decoration-none me-3" href="#!">Privacy Policy</a>
                        <a class="link-dark text-decoration-none" href="#!">Terms of Use</a>
                    </div>
                </div>
            </div>
        </footer>
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
        <script src="{% static 'js/scripts.js' %}"></script>
        <!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
        <!-- * *                               SB Forms JS                               * *-->
        <!-- * * Activate your form at https://startbootstrap.com/solution/contact-forms * *-->
        <!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *-->
        <script src="https://cdn.startbootstrap.com/sb-forms-latest.js"></script>
    </body>
</html>

 

반응형
반응형

1. 프로젝트 생성 ( 가상환경 생성)

 

2. 라이브러리 설치

pip install django

 

3. Django 프로젝트 설치

현재 폴더에 ( . ) mysite라는 프로젝트를 생성해줍니다. 이 경우 manage.py파일이 현재 폴더에 생성됩니다. (현재 폴더를 지정하지 않으면 한단계 더 아래 폴더에 프로젝트 및 manage.py 파일을 생성합니다. )

django-admin startproject mysite .

 

4. setting.py 수정

DB / Template / Media / Static 관련된 폴더들을 만들고, 이들 경로를 BASE_DIR을 기준으로 지정해줍니다. 그리고 TIME_ZONE, USE_TZ 값을 수정해줍니다.

"""
Django settings for mysite project.

Generated by 'django-admin startproject' using Django 3.2.8.

For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-1tq5c@vtc5f@-^2%sxpu5(42=3^n24fow@i1m*%+h(5#ghx9ml'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

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

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # 수정
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'mysite.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db' / 'db.sqlite3',  # 수정
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'Asia/Seoul'  # 수정

USE_I18N = True

USE_L10N = True

USE_TZ = False  # 수정


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

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

###  추가  ###
STATICFILES_DIRS = (BASE_DIR / 'static',)
# STATIC_ROOT =  # 배포시 사용

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

# AUTH_USER_MODEL =

# LOGGING =

 

5. DB생성

python manage.py migrate

 

6. 관리자계정 생성

python manage.py createsuperuser

 

7. mysite/urls.py 수정

"""adv URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

from adv.views import HomeView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', HomeView.as_view(), name='home')
]

8. mysite/views.py 생성

from django.views.generic import TemplateView


class HomeView(TemplateView):
    template_name = 'home.html'

9. templates/home.html 생성

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home.html</title>
</head>
<body>
hello
</body>
</html>

10. 결과 확인

반응형
반응형

Database에서 간단히 CRUD만 구성할 일이 있었는데, Backend 와 Frontend를 분리해서 구축하려니 조금 번거로워서 찾게되었습니다. Python 프레임워크들은 Jinja2 템플릿을 사용하여 Frontend도 쉽고 빠르게 구축할 수 있도록 지원해주기 때문에, 이런 용도로는 잘 맞는 것 같습니다. FastAPI도 일반적으로는 API 형태의 backend를 구성하겠지만, Jinja2를 이용하여 Frontend 연결까지 해보도록 하겠습니다.

1. 폴더구조

아래와 같이 폴더구조를 만들겠습니다. 이전에 만들었던 main.py를 업데이트하기 위해 mainlist.py 파일을 새로 작성하였습니다. 실행도 mainlist.py로 실행하겠습니다.

2. 라이브러리 설치

<기존 설치된 라이브러리>

pip install fastapi, uvicorn
pip install mysql, sqlalchemy
pip install starlette

<추가 라이브러리>

pip install jinja2 	#jinja2 실행
pip install python-multipart	#Form 기능을 위한 라이브러리

3. 코드

<mainlist.py>

route 처리부분을 별도의 파일로 분리하여 작성하고, 해당 router를 include_router로 연결합니다.

from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from routes import index

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(index.router)

 

<db/db.py>

가장 많이 예제로 주어지는 mysql database를 연결하도록 하겠습니다. sqlalchemy를 사용하여 연결하게 되는데, DATABASE 정보를 이용하여 ENGINE을 생성하고, ENGINE을 이용하여 session을 생성합니다.

Session이란 트랜잭션이 커밋되기 전 동작들이 거쳐가는 일종의 버퍼 같은 역할을 합니다. 커밋이 안된 데이터가 다른 트랜잭션으로 흘러들어가거나, 같은 테이블의 인스턴스를 두번 불러왔을 때 다른 객체로 인식되는 등의 문제가 없도록 합니다.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

user_name = "root"
user_pwd = ""
db_host = "127.0.0.1"
db_name = "project01"

DATABASE = 'mysql://%s:%s@%s/%s?charset=utf8' % (
    user_name,
    user_pwd,
    db_host,
    db_name,
)

ENGINE = create_engine(
    DATABASE,
    encoding="utf-8",
    echo=True
)

session = scoped_session(
    sessionmaker(
        autocommit = False,
        autoflush = False,
        bind = ENGINE
    )
)

Base = declarative_base()
Base.query = session.query_property()

 

<model/model.py>

Database 테이블의 Mapping Class를 생성합니다.

from sqlalchemy import Column, Integer, String
# from pydantic import BaseModel
from db.db import Base

class UserTable(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(50), nullable=False)
    age = Column(Integer)

 

<routes/index.py>

Jinja2Templates를 불러오고 html파일이 위치할 디렉토리를 지정해주면 해당 파일에서 Jinja2문법을 사용할 수 있습니다. Jinja2에서는 Post와 Get 메서드만 지원되며, put, delete 등의 다른 메서드는 지원되지 않습니다. Form 요소의 사용법에 대해서는 여기 문서를 참조하세요

from fastapi import APIRouter, Request, Form
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from starlette.responses import RedirectResponse

from db.db import session
from model.model import UserTable, User

router = APIRouter()
templates = Jinja2Templates(directory="templates")


# 전체 리스트
@router.get("/users", response_class=HTMLResponse)
async def read_users(request: Request):
    context={}
    users = session.query(UserTable).all()

    context["request"] = request
    context["users"] = users

    return templates.TemplateResponse("user_list.html", context)



# 특정 인원 보기
@router.get("/users/{user_id}", response_class=HTMLResponse)
async def read_user(request: Request, user_id: int):
    context={}
    user = session.query(UserTable).filter(UserTable.id == user_id).first()

    context["request"] = request
    context["name"] = user.name
    context["age"] = user.age

    return templates.TemplateResponse("user_list.html", context)

# 인원 생성
@router.post("/user")
async def create_user(
        name: str = Form(...),
        age:int = Form(...)):

    print(name, age)
    user = UserTable()
    user.name = name
    user.age = age

    session.add(user)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

# 인원 수정
@router.post("/users", response_class=HTMLResponse)
async def update_user(
        user_id: int = Form(...),
        n_name: str = Form(...),
        n_age: int = Form(...)):

    user = session.query(UserTable).filter(UserTable.id == user_id).first()
    user.name = n_name
    user.age = n_age
    print(n_name, n_age)
    session.commit()

    return RedirectResponse(url="/users", status_code=302)


# 인원 삭제
@router.post("/delete/{user_id}", response_class=HTMLResponse)
async def delete_users(request: Request, user_id: int):

    print('delete', user_id)
    session.query(UserTable).filter(UserTable.id == user_id).delete()
    session.commit()

    return RedirectResponse(url="/users", status_code=302)

 

<templates/user_list.html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MySQL연동</title>
</head>
<body>
    <div>
        <a href="/">HOME</a>
    </div>
    <form action="/user" method="post">
        <input type="text" id="username_" name="name" value="사용자" />
        <input type="text" id="userage_" name="age" value="1" />
        <input type="submit">등록</input>
    </form>
    <div id="usertable">
        <table width="100%" border="1" cellpadding="0" cellspacing="0">
            {% if users %}
                {% for user in users %}
                    <tr>
                        <form action="/users" method="post">
                            <td align="center"><input type="text"  name="user_id" value={{user.id}}></td>
                            <td align="center"><input type="text" id="username_{{user.id}}" name="n_name" value="{{user.name}}" /></td>
                            <td align="center"><input type="text" id="userage_{{user.id}}" name="n_age" value="{{user.age}}" /></td>
                            <td align="center"><input type="submit" value="수정"/></td>
                        </form>
                        <form action="/delete/{{user.id}}" method="post">
                            <td align="center">
                                <input type="submit" value="삭제"/>
                            </td>
                        </form>
                    </tr>
                {% endfor %}
            {% else %}
                <tr>
                    <td align="center" colspan="6">No user...</td>
                </tr>
            {% endif %}
        </table>
    </div>
</body>
</html>

 

<실행>

uvicorn mainlist:app --reload

<결과>

 

이상, FastAPI를 사용하여, 간단하게 Database와 연계, CRUD 실행, Frontend까지 작성해보았습니다.

 

~~끝~~

반응형

+ Recent posts