Code Monkey home page Code Monkey logo

g6's Introduction

GNUBOARD6 is Python CMS with fastapi

PyPI - Python Version Static Badge

데모 사이트

커뮤니티

그누보드6 커뮤니티

그누보드 유튜브 채널

시작하기

1. 설치

  • Git을 사용한 설치를 권장합니다.
  • 루트 디렉토리에 .env 파일이 없다면 설치를 자동으로 진행합니다.

설치 방법

# Github에서 그누보드6 복사 및 설치합니다.
git clone https://github.com/gnuboard/g6.git
# cd 명령어를 이용하여 g6 디렉토리로 이동합니다.
cd g6
# 가상환경을 만듭니다. 필수 설치 요소는 아닙니다.
python -m venv venv
# 또는
python3 -m venv venv

# Linux
source venv/bin/activate

# Windows
source venv\Scripts\activate
# 또는
source venv/Scripts/activate
# 실행에 필요한 파이썬 패키지들을 설치합니다.
pip install -r requirements.txt
# 또는
pip3 install -r requirements.txt
# uvicorn을 이용하여 그누보드6을 실행합니다.
# 기본으로 8000번 포트를 사용합니다.

# Linux
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Windows
uvicorn main:app --reload

그누보드6 데이터베이스 설정 방법

  1. 웹브라우저를 열고 http://127.0.0.1:8000 로 접속합니다.

  2. .env 파일이 없습니다. 설치를 진행해 주세요. 라는 경고창과 함께 설치 페이지로 이동합니다.

  3. 설치 메인페이지에서 설치할 그누보드버전, 파이썬버전, FastAPI버전 및 안내 사항을 확인할 수 있습니다.

  4. 그누보드6 라이센스를 확인하고 동의합니다.

  5. 데이터베이스 설정을 진행합니다.

    • MySQL, PostgreSQL, SQLite 중 하나의 데이터베이스를 선택하여 설정할 수 있습니다.
      • MySQL, PostgreSQL : 연결에 필요한 정보들을 입력합니다.
      • SQLite : 연결정보가 필요 없으며, 설치 시 루트 디렉토리에 sqlite3.db 데이터베이스 파일이 생성됩니다.
    • 접두사를 입력합니다.
      • {영문+숫자}_ 형식으로 입력해야 합니다.
      • 기본값은 g6_ 입니다(예: gnuboard6_)
    • 재설치 여부를 체크합니다. (선택)

      Warning
      재설치는 테이블을 삭제 후 재생성합니다. 기존 데이터가 사라질 수 있으니 주의하시기 바랍니다.

  6. 관리자 정보를 입력합니다. 입력한 정보를 바탕으로 관리자 계정이 생성됩니다.

  7. 설치를 진행합니다. 설치가 완료되면 설치완료 문구가 출력됩니다.

  8. 이제부터 자유롭게 그누보드6를 사용할 수 있습니다.

2. 디렉토리 구조

admin

관리자 관련 파일들이 포함되어 있습니다.
router, template 파일 및 관리자 메뉴를 설정하는 .json 파일이 속해 있습니다.

bbs

사용자 관련 router가 위치합니다. 요청에 따라 여러가지 기능들을 수행합니다.

core

프로젝트의 핵심 코드가 위치합니다. 데이터베이스 연결, 미들웨어 실행, 템플릿 엔진 설정 등 기본적인 실행에 필요한 코드를 포함하고 있습니다.

core
├─ database.py  # 데이터베이스 연결 및 세션 관리
├─ exception.py  # 예외 처리설정 클래스 & 함수
├─ formclass.py  # @dataclass를 이용한 폼 클래스 모음
├─ middleware.py  # 미들웨어 설정
├─ models.py  # 데이터베이스 모델
├─ plugin.py  # 플러그인 관련 함수
└─ template.py  # 템플릿 엔진 설정

data

data 디렉토리는 이미지 및 파일을 저장하기 위한 디렉토리 입니다.
초기에는 존재하지 않으며, 설치 진행 시 자동으로 생성됩니다.

install

설치 관련 파일들이 포함되어 있습니다.

lib

프로젝트에서 사용되는 여러 함수들을 포함한 디렉토리, 파일들이 속해 있습니다.

