먼저 쿼리를 통해 가져온 객체를 변수에 담고,

이를 delete() 메소드를 사용하여, 리다이렉트만 해주면 간단히 끝난다.



1
2
3
4
def menu_delete(request, menu_id):
    menu = Menu.objects.get(id=menu_id)
    menu.delete()
    return redirect("/partner/menu/")
cs


블로그 이미지

쵸잇

,

목적은 

데이터를 입력할 때의 폼과 그 데이터를 수정할 때의 폼동일하게 쓰인다는 점에서,

1개의 폼 사용을 유지하고자 템플릿을 서로 만들지 않고 같은 템플릿을 사용하게 한다.


조건 2가지

1) url 경로와 view 함수는 서로 달라야한다. (기능이 다르므로)

2) 서로 다른 기능을 하는 것임을 표시할 수 있도록 조건에 따라 다르게 화면에 보여야한다.



1-1) url과 view 함수를 따로 만든다.


서로 다른 경로를 가지고, 서로 다른 기능을 유지하기에 고유의 url와 view 함수를 지닌다.



1-2) 매핑할 템플릿 페이지를 동일하게 지정한다.


(데이터 추가하는 add 함수)


1
2
3
4
def add(request):
    ctx = {}
    (생략)
    return render(request, "add.html", ctx)
cs


(데이터 수정하는 edit 함수)


1
2
3
4
def edit(request):
    ctx = { "replacement" : "수정" }
    (생략)
    return render(request, "add.html", ctx)
cs



2) 기존 템플릿에서 조건문을 추가한다.


(데이터 추가 텍스트만 보이는 템플릿)


1
2
3
4
5
6
추가하기
<form enctype="multipart/form-data" method="post" action="">
  {% csrf_token %}
  {{ form.as_p }}
  <button class="btn btn-default btn-block">추가</button>
</form>
cs



(조건문을 넣어서 사용하는 함수에 따라 다르게 보이는 템플릿)


1
2
3
4
5
6
{% if replacement %}{{ replacement }}{% else %}추가{% endif %}하기
<form enctype="multipart/form-data" method="post" action="">
  {% csrf_token %}
  {{ form.as_p }}
  <button class="btn btn-default btn-block">{% if replacement %}{{ replacement }}{% else %}추가{% endif %}</button>
</form>
cs


edit 함수를 매핑하는 url로 접속시 replacement호출하도록 조건을 내세워 "추가하기" 텍스트를 "수정하기" 형태로 바꾼다.

블로그 이미지

쵸잇

,

텍스트와 다르게 이미지를 저장하려면 많은 수고가 든다.




1) Settings.py에서 이미지 파일을 저장베이스 폴더(media) 경로를 설정한다.


1
2
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
cs



2) Urls.py에서 개발 중미디어 파일(이미지, 비디오 등)을 다루기 위한 세팅

(단, DEBUG 상태에서만 가능하도록 if문을 별도로 작성)


1
2
3
4
5
6
from django.conf import settings
from django.conf.urls.static import static
 
if settings.DEBUG:
    urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
cs



3) Models.py에서 이미지 필드 만들기


1
image = models.ImageField()
cs


*이미지 필드 사용시 Pillow(https://pillow.readthedocs.io/en/latest/)라는 라이브러리 설치가 migrate 과정에서 요구된다.

(내가 올리는 이미지 파일의 포맷을 도와준다)



4) Forms.py에서 이미지 필드 지정하기


1
2
3
4
5
6
class Form(ModelForm):
     class Meta:
         model = 
         fields = (
             'image'
         )
cs



5) 템플릿에서 에 이미지 파일을 입력 받기 위해 이미지 전송을 위한 별도의 속성을 추가한다


1
<form enctype="multipart/form-data" method="post" action="">
cs



4) View에서 폼을 통해 입력 받은 이미지를 폼에 두고자 이미지 소스(request.FILES)를 데이터 소스와 같이 둔다. 


1
2
if request.method == "POST":
    form = Form(request.POST, request.FILES)
cs


블로그 이미지

쵸잇

,

쿼리 연습



1) 

상황: 메뉴페이지에서 판매자(로그인 상태)가 자신이 등록한 메뉴만으로 만들어진 리스트노출된다. 

