Account를 만들때는 단일 객체
게시판 같은 형태 -> 여러 개의 객체를 다룰 수 있는 뷰가 필요 = > List view (장고 기본 제공 ) + Pagination
List view의 형태
어던 모델 쓸건지, 어떤 식의 이름을 템플릿 안에서 쓸건지, 어떤 템플릿 쓸건지, 페이지네이션 할 때 한 페이지에 몇 개의 객체를 보여줄 것인지 + 알파
Pagination
=> Generate Page Of objects
ex. 

html template에서 쓸 객체들은 article_List와 page_obj
=> 게시글의 List, 객체들의 각각의 정보가 들어있음 (아이템들의 꾸러미)
=> page_obj은 하단에 페이지 생성 번호들에 관한 것, 현재 페이지 보여주고 이전 페이지, 다음 페이지로 갈 수 있도록 링크 만들어 줌
코딩 )
1. article app views.py에서 뷰 만들기
class ArticleListView(ListView):
model = Article
context_object_name = 'article_list'
template_name = 'articleapp/list.html'
paginate_by = 25
path('list/', ArticleListView.as_view(), name='list'),
<div class="container">
{% for article in article_list %}
<a href="{% url 'articleapp:detail' pk=article.pk %}">
<div>
< img src=""{{ article.image.url }} alt="">
</div>
</a>
</div>
<div>
<img src="{{ article.image.url }}" alt="">
</div>
{% include 'snippets/card.html' with article=article %}
+) 만약에 카드의 레이아웃(내용)을 바꾸고 싶을 때 4번 부분만 바꾸면 모든 레이아웃이 바뀜
</style>
{% if article_list %}
<div class="container">
{% for article in article_list %}
<a href="{% url 'articleapp:detail' pk=article.pk %}">
{% include 'snippets/card.html' with article=article %}
</a>
{% endfor %}
</div>
<script src="{% static 'js/magicgrid.js' %}"></script>
{% else %}
<div class="text-center">
<h1>No Articles YET!</h1>
</div>
{% endif %}
{% include 'snippets/pagination.html' with page_obj=page_obj %}
snippets 안에다가 pagenation.html 만들기
<div style="text-align: center; margin: 1rem 0;">
{% if page_obj.has_previous %}
<a href="{% url 'articleapp:list' %}?page={{ page_obj.previous_page_number }}"
class="btn btn-secondary rounded-pill">
{{ page_obj.previous_page_number }}
</a>
{% endif %}
<a href="{% url 'articleapp:list' %}?page={{ page_obj.number }}"
class="btn btn-secondary rounded-pill active">
{{ page_obj.number }}
</a>
{ % if page_obj.has_next %}
<a href="{% url 'articleapp:list' %}?page={{ page_obj.next_page_number }}"
class ="btn btn-secondary rounded-pill">
{{ page_obj.next_page_number }}
</a>
{% endif %}
</div>
page_obj에서 has_previous 객체: 현재 페이지에서 이전 페이지가 있는지 없는지 확인할 수 있는 객체 (이전 페이지가 있으면 이전 페이지로 넘어가는 링크 생성)
현재 있는 페이지로 향하는 코드
has_next -> previous랑 반대, 다음 페이지로 생성!
{% extends 'base.html' %}
{% block content %}}
<div>
<div style="text-align: center; max-width: 700px; margin: 4rem auto;">
<h1>
{{ target_article.title }}
</h1>
<h5>
{{ target_article.writer.profile.nickname }}
</h5>
<img style="width: 100%; border-radius: 1rem; margin: 2rem 0;"
src="{{ target_article.image.url }}" alt="">
<p>
{{ target_article.content }}
</p>
{% if target_article.writer == user %}
<a href="{% url 'articleapp:update' pk=target_article.pk %}"
class="btn btn-primary rounded-pill col-3">
Update
</a>
<a href="{% url 'articleapp:delete' pk=target_article.pk %}"
class="btn btn-danger rounded-pill col-3">
Delete
</a>
{% endif %}
<hr>
</div>
</div>
{% endblock %}
detail view는 폼이 없는데 폼과 함께 사용하고 싶을 때 문제가 생김
=> mixin으로 해결

mixin 제공하는 장고 기능 여러가지 있는데, Formmixin 사용하게 되면 다중상속 가능해짐
detail view만 상속받는게 아니라 Formmixin까지 한꺼번에 상속받게되면 내부에서 form클래스 지정해줌으로써 detailview안에서도 폼 사용 가능 
=> mixin은 addon이랑 비슷

