me

[python/development] project workplan2 개발
2024.10.10 / , , ,

project workplan2 란?

project workplan2

project workplan2 는 약 10년 정도 쓴 프로그램인 workplan의 필요한 기능 추가 및 삭제, 개선으로 리뉴얼한 버전이다. 기존 workplan은 다른 분이 개발한 것으로 2버전에서는 DB 설계부터 다시 시작한 것으로 이름은 이어서 2버전으로 진행하지만 실상 다른 프로그램이라고 해도 무방하다.

workplan2는 크게 근태 관리, 팀 근태 관리, 업무 보고, 업무 진행 현황, 팀 정보 공유 등에 초점을 맞춰 제작하였다.

프로젝트 기간: 10.2 ~ 11.22 작업 완료~!


기본 환경 세팅

파이썬 장고에 기반을 두고 mariaDB로 진행

가상환경 설치

py -3.11 -m venv vWorkplan2

cd D:\Develop\D_workplan2\vWorkplan2\Scripts   # 가상 환경 접속
activate
cd../..

python.exe -m pip install --upgrade pip

장고 설치

# 장고
pip install django

# MySQL DB 
pip install pymysql

# 장고 CORS
pip install django-cors-headers

# 이미지 필드 사용을 위해서
pip install Pillow

# 공휴일 처리
pip install holidays

# 엑셀 다운로드
pip install openpyxl

# outlook 주소록 추출
pip install pywin32

# iis 세팅용
pip install wfastcgi

# 장고 글쓰기 에디터
pip install django-ckeditor-5

# 기본 프로젝트
django-admin startproject back .

DB 생성

마리아 DB 설치 방법: [python/setting] 파이썬 – Django 설치 + 셋팅

create database workplan2;  # 데이터베이스 생성
use workplan2               # workplan2 DB 사용

show databases;             # 데이터베이스 생성 확인

관리자 세팅

python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser


기본 장고 세팅

settings.py

import os
import pymysql
from pathlib import Path
from django.contrib.messages import constants as messages


pymysql.install_as_MySQLdb()

DEBUG = True                           # 디버깅 켜기

ALLOWED_HOSTS = ["*"]

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',  # 장고 CORS

    'utils.apps.UtilsConfig',
    'org.apps.OrgConfig',
    'web.apps.WebConfig',
    'work.apps.WorkConfig',
    'check.apps.CheckConfig',
    'setting.apps.SettingConfig',
    'task.apps.TaskConfig',
    'status.apps.StatusConfig',
    'record.apps.RecordConfig',
    'info.apps.InfoConfig',

    'django_ckeditor_5',
]

AUTH_USER_MODEL = 'org.User'

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',   #에디터 때문에 추가
    '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',
]


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',
            ],
        },
    },
]

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'workplan2',
        'USER': 'root',  
        'PASSWORD': '0000',
        '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', '|',
            'bulletedList', 'numberedList', 'alignment', 'outdent', 'indent', 'horizontalLine', '|',
            'bold', 'italic', 'underline', 'strikethrough', '|',
            'highlight', 'fontColor', 'fontBackgroundColor', 'fontSize', '|',
            'insertTable', 'blockQuote', 'link',  'imageUpload', 'mediaEmbed', '|',
            'codeBlock',  '|',
            'undo', 'redo', '|',
        ],

        'fontSize': {
            'options': [10, 12, 'default', 16, 18, 20, 24, 28, 32, 40]
        },

        'alignment': {
            'options': ['left', 'center', 'right', 'justify']
        },

        'image': {
            'toolbar': ['imageTextAlternative', 'imageStyle:full', 'imageStyle:side']
        },

        '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': 'ul',
                    'classes': ['custom-list-circle']
                },
                {
                    'name': 'li',
                    'classes': ['custom-list-item']
                }
            ]
        },

        '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')]

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# CKEditor 5 업로드 경로
CKEDITOR_5_UPLOAD_PATH = 'uploads/'

# 로그인
LOGIN_URL = '/login/'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/login/'

파이참 세팅

파이참 설정

여기까지 세팅 하고 실행하면

파이참 설정

이렇게 뜨고 저 http://127.0.0.1:8000/ 주소를 누르면 장고가 성공적으로 설치되었습니다. 를 볼 수 있다.

여기까지 진행하면 개발 및 테스트를 위한 기본 환경 세팅 완료이다.


project workplan2 개발

python manage.py startapp org
python manage.py startapp web
python manage.py startapp work
python manage.py startapp check
python manage.py startapp task
python manage.py startapp status
python manage.py startapp record
python manage.py startapp setting
python manage.py startapp info
python manage.py startapp utils

이메일을 로그인 아이디로 쓰기

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin

