IT world

[Django] 24.02.23 Django 실습2 본문

모두의 연구소(오름캠프)/AI 모델 활용 백엔드 개발 과정

[Django] 24.02.23 Django 실습2

엄킹 2024. 2. 23. 17:38

Django 실습을 진행했고 오늘은 Django 템플릿 상속과 db에 대해서 설명하려고 한다.

 

Django 템플릿 상속 활용

템플릿 상속이란?

중복되는 코드를 한 번만 작성하고 재사용하는 기능을 뜻한다. 만약 메뉴바를 만들었는데 메뉴가 추가 되었다면? 해당 메뉴는 모든 html 페이지에 표현되어야하는데 몇 백개 되는 html 내용을 다 바꾸는 것은 비효율적이다. 따라서 중복되는 코드를 하나의 html 파일에 넣어서 효율적으로 관리하고자 하는 것이 템플릿 상속이다.

 

즉 중복되는, 기본 뼈대가 되는 문서를 기본 템플릿으로 정하고, 이는 공통의 코드이므로 다른 문서에서 기본 템플릿 코드가 필요하면 상속하여 가져다 사용하는 것이다.

 

중복되는 코드를 작성할 파일을 생성하며 주로 파일 이름은 base.html로 생성한다. 중복되는 코드를 붙여넣고 중복 코드 중간에 페이지 별로 다른 코드가 들어가는 부분에는 {% block 변수명 %} {% endblock %}을 작성한다. 각 페이지에서 {% extends 'base.html' %}을 작성하여 공통 코드를 상속받고 개발 코드는 base.html에 작성한  {% block 변수명 %} {% endblock %} 을 다른 페이지에서 사용하여 코드를 작성하기 위해  {% block 변수명 %} {% endblock %} 사이에 출력할 코드를 작성한다.

 

아래 예시처럼 상속받을 html 파일을 작성하고, block과 endblock 사이에 출력할 코드를 작성하면 되고 base.html에  {% block contents %} {% endblock %},  {% block view%} {% endblock %}  처럼 여러개 존재한다면, 동일 변수의 명칭으로 사용된 block 위치에 내용을 출력한다.

<!-- base.html -->

<body>
    {% block contents %}
    {% endblock %}
    .
    .
    . 
    {% block view %}
    {% endblock %}
</bod>
 {# A.html #}
 
 {% extends 'base.html' %}
 
 {% block contents %} 
 '각 페이지에서 출력할 코드 작성'
 {% endblock %}

위의 예시 코드를 보면  A.html의 출력 코드는 base.html의  {% block contents %} {% endblock %} 위치에 출력된다.

 

실습 과제로 bootstrap의 무료 템플릿을 사용하여 간단한 쇼핑몰 페이지를 만들었다. startproject tutorialdjango 를 만들고 main, product, qna 앱을 만들었고 무료 템플릿을 사용하면 정적 파일들 js, css, assets 등 static파일을 만들어 관리했고 templates > base 폴더를 만들어 공통으로 처리될 base.html 파일을 만들었다. 

# tutorialdjango > setting.py

TEMPLATES = [ # templates 폴더 기본 디렉토리 설정
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / "templates"],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]  # static 폴더를 기본 디렉토리로 설정

 

[폴더 구조]

static 폴더에는 무료템플릿에 사용된 js, css, assets 파일을 그대로 사용했으며 각각의 html 파일에서 static 폴더를 load해야하고 경로를 수정해야 한다.

{% load static %}

...

<link href="{% static 'css/styles.css' %}" rel="stylesheet" />

 

무료 템플릿의 공통 코드를 분석한 후 base.html을 작성했다. 상단의 이미지와 title을 보여주는 nav 와 하단의 footer 내용은 모든 html 파일에 공통으로 사용되어 해당 부분을 공통 코드로 작성했고, nav 와 footer 사이에 {% block contents %} {% endblock %}를 작성하여 해당 부분에 상속한 페이지들의 코드를 출력했다.

