본문 바로가기

About coding/Today I learned

2023년 04월 13일 TIL [#Django 입문_팀프로젝트_day4]


오늘의 학습 계획

_django 프로젝트 댓글 Read 기능 구현


기록하고 싶은 학습내용

_read 기능 구현 중에, 내가 이용자에게 입력 받아서 DB에 저장했던 사진을 html로 다시 보여주려고 했는데,

내가 그동안 DB에 저장했던 img는 그냥 경로에 불과 했다는 걸 알았다.

이미지를 반환하려면 이미지를 입력받았을 때 실제로 가지고 있어야 했는데, 그러려면,,,

CRUD 중 크리에이트부터 따라가본다.

#이용자가 보는 comment_create.html에서

<form method="post" enctype="multipart/form-data">
    <label for="comment_image">이미지 파일 추가</label> <!-- 자세한 코드는 DB 가용성 보고 수정 -->
    <br>
    <input type="file" id="comment_image" name="comment_image" onchange="setThumbnail(event);">
	<br>
    <button type="submit">등록</button>
</form>

이렇게 해서 이미지 파일을 받아야 한다. 여기서 포인트는

form 태그에 속성으로 enctype이라는 게 들어가 있다는 것이다.

html에서 사용자 입력 데이터를 백엔드로 전송하려면 보통은 form으로만 감싸면 되지만

감싼 데이터 중에 텍스트파일이 아닌 이진파일(이미지나 동영상)이 들어간다면

반드시 enctype="multipart/form-data" 속성을 추가해주어야 한다.

**enctype은 encording type의 줄임말.

그렇게 받은 데이터가 경로를 타고 들어와서

views.py에 있는 아래의 함수를 POST방식으로 건드리면,

@login_required    #위에서 임포트한 로그인 판별기 데코레이터
def comment_create(request):  # 작성하기 버튼 클릭 시 모든 인풋값을 받아서 comments DB에 저장하는 함수
    if request.method == 'GET':  # GET 메소드로 들어오면 댓글 작성용 화면을 보여줌
        return render(request, 'comments/comment_create.html')
    if request.method == 'POST':  # POST 메소드로 들어오면 작성한 인풋값들을 DB에 전송
        user = request.user  # 지금 계정에 로그인된 사용자 이름 가져옴
        my_comment = CommentModel()  # 댓글 모델클래스 이리와
        my_comment.comment_writer = user  # UserModel의 id값을 ForeignKey로 참조하여 로그인된 사용자에 할당

        # html에서 받은 각각 인풋값들을 DB에 넣기 위해 변수 선언
        my_comment.comment_content = request.POST.get('comment_content', '')  # 사용자가 입력한 댓글내용
        my_comment.comment_image = request.FILES.get('comment_image')  # 사용자가 업로드한 이미지파일

        my_comment.save()    #입력한 값들을 DB에 저장하는 중요한 명령어
        return redirect('/api/comments')    #저장하고 나면 댓글 보는 화면으로 보낸다.

함수가 진동하면서 고요하던 땅이 반으로 갈라지는 게 아니라

 

 

 

DB에 데이터가 잘 저장된다.

아래는 데이터 저장형태를 정의한 models.py 모습

class CommentModel(models.Model):
    class Meta:
        db_table = "my_comment"

    comment_writer = models.ForeignKey(UserModel, on_delete=models.CASCADE) #UserModel의 id값을 참조해서 comment_writer에 담음.
    comment_created_at = models.DateTimeField(auto_now_add=True)  # 이게 이미 날짜 찍는거
    comment_content = models.TextField(max_length=200) #개행을 할 수 있도록 TextField로 한다. CharField가 아니라.
    comment_image = models.ImageField(upload_to='images/') #DB이미지필드에 이미지이름을 넣으며 동시에 이미지 실물은 media의 하위폴더 images/ 경로로 저장.

그리고 이렇게만 하면 이미지 파일이 이름만 DB에 저장되고 끝난다.

실물을 받아야 들고 있다가 이용자가 보는 화면에 표시할 수 있기 때문에, 따로 디렉토리를 만들어

이용자가 입력한 이미지를 저장할 공간을 준비해야한다. 원래는 EngineX같은 웹서버에서 파일을 넘겨줘야지 django에서 바로 넘겨주면 안된다고 함. 왜냐하면 프로세스 기반의 언어인 python을 사용하는 django는 한번에 하나씩 요청을 처리하는데, 누군가 10기가 넘는 동영상이나 이미지를 업로드 하는 등 부하가 큰 요청을 시키면 그것 때문에 다른 사람들에게 주는 응답이 늦어지게 된다.

 

우선은 default 이미지파일 저장용으로 manage.py와 같은 위계에 static이라는 폴더를 생성하고,

그 하위에 admin 폴더 생성,

그 하위에 img 폴더 생성,

 

다음은 세팅스에 가서

STATIC_URL = 'static/'
# STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
STATICFILES_DIRS=[BASE_DIR / 'static']



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

이렇게 static과 media폴더에 대한 연결을 해주고,

 

앱폴더의

urls.py에 가서

from django.conf.urls.static import static #urlpatterns += 전용
from django.conf import settings #urlpatterns += 전용
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) #장고한테 이미지 루트를 알려줌

