작정하고 장고 (6)

·2023년 9월 16일

Django

목록 보기
6/8

Pagination

Account를 만들때는 단일 객체
게시판 같은 형태 -> 여러 개의 객체를 다룰 수 있는 뷰가 필요 = > List view (장고 기본 제공 ) + Pagination

  • List view의 형태
    어던 모델 쓸건지, 어떤 식의 이름을 템플릿 안에서 쓸건지, 어떤 템플릿 쓸건지, 페이지네이션 할 때 한 페이지에 몇 개의 객체를 보여줄 것인지 + 알파

  • Pagination
    => Generate Page Of objects
    ex.

  • html template에서 쓸 객체들은 article_Listpage_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
  1. url 설정 (list로 향하는 거 코드 수정)
 path('list/', ArticleListView.as_view(), name='list'),
  1. list.html에서 픽썸 사진들 다 지우고 for 문을 돌려서 article인데 in 문을 써서 articlelist 안에 있는 모든 article에 대해서 루프 도는 형식으로 만들기
 <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>
  1. 카드 형태에 들어가는 내용은 따로 빼주기, template 안(헤더 html있는 쪽)에서 snippets(코드 조각) 경로 설정, 그안에 card라는 이름으로 html 파일 생성
    html 파일에 붙여넣기
 <div>
     <img src="{{ article.image.url }}" alt="">
 </div>
  1. list.html 코드 수정 (방금 복붙한 코드 지우기)
 {% include 'snippets/card.html' with article=article %}

+) 만약에 카드의 레이아웃(내용)을 바꾸고 싶을 때 4번 부분만 바꾸면 모든 레이아웃이 바뀜

  1. article.list가 없을 때는 게시글이 없다고 하는 코드 (list.html)
</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 %}
  1. pagination 버튼 만들기 (버튼 없이 url에서 페이지 값 조정해서 넘기기 가능)
    list. html {% 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랑 반대, 다음 페이지로 생성!

  1. 디자인 수정, profile.nickname 뜨게 하는 코드 추가, 인증 코드 추가
    => 지금까지 detail.view 총 코드
{% 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 %}

Mixin

detail view는 폼이 없는데 폼과 함께 사용하고 싶을 때 문제가 생김
=> mixin으로 해결

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

CommentApp의 요건

코딩 )
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= [
    
]
  1. view 작성

  2. 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)
  1. forms. py 만들기/작성
class CommentCreationForm(ModelForm):
    class Meta:
        model = Comment
        fields = ['content']
  1. 마이그레이션 작업
    python manage.py makemigrations
    python manage.py migrate

  2. 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})
  1. url 설정
    path('create/', CommentCreateView.as_view(), name='create'),
  1. commentapp내부에 templates 경로 만들기
    그 안에 commentapp이라고 만들기 -> 그 안에 create html 파일 만들어주기
    앞에 creat.html 내용 같고 내용만 수정
    <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~ 구문 지워주기
  1. form 오류 해결하기 -> mixin 사용
    articleapp에서 view에서 detailview 보기 -> form 없음
    FormMixin가져와서 다중 상속시켜주기
class ArticleDetailView(DetailView, FormMixin):
    model = Article
    form_class = CommentCreationForm
    context_object_name = 'target_article'
    template_name = 'articleapp/detail.html'
  1. 서버에서 commentapp만들기 이전에 설정해줘야하는 값 설정
   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 값으로 설정

  1. 로그인을 안하고 comment 생성 요청을 보낼 수 없도록 수정
    comment create html 파일에서 요청 보내는 버튼을 로그인 돼 있을 떄만 보여주고 아닌 경우에는 로그인하는 url 향하도록 만들기
    히든 위의 코드에 추가
 {% 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 인자로 어디로 돌아와야하는지도 추가

  1. 무슨 작업인진 모르겠는데 추가작업임,, 뭐가 안뜬다했던 것 같기도
 {% 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 구문 사용)

  1. detail.html 만들기
<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>
        &nbsp&nbsp&nbsp
        {{ comment.created_at }}
    </div>
    <div style="margin: 1rem 0;">
        {{ comment.content }}
    </div>
</div>
  1. delete 기능 만들기
    (1) comment에서 view를 들어간 후 deleteview 만들기
    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')

Mobile Debugging

Responsive Design = 반응형으로 만들기

  • How to connect with Mobile browser?
  1. python manage.py runserver => 서버 구동할때마다 씀 이는 컴퓨터에서 서버를 돌리고 접속하는 것도 이 컴퓨터에서만 접속할 수 있게 설정하는 것 => 핸드폰으로 들어가고 싶으면 못 들어감

==>
0.0.0.0:8000포트로 구동을 하면 로컬 호스트(내 컴퓨터)말고도 다른 곳에서도 id 주소를 기반으로 지금 이 컴퓨터에서 구동되고있는 서버에 접속할 수 있는 포트가 열리게 된다.

+) 추가적으로 장고 세팅 안에 있는 ALLOWED_HOST에서 다른 호스트도 허용할 수 있게 설정해줄 것.
2. Connect to WIFI local network server IP
IP 서버를 IP 번호를 확인하고 그 번호를 모바일 브라우저에다가 친 다음에 접속할 것
IP 아는 법 (와이파이 공유기가 있고 모바일과 컴퓨터가 같은 지역 네트워크(같은 공유기) 안에 있다는 가정 하)

  • CMD창에서 ipconfig치는 법
  • wifi 공유기 세팅 페이지 들어가서 컴퓨터 ip 어떻게 되는지 알아내기

실습 )
1. 터미널에 python manage.py runserver 0.0.0.0:8000 입력

  1. pragmatic에 settings.py에서 ALLOWED_HOSTS = ['*'] * 추가!
    = > 모든 호스트에 대해 허용

    이러면 휴대폰에서 열릴 건데 왜 난 안열리누?
    모바일 연결 참고
    이렇게 해도 웨안대?OTL

    암튼, 개발자도구에서 모바일 환경에서 어케 보이는지 알 수 있듬

  2. 실제 디바이스 너비에 맞게 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 태그들이 적용된다는 느낌
 
 
 



0개의 댓글