Django FBV

Haks.·2025년 1월 19일
0

How to use

목록 보기
13/32

📖 Django

ORM
Admin Interface
Authentication
internationalization
URL Routing
Template Engin

  • 사용 이유 Flask 나 FastAPI 는 여러개 패키지를 가져다 써야하는데 모두 준비가 되있음

  • 개발 시간을 줄여주는 관리자 페이지의 마법

  • 개발 시간 및 코드를 줄여주는 모듈화의 마법

  • 확일화된 구조로 누가 코드를 봐도 적응시간 빠름

  • 수많은 패키지와 잘 구축된 커뮤니티

  • poetry : poetry는 Python의 의존성 관리와 패키지 관리를 쉽게 해주는 도구입니다. 이 명령어는 새로운 프로젝트를 시작할 때, 프로젝트의 메타데이터와 의존성 관리 파일을 생성합니다.

📝 MVT 구조

Model:DB 관련 알고리즘 View:메인알고리즘 Template: 화면단
장점 : 개발속도가 빠름, 코드재사용 및 모듈화 : 유연, 안전한 웹 애플리케이션 구축 : 보안
단점 : 잘 쓰려면 숙련 및 개념 탑재 필요

📌 Django 기본설정

  1. python -m venv .venv
  2. poetry init : 패키지 이름하고,n 만하고 다 엔터 완성
  3. poetry add django==버전 (버전입력안하면 최신 버전)
  • poetry add django-stubs
  1. 커맨드 + , : 파이참 인터프리터 -> show all 해서 내가 설정한 python 찾기
  2. existing 에서 해당 폴더 찾아서 venv->bin->python 해당 실행
  3. django-admin startproject config . : 해당 폴더에서 장고 기본파일들 생성
  4. Language,framwork 설정
  5. python manage.py startapp 앱 명 (REST API 기능을 가진 파일들 생성)
  6. settings.py에 생성한 폴더명 추가 ISTERTED_APP 에 'app명' , 주석을달아주거나 따로 리스트를 만들어서 추가
  • templates 을 render 해 줄거면 DIRS 에 BASE_DIR/'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",
            ],
        },
    },
]
  • urls.py

    • path('원하는경로/', 함수명)
  • return render(request, 'xx.html', context)

  • db browser for sqlite

    • db brwoser for sqlite 열어서 폴더안에 있는 sqlite3 열기
    • 간단하게 작업할때 모델에 들어가는거 확인해 가면서 작성가능
  • 블로그 모델 만들시

class 테이블명(models.Model):
  • python manage.py migrate : 모델에 있는 내용을 db에 처음 적용할때 사용
    • 기본으로 장고에 있는 db 넣는 명령어
  • python manage.py makemigrations : 모델 변경사항이 있을때 시행하는 명령어
    • 혹시 migartions 폴더에 파일이 생성이 안되면 settings.py 에 ISNTALLED_APP 이 추가가 안된것
    • migration.py 파일을 만듬, git commit 같은
    • 실제 DB에 영향 X, 실제 DB에 넣기 위한 정의를 하는 파일 생성
  • python manage.py migrate : migrations/ 폴더 안에 잇는 migration 파일들을 실제 DB에 적용함, git push 같은

📝 admin

관리자단 페이지에서 하나씩 수정할 수 있는 기능

  1. python manage.py migrate 한상태여야 접속가능
  2. python manage.py createsuperuser : admin 단에 들어가는 아이디 생성, superuser
  # admin.py admin 페이지에서 관리할 모델 임포트
  from.models import 모델명
  admin.site.register(모델명)
  • admin 페이지에서 모델 명 수정하면 python 코드가 바뀐다
  • class 모델안의 class Meta: 에서 verbose_name,verbos_name_plural 를 설정해서 바꾸면 테이블의 이름과 내용명을 설정해 줄 수 있다.
  • config -> settings.py -> 아래의 LANGUAGE_CODE -> "ko-KR" 로 바꾸면댐
  • 페이지에서 auto_now 가 포함된 것은 장고가 페이지단에서 보이게 안해 놨음
  • admin 을 좀더 쉽게 쓰기위해 데코레이터를 등록해 주면됨, 이걸 주로 쓸듯
from django.contrib import admin
from todo.models import Todo

# Register your models here.

@admin.register(Todo)
class TodoAdmin(admin.ModelAdmin):
    list_display = ( # 목록 페이지의 열으로 표시할 필드 지증
        'title',
        'description',
        'is_complete',
        'start_date',
        'end_date',
    )
    
    list_filter = ('is_complete',) # 필터링 옵션
    search_fields = ('title',) # 검색 창에서 검색 가능한 필드 지정
    ordering = ('start_date',)
    fieldsets = ( # 데이터 입력 페이지의 레이아웃을 커스터마이징, 섹션별로 나누어 표시
        ('Todo Info', {
            'fields': ('title', 'description', 'is_complete',)
        }),
        ('Date Range', {
            'fields': ('start_date', 'end_date',)
        }),
    )
  • 같은 이름을 가진 다른 url 이 있을수도 있으니 네이버 모바일 같은 admin.py 에 정의해 주어야 한다
  • url 로 들어갈수도 있게 한번에 만들려면
    admin.py
from django.contrib import admin
from bookmark.models import Bookmark

class BookmarkAdmin(admin.ModelAdmin):
    list_display = ['name', 'url'] # 이부분이 위에 표시됨 컬럼이 추가되는 느낌?
    list_display_links = ['name', 'url'] # 클릭 가능하게 

admin.site.register(Bookmark, BookmarkAdmin) # 추가로 등록 해줘야함

📝 Django ORM

📌 기본설정

  • poetry add ipython
  • poetry add django-extensions
  • INSERTED_APP에 'django_extensions' 추가
    • python manage.py shell_plus
    • 우리가 등록한 모듈을 전부 한번에 등록해서 사용 가능하게 해줌

