가상환경 활성화 후
django-admin startproject config .
django-admin startapp [프로젝트명]
python manage.py runserver
# db.sqlite3 파일, 즉 db 만들어진 거 확인 가능
python manage.py migrate
# 만들어진 db migrate
: Django 프로젝트와 상호 작용하도록 특별히 구성된 대화형 Python 환경
: 프로젝트의 모델, 설정 및 기타 Django 구성 요소에 직접 접근 가능하여
: 개발, 디버깅 및 데이터 조작을 위한 강력한 도구
python manage.py shell
>>> from pybo.models import Question, Answer
>>> from django.utils import timezone
# 객체화
>>> q = Question(subject='pybo가 무엇인가요?', content='pybo에 대해서 알고 싶
습니다.', create_date=timezone.now())
# 저장
>>> q.save()
데이터 저장이 잘 됐는지 결과를 db browser 에서 확인
확인했다면 꼭 꺼줄 것(shell 과 같이 실행되면 충돌 발생 가능)

# shell 에서도 저장된 데이터 확인 가능
>>> q.id
1
>>> q.subject
'pybo가 무엇인가요?'
>>> q.create_date
datetime.datetime(2025, 10, 1, 0, 36, 39, 694400, tzinfo=datetime.timezone.utc)
# Questions 테이블에 저장된 모든 데이터 조회
# 한 가지 자료형이 아니므로 object 자료형으로 나옴
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>, <Question: Question object (2)>]>
# 리스트로 묶을 수 있으므로 for문 활용 가능
>>> list(Question.objects.all())
[<Question: Question object (1)>, <Question: Question object (2)>]
데이터 유형이 출력되므로 사람이 보기 불편함
Question 모델에 str 메서드를 추가해 subject 필드가 뜨도록 함
class Question(models.Model):
...
def __str__(self):
return self.subject
모델이 수정되었으므로 장고 셸을 다시 시작해 변경사항이 반영된 것을 확인한다
>>> quit()
python manage.py shell
# 다시 필요한 모델 임포트
>>> from pybo.models import Question, Answer
>>> from django.utils import timezone
# 다시 Question 모델의 모든 데이터 확인
# id 가 아닌 제목이 표시되는 것을 확인 가능
>>> Question.objects.all()
<QuerySet [<Question: pybo가 무엇인가요?>, <Question: 장고 모델 질문입니다.>]>
모델이 수정되었는데 왜
makemigrations, migrate명령을 실행하지 않는가?
→makemigrations, migrate명령은 모델의 속성이 추가되거나 변경된 경우에 실행해야 하는 명령이다.
→ 지금은 메서드가 추가된 것이므로 이 과정은 하지 않아도 된다.
조건으로 데이터 조회
>>> Question.objects.filter(id=1)
<QuerySet [<Question: pybo가 무엇인가요?>]>
>>> Question.objects.filter(id__in=[1,2])
<QuerySet [<Question: pybo가 무엇인가요?>, <Question: 장고 모델 질문입니다.>]>
# 1보다 크고 10보다 작은
>>> Question.objects.filter(id__gte=1, id__lte=10)
<QuerySet [<Question: pybo가 무엇인가요?>, <Question: 장고 모델 질문입니다.>]>
# 제목이 장고를 포함하는
>>> Question.objects.filter(subject__contains='장고')
<QuerySet [<Question: 장고 모델 질문입니다.>]>
>>> Question.objects.get(id=1)
<Question: pybo가 무엇인가요?>
filter : 조건에 해당하는 데이터를 모두 찾아줌(여러 값)get : 조건에 해당하는 데이터 하나를 찾아줌(한 개의 값)filter() 에서의 __ 활용 방법은 무궁무진
https://docs.djangoproject.com/ko/5.2/topics/db/queries/
데이터 덮어쓰기(수정)
>>> q = Question.objects.get(id=2)
>>> q.subject
'장고 모델 질문입니다.'
>>> q.subject = 'Django Model Question'
>>> q.save()
save() 필요
데이터 덮어쓰기(삭제)
>>> q = Question.objects.get(id=1)
>>> q.delete()
(1, {'pybo.Question': 1})
delete함수를 수행하면 해당 데이터가 데이터베이스에서 즉시 삭제되며(save() 필요 x)
삭제된 데이터의 추가 정보가 반환된다
# 삭제되어서 2번 데이터밖에 보이지 않음
>>> Question.objects.all()
<QuerySet [<Question: Django Model Question>]>
연결된 데이터 확인
>>> q = Question.objects.get(id=2)
>>> a = Answer(question=q, content='네 자동으로 생성됩니다.', create_date=time
zone.now())
>>> a.save()

question_id 로 간소화되어 표현
>>> a.question.id
2
>>> a.question
<Question: Django Model Question>
연결된 데이터로 조회하기: 질문을 통해 답변 찾기
연결모델명_set
Question 과 Answer 테이블은
일대다 관계 ← 질문 1개에는 1개 이상의 답변이 달릴 수 있다
q.answer_set 을 사용>>> q.answer_set.all()
<QuerySet [<Answer: Answer object (1)>]>a.question 을 사용>>> a.question
<Question: Django Model Question>관리자 계정 생성
python manage.py createsuperuser

**models.py 로 테이블 생성 후 데이터를 넣는 방법 2가지**
admin.py 에 테이블 등록
from django.contrib import admin
from .models import Question
# Register your models here.
admin.site.register(Question)

admin 페이지에서 데이터 직접 추가

