본문 바로가기

프로그래밍/python django

투표 어플리케이션을 통해 django 이해하기-3



지난 시간 투표기능과 결과를 보는 것까지 만들어보았다. 이번에는 질문과 선택지를 직접 작성할 수 있는 화면을  만드는 작업을 수행하겠다.

해당 기능을 수행하기위해서는 input태그가 필수적이다. input 태그를 통해 질문이나 선택지를 입력하고 이를 보내 만들어야하기때문이다;

이런 input 태그를 쉽게 자동으로 만들어주는 기능을 Django는 제공하고있다.

해당 기능을 수행하기위해 우선 forms.py를 만들어주자.


vote 폴더를 우클릭해 pydev module을 만들어주자.


이때 이름은 forms.py로 해준다.




forms.py에 input태그 자동설정을 정의할 수 있다.

from django.forms.models import ModelForm 
#ModelForm을 사용하기위해 선언해준다.
#이 모델폼을 상속하는 객체를 만들어서 자동적으로 input태그를 생성시켜준다.
from .models import Question, Choice
#이전에 만든 model들을 사용하기위해 선언해준다.
class QuestionForm(ModelForm):
#ModelForm을 상속하여 새 객체를 만들어준다.
    class Meta:
#Meta는 다음과 같은 3가지로 이루어진다.
        model=Question
#어떤 모델을 연동시킬 것인지 지정해준다.
        fields = ['name']
#해당 모델에서 어떤 데이터 종류들이 입력양식으로 만들 것인지 선택해준다.
        #exclude = ['date']
#exclude는 fileds와 정반대이다. 어떤 종류를 입력양식으로 만들지 않을 것인지 선택해준다.
#exclude와 fields는 서로 반대이므로,  하나만 선택해서 사용할 수 있다.

class Choice(ModelForm):
    class Meta:
        model=Choice
        fields = ['q','name']




작성된 입력태그를 이용해 객체를 추가하거나 삭제, 편집하는 것이 가능하다.


질문만들기

from .forms import QuestionForm, ChoiceForm   #앞서만든 Form class를 선언해주고
from _datetime import datetime #나중에 사용할 datetime도 선언해주자.
                  
def qregister(request):
    if request.method == "GET": 
#Django에서 많이 사용하는 방식으로 GET과 POST일 때 각각 다른 동작을 지정시켜주는 것이다.
#처음 해당 함수를 수행할 때는 GET으로 수행된다.

        form = QuestionForm()
        return render(request, 'vote/qregister.html', {'f':form})
#처음 GET으로 수행하면  빈 QuestionForm 객체을 생성한다.
#자동으로 input 태그가 형성되며 페이지가 HTML로 넘어가는 부분이다.

    elif request.method == "POST":
#input 태그가 형성되면 빈칸에 원하는 데이터를 입력하고 다시 POST방식으로 함수를 실행한다.
        form = QuestionForm(request.POST)
#이때는 사용자가 입력한 데이터로 QuestionForm 객체를 생성한다.
        if form.is_valid(): # 사용자 입력데이터가 유효한지(글자 수, 중복 등등) 검사한다.
            q = form.save(commit=False)
#form.save는 DB에 저장하라는 뜻이다. 그러나 가로 안에 commit=False를 붙였다.
#commit=False는 실제로 DB에 저장하지말고 해당 홈페이지에서만 수행하라는 뜻이다.
#해당 명령어 실행 시 q라는 변수안에 form.save가 저장되고 실제로 DB에 저장하지는 않는다.
            q.date = datetime.now()
#commit을 쓰는 이유는 Question객체의 date 변수 때문이다.
#Question 객체의 date는 날짜를 저장하나 앞서 입력 양식으로 지정해주지 않았다.(fields에 x)
#고로 date 변수의 입력태그는 생기지않고 따라서 사용자가 임의로 입력할 수 없다
#그러면 DB에 Null값이 저장되는데 DB자체에서 Null값을 거부하기때문에 에러가 발생한다.
#고로 datetime.now()를 사용하여 현재 시간을 date에 저장해주자.
            q.save()
#그리고나서 save를 해주자.
            return HttpResponseRedirect(reverse('vote:index'))
