티스토리 뷰
[Django] 정적파일(STATIC_JavaScript, CSS, IMAGE) 넣기+ImageField(pillow, imagekit)
HAN_PY 2020. 7. 2. 13:53정적파일을 불러 오려면 static폴더를 만들어서 관리하고 가져온다.
templates에서 정적파일을 모아서 폴더를 만들듯이 app 안에다가 app에서 쓸 정적파일을 모아주면, django가 알아서 가지고 온다.
파일 위치
app_name/static/app_name/images/jpg파일.jpg
app_name/static/app_name/stylesheets/style.css
+ app_name의 static안에 다시 app_name을적어주는 이유는다른앱과 이름이 중복되는 것을 막기 위함이다.
base.html 코드
이부분에 아래 처럼 작성하면 base.html 이므로 전체 css 적용이나 전체 jpg 이미지 파일을 적용 할 때 이렇게 작성한다.
{% load static %}
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="{% static 'app_name/stylesheets/style.css' %}">
{% block css %}
{% endblock %}
</head>
<body>
<img src="{% static 'app_name/images/jpg이름.jpg' %}">
{% block body %}
{% endblock %}
</body>
</html>
반드시 상단에 {% load static %}을 해줘야 한다.(static을 사욜 할 떄 필수적인 거다.)
위와 같이 적으면 base.html이기 때문에 전체 페이지에
style.css에 있는 css들이 불러와 지고,
이미지도 전체 페이지에 불러와 지는거다.
각각의 html에서 따로 부르기 위해서는
각각의 html가서 각각의 css와 body block 사이에 위에 적은 <link rel="stylesheet" href="{% static 'app_name/stylesheets/style.css' %}"> 나 <img src="{% static 'app_name/images/jpg이름.jpg' %}">이걸 적어주면 된다. 이떼 주의 할 점은 {% load static %}은 필수적으로 적어줘야 한다. 그리고 {% load static %}는 {% extends 'base.html' %}바로 아래에 적어 줘야 한다. 위에 적으면 오류뜬다.
app말고 전체 적용시 부트스트렙 사용하기
bootstrap 공식문서 들어가서 다운로드누르면
https://getbootstrap.com/docs/4.5/getting-started/download/
위와 같은 싸이트에 접속이 가능하다. 만들어져 있는 css를 사용하는 것이다. 아래의 설명을 보니, jQuert와 popper.js는 없다고 하니 앞에서 한 CDN은 유지해 준다.
다운받은 후 열어보면 여러 css와 js가 있다. 종류는 세가지로 '전체쓰기, grid만 쓰기, 리부트만쓰기'로 세가지가 있는데 우리는 전체쓰기(bootstrap.min.css, bootstrap.min.js)파일 두개만 쓴다.
이제 base.html이 포함되어있는 templates가 있는 위치와 같은 곳에 static을 만들고 아래와 같이 만들고 위 파일들을 드레그해서 넣어주자
static/bootstrap/bootstrap.min.css
static/bootstrap/bootstrap.min.js
장고에서 어떻게 설정해야하는지 아래의 공식문서를 확인해보자
https://docs.djangoproject.com/en/2.2/howto/static-files/
우선 settings.py 아래 부분에
# serving 되는 URL 앞에 붙이는 설정
STATIC_URL = '/static/'
#app디렉토리가 아닌 내가 만듵 static 폴더로의 지정
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
마지막으로 html 추가한다.
head 부분
<link rel="stylesheet" href="{% static 'bootstrap/bootstrap.min.css' %}">
body 부분
<script src="{% static 'bootstrap/bootstrap.min.js' %}"></script>
Image Field
위에서 배운 image 넣는 것은 일반 배경화면이나 저장한 것들 쓰는 부분에 대해 배웠다면, 지금 부터 배울 내용은 DB에 저장되어 models.py에 같이 포함 될 내용들을 배울 것이다.
이 부분에서는 크게 2가지를 배운다. 첫번째는 이미지를 업로드 하기 위한 pillow에 대해 배우고, 두번째는 이미지를 잘라서 내부적 기준에 맞게 저장하기 위한 django-imagekit에 대해 배울 것이다.
순차적으로 아래를 읽어면서 이해해 보자.
1. Pillow
썸네일(thumbnail)도 아래의 방법과 동일하다. 여기서는 새글쓰기부분에 이미지 파일을 넣을수 있게 하자.
1. 설치
공식문서) ImageField
Pillow library가 필요하다.
pip install pillow 를 설치하자.
2. column 추가(articles/models.py/class Article)
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
image = models.ImageField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
만약 DB가 있는 상태에서 image = models.ImageField()를 추가한 후에 migrations를 하면
1) Provide a onn-off default now~~~
2) Quit, and let me add a default in models.py
select an option:
이라고 뜬다. 즉, 디폴트 값 없이 NOT NULL image를 생성하려하는거다. 쉽게 말하면 열이 하나 추가 되는데 기존에 있던 값들에 열이 추가가 되므로 어떤값을 넣을지 물어보는거다. 왜냐하면 빈칸을 허용하지 않기 때문이다.
그러면 타이핑을 1이나 2를 친 후 엔터를 해주면 된다.
만약, 1 을 치고 엔터를 치면 지금 설정하는 것이기 때문에 python console 환경으로 넘어간다.
2를 치면 끄고 다시 설정을 하는것이다. 이경우는 image = models.ImageField(blank=True)로 바꿔주고 다시 마이그래이션을 하면 된다.
초보자 기준으로는 그냥 기존의 DB를 다 지우기 위해 db.sqlite3과 migrations파일을 지우고 다시 migration을 하는게 빠르다.
이렇게 model에 추가 필드를 정의했다. 그런데 서버를 키고 글쓰기를 해보니, title과 content만 있고 image 넣는게 없다. 이 경우에는 우리는 모델폼을 쓰고 있기 때문에 html에서 고치는게 아니라 forms.py에 가서 고쳐야한다.
3. forms.py
fomrs.py에서 field에 image를 추가하자
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
field = ['title', 'content', 'image']
from django import forms
from .models import Article, Comment
class ArticleForm(forms.ModelForm):
class Meta:
model = Article
fields = ['title', 'content', 'image']
class CommentForm(forms.ModelForm):
content = forms.CharField(
label='댓글',
widget=forms.Textarea(
attrs={
'rows': 2,
}
)
)
class Meta:
model = Comment
fields = ['content']
위와 같이 image를 추가해 준다. 그러면 새글쓰기 시 파일을 넣는 '파일선택'이라는 것이 생성된다. 그렇다면 파일을 넣어서 제출을 눌러보면, 파일을 넣었음에도 필수항목이라 뜨고 안 넣은 것 처럼 다음 으로 안 넘어간다.
문제해결) 문제를 해결하기 위해서는 form tag의 특징을 알아야 한다. 디버깅 page의 Request impormation을 보면, USER, GET, POST, FILES, COOKIES... 등등이 있다. 이떄까지 우리는 정보를 POST를 이용해서 넘겼다. 현재 image도 POST로 넘기고 있다. 우리는 image를 POST가 아닌 Files로 넘겨야 한다.
즉, text를 제외한 다른 것들(파일 등등)을 받기 위해서는 다른 조건을 추가해서 POST가 아니라 Files로 받아야한다.
4. POST -> FILES 로 변경하여 받기
4-1. <form action="" method="POST" enctype="multipart/form-data">
form에 enctype="multipart/form-data"부분을 추가로 넣어주면 image가 POST가 아닌 FILES로 넘어간다.
즉, input type이 files로 바뀐다.
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block body %}
<!--
분기의 기준은 url_name이다.
path로 하면, url이 바뀔 때마다 바꿔줘야한다.
-->
{% if request.resolver_match.url_name == 'create' %}
<h2>새 글쓰기</h2>
{% else %}
<h2>수정하기</h2>
{% endif %}
<!-- bootstrap4 활용 -->
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
<!-- input type:submit -->
<button class="btn btn-primary">제출</button>
</form>
<hr>
{% endblock %}
4-2. articles/views.py
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES)
form에 request.FILES를 추가해서 파일 데이터도 당연히 같이 넘겨줘야한다.
@login_required
def create(request):
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES)
if form.is_valid():
article = form.save(commit=False)
article.user = request.user
article.save()
return redirect('articles:detail', article.pk)
else:
form = ArticleForm()
context = {
'form': form
}
return render(request, 'articles/form.html', context)
여기까지 코드를 작성하면, project tree에 내가 저장한 image가 자동으로 들어가서 저장된다. 여기까지가 저장관련 코드였고 이제 출력 관련 코드를 작성해보자.
5. articles/detail.html
이제 출력을 해보자.
<img src="{{ article.image.url }}"> 을 적어주면 된다.
imge 객체 안의 url을 출력하겠다는 의미이다. jpg를 url로 가져가게끔 .url을 붙이는 거다.
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block body %}
<h1>{{ article.pk }} : {{ article.title }}</h1>
<p>{{ article.user.username }}님</p>
<p class="text-secondary">{{ article.created_at }} | 수정: {{ article.updated_at }}</p>
{% if article.user == request.user %}
<div class="text-right">
<form action="{% url 'articles:delete' article.pk %}" method="POST" class="d-inline">
{% csrf_token %}
<button class="btn btn-secondary">삭제</button>
</form>
<a href="{% url 'articles:update' article.pk %}"><button class="btn btn-secondary">수정</button></a>
</div>
{% endif %}
{% with article_like_users=article.like_users.all %}
{% if request.user in article_like_users %}
<a href="{% url 'articles:like' article.pk %}">
<i class="fas fa-heart fa-lg animated delay-1s" style="color: red;"></i>
</a>
{% else %}
<a href="{% url 'articles:like' article.pk %}">
<i class="far fa-heart fa-lg animated infinite bounce delay-1s" style="color: gray;"></i>
</a>
{% endif %}
<p>{{ article_like_users|length }}명이 좋아합니다.</p>
{% endwith %}
<hr>
<p>{{ article.content }}</p>
<img src="{{ article.image.url }}">
<img src="{{ article.image_thumbnail.url }}">
<hr>
{% for comment in article.comment_set.all %}
<li>{{ comment.user.username }} : {{ comment.content }}</li>
{% endfor %}
<hr>
<form action="{% url 'articles:comments_create' article.pk %}" method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<button class="btn btn-primary">작성</button>
</form>
{% endblock %}
대략 위처럼 이런식으로 작성해 주면된다. 그러나 이렇게 작성한다고 해서 image 파일이 보여지는 것이 아니다. 아직도 깨져있다.
공식문서를 보자.
https://docs.djangoproject.com/en/3.0/howto/static-files/
꺠지지 않게 위와 같이 성정이 필요하겠군.
공식문서에 따라 아래의 settings.py와 urls.py를 수정해준다. 즉, MEDIA_ROOT 와 MEDIA_URL을 설정하고 urls.py에 다음의 snippet을 추가해 주자.
6. settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
settings.py 아래 부분에 위의 두줄을 추가한다.
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
저장 경로를 지정한거다. 글을 작성하면 media 폴더 안에 저장이 된다. 즉, 사진을 저장하면 자동으로 media 폴더가 생기고 그 안에 사진이 저장된다.
MEDIA_URL = '/media/'
파일들을 사용할 수 있게 경로를 설정해 준다.
추가)STATIC_URL과 MEDIA_URL을 비교
STATIC_URL - css, JS, image
MEDIA_URL - 유저가 업로드한 파일(image)
추가) 만약 이름이 같은 이미지 파일을 올린다면?
자동으로 뒤에 랜덤 이름 을 붙여준다. 2번 파일을 올린다면
media/2.jpg
media/2_AimcinEa.jpg
위와같이 랜덤으로 붙여준다.
7. project/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('articles/', include('articles.urls')),
path('accounts/', include('accounts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns 뒤에 + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)를 붙여 준다.
url에 갈 떄 그림도 같이 넘겨 준다는 의미이다.
2. djang-imagekit
https://github.com/matthewwithanm/django-imagekit
위의 공식문서를 참고하자.
1. 설치
pip install pilkit django-imagekit
(2개를 동시에 설치하자.)
2. models.py
공식문서를 참고해서 정리해 보면
- 원본만 저장하고 잘려진걸로 표현하기 => ImageSpaceField + Source처리
- 원본이 아닌 잘려진 것을 저장하기 => ProcessedImageField + upload_to 처리
우리는 imageField를 정리해 보자.
articles/models.py
from django.db import models
from django.conf import settings
from imagekit.models import ImageSpecField, ProcessedImageField
from imagekit.processors import ResizeToFill, ResizeToFit, Thumbnail
# ResizeToFill : 300*300 자르는 것(crop)
# ResizeToFit : 가장 긴 곳(너비/높이)을 300으로 맞추고, 비율에 맞춰서
# Create your models here.
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
# 원본 저장 + 자른 것을 활용 : ImageSpecField + source
image = models.ImageField()
# DB 저장 X, 호출하게되면, 잘라서 표현
image_thumbnail = ImageSpecField(source='image',
processors=[Thumbnail(300, 300)],
format='JPEG',
options={'quality': 60})
# 원본 잘라서 저장 : ProcessedImageField
# image = ProcessedImageField(
# processors=[ResizeToFill(100, 50)],
# format='JPEG',
# options={'quality': 60})
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL,
image_thumbnail = ImageSpecField(source='image',
processors=[Thumbnail(300, 300)],
format='JPEG',
option={'quality':60} )
이부분에 대해 코드 해석을 해보려고 한다. 기본적으로 DB에 저장이 아니라 Article의 맴버변수로 호출하면 위와 같이 잘라서 표현한다. 추가 후 migration할 필요도 없다
image_thumbnail = ImageSpecField(source='image',
image_thumbnail에서 image부분은 title이나 content 로바꿔도 된다.
source는 어떤 이미지를 자를지를 적는 부분이다.
이부분을 쓸 때는 ImageSpecField를 import 해줘야한다.
processors=[Thumbnail(300, 300)],
이 부분은 어떤 방식으로 자를지에 대한 부분이다. 이 부분은 Thumbnail 말고도
['Resize', 'ResizeToCover', 'ResizeToFill', 'SmartResize', 'ResizeCanvas', 'AddBorder', 'ResizeToFit', 'Thumbnail']
이와 같이 다양한 종류가 있다. 그 중 자주쓰는 것을 보면,
ResizeToFill(300, 300) | 300, 300 크기로 중앙기준으로 자른다. 그냥 크기에 맞게 자른다고 보면된다 |
ResizeToFit(300, 300) | 잘려지는게 아니고 가장 긴 곳(너비나 높이)을 300으로 맞추고 비율에 맞게 크기를 변환한다. 가로 세로 중 긴 것을 맞추고 나머지는 비율을 유지하면서 크기를 변환시킨다 |
Thumbnail | 쉽게 말해서 내부적기준으로 자동으로 깔끔하게 잘라준다. |
그리고 위의 공식문서를 참고하면 imagekit/processors/resize.py를 참고하면 된다. 이 떄 각각 쓰는 것을
from imagekit.processors import ResizeToFill 과 같이 import를 해줘야한다.
format='JPEG',
어떤 형식으로 반환할지를 적는 곳이다. 여기서는 JPEG로 반환한다.
option={'quality':60} )
JPEG에 60% 퀄리티로 반환하겠다는 이야기다.
3. articles/detail.html
코드 예시는 위의 pillow부분의 코드에서 detail부분을 참고하자
<img src="{{ article.image_thumbnail.url }}">
이 코드를 적어주면 이미지가 자동으로 짤려 있다.
4. articles/views.py
@login_required
def update(request, pk):
# 수정시에는 해당 article 인스턴스를 넘겨줘야한다!
article = get_object_or_404(Article, pk=pk)
if request.user == article.user:
if request.method == 'POST':
form = ArticleForm(request.POST, request.FILES, instance=article)
if form.is_valid():
article = form.save(commit=False)
article.user = request.user
article.save()
return redirect('articles:detail', article.pk)
else:
form = ArticleForm(instance=article)
context = {
'form': form
}
return render(request, 'articles/form.html', context)
else:
# 1. 메시지프레임워크를 사용하여, 메인페이지로 이동.
messages.warning(request, '본인 글만 수정 가능합니다.')
return redirect('articles:index')
# 2. 403 status code를 반환.
# return HttpResponseForbidden()
form = ArticleForm(request.POST, instance=article)을
form = ArticleForm(request.POST, request.FILES, instance=article)
으로 바꿔주면 된다.
'Web > Django' 카테고리의 다른 글
[Django] 프로필 이미지 구현_gravatar (python코드의 확장) (0) | 2020.07.08 |
---|---|
[Django] bootstrap (0) | 2020.07.06 |
[django] CRUD 한번에 구현하기 (0) | 2020.06.30 |
[Django] Model 기초 (0) | 2020.06.28 |
[Django] Migration (0) | 2020.06.26 |
- Total
- Today
- Yesterday
- 클라우데라
- Express
- typescript
- Python
- BFS
- read_csv
- useState
- error:0308010C:digital envelope routines::unsupported
- UserCreationForm
- NextJS
- vuejs
- Deque
- useHistory 안됨
- pandas
- 자연어처리
- Vue
- mongoDB
- react
- Queue
- django
- nextjs autoFocus
- logout
- next.config.js
- react autoFocus
- nodejs
- 자료구조
- login
- JavaScript
- TensorFlow
- DFS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |