Messages Framework
1회성 메세지를 담는 용도(한명의 유저에게만)
HttpRequest 인스턴스를 통해 메세지를 남길 수 있음
메세지는 1회 노출이 되고, 사라집니다. 새로고침하면 보여지지 않음.
Ex) “저장되었습니다”, “로그인되었습니다”
Messages Framework는 세션에 저장되는데 이것은 DB에 저장이됨. 이때 세션을 관리하기위해서는 HttpRequest 인스턴스가 필요함(???)
##Message levels를 통한 메세지 분류
메세지를 남길때 내용+레벨을 지정해야함
파이썬 로깅 모듈과 유사. 레벨 별로 필터링이 가능하며, 템플릿에서 다른 스타일로 노출이 가능
Level 종류(장고!!)
- DEBUG: 디폴트 설정 상으로 메세지를 남겨도 무시
- INFO
- SUCCESS
- WARNING
- ERROR
메세지 등록 코드(뷰안에)
해당 뷰 요청을 한 유저에게만 노출할 메세지를 다수 등록, 유저 별로 메세지가 따로 등록
1 | # blog/views.py |
메세지 소비 코드(모든템플릿에)(상속이용하자)
메세지가 등록되고 난 후, 템플릿 렌더링을 통해 메세지 노출
messages context_processors 를 통해 message list에 접근
message.tags속성을 통해 레벨을 제공
message.message속성을 통해 내용을 제공 (= str(message))
# 코드 뜯어보면 템플릿으로 뷰에서 인자 패스안해도 기본적인것가져오는 프로세스 코드있음
1 | <!-- askdjango/templates/layout.html --> #최상위 템플릿 #따라서 모든 템플릿 적용됨 |
아래것을 응용한것이 위에있음
1 | <div class="container"> #박스형식으로 보여주는것 |
참고, Django Context Processor
템플릿에서 쓸 변수목록을 템플릿 렌더링 요청 시에 넘기지 않아도, 독립적 으로 Context Processors 함수가 호출되어, 그 리턴값 (사전타입) 을 모 아서 템플릿에서 참조 디폴트 context_processors : debug, request, auth, messages
1 | # askdjango/settings.py |
Bootstrap Alert 스타일로 메세지 노출하기
- bootstrap alert class-name #ref : alert-info, alert-success, alertwarning, alert-danger
- message tags : debug, info, success, warning, error # 둘이 태그이름다르므로 겹치는걸 써야함.(아니면 아래에 서로 이름 이어주는걸 해주던가)
1 | <!-- askdjango/templates/layout.html --> |
bootstrap 측에 alert-debug 스타일이 없음
없으면 만들면 되지
1 | .alert-debug { |
- Tip: 장고 기본설정으로 debug 메세지는 무시되고 있음. debug level을 쓸려면 아래와 같이 settings.MESSAGE_LEVEL을 변경해줘야함
1 | # askdjango/settings.py |
bootstrap 측은 error가 아닌 danger
error로 지정하지만 message.tags는 danger로 출력토록 설정
1 | # askdjango/settings.py |
django-bootstrap3의 bootstrap_messages Tag
1 | {% raw %} |
편하게쓸수있지만 커스텀힘듬
StaticFiles
Static and Media Files
Static Files : 개발 리소스로서의 정적인 파일 (js, css, image, etc)
- 앱 단위로 저장/서빙
- 프로젝트 단위로 저장/서빙 (특정앱에 속하지않고 전반적으로 쓰임, 별도tree에넣기)
Media Files : 유저가 업로드한 모든 정적인 파일 (image, etc)
- 프로젝트 단위로 저장/서빙 (settings.MEDIA_ROOT 미디어 모든파일저장됨)
Static Files(3가지꼭알기)
관련 settings 예시
1 | # askdjango/settings.py |
Static Files Finders
Template Loader와 유사
다수 디렉토리 목록에서 지정 상대경로를 가지는 Static파일을 찾음
- AppDirectoriesFinder : “앱/static” 경로를 추가
- FileSystemFinder : settings.STATICFILES_DIRS=[] 의 경로를 추 가
위 Finder를 통해, Static 파일이 있을 후보 디렉토리 리스트를 작성합니 다. 이는 장고 서버 초기 시작시에만 1회 작성됩니다
후보 디렉토리 예시
1) blog/static
2) askdjango/static
네임 스페이스 개념으로 써줘야 같은 이름파일도 구별가능
Template Loader 와 비교
• app_directories.Loader : “앱/templates” 경로
• filesystem.Loader : settings.TEMPLATES 애 DIRS=[] 의 경로
템플릿 내에서 각 static 파일 URL처리
방법1) 수동으로 settings.STATIC_URL Prefix 붙이기 (BAD)
<img src=”/static/blog/title.png?” />
경고 : settings.STATIC_URL 설정은 언제라도 변경될 수 있음 />
방법2) __Template Tag를 통해 Prefix 붙이기 (GOOD) __ (장고태그기능)
{% load static %} \tip 프로젝트는 경로 추가해주고, 앱은 static디렉만들고 blog디렉안에 만들고 그안에 파일넣기
예시) 스타일을 템플릿에서 빼고
1 | <link rel="stylesheet" href="/static/style.css"> |
해서 불러옴
개발환경에서의 static 파일 서빙
- 개발서버(python3 manage.py runserver가 개발서버임) 를 통해 settings.DEBUG = True 설정에 한해 서빙 지원
- 원래는 urls.py에 없어도 가능
- 프로젝트/urls.py 에 Rule이 명시되어 있지 않아도 자동 Rule 추가
- 장고를 통해 직접 static파일 서빙하는 것은 개발목적으로만 제공
- 즉 실제 서비스는 따로 둬야함, 정적파일은 따로관리
자동 static serve가 수행되지 않을 때에도 장고를 통해 static serve를 수행할려면? (비추)
settings.DEBUG=False 이거나 개발서버를 쓰지않는 경우 django.views.static.serve 뷰에 대한 URL Rule을 명시
1 | # askdjango/urls.py |
실 서비스 배포 전에 collectstatic
실서비스 정적파일 서빙은 외부 웹서버 권장 (nginx/apache/S3/CDN)
여러 디렉토리에 나눠 저장된 static 파일들의 위치는 “현재 장고 프로젝 트” 만이 알고 있음. 외부 웹서버는 전혀 알지 못함.
외부 웹서버에서 Finder의 도움없이도 static파일 서빙을 하기위해, Finder를 활용하여 한 디렉토리 (settings.STATIC_ROOT) 로 파일들을 복사 => 복사된 파일을 서빙하면 Finder가 필요없다
1 | 외부에서는 /static/style.css , /static/blog/style.css는 같은 디렉토리로 판단됨. |
외부 웹서버에 의한 static/ media 컨텐츠 서비스
정적인 컨텐츠는, 외부 웹서버에서 직접 응 답을 하면, 더 빠른 응답
WAS (Django, RoR, Node) 보다 빠 르고 효율적으로 동작
정적 컨텐츠와 동적 컨텐츠 분리를 통해, 그 에 맞는 최적화 방법을 사용 memcache/redis 캐시 등
장고로 안거치고 바로 서버가 파일줌, 아니면 장고로 넘김
![스크린샷 2019-11-14 오후 6.10.37](/Users/spicyhoro/Desktop/스크린샷 2019-11-14 오후 6.10.37.png)
static 파일 모으는 순서, 적용순서
- 먼저 python manage.py collectstatic 명령을 통해, settings.STATIC_ROOT 경로 아래로 Static 파일들을 모두 복사
- settings.STATIC_ROOT 경로에 모아진 파일을 배포서버로 복사
- settings.STATIC_URL 설정이 배포서버를 가르키도록 수정
- 실서비스 static 서빙 웹서버 설정 (nginx/apache/S3/CDN 등)
nginx 웹서버에서의 static 서빙 설정 SIMPLE 예시(원리)
1 | server { |
Media Files
뷰에는 HttpRequest.FILES를 통해 파일이 전달되고,
뷰에서는 이를 적절 히 검증한 후, settings.MEDIA_ROOT 디렉토리 하단에 저장
1 | # askdjango/settings.py |
FileField/ImageField 파일저장을 지원하는 모델필드
파일은 settings.MEDIA_ROOT경로에 실제파일 저장하며,
DB필드에는 settings.MEDIA_ROOT내 저장된 하위 경로(문자열)를 저장
1 | # blog/models.py |
__Tip. __
File Storage API (디폴트: FileSystemStorage)를 통해 파일을 저장 해당 필드를 옵션필드로 두고자 할 경우, blank=True옵션 적용
Tip2.
ImageField (FileField 상속)
필요!! Pillow 이미지 처리 라이브러리 를 통해 이미지 width/height 획득
개발환경에서의 media 파일 서빙
- static files와 다르게 개발서버에서 서빙 미지원
- 개발 편의성 목적으로 직접 서빙 Rule 추가 가
1 | # askdjango/urls.py |
참고) 파일 업로드 시의 form enctype
- form method는 필히 POST : GET method는 enctype이 “application/x-www-form-urlencoded”로 강제됨
- form enctype은 필히 “multipart/form-data” : “application/xwww-form-urlencoded”로 지정될 경우, 파일명만 전송(>디폴트값임)
1 | <form action="" method="post" enctype="multipart/form-data"> |
Tip: 만약 업로드가 안된다면 둘중하나 multipart/form-data로 지정하고 뷰에 request.FILES로 해당 폼이 request.POST와 request.FILES를 받는지 확인
파일 저장경로
디폴트 설정으로 업로드된 파일은 “settings.MEDIA_ROOT/파일명”경 로에 저장되며, DB에는 “파일명”만 저장
ex) travel-20170131.jpg 파일을 업로드할 경우
- MEDIA_ROOT/travel-20170131.jpg 경로에 저장되며
- DB필드에는 “travel-20170131.jpg” 문자열 저장
파일 저장경로 커스텀 by upload_to
각 필드 별로 다른 디렉토리 저장경로를 가지게 하기
한 디렉토리에 파일들을 너무 많이 몰아둘 경우, OS 파일찾기 성능 저하 디렉토리 Depth가 깊어지는 것은 성능에 큰 영향이 없음
업로드가될때 upload_to경로가 참고되므로 바꾼다고 원래 경로 디렉토리이름은 안바뀜
대책1) 필드 별로 다른 디렉토리에 저장(부실함)(upload_to)
대책2) 업로드 시간대별로 다른 디렉토리에 저장 (upload_to)
예시) Post에
1 | # upload_to 경로입력에 맨앞뒤의 '/'는 뺀다 |
travel-20170131.jpg 파일을 업로드할 경우
photo = models.ImageField(upload_to=”blog”)
MEDIA_ROOT/blog/travel-20170131.jpg 경로에 저장
DB필드에는 “blog/travel-20170131.jpg” 문자열 저장
photo = models.ImageField(upload_to=”blog/photo”)
MEDIA_ROOT/blog/photo/travel-20170131.jpg 경로에 저장
DB필드에는 “blog/photo/travel-20170131.jpg” 문자열 저장
strftime 포맷팅 지원
업로드 당시의 시각을 디렉토리 경로에 쓸 수 있음.
photo = models.ImageField(upload_to=”blog/%Y/%m/%d”)
업로드 시각이 2017년 2월 1일이라면
- MEDIA_ROOT/blog/2017/02/01/travel-20170131.jpg 경로
- DB필드에는 “blog/2017/02/01/travel-20170131.jpg”
템플릿 내에서 각 media 파일 URL처리
방법1) 수동으로 settings.MEDIA_URL Prefix 붙이기 (BAD)
1 | <img src="/media/blog/2017/02/01/travel-20170131.jpg" /> |
경고 : settings.MEDIA_URL 설정은 언제라도 변경될 수 있음 (post.photo로경로확인가능)
방법2) FileField/ImageField의 .url 속성 (BAD)
1 | <img src="{{ post.photo.url }}" %}" /> |
Tip) .path속성은 파일시스템 상의 절대경로
- settings.MEDIA_ROOT가 Prefix로 붙음
기타 참고
1 | import re |
참고) File Upload Handler (뷰에전달시)
- 메모리에 담겨 전달 (by MemoryFileUploadHandler)
- 파일크기가 2.5MB (settings.FILE_UPLOAD_MAX_MEMORY_SIZE) 이하일 경우
- 디스크에 담겨 전달 (by TemporaryFileUploadHandler)
- 파일크기가 2.5MB 초과할 경우
이미지 썸네일 만들기
JPEG : 손실압축 포맷. 파일 크기가 작아 웹에서 널리 사용
- 압축률을 높이면 파일크기는 더 작아지나 이미지 품질은 떨어짐 (1
100%, 대개 6080%가 적정선) - 투명채널은 지원되지 않으며, 사진 이미지에 유용
PNG : 투명채널 지원하며, 24비트 색상 (2^24) 지원
- 투명이 필요하거나, 문자가 있는 이미지는 PNG포맷이 유리
GIF: 256색까지 지원. 애니메이션 지원 (움짤)
이미지 용량 줄이기
네트워크 트래픽은 돈
이미지 용량이 크면, 유저 다운로드에도 더 긴 시간이 소요
가급적 JPG 포맷 사용
- 투명채널이 필요하지 않고, 문자가 없는 이미지 (인물/풍경 사진 등)
- 글자가 많은 이미지는 JPG포맷을 쓰면, 글자가 뭉개집니다.
메타데이터(사진정보들)는 이미지 노출과 직접적인 관련성이 없으므로, 제거
서비스에 필요한 이미지 크기로 리사이징
파이썬 이미지 처리 라이브러리
PIL (Python Image Library) : 마지막 업데이트 2009
Pillow : PIL fork (많이사용)
- 기존 PIL 활용코드를 그대로 쓸 수 있습니다.
PILKit : PIL 유틸리티 라이브러리(많이사용)(pip3 install pilkit)
Wand : ImageMagick 파이썬 바인딩
다양한 processors 지원
- ProcessorPipeline, Adjust, Reflection, Transpose, Anchor, MakeOpaque
- TrimBorderColor, Crop, SmartCrop
- ColorOverlay
- Resize, ResizeToCover, ResizeToFill, SmartResize, ResizeCanvas, AddBorder, ResizeToFit, Thumbnail
requests를 이용하여, 이미지 다운받기
설치 : pip install requests
1
2
3
4
5import requests
img_binary = requests.get("이미지URL").content
with open("저장할파일경로" , "wb") as f:
f.write(img_binary)
Tip: pip3 install “ipython[notebook]”하면 쥬피터랑같이설치됨
[PILKit] Thumbnail (썸내일 사진 만들기)
1 | from PIL import Image |
즉, 링크만가지고 받아서 썸네일로 변경후 저장가능
ImageField 썸네일
##이미지 썸네일 Helper 장고 앱. 몇가지
- Django-imagekit
- Sorl-thumbnail
- Easy-thumbnails
Django-imagekit 설치
1 | pip3 install pillow |
- settings.INSTALLED_APPS에 “imagekit” 추가
- 실제 이미지 처리 시에는 PILKit가 사용됩니다.
활용1. 원본 ImageField로부터 썸네일 Field생성
ImageSpecField
- url, path, width, height 속성 지원
- Form에서는 렌더링되지 않음
1 | from django.db import models |
활용2 : 원본 이미지를 유지 X, 썸네일 Field만 저장
1 | from django.db import models |
템플릿에서 직접 이미지 썸네일 처리하기
기본 지원 Generator : “imagekit:thumbnail”
(장고 이미지 킷에서만 쓰는 용어)
여러가지 크기의 썸네일필요하면 이 방법이 최적임
따로 모델건드릴필요없음
- width (int) : thumbnail 가로크기 지정
- height (int) : thumbnail 세로 크기 지정
- crop (bool, 디폴트 True) : 이미지가 클 경우, Crop 여부
- upscale (bool, 디폴트 True) : 이미지가 작을 경우, 이미지 확대 여부
1 | blog/models.py |
thumbnail Template Tag 활용
템플릿 최상단에 꼭 __{% load imagekit %}__
1 | {% raw %} |
generateimage Template Tag 활용
1 | {% raw %} |
FormField Widget
Widget
UI 입력 요소
class Widget(attrs=None)
- Attires : 해당 tag의 property 지정
- Self.attrs를 통해 접근가능
1 | >>> from django import forms |
built-in widgets(다레이어존재)
텍스트 입력 : TextInput, NumberInput, EmailInput, URLInput, PasswordInput, HiddenInput, DateInput, DateTimeInput, TimeInput, Textarea (다들각자맞는제약존재)
select/콤보박스 : CheckboxInput, Select, NullBooleanSelect, SelectMultiple, RadioSelect, CheckboxSelectMultiple
File upload : FileInput, ClearableFileInput
Composite : MultipleHiddenInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, SelectDateWidget
위젯 지정
- Widget클래스 혹은 Widget인스턴스로 지정
1 | # 방법1) Form Field 정의 시에 widget 을 통해 지정 |
커스텀 위젯
위젯을 변경한다고 해서, 서버로 전달되는 값이 변경되는 것은 아님. 단지 유저에게 UI편의성을 제공
만드는 법
django.forms.Widget.render 멤버함수 재정의를 통해, 추가
HTML/CSS/JavaScript를 정의 (render의 역할은 html형식으로 바꿔줌,유저보여줌)
ex) forms.TextInput 이나 forms.HiddenInput 기반에서 네이버 맵 위젯, 다음 맵 위젯, 구글 맵 위젯
네이버맵 경도/위도 입력위젯 - NaverMapPointWidget
(커스텀위젯 직접만들기)(이 단원은 api적용하므로 어렵더라,,넘어가자일단.)
1 | import re |
Tip: 프로젝트의 모든 디렉토리(.py파일있는)는 무조건 __init__.py가 있어야함.
이과정에서 forms.ModelForm으로 폼을 만들었으니까 class Meta: 아래에 widget에 추가
원래 form에다가 필드 생성하면 해당 위젯의 기본값도 들어가있는데 이 기본값을 오버라이딩해서 지도 등을 추가하는 과정
Widget.py에다가 html까지쓰는건 비효율적이므로 분리추천.
알아둬야할것은 html은 문자열 render()는 HttpResponse인스턴스반환
html 에서는 id값은 유니크해야한다 두개면 하나만 출력된다
넘어가자!
Class Based View (CBV)
뷰의 정체는 호출가능한 객체 (Callable Object)
CBV의 정체는 __FBV를 만들어주는 클래스 __
as_view 클래스함수를 통해 함수뷰를 생성 !!!!
장고 기본 CBV 팩키지 위치 : django.views.generic
CBV는 범용적 (generic) 으로 쓸 뷰 모아놓음
step4를 이해하기위해 1~4까지 진행하자
Tip: get_object_or_404는
1
2
3
4#try:
# post = Post.objects.get(id=id)
#except Post.DoesNotExist:
# raise Http404 이코드와같다
샘플 STEP1 - FBV버전
1 | # myapp/views_fbv.py |
샘플 STEP2 - 함수를 통해, 뷰 생성 버전
# 중요한점은 generate_view_fn()이 호출될때마다 아래 view_fn이 새롭게 정의된다.
따라서 아래처럼 코딩하면 post_detail= view_fn(새로작성된) post_detail가 이제는 post_detail(request, id)를 받으면똑같이 행동
1 | def generate_view_fn(model): #모델클래스를 받는다 |
샘플 STEP3 - CBV형태로 컨셉만 구현한 버전
1 | # myapp/views_cbv.py |
Tip: arg는 불특정 다수의 positional argu *하나는 튜플형태로받음 *는 사전형태
**kwargs는 불특정 다수의 keyword argu 받을수있다
샘플 STEP4 - 장고 기본 제공 DetailView CBV 쓰기
1 | # myapp/views_cbv2.py |
샘플 urls.py
1 | # myapp/urls.py |
CBV는 구현은 복잡해도, 가져다 쓸 때는 SO SIMPLE !!!
그렇다고해서 CBV를 통한 뷰코드가 모두 심플해지는 것은 아님.
이미 CBV에서 정해둔 시나리오를 따를 때에만 SIMPLE
그 길을 벗어날 때에는 복잡해질 여지가 많습니다.
CBV 만능주의 금물. FBV와 적절히 섞어쓰세요. CBV틀을 벗어나는 구 현은 FBV가 훨씬 간단
함수로 구현하는 것이 재사용성은 낮지만, 로직 구현 복잡도도 낮음.
기본 제공 CBV
Generic Display Views
- ListView, DetailView
Generic Editing Views
- FormView, CreateView, UpdateView, DeleteView
Generic Date Views
- ArchiveIndexView, YearArchiveView, MonthArchiveView, WeekArchiveView, DayArchiveView, TodayArchiveView, DateDetailView
ListView
(get요청)지정 모델에 대한 전체 목록을 조회
paginate_by 옵션 : 페이지당 갯수 지정
디폴트 템플릿 경로 : “앱이름/모델명소문자_list.html”
디폴트 context object name : “모델명소문자_list”
• ex) post_list, comment_list, tag_list
1 | # myapp/views.py |
DetailView
(get요청)지정 pk 혹은 slug의 모델 인스턴스에 대한 Detail
디폴트 템플릿 경로 : “앱이름/모델명소문자_detail.html”
디폴트 context object name : “모델명소문자”
ex) post, comment, tag
1 | # myapp/views.py |
CreateView / UpdateView
[GET/POST 요청] ModelForm 을 통해 Model Instance를 생성/수정
- model 옵션 (필수)
- form_class 옵션 : 미제공시, ModelForm 생성/적용
- fields 옵션 : form_class 미제공 시, 지정 field에 대해서만 Form 처리
- __success_url 옵션 : 제공하지 않을 경우, model_instance.get_absolute_url() 획득을 시도 __
- 디폴트 템플릿 경로 : “앱이름/모델명소문자_form.html”
- GET 요청 : 입력 Form을 보여주고, 입력이 완료되면 같은 URL (즉 같은 뷰) 로 POST요청
- POST 요청 : 입력받은 POST데이터에 대해 유효성 검사를 수행하고
- invalid 판정 시 : Error가 있으면 다시 입력Form을 보여줍니다.
- valid 판정 시 : 데이터를 저장하고, success_url 로 이동
베이스 코드
1 | # myapp/models.py |
valid 판정을 받으면, 저장하고 post.get_absolute_url() 주소로 이동
1 |
|
HTML 템플릿은 FBV에서와 동일합니다
1 | <!-- myapp/templates/myapp/post_form.html --> |
DeleteView
- [GET/POST 요청] 지정 Model Instance 삭제확인 밎 삭제
- Model 옵션(필수)
- success_url 옵션 (필수)
- 디폴트 템플릿 경로 : “앱이름/모델명소문자_confirm_delete.html”
- GET 요청 : 삭제의사를 물어봅니다.
- POST 요청 : 삭제를 수행하고, 지정된 success_url 로 이동합니다.
샘플 코드
1 | # myapp/views.py |
1 | # templates/blog/post_confirm_delete.html |
reverse_lazy
모듈 import 시점에 url reverse가 필요할 때 사용
- 전역변수/클래스변수
실행하면 주소를 반환하여 success_url에 사용가능할것같지만 오류가뜸``` __이유는 reverse는 모든 프로젝트가 초기화를 한후에 실행해야하는데 이것은 ```post_delete 1
2
3
4
- 따라서 reverse_lazy를 사용하자 (필요 사용할때까지 지연시키므로가능)
- ex) Class 선언 시에myapp/views.py
from django.views.generic import CreateView
from django.urls import reverse_lazy
from .models import Post
class PostCreateView(CreateView): #커스텀 CBV만드는중
model = Post
success_url = reverse_lazy(‘blog:post_list’) # !!!
def form_valid(self, form):
post = form.save(commit=False)
post.ip = self.request.META[‘REMOTE_ADDR’]
post.user_agent = self.request.META[‘HTTP_USER_AGENT’]
return super().form_valid(form)
1 |
|
myproj/settings.py
from django.urls import reverse_lazy
LOGIN_URL = ‘/accounts/login/‘ # DO NOT
LOGIN_URL = reverse_lazy(‘login’) # DO !!!
LOGIN_REDIRECT_URL = ‘/accounts/profile/‘ # DO NOT
LOGIN_REDIRECT_URL = reverse_lazy(‘profile’) # DO !!!
#즉 url필요한 부분 직접쓰지말고 reverse로 쓰면 더 간편함