같이 프로젝트를 진행한 팀원: https://jihoon44-it.tistory.com/
https://k-python-note-taking.tistory.com/63
이거에 이어서 진행해보도록 하겠습니다.
1/2에서는 장고에 기본적인 기능과 index 페이지를 설정했습니다.
여기서 이 보기버튼을 눌러서 나오는 화면인
이 화면을 구현해보겠습니다.
먼저 html의 기능 빼고 틀 부터 잡아주었습니다.
{% extends "cafeinformation/base.html" %}
{% block content %}
<div class="container" style="width: 500px;">
<table class="table table-striped">
<tr>
<th colspan="2">구선택</th>
</tr>
<tr>
<form method="post">
<th>
{{ form.file }}
</th>
<th>
{% csrf_token %}
<button type="submit" class="btn btn-primary">확인</button>
</form>
</th>
</tr>
<tr>
<th colspan="2">
<form action="{% url 'cafe:seoul' %}">
<button type="submit" class="btn btn-primary">서울시 전체</button>
</form>
</th>
</tr>
<tr>
<th colspan="2">
<form action="{% url 'cafe:index' %}">
<button type="submit" class="btn btn-danger">뒤로가기</button>
</form>
</th>
</tr>
</table>
</div>
{% endblock %}
버튼에 있는 class들은 부트스트랩에서 조금 깔끔하게 하기위해 내가 원하는 스타일을 가져왔습니다.
중간에 있는 {% csrf_token %} 은 값을 POST방식으로 보낼때
CSRF(Cross-Site Request Forgery)라는 사이트간의 위조 방지를 목적으로 특정한 값의 토큰을 사용하는 방식입니다.
이것을 입력해줘야 POST방식으로 값을 보낼때 csrf_token도 같이 보내어 정상적으로 작동됩니다.
만약 csrf_token을 작성해주지 않았다면
이렇게 csrf검증에 실패했다는 화면이 나올겁니다.
처음에 엄청 당황했지만 구글링을 열심히 해주니 csrf에 대해 알게되었고 이 값을 같이 넘겨주어야 정상적으로 화면이 나오게 된다는것을 배웠습니다.
이어서 가겠습니다.
그리고 이걸 이어주는 views와 urls를 만들어주겠습니다.
먼저 views입니다.
def detail(request):
form = FileSelectForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
selected_file_id = form.cleaned_data['file']
detail_data = CsvFile.objects.get(id=selected_file_id)
if len(detail_data.file_name) == 15:
detail_data_name = detail_data.file_name[9:11]
elif len(detail_data.file_name) == 16:
detail_data_name = detail_data.file_name[9:12]
elif len(detail_data.file_name) == 17:
detail_data_name = detail_data.file_name[9:13]
return render(request, 'cafeinformation/detail_download.html', {'detail_data': detail_data,'detail_data_name':detail_data_name})
files = CsvFile.objects.all()
return render(request, 'cafeinformation/detail.html', {'form': form, 'files': files})
값을 보내거나 받거나 할때 POST나 GET방식으로 나누어지는데,
버튼을 누를때 메서드를 어떻게 설정하느냐에 따라 POST나 GET으로 사용자가 직접 설정해줄 수도 있습니다.
index에서 보기를 눌렀을때에는 GET방식으로 위에 POST가 아닌 밑에 있는
return render(request, 'cafeinformation/detail.html', {'form': form, 'files': files})
이부분만 실행하게 하였습니다.
코드의 처음부터 차근차근 정리해보도록 하겠습니다.
form.is_valid() 는 장고에서 이 html이 유효한지 확인하는 메서드입니다.
여기서는 위에서 FileSelectForm이라는 forms을 받아와서 그게 None이 아니라면 실행하도록 만들어 주었습니다.
form이란 장고에 있는 기능으로 html을 생성하거나 데이터베이스에 있는 모델을 이용할때 보다 더 간단하게 사용할 수 있게 해주는 기능입니다.
이 form을 사용하려면 프로젝트앱에 forms.py라는 파이썬 파일을 생성해주어 거기에 내가 원하는 모델을 어떻게 사용할건지 설정해주면 됩니다.
from django import forms
from .models import CsvFile
class CsvFileForm(forms.ModelForm):
class Meta:
model = CsvFile
fields = ['file_name','file']
class FileSelectForm(forms.Form):
file_choices = [
(file.id, file.file_name[9:12]) if len(file.file_name) == 16
else (file.id, file.file_name[9:11]) if len(file.file_name) == 15
else (file.id, file.file_name[9:13]) if len(file.file_name) == 17
else None
for file in CsvFile.objects.all()
]
# None을 제외한 값만 선택지에 포함
file_choices = [choice for choice in file_choices if choice is not None]
file = forms.ChoiceField(choices=[('', '선택하세요')] + file_choices, widget=forms.Select(attrs={'class': 'form-select'}))
저는 이렇게 CSV파일들의 이름을 보여줄 CsvFileForm과 구를 선택할때 사용할 FileSelectForm 을 생성해주었습니다.
슬라이싱으로 나누어준것은 그 구의 이름만 가져오기 위해서이고 if문으로 나누어준것은
이렇게 길이가 다른게 몇개 있기 때문입니다.
이어서 가도록 하겠습니다.
처음 index에서 detail을 클릭하면 GET방식으로 들어와 POST방식이 아닌
return render(request, 'cafeinformation/detail.html', {'form': form})
이부분만 실행하게 하였습니다.
그냥 html파일을 보여주고 form에 있는 선택하는 기능을 가진것만 구현했습니다.
여기서
이렇게 구를 선택하고 확인버튼을 누르면
POST방식으로 값을 보내어 또다른 detail의 화면을 보여주게 됩니다.
그 화면에 대해 정리하기 전에 코드먼저 정리하도록 하겠습니다.
POST방식으로 보내면 if문이 돌아가게 되면서 다음 코드들이 실행됩니다.
selected_file_id = form.cleaned_data['file']
여기서 cleaned_data를 사용해 유효성검사를 한 뒤 내가 지정해준 form에 있는 file이라는 값을 가져와줍니다.
detail_data = CsvFile.objects.get(id=selected_file_id)
CsvFile모델에서 파일을 저장할때 기본 id값이 1부터 자동적으로 1씩증가하면서 파일 하나마다 번호를 생성해줍니다.
내가 확인버튼을 눌렀을때 그에 해당하는 번호와 맞는 모든 값들을 가져와줍니다.
if len(detail_data.file_name) == 15:
detail_data_name = detail_data.file_name[9:11]
elif len(detail_data.file_name) == 16:
detail_data_name = detail_data.file_name[9:12]
elif len(detail_data.file_name) == 17:
detail_data_name = detail_data.file_name[9:13]
return render(request, 'cafeinformation/detail_download.html', {'detail_data': detail_data,'detail_data_name':detail_data_name})
이건 아까 설명드린 각 구의 이름만 갖고오기 위함입니다.
그 값들을 가져와
이런 화면이 나오도록 연결해주었습니다.
이어서 화면을 설명하기전에
이걸 이어주는 urls를 생성해야합니다.
urlpatterns = [
path('',views.index,name='index'),
path('detail/',views.detail,name='detail'),
]
detail이라는 함수로 만들었으므로 이렇게 만들어 주었습니다.
버튼을 눌렀을때의 화면은 이렇게 구현하였습니다.
{% extends "cafeinformation/base.html" %}
{% block content %}
<div class="container" style="width: 600px;" >
<table class="table table-striped">
<tr>
<th>자치구</th>
<th>{{ detail_data_name }}</th>
</tr>
<tr>
<td colspan="2">
<a href="{{ detail_data.file.url }}" download>
다운로드
</a>
</td>
</tr>
<tr>
<form action="{% url 'cafe:preview' detail_data.id %}">
<th colspan="2">
<button type="submit" class="btn btn-primary">다운로드 파일 미리보기
</button>
</th>
</form>
</tr>
<tr>
<th colspan="2">
<form action="{% url 'cafe:detail' %}">
<button type="submit" class="btn btn-danger">
뒤로가기
</button>
</form>
</th>
</tr>
</table>
<div>
{% endblock %}
잘못 골랐을때 뒤로 갈 수 있으므로 바로 전 화면을 보여주는 뒤로가기 화면을 보여주는 버튼을 하나 만들어주겠습니다.
그리고 그 파일을 다운받는것도 download라는 html에 기능을 활용해서 만들어주었습니다.
다음 그 다운로드 파일을 다운받기 전에 미리 볼 수 있는 폼을 하나 만들어주었습니다.
화면은 이렇게 오른쪽 하단에 맨 위로 가는 버튼이 계속 따라오게 만들었고,
모든 데이터들을 보여주게 하였습니다.
이 화면에 대한 코드입니다.
{% extends "cafeinformation/base.html" %}
{% block head %}
<style>
#preview{
text-align: left;
}
th, td {
text-align: left;
}
</style>
{% endblock %}
{% block content %}
<div class="container" style="width: 1200px;" >
<table class="table table-striped" id="preview">
<tr>
<th id="top">자치구</th>
<th style="text-align: center;">{{ detail_data_name }}</th>
<th style="text-align: center;">
<form action="{% url 'cafe:detail' %}">
<button type="submit" class="btn btn-danger" style="height: 40px; width:100px">
<span style="font-size: 15px;">
뒤로가기
</span>
</button>
</form>
</th>
</tr>
<tr>
<td colspan="3" style="text-align: center;">
<a href="{{ detail_data.file.url }}" download>
다운로드
</a>
</td>
</tr>
{% for data in previews %}
<tr>
<th colspan="3" style="text-align: center;">
-----------------------------{{ forloop.counter }}번째-----------------------------
</th>
</tr>
<tr>
<th style="width: 100px;">
카페이름
</th>
<th colspan="2">
</th>
</tr>
<tr>
<th>
카페종류
</th>
<th colspan="2">
{{ data.cafe_class }}
</th>
</tr>
<tr>
<th>
리뷰개수
</th>
<th colspan="2">
{{ data.review_count}}
</th>
</tr>
<tr>
<th>
카페주소
</th>
<th colspan="2">
{{ data.address }}
</th>
</tr>
<tr>
<th style="text-align: center;">
리뷰TOP3
</th>
<th colspan="2">
{{ data.review_top3}}
</th>
</tr>
<tr>
<th>
카페사진
</th>
<th colspan="2" style="text-align: center;">
<img src="{{ data.background_image_url }}" alt="{{ data.name }}" style="width: 300px;">
</th>
</tr>
<tr>
<th>
카페메뉴
</th>
<th colspan="2">
{{ data.cafe_menu }}
</th>
</tr>
{% endfor %}
</table>
<div>
<p>
<a href="{% url 'cafe:preview' detail_data.id %}#top" class="btn btn-primary" id="btn">
맨 위로
</a>
</p>
{% endblock %}
for문을 돌면서 데이터베이스에 있는 모든 내용들을 출력하게 만들었습니다.
이 화면을 보여주기 위한 views는
def preview(request,id):
detail_data = CsvFile.objects.get(id=id)
detail_data_name = detail_data.file_name
if id == 1:
detail_data_name = detail_data_name[9:12]
previews = SeoulCafeList.objects.filter(gu='강남구')
preview_menu = [preview.cafe_menu.replace('[','').replace(']','').replace('\'','').replace(',',' / ') for preview in previews]
# previews의 각 객체의 cafe_menu 필드를 가공된 값으로 변경
for preview, formatted_menu in zip(previews, preview_menu):
preview.cafe_menu = formatted_menu
# 변경된 값을 저장
for preview in previews:
preview.save()
preview_review_top3 = [preview.review_top3.replace('[','').replace(']','').replace('\'','').replace(',',' / ') for preview in previews]
# previews의 각 객체의 review_top3 필드를 가공된 값으로 변경
for preview, formatted_menu in zip(previews, preview_review_top3):
preview.review_top3 = formatted_menu
# 변경된 값을 저장
for preview in previews:
preview.save()
print(preview_menu)
return render(request,'cafeinformation/preview.html',{'previews':previews,'detail_data_name':detail_data_name,'detail_data':detail_data,'preview_menu':preview_menu})
이렇게 생성해주었습니다.
제가 봐도 비 효율적으로 비슷한 내용이 26개나 들어가는데,
아무리 생각해도 방법이 떠오르지 않아 어쩔 수 없이 이렇게 구현했습니다.
각 id값에 따른 데이터베이스들을 불러왔습니다.
preview_review_top3 = [preview.review_top3.replace('[','').replace(']','').replace('\'','').replace(',',' / ') for preview in previews]
이렇게 그 데이터베이스 preview에서 reivw_top3라는 부분이 리스트 형식으로 되어있었는데 이것을 값마다 / 를 넣어주어서 보기 편하게 해주었습니다.
메뉴 부분도 마찬가지로 같이 바꿔주었습니다.
데이터베이스에 있는 값을 바꾸어줄땐 .save()를 꼭 해주어야합니다.
추가로 서울시 전체의 파일을 보고 다운로드 받는 코드는
def seoul(request):
seoul_data = CsvFile.objects.get(id=15)
return render(request,'cafeinformation/seoul.html',{'seoul_data':seoul_data})
이렇게 id가 15인값이 서울시전체의 csv파일이라 값을 불러들여와서
{% extends "cafeinformation/base.html" %}
{% block content %}
<div class="container" style="width: 600px;" >
<table class="table table-striped">
<tr>
<th colspan="2">서울전체</th>
</tr>
<tr>
<td colspan="2">
<a href="{{ seoul_data.file.url }}" download>
다운로드
</a>
</td>
</tr>
<tr>
<form action="{% url 'cafe:preview' seoul_data.id %}">
<th colspan="2">
<button type="submit" class="btn btn-primary">다운로드 파일 미리보기
</button>
</th>
</form>
</tr>
<tr>
<th colspan="2">
<form action="{% url 'cafe:detail' %}">
<button type="submit" class="btn btn-danger">
뒤로가기
</button>
</form>
</th>
</tr>
</table>
<div>
{% endblock %}
seoul.html이라는 html파일을 생성해주어 위와 마찬가지로 간단하게 작성해보았습니다.
urls에도 추가해주어야 합니다.
urlpatterns = [
path('',views.index,name='index'),
path('detail/',views.detail,name='detail'),
path('preview/<int:id>/',views.preview,name='preview'),
path('seoul/',views.seoul,name='seoul'),
]
여기까지 부족한 제 프로젝트를 봐주셔서 감사합니다.