Django blog_pj(image)

Haks.·2025년 1월 25일
0

How to use

목록 보기
16/32

📌 Django summernote

  • poetry add django-summernote
  • summernote settings
  1. settings.py INSERTED_APP + 'django_summernote'
  2. 이미지가 등록될때 저장될 장소를 지정해 주어야 한다.
    • STATIC은 정적인 개발자가 올리는 곳의 루트
    • media는 사용자가 올리는 이미지,영상 같은 것을 저장하는 공간
  3. media 폴더 최상위 폴더에 생성 app과 같은 위치
  4. config/urls.py urlpatterns 입력 및 등록

from django.conf import settings
from django.conf.urls.static import static
from django.urls import path

from blog import cb_views

    # summernote 가 이미지를 업로드 해주거나 할때 들어갈 경로
    path('summernote/', include('django_summernote.urls')),

# 실제로 배포환경에서 media의 경로가 달라질거여서 debug 가 필요해서 사용
if settings.DEBUG: # STATIC 처럼 디폴트 값으로 설정되어 있지 않아서 이렇게 연결 해주어야 함
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
  1. summernote에서 필요한 migrate 수행 기본적으로 해야함
  2. admin 페이지에서 블로그 , 수정 눌러서 summernote 적용됬나 확인, 아직 안됨
  3. admin.py 에 등록, SummmernoteMModelAdmin 상속
from django_summernote.admin import SummernoteModelAdmin
@admin.register(Blog)
class BlogAdmin(SummernoteModelAdmin):
    summernote_fields = ['content']
    inlines = [ # 블로그 안에서 만들수 있께 해줌
        CommentInline
    ]
  1. 이미지 주소를 통해 등록가능
  2. admin 페이지에서는 나오지만 html 페이지에서는 코드로 나온다
  3. <p>{{ blog.content | safe }}</p>를 통해 content에 safe를 입히면 이미지가 출력이 된다.
<!-- todo 에선 -->
{% if key == "description" %}
    {{ value|safe }}
{% else %}
    {{ value}}
{% endif}
  1. media 폴더에는 내 이미즈리를 업로드해서 올리면 저장되는 것을 확인할 수 있다.

📝 CreateView Image 넣기

  1. fields를 통한 설정을 form 을 불러와서 설정하게 하기
# fields = ['catagory', ...] 
form_class = BlogForm
  1. summernote를 적용하기 위해 blog/form.py에 widgets 추가
from django_summernote.widgets import SummernoteWidget
class BlogForm(forms.ModelForm):    
    class Meta:
        model = Blog
        # 어떤필드를 적용 시킬
        fields = ('category', 'title', 'content') # Blog 모델의 'title'과 'content' 필드만 폼에 포함
        # Meta 클래스는 ModelForm의 메타 데이터를 정의하는 부분입니다. 이 클래스는 ModelForm이 어떤 모델과 연결되는지, 어떤 필드를 폼에 포함할지 등의 정보를 제공
        # fields =  '__all__' # 전체를  다 적용하고 싶을때
        
        widgets = {
            'content' : SummernoteWidget()
        }
  1. 생성 수행시 잘동작하나 확인, UpdateView에도 fomr_class 적용
  2. summernote 수정을 원할시 django summernote 깃헙링크 options 적용
  3. config/settings.py 에 젤아래부분에 작성
