forms.py
모델폼 상속해서 폼 만든 후,
뷰에서 임포트. QuestionForm(request.POST, instance=question)
messages.error(request, '에러')
forms.py
from django import forms
from b2b.models import Question, Answer
# forms.Form 과 forms.ModelForm
class QuestionForm(forms.ModelForm):
class Meta:
model = Question
fields = ['subject', 'content']
labels = {
'subject': '제목',
'content': '내용',
}
class AnswerForm(forms.ModelForm):
class Meta:
model = Answer
fields = ['subject', 'content']
labels = {
'subject': '답변제목/예약자, 출발일별 상품',
'content': '내용',
}
Question:b2b
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseNotAllowed
from django.shortcuts import render, get_object_or_404, redirect, resolve_url
from django.utils import timezone
from django.core.paginator import Paginator
# from django.http import HttpResponse
from .models import Question, Answer
from .forms import QuestionForm, AnswerForm
# FIXME: 1. forms.ModelForm을 상속해서, QuestionForm을 만든다.
# 2. view 에서 form 렌더링 컨텍스트를 위해, 임포트 사용.
# 1. 글저장: form = QuestionForm()
# 2. 글쓰기 : form = QuestionForm(request.POST)
# 3. 수정: form = QuestionForm(instance=question)
# 4. 수정글 저장: form = QuestionForm(request.POST, instance=question)
def index(request):
return HttpResponse("안녕하세요 ")
page = request.GET.get('page', '1')
question_list = Question.objects.order_by('-create_date')
paginator = Paginator(question_list, 10)
page_obj = paginator.get_page(page)
# 전체 리스트 --> 페이징
# context = {'question_list': question_list}
context = {'question_list': page_obj}
return render(request, 'b2b/question_list.html', context)
def detail(request, pk):
# q = Question.objects.get(id=pk)
q = get_object_or_404(Question, id=pk)
context = {'question': q}
return render(request, 'b2b/detail.html', context)
@login_required(login_url='common:login')
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
# q.answer_set.create(subject=request.POST.get('subject'), content=request.POST.get('content'), create_date=timezone.now())
if request.method == 'POST':
form = AnswerForm(request.POST)
if form.is_valid():
answer = form.save(commit=False)
answer.author = request.user # request.user = User or AnonymousUser
answer.create_date = timezone.now()
answer.question = question
answer.save()
# return redirect('b2b:detail', pk=question.id)
return redirect(f"{resolve_url('b2b:detail', pk=answer.question.id)}#answer_{answer.id}")
# urls.py 의 detail 에서, /<int:pk>/ 이므로, pk=q.id 또는 매개변수로 q.id 만 써야한다.
else:
# return HttpResponseNotAllowed('답변은 GET 방식 불가.')
form = AnswerForm() # 조회 후 폼 노출(GET), 포스트로 저장 후, NEXT파라미터로 GET호출. 빈 폼 출력.
# return redirect('b2b:detail', q.id)
# 'POST" 지만, form.is_valid()하지 않으면, 진행.
context = {'question': question, 'form': form}
return render(request, 'b2b/detail.html', context)
@login_required(login_url='common:login')
def question_create(request):
if request.method == 'POST':
form = QuestionForm(request.POST)
if form.is_valid():
question = form.save(commit=False)
question.author = request.user
question.create_date = timezone.now()
question.save()
return redirect('b2b:index')
else:
form = QuestionForm()
context = {'form': form}
return render(request, 'b2b/question_form.html', context)
@login_required(login_url='common:login')
# 작성자 동일 여부 -> messages.error()
# GET: instance=question, POST: request.POST, instance=question
def question_modify(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if request.user != question.author:
messages.error(request, '수정권한이 없습니다.')
return redirect('b2b:detail', pk=question.id)
if request.method == "POST": # instance 채운 폼에, reqeust.POST 덮어쓴 후, 유효성검사->저장
form = QuestionForm(request.POST, instance=question)
if form.is_valid():
form.save(commit=False)
question.modify_date = timezone.now()
question.save()
return redirect('b2b:detail', pk=question.id)
else:
form = QuestionForm(instance=question) # instance 입력된 폼을 보여준다.
context = {'form': form}
return render(request, 'b2b/question_form.html', context)
def question_delete(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if request.user != question.author:
messages.error(request, '삭제권한이 없습니다')
return redirect('b2b:detail', pk=question.id)
question.delete()
return redirect('b2b:index')
@login_required(login_url='common:login')
def answer_modify(request, answer_id):
answer = get_object_or_404(Answer, pk=answer_id)
if request.user != answer.author: # == , != 유의
messages.error(request, '권한없음')
return redirect('b2b:detail', pk=answer.question.id) #fk answer.question.id 이용
if request.method == 'POST': # 답변 저장에 대응
form = AnswerForm(request.POST, instance=answer)
if form.is_valid():
answer = form.save(commit=False) #form 임시저장 후, answer 객체에 담은 후, modify_date 추가 후, 저장한다. -> 저장 후, detail로+question_id
answer.modify_date = timezone.now()
answer.save()
return redirect('b2b:detail', pk = answer.question.id)
else:
form = AnswerForm(instance=answer)
context = {'form': form}
return render(request, 'b2b/answer_form.html', context)
def answer_delete(request):
pass
@login_required(login_url='common:login')
def question_vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
if request.user == question.author:
messages.error(request, '본인이 작성한 글은 추천할수 없습니다')
else:
question.voter.add(request.user)
return redirect('b2b:detail', pk=question.id)
def answer_vote(request, answer_id):
answer = get_object_or_404(Answer, pk=answer_id)
if request.user == answer.author:
messages.error(request, '자기 답변에는 추천할 수 없습니다.')
else:
answer.voter.add(request.user)
return redirect('b2b:detail', pk=answer.question.id)
# return redirect(f"{resolve_url('b2b:detail', pk=answer.question.id)}#answer_{answer.id}")
templates/b2b/question.list.html
{% extends 'base.html' %}
{% load pybo_filter %}
{% block content %}
<div class="container my-3">
<table class="table">
<thead>
<tr class="text-center table-dark">
<th>번호</th>
<th style="width:50%">제목</th>
<th>글쓴이</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
{% if question_list %}
{% for question in question_list %}
<tr class="text-center">
<td>
<!-- 번호 = 전체건수 - 시작인덱스 - 현재인덱스 + 1 -->
{{ question_list.paginator.count|sub:question_list.start_index|sub:forloop.counter0|add:1 }}
</td>
<td class="text-start">
<a href="{% url 'b2b:detail' question.id %}">{{ question.subject }}</a>
{% if question.answer_set.count > 0 %}
<span class="text-danger small mx-2">{{ question.answer_set.count }}</span>
{% endif %}
</td>
<td>{{question.author.username}}</td> <!-- 글쓴이 추가 -->
<td>{{ question.create_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4">질문이 없습니다.</td>
</tr>
{% endif %}
</tbody>
</table>
<!-- 페이징처리 시작 -->
<ul class="pagination justify-content-center">
<!-- 이전페이지 -->
{% if question_list.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ question_list.previous_page_number }}">이전</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
</li>
{% endif %}
<!-- 페이지리스트 -->
{% for page_number in question_list.paginator.page_range %}
{% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %}
{% if page_number == question_list.number %}
<li class="page-item active" aria-current="page">
<a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
</li>
{% endif %}
{% endif %}
{% endfor %}
<!-- 다음페이지 -->
{% if question_list.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ question_list.next_page_number }}">다음</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
</li>
{% endif %}
</ul>
<!-- 페이징처리 끝 -->
<a href="{% url 'b2b:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
{% endblock %}
templates/b2b/detail.html
{% extends 'base.html' %}
{% block content %}
<div class="container my-3">
<!-- messages.error -->
{% if messages %}
<div class="alert alert-danger" role="alert">
{% for m in messages %}
<strong>{{ m.tags }}</strong>
<ul>
<li>{{ m.message }}</li>
</ul>
{% endfor %}
</div>
{% endif %}
<h2 class="border-bottom py-2">{{ question.subject }}</h2>
<div class="card my-3">
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ question.content }}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-start">
<div>
badge text-start: {{ question.author.username }}
</div>
<div>
{{ question.create_date }}
</div>
</div>
</div>
<!-- FIXME:question_vote -->
<div class="my-3">
<a href="javascript:void(0)" data-uri="{% url 'b2b:question_vote' question.id %}" class="recommend btn btn-sm btn-outline-secondary">질문 추천 <span class="badge rounded-pill bg-success">{{question.voter.count}}</span></a>
{% if request.user == question.author %}
<a href="{% url 'b2b:question_modify' question.id %}" class="btn btn-sm btn-outline-secondary">수정</a>
{% comment %} <a href="{% url 'b2b:question_delete' question.id %}" class="btn btn-sm btn-outline-secondary">Delete</a> {% endcomment %}
<a href="javascript:void(0)" data-uri="{% url 'b2b:question_delete' question.id %}" class="delete btn btn-sm btn-outline-secondary">Delete</a>
{% endif %}
</div>
</div>
</div>
<h5 class="border-bottom my-3 py-2">{{question.answer_set.count}}개의 답변이 있습니다.</h5>
<!-- TODO: url 앵커태그 -->
{% for answer in question.answer_set.all %}
<a id="answer_{{ answer.id }}">{{answer.id}}</a>
<div class="card my-3">
<h4>댓글 제목 : {{answer.subject}}</h4>
<div class="card-body">
<div class="card-text" style="white-space: pre-line;">{{ answer.content }}</div>
<div class="d-flex justify-content-end">
<div class="badge bg-light text-dark p-2 text-end">
<div clss="mb-2">badge text-end {{ answer.author.username }} </div>
<div>
{{ answer.create_date }}
</div>
</div>
</div>
<div class="my-3">
<a href="javascript:void(0)" data-uri="{% url 'b2b:answer_vote' answer.id %}" class="recommend btn btn-sm btn-outline-secondary">답변 추천 <span class="badge rounded-pill bg-success">{{answer.voter.count}}</span></a>
{% if request.user == answer.author %}
<a href="{% url 'b2b:answer_modify' answer.id %}" class="btn btn-sm btn-outline-secondary">댓글 수정</a>
{% comment %} <a href="{% url 'b2b:answer_delete' question.id %}" class="btn btn-sm btn-outline-secondary">Delete</a> {% endcomment %}
<a href="javascript:void(0)" data-uri="{% url 'b2b:answer_delete' answer.id %}" class="delete btn btn-sm btn-outline-secondary">댓글 삭제</a>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<form action="{% url 'b2b:answer_create' question.id %}" method="post" class="my-3">
{% csrf_token %}
<!-- 오류표시 Start -->
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% for field in form %}
{% if field.errors %}
<div>
<strong>{{ field.label }}</strong>
{{ field.errors }}
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
<!-- form.errors 가 있으면, submit 이 실행되지 않는다.-->
<!-- 오류표시 End -->
<div class="mb-3">
<label for="subject" class="form-label">댓글</label>
<input name="subject" id="subject" class="form-control">
<label for="content" class="form-label">답변내용</label>
<textarea {% if not user.is_authenticated %}disabled{% endif %} name="content" id="content" class="form-control" rows="10">{% if not user.is_authencated %}not user.is_authenticated{% endif %}</textarea>
</div>
<input type="submit" value="답변등록" class="btn btn-primary">
</form>
</div>
{% endblock %}
{% block script %}
<script type='text/javascript'>
const delete_elements = document.getElementsByClassName("delete");
Array.from(delete_elements).forEach(function(element) {
element.addEventListener('click', function() {
if(confirm("정말로 삭제하시겠습니까?")) {
location.href = this.dataset.uri;
};
});
});
const recommend_elements = document.getElementsByClassName("recommend");
Array.from(recommend_elements).forEach(function(element) {
element.addEventListener('click', function() {
if(confirm("딩짜 추천?")) {
location.href = this.dataset.uri;
};
});
});
</script>
{% endblock %}
'장고 > 점프투장고' 카테고리의 다른 글
회원가입 폼 (부트스트랩) (1) | 2023.11.16 |
---|---|
회원 가입view - 글쓴이 추가(모델에 fk 추가) (0) | 2023.11.16 |
댓글 수: {% if question.answer_set.count > 0 %} (0) | 2023.11.15 |
로그인 로그아웃 구현 + 리다이렉트 URL ( config/urls.py ) (0) | 2023.11.15 |
urls.py <int:pk> <------- views.py : redirect(___, pk=question.id) (0) | 2023.11.14 |
폼 에러 표시. class , form.errors/field.label/field.errors (0) | 2023.11.13 |
forms.py 에서 폼디자인( form.as_p 폼위젯, 수동폼 사용) (0) | 2023.11.12 |
테이블 모양 / 부트스트랩 (0) | 2023.11.12 |
댓글