본문 바로가기

매일 TIL

[내일배움캠프 8-6일] Static Files, Media Files, Django Admin

Static Files

STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'

우선 settings.py를 보면 STATIC_URL는 이미 존재함. STATIC_ROOT, STATICFILES_DIRS 만 만들어준다.

STATIC_ROOT는 배포 시 사용하는 것이고, 모든 static 파일들을 모아서 BASE_DIR / 'staticfiles' 경로에 모아줄 예정.

STATICFILES_DIRS는 앱 안쪽에 있는 static 파일들을 뒤지는 것을 제외하고, 더 찾을 경로를 커스텀하는 것.

(예전에 template 찾을 때 경로 커스텀했던 것과 동일하다.)

my_first_pjt 안쪽에 static 폴더를 하나 생성.

이 위치가 바로 STATICFILES_DIR이 되는 것.(BASE_DIR이 결국 가장 상위 폴더인 my_first_pjt이기 때문에)

 

그럼 앱 안쪽에도 static 파일을 만들어 보자.

굉장히 어디서 보던 구조. 바로 템플릿 할때 봤던 햄버거 구조이다.

똑같이 해주면 됨.(앱 안쪽에 static -> static 안쪽에 앱이름 폴더 -> 그 안에 static 파일들을 차차 저장)

 

index 페이지에 nekoma 그림을 표시해보자.

{% extends 'base.html' %}
{% load static %}

{% block content %}
    <h1>INDEX PAGE</h1>

    <img src="{% static 'articles/nekoma.png' %}">
   
    <ur>
        <li><a href="{% url 'articles:articles' %}">글목록으로 이동</a></li>
        <li><a href="{% url 'articles:data-throw' %}">Throw로 이동</a><br></li>
        <li><a href="{% url 'users:users' %}">Users로 이동</a></li>
    </ur>

{% endblock content %}

articles 앱의 index.py

우선 {% load static %} 문장을 추가. 이후 이미지 삽입문으로 그냥 넣으면 된다.

 

크기가 너무 크다. CSS를 만들어보자. 다른 앱도 쓸 수 있도록 공통 CSS를 만들어야 함.

my_first_pjt 안에 있는 static에 CSS 폴더를 추가. 그 아래 style.css 파일을 하나 생성한다.

img {
    width: 200px;
}

style.css

우선 이렇게만 작성.

{% load static %}

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <title>Document</title>

    {% block head %}{% endblock head %}
</head>

base.html에서 적용시키자.(<head> 부분이 필요하기 때문에)

<link rel="stylesheet" href="{% static 'css/style.css' %}"> 해주면 된다.

우리는 stylesheet파일을 static에서 가져오는 것. 즉 {% load static %} 해줘야 함.

여기까지가 static 파일을 적용하는 방법.


Media Files

Media 파일이란?

유저가 웹에서 업로드한 모든 파일.(우리 서비스 내 포함된 파일이 아닌, 유저가 넣고 빼고 하는 파일)

방금 한 Static Files와 구조가 같다.

바로 구현해보자.

# Media Files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

settings.py에 Media File 부분을 만든다.

Static Files 했던 것과 거의 같음.

여기서 기본 설정 한 단계가 더 추가된다.

원래 장고는 이미지를 DB에 저장하지 않고 이미지 url을 저장해 두었다가 그걸 유저에게 줌.

하지만 개발 단계에서는 media url로 요청이 들어오면 마치 이미지를 저장하는 곳이 존재하는 것처럼 우리가 유저에게 response를 줘야 함.

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from articles import views


urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index, name='index'),
    path('articles/', include('articles.urls')),
    path('users/', include('users.urls')),
    path('accounts/', include('accounts.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

my_first_pjt의 urls.py. 먼저 import를 해주고,

if settings.DEBUG 조건문을 하나 추가.

우리는 현재 개발 모드, 즉 DEBUG 모드가 True로 설정되어 있음. 이 상태이면 조건문 이하를 실행한다는 것.

만약 url이 /media/~ 형태의 경로로 들어오면 ~ 부분은 documnet_root=settings.MEDIA_ROOT에서 찾으라는 의미.

이러면 기본 설정 완료.

 

Article 모델에 이미지 필드를 넣어보자.

from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    image = models.ImageField()

    def __str__(self):
        return self.title

articles 앱의 models.py

image = models.ImageField() 로 이미지 필드를 추가해준다.

이 과정에서 Pillow라는 패키지를 필요로 함. 다운받아주자.

패키지를 추가하면? -> freeze 해줘야함.

from django.db import models

# Create your models here.
class Article(models.Model):
    title = models.CharField(max_length=50)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    image = models.ImageField(upload_to='images/', blank=True)

    def __str__(self):
        return self.title

이제 upload_to를 활용해 이미지가 업로드 되는 경로를 지정하자. MEDIA_ROOT의 하위 경로를 지정.

image = models.ImageField(upload_to='images/', blank=True) 해주면 된다.

여기서 blank=True의 의미는 이미지가 필수가 아니라는 것. 비워놔도 된다는 뜻.

 

자 우리는 모델 수정을 해버렸다. 이는 DB에 영항을 준다. 즉 migrate 과정이 필요하다는 것.

그래그래

하던대로 하면 된다.

 

자 이제 이미지를 올리는 것 까지 형식은 완료. 이미지 파일을 처리하는 부분을 구현해보자.

{% extends 'base.html' %}

{% block content %}
<h1>New Article</h1>

<form action="{% url 'articles:create' %}" method="POST" enctype="multipart/form-data">
    {% csrf_token %}

    {{ form.as_p }}

    <button type="submit">저장</button>
</form>

<a href="{% url 'articles:articles' %}">목록으로</a>
{% endblock content %}

articles 앱의 create.html

enctype="multipart/form-data" 를 추가해준다.

enctype으로 form의 데이터 전송 형식을 지정한다. 기본적으로는 텍스트 형식의 데이터 교환.

html에서 파일을 전송하면, 이제 받아야함. -> 이 부분은 view에서 구현한다.

@login_required
def create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST, request.FILES)
        if form.is_valid():
            article = form.save()
            return redirect('articles:article_detail', article.pk)
    else:
        form = ArticleForm()

    context = {'form': form}
    return render(request, 'articles/create.html', context)

articles 앱의 views.py

ArticleForm의 첫번째 인자는 data(request.POST) 이고 두번째는 files(request.FILES)이다.

form = ArticleForm(request.POST, request.FILES) 가 되어야 하는 것.

넘어온 이미지 파일은 request.FILES에 전부 들어있다.

 

이제 글 작성 페이지에서 이미지를 넣어보자.

이미지 파일 선택 잘 이뤄지고, 선택한 coinman.png도 잘 입력되는 것을 볼 수 있음.

articles 앱 내부에 media 폴더가 생성되고,

그 내부 images 폴더 안에 내가 선택한 coinman.png가 들어간 것을 확인할 수 있음.

 

자 근데 뭔가 좀 아쉬움. 내가 넣은 이미지를 보여줘야지.

해당 글의 디테일 페이지에 나타내면 좋겠다.

{% extends 'base.html' %}

{% block content %}

    <h2>글 상세 페이지</h2>
    <p>제목: {{ article.title }}</p>
    <img src="{{ article.image.url }}">
    <p>내용: {{ article.content }}</p>
    <p>작성일시: {{ article.created_at }}</p>
    <p>수정일시: {{ article.updated_at }}</p>

    <a href="{% url 'articles:articles' %}">목록 보기</a>
    <a href="{% url 'articles:update' article.pk %}">
        <button>수정하기</button>
    </a>

    <form action="{% url 'articles:delete' article.pk %}" method="POST">
        {% csrf_token %}
        <input type="submit" value="삭제">
    </form>

{% endblock content %}

aritcle_detail.html

제목 바로 아래 표시되도록 설정.

article.image를 가져오는데, 이때 ImageField에는 안쪽에 url이 있다.

<img scr="{{ article.image.url }}"> 이렇게 작성해주면 됨.

잘 출력되는 것을 확인.

 

그런데 문제가 발생한다. 이미지를 업로드하지 않은 다른 기존의 글을 선택하면 에러.

이미지를 업로드 하지 않으면 article.image.url에 접근할 수 없기 때문에 에러가 나는 것.

해결해주자.

{% extends 'base.html' %}

{% block content %}

<h2>글 상세 페이지</h2>
<p>제목: {{ article.title }}</p>

{% if article.image %}
<img src="{{ article.image.url }}">
{% endif %}

<p>내용: {{ article.content }}</p>
<p>작성일시: {{ article.created_at }}</p>
<p>수정일시: {{ article.updated_at }}</p>

<a href="{% url 'articles:articles' %}">목록 보기</a>
<a href="{% url 'articles:update' article.pk %}">
    <button>수정하기</button>
</a>

<form action="{% url 'articles:delete' article.pk %}" method="POST">
    {% csrf_token %}
    <input type="submit" value="삭제">
</form>

{% endblock content %}

article_detail.html을 수정해주면 된다.

하던대로 조건문을 걸어 article.image가 있을 때에만 보여주면 됨.

Media Files 구현 완료.


Django Admin

이제 서비스를 관리하는 페이지가 필요하다.

개발자인 나 말고도 다른 관리자들이 우리 서비스 운영에 접근할 수 있어야 하기 때문에.

이걸 또 언제 만들어? -> 장고는 다 준비되어 있다.

path('admin/', admin.site.urls),

my_first_pjt에 urls.py를 보면 첫번째 path가 자동으로 입력되어 있었음.(사실 예전에 들어가봤다.)

이게 바로 장고가 제공하는 서비스 관리 페이지.

 

우리는 admin에서 관리할 모델을 직접 등록해야 한다.

from django.contrib import admin
from .models import Article

# Register your models here.
admin.site.register(Article)

articles 앱의 admin.py

Article 모델을 import 해주고, admin.site.register(Article) 로 등록해주면 끝.

관리자 페이지에 잘 등록된 것을 볼 수 있음.

다양한 기능으로 페이지를 운영할 수 있다.

 

뭔가 검색창도 추가하고, 필터도 추가하고 싶은데? 하면 됨. 커스텀 할 수 있다.

from django.contrib import admin
from .models import Article

# Register your models here.
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ("title", "created_at")
    search_fields = ("title", "content")
    list_filter = ("created_at",)
    date_hierarchy = "created_at"
    ordering = ("-created_at",)

아까 register 한 부분을 데코레이터로 빼줄 수 있음.

admin.ModelAdmin을 상속받는 ArticleAdmin이라는 클래스를 만들어주고,

아래 옵션들 형태로 넣어주면 된다.

이런 커스텀 옵션은 장고 문서를 참고하면서 내가 하나씩 해보면 될듯.

검색바, 필터 등 여러 요소가 추가된 것을 확인할 수 있음.

이런 식으로 커스텀해서 필요에 따라 사용하면 됨.


오늘의 회고

주말은 없다.(사실 쉬면서 하는중)

 

내일의 목표도 강의 마저 듣기