Admin 에 데이터 검색 기능 추가
class QuestionAdmin(admin.ModelAdmin):
search_fields = ['subject']
admin.site.register(Question, QuestionAdmin)

[/pybo/~ url 요청] |-> settings.py -> ROOT_URLCONF
|-> config/urls.py
|-> pybo/urls.py
|-> pybo/views.py -> index()
views.py
from django.shortcuts import render
from .models import Question
def index(request):
# create_date 역순으로 순서 정렬
question_list = Question.objects.order_by("-create_date")
# 포장
context = {"question_list" : question_list}
# render 로 화면 출력
return render(request, 'pybo/question_list.html', context)
render(request, 'pybo/question_list.html', context)
템플릿을 저장할 디렉터리 생성
C:\Users\DS 14\projects\mysite\templates
템플릿 디렉터리 위치 등록
config/settings.py
TEMPLATES = [
{
...
'DIRS': [BASE_DIR / 'templates'],
...
템플릿 파일 생성
C:\Users\DS 14\projects\mysite\templates\pybo\question_list.html
...
<body>
{% if question_list %}
<ul>
{% for question in question_list %}
<li><a href="/pybo/{{ question.id }}">{{ question.subject }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>질문이 없습니다.</p>
{% endif %}
</body>
...
템플릿 태그 {% %}
| 태그 | 설명 |
|---|---|
{% if question_list %} | question_list가 존재하면 실행 |
{% for question in question_list %} | question_list를 순회하며 순차적으로 하나씩 question에 대입 |
{{ question.id }} | for문에 의해 대입된 question 객체의 id 번호를 출력 |
{{ question.subject }} | for문에 의해 대입된 question 객체의 제목을 출력 |


pybo/urls.py
path('<int:question_id>/', views.detail),
pybo/views.py
def detail(request, question_id):
question = Question.objects.get(id=question_id)
context = {'question' : question}
print(context)
return render(request, 'pybo/question_detail.html', context)
question_detail.html
<body>
<h1>{{ question.subject
}}</h1>
<div>
{{ question.content }}
</div>
</body>
결과

데이터베이스에 없는 데이터 아이디를 집어넣으면
당연히 오류가 남

500 error 가 뜨는데 이건 서버 에러가 아니라 존재하지 않는 페이지이므로 404로 바꿔준다
from django.shortcuts import render, get_object_or_404
...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
...

이제 존재하지 않는 아이디로 조회하면 404 에러가 뜨게 됨
이전까지는 템플릿에서 url 을 사용할 때 하드코딩을 함
<li><a href="/pybo/{{ question.id }}">{{ question.subject }}</a></li>
그러나 url 규칙이 변경된다면 하드코딩해준 부분을 일일이 찾아 수정해줘야 한다
pybo/urls.py
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
]
templates\pybo\question_list.html
<li><a href="{% url 'detail' question.id %}">{{ question.subject }}</a></li>
하드코딩된 /pybo/{{ question.id }} 링크를 {% url 'detail' question.id %}로 변경
pybo/urls.py
app_name = "pybo"
templates\pybo\question_list.html
<li><a href="{% url 'pybo:detail' question.id %}">{{ question.subject }}</a></li>
render vs. redirectrender : 새로운 페이지로 이동 : html 필요redirect : 기존 페이지로 이동 : html 필요 x<h1>{{ question.subject }}</h1>
<div>
{{ question.content }}
</div>
<form action="#" method="post">
{% csrf_token %}
<textarea name="content" id="content" rows="15"></textarea>
<input type="submit" value="답변등록">
</form>
{% csrf_token %}
: 보안과 관련
: form을 통해 전송된 데이터가 실제 웹 페이지에서 작성된 것인지 확인하는 역할
→ 만약 해커가 비정상적인 방법으로 데이터를 전송한다면, 서버에서 발행한 csrf_token 값과 해커가 보낸 csrf_token 값이 일치하지 않아 요청이 차단될 것임
action="#"
: url과 요청 처리 로직이 따로 필요한 부분은 아직 만들어지지 않았으므로 만들어진 페이지 확인을 위해 우선 #으로 설정해서 비워놓음
→ 설계할 땐 비워놓고 만들어놓은 부분 제대로 동작하는지 확인한 후에 action 에 해당하는 부분을 채우고 거기서 필요한 부분을 채우는 흐름으로 하는 게 좋음
action="#" 결과


텍스트 입력 후 답변등록 버튼을 눌러 제출하면 url 뒤에 # 이 붙는 걸 확인 가능
action="{% url 'pybo:answer_create' [question.id](http://question.id/) %}" 넣었을 때 결과

답변 등록을 위한 url 매핑 등록하기
pybo/urls.py
path('answer/create/<int:question_id>/', views.answer_create, name='answer_create'),
answer_create 함수 추가하기
pybo\views.py
...
from django.utils import timezone
...
def answer_create(request, question_id):
question = get_object_or_404(Question, pk=question_id)
question.answer_set.create(content=request.POST.get('content'),
create_date=timezone.now())
# 답변 데이터 추가 후에 상세 페이지로 리다이렉트(다시 이동)
return redirect('pybo:detail', question_id=question.id)
answer 객체 생성 방식
question.answer_set.create(content=request.POST.get('content'),
create_date=timezone.now())answer = Answer(question=question, content=request.POST.get('content'),
create_date=timezone.now())
answer.save()request.POST
: 폼 POST 데이터(QueryDict)
: 폼 방식으로 보낸 ‘텍스트 필드’만 QueryDict(request.POST)에 들어감 (json x)