#그리고나서 자동으로 vote의 메인 페이지인 index로 넘겨주자.)




조금은 어려운 부분이 많이 등장한다. 전체적인 이해를 돕기위해 빠르게 template과 url을 작성해보자

templates의 vote 폴더에 qregister.html을 만들어주고 다음과 같이 입력한다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>설문조사 등록</h1>

<form action = "" method="POST"> 
<!-- action에 아무것도 입력하지 않으면 다시 본인의 url을 부른다.-->
<!-- 당연히 POST 형식으로 본인의 url을 부른다. -> 같은 함수지만, 다른 기능이 수행된다. -->
{% csrf_token %}
{{ f.as_p}}
<!-- 입력태그가 어떤 식으로 입력될지를 결정해준다 -->
<!-- f.as_p는 input 태그가 <p><input></p>과 같이 <p>태그사이에 들어간다.-->
<!-- f.as_table은 <tr><td>태그 안에 <input>태그가 들어간다.-->
<input type="submit" value="질문등록">
</form>
</body>
</html>





url은 다음과 같이 한줄만 추가해주면 된다.

urlpatterns = [
    path('', index, name='index'),
    path('<int:qid>',detail, name='detail'),
    path('vote/', vote, name='vote'),
    path('result/<int:q_id>/', result, name="result"),
    path('qr', qregister, name='qregister') #qr이름으로 qregister 함수를 실행시켜준다.
    ]




index.html의 </body>태그 전에 다음과 같은 한 줄을 추가시키면 연결이 완료된다.

</table>
<a href="{% url 'vote:qregister' %}">질문지등록</a>

</body>





그리고 서버를 구동시켜 접속해보면


index.html로 들어가 새로 생긴 질문지 등록을 누른다.






이때는 qregister함수가 get방식으로 수행된다. 고로 빈 form 객체만 생성된다. 여기다가 원하는 질문을 입력 후 등록을 누르면






이때는 POST 방식으로 실행되어 질문을 만들 수 있다.




질문수정

질문 수정도 만들기와 비슷하게 진행되면 된다.


우선 views.py 부터 작성해보자. 

def qupdate(request, q_id):
    obj = get_object_or_404(Question, id=q_id) # q_id를 기준으로 객체를 불러온다.
    if request.method == "GET":
        form = QuestionForm(instance=obj) 
#instance=obj는 현재 객체값이 화면에 떠있는 것 형태를 만들어준다.
#수정이기때문에 현재 객체값이 무엇인지 화면에 떠있는 형태로 작성해줘야한다.
        return render(request, 'vote/qupdate.html',{'f':form} )
    
    elif request.method == "POST":
        form = QuestionForm(data=request.POST, instance=obj)
        if form.is_valid:
            q = form.save()
            return HttpResponseRedirect(reverse('vote:index'))



그리고 template을 작성해준다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="" method="POST">
{% csrf_token %}
<table>

{{f.as.table}}

</table>
<input type="submit" value="수정">
</form>
</body>
</html>



url에 다음과 같은 한줄을 추가해준다.

    path('qu<int:q_id>', qupdate, name='qupdate'),



마지막으로 detail.html의 </form>과 돌아가기 링크 사이에 다음과 같이 추가해준다.



</for
<a href="{% url 'vote:qupdate' q.id %}">수정하기</a>
<br>
<a href="{% url 'vote:index' %}">돌아가기</a>



수정하기 버튼이 추가된 것을 확인 후 눌러준다.




원하는 질문을 입력 후 수정을 눌러주면




질문이 수정된 것을 확인 가능하다.



질문삭제

질문삭제는 비교적 간단하게 가능하다. 그냥 객체를 삭제해주면 되기때문이다.



views.py는 다음과 같이 작성해주면된다.

def qdelete(request, q_id):
    q = get_object_or_404(Question, id=q_id)
    q.delete() #선택된 객체를 삭제
    return HttpResponseRedirect(reverse('vote:index'))
        



template은 당연히 작성해줄 필요가 없고 url만 따로 작성해주면된다.

  path('qd<int:q_id>', qdelete, name='qdelete')




