[배경]
게시판과 게시판에 대한 파일 테이블이 1:n 으로 매핑되어 있다.
근데 문제는
- 일반게시판 테이블 - 일반게시판 파일 테이블
- 공모전게시판 테이블 - 공모전게시판 파일 테이블
- 강의게시판 테이블 - 강의게시판 파일 테이블
이런 식으로 게시판 종류에 따라, 그에 해당하는 파일 테이블이 하나씩 붙어있는 구조였다.
그래서 ORM 상에서도 모든 게시글 도메인과 파일 도메인이 각각 따로 존재했고,
중복되는 코드도 너무 많았다.
예로, board 앱의 views.py 에서 normalBoard - normalBoardFile 에 대한 CRUD 와 유효성 검사 로직이 있으면,
contest 의 views.py 에서도 contestBoard - contestBoardFile 에 대한 거의 유사한 로직이 반복되는 것이다.
파일은 대부분은 비슷하게 처리하는데, 동일한 코드를 반복해서 작성하는 것을 줄일 수 있지 않을까? 라는 생각으로 접근했다.
[구현]
(1) 파일 util 묶기 & 엔티티 매핑
일단 파일 처리 관련 함수들을 하나로 묶었다.
게시판 종류에 따른 파일 클래스를 매핑함으로써 유연성을 확보했다.
File_of_ = {
Bank: BankFile,
Board: BoardFile,
ContestBoard: ContestFile,
LectBoard: LectBoardFile,
UserDelete: UserDeleteFile,
LectAssignmentSubmit: LectAssignmentSubmittedFile,
}
class FileController:
# 생략...
# INPUT : InMemoryUploadedFile 객체 리스트 / Board 또는 ContestBoard 객체
@staticmethod
def upload_new_files(files_to_upload, instance):
# 새로 사용자가 파일을 첨부한 경우
# files_to_upload : InMemoryUploadedFile 객체 리스트를 넘겨 받는다.
# 파일 폼에서 save() 할 때 이 함수를 호출!
for file in files_to_upload:
File_of_[type(instance)].objects.create( # 각각의 파일을 InMemoryUploadedFile 객체로 받아옴
file_fk=instance,
file_path=file, # uploadedFile 객체를 imageField 객체 할당
file_name=file.name.replace(' ', '_') # imageField 객체에 의해 파일 이름 공백이 '_'로 치환됨
)
(2) FileBaseForm 생성
그 다음으로 모든 FileForm 이 상속받아야하는 FileBaseForm 클래스를 만들었다.
from django import forms
class FileFormBase(forms.Form):
upload_file = forms.FileField(
required=False,
widget=forms.FileInput(
attrs={'multiple': True,
'accept': ".xlsx,.xls,image/*,.doc,.docx,video/*,.ppt,.pptx,.txt,.pdf,.py,.java,.zip"})
)
"""
[폼 객체 생성주기]
1) (forms.py) 에서 클래스 정의
2) (views.py) 에서 객체 생성 => 컨텍스트 변수에 담아 템플릿으로 넘겨줌
3) (template) 폼 객체 멤버 변수는 각각 해당변수선언에 대응하는 input 태그로 변환됨.(밑에 예시)
4) (template) 해당 템플릿에서 이탈하면 폼 객체 소멸.
[ 변환 예시 ]
: 변수이름은 input 태그의 name 으로 들어감!
: 자동으로 required 설정됨. 안하려면 required=False 선언해야함.
#######################################################################################
(forms.py) => (template)
-------------------------------------------------------------------------------------------------------------------
FileForm.upload_file => <input type="file" name="upload_file" multiple
accept=".xlsx,.xls,image/*,.doc,.docx,video/*,.ppt,.pptx,.txt,.pdf,.py,.java">
########################################################################################
"""
def save(self, instance):
# 이 폼은 여러개의 파일을 갖고 있을 것이다.
# upload_new_files 이용해서 저장하기
# 파일 폼 객체는 files 라는 Query dict 객체 존재 {'upload_file' : InMemoryUploadedFile 리스트}
from file_controller import FileController
FileController.upload_new_files(
files_to_upload=self.cleaned_data.get('upload_file'),
instance=instance
)
# overriding
# is_valid 호출 시 내부에서 자동적으로 clean 호출
def clean(self):
super().clean()
self.cleaned_data['upload_file'] = self.files.getlist('upload_file')
기본적으로 장고폼은 request body의 upload_file 에 있는 파일리스트에서 단 1개의 파일만 가져오게 되어있는데, clean 함수에서 모두 가져오도록 변경했다.
아래와 같이 상속 받아서 사용하면 된다.
class FileForm(FileFormBase):
# overriding
# is_valid 호출 시 내부에서 자동적으로 clean 호출
def clean(self):
super().clean()
# 호출 함수 이름 반환 : sys._getframe(5).f_code.co_name
# 함수 호출 스택 : clean(0계층, 현재 함수) << _clean_form(1계층) << full_clean(2계층) << errors(3계층) << is_valid(4계층)
# 즉 is_valid 를 호출한 함수 이름을 묻는 것.
calling_function = sys._getframe(5).f_code.co_name
if calling_function in ['contest_register', 'contest_update', 'activity_register', 'activity_update']:
# 이미지 파일이 없는 경우
if not self._check_contest_thumbnail():
print("이미지 있니?", self._check_contest_thumbnail())
self.cleaned_data['upload_file'] = None # cleaned_data 를 비운다
raise forms.ValidationError(
_('공모전 이미지를 적어도 한 개 이상 등록해주세요!'),
code='invalid'
)
# protected
def _check_contest_thumbnail(self):
pass
그러면 views.py 는 아래와 같이 작성할 수 있다. 유효성 검사에 대한 부분이 사라지고, 파일 처리에 대한 부분도 딱히 신경쓰지 않아도 비지니스 로직을 처리할 수 있게 된 것이다!!
def contest_register(request): # 공모전 등록
if request.method == 'POST':
contest_form = ContestForm(request.POST)
file_form = FileForm(request.POST, request.FILES)
if contest_form.is_valid() and file_form.is_valid():
with transaction.atomic():
contest = contest_form.save(
contest_writer=get_logined_user(request))
file_form.save(instance=contest)
messages.success(request, '게시글을 성공적으로 등록하셨습니다.')
return redirect("contest_detail", contest_no=contest.contest_no)
'웹 프로젝트 (IBAS) > Django 레거시' 카테고리의 다른 글
[Django 웹 프로젝트] 6. 유지 보수를 위한 새로운 아키텍처 고민 (2021-10-21) (0) | 2022.03.01 |
---|---|
[Django 웹 프로젝트] 5. static file name hashing 하기 (2021-09-09) (0) | 2022.02.28 |
[Django 웹 프로젝트] 4. 댓글(vue.js)을 django 에 붙이기 (2021-08-03) (0) | 2022.02.28 |
[Django 웹 프로젝트] 2. 장고 폼(forms) 도입 => 코드 간결화 (2021-04-28) (0) | 2021.07.09 |
[Django 웹프로젝트] 1. 어쩌다 생애 첫 프로젝트 (2021-04-04) (0) | 2021.04.04 |