인프런 커뮤니티 질문&답변

뤀쪼님의 프로필 이미지
뤀쪼

작성한 질문수

파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트

장고 기본 CBV API (Generic display views) (2)

bootstrap 적용 풀림 문제

해결된 질문

작성

·

895

1

1)
안녕하세요 강사님,
함수뷰 post_list 부트스트랩(검색 기능, 깔끔한 테이블) 적용이 정상적으로 진행됐습니다. 클래스 뷰 PostListView역시 bootstrap4까지 더해서 pagination이 정상적으로 작동되는 것을 확인했습니다. 그런데 장식자 뷰 강의를 넘어 진행하면서, 문득 아래처럼 부트스트랩이 풀린 모습을 확인했습니다. 이때 다음과 같이
{{ is_paginated}}
    {{ page_obj }}

    {% if is_paginated %}
    {% bootstrap_pagination page_obj size="large" %}
  {% endif %}
부분이 소스와 웹에서 False None으로 표시됩니다. 그리고 검색에서 '세번 째'를 입력해도 {{ q }}가 적용되지 않고 웹에서는 "?q=세번+째"로 보이나 소스 부분에서는 공란으로 표시됩니다. value = ""/>


먼저 href 부분을 지우고 웹을 켜보니 아래와 같이 조악한 테이블마저 없어지는 걸 봐서 부트스트랩이 애매하게 적용되긴 하는 것 같습니다...
 
2) 혹시해서 코딩의 클래스뷰를 풀고 함수 뷰 post_list를 적용했을 때는 검색 기능은 원래대로 돌아왔지만, (페이지네이션은 함수뷰에서는 적용하지 않았으니 없어졌고) 깔끔한 테이블은 아직 돌아오지 않았습니다. 그래서 저는 paginatin(bootstrap4)의 문제 역시 깔끔한 테이블(bootstrap) 문제의 영향을 받았다고 생각했습니다. (bootstrap4를 위한 install 및 settings.py 적용 여부는 확인했습니다)
 
3) 그리고 html 자체는 보여지기 때문에 뷰에서 "html을 렌더링해라" 요청까지는 들어가는데 html에서 "이제 부트스트랩을 적용할게"하는 부분이 문제가 생겼다고 생각했습니다. html 상단에 href 링크도 그대로인 상태에서 작동하던 부트스트랩이 풀린 부분이 이해가 가지 않습니다.그 다음으로 부트스트랩 적용만 문제라면 왜 '클래스 리스트뷰'에서는 검색 기능이 작동하지 않는지 이해가 되지 않습니다. 함수 뷰로 작성한 post_list나 클래스 ListView를 상속해서 생성한 post_list는 기능상 같을 것으로 기대해서요...(어차피 검색 액션은 html에서 구현되어 있으니 이때는 html에 q를 넘기는 문제라고 짐작했습니다.)
 
(views.py, urls.py(instagram), post_list.html은 다음과 같습니다.)
 
views.py
from django.views.generic import ListView, DetailView, ArchiveIndexView, YearArchiveView
from django.http import HttpRequest, HttpResponse, Http404
from django.shortcuts import render, get_object_or_404
from .models import Post
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator

# post_list = login_required(ListView.as_view(model = Post, paginate_by = 10))

@method_decorator(login_required, name = 'dispatch')
class PostListView(ListView):  
    model = Post
    paginated_by = 10
   
post_list = PostListView.as_view()

# @login_required
# def post_list(request):
#     qs = Post.objects.all()
#     q = request.GET.get('q', '')
#     if q:
#         qs = qs.filter(message__icontains = q)
#     # instagram/templates/instagram/post_list.html
#     return render(request, 'instagram/post_list.html', {
#         'post_list' : qs,
#         'q' : q,
#     })

# def post_detail(request: HttpRequest, pk: int) -> HttpResponse:
#     post = get_object_or_404(Post, pk=pk)
#     # try:
#     #     post = Post.objects.get(pk=pk)
#     # except Post.DoesNotExist:
#     #     raise Http404
#     return render(request, 'instagram/post_detail.html',{
#         'post': post,
#         'object': post,
#     })

# post_detail = DetailView.as_view(
#     model = Post,
#     queryset = Post.objects.filter(is_public = True))

class PostDetailView(DetailView):
    model = Post
    #queryset = Post.objects.filter(is_public = True)

    def get_queryset(self):
        qs = super().get_queryset()
        if not self.request.user.is_authenticated:
            qs = qs.filter(is_public = True)
        return qs

post_detail = PostDetailView.as_view()

# def archives_year(request, year):
#     return HttpResponse(f"{year}년 archives")

post_archive = ArchiveIndexView.as_view(model = Post, date_field = 'created_at', paginate_by=10)

post_archive_year = YearArchiveView.as_view(model = Post, date_field = 'created_at', make_object_list = True)
 
urls.py
from django.urls import path, re_path, register_converter
from . import views
from .converters import YearConverter, MonthConverter, DayConverter

register_converter(YearConverter, 'year')
register_converter(MonthConverter, 'month')
register_converter(DayConverter, 'day')