요로캐 장고한테 내가 어디서 이미지를 가져와야 될 지 알려준다.

 

 

 

 

이렇게 저장이 잘 됐으면 이제 Read쪽~

 

views.py에서 api/comments 요청이 오면 아래 함수를 건드려서

def comment_read(request):  # comments DB 댓글들을 모두 불러와서 html에 표시하는 함수. 표시된 댓글들 중 나의 댓글에는 수정하기와 삭제하기 버튼이 보인다.
    all_comment = CommentModel.objects.all().order_by("-comment_created_at") #커멘트모델의 모든 오브젝트를 불러오는데 오더바이 안의 순서로 불러와라. 앞에 - 썼으니 역순이라 최신글 상단
    return render(request, "comments/comment_read.html", {"all_comment": all_comment}) #렌더를 해오되, 왼쪽all_comment 오른쪽all_comment 값을 담아서 html에 보내주는 것.

미리 준비해둔 템플릿을 렌더하여 html 화면을 찍어낸다. 그 때 내가 저장해 놨던 데이터들을 .object.all() 해서 모두 불러온 뒤 템플릿을 전달하는 응답에 같이 실어보낸다.

 

 

{% load static %} #스태틱폴더를 참조한다는 장고명령어
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>챌린지 댓글을 남겨주세요!</h1>
<div style="background-color: gray; width: 70%; height: 50%; padding: 10px;">
    <a href="/api/comments/create">나도 작성하기!</a>
    {% if all_comment %} #조건문을 걸기 시작한다. views.py에서 렌더 해줄때 담아 보낸 DB데이터가 all_comment
        {% for comment in all_comment %} #데이터를 돌려서
            <div style="background-color: white; width: 100%; margin-top: 10px;">
                <p style="display: flex; flex-direction: row; justify-content: space-between; padding: 10px">
                    <span>{{ comment.comment_writer }}</span>   #이런식으로 데이터를 돌리면서 원하는 데이터를 추출해서 포맷팅.
                    <span>{{ comment.comment_created_at }}</span> # {{ for문 인덱스.원하는데이터 }}
                </p>
                <p style="display: flex; flex-direction: row; padding: 10px; justify-content: space-around;">
                    {% if comment.comment_image %}
                        <img src="{{ comment.comment_image.url }}" alt="comment_image" #이미지의 경우엔 뒤에 .url을 붙여줌. 이미지 이름만 나오면 안되고 이미지 이름이 곧 이미지의 url이라고 알려줘야함.
                             style="height : 130px ; margin-left:20px">
                    {% else %}
                        <img src="{% static 'admin/img/defaultimg.png' %}" # DB에서 이미지가 잘 꺼내진다면 if문처럼 보여주지만 잘 안꺼내진다면 static/admin/img에 넣어 놓은 디폴트 이미지를 출력한다.
                             alt="default Image" style="height : 130px; margin-left:20px">
                    {% endif %}
                    <span>{{ comment.comment_content }}</span>
                </p>
            </div>
        {% endfor %}
    {% endif %}
</div>
</body>
</html>

일단은 CSS를 인라인으로 처리 ㅋㅋㅋㅋㅋㅋㅋㅋ static에서 이미지가 왔다갔다 하기 때문에 뭔가 에러날 것 같은 느낌이 싫어서 작업이 다 끝나면 CSS 파일로 분리할 예정이다.

 

이렇게 이미지를 화면에 보여주면!

ㅎㅎㅎㅎ 실행되고 나서 너무 기뻤다.

앱폴더의 urls.py에서 django에게 끌어올 이미지 파일 경로 알려주는 작업이

제일 포인트였다.

포인트를 알려주신 창호튜터님께 무한 감사를...

 

이제 CSS 작업이 남았다.