# summernote
SUMMERNOTE_CONFIG = {
    # Using SummernoteWidget - iframe mode, default


    # Or, you can set it to `False` to use SummernoteInplaceWidget by default - no iframe mode
    # In this case, you have to load Bootstrap/jQuery sources and dependencies manually.
    # Use this when you're already using Bootstrap/jQuery based themes.
    'iframe': True,

    # You can put custom Summernote settings
    'summernote': {
        # As an example, using Summernote Air-mode
        'airMode': False,

        # Change editor size
        'width': '100%',
        'height': '480',

        # Use proper language setting automatically (default)


        # Toolbar customization
        # https://summernote.org/deep-dive/#custom-toolbar-popover
        'toolbar': [
            ['style', ['style']],
            ['font', ['bold', 'underline', 'clear']],
            ['fontname', ['fontname']],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['table', ['table']],
            ['insert', ['link', 'picture', ]],
            ['view', ['fullscreen', 'help'],],
        ],

        # Or, explicitly set language/locale for editor
        'lang': 'ko-KR',


        # You can also add custom settings for external plugins

        'codemirror': {
            'mode': 'htmlmixed',
            'lineNumbers': 'true',
            # You have to include theme file in 'css' or 'css_for_inplace' before using it.
            'theme': 'monokai',
        },
    },

    # Require users to be authenticated for uploading attachments.
    'attachment_require_authentication': True,

    # You can completely disable the attachment feature.
    'disable_attachment': False,

    # Set to `False` to return attachment paths in relative URIs.
    'attachment_absolute_uri': True,

    # test_func in summernote upload view. (Allow upload images only when user passes the test)
    # https://docs.djangoproject.com/en/2.2/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin



    # You can add custom css/js for SummernoteWidget.

}
  1. 'codeview' 는 매우 위험한 동작 지우는걸 권장
  2. shell을 통하여 우리의 이미지 코드 수정 가능
In [1]: blog = Blog.objects.get(id=151)

In [2]: blog.content

blog.content = '필요한 부분'
   
blog.save() # 로 바꿀수 있음

📝 Image 업로드 기능 with Pillow 라이브러리

  • poetry add Pillow
  • Django 내장라이브러리가 아니기에 installed app에 추가하지 않아도 된다.
  1. models.py image 필드 추가
image= models.ImageField('이미지', null=True, blank=True, upload_to='blog/%Y/%m/%d') #파일 저장시 /년 별로/달 별로/일 별로 저장됨
  1. 이미지 추가를 위한 Pillow 라이브러리 추가
  2. migrate
  3. DB.browser 을 통해 이미지 필드 추가 확인 ImageField 는 실제로 VARCHAR 필드임 => 실제 이미지를 저장하는 것이 아니라 이미지 경로를 저장하는 것이다. meida 에 저장되어 있는 경로로 보내는 경로를 저장하는 것임
  4. 저장후 admin을 통해 이미지 등록을 하면 db에서 경로가 저장되는 것을 확인할 수
  5. form.py 에 image 추가
# 순서대로 읽기에 image 위치 선정
        fields = ('category', 'title','image' ,'content') # Blog 모델의 'title'과 'content' 필드만 폼에 포함
  1. 저장후 실제 페이지로 들어가서 이미지를 저장후 추가하면 동작하지 않는다
  2. 어떤것들이 출력되고 들어가고 있는지 확인을 해보자.
# is_valid 후에 사용가능
def form_valid(self, form):
    print(form.cleaned_data)  # 폼 데이터를 출력
    return super().form_valid(form)  # 기본 동작 수행
  1. None으로 출력되어 서버로 전달이 되고 있지 않은 것을 확인할 수 있다. html 코드 쪽의 문제를 확인해 보자
  2. 이미지를 넣을 POST 동작을 수행할떄 enctype='multipart/form-data' 를 추가해 주어야 한다.
<form method="POST" enctype="multipart/form-data">
  1. 장고는 같은이미지를 2번올려도 자기만의 언어를통해 다른 이미지로 저장을 한다.
  2. 이제 detail.html 을 수정해서 마무리해 주자.
{% if blog.image %}
    <img src="{{ blog.image.url }}" alt="Blog Image" style="max-width: 100%; height: auto;">
{% endif %}
  1. list.html 을 수정해서 list 에서도 이미지가 보이게 해주자.
               {% if blog.image %}
                   <img src="{{ blog.image.url }}" class="col-2">
                {% endif %}
            <span class="col-10">
                ({{ blog.get_category_display }}) {{ blog.title }} <span>{{ blog.author.username }}</span> - {{ blog.created_at|date:"Y-m-d" }}
            </span>

📌 FBV에서 이미지 파일 업로드 하기

  • form = BlogForm(request.POST or None, request.FILES or None, instance=blog) request.FILES or None 이부분 추가해주면 된다