{% load static %}
<!DOCTYPE html>
<html lang="en">
    <head>
        <!-- 각종 meta, link 코드 작성 -->
        <!-- static 경로 작성 -->
        <link href="{% static 'css/styles.css' %}" rel="stylesheet" />
    </head>
    <body>
        <!-- Navigation-->
        <nav class="navbar navbar-expand-lg navbar-light bg-light">
            code ~ 
        </nav>

		<!-- 해당 위치에 각 페이지 내용 출력 -->
        {% block contents %}
        {% endblock %}

        <!-- Footer-->
        <footer class="py-5 bg-dark">
        	code ~
        </footer>
        
        <!-- JS static 호출 -->
        <script src="{% static 'js/scripts.js' %}"></script>
    </body>
</html>

 

main page는 등록한 상품의 판매량이 많은 6개만 출력되도록 설정했다.

# tutorialdjango > urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("main.urls")),
]

# main > urls.py
urlpatterns = [
    path("", views.index, name="index"),
    # path("about/", views.about, name="about"),
]

# main > views.py
def index(request):
    # 판매량이 많은 6개를 호출 
    sotred_product = sorted(mock_data_list, key=lambda x: x["sale_count"], reverse=True)[:6]  
    context = {"mock_data_list": sotred_product} 
    return render(request, "main/index.html", context)

 

base.html를 상속받아 각 페이지별 내용을 출력하기 위해 index.html를 작성했다