lib
├─ captcha  # 캡차 관련 함수, 템플릿 (Google reCAPTCHA v2, v2 invisible)
├─ editor  # 에디터 관련 함수, 템플릿 (ckeditor4)
├─ social  # 소셜 로그인 관련 함수 (naver, kakao, google, facebook, twitter)
├─ board_lib.py  # 게시판 관련 함수
├─ common.py  # 공통 함수
├─ dependencies.py  # 종속성 함수
├─ member_lib.py  # 회원 관련 함수
├─ pbkdf2.py  # 암호화 라이브러리
├─ point.py  # 포인트 함수
├─ template_filters.py  # 템플릿 필터 함수
├─ template_functions.py  # 템플릿 출력 관련 함수 모음
└─ token.py  # 토큰 함수

plugin

사용자가 만든 독립된 기능을 저장하는 디렉토리 입니다.

  1. 플러그인 제작
    • /plugin 폴더에 제작할 플러그인 디렉토리를 추가합니다.
    • 다른 플러그인과 겹치지 않도록 고유한 이름으로 지어주세요.
    • 아래 파일 구성과 plugin/demo_plugin디렉토리를 참고하여 플러그인을 제작합니다.
plugin
├─ {플러그인1}
   └─ admin  # 관리자 라우터&메뉴 설정
      ├─ __init__.py 
      └─ admin_router.py
   ├─ static  # css, js, image 등 정적 파일
   ├─ templates  # 템플릿 파일
   ├─ user  # 사용자 라우터 설정
   ├─ __init__.py  # 플러그인 초기화 파일
   ├─ models.py  # 데이터베이스 모델
   ├─ plugin_config.py  # 플러그인 설정 파일
   ├─ readme.txt  # 플러그인 상세정보
   ├─ screenshot.png 또는 webp  # 대표 이미지
├─ {플러그인2}
...
└─ plugin_states.json  # 전체 플러그인 정보 파일
  1. 관리자 메뉴 주소등록
    • plugin_config.py > admin_menu 딕셔너리에 추가할 url, name을 등록합니다.
      • 등록한 메뉴는 admin > __init__.py > register_admin_menu() 함수를 통해 관리자메뉴에 등록됩니다.

static

css, js, image 등 정적 파일을 저장하는 디렉토리 입니다.

templates

템플릿 파일을 저장하는 디렉토리 입니다.
여러개의 템플릿으로 구성할 수 있으며 관리자페이지 > 템플릿관리 메뉴에서 템플릿을 변경할 수 있습니다.

templates
├─ {템플릿1}
   └─ ...
├─ {템플릿2}
   └─ ...
...
  • 반응형/적응형
    • .env파일의 IS_RESPONSIVE 설정 값에 따라 반응형/적응형 웹사이트로 표시됩니다. 적응형일 경우에만 templates/{템플릿}/moblile 디렉토리를 생성하여 모바일 화면을 따로 구성할 수 있습니다.
    • 모바일 화면이 없을경우 자동으로 반응형(PC) 웹사이트로 표시됩니다.

.env(.example.env)

사용자 설정에 필요한 파일입니다. 설치 진행 시, .example.env을 복사해서 .env파일을 자동으로 생성합니다.

  • .example.env.env파일을 생성하기 위한 예제 파일이므로 삭제하지 않는 것을 추천합니다.

입니다. 설치 진행 시,

main.py

프로젝트의 시작점입니다. uvicorn을 이용하여 서버를 실행합니다.

# Linux
uvicorn main:app --reload --host 0.0.0.0 --port 8000

# Windows
uvicorn main:app --reload

requirements.txt

프로젝트에 필요한 라이브러리를 기록한 파일입니다. 명령어를 통해 자동으로 설치할 수 있습니다.

pip install -r requirements.txt

3. 설정

설치 후 생성된 .env파일을 수정하여 사용자 설정을 변경할 수 있습니다.

  • True/False 는 반드시 문자열로 입력해야 합니다.
  • 전체 설정은 .env.example 파일을 참고하세요.

Note
설정을 변경하면 서버를 재시작해야 정상 적용됩니다.

데이터베이스 설정

# sqlite는 접속정보관련 설정값은 무시됩니다.
# (DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)

# 테이블 이름 접두사 설정
DB_TABLE_PREFIX = "g6_"
# mysql, postgresql, sqlite
DB_ENGINE = ""
DB_USER = "username"
DB_PASSWORD = ""
DB_HOST = ""
DB_PORT = ""
DB_NAME = ""
DB_CHARSET = "utf8"

이메일 발송 설정

SMTP_SERVER="localhost"
SMTP_PORT=25
# 메일 테스트시 보내는 사용자 이름 및 이메일 주소 반드시 넣어야 합니다.
SMTP_USERNAME="[email protected]"
SMTP_PASSWORD=""