app_name = 'instagram' # URL Reverse에서 namespace 역할을 할 것

urlpatterns = [
    path('', views.post_list, name = 'post_list'),
    path('<int:pk>', views.post_detail, name='post_detail'),
    # path('archives/<year:year>/', views.archives_year),
    # re_path(r'archives/(?P<year>\d{4})/', views.archives_year),
    # path('archives/<int:year>/', views.archives_year),
    # re_path(r'(?P<pk>\d+)/$', views.post_detail),
    path('archive/', views.post_archive, name='post_archive'),
    path('archive/<year:year>/', views.post_archive_year, name = 'post_archive_year'),
    # path('archive/<year:year>/<month:month>', views.post_archive_month, name = 'post_archive_month'),
    # path('archive/<year:year>/<month:month>/<day:day>', views.post_archive_day, name = 'post_archive_day'),
   
]
 
post_list.html
{% load bootstrap4 %}

<!doctype html>
<html lang = "ko">
<head>
    <meta charset = "utf-8"/>
    <title> Instagram / Post List </title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body>

    <form action = "" method = "get">
        <input type = "text" name = "q" value = "{{ q }}"/>
        <input type = "submit" value = "검색"/>
   </form>
   
    <table class = "table table-bordred table-hover">
        <tbody>
            {% for post in post_list %}
                <tr>
                    <td>
                        {{ post.pk }}
                    </td>
                    <td>
                        {% if post.photo %}
                            <img src = "{{ post.photo.url }}" style = "width: 100px;" />
                        {% else %}
                            No Photo
                        {% endif %}
                    </td>
                    <td>
                        <!-- <a href="{% url 'instagram:post_detail' post.pk %}"> -->
                        <a href="{{ post.get_absolute_url }}">
                            {{ post.message }}
                        </a>
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

    {{ is_paginated}}
    {{ page_obj }}

    {% if is_paginated %}
    {% bootstrap_pagination page_obj size="large" %}
    {% endif %}
</body>
</html>

답변 2

1

이진석님의 프로필 이미지
이진석
지식공유자

안녕하세요.

템플릿에서 클래스명으로 table table-bordred table-hover 라고 썼는 데,
부트스트랩 스타일이 적용되지 않은 것은 부트스트랩 CSS를 정상적으로 포함되지 않아서입니다.

보여주신 post_list.html 템플릿을 보시면
{% load bootstrap4 %}를 하셨구요. 이는 부트스트랩 버전4를 쓰셨는 데,

그 아래의 부트스트랩 CSS를 보시면 부트스트랩 버전 5를 쓰신 것을 확인하실 수 있습니다.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">

부트스트랩은 최근 버전 5가 출시되었습니다. 그래서 bootstrap cdn 사이트에서도 최근 버전인 5를 안내해주는 듯 하구요.

부트스트랩 버전4과 버전5는 지원하는 클래스명이 변경되었습니다. 부트스트랩 버전을 일치시켜주셔야 여러 기능들이 제대로 동작할 것입니다. 버전5가 아닌 버전4를 포함시켜주세요.

그리고 직접 부트스트랩 포함 태그를 작성하실 수도 있지만, 아래의 템플릿태그도 django-bootstrap4에서 지원을 하니 사용해보세요. js/css 포함 태그를 생성해줍니다.

{% bootstrap_javascript_url %}
{% bootstrap_css_url %}

강의에서는 부트스트랩4 기반으로 진행으로 하시고, 강의 완료 후에 부트스트랩5 기반으로 올려보시면 좋습니다. 얼마 전에 django-bootstrap5 라이브러리가 출시되었거든요.
https://django-bootstrap5.readthedocs.io/

---

그리고, 보여주신 뷰 코드에서 PostListView 클래스기반뷰와 post_list 함수기반뷰의 동작은 동일하지 않습니다. 최소한 템플릿 context data를 처리하는 부분이 다릅니다.

request.GET에서 q를 꺼내서 쿼리셋에 반영하는 부분과 => get_queryset 재정의
q 값을 context_data에 반영하는 부분 => get_context_data 재정의

이 두 가지를 PostListView 클래스에 구현해주셔야 합니다.

---

차근차근 살펴보세요. 또 질문주세요. 화이팅입니다. :-)

0

뤀쪼님의 프로필 이미지
뤀쪼
질문자

1)

link href 부분을 bootstrap4 버전으로 낮췄더니 글씨체나 pagination 기능 제대로 돌아오지 않았지만, 테이블 레이아웃 정도는 정상적으로 적용되었습니다. 그런데 이번에는 localhost:8000/instagram 페이지에 접속할 때

TemplateSyntaxError at /instagram/

'bootstrap4' is not a registered tag library. Must be one of:
라는 에러가 발생합니다.

settings.py에는 bootstrap4가 들어가있고, post_list.html의 {% load bootstrap4 %} 부분을 지우고 진행할 때는 다시 해당 에러가 발생하지 않아서 렌더링하는 html의 bootstrap4를 가져올 때 에러가 발생한다고 판단했습니다.