{% extends 'base/base.html' %} {# base.html 상속 #}
{% load static %} {# static 폴더를 load 한다. #}
{% block contents %} {# 출력할 내용을 작성하기 위한 시작 지점 설정 #}
<!-- Header-->
<header class="bg-dark py-5">
    code ~
</header>
<!-- Section-->
<section class="py-5">
    <h2 class="fw-bolder mb-5 text-center">Main Page</h2>

    <div class="container px-4 px-lg-5 mt-5">
        <div class="row gx-4 gx-lg-5 row-cols-2 row-cols-md-3 row-cols-xl-3 justify-content-center">
            {% for product in mock_data_list %} 
            <div class="col mb-5">
                <div class="card h-100">
                    <!-- Product image-->
                    <img class="card-img-top" src="https://dummyimage.com/450x300/dee2e6/6c757d.jpg" alt="..." />
                    <!-- Product details-->
                    <div class="card-body p-4">
                        <div class="text-center">
                            <!-- Product name-->
                            <h5 class="fw-bolder">{{product.name}}</h5>
                            <p>{{product.content}}</p>
                            <!-- Product price-->
                            <p>${{product.price}}</p>
                        </div>
                    </div>
                    <!-- Product actions-->
                    <div class="card-footer p-4 pt-0 border-top-0 bg-transparent">
                        <div class="text-center"><a class="btn btn-outline-dark mt-auto" href="#">View options</a></div>
                    </div>
                </div>
            </div>
            {% endfor %}
        </div>
    </div>
</section>
{% endblock %}

{# {% block contents %} ~ {% endblock %} 사이의 내용을 출력한다. #}

위 코드를 분석하면 extends를 통해 base.html를 상속받았고 {% block contents %} {% endblock %} 사이에 출력 내용을 작성하여 base.html의 {% block contents %} {% endblock %}위치에 출력되도록 했다. 또한 반복문을 사용하여 views.py에서 context로 보낸 객체를 사용하여 화면에 출력될 내용을 작성하도록 했다.

[실행 결과]


Django 기본 DB 사용

Django의 기본 DB는 파이썬 언어로 간단하게 Control 할 수 있다는 장점이 있다.

 

우선 새로운 프로젝트로 만들어 관리했고 DB에 내용을 저장하고 이미지를 출력하고 검색하는 실습 예제를 진행했다.

각각의 앱에서 DB 사용을 위한 작업을 실시 했다.  makimigrations는 Django에서 제공하는 모델의 변경사항들을 감지하고 기록하는 역할을 하고, migrate는 그러한 기록된 파일들과 설정값들을 읽어서 그 변경사항을 db에 저장하는 역할을 한다.

  1. 1. python manage.py makemigrations : models에 적용한 변경 내용을 기반으로 새로운 migrations 파일을 만든다.
  2. 2. python manage.py migrate : 해당 마이그레이션 파일을 DB에 반영한다.

관리자 계정을 생성하여 admin page에 접속할 수 있도록 설정한다. → python manage.py createsuperuser

나는 django admin page에서 BLOG > POST에 3개의 게시글을 추가했고, models.py 모델을 통해 Post class에 할당했다.

[admin page 게시글 추가]

 

db를 control 하기 위해 model 작성

# blog > models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    contents = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True) # 처음 생성될 때에만
    updated_at = models.DateTimeField(auto_now=True) # 수정될 때마다

    # 해당 Post를 대표하는 내용을 작성
    # 매직 메서드 __str__, __repr__을 사용하여 해당 게시글에 대한 내용을 출력
    def __str__(self):
        create_time = self.created_at.strftime("%Y-%m-%d %H:%M:%S")
        update_time = self.updated_at.strftime("%Y-%m-%d %H:%M:%S")
        return f'제목: {self.title}, 생성 시간: {self.created_at}, 수정 시간: {self.updated_at}'

[title 사용자 정의에 따라 출력]

 

그 후 admin과 연결하여 DB에 생성한 데이터를 Post class 객체에 할당? 한다.(이 부분은 정확하지 않아, 추후 정확한 내용 확인 후 수정하겠다. 나는 이렇게 이해하는게 빨라서 이렇게 작성함..)

from django.contrib import admin
from .models import Post # models.py의 클래스명

admin.site.register(Post)

 

blog > views.py에 DB에 저장된 데이터를 Post 객체를 통해 호출한 후에 각 페이지에 해당 내용을 출력했다.

# blog > views.py

from django.shortcuts import render
from .models import Post


def blog_list(request):
    blogs = Post.objects.all() # Post 객체에 저장된 DB 데이터 전부 호출
    context = {"db": blogs}
    return render(request, "blog/blog_list.html", context)


def blog_detail(request, pk):
    blog = Post.objects.get(pk=pk)
    context = {"db": blog}
    return render(request, "blog/blog_detail.html", context)


def blog_test(request):
    return render(request, "blog/blog_test.html")

 

 

ORM이란 우리가 만든 모델 클래스(Post class)와 DB에 생성된 테이블을 자동으로 연관지어 주는 기술로 우리가 DB를 직접 조작할 필요 없이 모델 클래스의 python 문법을 통해서 DB를 조작할 수 있는 편리한 기술이다.

 

실습 중간 터미널에서 python manage.py shell 입력 후 DB 작업을 연습했다.

 

1. from blog.models import Post → blog > moldes의 Post 객체 import

2. Post.objects.all() DB와 연결된 Post 객체의 모든 데이터 호출

3. Read 

  • Post.objects.all().order_by('-pk')  pk를 기준으로 역순으로 정렬
  • Post.objects.filter(id__lt=2) id가 2보다 작은 것을 출력. 
  • Post.objects.filter(id__gt=2) id가 2보다 큰 것을 출력.
  • Post.objects.filter(title__contains='1') title에 1이 포함되어 있는것을 찾음

4. Create

  • q = Post.objects.create(title='c4', contents='c44')    c4라는 title과 c44라는 contents를 가진 객체 생성
  • q.save() 반드시 save()를 해야 db에 반영

5. Delete

  • q = Post.objects.get(pk=3)    pk가 3인 것을 가져와서  
  • q.delete()   삭제한다.

6. Update

  • q = Post.objects.all()[0] → 전체 데이터 중 가장 처음의 데이터를 호출
  • q.title = "hello world" 데이터 수정
  • q.save() 데이터 DB 반영

간단한 예시로 해당 url로 접속 시 경로의 이름으로 새로운 데이터를 생성해 DB에 저장하는 것을 작성했다.

해당 경로로 접속 시 orm, jeju 데이터를 생성한다.

http://127.0.0.1:8000/blog/create/orm

http://127.0.0.1:8000/blog/create/jeju

# blog > urls.py
from django.urls import path
from . import views

urlpatterns = [
    path("", views.blog_list, name="blog_list"),
    path("test/", views.blog_test, name="test"),
    path("<int:pk>/", views.blog_detail, name="blog_detail"),
    path("create/<str:title>/", views.blog_create, name="blog_create"), # 추가
]

# blog > views.py
def blog_create(request, title): 
    # url의 title을 통해 Post class 의 속성 값을 작성
    contents = 'hello world {title}'
    q = Post.objects.create(title=title, contents = contents) # 데이터 새로 생성
    q.save() # DB에 반영
    
    # urls.py에 blog_list라는 name을 가진 경로로 바로 접속하도록 한다
    return redirect("blog_list")

 


추가로 이미지를 저장하고 검색하는 사이트를 만들었다.

이미지 사용을 위해 pip install pillow 입력하여 pillow 라이브러리를 설치했고 이미지 저장 시 이미지를 저장할 경로를 작성했다. urls.py에 MEDIA URL에 대한 처리를 작성했다. 그 후 db 내용을 호출하여 blog_list.html에 출력했다.

# tutorialdjango > settings.py

# 이미지 저장 시 경로 설정
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"


# tutorialdjango > urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('blog/', include('blog.urls')),
]

# MEDIA URL을 MEDIA ROOT로 처리하겠다.
# settings.MEDIA_URL 해당 url로 접속하면 document_root=settings.MEDIA_ROOT로 처리하겠다. 
# 우리가 settings.py 설정한 미디어 경로로 처리하겠다
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

# blog > views.py
def blog_list(request):
    db = Post.objects.all()
    context = {
        "db": db,
    }
    return render(request, "blog/blog_list.html", context)

 

blog_list.html에는 검색 기능을 추가하여 검색 조건에 맞는 db내용을 출력하도록 했으며 검색 버튼 클릭 시 본인을  호출하여 본인을 호출하는 함수를 수행하여 해당 검색 조건에 맞는 내용을 재출력하도록 작성했다.

<!-- blog_list.html-->

<!-- action에 대한 행동이 없으므로 본인이 본인을 호출하게 된다. 
또한 input 태그의 내용을 통해 db에 조건으로 사용한다. -->
<form action="" method="get">
    <input name="q" type="search">
    <button type="submit">검색</button>
</form>


blog > views.py
def blog_list(request):
    # blog_list의 검색하기를 누르면 검색 내용이(input에 작성한 내용) request에 담긴다.
    if request.GET.get('q'): # q는 input 태그의 name 
        q = request.GET.get('q')

        # 즉 input 태그 중 q의 값을 filter를 통해 title과 contets 필드중에 해당 값이 존재하면 해당 값을 전부 출력
        # input 태그에 값을 1로 하면 title과 contents 필드에 1이란 값을 전부 호출한다.(like %'1'%)
        # 따라서 1, 11, 111, 111..... 1이 포함된 모든 데이터가 출력된다.
        db = Post.objects.filter(Q(title_icontains=q) | Q(contents_icontains=q)) # title 과 contents의 내용에서 q의 내용이 있는지 확인한다.

    else:
        db = Post.objects.all()
    context = {
        "db": db,
    }
    return render(request, "blog/blog_list.html", context)

 

검색을 실시하면 해당 검색 조건을 db에서 찾아 반환된 내용을 다시 blog_list.html에 출력한다.

Comments