점프 투 장고 추가 기능(카테고리)
카테고리 기능 추가
이번에는 카테고리를 추가하여 현재 사용하고 있는 질문게시판 외에 자유게시판을 만들어보도록 하겠습니다.
먼저 카테고리 모델을 생성합니다. 이름과 설명 항목을 넣었습니다. 그리고 Question 모델에서 카테고리를 참조하도록 합니다.
models.py
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class Question(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='author_question')
subject = models.CharField(max_length=200)
content = models.TextField()
create_date = models.DateTimeField()
modify_date = models.DateTimeField(null=True, blank=True)
voter = models.ManyToManyField(User, related_name='voter_question')
category = models.ForeignKey(Category, on_delete=models.CASCADE) # 카테고리 추가
def __str__(self):
return self.subject
모델을 생성했으니 makemigrations을 실행해 줘야 하는데 기존에 있던 질문 글들은 카테고리가 없는 상태라 오류가 발생합니다. 따라서 우선 Question 모델에서 category를 다음과 같이 null 값이 올 수 있도록 설정했습니다.
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='questions', null=True)
그리고 python manage.py shell을 실행하여 질문게시판, 자유게시판 카테고리를 생성하고 기존 글들의 카테고리를 설정해준다음 migration을 진행했습니다.
다음 코드는 shell을 실행하여 기존의 글들을 질문게시판으로 옮기는 코드입니다.
python manage.py shell
from pybo.models import Category, Question
# '질문 게시판' 카테고리 객체 가져오기
question_category = Category.objects.get(name='질문게시판')
# 모든 질문의 category를 '질문 게시판'으로 업데이트
Question.objects.update(category=question_category)
글을 생성하거나 수정할 때 카테고리를 설정할 수 있도록 메타 클래스를 수정합니다.
forms.py
class QuestionForm(forms.ModelForm):
class Meta:
model = Question # 사용할 모델
fields = ['subject', 'content', 'category'] # QuestionForm에서 사용할 Question 모델의 속성
labels = {
'subject': '제목',
'content': '내용',
}
category = forms.ModelChoiceField(queryset=Category.objects.all(), empty_label='카테고리 선택', widget=forms.Select())
question_form.html에서 카테고리를 선택할 수 있도록 해줍니다. 저는 오류표시 밑에 코드를 추가했습니다.
<div class="mb-3">
<label for="category" class="form-label">카테고리</label>
<select class="form-select" id="category" name="category">
<option value="" disabled selected>카테고리 선택</option>
{% for category in form.category.field.queryset %}
<option value="{{ category.id }}" {% if category.id == form.category.value %}selected{% endif %}>
{{ category.name }}
</option>
{% endfor %}
</select>
</div>
다음 사진은 카테고리 선택이 적용된 질문 폼 화면입니다.
이제 카테고리에 맞게 게시글들을 페이징하고 context에 담아 넘겨주면 됩니다. question_detail 화면에서 카테고리를 선택하면 해당 카테고리의 값을 url에 추가하고 이를 통해 무슨 카테고리인지 판별하면 됩니다.
아무 값도 없다면 카테고리를 구분하지 않고 모든 글들을 보여주도록 코드를 작성했습니다. filter 기능을 사용해 카테고리를 쉽게 분류할 수 있습니다. 그리고 다른 카테고리가 추가되더라도 수정할 일이 없도록 코드를 작성했습니다.
base_views.py
def index(request):
page = request.GET.get('page', '1') # 페이지
kw = request.GET.get('kw', '') # 검색어
category_name = request.GET.get('category', '')
if category_name != '':
question_list = Question.objects.filter(category__name=category_name).order_by('-create_date')
else:
question_list = Question.objects.order_by('-create_date')
if kw:
question_list = question_list.filter(
Q(subject__icontains=kw) | # 제목 검색
Q(content__icontains=kw) | # 내용 검색
Q(answer__content__icontains=kw) | # 답변 내용 검색
Q(author__username__icontains=kw) | # 질문 글쓴이 검색
Q(answer__author__username__icontains=kw) # 답변 글쓴이 검색
).distinct()
paginator = Paginator(question_list, 10) # 페이지당 10개씩 보여주기
page_obj = paginator.get_page(page)
categories = Category.objects.all()
context = {'question_list': page_obj, 'page': page, 'kw': kw, 'categories': categories, 'category_name': category_name,}
return render(request, 'pybo/question_list.html', context)
마지막으로 question_list.html을 수정해줍니다. 카테고리를 선택하는 코드를 추가했습니다.
<div class="row my-3">
<div class="col-2">
<a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
<div class="col-4">
<div class="input-group">
<select class="form-control" id="category_select" name="category">
<option value="" {% if category_name == "" %}selected{% endif %}>전체</option>
{% for category in categories %}
<option value="{{ category.name }}" {% if category.name == category_name %}selected{% endif %}>
{{ category.name }}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-2">
</div>
<div class="col-4">
<div class="input-group">
<input type="text" id="search_kw" class="form-control" value="{{ kw|default_if_none:'' }}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="btn_search">찾기</button>
</div>
</div>
</div>
</div>
<form id="searchForm" method="get" action="{% url 'index' %}">
<input type="hidden" id="kw" name="kw" value="{{ kw|default_if_none:''}}">
<input type="hidden" id="page" name="page" value="{{ page }}">
<input type="hidden" id="category" name="category" value="{{ category_name }}">
</form>
<script type='text/javascript'>
const category_select = document.getElementById("category_select");
category_select.addEventListener('change', function() {
document.getElementById('category').value = this.value;
document.getElementById('page').value = 1;
document.getElementById('searchForm').submit();
});
</script>
구현된 화면입니다.