해결: 

(*메뉴 정보에 판매자 데이터 포함) 

메뉴클래스에서 판매자가 누구인지 필터하면 해당 판매자가 등록한 메뉴가 추출된다.

단, 접속한 판매자의 정보로만 구성된 메뉴리스트가 필요하다.

필터 값로그인한 판매자로 지정하여 해당 판매자의 업체 메뉴만 노출되도록 한다.


1
menu_list = Menu.objects.filter(partner=request.user.partner)
cs


블로그 이미지

쵸잇

,

html로 하나씩 입력칸을 만드는게 아니고 모델 폼을 만들어서 효율적으로 데이터를 입력하는 방법을 공부했다. 

(https://practice-a-lot.tistory.com/56)


데이터를 입력했으면 수정하는 방법도 알아보자.


입력한 데이터를 가져오는 방법쿼리를 통해데이터가 저장된 객체를 호출하여 폼에 두는 것이다.

빈 폼과의 차이점은 폼이 데이터를 보유했냐는 여부이다.



1
2
3
4
5
6
def edit_info(request):
    ctx = {}
    if request.method == "GET":
        character = Character.objects.get(user=request.user)
        form = CharacterForm(instance=character)
        ctx.update({ "form" : form })
cs



쿼리 없이 더 간단하게 처리하는 방법직접 데이터에 접근하는 것이다.


1
2
3
4
5
def edit_info(request):
    ctx = {}
    if request.method == "GET":
        form = CharacterForm(instance=request.user.character)
        ctx.update({ "form" : form })
cs


request.user.character는 이미 모델 폼을 통해 입력된 데이터가 포함되어있다. 

그러므로 굳이 쿼리를 사용해 변수에 지정하는 수고를 덜 수 있다.



데이터를 수정 후 저장할 때에도 동일한 인스턴스를 지정해둔다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def index(request):
    ctx = {}
    if request.method == "GET":
        form = CharacterForm(instance=request.user.character)
        ctx.update({ "form" : form })
    elif request.method == "POST":
        form = CharacterForm(
            request.POST,
            instance=request.user.character
        )
        if form.is_valid():
            character = form.save(commit=False)
            character.user = request.user
            character.save()
            return redirect("/character/")
        else:
            ctx.update({ "form" : form })
cs


블로그 이미지

쵸잇

,
폼을 직접 만들어서 데이터를 처리한 적이 있다. 

폼의 작동 원리를 알았다면 활용도 높은 모델 폼(ModelForm)을 통해 데이터를 처리하는 방법을 알아본다.

* 모델 폼 작성요령(https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/)은 이곳에서 살펴보면 된다.


웹사이트에서 모델 폼을 통해 입력 받은 데이터를 view에서 어떻게 처리하는지는 
여기(https://docs.djangoproject.com/en/1.10/topics/forms/)에 설명이 되어있다.


나의 목표는 

1) 웹사이트에 모델 폼을 만들어서 데이터를 입력 받을 수 있도록 만들고
2) 모델 폼을 통해 입력 받은 데이터데이터베이스에 저장하도록 한다 (단, 조건에 부합한 경우저장, 아니면 재작성)


1-1) forms.py에서 Character 모델 클래스를 반영한 모델 폼 클래스를 만든다.

1
2
3
4
5
6
7
8
9
class CharacterForm(ModelForm):
     class Meta:
         model = Character
         fields = (
             'user',
             'name',
             'type',
             'gender'
         )
cs


1-2) View에서 만들어진 모델 폼호출하여 템플릿에 전달한다. 
   
1
2
3
4
5
def index(request):
    ctx = {}
    if request.method == "GET":
        form = CharacterForm()
        ctx.update({ "form" : form })
cs


GET 방식일 때, 빈 폼변수에 담아 템플릿으로 보낸다. 
폼이 비어있는 상태에서 데이터를 입력 받을 수 있도록 하기 위함이다. 
(템플릿에서 직접 폼을 만드는 번거로움이 확연히 줄었다)



2) 폼을 통해 입력 받은 데이터를 View에서 처리하여 데이터베이스에 저장한다.

 

