3월 06, 2022

[Django] Q객체

django 공식 문서에 기반하여 게시물을 작성했습니다.

Q객체는 데이터베이스 관련 작업에서 사용할 수 있는 SQL 조건을 나타냅니다. 

Q객체는 |(OR) , &(AND)등을 활용하여 조건들을 정의하고, 재사용하고, 합칠 수 있게 합니다. 


Keyworded argument queries는 filter() 등에서 AND조건으로만 합쳐집니다.

복잡한 쿼리를 실행하기 위해서(OR 등을 사용하고 싶을때), Q 객체를 사용할 수 있습니다.


Q객체는 Keyword arguments 들을 encapsulate 하기 위해 쓰이는 객체입니다. 

(encapsulate=캡슐화 : 연관 있는 변수와 함수를 하나의 단위로 묶는 것, 이는 데이터의 번들링을 의미함, 파이썬의 경우에 이 번들링은 클래스를 통해 이뤄지고, 이 클래스의 인스턴스 생성으로 클래스 안에 포함된 변수와 메소드에 접근할 수 있게 됨) 

(Q객체의 경우는 keyword argument - 정렬의 기준이 되는 값 - 과 field lookup을 통한 sql WHERE 절 - 즉 메소드를, Q클래스를 상속받은 Q객체를 통해서 번들링 한다고 이해했습니다)

이 keyword arguments들은 "Field lookups"를 통해 특정화되는데, 이에 대해서는 후술하겠습니다.


Q 객체는 & 와 | 를 통해서 합쳐질 수 있습니다. 두개의 Q 객체는 & , | 의 오퍼레이터를 거쳐 하나로 합쳐집니다. 