코딩 )
1. 코멘트 앱 만들기
python manage.py startapp commentapp
2. settings.py에서 앱 등록
'commentapp',
3. urls.py에서 url 포함
path('comments/', include('commentapp.urls')),
4. commentapp 들어가서 urls.py 만들어주기
app_name = 'commentapp'
urlpatterns= [
]
view 작성
models.py 만들기/작성
from django.contrib.auth.models import User
from django.db import models
from articleapp.models import Article
# Create your models here.
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.SET_NULL, null=True, related_name='comment')
writer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, relatede_name='comment')
content = models.TextField(null=False)
created_at = models.DateTimeField(auto_now=True)
class CommentCreationForm(ModelForm):
class Meta:
model = Comment
fields = ['content']
마이그레이션 작업
python manage.py makemigrations
python manage.py migrate
view 마무리
class CommentCreateView(CreateView):
model = Comment
form_class = CommentCreationForm
template_name = 'commentapp/create.html'
def get_success_url(self):
return reverse('articleapp:detail', kwargs={'pk': self.object.article.pk})
path('create/', CommentCreateView.as_view(), name='create'),
<div style="text-align: center; max-width: 500px; margin: 4rem auto">
<div class="mb-4">
<h4>Comment Create</h4>
</div>
<form action="{% url 'commentapp:create' %}" method="post">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
<input type="hidden" name="article_pk" value=""> // --> ????
</form>
</div>우리는 comment만 따로 만들 수 있는 페이지가 아닌, 게시글 밑에서 달 수 있는 형태로 만들고 싶음 => 레이아웃을 게시글 밑에 넣는 작업 (include 구문 사용 )
12. articleapp에서 templates에서 detail로 들어가기
hr 태그 밑에 코드 추가
{% include 'commentapp/create.html' with article=target_article %}
=> commentapp 안에 있는 create.html을 가져오고, with 구문을 이용해서 안에 있었던 article이라는 거를 현재 있는 target_article이랑 동기화 시켜줌.
-> 그러면 create안에서 article이라는 변수 사용 가능
그래서 숨겨서 보내는 article_pk value 안에다가 article.pk 담아서 서버로 보낼 것 (코드 수정)
```
주의 ) 이 값(article. pk)은 브라우저에서 조작이 가능하다
+) 맨위의 extends base~ 구문 지워주기
FormMixin가져와서 다중 상속시켜주기class ArticleDetailView(DetailView, FormMixin):
model = Article
form_class = CommentCreationForm
context_object_name = 'target_article'
template_name = 'articleapp/detail.html'
def form_valid(self, form):
temp_comment = form.save(commit=False)
temp_comment.article = Article.objects.get(pk=self.request.POST['article_pk'])
temp_comment.writer = self.request.user
temp_comment.save()
return super().form_valid(form)
아까 hidden으로 보낸 artiecle pk가 넘어와서 이 pk가 가지고 있는 article을 지금 만들 comment에 article 값으로 설정
{% if user.is_authenticated %}
<input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
{% else %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}"
class="btn btn-dark rounded-pill col-6 mt-3">
Login
</a>
{% endif %}
next 인자로 어디로 돌아와야하는지도 추가
{% for comment in target_article.comment.all %}
{% include 'commentapp/detail.html' with comment=comment %}
{% endfor %}
=> target_article에 foreignkey로 엮여있는 comment들을 모두 가져오겠다는 형식
include 구문을 commentapp안에 있는 detail.html을 가져올거고, 안에는 comment에 들어있는 내용을 접근해야하니까 안에도 comment 또 넣어줌 (comment를 안에서 comment 이름으로 접근할 수 있게 with 구문 사용)
<div style="border: 1px solid; text-align: left; padding: 4%; margin: 1rem 0; border-radius: 1rem;
border-color: #bbb;">
<div>
<strong>
{{ comment.writer.profile.nickname }}
</strong>
   
{{ comment.created_at }}
</div>
<div style="margin: 1rem 0;">
{{ comment.content }}
</div>
</div>
class CommentDeleteView(DeleteView):
model = Comment
context_object_name = 'target_commit'
template_name = 'commentapp/delete.html'
def get_success_url(self):
return reverse('articleapp:detail', kwargs={'pk': self.object.article.pk})(2) 링크 만들기
comment detail에서 한 층 추가
```
(3) url 설정
```
path('delete/', CommentDeleteView.as_view(), name='delete'),
```
어떤 코멘트를 지울지 pk로 받아줌
(4) 유저가 이 코멘트를 가지고 있는 주인과 같은 지 확인 과정 넣기 (아무때나 delete 보여주면 안됨)
{% if comment.writer == user %}
<div style="text-align: right">
<a href="{% url 'commentapp:delete' pk=comment.pk %}"\\
class="btn btn-danger rounded pill">
Delete
</a>
</div>
{% endif %}
(5) delete에 해당하는 html 파일 만들기
{% extends 'base.html' %}
{% block content %}
<div style="text-align: center; max-width: 500px; margin: 4rem auto">
<div class="mb-4">
<h4>Delete Comment : {{ target_comment.content }}</h4>
</div>
<form action="{% url 'commentapp:delete' pk=target_comment.pk %}" method="post">
{% csrf_token %}
<input type="submit" class="btn btn-danger rounded-pill col-6 mt-3">
</form>
</div>
{% endblock %}
+)하놬ㅋ NoReverseMatch 오류 떠서 몇시간을 찾았는데 ㅇㄴ views.py deleteview 부분에
target_commit 오류 있었음 ㅇㄴㅇㄴㅇㄴㅇㄴ
urls.py 파일의 URL 패턴을 다시 확인하여 되돌리려는 URL과 일치하는지 확인
반전하려는 URL의 매개변수를 확인하여 보기 기능에서 예상하는 매개변수와 일치하는지 확인
reverse() 함수에 전달된 인수가 올바르고 URL 패턴과 일치하는지 확인
이름이 지정된 URL 패턴을 사용 중이고 이름이 올바른지 확인
모든 애플리케이션이 설치되었고 settings.py 파일의 INSTALLED_APPS 튜플에 추가되었는지 확인
이 정도 확인하고 오타 검수 잘하면 해결되는 듯...ㅎ
(6) comment의 주인인지 아닌지 확인 필요, decorator 사용
decorator commentapp에 그대로 가져와서 사용
from django.http import HttpResponseForbidden
from commentapp.models import Comment
def comment_ownership_required(func):
def decorated(request, *args, **kwargs):
comment = Comment.objects.get(pk=kwargs['pk'])
if not comment.writer == request.user:
return HttpResponseForbidden()
return func(request, *args, **kwargs)
return decorated
(7) views.py에서 decorator 추가
delete view 위에 추가
@method_decorator(comment_ownership_required, 'get')
@method_decorator(comment_ownership_required, 'post')
Responsive Design = 반응형으로 만들기
이는 컴퓨터에서 서버를 돌리고 접속하는 것도 이 컴퓨터에서만 접속할 수 있게 설정하는 것 => 핸드폰으로 들어가고 싶으면 못 들어감 ==> 
0.0.0.0:8000포트로 구동을 하면 로컬 호스트(내 컴퓨터)말고도 다른 곳에서도 id 주소를 기반으로 지금 이 컴퓨터에서 구동되고있는 서버에 접속할 수 있는 포트가 열리게 된다.
+) 추가적으로 장고 세팅 안에 있는 ALLOWED_HOST에서 다른 호스트도 허용할 수 있게 설정해줄 것.
2. Connect to WIFI local network server IP
IP 서버를 IP 번호를 확인하고 그 번호를 모바일 브라우저에다가 친 다음에 접속할 것
IP 아는 법 (와이파이 공유기가 있고 모바일과 컴퓨터가 같은 지역 네트워크(같은 공유기) 안에 있다는 가정 하)
실습 )
1. 터미널에 python manage.py runserver 0.0.0.0:8000 입력
pragmatic에 settings.py에서 ALLOWED_HOSTS = ['*'] * 추가!
= > 모든 호스트에 대해 허용
이러면 휴대폰에서 열릴 건데 왜 난 안열리누?
모바일 연결 참고
이렇게 해도 웨안대?OTL
암튼, 개발자도구에서 모바일 환경에서 어케 보이는지 알 수 있듬
실제 디바이스 너비에 맞게 resize해주는 태그 넣어주기
templates에 head에서 meta charset~ 코드 밑에 추가
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
4. list.html에서 사진 크기 조정
.container {
padding 0;
margin : 0, auto;
}
.container a {
width: 45%
max-width: 250px;
}
.container div {
display: flex;
justify-content: center;
align-items: center;
5. 간격 조정
magicgrid에서 변경
` gutter: 12,`로 변경
6. 크기 조정(작게, 미디어쿼리 이용해서 일정 크기 이하로 떨어지면 폰트사이즈도 줄어들게 만들기)
base.css 들어가서 코딩
스크린의 사이즈가 500이하로 떨어지면 이 안에 있는 css 태그들이 적용된다는 느낌