project Amelia2 는 내부 팀에서 외부팀으로 전달할 자료나 프로그램 업데이트 관련 내용을 전달할 때 쓰는 게시판이다.
기존 장고로 2021년에 제작 했는데 이번에 사용하던 서버 교체로 데이터를 옮기다가 이참에 안 쓰는 게시판은 정리하고 파이썬 버전도 3.8.10에서 3.11.6으로 옮기고 mysqlclient도 PyMySQL로 변경해서 가져오기로 했다.
프로젝트 기간: 11.30 ~ 12.13
파이썬 장고에 기반을 두고 mariaDB로 진행 / 기존 아멜리아1에서 DB 커스텀 이전(항목 일부 상이)
# 가상환경 D:\Python\python311\python.exe -m venv vAmelia2 # 장고 pip install django # MySQL DB pip install pymysql # 장고 CORS pip install django-cors-headers # 이미지 필드 사용을 위해서 pip install Pillow # iis 세팅용 pip install wfastcgi # 장고 글쓰기 에디터 pip install django-ckeditor-5 # html 접근 pip install beautifulsoup4 # 기본 프로젝트 django-admin startproject back . # 앱 python manage.py startapp org python manage.py startapp web python manage.py startapp history python manage.py startapp handover python manage.py startapp guide python manage.py startapp setting python manage.py startapp utils
CREATE DATABASE amelia2 CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
python manage.py makemigrations python manage.py migrate python manage.py createsuperuser
import os import pymysql from pathlib import Path from django.contrib.messages import constants as messages pymysql.install_as_MySQLdb() BASE_DIR = Path(__file__).resolve().parent.parent ALLOWED_HOSTS = ["*"] INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', # 장고 CORS 'org.apps.OrgConfig', 'web.apps.WebConfig', 'history.apps.HistoryConfig', 'handover.apps.HandoverConfig', 'guide.apps.GuideConfig', 'setting.apps.SettingConfig', 'utils.apps.UtilsConfig', 'django_ckeditor_5', ] AUTH_USER_MODEL = 'org.User' MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', ... ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(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', ], 'debug': True, }, }, ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'amelia2', 'USER': 'root', #root 'PASSWORD': '0000', #password 'HOST': 'localhost', 'PORT': '3306', } } # 쉬운 비밀번호 사용을 위해 비밀번호 유효성 검사기 제거 AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': { 'min_length': 4, # 최소 길이를 4로 설정 } }, ] LANGUAGE_CODE = 'ko' TIME_ZONE = 'Asia/Seoul' LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, 'simple': { 'format': '{levelname} {message}', 'style': '{', }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'console': { 'level': 'INFO', 'filters': ['require_debug_true'], 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'file': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': 'error.log', 'formatter': 'verbose' }, }, 'loggers': { 'django': { 'handlers': ['console', 'file'], 'level': 'INFO', }, 'task': { # 'task'는 당신의 앱 이름입니다. 필요에 따라 변경하세요. 'handlers': ['console', 'file'], 'level': 'INFO', 'propagate': False, }, } } MESSAGE_TAGS = { messages.DEBUG: 'debug', messages.INFO: 'info', messages.SUCCESS: 'success', messages.WARNING: 'warning', messages.ERROR: 'danger', } CKEDITOR_5_CONFIGS = { 'default': { 'toolbar': [ 'heading', '|', 'highlight', 'fontColor', 'fontBackgroundColor', 'fontSize', '|', 'insertTable', 'blockQuote', 'link', 'imageUpload', 'mediaEmbed', 'codeBlock', '|', 'bulletedList', 'numberedList', 'alignment', 'outdent', 'indent', 'horizontalLine', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'undo', 'redo', '|', ], 'extra_plugins': ['ImageUpload', 'Clipboard', 'Image', 'ImageToolbar', 'ImageStyle'], 'indentBlock': { 'classes': [ 'indent-1', 'indent-2', 'indent-3', 'indent-4' ], 'offset': 40, 'unit': 'px', 'tags': ['p', 'ul', 'ol', 'blockquote', 'figure'], }, 'fontSize': { 'options': [10, 12, 'default', 16, 18, 20, 24, 28, 32, 40] }, 'alignment': { 'options': ['left', 'center', 'right', 'justify'] }, 'image': { 'toolbar': [ 'imageTextAlternative', 'imageStyle:alignLeft', 'imageStyle:alignCenter', 'imageStyle:alignRight', ], 'styles': [ 'alignLeft', 'alignCenter', 'alignRight' ], }, 'ckfinder': { 'uploadUrl': '/ckeditor5/image_upload/', }, 'table': { 'contentToolbar': ['tableColumn', 'tableRow', 'mergeTableCells'] }, 'heading': { 'options': [ {'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph'}, {'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1'}, {'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2'}, {'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3'} ] }, 'htmlSupport': { 'allow': [ { 'name': '*', # 모든 태그 허용 'attributes': True, # 모든 속성 허용 'classes': True, # 모든 클래스 허용 'styles': True # 모든 스타일 허용 } ] }, 'fontColor': { 'colors': [ { 'color': '#000000', }, { 'color': '#7F7F7F', }, { 'color': '#BFBFBF', }, { 'color': '#DFDFDF', }, { 'color': '#FFFFFF', }, { 'color': '#C00000', }, { 'color': '#F79646', }, { 'color': '#FFD966', }, { 'color': '#9BBB59', }, { 'color': '#00B050', }, { 'color': '#4472C4', }, { 'color': '#002060', }, { 'color': '#7030A0', }, { 'color': '#D58EBF', }, { 'color': '#8B5E3C', }, ], 'columns': 5 # 색상을 한 줄에 몇 개씩 표시할지 설정 }, 'fontBackgroundColor': { 'colors': [ { 'color': '#F2F2F2', #연한 회색(흰색에 가까운 배경) }, { 'color': '#D9D9D9', # 밝은 회색 }, { 'color': '#BFBFBF', #중간 회색 }, { 'color': '#FDE9D9', #연한 주황빛 }, { 'color': '#FFF2CC', #연한 노랑 }, { 'color': '#E4F0E2', # 연한 연두 }, { 'color': '#D9EAD3', #부드러운 초록 }, { 'color': '#D9EAF3', # 연한 하늘색 }, { 'color': '#D9DFF4', # 연한 파란색 }, { 'color': '#EAD1DC', # 연한 핑크 }, { 'color': '#F4CCCC', #연한 빨강 }, { 'color': '#FCE4D6', # 연한 주황 }, { 'color': '#F9CB9C', # 중간 주황 }, { 'color': '#D9D2E9', # 연보라색 }, { 'color': '#F6E3DA', #부드러운 크림색 }, ], 'columns': 5 # 색상을 한 줄에 몇 개씩 표시할지 설정 }, 'language': 'ko', 'removePlugins': ['WordCount'], } } # 보안 설정 추가 SECURE_CONTENT_TYPE_NOSNIFF = True # 브라우저가 Content-Type 헤더를 무시하지 못하도록 설정 - 콘텐츠 스니핑(콘텐츠 유형 추측)으로 인한 공격을 방지 SECURE_BROWSER_XSS_FILTER = True # 브라우저의 XSS(크로스 사이트 스크립팅) 필터를 활성화 - 사용자가 입력한 데이터가 악의적으로 조작되어 XSS 공격에 악용되는 것을 방지 X_FRAME_OPTIONS = 'DENY' # 페이지가 iframe으로 렌더링되는 것을 방지 - 클릭재킹(clickjacking) 공격을 방지 SECURE_CROSS_ORIGIN_OPENER_POLICY = None # Django에서 기본으로 설정되는 Cross-Origin-Opener-Policy (COOP) 헤더를 비활성화 # STATIC_URL = 'static/' STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] # CKEditor 5 업로드 경로 CKEDITOR_5_UPLOAD_PATH = 'uploads/' # 정적 파일 모아서 static에 넣어주기 # STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # python manage.py collectstatic # 미디어 파일 경로 설정 MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 로그인 아웃 후 리다이렉션 경로 LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/login/'
from django.contrib import admin from django.urls import path, include from django.conf import settings from django.conf.urls.static import static from web import views urlpatterns = [ path('admin/', admin.site.urls), path('', views.index, name='index'), path('org/', include('org.urls')), path('web/', include('web.urls')), path('history/', include('history.urls')), path('handover/', include('handover.urls')), path('guide/', include('guide.urls')), path('setting/', include('setting.urls')), path('ckeditor5/', include('django_ckeditor_5.urls')), path('ckeditor5/image_upload/', views.upload_image, name='upload_image'), ] # 개발 환경에서 미디어 파일 제공 if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
HeidiSQL를 이용해서 원하는 데이터를 우클릭 후 [데이터베이스를 SQL로 내보내기] 후에 노트 패드 같은 에디터 프로그램 아무거나 열어서 데이터 수정 한 후
MySQL Client (MariaDB 10.6 (x64)) 프로그램 열어서~
source D:/Develop/D_Amelia2/db/p.sql; source D:/Develop/D_Amelia2/db/m.sql; source D:/Develop/D_Amelia2/db/x.sql;
이런 식으로 실행해서 DB 덮어주기~ 하면 됨
이번 아멜리아2는 회사 내 외부 팀과의 정보 공유가 목적이라서 글쓰기 에디터와 디테일이 중점을 두었다.
특이점은 탭이랑 워드에 비해 한없이 기능이 적은 에디터를 어떻게 꾸리기 ㅠㅡㅠ~ 정도~
처음엔 탭 수를 사용자가 임의로 만들 수 있게 하려다 보니 에디터를 프론트 단에서 붙여야 하는데 그럴 때 에디터 내부에 지원되는 범위가 더 작아서 고정 탭 3개로 하고 3개에 전부 장고 쪽에서 에디터 붙여서 쓰기로 했다.
한 예로 이미지 복사 넣기로 넣은 다음에 이미지 크기 조정 하려니 프론트 쪽 에디터에선 잘 안되던.. 백에서 붙인 에디터에선 가능했다.