AI 웹 개발 과정/팀 프로젝트

최종 프로젝트 - 모델링 / admin 작업

만 기 2022. 7. 14. 10:11

 

목차

1. ERD 설계 참조하여 review 앱에 models.py 작성

    1) 모델 관계

    2) on_delete 속성

    3) DateField와 DateTimeField 속성

    4) ImageField 



2. review 모델 admin 페이지 커스텀
    1) 어드민 페이지 설정
    2) url로 저장된 이미지 필드를 이미지로 보여주기
    3) search_fields 에서 관계된 모델 필드로 검색하기
    4) def get_readonly_fields(self, request, obj):  사용했을때
    5) Review 어드민 상세 페이지에서 ReviewImage 필드 넣기

 

 

 

1. ERD 설계 참조하여 review 앱에 models.py 작성

review/models.py

# 리뷰 모델
class Review(models.Model):
    user = models.ForeignKey(User, verbose_name='작성자', on_delete=models.CASCADE)
    trip = models.ForeignKey(Trip, verbose_name='여행 일정', on_delete=models.CASCADE)
    
    title = models.CharField('제목', max_length=50)
    content = models.TextField('내용')
    created_at = models.DateTimeField('작성일', auto_now_add=True)

    def __str__(self):
        return f"{self.title} - {self.user.username}"

# 리뷰 이미지 모델
class ReviewImage(models.Model):
    review = models.ForeignKey(Review, verbose_name='리뷰', on_delete=models.CASCADE)
    
    image = models.ImageField('이미지', upload_to='static/images/review/%Y%m%d')
    order = models.IntegerField("이미지 순서")

    def __str__(self):
        return f"{self.review.title}'s image - {self.order}"

 

1) 모델 관계

* User - Review : One To Many 

* Trip - Review : One To Many

* Review - ReviewImage : One To Many 

 

2) on_delete 속성

  • CASCADE : FK로 참조하는 레코드가 삭제 될 경우 해당 레코드를 삭제한다.
  • SET_NULL : FK 필드의 값을 Null로 변경해준다. null=True가 정의되어 있어야 사용 가능하다.
  • PROTECT : 해당 레코드가 삭제되지 않도록 보호해준다.
  • SET_DEFAULT : FK 필드의 값을 default로 변경해준다. default=””가 정의되어 있어야 사용 가능하다.
  • SET() : FK 필드의 값을 SET에 설정된 함수를 통해 원하는 값으로 변경할 수 있다.
  • DO_NOTHING : 아무런 동작을 하지 않는다. 참조 관계의 무결성이 손상될 수 있기 때문에 권장하지 않는다.

 

3) DateField와 DateTimeField 속성

  • default = $date : 지정한 값을 기본 값으로 설정한다.
  • auto_now_add = True : 레코드가 생성될 때의 date를 기준으로 값을 지정한다.
  • auto_now = True : 레코드가 save()될 때마다 갱신된다.

 

4) ImageField 

ImageField ( upload_to = None , height_field = None , width_field = None , max_length = 100 , ** options )

  • upload_to : {MEDIA_ROOT}/{upload_to}/{업로드 하는 파일명}. datetime 모듈을 import하지 않고 %Y%m%d의 형태로 날짜 또는 시간을 입력할 수 있다.
  • ImageField.height_field : 모델 인스턴스가 저장될 때마다 이미지 높이가 자동으로 채워지는 모델 필드의 이름이다.
  • ImageField.width_field : 모델 인스턴스가 저장될 때마다 이미지 너비가 자동으로 채워지는 모델 필드의 이름이다.
  • ImageField인스턴스는 varchar 기본 최대 길이가 100자인 열로 데이터베이스에 생성됩니다. max_length다른 필드와 마찬가지로 인수 를 사용하여 최대 길이를 변경할 수 있습니다 

upload_to 참조 : http://johnnykims.blogspot.com/2016/05/django-imagefield-uploadto.html

 

django ImageField upload_to 설정하기

django에서 파일 또는 이미지 업로드 기능을 사용하려면 장고 모델의 ImageField, FileField에서 사용하는 upload_to argument를 설정해야 합니다. upload_to 에 설정한 값은 DEFAULT_FILE_STORAGE...

johnnykims.blogspot.com

 

 


 

2. review 모델 admin 페이지 커스텀

reviews/admin.py

from django.contrib import admin
from .models import ReviewImage, Review

from django.utils.html import mark_safe


class ReviewImageInline(admin.StackedInline):
    model = ReviewImage
    extra = 0

    fieldsets = (
        ('image', {'fields': ('order','image','image_tag',)}),
        )

    def get_readonly_fields(self, request, obj): 
        if obj:
            return ('image_tag',)
        else:
            return ('image_tag',)

    def image_tag(self, obj):
        if obj.image:
            return mark_safe('<img src="{}" width="100" height="100"/>'.format(obj.image.url))


class ReviewAdmin(admin.ModelAdmin):
    
    list_display = ('id', 'user', 'title', 'image_preview',)
    list_display_links = ('id', 'user', 'title', 'image_preview',)
    list_filter = ('user',)

    search_fields = ('user__username', 'title')

    readonly_fields = ('created_at',)

    fieldsets = (                               
        ('info', {'fields': ('created_at', 'user', 'trip')}),
        ('content', {'fields': ('title', 'content')}),
        )


    def image_preview(self, obj):
        images = obj.reviewimage_set.all().first()
        if obj.reviewimage_set:
            return mark_safe(f'<img src="{images.image.url}" width="100" height="100"/>')

    inlines = (
            ReviewImageInline,
        )