🧑‍💻 테스트 코드

  • objects => 모델 매니저/쿼리를 할수 있게 해줌, obeject 붙어 잇으면 아이건 쿼리구나 생각
  • 쿼리문에 해당하는 django orm 메서드
  • SELECT
bookmarks = Bookmark.objects.all() 
# SELECT * FROM bookmark
bookmark = Bookmark.objects.get(pk=pk)
# SELECT * FROM bookmark WEHER id=id LIMIT 1
Bookmark : [Bookmark] = Bookmark.objects.filter(name='네이버')
# SELECT * FROM bookmark WHERE name ='네이버'

now = datetime.now()
Bookmark : [Bookmark] = Bookmark.objects.filter(created_at__gte = now) 
# gte gratter than equal >= , gt > 
# SELECT * FROM bookmark WHERE created_at > now
# lte, lt >= now
Bookmark : [Bookmark] = Bookmark.objects.filter(nae__icontains='네이')
# SELECT * FROM bookmark WHERE name LIKE '%네이%'

In [18]: Bookmark.objects.filter(name__startswith='다')
Out[18]: <QuerySet [<Bookmark: 다음 https://daum.net>]>

In [18]: Bookmark.objects.filter(name__endswith='음')
Out[18]: <QuerySet [<Bookmark: 다음 https://daum.net>]>

In [21]: Bookmark.objects.filter(name__in=['다음','네'])
Out[21]: <QuerySet [<Bookmark: 다음 https://daum.net>]>

In [22]: Bookmark.objects.filter(name='네이버',url__startswith='https://naver')
Out[22]: <QuerySet [<Bookmark: 네이버 https://naver.com>]
  • CREATE

In [24]: Bookmark.objects.create(name='야후', url="https://yahoo.com")
Out[24]: <Bookmark: 야후 https://yahoo.com>

In [28]: bookmark = Bookmark(name='야후2', url='https://yahoo.com')

In [29]: bookmark
Out[29]: <Bookmark: 야후2 https://yahoo.com>
# 아직 생성 하지 않음
In [30]: bookmark.save() # 생성

In [32]: b = _.first() # 마지막 아웃풋 값의 첫번째값을 가져와라
# _ : 전 아웃풋 전부 가져오는것 objects.all()

In [33]: b
Out[33]: <Bookmark: 네이버 https://naver.com>

In [33]: b
Out[33]: <Bookmark: 네이버 https://naver.com>

In [34]: b.id
Out[34]: 2

In [35]: b.id = None

In [36]: b.save() # 장고에서 id 값을 마지막에 auto_increment 해서 추가함
# 장고에서는 id 값이 있냐 없냐에 따라 이 객체가 db에 있냐 없냐 판단하기에 중요한 값임
In [37]: b.id
Out[37]: 8

bookmark_list = [Bookmark(name=f" 테스트 구글 {i}", url=f'https://google.com') for i in range(10)]

Bookmark.objects.bulkcreate(bookmark_list)
  • UPDATE

In [39]: Bookmark.objects.filter(url__icontains='naver.com')
Out[39]: <QuerySet [<Bookmark: 네이버 https://naver.com>, <Bookmark: 네이버 https://naver.com>]>

In [40]: Bookmark.objects.filter(url__icontains='naver.com').update(name='Naver')
Out[40]: 2 # 3개가 업데이트 되었다.

In [41]: Bookmark.objects.filter(url__icontains='naver.com')
Out[41]: <QuerySet [<Bookmark: Naver https://naver.com>, <Bookmark: Naver https://naver.com>]>
  • DELETE

In [42]: b
Out[42]: <Bookmark: 네이버 https://naver.com>

In [43]: b.id
Out[43]: 8

In [44]: b.delete()
Out[44]: (1, {'bookmark.Bookmark': 1})

In [45]: b
Out[45]: <Bookmark: 네이버 https://naver.com>

In [46]: b.id # 장고에서 id 값이 없으면 db 에 없는 것임

In [47]: Bookmark.objects.all()
Out[47]: <QuerySet [<Bookmark: Naver https://naver.com>, <Bookmark: 다음 https://daum.net>, <Bookmark: 네이버 https://m.mobile.com>, <Bookmark: 야후 >, <Bookmark: 야후 https://yahoo.com>, <Bookmark: 야후2 https://yahoo.com>]>

In [48]: Bookmark.objects.filter(name__icontains='야후')
Out[48]: <QuerySet [<Bookmark: 야후 >, <Bookmark: 야후 https://yahoo.com>, <Bookmark: 야후2 https://yahoo.com>]>

In [49]: Bookmark.objects.filter(name__icontains='야후').delete()
Out[49]: (3, {'bookmark.Bookmark': 3})

In [50]: Bookmark.objects.all()
Out[50]: <QuerySet [<Bookmark: Naver https://naver.com>, <Bookmark: 다음 https://daum.net>, <Bookmark: 네이버 https://m.mobile.com>]>

📝 블로그 실습 구현

from django.db import models

# models.py 구현
# Create your models here.

# 제목
# 본문
# 작성자
# 수정일자
# 카테고리

class Blog(models.Model):
    CATEGORY_CHOICES = (
        # db에 들어갈 글자 : 실제로 보여질 글자
        ('free', '자유'),
        ('travel', '여행'),
        ('cat', '고양이'),
        ('dog', '강아지'),
    )
    # enum 같은 역할  charfield 인데도 불구하고 고류는 걸로 나오게함
    category = models.CharField('카테고리', max_length=20, choices = CATEGORY_CHOICES)
    title = models.CharField('제목', max_length=100)
    content = models.TextField('본문')

    created_at = models.DateTimeField('작성일자', auto_now_add=True)
    updated_at = models.DateTimeField('수정일자', auto_now=True)

# admin.py

from django.contrib import admin
from .models import  Blog

# Register your models here.

@admin.register(Blog)
class BlogAdmin(admin.ModelAdmin):
    ...

  • a = href 가 수정되더라도 urls name 에 해당하는것을 가르키는 것을 만들면 해당 path 경로로 들어갈 수 있게 만들 수 있다. 꼭 네이밍 달기
path('blog/detail/<int:pk>/', views.blog_detail, name = "blog_detail"), # 네임에 해당하는 url path 경로를 잡아주기 위해 사용
    <h1>블로그 목록</h1>
    {% for blog in blogs%}
        <p>
            <!-- 이름이 blog_detail 인 것의 path 로 가겠다. id 값은 저거고 -->
            <a href="{% url 'blog_detail' blog.pk %}">
                {{ blog.title }} - {{ blog.created_at|date:"Y-m-d" }}
            </a>
        </p>
        
    {% endfor %}

📝 쿠키와 세션

사용자 행동에 대한 정보를 기억하기 위해 필요

  • 웹은 기본적으로 상태가 없는 stateless 임 그래서 매번의 요청이 독립적이여서 브라우저는 기억하고 있어야 할 정보를 같이 보내는 작업이 필요함(로그인 같은),

  • 그래서 보낼떄 쿠키에 담아서 보냄

  • Cookie : 정보를 유저단에 저장함, 브라우저에 저장되기에 보안에 취약함

  • session : 기본적으로 쿠키에 유사하지만,브라우저에서 보안상의 이유로 갖고있을 수 없는 것들을 가지고 있음

항목쿠키세션
저장 위치클라이언트서버
저장 형식텍스트객체 형태
종료 시점설정한 시간 (없으면 브라우저 종료 시)정확한 종료 시점 알 수 없음
자원 사용클라이언트 자원서버의 자원
용량 제한한 도메인당 20개, 쿠키당 4KB, 총 300개서버가 허용하는 한 제한 없음
from django.shortcuts import render , get_object_or_404
from .models import Blog

# Create your views here.
def blog_list(request):
    blogs = Blog.objects.all()

    # 리턴값은 string 방문자수 는 1이였으면 2가 됨
    visits = int(request.COOKIES.get('visits', 0)) + 1 # get 은 뒤에 key 값을 가져오는데 혹시 없으면 defaul로 지정해 논 0을 가져옴

    request.session['count'] = request.session.get('count',0) + 1

    context = {
        'blogs': blogs,
        'count' : request.session['count']
    }
    response =  render(request, 'blog_list.html', context)

    response.set_cookie('visits', visits)

    return response
    
# html 에 추가 <h1>{{ count }}</h1>

📝 장고 로그인, 로그아웃(admin)

Django autentication, Autentication views 활용해서 제작

  1. urls.py
    • path('accounts/', include("django.contrib.auth.urls")),
  2. 되나 확인, 확인후 template 만들어 주자
  3. 화면에 나와있는 registration/login.html 만들어주기 (폴더만들고 안에 login 만든거임)
<!-- login.html -->
<body>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }}
        <button>submit</button>
    </form>
</body>
  1. 들어가서 제출 누르면 이제 로그인이 처리된 후 어디로 가야할지 몰라서 profile 로 리다이렉트 된거임
  2. config settings에 , LOGIN_REDIRECT_URL = '/' , 입력
  3. 페에지에서 element 확인해 보면 잘 나와 있는지 확인 가능
  4. 잘 로그인 되어 있는지 확인하려면 메인 페이지로 redierect 했으니 거기에 코드추가
    • <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
  5. logout 페이지
        <div style ="text-align: right">
            <a href="{% url 'login' %}">로그인</a>
        {#  <a href="{% url 'logout' %}">로그아웃</a> <!-- 현재 logout은 get요청으로 들어간다 그래서 이걸 형식을 바꿔줘야 작동한다 -->#}
            <form action="{% url 'logout' %}" method="POST" style="display: inline">
                {% csrf_token %}
                <button>로그아웃</button>
            <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
            </form>
        </div>
  1. settings.py 에
    • LOGOUT_REDIRECT_URL ='/' 작성
  2. 로그인이 되어있어도 로그인 버튼이 나오고 로그아웃이 되어있어도 나오는 부분을 처리
            {% if request.user.is_authenticated %}

            {# <a href="{% url 'logout' %}">로그아웃</a> <!-- 현재 logout은 get요청으로 들어간다 그래서 이걸 형식을 바꿔줘야 작동한다 -->#}
                <form action="{% url 'logout' %}" method="POST" style="display: inline">
                    {% csrf_token %}
                    <button>로그아웃</button>
                <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
            {% else %}
                <a href="{% url 'login' %}">로그인</a>
            {% endif %}

🧑‍💻 최종 코드

# urls.py
from django.contrib import admin
from django.urls import path, include

from blog import views
from member import views as member_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('',  views.blog_list, name = "blog_list"),
    path('blog/detail/<int:pk>/', views.blog_detail, name = "blog_detail"), # 네임에 해당하는 url path 경로를 잡아주기 위해 사용
    path('accounts/', include("django.contrib.auth.urls")), # 이걸해줘야 logout  path도 자동으로 인식됨
    path('signup/', member_views.sign_up, name = 'signup'),
    path('login/', member_views.login, name = 'login'),
]
<!-- login.html 생성후 제작 POST 보내는 폼 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="POST">
        <!-- csrf 가 없으면 서버에서 거부함 -->
        {% csrf_token %} <!-- 클라이언트와 서버가 공유되고 있는 인증 값임, settings 에 보면 middleware.csrf가 있을거임, 그래서 장고의 모든 post 요청에는 이런것을 검증하는 로직이 있을거임 -->
        {{ form.as_p }}
        <button>submit</button>
    </form>
</body>
</html>

<!-- blog_list 페이지 수정 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <nav>
        <div style ="text-align: right">
            {% if request.user.is_authenticated %}

    {#            <a href="{% url 'logout' %}">로그아웃</a> <!-- 현재 logout은 get요청으로 들어간다 그래서 이걸 형식을 바꿔줘야 작동한다 -->#}
                <form action="{% url 'logout' %}" method="POST" style="display: inline">
                    {% csrf_token %}
                    <button>로그아웃</button>
                <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
            {% else %}
                <a href="{% url 'login' %}">로그인</a>
            {% endif %}
        </div>
    </nav>
    <h1>블로그 목록</h1>
    <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
    <h1>{{ count }}</h1> <!-- 이렇게 출력해 주지 않으면 클라이언트가 알 수가 없음 -->
    {% for blog in blogs%}
        <p>
            <a href="{% url 'blog_detail' blog.pk %}">
                {{ blog.title }} - {{ blog.created_at|date:"Y-m-d" }}
            </a>
        </p>
        
    {% endfor %}
</body>
</html>

📝 회원가입 페이지 제작

모듈화, 어디서든 쓰일 수 있게 제작

  1. app 생성 , python manage.py startapp member
  2. settings.py, INSTALLED_APP 에 추가
  3. member/views.py 에 기본적인 함수 작성
  4. templates/registration/signup.html 생성
  5. urls.py 에 추가 임포트시 그대로 가져오면 views 가 2개여서 충돌이 일어나면 as member_views 라고 추가
  6. views.py
  • form을 장고설정에서 얻기 위해
from django.contrib.auth.forms import UserCreationForm
    form = UserCreationForm()
    context = {
        'form' : form
    }

생성 및 signup.html 작성

    <h1>회원가입</h1>
    <form method="POST">
        {% csrf_token %}
        {{ form.as_p }} <!-- 로그인 페이지에는 장고에서 기본적으로 제공되는 views 가있어서 이렇게 바로 쓸 수 있지만 우리가 만드는페이지에서는 아직 정의 되지 않아서 사용 불가능 하다 -->
        <button>회원가입</button>
  1. 현재 post 요청이 잘보내지는 것을 확인 할 수 있음 (터미널 로그에 잘나옴)
  2. 이제 페이지에서 보낸것을 받아서 사용하는 것을 만들어야함
    username= request.POST['username']
    password1 = request.POST['password1']
    password2 = request.POST['password2']
  1. 근데 처음 페이지에 접속할떄 get 요청 박예 없어서 post 한게 없어 오류가 난다
  2. 그래서 get을 사용하는 것이 좋다
    username= request.POST.get('username') # get은 있으면 받고 없으면 넘긴다
    password1 = request.POST.get('password1')
    password2 = request.POST.get('password2')
  1. 유저네임 중복화인작업
  2. 패스워드가 맞는지, 정책에 올바른지(대소문자, 8자이상, Form에 들어있는 기능을 사용할 것임)
    if request.method =='POST':
        form = UserCreationForm(request.POST) # 넣어주고 확인만 해주면 됨
        if form.is_valid():
            form.save()
            return redirect('/accounts/login') # django.shortcut 에서 임포트하면됨
    else :
        form = UserCreationForm # get 요청일때도 써야하니까
  1. 마지막으로 블로그 목록 페이지에 회원가입 페이지도 만들어 줘야함
  2. <a href="{% url 'signup' %}">회원가입</a>
  3. 코드 단순화 가능
    form = UserCreationForm(request.Post or None) # 이걸 사용 하면 if request.method =="post") 이걸 삭제 시킬수 있음

    if form.is_valid():
        form.save()
        return redirect('/accounts/login') # django.shortcut 에서 임포트하면 됨
  1. 정적으로 경로를 적어놓으면 고치기 어려우니까 settings.py 에
    • LOGIN_URL = '/accounts/login/' 를 적고
    • redirect 경로에 redirect(settings.LOGIN_URL), 임포트 하고
    if form.is_valid():
        form.save()
        from django.conf import settings  # 장고의 config 에서 가져오니까 나중에 config 이름이 바껴도 오류를 발생시키지 않고 가져옴
        return redirect(settings.LOGIN_URL) # django.shortcut 에서 임포트하면됨

🧑‍💻 로그인 페이지

def login(request):
    form = AuthenticationForm(request, request.POST or None)
    if form.is_valid():
        from django.contrib.auth import login as django_login
        django_login(request, form.get_user())
        # return redirect('/') # 가능
        from django.urls import reverse
        return redirect(reverse('blog_list')) # config url에 있는 name 에 해당하는 것을 보냄
    context = {
        'form' : form
    }
    return render(request, 'registration/login.html', context)

📝 모델 author 추가

  1. 유저 가져오기 models 에 임포트
from django.contrib.auth import get_user_model # 장고에 어떤것이 연결되어 있는지 확인한 후 가져오는 것이 이 함수
# 장고에 기본 설정에 있는 user 를 가져오는 것임

User = get_user_model()

# 블로그 클래스 안에
    author = models.ForeginKey(User)
  1. makemigrations 하면 기본값을 null Ture로 하지않았고 default 값도 없기에 기본값을 넣을거냐 아무동작도 하지않을꺼냐 라고 물음, 기존에 넣은것이 있기에 어떻게 할거냐 묻는거임
  2. admin 페이지로 가서 넣을 값의 id 값을 확인한 후 다시 makemigraitons -> 1
  3. user_id 에해당하는 1번 누르면 => default 값을 1로준 것이 생성된것을 migrations 에서 확인 가능
  4. blog model에 추가
    author = models.ForeignKey(User, on_delete=models.CASCADE) # 유저가 삭제되면 같이 삭제되는게 블로그
    # ForeignKey 같은 경우는 on_delete를 사용해야 함
    # models.CASCADE => 같이 삭제
    # models.PROTECT => 삭제가 불가능함 (유저를 삭제하려고 할 떄 블로그가 있으면 유저 삭제가 불가능)
    # models.SET_NULL => 널값을 넣음 (유저 삭제시 블로그의 author가 null이 됨
    <h1>{{ blog.title }}</h1>
    <div style="text-align: right">
        {{ blog.author.username }}
    </div>
    <p>{{ blog.content }}</p>
    <a href ="{% url 'blog_list' %}">목록으로 돌아가기</a>
  1. 현재 foreignkey 로 설정된 값의 값을 넣는게 없어 오류가 남
  2. admin 에서는 author 를 설정할 수 있지만 페이지단에서 그렇게 되면 안되고 저자가 본인이어야 한다.
  3. form 태그 잘 닫자..+ 모든태그..

📌 Base.html 코드제작 후 extends기능 활용

공통적인 부분 적용시켜 필요한 부분만 제작하기

  • Template Engine extends
    • 공통으로 쓰일 부분을 만들어 놓고 그 안에는 다른걸 쓰게 뻥 뚫어 놓음 base.html에
  1. templates에 base.html 생성
  2. blog_list 에 만들어 놓은 기본기능 갖고오기, 홈기능 추가, 컨텐트 넣을 구멍 뚫기, 기본기능 젤밑에 작성 {% block content %}{% endblock %}
<!-- 공통 부분 -->
<body>
    <nav>
        <div style ="display: flex;  justify-content: space-between">
            <a href="{% url 'blog_list' %}"></a>
        </div>
        <div style ="text-align: right">
            {% if request.user.is_authenticated %}

    {#            <a href="{% url 'logout' %}">로그아웃</a> <!-- 현재 logout은 get요청으로 들어간다 그래서 이걸 형식을 바꿔줘야 작동한다 -->#}
                <form action="{% url 'logout' %}" method="POST" style="display: inline">
                    {% csrf_token %}
                    <button>로그아웃</button>
                <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
            {% else %}
                <a href="{%  url 'signup' %}">회원가입</a>
                <a href="{% url 'login' %}">로그인</a>
            {% endif %}
        </div>
    </nav>
      <!-- 각자의 사용 부분 -->
    {% block content %}{% endblock %} <!-- 컨텐트라는 구멍을 뚫어놓음 -->
</body>
  1. body 안에 부분 복사후 전체 html 코드삭제
  2. {% extends 'base.html' %}
  3. {% block content %}
  4. 붙여넣기
  5. {% endblock %}
  6. 해당되는 모든 html 코드 아래와 같이 수정
{% extends 'base.html' %}
{% block content %}
    <h1>{{ blog.title }}</h1>
    <div style="text-align: right">
        {{ blog.author.username }}
    </div>
    <p>{{ blog.content }}</p>
    <a href ="{% url 'blog_list' %}">목록으로 돌아가기</a>
{% endblock %}

📝 Django form

Django의 Form은 사용자로부터 데이터를 수집하고 처리하는 데 사용되는 도구로, HTML 폼을 쉽게 생성하고 데이터 유효성을 검사할 수 있도록 도와줍니다. Django의 Form은 HTML 폼을 정의, 폼 데이터를 수집, 검증 및 처리하는 과정을 간소화합니다.

1.	HTML 폼 생성:
  •	Django는 Python 코드로 HTML 폼을 자동으로 생성할 수 있도록 도와줍니다.
  •	입력 필드, 라벨, 위젯 등을 쉽게 정의할 수 있습니다.
2.	데이터 검증:
  •	Form 클래스는 입력받은 데이터가 유효한지 검증하고, 유효하지 않을 경우 에러 메시지를 제공합니다.
  •	기본 제공되는 데이터 검증(예: 필수 입력값, 이메일 형식 등) 외에, 커스텀 검증도 가능합니다.
3.	보안:
  •	Django의 Form은 CSRF(Cross-Site Request Forgery) 보호 기능을 제공합니다.
  •	사용자 입력 데이터를 안전하게 처리하고, SQL Injection 등의 공격을 방지합니다.
4.	간편한 데이터 처리:
  •	폼 데이터를 정리(clean)하고, Python 객체로 변환하거나 데이터베이스에 저장할 수 있습니다.
  • Django에서는 Form 클래스를 정의해 사용합니다. Form 클래스는 django.forms.Form 또는 django.forms.ModelForm을 기반으로 작성됩니다.

🧑‍💻 form 제작, 생성을 위해

  1. blog 폴더에 forms.py 생성
  2. 블로그 폼에 해당하는 함수 작성
  3. class Meta 안에 어떤 필드를 적용시킬지 정해서 작성
from django import forms
from blog.models import Blog

# form을 내 모델의 어떤 테이블과 맞출 건지에 관한 작업
class BlogForm(forms.ModelForm): # 폼의 모델 가져오기
    class Meta:
        model = Blog
        # 어떤필드를 적용 시킬
        fields = ('title', 'content') # Blog 모델의 'title'과 'content' 필드만 폼에 포함
        # Meta 클래스는 ModelForm의 메타 데이터를 정의하는 부분입니다. 이 클래스는 ModelForm이 어떤 모델과 연결되는지, 어떤 필드를 폼에 포함할지 등의 정보를 제공
        # fields =  '__all__' # 전체를 다 적용하고 싶을때
  1. blog/views.py 에 블로그 생성하는 함수 작성
def blog_create(request):
    form = BlogForm()
    context = {
        'form' : form
    }
    return render(request, 'blog_create.html', context)
  1. urls.py 에 경로 추가 , 이쯤에서 admin 단이랑 구분
    path('create/', views.blog_create, name = 'blog_create'),
  1. html 코드 작성
{% extends 'base.html' %}
{% block content %}
    <form method ="POST">
    <h1>블로그 작성</h1>
        {% csrf_token %} <!-- 장고에서 포스트 요청 보낼때 보안기능으로 꼭 해줘야함 -->
        {{ form.as_p }}
        <button>생성</button>
    </form>
{% endblock %}
  1. 현재 post 요청시 처리 되는 것이 없기에 views.py로 가서 post 요청이 오면 동작할 행동 작성
def blog_create(request):

    form = BlogForm(request.POST or None)
    if form.is_valid():
    	blog = form.save(commit=False)  # 데이터베이스 저장을 지연
        blog.author = request.user  # 현재 로그인한 사용자를 author로 설정
        blog = form.save()
        return redirect(reverse('blog_detail', {'pk':blog.pk}))
    context = {
        'form' : form
    }
    return render(request, 'blog_create.html', context)

📝 로그인 돼 있는지 안돼있는지

  1. 이렇게 직접 if 문을 써서 생성할 수도 있지만 장고에 기능이 있다.
if not request.user.is_autenticated:
    return redirect(reverse('login'))
  1. 대부분 이걸 쓴다
from django.contrib.auth.decorators import login_required
# 이걸 등록하면 데코레이터로
@loging_required() # 로그인중이 아닐때 생성 하면 로그인 페이지로 돌려보내느 데코레이터 이다, settings.py 에 LOGIN_URL 로 세팅 해놓은 곳으로 보낸다.
  1. 여기까지하고 페이지를 뛰워보면 ?next=create 라고 뜬다 하지만 처리해 놓지 않았기에 그냥 목록으로 간다 로그인하면
  2. member/views.py 파일에 로그인 함수를 수정한다
def login(request):
    form = AuthenticationForm(request, request.POST or None)
    if form.is_valid():
        django_login(request, form.get_user())

        next = request.GET.get('next') # GET['next'] 로 해도 되지만 그렇게 하면 없으면 오류가 난다.
        if next:
            return redirect(next)
# 이렇게 설정하면 create 로 잘 간다.
  1. 새로운 글 젤 위로 올리기
  2. biog_list 함수에서
    • blogs = Blog.objects.all().order_by('-created') (DESC '-'표시가 없으면 ASC), created_at 을 기준으로 desc한거임
  3. blog_list.html 에 생성버튼 만들기

📝 수정 페이지

수정 버튼은 로그인된 유저와 작성자가 같을때만 보여야함
create 와 detail의 혼합

  1. blog/views.py blog_update 함수 작성
@login_required()
def blog_update(request, pk):
    blog = get_object_or_404(Blog, pk=pk, author=request.user) # 조건이 2가지 들어간 것 아니면 404 페이지 

    # 밑에꺼나 위에꺼 쓰면됨
    # if request.user != blog.author:
    #     raise Http404

    context ={
        'blog' : blog
    }

    return render(request, 'blog_update.html', context)
  1. urls 연결
path('<int:pk>/update', views.blog_update, name='blog_update'),
  1. html 작성
  2. blog_detail에 수정으로 가게하는 거 하나 생성
    <!-- 로그인 시에만 보여주는 코드 if -->
    {% if request.user == blog.author %}
    <div style="text-align: right">
        <a href="{% url 'blog_update' blog.pk %}">수정</a>
    </div>
    {% endif %}
  1. create 에 있는 부분과 매우 유사해서 가져오기, 필요없는 부분 제거 author는 =user 어차피 같을 거니까 제거
@login_required()
def blog_update(request, pk):
    blog = get_object_or_404(Blog, pk=pk, author=request.user) # 조건이 2가지 들어간 것 아니면 404 팅김
    # 밑에꺼나 위에꺼 쓰면됨
    # if request.user != blog.author:
    #     raise Http404
    form = BlogForm(request.POST or None, instance=blog) # 생성시에는 기본값이 없었으나 폼 자체가 블로그 기반 모델이니까 타이틀이면 타이틀 자동으로 들어가게 됨
    if form.is_valid():
        blog.save()
        return redirect(reverse('blog_detail', kwargs={'pk': blog.pk}))  # 딕셔너리로 넣어서 kwargs

    context ={
        'form' : form, # 여기 있는 form 이 저장된 db 의 form 임
    }

    return render(request, 'blog_update.html', context)

🐚 데이터 넣기.shell

  • python manage.py shell_plus, 여러개 만들기
In [1]: blog_list = Blog.objects.all()

In [2]: new_blog_list = []

In [3]: for i in range(20):
   ...:     for blog in blog_list :
   ...:         blog.id = None # 장고에서 id 값이 없으면 새로운 모델이라 생각하니까 기존의 값을 지우고 none 이라함
   ...:         new_blog_list.append(blog
                                     In[6]:Blog.objects.bulk_create(new_blog_list)

📝 페이지네이션

  1. blog_list 함수에
    paginator = Paginator(blogs, 10) # 한페이지당 몇개씩 할지
    page = request.GET.get('page') # 페이지가 있으면 페이지에 넣어줌
    page_object = paginator.get_page(page)
  1. blog_list.html 수정 {% for blog in page_object%},blogs 에서 page_objects 10개가 저장되 있을거임
  2. /?page=2 하고 엔터 치면 그 다음 목록이 나올 거임, 범위가 넘으면 마지막 페이지를 가리킴 0이여도 마지막, 이상한 타입을 치면 첫페이지로 접속
  3. 1페이지, 2페이지, 나오는 링크 생성, endfor 이끝나고 생성되야 겠지?
    {% endfor %}
    <div>
    {% if page_object.has_previous %} <!-- 이전테그가 있는지 확인하는 메서드 -->
        <a href="?page=1">&laquo 첫번째</a>
        <a href="?page={{ page_object.previous_page_number }}">이전</a>
        <span>
            Page {{ page_object.number }} of {{ page_object.paginator.num_pages }} <!-- 왼쪽 현재 몇번째 페이진지 오른쪽 총페이지 -->
        </span>
    {% endif %}
    {% if page_object.has_next %}
        <a href="?page={{ page_object.next_page_number }}">다음</a>
        <a href="?page={{ page_object.paginator.num_pages }}">마지막</a>
    {% endif %}
    </div>
  1. 특정 페이지로 가는 것 만들기 span에 해당하는 부분 수정 할거임
{% extends 'base.html' %}
{% block content %}
<h1>블로그 목록</h1>
    <p style="text-align: right">
     <a href="{% url 'blog_create' %}">생성</a>
    </p>
    <h3>{{ request.user.username }}</h3> <!-- 잘적용되 있는지 확인 -->
    <h1>{{ count }}</h1> <!-- 이렇게 출력해 주지 않으면 클라이언트가 알 수가 없음 -->
    {% for blog in page_object%}
        <p>
            <a href="{% url 'blog_detail' blog.pk %}">
               ({{ blog.id }}) {{ blog.title }} <span>{{ blog.author.username }}</span> - {{ blog.created_at|date:"Y-m-d" }}
            </a>
        </p>
    {% endfor %}
    <div>
    {% if page_object.has_previous %} <!-- 이전테그가 있는지 확인하는 메서드 -->
        <a href="?page=1">&laquo; 첫번째</a>
        <a href="?page={{ page_object.previous_page_number }}">이전</a>
    {% endif %}

    {% if page_object.number|add:-2 > 1 %}
        <a href="?page={{ page_object.number|add:-3 }}">&hellip;</a>
    {% endif %}
                {#   1페이지부터 최대페이지 까지 들어있음     #}
        {% for i in page_object.paginator.page_range %}
            <!-- 현재페이지 == i 이면 -->
            {% if page_object.number == i %}
            <span>(현재페이지)</span>
{#        2페이지 -4+i < i < i+4  일때 쓸려는듯 이범위의 페이지만 나오게  #}
            {% elif i > page_object.number|add:-3 and i < page_object.number|add:3 %} <!-- 띄워쓰면 오류나니까 잘 붙여쓰자 add -->
                <a href="?page={{ i }}">{{ i }}</a>
            {% endif %}
        {% endfor %}
{#   최대 페이지    #}
    {% if page_object.paginator.num_pages > page_object.number|add:2 %}
        <a href="?page={{ page_object.number|add:3}}">&hellip;</a>
    {% endif %}
    {% if page_object.has_next %}
        <a href="?page={{ page_object.next_page_number }}">다음</a>
        <a href="?page={{ page_object.paginator.num_pages }}">마지막 &raquo;</a>
    {% endif %}
    </div>
{% endblock %}

🔎 검색 기능

html 부터 작성해야함

  1. blog_list 폼에 만들어야 하니까 검색어 입력기능 삽입
{#  검색 폼   #}
    <form method="get" style="margin-bottom: 10px">
    <input name="q" type="text" placeholder="검색어를 입력하세요">
    <button>검색</button>
    </form>
  1. 쿼리 스트링으로 받아오게 만들거니까 그걸 등록 해놔야함
    • name="q" 지금 q를 받아오게 만들어 놨다 그럼 그 q를 사용자로부터 받아오는 함수를 작성해야함
    q= request.GET.get('q') # 페이지 네이터에 넣을떄 이미 필터링이 진행되어 있어야함
    if q : # q 가 있을때만 동작
        blogs = blogs.filter(title__icontains=q) # 쿼리셋을 이미 잡은것을 가져온것이기에 Blog 에 filter을 하면 안되고 가져온 값에 해야한다
        # 그리고 쿼리를 가져올때는 무조건 objects 를 사용해야 한다.
        # 아니면 blog = get_object_or_404(Blog, pk=pk) 이런식
        # 본문에서 검색하고 싶으면 content__icontains 
        # 본문에서 이미 blogs= 을 사용해서 쓰고 있으니까
        # blogs 자체를 받아온것이다 blogs=blogs
        # 그래야 html 에서 사용되는 것 자체가 수정되서 화면에 나타날 것이다
        

# context 에 q 를 추가하지 않았지만 사용자로부터 q를 받아오는 것이기에 html 에서
# 사용할 수 있다.
# html 코드에 맞춰서 q를 작성하는 것임 

# or 검색도 가능
    if q :
        # or 검색
        from django.db.models import Q
        blogs = blogs.filter(
            Q(title__icontains=q) | # 논리연산자 반드시 명시
            Q(content__icontains=q)
        )
        
        # blogs = blogs.filter(title__icontains=q)
  • 총 코드
### def blog_list(request)
def blog_list(request):
    blogs = Blog.objects.all().order_by('-created_at')

    q= request.GET.get('q') # 페이지 네이터에 넣을떄 이미 필터링이 진행되어 있어야함
    if q :
        # or 검색
        from django.db.models import Q
        blogs = blogs.filter(
            Q(title_icontains=q),
            Q(content_icontains=q)
        )
        
        # blogs = blogs.filter(title__icontains=q)

    from django.core.paginator import Paginator
    paginator = Paginator(blogs, 10) # 한페이지당 몇개씩 할지
    page = request.GET.get('page') # 페이지가 있으면 페이지에 넣어줌
    page_object = paginator.get_page(page)

    # 리턴값은 string 방문자수 는 1이였으면 2가 됨
    visits = int(request.COOKIES.get('visits', 0)) + 1 # get 은 뒤에 key 값을 가져오는데 혹시 없으면 defaul로 지정해 논 0을 가져옴

    request.session['count'] = request.session.get('count',0) + 1

    context = {
        'blogs': blogs,
        'count' : request.session['count'],
        'page_object' : page_object,
    }
    response =  render(request, 'blog_list.html', context)

    response.set_cookie('visits', visits)
    return response
<!-- html 코드 -->

{#  검색 폼   #}
    <form method="get" style="margin-bottom: 10px">
    <input name="q" type="text" placeholder="검색어를 입력하세요" value="{% if request.GET.q %}{{ request.GET.q }}{% endif %}"><!-- 검색한 것이 있으면 안에 들어가 있게 -->
    <button>검색</button>
    </form>

⛔️ 블로그 삭제 만들기

  1. 함수 작성
@login_required()
def blog_delete(request, pk):
    blog = get_object_or_404(Blog, pk=pk, author=request.user)
    blog.delete()
    return redirect(reverse('blog_list'))
  1. path 연결
    path('<int:pk>/delete/', views.blog_delete, name='blog_delete'),
  1. 상세페이지에 삭제버튼 추가
    <div style="text-align: right">
        <a href="{% url 'blog_update' blog.pk %}">수정</a>
        <a href="{% url 'blog_delete' blog.pk %}">삭제</a>
    </div>
  1. 보통 삭제나 수정은 POST 요청으로만 보내야함 GET 요청으로 받으면 안됨
@login_required()
@require_http_methods(['POST']) # 특정 메소드만 넣을수 있는 기능
def blog_delete(request, pk):
    # if request.method != "POST": # 데코레이터 썻기에 안써도댐
    #     raise Http404
    blog = get_object_or_404(Blog, pk=pk, author=request.user)
    blog.delete()
    return redirect(reverse('blog_list'))
        <a href="{% url 'blog_update' blog.pk %}">수정</a>
            {# <a href> 는 get 요청을 보내기에 post 요청을 보내야하는 것과 적합하지 않다 그래서 밑에 처럼쓴다 #}
        <form action="{% url 'blog_delete' blog.pk %}" method="POST" style="display: inline">
            {% csrf_token %}
            <button>삭제</button>
        </form>

📌 Django 모듈 정리

  • 해당 값이 있을떄 없으면 404를 일으키는 object
from django.urls import reverse

from django.shortcuts import get_object_or_404
	blog = get_object_or_404(Blog,pk=pk)

from django.urls import include  # path 경로에 등록
   - path('accounts/', include("django.contrib.auth.urls")),

from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
    form = UserCreationForm()
    form = AuthenticationForm(request, request.POST or None)
    
from django.shortcut import redirect
# 장고의 설정으로 등록해놓은 것을 상요하려면 , config 의 폴더이름이 바뀌더라도 상관없는것
from django.conf import settings # 이렇게 해주면 세팅이 변해도 상관없다.
	return redirect(settings.LOGIN_URL) # LOGIN_URL 쓰려고 임포트 한것

from django.contrib.auth import get_user_model # 장고에 어떤것이 연결되어 있는지 확인한 후 가져오는 것이 이 함수
# 장고에 기본 설정에 있는 user 를 가져오는 것임
	User = get_user_model()
    
from django.contrib.auth.decorators import login_required
# 이걸 등록하면 데코레이터로
@login_required() # 로그인중이 아닐때 생성 하면 로그인 페이지로 돌려보내느 데코레이터 이다, settings.py 에 LOGIN_URL 로 세팅 해놓은 곳으로 보낸다.

from django.core.paginator import Paginator
paginator = Paginator(blogs, 10) # 한페이지당 몇개씩 할지

from django.shortcuts import render , get_object_or_404, redirect

from django.views.decorators.http import require_http_methods
@require_http_methods(['POST']) # 특정 메소드만 넣을수 있는 기능

📌 Tip

  • objects.all() 에서 특정 객체만 가져오기
     todo_list = Todo.objects.all().values_list('pk','title')
  • 장고에서 pk 를 객체로 지정해서 넘겨주면 자동으로 그 테이블의 프라이머리 키값을 읽어준다

  • objects.count() : 몇개있는지

  • 오류확인 : 오류가 없는데 페이지단에 안드면 개발자 도구로 가서 element확인해보고 기타등등 확인

  • 보통 해당 페이지의 상세페이지는 pk 즉 프라이머리 키라고 변수를 선언한다

  • 해당 번호를 지우거나 넘기는 페이지를 입력하면 404 에러를 출력 시켜줘야함

  • django admin docs 를 검색해서 모르는 admin 기능 확인 가능

  • return redirect("/gugu/2/") # 앞에 / 가있어야 // 가되서 홈으로 redirect 되고 gugu2가나옴

  • html 문법

    • {{딕셔너리.key}} , 대괄호가아님
    • 딕셔너리 타입의 context ={ 'dan' : 4} 이 주어지면 html 안에서는 dan 을쓰면 4가 나온다
    • {{dan}} -> 화면에 4 표시됨
{% for moive in movie_list %}
  {% if forloop.counter <= 3 %}
    {{ forloop.counter | add : 5 }}
  {% endif %}
{% endfor %}
{% for moive in movie_list %}
  {% if forloop.counter0 == index %}
    {{ forloop.counter | add : 5 }}
  {% endif %}
{% endfor %}

{% for i in '12345689'|make_list %}

{% widthration n y i %} -> n/y * i

forloop.counter : for 문 안에서 인덱스가 증가함 설정안하면 1부터
forloop.counter0 : for 문 안에서 인덱스가 증가함 0부터     
      
<h1>
    <a href="/book_list/{{num}}/"> Book {{num}} ><!-- 페이지 넘어가는 코드  -->
</h1>
  • verbose 의 역할
  	•	'verbose'는 필드의 레이블로 사용됩니다.
	•	Admin 페이지에서 해당 필드를 보여줄 때(예: created_at 대신)에 레이블로 표시됩니다.
	•	즉, 관리자가 보기 좋도록 필드 이름을 한국어로 지정하거나 커스터마이즈할 수 있는 옵션입니다.
	•	예를 들어, Django Admin 페이지에서 created_at 필드 대신 '작성일자'가 나타납니다
  • auto_now_add=True와 auto_now=True의 차이
	•	auto_now_add=True:
	•	레코드가 처음 생성될 때 해당 필드에 자동으로 현재 시간을 저장합니다.
	•	이후에는 값을 변경할 수 없고, 레코드 생성 시점의 시간만 유지됩니다.
	•	일반적으로 “작성일”이나 “생성일”처럼 한 번 설정된 후 변하지 않는 시간에 사용됩니다.
	•	auto_now=True:
	•	레코드가 저장될 때마다(수정될 때 포함) 해당 필드에 현재 시간을 자동으로 저장합니다.
	•	즉, “수정일”처럼 수정될 때마다 최신 시간으로 업데이트해야 하는 필드에 적합합니다.

0개의 댓글

관련 채용 정보