1
Q(question__startswith='where') | Q(question__startswith='When)
예를 들어 위 코드는 두개의 question__startswith 쿼리들을 'OR'로 합친 하나의 Q객체를 만들어냅니다.

keyword arguments들을 받는 lookup function들은(filter()나 exclude(), get() 등) 하나 이상의 Q객체를 positional arguments로 받을 수 있습니다. lookup function에 여러개의 Q 객체를 준다면 각각의 argument들은 AND될 것입니다.


1
2
3
4
Drink.objects.get(
    Q(name__startswith='Nok'),
    Q(caffeine=0 | Q(caffeine=1)
)
get()안의 두개의 Q객체가 있는데, 이들은 AND조건으로 연결된다는 뜻입니다.
 

Lookup 함수들은 Q객체와 keyword argument을 함께 사용할 수 있습니다. 마찬가지로 lookup 함수에 주어지는 모든 arguments는 AND로 연결됩니다. 
이때 주의해야 할 것은 Q객체가 keyword arguments 보다 먼저 와야 된다는 것입니다.


1
2
3
4
Drink.objects.get(
    name__startswith='Nok',
    Q(caffeine=0 | Q(caffeine=1)
)
이러한 코드 구성은 유효하지 않다는 뜻입니다.


앞서 field lookup에 대해 얘기했었죠,
Field lookups는 SQL WHERE 절의 의미를 구체화하는 방식입니다.
이것들은 get() filter() exclude()등의 쿼리셋 메소드 에서 사용됩니다

field__lookuptype=value 
의 형식으로 field lookup을 사용합니다.
lookup에 사용된 field의 이름은 모델 필드의 이름과 같아야 합니다.

다만, 예외가 있습니다. Foreign key의 경우 필드네임을 접미사에 _id를 붙임으로써 나타낼 수 있습니다. 이 때, value 값은 foreign 모델의 pk의 raw value를 포함하고 있어야 합니다.
Drink.objects.filter(review_id=4)
이런식으로 말이죠.

다양한 필드 lookup reference가 있습니다. 

https://docs.djangoproject.com/en/4.0/ref/models/querysets/#field-lookups

해당 링크에서 이에 대해 확인해 볼 수 있습니다. 이들 중 프로젝트에 사용됐던 몇가지 field lookup reference를 알아보도록 하겠습니다.


price_lower과 price_upper 사이의 range 안에 price가 있는지 확인하는 코드입니다. 미리 선언해두었던 q = Q()에 add를 통해서 조건을 더해줍니다.



사용자가 카테고리를 여러가지 선택할 수 있도록 구현해야 했기에 str로 값을 받고, 이를 쉼표로 나눠준 리스트를 만듭니다.  

저 __name 부분에서 매우 애를 먹었는데, __name을 해주지 않으면 id값으로만 필터링이 가능했습니다. erd를 보시면 아시겠지만, category(클래스명은 Category)는 테이블 이름이지, 테이블 내의 컬럼명이 아닙니다. 따라서 그냥 category만 써줬을 경우에는 pk인 id 값을 통해서 구분을 지어준 것이 아닌가 생각이 듭니다. category 테이블의 컬럼명인 name을 __name이렇게 연결해 주고서야 name을 통해서 필터링이 가능해졌습니다. 
__in은 categories라는 iterable (list, tuple , queryset 등) 안에 category__name 이 있는지 확인하기 위해 사용된 filed lookup입니다.

2월 25, 2022

[django] westagram포스팅 구현, 로그인 데코레이터 구현

1. 특정 요청을 보낸 유저의 권한을 인가(확인)하는 데코레이터를 구현합니다 

2. westagram이라는 SNS에 게시물을 POST할 수 있는 기능을 구현하려 합니다. 


우선 데코레이터의 코드입니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import jwt

from django.http import JsonResponse

from my_settings import SECRET_KEY, ALGORITHM
from users.models import User


def trace(func):
    def wrapper(self,request, *args, **kwargs):  
        try:
            access_token = request.headers['Authorization']     
            header = jwt.decode(access_token, SECRET_KEY,  algorithms=ALGORITHM)
            pk = header['id']

            User.objects.get(id=pk)
            request.pk = pk 

            return func(self, request, *args, **kwargs) 

        except User.DoesNotExist:
            return JsonResponse({"message":"Invalid User"}, status = 401)
        except jwt.DecodeError:
            return JsonResponse({"message":"Invalid token"}, status = 401) 
            
    return wrapper

  • trace는 실행되지 않은 wrapper라는 함수를 리턴합니다
  • wrapper가 실행됩니다
  • "access_token"이라는 변수에 요청의 헤더에 담긴 jwt토큰 값을 담습니다
  • 이를 jwt.decode()를 통하여 다시 decode 해주면 유저 id와 값이 담긴 딕셔너리가 나옵니다
  • "pk" 변수에 이 id 값을 담아줍니다
  • 인수로 받은 request라는 인스턴스에 .pk로 변수를 선언하여 pk 값을 저장합니다.
  • 예외처리는 두개를 해주었는데
    • 토큰을 발급받은 유저가 탈퇴 등으로 DB에서 삭제된 경우와(토큰은 여전히 유효)
    • 토큰이 잘못된 경우입니다.
  • 두번째 경우는 jwt.DecodeError를 통하여 예외처리를 해줍니다
  • 첫번째 경우는 User.objects.get(id=pk) 를 통해 나온 값이 없을 때, 토큰은 유효하지만 user가 이미 DB에서 삭제된 경우이기에 User.DoesNotExist를 사용해줍니다

에러가 발생하지 않은 경우, 데코레이터를 받는 함수를 그대로 리턴해줍니다.


다음으로 posting 기능을 구현해줍니다. Django에서는 다루는 데이터의 종류가 달라지는 시점에서 앱을 분리합니다. posting에 관한 데이터는 이전까지 다루지 않던 데이터이니 app을 분리해줘야 합니다. startapp으로 app을 만들어줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from djang.db import models
from users.models import User

class Posting(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    content = models.CharField(max_length=100, blank=True)
    class Meta:
        db_table = 'postings'

class Image(models.Model):
        posting = models.ForeignKey(Posting, on_delete=models.CASCADE)
        image = models.CharField(max_length=100)
        class Meta:
            db_table = 'images'

posting은 어떠한 유저가 올린 글이기에, 각 posting은 users app의 User클래스를 정참조합니다

posting 하나에 여러개의 사진이 등록될 수 있기에 images가 postings를 정참조 하도록 모델을 구현합니다. 

created_at은 auto_now_add 속성을 통하여 글을 올리는 시간을 기록해줍니다. 

그런데 이렇게 하니 updated_at도 같이 생성됩니다. 



PostingView는 posting과 담당된 기능을 총괄하는 클래스입니다.

우선, post함수를 구현하여 포스팅을 올릴 수 있게 해주고, 추후에 get으로 이를 받아 올 수 있도록 구현하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import json

from django.http            import JsonResponse
from django.views           import View

from postings.models import Posting,Image
from postings.utils import trace
from users.models import User
# Create your views here.

class PostingView(View):
    @trace
    def post(self, request):
        data = json.loads(request.body)

        user_id = User.objects.get(id=request.pk) 
        content = data.get("content", '')
        image = data["image"]
 
        post = Posting.objects.create(
            user = user_id,
            content = content
        )

        for i in image:
            Image.objects.create(
                image = i,
                posting = post #Posting 객체를 넣어줬는데 알아서 테이블에 객체의 id값이 들어가는지? 
            )

 

        return JsonResponse({'message':'SUCCESS'}, status=201)

글을 올리려는 유저가 유효한 토큰을 가지고 있는지 확인하기 위하여 @trace로 데코레이터를 먼저 선언해줍니다.


앞서 데코레이터의 reqeust인스턴스에 pk라는 변수를 저장해주고, 이를 함수의 인자로 리턴해주었죠? 이를 이용하여 user FK컬럼에 글을 올리는 유저의 id값을 넣어줍니다.

이미지를 여러개 유저가 보내주었을시, 이를 각각 images 테이블에 넣기 위하여, 이를 for문을 통하여 각각 테이블에 넣어줘야 합니다.

앞서 만들어진 포스팅에 이미지가 들어가야 하므로, 방금 만들어진 포스팅 인스턴스를 담은 "post" 변수를 그대로 넣어줍니다. 

주석을 달아놓았듯이 Image 객체를 만들어줄때, posting값에 post라는 인스턴스를 그대로 할당하면 , sql상의 posting_id에는 해당 인스턴스의 id값이 알아서 잘 들어갑니다.






2월 03, 2022

can't open file [Errno 2] No such file or directory

 djTutorial이라는 파일 안에


...\> django-admin startproject mysite

을 통해 mysite라는 프로젝트를 생성했습니다. 


개발 서버를 시작하기 위해 
...\> py manage.py runserver

를 입력했는데 계속 에러가 납니다.

자세히 보니 root 파일인 mysite안에 manage.py 가 존재하는데, 현재의 경로는 그 밖입니다

cd 명령어를 통해 mysite 안으로 이동해주고 다시 runserver 명령을 실행합니다


이렇게 했더니 잘 실행됩니다.

하하 이래가지고 개발자가 될 수 있을까요
 
manage.py는 root project folder안에 있음을 기억해야겠습니다.



2월 01, 2022

CRUD구현

  *생활코딩 Django 편을 개인 공부를 목적으로 정리한 글임을 밝힙니다*

출처:https://www.youtube.com/watch?v=pbKhn2ten9I&list=PLuHgQVnccGMDLp4GH-rgQhVKqqZawlNwG


아무리 복잡한 웹 애플리케이션도 결국 4가지 작업 안에 속합니다.

C reate

R ead

U pdate

D elete

전체적인 구성을 보고 자세히 분석해보겠습니다.

파일 구성은 이러합니다.



myproject.urls.py 입니다. admin접속이 아닌 접속은 myapp으로 위임하는 것을 알 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path, include

# http://127.0.0.1/
# http://127.0.0.1/app/

# http://127.0.0.1/create/
# http://127.0.0.1/read/1/

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


이후 routing을 담당하는 myapp.urls.py 입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.index), 
    #사용자가 아무것도 없는 경로로 들어왔다면, views.index 실행해라
    path('create/',views.create), #사용자가 create로 들어왔다면 #views.create실행
    path('read/<id>/',views.read), #사용자가 read/<id>으로 들어왔다면 views.read 실행
    path('update/<id>/',views.update), #사용자가 read/<id>으로 들어왔다면 views.read 실행
    path('delete/', views.delete)
]

myapp.urls.py에서 처리를 위임받은 views.py입니다.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
from glob import glob
from typing import NewType
from django.shortcuts import render, HttpResponse, redirect
from django.views.decorators.csrf import csrf_exempt # 장고보안기능면제

nextId = 4 #id값이 없는 create함수의 NewTopic을 위해 새로운 id를 만드는 과정

topics = [
    {'id':1, 'title':'routing', 'body':'Routing is...'},
    {'id':2, 'title':'view', 'body':'View is..'},
    {'id':3, 'title':'Model', 'body':'Model is..'},
]

def HTMLTemplate(articleTag, id=None):     #html을 함수화함으로서 재활용의 기틀 마련 
    global topics
    contextUI = ''
    if id != None:   #홈에서는 delete버튼이 뜨면안되니깐. #상세보기 페이지에서만 뜨도록
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
            <li><a href="/update/{id}">update</a></li>
        '''
    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>'
    return f'''
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        {articleTag}
        <ul>
            <li><a href="/create/">create</a></li>
            {contextUI}
        </ul>
    </body>
    </html>
    '''
#hidden타입의 input: 눈에 보이지 않지만 서버로 데이터를 전송하는 폼

@csrf_exempt
def update(request,id):
    global topics
    if request.method == 'GET': #사용자가 get으로 접속하면 update라는 텍스트를 출력
        for topic in topics:
            if topic['id'] == int(id):
                selectedTopic = {
                    'title':topic['title'],
                    'body':topic['body'],
                    }
        article = f'''
        <form action="/update/{id}/" method="post">
            <p><input type="text" name="title" placeholder="title" value={selectedTopic["title"]}></p>
            <p><textarea name="body" placeholder="body">{selectedTopic['body']}</textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' 
        return HttpResponse(HTMLTemplate(article,id))
    elif request.method == 'POST': #post로 데이터를 수정하면
        title = request.POST['title']
        body = request.POST['body']
        for topic in topics:
            if topic['id'] == int(id):     #일치한다면 값을 변경
                topic['title'] = title
                topic['body'] = body
        return redirect(f'/read/{id}')



# Create your views here.
def index(request):
    article = '''
    <h2>Welcome</h2>
    Hello, Django
    '''
    return HttpResponse(HTMLTemplate(article))

def read(request, id):
    global topics
    article = ''
    for topic in topics:
        if topic['id'] == int(id):
            article = f'<h2>{topic["title"]}</h2>{topic["body"]}'
    return HttpResponse(HTMLTemplate(article, id))

@csrf_exempt #장고의 보안기능 면제 
def create(request):
    global nextId
    if request.method == "GET":  #get방식이냐 post방식이냐에 따라서 다르게 처리
        article = '''
        <form action="/create/" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' # action 속성: submit을 눌렀을 때 보내고 싶은 서버의path 
        return HttpResponse(HTMLTemplate(article))
    elif request.method =="POST": #post들어오는 처리일때
        title = request.POST["title"]   #서버로부터 들어온 데이터 이름붙이기
        body = request.POST["body"] 
        NewTopic = {"id":nextId, "title":title, "body":body} #그를 통해 새로운 데이터 만들기 
        # 이 NewTopic을 위의 topics리스트에 추가하려는데, 얘한테 id값이 없음 (6번라인이동) - nextId 값 만듬
        topics.append(NewTopic)
        url = '/read/'+str(nextId)
        nextId = nextId + 1 #nextId값이 계속 4에 머무르는것을 막아야 함
        return redirect(url)
        #작업이 끝나고 서버가 웹 브라우저한테: 지금 생성한 글의 상세보기 페이지로 redirect해 ! 라고 해야함

@csrf_exempt
def delete(request):
    global topics
    if request.method == 'POST':
        id = request.POST['id']
        newTopics = []
        for topic in topics:
            if topic['id'] != int(id):  #topics리스트의 topic의 id값이 post로 보내준 id값가 같지않다면 newtopics에 추가
                newTopics.append(topic)
        topics = newTopics   #이렇게함으로써 post로 보내준 id값을가진 topic은 새로운리스트에서 빠지게 됨(삭제)
        return redirect('/') #과정이 끝나면사용자는 홈으로 보내버림
        

views.py의 코드를 자세히 뜯어보며 각 기능을 분석해보겠습니다.

index함수는 홈화면을 구현하는 함수입니다

1
2
3
4
5
6
def index(request):
    article = '''
    <h2>Welcome</h2>
    Hello, Django
    '''
    return HttpResponse(HTMLTemplate(article))

장고는 request와 response 객체로 상태를 server와 client가 주고 받습니다.

HttpResponse가... 뭐죠..?

a. 이때 특정 페이지가 Request되면, 장고는 metadata(데이터에 관한 데이터)를 포함하는 HttpRequest 객체를 생성합니다

b. 장고는 urls.py에 정의한대로 특정 views의 클래스 또는 함수의 첫번째 인자로 위의 HttpRequest 객체를 전달합니다. (함수의 들어가는 request가 이것)

c. 해당 views는 결과값을 HttpResponse(또는 JsonResponse) 객체에 담아 전달합니다. 

HttpResponse는 HttpRequest을 받은 views안의 함수의 결과값을 전달하는 객체이군요! 


HttpResponse 의 전달인자인  HTMLTemplate(article)은 또 무엇일까요?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def HTMLTemplate(articleTag, id=None):     #html을 함수화함으로서 재활용의 기틀 마련 
    global topics
    contextUI = ''
    if id != None:   #홈에서는 delete버튼이 뜨면안되니깐. #상세보기 페이지에서만 뜨도록
        contextUI = f'''
            <li>
                <form action="/delete/" method="post">
                    <input type="hidden" name="id" value={id}>
                    <input type="submit" value="delete">
                </form>
            </li>
            <li><a href="/update/{id}">update</a></li>
        '''
    ol = ''
    for topic in topics:
        ol += f'<li><a href="/read/{topic["id"]}">{topic["title"]}</a></li>'
    return f'''
    <html>
    <body>
        <h1><a href="/">Django</a></h1>
        <ol>
            {ol}
        </ol>
        {articleTag}
        <ul>
            <li><a href="/create/">create</a></li>
            {contextUI}
        </ul>
    </body>
    </html>
    '''
#hidden타입의 input: 눈에 보이지 않지만 서버로 데이터를 전송하는 폼

HttpResponse는 결과값으로 client에게 html값을 전달해야겠죠! 

이때, 제가 구현하고자 하는 페이지는 전반적인 기틀이 비슷하기에 html을 HTMLTemplate 함수를 사용하여 모듈화 시킨것입니다. article은 HTML의 본문부분을 담당하는 매개변수입니다. 

위의 사진이 index가 구현하는 홈페이지입니다.
1.routing ~ 3으로 된 부분이 HTMLTemplate의 {ol}변수 부분입니다. 뒤에서 자세히 알아보겠습니다.

아무튼 index함수가 article변수를 통해 본문부분에 "Welcome" "Hello, Django"를 표시한것을 볼 수 있습니다. 


다음으로 Read입니다

1
2
3
4
5
6
7
def read(request, id):
    global topics
    article = ''
    for topic in topics:
        if topic['id'] == int(id):
            article = f'<h2>{topic["title"]}</h2>{topic["body"]}'
    return HttpResponse(HTMLTemplate(article, id))

우선, topics 전역변수를 사용하는 것을 볼 수 있습니다. topics전역변수는 각 상세페이지(1.routing , 2.view.....) 등의 정보를 담아놓은 리스트입니다. 

HTMLTemplate의 {ol}변수는 topics안의 각 딕셔너리인 topic의 id값을 활용한 /read/링크가 삽입된 리스트를 만듭니다.

read함수는 매개변수로 들어오는 id

(ol변수에 id값에 따라 다른 /read/id 링크를 생성하고, urls.py 에서  path('update/<id>/',views.update)

의 형식으로 id를 확인합니다. ) 를 확인하고, 만약 인수로 주어진 id와 topic의 id 값이 같다면,

본문인 article 부분에 topic의 title과 body를 표시합니다.


다음은 Create입니다

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@csrf_exempt #장고의 보안기능 면제 
def create(request):
    global nextId
    if request.method == "GET":  #get방식이냐 post방식이냐에 따라서 다르게 처리
        article = '''
        <form action="/create/" method="post">
            <p><input type="text" name="title" placeholder="title"></p>
            <p><textarea name="body" placeholder="body"></textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' # action 속성: submit을 눌렀을 때 보내고 싶은 서버의path 
        return HttpResponse(HTMLTemplate(article))
    elif request.method =="POST": #post들어오는 처리일때
        title = request.POST["title"]   #서버로부터 들어온 데이터 이름붙이기
        body = request.POST["body"] 
        NewTopic = {"id":nextId, "title":title, "body":body} #그를 통해 새로운 데이터 만들기 
        # 이 NewTopic을 위의 topics리스트에 추가하려는데, 얘한테 id값이 없음 (6번라인이동) - nextId 값 만듬
        topics.append(NewTopic)
        url = '/read/'+str(nextId)
        nextId = nextId + 1 #nextId값이 계속 4에 머무르는것을 막아야 함
        return redirect(url)
        #작업이 끝나고 서버가 웹 브라우저한테: 지금 생성한 글의 상세보기 페이지로 redirect해 ! 라고 해야함

새로운 항목을 만드는 함수입니다.

요청이 get방식이냐, post 방식이냐에 따라 분기하여 다르게 처리합니다.

브라우저가 서버로부터 데이터를 요청하면 GET

브라우저가 서버의 데이터를 변경하고자 하면 POST입니다 

get 방식의 request가 들어왔을때 부터 살펴보면, article 변수에 form을 만듦으로써 새로운 입력을 받을 수 있는 환경을 구현합니다. form의 method를 "post"로 설정하여 form의 전송 방식을 post로 설정해놓은 것을 확인할 수 있습니다.

제출을 누름으로써 post로 들어오는 처리가 발생하면, 폼에 작성된 데이터를 처리합니다.

이 과정에서, 서버로부터 들어온 데이터에 id값을 붙여주기 위해 전체코드의 처음부분에 nextId 변수를 설정해놓았습니다. 

코드에 따른 작업이 끝나고, 새롭게 create된 topic의 상세보기 페이지를 return합니다

create를 통해 새로운 topic을 추가한 모습입니다


만들었으면 삭제를 해야겠죠? Delete입니다

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@csrf_exempt
def delete(request):
    global topics
    if request.method == 'POST':
        id = request.POST['id']
        newTopics = []
        for topic in topics:
            if topic['id'] != int(id):  #topics리스트의 topic의 id값이 post로 보내준 id값가 같지않다면 newtopics에 추가
                newTopics.append(topic)
        topics = newTopics   #이렇게함으로써 post로 보내준 id값을가진 topic은 새로운리스트에서 빠지게 됨(삭제)
        return redirect('/') #과정이 끝나면사용자는 홈으로 보내버림

HTMLTemplate 함수를 보시면, id가 None이 아닐때, 즉 홈 화면이 아닐때는 delete버튼과 update버튼이 생기도록 설정해놓은 코드를 보실 수 있습니다

사용자가 delete를 통해 post 방식으로 처리를 요청하면, newTopics리스트에 현재 id값을 제외한 나머지 id값을 가진 topic을 추가하여, 해당 newTopics리스트를 topics리스트로 교체해줌으로써 현재 topic을 삭제합니다.


마지막으로 Update입니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@csrf_exempt
def update(request,id):
    global topics
    if request.method == 'GET': #사용자가 get으로 접속하면 update라는 텍스트를 출력
        for topic in topics:
            if topic['id'] == int(id):
                selectedTopic = {
                    'title':topic['title'],
                    'body':topic['body'],
                    }
        article = f'''
        <form action="/update/{id}/" method="post">
            <p><input type="text" name="title" placeholder="title" value={selectedTopic["title"]}></p>
            <p><textarea name="body" placeholder="body">{selectedTopic['body']}</textarea></p>
            <p><input type="submit"></p>
        </form>
        ''' 
        return HttpResponse(HTMLTemplate(article,id))
    elif request.method == 'POST': #post로 데이터를 수정하면
        title = request.POST['title']
        body = request.POST['body']
        for topic in topics:
            if topic['id'] == int(id):     #일치한다면 값을 변경
                topic['title'] = title
                topic['body'] = body
        return redirect(f'/read/{id}')

 마찬가지로 get방식 접속과 post방식 접속을 분기해두었습니다.

사용자가 get으로 접속하게 되면 현재 topic의 title과 body값을 update폼에 표시합니다

사용자가 submit을 통해 post방식으로 전송을 요청하면 topics리스트의 값을 POST된 값으로 교체하고, 사용자를 read/{id} , 즉 topic의 상세페이지로 redirect 시킵니다.


django를 처음써보기도 했고, 각 요소 요소가 물고 물리는 관계여서 이 정도 페이지를 구현하는데도 어려움을 많이 겪었습니다.. 익숙해지도록 노력해야겠습니다





1월 31, 2022

~Routing

 *생활코딩 Django 편을 개인 공부를 목적으로 정리한 글임을 밝힙니다*

출처:https://www.youtube.com/watch?v=pbKhn2ten9I&list=PLuHgQVnccGMDLp4GH-rgQhVKqqZawlNwG



웹 어플리케이션을 만드는 작업중에 많은 부분은 공통적입니다. 따라서 어떠한 사이트를 만들더라도 똑같이 해야할 일들이 많습니다. 

⇒ 이런 작업을 미리 해둔 소프트웨어를 Web Framework라고 합니다.

Django는 파이썬 진영의 대표적인 Web Framework로써,요청이 들어올때마다 웹 페이지를 순간적으로 찍어낼 수 있는 일종의 웹 페이지 공장입니다. 


포트의 개념

127.0.0.1이라는 IP adress를 가진 서버컴퓨터에 3개의 서버 소프트웨어가 동작하고 있습니다. 저는 서버에 접속하기 위해 http://127.0.0.1 으로 주소를 입력합니다.

이때, 3개의 소프트웨어 중 어느것과 통신하게 될지 모호해지는 문제가 발생합니다. 

이러한 문제를 해결하기 위하여 PORT라는 개념이 존재합니다. 

예를들어, 첫번째 프로그램이 80번 포트를 listening하는 중이고, 제가 그 포트를 통해 접속하고 싶다면, http://127.0.0.1:80 이렇듯 주소에 포트번호를 주어 80번 포트를 listening하는 서버와 통신할 수 있게 됩니다. 

파이썬 개발서버는 기본적으로 8000번 포트에서 listening하게 됩니다. 제가 8000번 포트를 접속하려 하는데, 가끔은 이미 8000번 포트에 파이썬 개발서버가 아닌 다른 서버가 실행되고 있을 수 있겠죠? 이럴때는 

1. 그 개발 서버를 끄고 해당 개발 서버를 실행하거나

2. 나의 개발서버를 다른 포트에 listening시킴으로서 문제를 해결할 수 있습니다

2번의 경우 ,python manage.py runserver 8888,  이런식으로 다른 포트(8888)에서 서버를 운용하라는 명령어를 주면 됩니다.


app 만들기


django-admin startproject myproject .  

으로 현재 디렉토리에 myproject라는 프로젝트를 시작합니다.

settings.py 안에는 프로젝트를 운용하는데 필요한 여러 설정들이 있습니다

urls.py 는 사용자가 접속하는 그 path에 따라서, 그 접속을(요청을) 어떻게/누가 처리해줄것인가를 지정하는 , 말하자면 routing(네트워크 안에서 통신 데이터를 보낼 때 최적의 경로를 선택하는 과정)을 담당하는 파일입니다

myproject 폴더 밖에 있는 manage.py : 프로젝트를 진행하는데 있어서 필요한 여러가지 기능이 들어가 있는 유틸리티 파일입니다.

근데, 실제로 애플리케이션은 프로젝트 안에서 구현하는 것이 아니라, app이라고 하는 더 작은 단위를 만들어서, 그 안에서 실제 구현을 하게 됩니다.

프로젝트를 만들다보면 복잡해질 수 있는데, 여러개의 app들을 만든 후, 서로 연관된 로직들을 모아서 grouping하게 됩니다. 

각각의 app안에도 urls.py 라는 파일이 또 담겨져 있게 됩니다. 

그리고 app 안에는 views.py 라는 파일이 담기고 , 그 파일 안에 여러 함수를 만들어 application의 구체적인 구현을 하게 됩니다. 

전반적인 작동과정을 보면 이해가 한결 쉽습니다.




1. 사용자가  여러가지 경로로 접속하게 되는데, 그 각각의 경로를 누구에게 위임할 것인가를 urls.py 파일을 수정해서 결정합니다
2. 그 urls.py 파일에 적혀있는데로 적당한 app으로 위임이 됩니다
3. 그럼 app 안에 있는 urls.py 안에서도 제가 작성한데로 적당한 view의 적당한 함수에 위임이 됩니다
4. 이제 구체적인 작업을 해나갈 것인데, 그 과정에서 Database를 사용하게 됩니다. 근데 django안에 model이라는 굉장히 편리한 수단이 있어서, 그 model을 통해서 DB를 사용하게 됩니다.
5. model을 통해 DB에 있는 정보를 받아서, 우리는 클라이언트에게 응답을 해주게 됩니다.
6. 그 결과 최종적으로 html이나 json이나 xml같은 형태의 데이터를 만들어 사용자에게 보내주게 됩니다.

app을 직접 만들어봅시다. django-admin startapp myapp(뒤의 myapp은 app이름) 의 명령어를 실행하자




myapp이라는 폴더가 생겼습니다.


Routing

사용자가 접속한 각각의 경로를 누가 처리할 것인가를 지정하는 것이 routing입니다

Django에서는 project폴더 안의 urls.py가 가장 큰 틀의 routing을 하고, 그것을 적당한 app으로 위임을 해주면, app이 view안에 있는 특정 함수로 위임을 하여 그 함수가 작업을 처리하게 됩니다. 


myproject/urls.py 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path, include    #include를 사용하기 위해import해줌

# http://127.0.0.1/
# http://127.0.0.1/app/

# http://127.0.0.1/create/
# http://127.0.0.1/read/1/

urlpatterns = [                          # urlpatterns안에 routing과 관련된 list가 있어야 함
    path('admin/', admin.site.urls),     # 관리자화면으로 이동하기 위한 routing설정
    path('',include('myapp.urls'))       # http://127.0.0.1/ 으로 사용자가 접속했을 때 myapp에 있는 urls의 views로 위임하기 위해
]                                     # include의 인자는 myapp.urls

myapp/urls.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
"""myproject URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/4.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from xml.etree.ElementInclude import include
from django.contrib import admin
from django.urls import path
from myapp import views 

urlpatterns = [
    path('', views.index), 
    #사용자가 아무것도 없는 경로로 들어왔다면, views.index 실행해라
    path('create/',views.create), #사용자가 create로 들어왔다면 #views.create실행
    path('read/<id>/',views.read) #사용자가 read/1으로 들어왔다면 views.read 실행
]                                  # 가변 값이라는 것을 나타내기 위해 <>안에 값의 이름을 넣음

myapp/views.py

1
2
3
4
5
6
7
8
9
from django.shortcuts import render, HttpResponse

# Create your views here. #클라이언트로 정보를 전송하는 역할을 하는 함수
def index(request):                    #관습적으로 request를 인자에 사용(요청과 관련된 여러 정보 들어오는 객체가 전달되기에)          
    return HttpResponse('Welcome!')    # client로 보내줄때 http를 이용해서 return하겠다는 의미
def create(request):
    return HttpResponse("create")
def read(request,id):                  #가변값 id를 파라미터로 받아 return함
    return HttpResponse("Read"+id)  

127.0.0.1:8000/read/1/ 로 접속했을 시 




127.0.0.1:8000/read/2/ 로 접속했을 시 



정리!!



1. read/1 로 들어오면 사용자는 myproject의  urls.py의 urlpatterns를 확인
1-a. 확인 후 myapp.urls.py 로 이동
2. myapp.urls.py의 urlpatterns의 read/<id>를 확인
2-a. views.read로 위임됨 
3. read함수가 실행되어 HttpResponse(’Read’+id)값을 client에 보내줌