(bootstrap4는 이미 설치했고 settings.py에도 넣어두었고 이전에 html 렌더링이 잘 되는 것을 확인했는데, 갑자기 load할 때 문제가 생긴다는 것은 경로 문제인지 아니면 제가 이것저것 건드리면서 덮어쓰거나 할 때 문제가 생긴건지 확신이 서질 않습니다.)

 

2)

그래서 bootstrap4를 재설치하려고

pip install bootstrap4

pip install django_forms_bootstrap
pip3 install django-bootstrap4
세 가지 명령어를 통해서 bootstrap4를 다시 설치해주고 진행해도 같은 에러가 발생하는데 추가적으로 확인할만한 사항이 있을까요?

(전체 내용)

TemplateSyntaxError at /instagram/

'bootstrap4' is not a registered tag library. Must be one of: admin_list admin_modify admin_urls cache debugger_tags highlighting i18n indent_text l10n log static syntax_color tz widont

Request Method: GET
Request URL: http://localhost:8000/instagram/
Django Version: 4.0.5
Exception Type: TemplateSyntaxError
Exception Value: 'bootstrap4' is not a registered tag library. Must be one of: admin_list admin_modify admin_urls cache debugger_tags highlighting i18n indent_text l10n log static syntax_color tz widont
Exception Location: C:\Users\ukyanjo\.conda\envs\askcompany\lib\site-packages\django\template\defaulttags.py, line 1029, in find_library
Python Executable: C:\Users\ukyanjo\.conda\envs\askcompany\python.exe
Python Version: 3.9.12
Python Path: ['C:\\Dev\\askcompany', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\python39.zip', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\DLLs', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\lib', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany', 'C:\\Users\\ukyanjo\\AppData\\Roaming\\Python\\Python39\\site-packages', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\lib\\site-packages', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\lib\\site-packages\\win32', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\lib\\site-packages\\win32\\lib', 'C:\\Users\\ukyanjo\\.conda\\envs\\askcompany\\lib\\site-packages\\Pythonwin']
Server time:

Mon, 27 Jun 2022 03:11:58 +0000

이진석님의 프로필 이미지
이진석
지식공유자

django-bootstrap4 라이브러리를 설치하셨다면, 아래 공식 저장소의 설치 가이드를 확인해보세요. 

https://github.com/zostera/django-bootstrap4#installation

django-bootstrap4도 장고앱 형태로 제공되는 데요. 단순히 pip install 만으로 장고앱을 사용할 수 있는 것은 아닙니다. 사용할 장고앱은 현 프로젝트에 등록하는 과정이 필요합니다.

프로젝트에 등록된 장고앱에 한해서, 그 장고앱 내에 있는 템플릿태그와 모델/템플릿 등을 사용할 수 있게 됩니다. 아래의 에러메세지는 bootstrap4 라는 태그가 있는 장고앱을 찾을 수 없다라고도 이해하실 수 있습니다.

'bootstrap4' is not a registered tag library.

settings.INSTALLED_APPS가 아닌 다른 설정에 'bootstrap4'를 추가하셨을 수도 있고, settings.py 파일을 수정 후에 저장을 하지 않으셨을 수도 있습니다.

그리고, django-bootstrap4 라이브러리는 이 라이브러리 하나만 설치하시면, 강의에서는 충분합니다. 다른 라이브러리를 선택해서 추가로 설치하셨다면, 이에 대한 맥락(context)도 알려주셔야 제가 보다 좋은 답변을 드릴 수 있습니다.

뤀쪼님의 프로필 이미지
뤀쪼
질문자

INSTALLED APPS 리스트의

'bootstrap4',

부분을 지우고 똑같은 문자열을 다시 입력했을 뿐인데 정상적으로 작동했습니다.

->

'최초 INSTALLED APPS에 저장할 때 꼬여서 실제로는 저장되지 않았으나 저에게 보여질 때는 리스트 안에 있는 것 처럼 보여졌음'으로 이해되는데, 컴퓨터의 구조를 몰라서 이런 오류 메커니즘도 가능한지 모르겠네요.

 

시간 내주셔서 감사합니다.

이진석님의 프로필 이미지
이진석
지식공유자

장고에 포함된 파이썬 소스코드가 수정이 되면, 장고 개발서버가 자동 재시작이 되는 데요. 저장 후에 개발서버 재시작까지 확인해주시면 좋습니다. 재시작이 되었다는 것은 저장이 되었다는 의미니깐요.

"꼬여서 실제로는 저장되지 않았으나" 라는 상황이 어떤 상황일까요? 막연한 설명인데요. 학습을 이어가시며 이러한 설명들을 조금씩 줄여나가시면, 실력향상에 도움이 되실 것입니다.

화이팅입니다. :-)

뤀쪼님의 프로필 이미지
뤀쪼
질문자

제 기반 지식이 부족해 정말 막연한 가정-추측을 한거라 신경쓰지 않으셔도 됩니다 :) 화이팅 하겠습니다! 

이진석님의 프로필 이미지
이진석
지식공유자

👍

뤀쪼님의 프로필 이미지
뤀쪼

작성한 질문수

질문하기