# 예) 네이버 메일 설정 
# SMTP_SERVER="smtp.naver.com"
# SMTP_PORT=465 # 보안 연결(SSL) 필요
# SMTP_USERNAME="네이버 로그인 아이디"
# SMTP_PASSWORD="네이버 로그인 비밀번호"

관리자 테마 설정

# 관리자 테마 설정
# 관리자 테마는 /admin/templates/{테마} 에 위치해야 합니다.
# 테마 이름을 입력하지 않으면 기본 테마(basic)가 적용됩니다.
ADMIN_THEME = "basic"

이미지 설정

# 이미지 크기변환 여부
UPLOAD_IMAGE_RESIZE = "False"
# MB 이미지 업로드 용량 (기본값 20MB)
UPLOAD_IMAGE_SIZE_LIMIT = 20
# (0~100) default 80 이미지 업로드 퀄리티(jpg)
UPLOAD_IMAGE_QUALITY = 80

# UPLOAD_IMAGE_RESIZE 가 True 이고 설정된값보다 크면 크기를 변환합니다.
# px 이미지 업로드 크기변환 가로 크기
UPLOAD_IMAGE_RESIZE_WIDTH = 1200
# px 이미지 업로드 크기변환 세로 크기
UPLOAD_IMAGE_RESIZE_HEIGHT = 2800

기타 설정들

# 디버그 모드 설정 (True/False)
APP_IS_DEBUG = "False"

# 웹사이트 표시 방법 (True/False)
# "True" (기본값) : 반응형 웹사이트 (참고: 반응형 템플릿만 제공합니다.)
# "False" : 적응형 웹사이트
IS_RESPONSIVE = "True"

# www.gnuboard.com 과 gnuboard.com 도메인은 서로 다른 도메인으로 인식합니다. 
# 쿠키를 공유하려면 .gnuboard.com 과 같이 입력하세요.
# 이곳에 입력하지 않으면 www 붙은 도메인과 그렇지 않은 도메인은 쿠키를 공유하지 못하므로 
# 로그인이 풀릴 수 있습니다.
COOKIE_DOMAIN = ""

g6's People

Contributors

dependabot[bot] avatar dungdang39 avatar ellee12 avatar eltociear avatar jisungbin avatar junanjunan avatar kagla avatar kimtom89 avatar kisa002 avatar kitrio avatar letm3through avatar letnaturebe avatar minari2 avatar mirusu400 avatar seiblog-nandsoft avatar whitedot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

g6's Issues

templates/basic/moible/board 디렉토리 없을때 오류

관리자 > 게시판 관리

File "admin/templates/board_list.html", line 1, in top-level template code
    {% extends "base.html" %}
  File "admin/templates/base.html", line 175, in top-level template code
    {% block content %}{% endblock %}
  File "admin/templates/board_list.html", line 71, in block 'content'
    {{ get_skin_select('board', 'bo_mobile_skin[]', board.bo_mobile_skin, event='required', device='mobile')|safe }}
  File "/home/ubuntu/gnu6/lib/common.py", line 182, in get_skin_select
    for skin in os.listdir(skin_path):
FileNotFoundError: [Errno 2] No such file or directory: 'templates/basic/mobile/board'

그누보드6 코드 <-> 그누보드5 DB 호환: 게시판

1. 자유게시판 -> 글 작성 -> 댓글 달기시 에러 발생

  • DataError: (pymysql.err.DataError) (1406, "Data too long for column 'wr_last' at row 1")
  • g6_write_free의 wr_last 컬럼은 VARCHAR(30)
    g5_write_free의 wr_last 컬럼은 VARCHAR(19)
  • 그누보드 6는 wr_last 컬럼에 2023-12-20 17:48:32.607198와 같은 형식으로 값이 들어가는데
    그누보드 5의 wr_last 컬럼은 19자로 설정해두어서 값이 입력되지 못해 오류 발생
  • 관련 테이블: write_free, write_gallery, write_notice, write_qa

2. write_free 테이블 wr_datetime 컬럼

  • write_free 테이블에서 wr_last 컬럼 varchar 글자수 변경하는 과정에서 다음과 같은 에러 발생
    Invalid default value for 'wr_datetime'
    그누보드 5 DB는 wr_datime의 default 값이 0000-00-00 00:00:00으로 되어 있음
  • MySQL의 NO_ZERO_DATE 모드로 실제로 존재하지 않는 날짜, 시간 형식 저장이 안되도록 default 지정되어 있어서 오류 발생
  • 관련 테이블: write_free, write_gallery, write_notice, write_qa 외 datetime 타입 컬럼을 사용하는 다수의 테이블