(웹사이트에서 폼을 통해 데이터가 전송된 상황) POST 요청일때, request로부터 데이터를 가져와 폼 인스턴스를 만든다.

- *폼은 데이터가 입력된 폼 인스턴스에 대한 유효성 검사(데이터베이스 필드조건 부합)를 할 수 있다.

- Partner 클래스에 user 필드를 제외한 name, contact, address, description 필드에만 데이터를 입력 받은 상태이다.

- 인스턴스는 비어있는 필드가 있으면 데이터베이스에 저장하지 않는 경우가 발생한다.

- 아니면 저장하기 전직접 필드에 데이터를 입력하여 최종 저장시킨다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
def index(request):
    ctx = {}
    if request.method == "GET":
        form = CharacterForm()
        ctx.update({ "form" : form })
    elif request.method == "POST":
        form = CharacterForm(request.POST)
        if form.is_valid():
            character = form.save(commit=False)
            character.user = request.user
            character.save()
            return redirect("/character/")
        else:
            ctx.update({ "form" : form })
cs


2-1) 데이터를 저장하므로 POST 방식을 사용하고, 데이터를 입력 받은 폼변수로 지정한다.

 

2-2) form.is_valid()를 통해 입력된 데이터의 유효성 검사를 한다. 올바르지 않으면 재작성한다.


2-3) user 값은 직접 입력하는 것이 아니므로 별도로 채워서 최종 저장을 한다.

블로그 이미지

쵸잇

,

서비스를 이용하다가 1개 계정당 게임 캐릭터를 1개 사용할 수 있다던지하는 예를 본 적이 있을 것이다.

계정 데이터를 갖고 있는 User 테이블게임 캐릭터 정보를 갖고 있는 Charater 테이블을 만들면 아래와 같다.


User

id

username

email

password

1

streetfighter

realguy@world.com 

************** 

2

kingoffighter

toughguy@world.com 

*********** 


Character 

id

user(OneToOne)

name 

type

gender

1

1

길거리파이터

태권도

여자

2

2

싸움왕

무에타이

남자


로그인시 사용하는 계정 이름"streetfighter"인 사용자는 게임에서 "길거리파이터"라는 캐릭터 이름을 갖고 있다. 

"streetfighter"로 접속한 사람이 게임에서 사용할 캐릭터는 "길거리파이터" 뿐인 것이다.


이렇게 1대1인스턴스를 이어주는 역할을 하는 것이 OneToOneField이다.


서로 데이터가 연결되어있으니 User 테이블에서 Charater 테이블로 자유롭게 접근이 가능해진다.

OneToOneField를 사용하기 위해서는 관계를 맺을 테이블에서 새로 필드를 만들어서 자신의 테이블명을 입력하면 완성된다.


1
2
3
4
5
class Character(models.Model):
    user = models.OneToOneField(User)
    name
    type
    gender
cs


블로그 이미지

쵸잇

,

회원으로 인증 받기(로그인) 위해서 필요한 절차는?


보통 로그인은 내가 입력한 username, password 값을 회원가입 때 저장된 username, password 값과 대조하는 시스템이다. 



로그인 폼을 만들어 username과 password를 입력하고,


view에서 전달 받은 username과 password를 갖고 데이터베이스에 저장된 데이터와 대조하여 인증 절차를 거친다.

(인증 기능을 갖고 있는 authenticate 함수를 활용한다)


그 결과 회원 인증 완료시 원하는 페이지를 출력한다.



1) 로그인 폼을 만들어 username과 password 입력칸을 만든다


1
2
3
4
5
6
7
8
9
10
11
{% block body %}
<div class="container">
  <form method="post" action="" class="form-signin">
    {% csrf_token %}
    <h2 class="form-signin-heading">Please log in</h2>
    <input type="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
    <input type="password" name="password" class="form-control" placeholder="Password" required="">
    <button class="btn btn-lg btn-primary btn-block" type="submit">Log in</button>
  </form>
</div>
{% endblock %}
cs



view에서 인증처리하는 방식에 대한 설명은 아래와 같다.

