장고/점프투장고

점프 투 장고. 실습한 소스

나도초딩 2023. 11. 18.

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 %}

댓글