그누보드6 코드 <-> 그누보드5 DB 호환: 쪽지

쪽지 보내기 기능

  • 그누보드 6에서는 g6_memo테이블의 me_read_datetime column이 Null 값이 허용되어 있음
me_read_datetime = Column(DateTime, nullable=True)
  • 그누보드 5 DB는 해당 컬럼이 Not Null, Default는 0000-00-00 00:00:00으로 되어 있음
    -> 그누보드 5 DB에서는 쪽지를 보낼 시에 me_read_datetime이 입력되지 않아서 아래의 오류 발생
sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1048, "Column 'me_read_datetime' cannot be null")

게시판 글 읽기시 오류

master 를 새로 pull 한 후 새로 설치하여 테스트 해보세요.

Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
    return await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/applications.py", line 292, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/core/middleware.py", line 68, in core_middleware
    return await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/core/middleware.py", line 101, in dispatch
    return await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/sessions.py", line 86, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 211, in main_middleware
    response: Response = await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 273, in app
    raw_response = await run_endpoint_function(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 190, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/ubuntu/gnu6/bbs/board.py", line 867, in read_post
    insert_point(request, mb_id, read_point, f"{board.bo_subject} {write.wr_id} 글읽기", board.bo_table, write.wr_id, "읽기")
  File "/home/ubuntu/gnu6/lib/common.py", line 880, in insert_point
    db.commit()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1969, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 139, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1256, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 139, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1231, in _prepare_impl
    self.session.flush()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4312, in flush
    self._flush(objects)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4447, in _flush
    with util.safe_reraise():
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4408, in _flush
    flush_context.execute()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 93, in save_obj
    _emit_insert_statements(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 1226, in _emit_insert_statements
    result = connection.execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1820, in _execute_context
    self._handle_dbapi_exception(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1814, in _execute_context
    context = constructor(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1455, in _init_compiled
    l_param: List[Any] = [
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1456, in <listcomp>
    flattened_processors[key](compiled_params[key])
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/dialects/sqlite/base.py", line 1142, in process
    raise TypeError(
sqlalchemy.exc.StatementError: (builtins.TypeError) SQLite Date type only accepts Python date objects as input.
[SQL: INSERT INTO g6_point (mb_id, po_datetime, po_content, po_point, po_use_point, po_expired, po_expire_date, po_mb_point, po_rel_table, po_rel_id, po_rel_action) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]
[parameters: [{'po_mb_point': 219, 'po_content': '질문답변 1 글읽기', 'po_rel_id': '1', 'po_rel_table': 'qa', 'po_datetime': datetime.datetime(2024, 1, 3, 15, 44, 48, 225496), 'po_expire_date': '2024-01-03', 'po_use_point': 0, 'po_rel_action': '읽기', 'mb_id': 'admin', 'po_point': -1, 'po_expired': 1}]]

게시판 글쓰기 시 오류

Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
    return await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/applications.py", line 292, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/sessions.py", line 86, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 250, in main_middleware
    response: Response = await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 109, in dispatch
    return await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 273, in app
    raw_response = await run_endpoint_function(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 190, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/ubuntu/gnu6/bbs/board.py", line 864, in read_post
    insert_point(request, mb_id, read_point, f"{board.bo_subject} {write.wr_id} 글읽기", board.bo_table, write.wr_id, "읽기")
  File "/home/ubuntu/gnu6/lib/common.py", line 927, in insert_point
    db.commit()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1969, in commit
    trans.commit(_to_root=True)
  File "<string>", line 2, in commit
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 139, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1256, in commit
    self._prepare_impl()
  File "<string>", line 2, in _prepare_impl
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/state_changes.py", line 139, in _go
    ret_value = fn(self, *arg, **kw)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 1231, in _prepare_impl
    self.session.flush()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4312, in flush
    self._flush(objects)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4447, in _flush
    with util.safe_reraise():
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/util/langhelpers.py", line 146, in __exit__
    raise exc_value.with_traceback(exc_tb)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 4408, in _flush
    flush_context.execute()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 466, in execute
    rec.execute(self)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/unitofwork.py", line 642, in execute
    util.preloaded.orm_persistence.save_obj(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 93, in save_obj
    _emit_insert_statements(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/persistence.py", line 1226, in _emit_insert_statements
    result = connection.execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1820, in _execute_context
    self._handle_dbapi_exception(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1814, in _execute_context
    context = constructor(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1455, in _init_compiled
    l_param: List[Any] = [
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 1456, in <listcomp>
    flattened_processors[key](compiled_params[key])
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/dialects/sqlite/base.py", line 1142, in process
    raise TypeError(
sqlalchemy.exc.StatementError: (builtins.TypeError) SQLite Date type only accepts Python date objects as input.
[SQL: INSERT INTO g6_point (mb_id, po_datetime, po_content, po_point, po_use_point, po_expired, po_expire_date, po_mb_point, po_rel_table, po_rel_id, po_rel_action) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]
[parameters: [{'po_rel_action': '읽기', 'po_rel_table': 'free', 'po_content': '자유게시판 2 글읽기', 'po_point': -1, 'po_datetime': datetime.datetime(2024, 1, 3, 10, 18, 23, 707228), 'po_expired': 1, 'po_use_point': 0, 'mb_id': 'admin', 'po_rel_id': '2', 'po_mb_point': 209, 'po_expire_date': '2024-01-03'}]]

데이터베이스 관련 개선안

개요

개선안

1. ORM 연관관계

  • 현재는 ORM 연관관계를 사용하지 않고 있음.
  • ORM 연관관계를 사용하면 코드량 & Query 요청감소/속도증가 효과를 보일 것으로 예상됨
  • 데이터베이스에 연관관계가 물리적으로 존재하지 않아도 사용이 가능함.

2. SQLAlchemy 표현식

3. SQLModel

장점

단점

  • 개발이 진행 중이다.
  • 기본적인 메뉴얼만 제공되고 있다. => 사용법을 익히는데 어려울 가능성이 있다.

결론

  • ORM 연관관계는 초기에 적용해야 할 것으로 판단됨. (성능/코드)
  • SQLModel의 도입과 함께 표현식도 변경하는 것이 좋다고 판단됨.
    • 표현식을 변경하기 위해선 작업량이 많은 것으로 예상됨.
  • SQLModel의 도입 시기를 잘 정해야 할 듯 하다..

관리자 리스트의 경우 board_list(), member_list() 함수 참고하세요.

공통 검색 파라미터를 사용시

async def member_list(request: Request, db: Session = Depends(get_db), search_params: dict = Depends(common_search_query_params)):

select query 시

result = select_query(
            request, 
            models.Member, 
            search_params, 
            same_search_fields = ["mb_level"], 
            prefix_search_fields = ["mb_name", "mb_nick", "mb_tel", "mb_hp", "mb_datetime", "mb_recommend"]
        )

    same_search_fields: # 값이 완전히 같아야지만 필터링 '검색어'
    prefix_search_fields: # 뒤에 %를 붙여서 필터링 '검색어%'

수정된 회원 관리 리스트 코드

@router.get("/member_list")
async def member_list(request: Request, db: Session = Depends(get_db), search_params: dict = Depends(common_search_query_params)):
    '''
    회원관리 목록
    '''
    request.session["menu_key"] = MEMBER_MENU_KEY
    
    result = select_query(
                request, 
                models.Member, 
                search_params, 
                same_search_fields = ["mb_level"], 
                prefix_search_fields = ["mb_name", "mb_nick", "mb_tel", "mb_hp", "mb_datetime", "mb_recommend"]
            )
    
    query_string = generate_query_string(request)
    
    context = {
        "request": request,
        "members": result['rows'],
        "admin": request.state.context['member'], # 로그인해 있는 회원을 관리자로 간주함
        "total_count": result['total_count'],
        "paging": get_paging(request, search_params['current_page'], result['total_count'], f"/admin/member_list?{query_string}&page="),
    }
    return templates.TemplateResponse("member_list.html", context)

에러) 댓글에 답변 달기 할때 에러 납니다.

에러 메세지

INFO:     59.10.38.107:0 - "POST /board/write_comment_update/ HTTP/1.0" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
    cursor.execute(statement, parameters)
sqlite3.OperationalError: near "(": syntax error

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 426, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/ubuntu/.local/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__
    return await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/applications.py", line 292, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/sessions.py", line 86, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 260, in main_middleware
    response = await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 95, in dispatch
    return await call_next(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 84, in call_next
    raise app_exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 70, in coro
    await self.app(scope, receive_or_disconnect, send_no_error)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 273, in app
    raw_response = await run_endpoint_function(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/fastapi/routing.py", line 190, in run_endpoint_function
    return await dependant.call(**values)
  File "/home/ubuntu/gnu6/bbs/board.py", line 1123, in write_comment_update
    comment.wr_comment_reply = generate_reply_character(board, parent_comment)
  File "/home/ubuntu/gnu6/lib/board_lib.py", line 891, in generate_reply_character
    result = query.order_by(desc("reply")).first()
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 2748, in first
    return self.limit(1)._iter().first()  # type: ignore
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/query.py", line 2847, in _iter
    result: Union[ScalarResult[_T], Result[_T]] = self.session.execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2308, in execute
    return self._execute_internal(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
    result: Result[Any] = compile_state_cls.orm_execute_statement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
    result = conn.execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
    return self._exec_single_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
    self._handle_dbapi_exception(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) near "(": syntax error
[SQL: SELECT right(g6_write_notice.wr_comment_reply, ?) AS reply 
FROM g6_write_notice 
WHERE g6_write_notice.wr_parent = ? AND g6_write_notice.wr_comment = ? AND length(g6_write_notice.wr_comment_reply) = ? ORDER BY reply DESC
 LIMIT ? OFFSET ?]
[parameters: (1, 4, 1, 1, 1, 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)

웹브라우저에서 설치하는 기능 추가

그누보드5 버전과 같이 웹브라우저에서 설치하는 기능 추가

DB 선택
MySQL 선택시 HOST, PORT, USER, PASSWORD, DB NAME 설정
PostgreSQL 선택시 ???

관리자 아이디, 패스워드 등 설정

재설치 여부

설치시 .env 에 COOKIE_DOMAIN 추가 바랍니다.

# www.gnuboard.com 과 gnuboard.com 도메인은 서로 다른 도메인으로 인식합니다. 
# 쿠키를 공유하려면 .gnuboard.com 과 같이 입력하세요.
# 이곳에 입력하지 않으면 www 붙은 도메인과 그렇지 않은 도메인은 쿠키를 공유하지 못하므로 
# 로그인이 풀릴 수 있습니다.
COOKIE_DOMAIN = ""

급) base.html 을 분할 했을때 나오는 오류

브랜치 : https://github.com/gnuboard/gnu6/tree/split-html

File "/home/ubuntu/.local/lib/python3.10/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "templates/basic/alert.html", line 1, in top-level template code
    {% extends "base_head.html" %}
  File "templates/basic/base_head.html", line 11, in top-level template code
    <link rel="stylesheet" href="{{ theme_asset('css/board.css') }}">
  File "/home/ubuntu/.local/lib/python3.10/site-packages/jinja2/utils.py", line 83, in from_obj
    if hasattr(obj, "jinja_pass_arg"):
jinja2.exceptions.UndefinedError: 'theme_asset' is undefined

글쓰기, 쪽지 보내기시 오류 발행

url_for() 함수가 https가 아닌 http를 반환하는 현상

원인

  • uvicorn 실행 시, HTTP로 실행되어 FastAPI에서 사용하는 base_url이 http로 설정됨.

테스트

1. uvicorn 실행 시, HTTPS로 실행

  • 간단하고 근본적인 해결책
  • 서버 실행 시, 명령어가 길어져 사용성이 떨어지므로 프로그램 방식으로 실행하는 방법을 테스트 해봄
명령어 방식
  • uvicorn main:app --reload --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem
프로그램 방식
  • python deployment.py
  • 실행 코드
# deployment.py
import uvicorn

if __name__ == "__main__":
    uvicorn.run("main:app", port=8000, reload=True, ssl_keyfile="key.pem", ssl_certfile="cert.pem")
실행결과

image

2. url_for 함수 사용시 https로 강제 변경

  • 근본적인 해결책이 되지 않으므로 지양해야할 방법이라고 판단된다.
Jinja2 Template > url_for 함수 테스트 코드
  • 아래 코드는 template에서 사용하는 url_for만 변경한다. python 파일 > request.url_for은 별개의 처리가 필요하다..
try:
    import jinja2
    if hasattr(jinja2, "pass_context"):
        pass_context = jinja2.pass_context
    else:  # pragma: nocover
        pass_context = jinja2.contextfunction  # type: ignore[attr-defined]
except ImportError:  # pragma: nocover
    jinja2 = None  # type: ignore


@pass_context
def url_for(context: dict, name: str, **path_params) -> str:
    request = context["request"]
    http_url = request.url_for(name, **path_params)
    if request.url.scheme == 'https' or "x-forwarded-for" in request.headers.keys():
        return http_url.__str__().replace("http", "https", 1)
    else:
        return http_url

class MyTemplates(Jinja2Templates):
    """
    Jinja2Template 설정 클래스
    """
    def __init__(self,
                 directory: Union[str, os.PathLike],
                 context_processors: dict = None,
                 globals: dict = None,
                 env: Environment = None
                 ):
        super().__init__(directory=directory, context_processors=context_processors)
        # 공통 env.global 설정
        self.env.globals["editor_path"] = editor_path
        self.env.globals["generate_token"] = generate_token
        self.env.globals["getattr"] = getattr
        self.env.globals["get_selected"] = get_selected
        self.env.globals["get_member_icon"] = get_member_icon
        self.env.globals["get_member_image"] = get_member_image
        self.env.globals["url_for"] = url_for

...

결론

  • 테스트 1번과 같은 방식으로 해결
  • 서버 실행을 명령어 방식으로 할 것인가 프로그램 방식으로 할 것인가 정해야 한다.

dataclasses 반영

dataclassform.py 에 폼 관련 class 생성하세요.
_admin/admin_config.py 반영 완료 (참고하세요)

cheditor4/config.js 의 dataFeed()

사용하지 않는 함수라면 삭제해 주세요.

function dataFeed( opts, callback ) {
    var data = [];

    if (opts.marker == "@" && opts.query) {
        
        var thisVal = opts.query,
            srcRegex = /<img.*?src=["'](.*?)["']/;
        
        ck_itemsMentions = ck_cachequeryMentions[thisVal];

        if(typeof ck_itemsMentions == "object"){
            callback(ck_itemsMentions);
        } else {
            var data_url = g5_plugin_url+"/mention/q.php";

버전 표시 안내

예) 6.0.1-beta

fastapi 의 버전 표시를 참고하여 예) 와 같이 표시할까 합니다.
앞에 v 는 붙이지 않습니다.
https://github.com/tiangolo/fastapi/tags

beta 버전을 거쳐 안정화가 되었다고 하는 시기에 -beta 를 떼려고 합니다.
예) 6.1.1

다른 의견 있으면 주세요.

템플릿 변경시 기존템플릿 로딩

jinja 템플릿 캐시로 인해 템플릿이름이 동일한 경우 바뀌지 않습니다.

basic 테마 index.html -> test 템플릿 로딩 index.html -> basic index.html

env.loader.searchpath 변경시에도 basic index.html 계속로딩

변경시 캐시삭제

그누보드6 코드 <-> 그누보드5 DB 호환: Config

1. config 테이블 cf_id pk 컬럼

  • 그누보드6는 primary key로 cf_id를 설정
  • 그누보드5는 cf_id값 존재하지 않음
    install.py 파일 실행시 다음과 같은 오류 발생
    sqlalchemy.exc.OperationalError: (pymysql.err.OperationalError) (1054, "Unknown column 'g5_config.cf_id' in 'field list'")

2. config 테이블 cf_optimize_date 컬럼

  • config table에 cf_id 컬럼 추가하는 과정에서 다음과 같은 오류 발생
    Invalid default value for 'cf_optimize_date'
    그누보드 5 DB는 default 값이 0000-00-00으로 되어 있음
  • MySQL의 NO_ZERO_DATE 모드로 실제로 존재하지 않는 날짜, 시간 형식 저장이 안되도록 default 지정되어 있어서 오류 발생

base_login.html 과 base_window.html 을 없애기 위해 base.html 을 3개의 파일로 나눕니다.

기존

base.html

<html>
<head>
<title>제목</title>
<script></script>
</head>
<body>
{% block content %}
{% endblock content %}
</body>
</html>

변경

base.html

{% extends "base_body.html" %}

{% block content %}
{% endblock content %}

base_body.html

{% extends "base_head.html" %}

{% block base_body %}
<!-- layout -->
{% endblock base_body %}

base_head.html

<html>
<head>
<title>제목</title>
<script></script>
</head>
<body>
{% block base_body %}
    {% block content %}
    {% endblock content %}
{% endblock base_body %}
</body>
</html>

기존의 base_window.html 을 사용하는 파일

memo_list.html

{% extends "base_head.html" %}
{% import "/sideview/macros.html" as sideview %}

{% block title %}내 쪽지함{% endblock title %}

{% block content %}
<!-- 컨텐츠 -->
{% endblock content %}

.env 파일 없을 경우 설치시 오류

화면이 출력되지 않으며 아래와 같은 오류 발생

File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/middleware/base.py", line 108, in __call__
    response = await self.dispatch_func(request, call_next)
  File "/home/ubuntu/gnu6/main.py", line 129, in main_middleware
    return template_response("alert.html", context, e.status_code)
  File "/home/ubuntu/gnu6/core/exception.py", line 73, in template_response
    return template.TemplateResponse(template_html, context, status_code)
  File "/home/ubuntu/.local/lib/python3.10/site-packages/starlette/templating.py", line 110, in TemplateResponse
    context.update(context_processor(request))
  File "/home/ubuntu/gnu6/core/template.py", line 87, in _default_context
    "menus": get_menus(),
  File "/home/ubuntu/gnu6/lib/common.py", line 1305, in get_menus
    parent_menus = db.scalars(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2420, in scalars
    return self._execute_internal(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/session.py", line 2190, in _execute_internal
    result: Result[Any] = compile_state_cls.orm_execute_statement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/orm/context.py", line 293, in orm_execute_statement
    result = conn.execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1416, in execute
    return meth(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/sql/elements.py", line 516, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1639, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1848, in _execute_context
    return self._exec_single_context(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1988, in _exec_single_context
    self._handle_dbapi_exception(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 2343, in _handle_dbapi_exception
    raise sqlalchemy_exception.with_traceback(exc_info[2]) from e
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/base.py", line 1969, in _exec_single_context
    self.dialect.do_execute(
  File "/home/ubuntu/.local/lib/python3.10/site-packages/sqlalchemy/engine/default.py", line 922, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: g6_menu
[SQL: SELECT g6_menu.me_id, g6_menu.me_code, g6_menu.me_name, g6_menu.me_link, g6_menu.me_target, g6_menu.me_order, g6_menu.me_use, g6_menu.me_mobile_use 
FROM g6_menu 
WHERE length(g6_menu.me_code) = ? ORDER BY g6_menu.me_order]
[parameters: (2,)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)

Annotated 활용 제안

정의

Annotated 클래스는 파이썬 타입 힌트와 FastAPI의 의존성 주입 시스템을 세밀하게 제어하기 위한 도구입니다.
Annotated을 사용하면 더 많은 정보를 타입 힌트에 추가하고 FastAPI가 이 정보를 활용하도록 할 수 있습니다.

Annotated 활용해야하는 이유

  • FastAPI 공식사이트 및 블로그에서 참고한 장점들을 정리

1. 기본값 선언이 더 직관적이다.

  • 이 부분은 솔직히 공감이 잘 안된다...
# Before
kind: str = Query(default="recv"),

# After
kind: Annotated[str, Query()] = "recv",

2. 중복코드량 감소

# 데이터베이스 의존성 주입
DBSession = Annotated[Session, Depends(get_db)]

@router.post("/memo_form_update")
async def memo_form_update(
    request: Request,
    db: DBSession,
    ...

3. parmater 순서에 대한 제한이 없음

4. 의존성주입을 세부적으로 커스터마이징 가능

5. API 문서화

  • 그누보드6는 자동 문서화를 사용하지 않으니 상관없을듯..

결론

  • 2, 4번의 활용방법이 적절할 것으로 판단됨.
  • Annotated를 사용하지 않은 현 상태가 가독성이 더 좋은 경우가 많은 것으로 보임.
  • 일부 복잡한 유효성 검사 및 중복된 파라미터의 체크 등에는 적용이 필요할 것으로 판단됨.
    • 토큰
    • 캡챠
    • 일부 파라미터의 유효성검사

member 테이블 mb_leave_date 컬럼 제약조건 관련 에러

문제 상황

회원 탈퇴 시 백엔드를 통해 g6_member 테이블의 mb_leave_date에 입력되는 값은 '2023-12-21'와 같이 10 글자임 (코드 참고)

# bbs/member_leave.py

@router.post("/member_leave", dependencies=[Depends(validate_token)])
async def member_leave(
    ...
    leave_date = datetime.now().strftime("%Y-%m-%d")     # 10 글자로 생성 ex) 2023-12-22
    memo = f"{login_member.mb_memo}\n{leave_date}탈퇴함"
    db.execute(
        update(Member)
        .values(mb_leave_date=leave_date, mb_memo=memo)  # 8글자 제약조건의 mb_leave_date에 10글자 leave_date 값 입력
        .where(Member.mb_id == login_member.mb_id)
    )

컬럼 제약조건은 varchar(8)로 8글자라서 아래와 같은 오류 발생

 sqlalchemy.exc.DataError: (pymysql.err.DataError) (1406, "Data too long for column 'mb_leave_date' at row 1")

수정시 고려 사항

그누보드 5 DB도 해당 컬럼의 제약조건이 varchar(8)이므로 호환성을 고려하여 수정해야 할 것으로 보임

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.