마지막으로 아까 추가한 수정하기 밑에 삭제하기를 추가해주자.

<a href="{% url 'vote:qupdate' q.id %}">수정하기</a>
<a href="{% url 'vote:qdelete' q.id %}">삭제하기</a>
<br>





삭제하기를 누르면






4번 객체가 삭제된 것을 확인 가능하다.





Choice 작성도 이와 같이 진행하면 된다.


답변만들기

답변 만들기도 질문만들기와 똑같이 진행해주면 된다. 우선 view부터

def cregister(request):
    if request.method == "GET":
        form = ChoiceForm()
        return render(request, 'vote/cregister.html',{'f':form} )
    elif request.method == "POST":
        form = ChoiceForm(request.POST)
        if form.is_valid():
            c = form.save()
            return HttpResponseRedirect(reverse('vote:detail', args=(c.q.id,)))
        else: # 이번엔 error일때 경우를 넣어줘봤다.
            return render(request, 'vote/cregister.html',{'f':form, 'error':'유효하지않은 값입니다'})
#error일 경우 error 변수에 메시지를 담아서 보낸다. 이것을 template에서 보여줄 수 있다.




template은 cregister의 이름으로 다음과 같이 작성한다.

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Insert title here</title>

</head>

<body>

<h1>답변등록</h1>

{% if error %}  <!-- error 변수에 값이 들어있을 경우 메시지를 보여주기위해 작성 -->

<p style="color:red">{{ error }} </p>

{% endif %}

<form action="" method="POST">

{% csrf_token %}

<table>

{{ f.as_table }} 

</table>

<input type="submit" value="등록하기">

</form>


</body>

</html>


urls.py 에 한줄을 추가하고

path('cr/', cregister, name='cregister') #urls.py에 url 추가



detail.html에서 하이퍼 링크를 추가시킨다.

</form>
<a href="{% url 'vote:cregister' %}">답변등록</a><br>



답변등록을 눌러주고




원하는 객체를 선택해주고 등록하기를 누른다.




등록된 것을 확인할 수 있다.





답변을 등록할때도 봤지만 약간의 불편함이 있다. 우선 Question이 정확히 무엇인지 보이는 것이 아니 object number로 보여서 질문을 구분하기 힘들다. 또한 Q와 name이라고만 나와있어서 정확한 의미가 무엇인지 구분하기 힘들다.

우선 object number가 아닌 질문 이름으로 보이기 위해선 models.py에서 코드를 작성해주어야한다.

class Question(models.Model):
    name = models.CharField(max_length=100)
    date = models.DateTimeField()
    def __str__(self): #__str__ 함수를 써서 보이는 양식을 바꿀 수 가 있다.
        return self.name #return 값으로 보이고 싶은 변수 명을 쓰면 된다.


수정 후 다시 사이트에 들어가서 새로고침을 해보면

다음과 같이 질문으로 보이는 것을 확인 가능하다.



 


Q와 Name처럼 보여지는 변수의 이름을 바꾸고싶다면 forms.py를 수정해주면 된다.

class ChoiceForm(ModelForm):
    def __init__(self, *args, **kwarg): #이와같이 생성자를 호출해서
        super().__init__(*args, **kwarg)
        self.fields['q'].label="질문"  #각각 필드의 라벨을 바꾸어주면된다.
        self.fields['name'].label = "답변"
        







답변 수정/삭제

이것 역시 똑같이 진행하면 된다.

답변 수정과 삭제 함수는 다음과 같다.

def cupdate(request, c_id):
    obj = get_object_or_404(Choice, id=c_id)
    if request.method == "GET":
        form = ChoiceForm(instance=obj)
        return render(request, 'vote/cupdate.html',{'f':form} )
    elif request.method == "POST":
        form = ChoiceForm(data=request.POST, instance=obj)
        if form.is_valid:
            c = form.save()
            return HttpResponseRedirect(reverse('vote:detail', args=(c.q.id, )))

def cdelete(request, c_id):
    c = get_object_or_404(Choice, id=c_id)
    c.delete()
    return HttpResponseRedirect(reverse('vote:detail', args=(c.q.id, )))