📝 Thumbnail image

  • 목록에서 보이는 이미지 제작, 해당 이미지의 용량 줄이기
  • pixel <- 랜덤 이미지 다운

1.models.py thumbnail 추가, migrate

 # null은 db에 들어갈때 의미하고 blank 는 form 에서 비어도 되는지안되는지 의미한다.
    thumbnail = models.ImageField('썸네일', null=True, blank=True, upload_to='blog/%Y/%m/%d/thumbnail')
  1. 작은 크기의 썸네일을 저장하여 데이터베이스를 최적화하고, 이미지 로딩시간을 단축시켜 퍼포먼스를 향상시키고, 원본이미지와 별도의 썸네일이미지 관리를 위한 유연성 제공을 위하여 모델의 내장함수 def save()를 오버라이드 할것이다.
from PIL import Image # Pillow 에 있는 모듈
from pathlib import Path
from io import BytesIO
  def save(self, *args, **kwargs):
        if not self.image: # 이미지가 없으면 해당과정이 필요없어서 종료
            return super().save(*args, **kwargs)
        image = Image.open(self.image) # 이미지를 열고
        image.thumbnail((300,300)) # 원하는 이미지로 조절

        image_path = Path(self.image.name) # Path 라이브러리를 통해 이미지 경로 가져오기
        thumbnail_name = image_path.stem # 확장자를 제외한 파일 이름
        thumbnail_extension = image_path.suffix.lower() # 확장자만 가져옴
        thumbnail_filename = f'{thumbnail_name}_thumb{thumbnail_extension}' # database_thumb.png 로 될거임, 원본파일과의 이름을 구분짓기 위해 사용

        if thumbnail_extension in ['.jpg','jpeg']:
            file_type = 'JPEG' 
        elif thumbnail_extension == '.gif':
            file_type = 'GIF'
        elif thumbnail_extension == '.png':
            file_type = 'PNG'
        else:# 지원하지않는 확장자면 그냥 종료
            return super().save(*args, **kwargs)
         
            
        # 바로 저장하면 좋지만 날짜나 이런걸 실수할수도 있다 그래서 어딘가로 메모리에 들고있다가
        # 메모리에 있는걸 장고의 파일에 있는척 하면서 세이브 시켜줄겅임
        temp_thumb = BytesIO()
        image.save(temp_thumb, file_type) #  이미지를 지정한 파일 형식으로 메모리에 저장
        temp_thumb.seek(0)

        self.thumbnail.save(thumbnail_filename, temp_thumb, save= False) #  Django의 FileField/ImageField를 사용해 썸네일을 모델에 저장.
        temp_thumb.close() # 메모리 자원을 해제.

		return super().save(*args, **kwargs)
        
    # BytesIO 사용 이점
    •	이미지를 바로 파일 시스템에 저장하지 않고, BytesIO라는 메모리 기반 객체에 임시로 저장합니다.
	•	파일을 저장하고 다시 읽어오는 디스크 작업을 생략하고, 메모리 내에서 즉시 조작이 가능합니다.
    
    실제 저장하는곳
    self.thumbnail.save(thumbnail_filename, temp_thumb, save=False)
    이곳에서 파일 시스템을 사용해 데이터를 저장한다
    데이터는 이미 메모리에서 준비되었기에 I/O 비용을 최소화함
  1. migrate 후 이미지 파일 삽입시 media 폴더에 생성
  2. 썸네일 파일의 이미지 용량의 변경 확인
  3. detail.html는 원본이미지를 사용하고, list.html에는 thumb에 해당하는 이미지 사용
               {% if blog.image %}
                   <img src="{{ blog.image.url }}" class="col-2">
                {% endif %}
<!--  수정 -->
               {% if blog.thumbnail %}
                   <img src="{{ blog.thumbnail.url }}" class="col-2">
               {% elif blog.image %}
                   <img src="{{ blog.image.url }}" class="col-2">
                {% endif %}
  1. 모델에 추가한후 html 코드에서 사용하는 방법도 존재.
