소프트웨어공학 우정퀴즈 팀프로젝트 (2021.09~2021.12)

이번 학기는 유독 팀플이 많았다. 소프트웨어 공학 수업에서도 팀프로젝트를 진행했다. 목적은 소프트웨어 공학론을 팀별로 적용해보기 위해 자유주제로 개발 팀 프로젝트를 진행하는 것이었다. 우리 팀은 웹으로 crud기반의 최대한 간단한 서비스를 만들기로 했고 우정퀴즈를 제작하기로 했다.
기획은 다 같이 진행했다. 출제자가 자신의 이름으로 7문제를 내면 URL이 생성된다. 응시자는 해당 URL을 통해 문제를 풀 수 있고, 점수판에 이름을 올릴 수 있다. 사용한건 HTML, CSS, Django 위주였다. 나같은 경우에는 멋쟁이 사자처럼을 통해 한번 배운 경험이 있으나 팀장님을 제외한 나머지 팀원분들은 관련 경험이 없어 걱정이 많았으나, 지금 뒤돌아보면 다들 공부를 열심히 하셨는지 자신의 맡은 바를 충분히 다 해오셨다.
대체적인 Django 세팅은 팀장님이 다 해두신 상태였고 그 상태에서 분배를 진행했다. 나는 '문제 풀이 페이지'를 거의 전담했다. 프론트와 백의 코드를 거의 작성했다.
1) 프론트엔드
{% extends 'quiz/_default.html' %}
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static 'css/page/solvePage.css' %}" />
<body>
<div class="solvenumb">{{host}}님의 퀴즈를 푸는 페이지</div>
<!--퀴즈 응답자 이름 적는 란-->
<form action="{% url 'solve' quiz_set_id %}" method="post" onsubmit="return false" id="Form">
{% csrf_token %}
<div class="quizguest">퀴즈 푸는 사람:</div> <input type="text" name="guestname" class="guestname" required {% if guestname %} value="{{ guestname }}" {% endif %}>
{% if messages %}
{% for message in messages %}
<div {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</div>
{% endfor %}
{% endif %}
<div class='questions'>
{% for quiz, is_checked in quizes %}
<div>
<h2>{{ forloop.counter }}. {{quiz.question}}</h2>
</div>
<input type="radio" name="{{ forloop.counter }}" id="{{ forloop.counter }}_1" value="1" {% if is_checked == 1 %} checked {% endif%}> <label for="{{ forloop.counter }}_1">{{quiz.option_1}}</label><br>
<input type="radio" name="{{ forloop.counter }}" id="{{ forloop.counter }}_2" value="2" {% if is_checked == 2 %} checked {% endif%}> <label for="{{ forloop.counter }}_2">{{quiz.option_2}}</label><br>
<input type="radio" name="{{ forloop.counter }}" id="{{ forloop.counter }}_3" value="3" {% if is_checked == 3 %} checked {% endif%}> <label for="{{ forloop.counter }}_3">{{quiz.option_3}}</label><br>
<input type="radio" name="{{ forloop.counter }}" id="{{ forloop.counter }}_4" value="4" {% if is_checked == 4 %} checked {% endif%}> <label for="{{ forloop.counter }}_4">{{quiz.option_4}}</label><br>
<input type="radio" name="{{ forloop.counter }}" id="{{ forloop.counter }}_5" value="5" {% if is_checked == 5 %} checked {% endif%}> <label for="{{ forloop.counter }}_5">{{quiz.option_5}}</label><br>
{% endfor %}
</div>
<button type="submit" value="퀴즈제출" class="btn" onclick="btnClick()">퀴즈 제출 및 점수 확인</button>
<script>
document.getElementById("Form").onsubmit=function(){
var confirm_Check=confirm("제출하시겠습니까?");
if (!confirm_Check){
return false;
}
}
</script>
</form>
</body>
{% endblock %}
프론트는 백에서 message와 quiz, host, is_checked를 가져온다. quiz는 필드로 question과 option을 가지고 있다. message는 지난 제출에서 형식이 맞지 않을 때 전달되며 에러 메시지를 노출한다. host는 출제자 이름으로 단순히 띄워주면 된다. quiz와 is_checked는 같이 사용하며 is_checked는 형식 미달로 redirect할 때 답변을 유지하기 위해 사용한다.
forloop.counter를 많이 활용하였다. 문제와 다섯 개의 선지를 보여주며, 선지는 is_checked 항목이 있을 때 checked를 사용하여 선택해 두었다. 해당 코드를 생각하는 과정에서 어려움이 팀장님께 여쭤봤었는데, view에서 zip 함수를 활용하여 quiz와 is_checked를 묶어 전달해 준 것이 유효했다. 제출버튼을 누르면 form을 제출한다. Forms.py를 활용했다면 조금 더 간편했을테다.
2) 백엔드
def get_solve_page(request, quiz_set_id):
latest_quizset = QuizSet.objects.latest("id")
if quiz_set_id > latest_quizset.id:
return redirect('/error')
quiz_set = QuizSet.objects.get(id=quiz_set_id)
quizes = Quiz.objects.filter(quiz_set_id = quiz_set_id)
previous_checked = [0 for x in range(7)]
if request.method == 'POST':
if len(request.POST) < 9 or request.POST['guestname'] == None:
messages.error(request,'이름과 모든 문제의 답을 입력해주세요.')
for i in range(1,8):
if str(i) in dict(request.POST).keys():
previous_checked[i-1] = int(request.POST[str(i)])
return render(request, 'quiz/solvePage.html', {
'quiz_set_id':quiz_set_id,
'quizes':zip(quizes,previous_checked),
'guestname': request.POST['guestname'],
'host': quiz_set.host
})
quiz_set = get_object_or_404(QuizSet, pk=quiz_set_id)
point = 0
cnt = 1
for quiz in quizes:
if quiz.answer == int(request.POST[str(cnt)]):
point += 1
cnt += 1
new_anwer = Answer()
new_anwer.quiz_set_id = quiz_set
new_anwer.guest = request.POST['guestname']
new_anwer.points = point
new_anwer.save()
return redirect(f'/result/{quiz_set_id}/{new_anwer.id}')
return render(request, 'quiz/solvePage.html', {
'quiz_set_id':quiz_set_id,
'quizes':zip(quizes,previous_checked),
'host': quiz_set.host
})
백엔드 코드 역시 Forms.py를 사용하지 않아 상당히 더럽다. 때문에 되게 다양한 파이썬 문법을 활용하게 됐다.
요청이 POST 즉 폼을 제출하는 때는 먼저 유효성 검사를 실시한다. 유효성이 충족되지 않을 시, previouschecked 배열을 갱신해 이를 quiz 객체와 같이 전달했다. 충족될 시 채점을 실시한다. 채점 점수로 답안 객체를 생성해 저장해주고, result페이지로 redirect해준다. POST 방식이 아닐 시 단순히 객체를 넘겨주며 페이지를 렌더한다.
장고와 HTML의 경우 꽤 익숙한 기술들이라 기본적인 기능은 하던대로 하면 됐었다. 다만 유효성 검사나 에러 메시지 노출, 이전 답변 유지 등의 테크닉은 다뤄본 적이 없어 팀장님과 구글링을 통해 배운 점이 많았다. 친분이 없는 팀원들과 규모있는 프로젝트를 해본 경험은 처음이라 뜻깊었다. 특히 웹 관련회사에서 일하다 오신 팀장님한테서, 기술적으로든 개발 프로세스 적으로든 많은 걸 어깨 너머 배울 수 있었다. 결과물 역시 처음 기획만큼 완벽히 되어서 만족스럽다. 서비스는 배포까지 되었으니 궁금하신 분들은 해보시면 좋을 거 같다.
깃허브
https://github.com/MaxKim-J/friendship-quiz
GitHub - MaxKim-J/friendship-quiz: 소프트웨어공학 11조 과제 레포지토리
소프트웨어공학 11조 과제 레포지토리. Contribute to MaxKim-J/friendship-quiz development by creating an account on GitHub.
github.com
배포된 서비스
https://friendship-quiz-live.herokuapp.com/
★우정퀴즈★
우정퀴즈를 출제하고, 풀어보세요!
friendship-quiz-live.herokuapp.com