class ReviewImageAdmin(admin.ModelAdmin):

    list_display = ('id', 'review', 'image_tag',)
    list_display_links = ('id', 'review', 'image_tag',)
    list_filter = ('review',)

    search_fields = ('review__title',)

    fieldsets = (                               
        ('info', {'fields': ('review', 'order',)}),
        ('image', {'fields': ('image', 'image_tag')}),
        )


    def get_readonly_fields(self, request, obj=None): 
        if obj:
            return ('image_tag',)
        else:
            return ('image_tag',)

    def image_tag(self, obj):
        if obj.image:
            return mark_safe(f'<img src="{obj.image.url}" width="100" height="100"/>')
        return None
    
    
admin.site.register(Review, ReviewAdmin)
admin.site.register(ReviewImage, ReviewImageAdmin)

 

 

1) 어드민 페이지 설정

  • list_display : 모델 오브젝트들을 간략히 보여줄때 보여줄 필드
  • list_display_links : 오브젝트를 수정하거나 상세히 보기위한 페이지로 넘어가기 위해 링크를 달아줄 필드
  • list_filter : 설정한 필드별로 볼 수 있다.
  • search_fields : 검색할 수 있는 필드
  • readonly_fields : 읽기 전용 필드
  • fieldsets : 오브젝트 상세 페이지에서 그룹을 만들 수 있다.
  • get_readonly_fields :
    • ModelAdmin.get_readonly_fields(request, obj=None)
    • The get_readonly_fields method is given the HttpRequest and the obj being edited (or None on an add form) and is expected to return a list or tuple of field names that will be displayed as read-only, as described above in the ModelAdmin.readonly_fields section.

참조 : https://docs.djangoproject.com/en/4.0/ref/contrib/admin/

 

The Django admin site | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 

 

2) url로 저장된 이미지 필드를 이미지로 보여주기

프로젝트폴더/settings.py 

import os

...

MEDIA_URL = '/media/'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

 

프로젝트폴더/urls.py

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

...

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

 

review/admin.py

from django.utils.html import mark_safe

...
    
    def image_preview(self, obj):
        images = obj.reviewimage_set.all().first()
        if obj.reviewimage_set:
            return mark_safe(f'<img src="{images.image.url}" width="100" height="100"/>')

 

mark_safe : 출력 목적으로 문자열을 안전한 것으로 명시적으로 표시합니다. 반환된 개체는 문자열 또는 유니코드 개체가 적절한 모든 곳에서 사용할 수 있습니다. 문자열을 파이썬으로부터 안전한 것으로 표시해야 합니다.

- 참조 : https://stackoverflow.com/questions/32799615/why-and-when-to-use-django-mark-safe-function

 

Why and When to use Django mark_safe() function

After reading the document, the function of mark_safe() is still unclear. I guess it is related to CSRF stuff. But why and when shall the mark_safe() be used? Here is the documentation mark_safe(s)

stackoverflow.com

 

static 파일 다루기 참조 : https://velog.io/@duo22088/Django-Media-file-%EB%8B%A4%EB%A3%A8%EA%B8%B0

 

(Django) Media file 다루기

장고에서 어떻게 static 파일을 서빙하는지 알아봅시다.

velog.io

 

 

 

3) search_fields 에서 관계된 모델 필드로 검색하기

Review 모델과 ReviewImage 모델이 One To Many 인 관계에서

ReviewImage 어드민 페이지의 search_fields 를 Review 모델의 title 필드로 정하기

search_fields = ('review__title',)

 

 

4) def get_readonly_fields(self, request, obj):  사용했을때

Review 모델에 created_at 필드를 쓰기에 사용하지 않고 읽기에만 사용하기 위해 

    def get_readonly_fields(self, request, obj): 
        if obj:
            return ('created_at',)
        else:
            return ()

위 처럼 작성 하였으나, 어드민페이지에서 새로 오브젝트를 생성할 때 

'created_at' cannot be specified for Review model form as it is a non-editable field.

라는 오류가 발생한다.

수정할 수 없는 필드인데 fieldsets 안에 들어가있기 때문인것 같다.

 

공식문서를 참조하여 readonly_fileds 를 사용하니 해결되었다.

readonly_fields = ('created_at',)

참조 : https://docs.djangoproject.com/en/4.0/ref/contrib/admin/

 

The Django admin site | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 

 

 

5) Review 어드민 상세 페이지에서 ReviewImage 필드 넣기

review/admin.py

class ReviewAdmin(admin.ModelAdmin):

...

    inlines = (
            ReviewImageInline,
        )

ReviewAdmin 위에 ReviewImageInline 추가

class ReviewImageInline(admin.StackedInline):
    model = ReviewImage
    extra = 0

    fieldsets = (
        ('image', {'fields': ('order','image','image_tag',)}),
        )

    def get_readonly_fields(self, request, obj): 
        if obj:
            return ('image_tag',)
        else:
            return ('image_tag',)

    def image_tag(self, obj):
        if obj.image:
            return mark_safe('<img src="{}" width="100" height="100"/>'.format(obj.image.url))

 

inline : admin page에서 다른 model을 edit할 수 있는 ability를 부여한다.

StackedInline : 다른 모델에 대한 내용을 세로로 쌓아서 보여줌

TabularInline : 다른 모델에 대한 내용을 가로로 늘려서 보여줌

 

model에 가져올 모델 정하고 extra 는 숫자만큼 빈 오브젝트를 만든다.

이후에는 모델 어드민 작성할때와 동일하게 작성하면 된다.

참조 : https://clownhacker.tistory.com/145

 

Django TabularInline and StackedInline

TabularInline TabularInline은 django.contrib.admin이 제공하는 같은 admin page에서 다른 model을 edit할 수 있는 ability를 부여한다. 예를 들어, photos/models.py에 Photo model이 있다고 하자. 그리고 room..

clownhacker.tistory.com