(https://docs.djangoproject.com/en/1.10/topics/auth/default/#how-to-log-a-user-in)



1
2
3
4
5
6
7
8
9
10
11
from django.contrib.auth import authenticate, login
 
def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
    else:
        # Return an 'invalid login' error message.
cs



로그인 폼에서 입력 받은 username과 password 값을 authenticate 함수를 통해 인증 절차를 거치고,

if문을 작성하여 확인 여부에 따라 로그인 처리를 하는 login 함수를 사용한다.

로그인에 성공하면 원하는 페이지로 redirect 시킬 수 있게 url을 입력한다.



2) authenticate 함수와 login 함수를 적용한다


1
2
3
4
5
6
7
8
9
10
11
12
13
def login(request):
    if request.method == "GET":
        pass
    elif request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")
        user = authenticate(username=username, password=password)
        if user is not None:
            auth_login(request, user)
        else:
            pass
 
        return redirect("/partner/")
cs



login에서 auth_login으로 바뀐 까닭은?

이미 view의 이름으로 login이 존재하므로 중복사용을 피하고자 다른 명칭으로 login 함수를 사용해야한다.

안그러면 오류가 발생하는데 이유는 최초에 쓰인 login 함수가 2개의 인자를 받지 않고 request 한 개만 받고 있기 때문이다.


이를 위해 함수를 import할 때 전치사 "as"를 사용하여 auth_login으로 사용함을 명시해주어야한다.


1
from django.contrib.auth import authenticate, login as auth_login
cs


그럼 중복을 피하면서 똑같은 기능을 유지할 수 있다. 


이번에는 로그인에 실패한 경우 실패메시지를 보내도록 설정해보자.


로그인 폼에서 입력 받은 정보가 똑같이 데이터베이스로 전달되지만,


User 모델에 저장된 회원 정보와 다를 경우 login 처리를 시키지 않고,


로그인 폼 아래에 "존재하지 않는 사용자"라는 텍스트만 전달할 뿐 어떠한 변화를 주지 않는다.

   


1
2
        else:
            ctx.update({ "error" : "존재하지 않는 사용자입니다." })
cs


비어있는 ctx 변수의 값을 변경하는 방식으로 메시지 전달 방식을 택했다.


1
2
3
4
5
6
7
8
9
10
<div class="container">
  <form method="post" action="" class="form-signin">
    {% csrf_token %}
    <h2 class="form-signin-heading">Please log in</h2>
    <input type="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
    <input type="password" name="password" class="form-control" placeholder="Password" required="">
    {{ error }}
    <button class="btn btn-lg btn-primary btn-block" type="submit">Log in</button>
  </form>
</div>
cs



로그아웃 하기


로그아웃은 로그인시 사용된 우리의 정보가 떨어져 나가게 하는 시스템이다.

따라서 로그인과 다른 점은, 데이터 입력이 필요 없다. logout 함수만 사용하면 로그아웃 완료된다.



1
2
3
def logout(request):
    auth_logout(request)
    return redirect("/partner/")
cs


단, 로그아웃 버튼에 대한 url 주소를 추가할 필요가 있다. 그래야 logout 함수가 활성화된다.


1
2
3
4
5
6
7
8
9
from django.conf.urls import url
from .views import index, signup, login, logout
 
urlpatterns = [
    url(r'^$', index, name='index'),
    url(r'^signup/$', signup, name='signup'),
    url(r'^login/$', login, name='login'),
    url(r'^logout/$', logout, name='logout'),
]
cs


블로그 이미지

쵸잇

,

장고의 장점 중에 하나인 내장된 User 모델을 활용하여 유저 관리를 쉽게 할 수 있다.


장고 authentication system에서 User 모델에 대한 설명이 있다.

https://docs.djangoproject.com/en/1.10/topics/auth/default/


위 자료를 통해 User 모델의 속성을 파악할 수 있고,

실제로 어떤 코드로 User 모델이 짜여져 있는지 확인하려면 아래 깃허브를 따라서 살펴볼 수 있다.
https://github.com/django/django/blob/master/django/contrib/auth/models.py



User 모델의 사용 목적은 회원가입을 받아서 회원들의 데이터를 저장하고자하는 것이다.