# 이함수를 모델에 추가해놓으면
    def get_thumbnail_image_url(self):
        if self.thumbnail:
            return self.thumbnail.url
        elif self.image:
            return self.image.url
        return None

               {% if blog.get_thumbnail_image_url %}
                   <img src="{{ blog.get_thumbnail_image_url }}" class="col-2">
{#               {% if blog.thumbnail %}#}
{#                   <img src="{{ blog.thumbnail.url }}" class="col-2">#}
{#               {% elif blog.image %}#}
{#                   <img src="{{ blog.image.url }}" class="col-2">#}
                {% endif %}

📌 Django cleanup

  • poetry add django-cleanup
  • installed_app 에 등록만 해주면 imagefield나 filefield 가 사라지거나 등록될떄
  • 경로에서 자동으로 삭제 해준다.

📌 모듈정리

from django.conf import settings # 세팅 부를때 이렇게 부르는거 추천 환경변수가 바뀌어도 안변하고 settings 로 들어감

from django.conf.urls.static import static
    urlpatterns += static(settings.MEDIA_URL, doucument_root=settings.MEDIA_ROOT)

from django_summernote.widgets import SummernoteWidget
        widgets = {
            'content' : SummernoteWidget()
            
from PIL import Image # Pillow 에 있는 모듈
from pathlib import Path
from io import BytesIO
    def save(self, *args, **kwargs):
        if self.image:
            image = Image
    image_path = Path(self.image.name)
    temp_thumb = BytesIO()

📝 queryset & object

  1. queryset
    • 정의: 데이터베이스로부터 가져온 객체들의 집합입니다. 즉, 특정 모델의 여러 인스턴스를 포함하는 컬렉션입니다.
    • 주로 사용되는 곳: get_queryset() 메서드에서 사용됩니다.
    • 역할:
    • 다수의 객체를 가져올 때 사용됩니다.
    • 필터링, 정렬 등의 작업을 통해 원하는 데이터의 서브셋을 가져오는 데 유용합니다.
    • 예를 들어, 블로그의 댓글 전체를 가져오거나 작성자가 특정 유저인 블로그만 가져오는 데 사용됩니다.

  2. self.object
    • 정의: 특정 단일 객체를 나타냅니다. 즉, 모델의 한 인스턴스입니다.
    • 주로 사용되는 곳: get_object() 메서드나 DetailView, UpdateView, DeleteView 등에서 사용됩니다.
    • 역할:
    • 주로 하나의 객체를 다룰 때 사용됩니다.
    • 예를 들어, 블로그 상세 페이지나 댓글 수정 페이지에서 해당 블로그 또는 댓글의 객체를 가져올 때 사용됩니다.

특징querysetself.object
내용객체들의 집합 (다수)단일 객체 (하나)
사용 위치get_queryset() 또는 ListViewget_object() 또는 DetailView, UpdateView
사용 예댓글 목록, 블로그 목록 가져오기특정 블로그, 댓글, 사용자의 객체 가져오기
역할데이터의 여러 개의 집합을 조작하거나 필터링데이터베이스에서 단일 객체를 검색 및 반환
기반 클래스QuerySet특정 모델의 인스턴스 (Model 클래스 객체)

📌 Tip

필드

  • 텍스트

    • CharField : 짧은글 / 길이제한 O
    • TextField : 긴글 / 길이제한 X
    • UrlField : URL 저장 / 사실상 CharField
    • SlugField : Slug저장 / 사실상 CharField / 잘안씀
    • UUIDField : UUID 저장 / 잘안씀
  • 파일

    • EmailField : 이메일 저장
    • FileField : 파일 저장
    • ImageField : 이미지 저장
  • 숫자

    • IntegerField : 숫자 필드
    • PositiveIntegerField : 양수
    • BigIntegerField : 큰 숫자 필드
    • PositiveBigIntegerField : 양수만 가능한 큰 숫자 필드
    • DecimalField : Decimal 저장 / 고정 소수점 => 정확하지만 상대적으로 작은 수
    • FloatField : Float 저장 / 부동소수점 => 조금 부정확하지만 큰 수를 표현
  • 날짜, 시간

    • DateTimeField : 날짜 및 시간
    • DateField : 날짜만 저장
    • TimeField : 시간만 저장
  • 연결

    • ForeignKey : 1:N 관계 / 내가 N
    • ManyToManyField : N:N 관계
    • OneToOneField : 1:1 관계
  • 기타

    • JSONField : 리스트나 딕셔너리 형태로 받을 수 있음, 컬럼으로 받긴 애매하고 데이터를 받아야 할떄 사용
class Payment(mdoels.Mdoel):
    payment_response = models.JSONField(default = {},[])
  • django fields 검색해서 보면 찾아서 사용

DB 설계를 잘하는 법 / 노하우

  • 중복된 데이터를 많이 적지 않는것이 제일 중요

📌 get_object

기본적으로 get_object는 pk 또는 slug 값을 URL에서 가져와서, 뷰에 연결된 모델의 객체를 데이터베이스에서 검색하여 반환합니다.

def get_object(self, queryset=None):
    return get_object_or_404(Blog, pk=self.kwargs['blog_pk'])
    
1. self.kwargs: # 기본적으로 url 에서 받아온거네 그걸담고있는게 저거고
	•	URLconf에 정의된 매개변수(: <int:blog_pk>)를 담고 있습니다.
	•	self.kwargs['blog_pk']는 URL에서 blog_pk라는 키로 전달된 값을 가져옵니다.
# 그래서 기존의 pk 로된 것을 불러오는 것이 아닌 blog_pk를 가져오는거네

# 기본적으로 Django는 pk 또는 slug를 기준으로 객체를 가져옵니다. 그런데 지금 URLconf에서 blog_pk라는 키를 사용하고 있기 때문에 기본 get_object는 이를 인식하지 못합니다. 그래서 아래처럼 get_object_or_404를 사용해 명시적으로 Blog 객체를 가져오도록 오버라이드한 것입니다.

form_valid 란?

기본적으로 is_valid 이후에만 사용이 가능하며 제대로 전달이 됬나 확인 할 수 있음

  • 클래스 기반 뷰에서 제공되는 메서드로 CreateView,UpdateView 에서 사용된다. 폼의 데이터가 유효한 경우에 호출되며 기본적으로 폼데이터를 저장하고 성공적으로 처리된 응답을 반환.
  • form.is_valid() 가 true 를 반환하면 form_valid메서드가 호출됨
  • def form_valid(self, form):
    print(form.cleaned_data) # 폼 데이터를 출력
    return super().form_valid(form) # 기본 동작 수행
  • form.cleaned_data:
    • form.cleaned_data는 폼 데이터를 유효성 검사가 완료된 후 정리된 딕셔너리 형태의 데이터입니다.
    • 이 코드는 폼 데이터가 어떻게 입력되었는지 콘솔에 출력하는 역할을 합니다.
  • form_valid는 폼이 유효한 데이터를 가지고 있는 상태에서 호출됩니다. 따라서 이 메서드 자체가 유효성을 판단하는 것은 아니지만, 폼이 유효한 상태에서 추가 작업을 하거나 기본 동작을 커스터마이징할 수 있는 지점입니다.

  • 폼의 유효성 판단은 이미 form.is_valid() 단계에서 이루어졌고, 이 메서드는 그 다음 단계에서 동작합니다.

# 사용방법 2가지
# 1. 저장전에 데이터추가
def form_valid(self, form):
    self.object = form.save(commit=False)  # 데이터를 저장하지 않고 객체 생성
    self.object.author = self.request.user  # 현재 사용자 추가
    self.object.save()  # 저장
    return super().form_valid(form)
# 저장후 추가작업
def form_valid(self, form):
    response = super().form_valid(form)  # 기본 저장 작업
    send_email_to_admin(form.cleaned_data)  # 저장 후 추가 작업 수행
    return response

0개의 댓글

관련 채용 정보