www.youtube.com/watch?v=aNk2CAkHvlE&list=PL-51WBLyFTg2vW-_6XBoUpE7vpmoR3ztO&index=17
이 포스팅은 다음과 같은 youtube 영상을 따라하면서 배운 내용과 학습 내용을 담은 것이다:
- Dennis Ivy - Image File Upload to User Profile Model | Django Framework (3.0) Crash Course Tutorials (pt 14)
현재 Django Version 3.1.2 를 쓰고 있다. Django는 파이썬을 쓰는 오픈소스 웹 프레임워크이다. 웹사이트 만들때 주로 쓰며, 간단한 것이 큰 특징이다.
※ Settings.py 설정하기
현재 settings.py는 다음과 같이 설정되어 있을 것이다:
# settings.py
...
STATIC_URL = '/static/'
STATICFILES_DIRS = [
BASE_DIR / "static"
]
여기에 다음과 같이 추가한다:
# settings.py
...
STATIC_URL = '/static/'
MEDIA_URL = '/images/'
STATICFILES_DIRS = [
BASE_DIR / "static"
]
MEDIA_ROOT = BASE_DIR / 'static/images'
(참고로 많은 Django Tutorial 영상들에서는 MEDIA_ROOT = os.path.join(BASE_DIR, 'static/images') 이런식으로 소개를 할 것이다. 이를 하기 위해서는 settings.py에 import os 라는 것을 추가해주어야 하며, 현재의 Django 3.1 version은 그러한 방식으로 하라고 하지 않기 때문에 이 문서를 참고하여 하게 되었다.)
일단 우리의 폴더 구조를 다시 한번 보여주고 설명하도록 하겠다:
crm1
ㄴ accounts
ㄴtemplates
ㄴaccounts
ㄴ __pycyache__
ㄴ migrations
ㄴ __init__.py
ㄴ admin.py
ㄴ apps.py
ㄴ models.py
ㄴ tests.py
ㄴ views.py
ㄴ forms.py
ㄴ filters.py
ㄴ decorators.py
ㄴ cmr1
ㄴstatic
ㄴcss
ㄴimages
ㄴjs
ㄴ db.sqlite3
ㄴ manage.py
static 이라는 폴더 안에 images라는 폴더가 존재한다. 이 images 안에는 우리가 전에 Dennis Ivy의 logo를 넣은 바가 있다. 하지만 이제 우리가 새로운 사진들을 업로드하고 이를 불러올 것이라면, settings.py를 바꿔주면서 다른 방식을 적용해야 한다. 따라서 settings.py를 위와 같이 바꾼 것이다.
MEDIA_URL = '/images/'는 MEDIA_ROOT의 미디어들을 다뤄주는 URL이고, MEDIA_ROOT는 user가 업로드하는 파일들을 저장하는 장소이다.
이제는, 우리가 프로젝트를 만들면서 처음에 생긴 urls.py를 다음과 같이 추가해준다:
# crm1/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('accounts.urls')),
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)를 통해 Django가 우리가 업로드하는 media들을 어디서 찾아야 할지 가르쳐 준다.
※ models.py 설정하기
이제는 user마다 프로필 사진을 갖게 하기 위해서는, 우선적으로 가입할 때 기본 프로필을 제공을 해주어야 한다. 따라서, Customer class에 다음과 같이 추가한다:
# models.py
from django.db import models
from django.contrib.auth.models import User
...
class Customer(models.Model):
user = models.OneToOneField(User, null=True, blank=True, on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True)
phone = models.CharField(max_length=200, null=True)
email = models.CharField(max_length=200, null=True)
profile_pic = models.ImageField(default="profile1.png", null=True, blank=True)
date_created = models.DateTimeField(auto_now_add=True, null=True)
def __str__(self):
return self.name
...
profile_pic이라는 field를 추가하면서 ImageField를 넣어주고, default="profile1.png"로 설정해준다. 이 뜻은 이 사진이 들어가야 될 구역에 profile1.png가 기본 프로필 사진으로 설정이 된다는 뜻이다. 따라서, 우리가 설정해놓은 profile1.png는, 우리가 MEDIA_ROOT으로 설정해놓은 static/images 안에 이미 들어가있어야 이 profile1.png를 불러와서 기본 프로필 사진으로 설정을 해줄 수 있다.
crm1
ㄴ accounts
ㄴ cmr1
ㄴstatic
ㄴcss
ㄴimages
ㄴlogo.png
ㄴprofile1.png
ㄴjs
ㄴ db.sqlite3
ㄴ manage.py
※ forms.py / views.py 설정하기
기본적으로 우리가 만들 account_settings.html은, 계정의 사진 등을 계속 업데이트 시켜줄 수 있는 기능이 있을 것이다. 따라서 이는 form을 불러오게 해야하는데, 이제 우리는 account_settings.html에 적용할 custom form을 만들어줘야 한다. forms.py에 다음을 추가해준다:
# forms.py
from django.forms import ModelForm
from django.contrib.auth.forms import UserCreationForm
from django import forms
from django.contrib.auth.models import User
from .models import *
class CustomerForm(ModelForm):
class Meta:
model = Customer
fields = '__all__'
exclude = ['user']
...
from django.contrib.auth.models import User를 추가해줘야 한다. 이는 exclude = ['user']에서 볼 수 있듯이, 우리가 account_settings.html에 보여줄 field 중에는 user이라는 field를 보여주지 않을 것이기 때문이다. 우리는 단지 user field를 제외한 Customer class의 다른 field들을 보여주려고 하기 때문이다. (이렇게 하게 되면 date_created 필드 값은 보이지 않게 되는데 이유는 date_created는 단순히 우리가 Customer을 추가하면서 만들어준 내재된 값이기 때문이다. 우리가 이를 바꿀 수 있는 것은 없기 때문이다.)
forms.py 설정을 마쳤으니, 이제는 views.py를 설정해주어야 한다:
# views.py
...
from .forms import OrderForm, CreateUserForm, CustomerForm
...
@login_required(login_url='login')
@allowed_users(allowed_roles=['customer'])
def accountSettings(request):
customer = request.user.customer
form = CustomerForm(instance=customer)
if request.method == 'POST':
form = CustomerForm(request.POST, request.FILES,instance=customer)
if form.is_valid():
form.save()
context = {'form':form}
return render(request, 'accounts/account_settings.html', context)
...
.forms에 CustomerForm도 추가를 해주어야한다. 이 accountSettings 함수도 기본적으로 form을 가진 다른 함수와 비슷하다. 다만 여기서 볼 수 있는 두드러진 차이점은 form = CustomerForm(request.POST, request.FILES, instance=customer) 이다. 여기서 request.FILES는 우리가 이 account_settings.html에는 기본적으로 파일들 (프로필 사진)을 업로드해서 사진을 바꾸는 등의 행위를 할 것이기 때문에 이를 추가해줘야 한다. instance=customer을 해줌으로써, user의 customer를 form에 통과시킨다.
※ account_settings.html 설정해주기
마지막으로 이제 account_settings.html을 설정해줘야 한다. 우선 urls.py에 다음을 추가를 해주고:
# urls.py
...
urlpatterns = [
...
path('account/', views.accountSettings, name="account"),
...
]
account_settings.html을 다음과 같이 추가해준다:
<!-- account_settings.html -->
{% extends 'accounts/main.html' %}
{% load static %}
{% block content %}
<style>
.profile-pic{
max-width: 200px;
max-height:200px;
margin: 0 auto;
border-radius: 50%;
}
</style>
<br>
<div class="row">
<div class="col-md-3">
<div class="card card-body">
<a class="btn btn-warning" href="{% url 'home' %}"> ← Back to Profile</a>
<hr>
<h3 style="text-align: center">Account Settings</h3>
<hr>
<img class="profile-pic" src="{{request.user.customer.profile_pic.url}}">
</div>
</div>
<div class="col-md-9">
<div class="card card-body">
<form method="POST" action="" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}
<input class="btn btn-primary" type="submit" name="Update Information">
</form>
</div>
</div>
</div>
{% endblock %}
여기서 볼 수 있는 점은 세가지가 있는데 첫번째는 src="{{request.user.customer.profile_pic.url}}" 이고, 두번째는 enctype="multipart/form-data", 그리고 마지막 세번째는 {{form.as_p}}이다.
첫번째 src="{{request.user.customer.profile_pic.url}}"는 기본적으로 사진을 어디서 불러올 것인지 알려주는 것이다. request.user은 로그인된 유저를 불러오는 것이고, .customer.profile_pic은 그 유저의 customer정보에 내재된 profil_pic을 불러오면서, .url을 추가함으로써 사진을 url 형태로 불러와 template에 적용을 시켜준다. 만약 .url이 없으면 그 profile_pic이 안보이고 사진 아이콘만 보인다.
두번째 enctype="multipart/form-data"는 우리가 사진이나 파일을 업로드 할 때 필요하다. 따라서 이 template에서 쓰는 특정 form에서 파일들을 업로드하는 것을 사용하게 된다면 필연적으로 넣어줘야 한다.
세번째로는 {{form.as_p}}인데, 이는 각각의 form field와 label을 함께 문단(paragraph, 즉 <p> 태그)으로 감싸서 template에 출력시켜준다. 따라서 훨씬 더 깔끔하고 간결하게 보인다.
※ 주의할 점과 의견
우선 주의할 점부터 얘기하겠다. 이 방법은 절대로 웹사이트에 자신의 project를 올리고 나서는 사용하면 안되는 방법이다. 지금 이렇게 localhost에서 웹사이트를 테스트 하는 용도로는 문제가 없으나, AWS나 GCP 같은 곳에 올리면서는 이 방법을 사용하면 안된다. Production level 에서는 이 방법을 사용하지 않고 다른 방법을 사용한다. 또한, 프로덕션 레벨에서는 DEBUG = True 라고 되어있는 부분을 False로 꺼주어야 한다.
사실 이 강의를 듣고 하면서 헷갈렸던 부분도 너무 많았고 어려웠던 부분이 많았다. 특히 models.py 에서 default="profile1.png"는 맨처음에 내가 "images/profile1.png"로 설정하면서 어디서 틀렸는지 찾아보는 잘못된 상황을 연출하기도 했었다. (틀린 이유는 저렇게 할 경우 profile1.png의 file location이 images/images/profile1.png로 설정된다. 그냥 default=profile1.png로 설정해도 애초에 MEDIA_ROOT=BASE_DIR / 'static/images'로 설정해놔서 images를 가르키고 있기 때문에 상관이 없다.) 헷갈리는 부분이 있으면 명확하게 짚고 넘어가는 것이 이 Django의 큰 구조를 이해하는데 중요한 것 같다.