회원가입 폼을 만들어 "username" "email" "password"를 입력 받는다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
{% extends "base.html" %}
{% block title %}my_project{% endblock %}
{% block body %}
<div class="container">
  <form method="post" action="" class="form-signin">
    {% csrf_token %}
    <h2 class="form-signin-heading">Please sign in</h2>
    <input type="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
    <input type="email" name="email" class="form-control" placeholder="Email address" required="">
    <input type="password" name="password" class="form-control" placeholder="Password" required="">
    <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
  </form>
</div>
{% endblock %}
cs


1
<input type="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
cs


(*autofocus는 해당 웹페이지를 처음 열었을 때, 입력 폼에서 "username"을 입력하는 칸에 커서가 깜빡이도록 설정)


앞서 말했듯 장고에서 별도로 User 모델을 갖추고 있으므로 models.py에서 우리가 관여할 것은 전혀 없다.

User 모델이 저장된 곳에서 view로 잘 불러오기만 하면 된다. (from django.contrib.auth.models import User)

그리고 폼에서 입력 받은 데이터를 view에서 처리하여 하나의 user 객체로 만들어 User 모델에 저장된다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.shortcuts import render
from django.contrib.auth.models import User
 
# Create your views here.
def index(request):
    ctx = {}
    return render(request, "index.html", ctx)
 
def signup(request):
    if request.method == "GET":
        pass
    elif request.method == "POST":
        username = request.POST.get("username")
        email = request.POST.get("email")
        password = request.POST.get("password")
 
        user = User.objects.create_user(username, email, password)
 
    ctx = {}
    return render(request, "signup.html", ctx)
cs



user 인스터스가 만들어질 때 기존의 방식과 다른 점이 있다.


하나는 user 변수에 데이터를 입력 받은 객체를 담는다는 것이다.


두번째는 User.objects.create가 아닌 User.objects.create_user로 작성하는 것이다.


이 차이에 발생하는 장점은 폼에서 password를 입력한 그대로 저장되어 관리자에게 보여지지 않고,

특수 암호화되어 관리자도 볼 수 없게 처리하기 때문에 보안측면에서 도움이 된다.

블로그 이미지

쵸잇

,

최근에 만든 댓글 입력 폼은 작성하자마자 리다이렉션이 되어 화면에 댓글이 나타났다.

이를 즉시 노출하는 것에서 Admin을 통해 승인된 댓글만 보여지는 형태로 바꿔볼 것이다.


어떻게 댓글이 안보이다가 보이는지 생각해보면,

댓글의 허용 여부에 따라 화면에 노출되냐 마냐가 결정된다.


그러므로 Comment에 새로운 속성이 추가되어야 한다.

https://docs.djangoproject.com/en/2.1/ref/models/fields/


우리의 목적에 필요한 필드 속성은 참과 거짓의 값을 담는 BooleanField이다.

BooleanField 사용시 기본값을 정해주는데 최초 노출은 하지않으므로 False를 입력한다.

필드명은 목적에 부합하는 approved_comment로 정했다.



1
2
3
4
5
6
7
8
9
class Comment(models.Model):
    article = models.ForeignKey(
        Article,
        related_name="article_comments",
        on_delete=models.CASCADE
    )
    username = models.CharField(max_length=50)
    content = models.CharField(max_length=200)
    approved_comment = models.BooleanField(default=False)
cs



폼을 통해 comment에 대한 데이터를 받을 때에도 False 값을 받도록 고정시켰다.



1
2
3
4
5
6
7
8
9
    elif request.method == "POST":
        username = request.POST.get("username")
        content = request.POST.get("content")
        Comment.objects.create(
            article=article_detail,
            username=username,
            content=content,
            approved_comment=False
        )
cs



이제 템플릿에서 댓글 데이터를 가져올 때 True or False 여부를 따져서 노출시킬 필요가 있다.



1
2
3
4
5
6
7
8
       {% for comment in article_detail.article_comments.all %}
       {% if comment.approved_comment is True %}
       <h4 class="author">{{ comment.username }}</h4>
       <div class="text">
       {{ comment.content }}
       </div>
       {% endif %}
       {% endfor %}
cs


approve_comment의 값이 True인 경우에만 데이터가 노출되도록 if문을 작성했다.

기본적으로 댓글에 False 값을 가지므로 Admin을 통해 허용 여부가 체크되어야 비로소 화면에 노출된다.

블로그 이미지

쵸잇

,