class UserManager(BaseUserManager):
    # 유저 생성 메서드
    def create_user(self, email, password=None, **extra_fields):
        if not email:
            raise ValueError('이메일을 아이디로 쓰고 있습니다. 이메일은 필수입니다.')

        email = self.normalize_email(email)  # 이메일 형식을 표준화 (대소문자 구분 없이)
        user = self.model(email=email, **extra_fields)  # 유저 생성
        user.set_password(password)  # 비밀번호 설정
        user.save(using=self._db)  # 데이터베이스에 저장
        return user

    # 관리자(슈퍼유저) 생성 메서드
    def create_superuser(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_manager', True)
        return self.create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    POSITION_CHOICES = [
        ('intern', '인턴'),
        ('staff', '사원'),
        ('associate', '주임'),
        ('assistant', '대리'),
        ('manager', '과장'),
        ('chief', '차장'),
        ('director', '부장'),
        ('leader', '팀장'),
        ('executive', '이사'),
        ('CEO', '대표이사')
    ]

    user_code = models.AutoField(primary_key=True)  # 자동 증가하는 고유 코드
    email = models.EmailField(unique=True)  # 로그인에 사용되는 이메일 (중복 불가)
    password = models.CharField(max_length=128)  # 기본 비밀번호 필드 (AbstractBaseUser에서 제공)
    user_name = models.CharField(max_length=100)  # 사용자 이름
    user_phone = models.CharField(max_length=15, null=True, blank=True)  # 전화번호 필드
    user_img = models.ImageField(upload_to='profiles/', null=True, blank=True)  # 프로필 이미지
    user_department = models.ForeignKey(Department, on_delete=models.CASCADE, null=True, blank=True)  # 소속 본부 정보
    user_team = models.ForeignKey(Team, on_delete=models.CASCADE, null=True, blank=True)
    user_position = models.CharField(max_length=50, choices=POSITION_CHOICES)  # 직급 정보 (인턴, 사원 등)
    user_start = models.DateField(null=True, blank=True)  # 입사일
    is_manager = models.BooleanField(default=False)  # 관리자 여부
    is_staff = models.BooleanField(default=False)  # 관리자 페이지 접근 여부
    is_working = models.BooleanField(default=True)  # 근무 여부
    create_time = models.DateTimeField(auto_now_add=True)  # 계정 생성 시간
    update_time = models.DateTimeField(auto_now=True)  # 업데이트 시간
    objects = UserManager()  # 커스텀 매니저 설정
    etc_leave = models.DecimalField(
        max_digits=5,  # 최대 5자리까지 저장 가능 (정수부 + 소수부)
        decimal_places=2,  # 소수점 이하 2자리까지 저장 가능
        default=0,
        verbose_name='기존 사용 연차'
    )

    # 이메일을 로그인 아이디로 사용하기 위한 설정
    USERNAME_FIELD = 'email'  # 로그인에 사용할 필드를 이메일로 설정

    def __str__(self):
        return self.user_name  # 객체 출력 시 유저 이름 반환

back/urls 설정

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('', include('org.urls')),
    path('web/', include('web.urls')),
    path('work/', include('work.urls')),
    path('check/', include('check.urls')),
    path('setting/', include('setting.urls')),
    path('task/', include('task.urls')),
    path('status/', include('status.urls')),
    path('record/', include('record.urls')),
    path('info/', include('info.urls')),

    path('ckeditor5/', include('django_ckeditor_5.urls')),
]

# 개발 환경에서 미디어 파일 제공
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

project workplan2 프로젝트에서 가장 어려웠던 점

장고로 웹 사이트 설계는 처음이 아니라서 개발 자체에서 큰 문제가 없었다.

오히려 개발보다는 기획 쪽이 더 시간이 걸렸달까?

개인마다 다르게 나타나는 설정 결과와 뷰 부분과 팀 공통으로 나와야 하는 정보를 구분하는 기획과 처리에 꽤 많은 시간을 투자하였다.

예를 들면 프로젝트 등의 순서도 팀 단위로 누구든 등록해서 공유하지만, 나오는 순서는 개인마다 다르게 나오게 한다든지 하는 부분?

그리고 주간 보고서를 위한 엑셀 처리 정도가 이 프로젝트에서 시간이 많이 투자된 부분이었다.

그래도 꽤 만족한 프로젝트였고, 배포 사용 후 팀 단위가 아닌 전사 단위 근태 프로젝트를 진행해달라는 의뢰가 쿨럭… 뭐 잘 만들었다는 이야기겠지;;;

나중에 회사 전체 단위의 근태 관리. 보고. 연차 관리 등 프로젝트를 진행할 예정이다… 다시 한번 말하지만 당장 아니고 예정이다!

DB가 들어가는 프로젝트는 DB 설계 등에서 생각할 게 너무 많단 말이지 ㅠㅡㅠ~


project workplan2 결과

프로젝트 기간: 10.2 ~ 11.22 작업 완료~!

아무도 모르고 티도 별로 안 나지만 디자인을 스타벅스 컨셉으로 해보았다. 평소 쓰던 그레이 색상 기준으로 하지 않고 나름 화려하게 써봤다. 나름 이렇게 화려한 색상은 첫 도전이었다.

프로그램적으로는 개인별 일반 업무 작성이 기존 대비 편해지고 상·하반기 업무 기록 자동 추출 가능해졌다.

관리자 기존 업무 보고 수동 작성 시간이 사라지고 자동 보고서로 제출 가능해졌다. 주 단위 몇 시간씩을 아낄 수 있다고 이거 너무 좋아하신 기능이었다.

또 업무 보고서 작성 완료 버튼으로 일일이 매주 보고서 작성 완료된 건지 물어보는 것도 없어져서 체크 사항이 조금 더 편해졌다.

처음에 연차, 오전 오후 반차 정도로 시작한 연차 처리 부분도 나중에는 연차, 오전 반차, 오후 반차, 특별 휴가, 근속 휴가, 청원 휴가, 출장, 컨퍼런스, 팀 회식 도 다 처리 되어 팀 공유가 훨씬 편해졌다.

사실 이런 웹 프로젝트는 좋아하는 프로젝트라 꽤 재미있게 완료 했다.


기타 프로젝트: https://pixiclue.com/tag/project/