[Django] 장고 Models를 활용한 데이터베이스 처리 - 2
카테고리: Django
장고가 media 파일을 다루는 방법
Static & Media 파일
Static 파일
- 개발 리소스로서의 정적인 파일(js, css, image 등)
- 앱 / 프로젝트 단위로 저장 / 서빙
Media 파일
- FileField / ImageField를 통해 저장한 모든 파일
- DB필드에는 저장경로를 저장하며, 파일은 파일 스토리지에 저장
–실제로 문자열을 저장하는 필드
- 프로젝트 단위로 저장/서빙
-
instagram/models.py 수정
from django.db import models class Post(models.Model): message = models.TextField() photo = models.ImageField(blank=True) is_public = models.BooleanField(default=False, verbose_name='공개여부') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) # Java의 toString def __str__(self): return self.message
-
pillow 라이브러리 설치
pip install pillow
-
라이브러리 requirements.txt 파일 만들기
diango~=3.0.0 pillow
추후, 패키지를 install 할 경우, 버전 관리에 용이
설치 방법
pip install -r requirements.txt
-
migration 진행
python manage.py makemigrations instagram python manage.py migrate instagram
Media 파일 처리 순서
- HttpRequest.FILES를 통해 파일이 전달
- 뷰 로직이나 폼 로직을 통해, 유효성 검증을 수행하고,
- FileField/ImageField 필드에 “경로(문자열)”를 저장하고,
- settings.MEDIA_ROOT 경로에 파일을 저장합니다.
기본 settings
-
PROJECT_NAME/settings.py 수정
STATIC_URL = '/static/' # STATIC_ROOT = '' # TODO MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
FileFiend와 ImageField
FileField
- File Storage API를 통해 파일을 저장
– 장고에서는 File System Storage만 지원. django-storages를 통해 확장 지원. - 해당 필드를 옵션 필드를 두고자 할 경우, blank=True 옵션 적용
ImageField(FileField 상속)
- Pillow(이미지 처리 라이브러리)를 통해 이미지 width/height 획득
– Pillow 미설치 시에, ImageField를 추가한 makemigration 수행에실패
한다.
위 필드를 상속받은 커스텀 필드를 만들 수도 있다.
- ex) PDFField, ExcelField 등
사용할 만한 필드 옵션
blank 옵션
- 업로드 옵션처리 여부
- 디폴트 : False
upload_to 옵션
- settings.MEDIA_ROOT 하위에서 저장한 파일명/경로명 결정
- 디폴트 : 파일명 그대로 settings.MEDIA_ROOT에 저장
–추천) 성능을 위해, 한 디렉토리에 너무 많은 파일들이 저장되지 않도록 조정하기
- 동일 파일명으로 저장 시에, 파일명에 더미 문자열을 붙여 파일 덮어쓰기 방지
-
PROJECT_NAME/urls.py 수정
from django.conf.urls.static import static from django.contrib import admin from django.urls import path, include # from django.conf import global_settings # from askcompany import settings from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), path('blog1/', include('blog1.urls')), path('instagram/', include('instagram.urls')), ] if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
-
instagram/admin.py 수정
from django.contrib import admin from django.utils.safestring import mark_safe from .models import Post @admin.register(Post) # wrapping class PostAdmin(admin.ModelAdmin): list_display = ['id', 'message', 'message_length', 'is_public', 'created_at', 'updated_at'] list_display_links = ['message'] list_filter = ['created_at', 'is_public'] search_fields = ['message'] def photo_tag(self, post): if post.photo: return mark_safe(f'<img src="{post.photo.url}" style="width: 72px;" />') return None def message_length(self, post): return f"{len(post.message)} 글자"
-
instagram/urls.py 수정
from django.db import models class Post(models.Model): message = models.TextField() photo = models.ImageField(blank=True, upload_to='instagram/post/%Y/%m/%d') is_public = models.BooleanField(default=False, verbose_name='공개여부') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) # Java의 toString def __str__(self): return self.message
파일 업로드 시에 HTML Form enctype
form method는 필히 POST로 지정
- GET의 경우 enctype이 “application/x-www-form-urlencoded”로 고정
form enctype을 필히 “multipart/form-data”로 지정
- “application/x-www-form-urlencoded”의 경우, 파일명만 전송
upload_to 인자
파일 저장 시에
upload_to 함수를 호출하여, 저장 경로를 계산
- 파일 저장 시에 upload_to 인자를 변경한다고 해서, DB에 저장된 경로값이 갱신되진 않는다.
인자 유형
- 문자열로 지정
– 파일을 저장할 “중간 디렉토리 경로”로서 활용 - 함수로 지정
– “중간 디렉토리 경로” 및 “파일명”까지 결정 가능
파일 저장 경로 / 커스텀 (upload_to 옵션)
한 디렉토리에 파일을 너무 많이 몰아둘 경우, OS 파일찾기 성능 저하. 디렉토리 Depth가 깊어지는 것은 성능에 큰 영향 없음.
필드 별로, 다른 디렉토리 저장경로를 가지기
-
대책 1) 필드 별로 다른 디렉토리에 저장
– photo = models.ImageField(upload_to=”blog”) – photo = models.ImageField(upload_to=”blog/photo”) -
대책 2) 업로드 시간대 별로 다른 디렉토리에 저장
– upload_to에서 strftime 포맷팅을 지원
– photo = models.ImageField(upload_to=”blog/%Y/%m/%d”)
예시) uuid를 통한 파일명 정하기
import os
from uuid import uuid4
from django.utils import timezone
def uuid_name_upload_to(instance, filename):
app_label = instance.__class__._meta.app_label # 앱 별로
cls_name = instance.__class__.__name__.lower() # 모델 별로
ymd_path = timezone.now().strftime('%Y/%m/%d') # 업로드하는 년/월/일 별로
uuid_name = uuid4().hex
extension = os.path.splitext(filename)[-1].lower() # 확장자 추출하고, 소문자로 변환
return '/'.join([
app_label,
cls_name,
ymd_path,
uuid_name[:2],
uuid_name + extension,
])
템플릿에서 media URL 처리 예시
필드의 .url 속성을 활용한다.
- 내부적으로 settings.MEDIA_URL과 조합을 처리
<img src="{{ post.photo.url }}" %}" />
- 필드에 저장된 경로에 없을 경우, .url 계산에 실패함에 유의. 그러니 안전하게 필드명 저장유무를 체크
{% if post.photo %} <img src="{{ post.phoro.url }}" %}"> {% endif %}
참고
- 파일 시스템 상의 절대경로가 필요하다면, .path 속성을 활용하세요.
– settings.MEDIA_ROOT와 조합
개발환경에서의 media 파일 서빙
static 파일과 다르게, 장고 개발서버에서 서빙 미지원
개발 편의성 목적으로 직접 서빙 Rule 추가 가능
from django.conf import settings
from django.conf.urls.static import static
# 중략
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
장고 쉘
모델을 통한 조회(기초)
데이터베이스 질의 인터페이스를 제공
디폴트 Manager로서 ModelCls.objects가 제공
# 생성되는 대강의 SQL 윤곽 -> SELECT * FROM app_model
ModelCls.objects.all()
# 생성되는 대강의 SQL 윤곽 -> SELECT * FROM app_model ORDER BY id DESC LIMIT 10
ModelsCls.object.all().order_by('-id')[:10]
# 생성되는 대강의 SQL 윤곽 -> INSERT INTO app_model (title) VALUES ("New Title")
ModelCls.objects.create(title="New Title")
QuerySet
SQL을 생성해주는 인터페이스
순회가능한 객체
Model Manager를 통해, 해당 Model에 대한 QuerySet을 획득
- Post.objects.all() 코드는 “SELECT * FROM post …”
- Post.objects.create(…) 코드는 “INSERT INTO…”
QuerySet은 Chaining을 지원
Post.objects.all().filter(…).exclude(…).filter(…) -> QuerySet
QuerySet은 Lazy한 특성
- QuerySet을 만드는 동안에는 DB접근을 하지 않는다.
- 실제로 데이터가 필요한 시점에 접근을 한다.
데이터가 필요한 시점은 언제인가?
- queryset
- print(queryset)
- list(query)
- for instance in queryset: print(instance)
다양한 조회요청 방법(SELECT SQL 생성)
조건을 추가한 Queryset, 획득할 준비
- queryset.filter(…) -> queryset
- queryset.exclude(…) -> queryset
특정 모델객체 1개 획득을 시도
- queryset[숫자인덱스]
– 모델객체 혹은 예외발생(IndexError) - queryset.get(…)
– 모델객체 혹은 예외발생(DoesNotExist, MultipleObjectsReturned)
queryset.first() -> 모델객체 혹은 None
querset.last() -> 모델객체 혹은 None
filter <-> exclude(SELECT 쿼리에 WHERE 조건 추가)
인자로 “필드명 = 조건값” 지정
1개 이상 인자 지정 -> 모두 AND 조건으로 묶임
OR 조건을 묶을려면, django.db.models.Q 활용
필드 타입별 다양한 조건 매칭(데이터베이스에 따라 생성되는 SQL이 다르다.)
숫자/날짜/시간 필드
- 필드명__lt = 조건값 -> 필드명 < 조건값
- 필드명__lte = 조건값 -> 필드명 <= 조건값
- 필드명__gt = 조건값 -> 필드명 > 조건값
- 필드명__gte = 조건값 -> 필드명 >= 조건값
문자열 필드
- 필드명__startswith = 조건값 -> 필드명 LIKE “조건값%”
- 필드명__istartswith = 조건값 -> 필드명 ILIKE “조건값%”
- 필드명__endswith = 조건값 -> 필드명 LIKE “%조건값”
- 필드명__iendswith = 조건값 -> 필드명 ILIKE “%조건값”
- 필드명__contains = 조건값 -> 필드명 LIKE “%조건값%”
- 필드명__icontains = 조건값 -> 필드명 ILIKE “%조건값%”
예제)
# shop/views.py
from django.shortcuts import render
from .models import Item
# 중략 ...
def item_list(request):
qs = Item.objects.all()
q = request.GET.get('q', '')
if q:
qs = qs.filter(name__icontains=q)
return render(request, 'shop/item_list.html', {
'item_list': qs,
'q': q,
})
# shop/views.py
# 중략...
urlpatterns = [
path('', views.item_list)
]
<-- shop/templates/shop/item_list.html -->
<form action="" method="GET">
<input type="text" name="q" value=""/>
<input type="submit" value="검색"/>
</form>
🐢 현재 공부하고 있는 파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 - 이진석 강사
의 강의를 학습하며 기록 및 정리를 하기위한 내용들입니다. 🐢
감사합니다.😊
댓글남기기