template 또한 cupdate.html만 따로 만들어주면 된다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>답변 수정</h3>
<form action="" method="POST">
{% csrf_token %}
<table>
{{ f.as_table }}
</table>
<input type="submit" value="수정하기">
</form>
</body>
</html>


마지막으로 똑같이 url과

    path('cu<int:c_id>', cupdate, name='cupdate'),
    path('cd<int:c_id>', cdelete, name='cdelete'),

detail.html for문 사이에 하이퍼 링크를 추가해주면 된다.

<a href="{% url 'vote:cupdate' c.id %}">수정</a>
<a href="{% url 'vote:cdelete' c.id %}">삭제</a>





Template BASE 작성

작성한 template을 보다보면 body 내부 태그를 제외하고 모두 비슷한 설정인 것을 확인 가능하다.

대부분의 homepage를 보면 위와 아래는 고정되고 가운데 부분만 바꾸어서 서비스를 제공하는 것을 볼 수 있다.

지금부터 이러한 홈페이지를 만들어 볼 것이다. 우선 고정되는 부분을 base.html이라는 문서로 templates 폴더에 작성해준다.

(vote폴더가 아닌 templates 폴더!!)

templates 폴더 우클릭 - 새로만들기 - other로 들어가 HTML 파일 선택후






base.html을 만들어준다.




base.html의 내용은 다음과 같다.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>netdream 홈페이지</h2>
<a href="{% url 'vote:index' %}">설문조사</a>
<hr> 
<!-- 윗부분(고정) -->



{% block content %}
<!-- 변동부분을 block 지정해준다. -->
{% endblock %}    


<!--  아랫부분(고정) -->
<hr> 
<p>netdream blog</p>
<p><a href=http://netdream.tistory.com/>netdream.com</a>으로 놀러오세요</p>


</body>
</html>


이제 여태까지 작성했던 html 파일에 가서 <body> </body>태그를 포함하여 위,아래를 다 지우고 아래와 같이 추가해준다.

{% extends 'base.html' %}
{% block content %}
<!-- body 태그를 지우고 위와 같이 추가해준다. -->
<h1>투표 어플리케이션</h1>
<p>투표할 질문을 선택해주세요</p>

<table>
<tr>
<th>질문번호</th>
<th>제목</th>
<th>생성일</th>
</tr>
{# html+pydev 파일에서의 주석은 다음과 같이 괄호와 샵을 사용한다. #}

{% for q in a %} {# 만약 파이썬 문법을 사용하고 싶다면 {% %}를 사용한다. #}
{# for문의 시작 #}

<tr>
<td>{{q.id}}</td> {# 단순히 값을 표한하고 싶다면 {{ }} 을 사용한다.#}
<td><a href="{% url 'vote:detail' q.id %}">{{q.name}}</a></td>
<td>{{q.date}}</td>
</tr>
{% endfor %} {# for문의 마지막 #}

</table>
<a href="{% url 'vote:qregister' %}">질문지등록</a>
{% endblock %}
<!-- 마지막 부분도 위와 같이 추가해준다. -->

다른 HTML 파일도 전부 다 똑같이 진행해준다.

{% extends 'base.html' %}
{% block content %}

<h1>{{q.name}}</h1>
<p>{{q.date}}</p>
<form action="{% url 'vote:vote' %}" method="POST">
{% csrf_token %}
{% for c in q.choice_set.all %}
<input type="radio" name="a" value="{{c.id}}">
<label>{{c.name}}</label>
<a href="{% url 'vote:cupdate' c.id %}">수정</a>
<a href="{% url 'vote:cdelete' c.id %}">삭제</a>
<br>
{% endfor %}
<input type="submit" value="투표하기">
</form>
<a href="{% url 'vote:cregister' %}">답변등록</a><br>
<a href="{% url 'vote:qupdate' q.id %}">수정하기</a>
<a href="{% url 'vote:qdelete' q.id %}">삭제하기</a>
<br>
<a href="{% url 'vote:index' %}">돌아가기</a>

{% endblock %}





다음과 같이 어떤 HTML 페이지에 들어가든






위와 아래는 고정된 것을 확인 가능하다.