Dstargram project - 3

[ Dstargram project ]

  • 저장한 페이지 목록 불러오기

    • models.py 수정

      1
      2
      3
      4
      5
      6
      7
      8
      class Photo(models.Model):

      # save라는 변수를 사용하면 안되는 이유
      # 예약어를 필드명을 사용하면 안된다.
      # save라는 변수를 사용해서 예약어와 충돌이나 오류가 났다.
      # favorite로 수정
      # makemigrations 및 migrate 적용
      favorite = models.ManyToManyField(User, related_name='save_post', blank=True)
    • views.py 수정

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      from django.urls import reverse

      from urllib.parse import urlparse

      # 뷰를 실행하기 전에 특정한 로직을 추가로 실행하고 싶다면?
      # 로그인 여부, csrf 체크를 수행할 것인가?
      # 믹스인 : 클래스형 뷰
      # 데코레이터 : 함수형 뷰
      from django.contrib.auth.mixins import LoginRequiredMixin
      # LoginRequiredMixin : 로그인을 해야만 실행할 수 있게 해준다.

      # 로그인되지 않았다면 로그인할 수 있는 페이지로 갈 수 있게 수정
      class PhotoList(ListView):
      model = Photo
      template_name = 'photo/photo_list.html' # 파일 이름 전체를 지정

      def dispatch(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
      return HttpResponseRedirect(reverse('accounts:signin'))
      else:
      return super(PhotoList, self).dispatch(request, *args, **kwargs)

      # models.py의 필드에서 설정한 related_name을 통해 쉽게 해당 리스트를 불러옴
      # get_queryset()을 통해 반환
      # 좋아요를 한 페이지 목록 불러오기
      class PhotoLikeList(LoginRequiredMixin, ListView):
      model = Photo
      template_name = 'photo/photo_list.html'

      def get_queryset(self):
      # 로그인한 유저가 좋아요를 클릭한 글을 찾아서 반환
      user = self.request.user
      queryset = user.like_post.all()
      return queryset

      # 저장한 페이지 목록 불러오기
      class PhotoSaveList(LoginRequiredMixin, ListView):
      model = Photo
      template_name = 'photo/photo_list.html'

      def get_queryset(self):
      user = self.request.user
      queryset = user.save_post.all()
      return queryset

      # 로그인한 유저가 작성한 페이지 목록 불러오기
      class PhotoMyPageList(LoginRequiredMixin, ListView):
      Model = Photo
      template_name = 'photo/photo_list.html'

      def get_queryset(self):
      user = self.request.user
      queryset = user.photos.all()
      return queryset

      # 좋아요와 저장을 하는 버튼은 리스트 페이지와 디테일 페이지에서 모두 가능하다.
      # 좋아요와 저장을 눌러도 해당 페이지에서 계속 머물러야 하기 때문에 경로 부분을 수정한다.
      class PhotoLike(View):
      def get(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
      return HttpResponseForbidden()
      else:
      if 'photo_id' in kwargs:
      photo_id = kwargs['photo_id']
      photo = Photo.objects.get(pk=photo_id)
      user = request.user

      if user not in photo.like.all():
      photo.like.add(user)
      else:
      photo.like.remove(user)

      # 수정한 부분
      # 도메인 주소 필요없이 경로만 필요하다.
      referer_url = request.META.get('HTTP_REFERER')
      path = urlparse(referer_url).path
      return HttpResponseRedirect(path)

      class PhotoSave(View):
      def get(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
      return HttpResponseForbidden()
      else:
      if 'photo_id' in kwargs:
      photo_id = kwargs['photo_id']
      photo = Photo.objects.get(pk=photo_id)
      user = request.user

      if user not in photo.favorite.all():
      photo.favorite.add(user)
      else:
      photo.favorite.remove(user)

      # 수정한 부분
      referer_url = request.META.get('HTTP_REFERER')
      path = urlparse(referer_url).path
      return HttpResponseRedirect(path)
    • settings.py 수정

      1
      2
      3
      # 로그인 페이지로 이동할 수 있도록 경로 지정
      from django.urls import reverse_lazy
      LOGIN_URL = reverse_lazy('accounts:signin')
    • photo/urls.py 수정

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      from django.urls import path

      # 좋아요와 저장한 페이지 목록을 가져오기 위해 모듈 추가
      from .views import PhotoList, PhotoCreate, PhotoUpdate, PhotoDelete, PhotoDetail, \
      PhotoLike, PhotoLikeList, PhotoSave, PhotoSaveList, PhotoMyPageList

      app_name = 'photo'

      # 경로 추가
      urlpatterns = [
      path('like/<int:photo_id>/', PhotoLike.as_view(), name='like'),
      path('save/<int:photo_id>/', PhotoSave.as_view(), name='save'),
      path('like/', PhotoLikeList.as_view(), name='like_list'),
      path('save/', PhotoSaveList.as_view(), name='save_list'),
      path('mypage/', PhotoMyPageList.as_view(), name='mypage_list'),
      path('', PhotoList.as_view(), name = 'index'),
      path('create/', PhotoCreate.as_view(), name = 'create'),
      path('update/<int:pk>', PhotoUpdate.as_view(), name = 'update'),
      path('delete/<int:pk>', PhotoDelete.as_view(), name = 'delete'),
      path('detail/<int:pk>', PhotoDetail.as_view(), name = 'detail'),
      ]
    • templates 수정

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74

      <!--//
      MyPage, Like, Saved 추가
      중요한건 로그인한 상태에서만 보이기 위해 조건문 이용
      //-->
      {% if user.is_authenticated %}
      <li class="nav-item">
      <a class="nav-link" href="{% url 'photo:mypage_list' %}">MyPage</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="{% url 'photo:like_list' %}">Like</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="{% url 'photo:save_list' %}">Saved</a>
      {% else %}
      <li class="nav-item">
      <a class="nav-link" href="{% url 'accounts:signin' %}">Sign-in</a>
      </li>
      <li class="nav-item">
      <a class="nav-link" href="#">Sign-up</a>
      </li>
      {% endif %}

      <!--// 스크립트 부분 주석 처리//-->
      <script type="text/javascript">
      $(function() {
      $('.btn-like').click(function(e) {
      //e.preventDefault();
      //$(this).toggleClass('active');
      //$(this).blur();
      });

      $('.btn-save').click(function(e) {
      //e.preventDefault();
      //$(this).toggleClass('active');
      //$(this).blur();
      });
      });
      </script>



      <!--//
      photo_list.html

      좋아요가 눌린 수를 출력하기 위해 수정
      //-->
      <a href="{% url 'photo:like' object.id %}" class="float-left btn-like {% if user in object.like.all %} active {% endif %}">Like</a>

      <!--// 좋아요가 눌린 수 출력 //-->
      {% if object.like.all.count %}
      <span class="float-left badge badge-danger" style="margin-left:0.5em;">
      {{object.like.all.count}}
      </span>
      {% endif %}

      <a href="{% url 'photo:save' object.id %}" class="float-right btn-save {% if user in object.favorite.all %} active {% endif %}">Save</a>



      <!--//
      photo_detail.html
      디테일 페이지도 리스트 페이지와 같으므로 같게 수정
      //-->
      <a href="{% url 'photo:like' object.id %}" class="float-left btn-like {% if user in object.like.all %} active {% endif %}">Like</a>

      <!--// 좋아요가 눌린 수 출력 //-->
      {% if object.like.all.count %}
      <span class="float-left badge badge-danger" style="margin-left:0.5em;">
      {{object.like.all.count}}
      </span>
      {% endif %}

      <a href="{% url 'photo:save' object.id %}" class="float-right btn-save {% if user in object.favorite.all %} active {% endif %}">Save</a>
  • 팔로우 기능 추가하기

    • models.py 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      from django.db import models

      # 중간 모델 착성
      from django.contrib.auth.models import User

      class Follow(models.Model):
      # 2개 필드 = ForeignKey
      # A가 B를 팔ㄹ우 하고 있다.
      # on_delete = 연관된 객체가 삭제된다면 어떻게 할 것인가?
      # related_name은 참조 객체의 입장에서 필드명, 속성값
      me = models.ForeignKey(User, on_delete=models.CASCADE, related_name='following') # 내가 팔로잉한 사람들
      you = models.ForeignKey(User, on_delete=models.CASCADE, related_name='follower') # 나를 팔로워한 사람들

      def __Str__(self):
      return self.me.username + " follow " + self.you.username
    • admin.py 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from django.contrib import admin

      # Register your models here.
      from .models import Follow

      # 옵션 추가
      # raw_id_fields 적용
      class FollowOption(admin.ModelAdmin):

      list_display = ['id','me','you']
      raw_id_fields = ['me', 'you']

      admin.site.register(Follow, FollowOption)
    • views.py 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      from django.shortcuts import render

      # 유저 목록이 출력되는 뷰
      # + Follow라는 기능 추가
      # 중간 테이블을 직접 생성 - 모델

      # 유저 모델을 커스터마이징해야 한다. -> 1. 커스터마이징 하는 방법을 배운다.
      # 확장하는 방법에 따라서
      # 1) 새로운 유저 모델을 만드는 방법 - 기존 유저 데이터를 유지할 수가 없다.
      # 2) 기존 모델을 확장하는 방법 - DB 다운 타임 alter table을 하면 table lock이 걸린다.
      # -> 두 방법 다 운영하는 중간 입장에서는 위험한 방법이다.

      # 유저 모델(나)
      # 나를 팔로우한 사람 필드
      # 내가 팔로우한 사람 필드

      # 하지만 커스터마이징 할 수 없다면?
      # 새로운 모델을 추가하는 방법

      # 사진 모델
      # 사진을 좋아요한 사람 필드
      # 사진을 저장한 사람 필드


      """
      팔로우 기능 구현
      1. 유저 목록 혹은 유저 프로필에서 팔로우 버튼
      1-1. 전체 유저 목록을 출력하는 뷰 - User 모델에 대한 ListView
      2. 팔로우 정보를 저장하는 뷰
      """

      from django.views.generic.list import ListView
      from django.views.generic.base import View
      from django.contrib.auth.models import User

      from django.http import HttpResponseRedirect
      from django.http import HttpResponseForbidden
      from django.contrib import messages

      from django.urls import reverse

      from urllib.parse import urlparse

      from django.shortcuts import redirect

      from django.contrib.auth.mixins import LoginRequiredMixin

      from .models import Follow

      class UserList(ListView):
      model = User
      template_name = 'accounts/user_list.html'

      def dispatch(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
      return HttpResponseRedirect(reverse('accounts:signin'))
      else:
      return super(UserList, self).dispatch(request, *args, **kwargs)

      # get_queryset()을 통해 반환되는 object는 Follow object이다.
      # 그래서 user object의 정보를 가져오지 않아 화면에 출력이 되지 않았다.
      # 따라서 리스트에 user object를 받아 넘겼다.
      class FollowerList(LoginRequiredMixin, ListView):
      Model = User
      template_name = 'accounts/user_list.html'

      def get_queryset(self):
      user = self.request.user
      queryset = user.follower.all()
      user_list = []
      for idx in queryset:
      user_list.append(idx.me)
      return user_list

      # 위와 같은 방법
      class FollowingList(LoginRequiredMixin, ListView):
      Model = User
      template_name = 'accounts/user_list.html'

      def get_queryset(self):
      user = self.request.user
      queryset = user.following.all()
      user_list = []
      for idx in queryset:
      user_list.append(idx.you)
      return user_list

      # user_id를 받아와서 filter를 통해 로그인한 유저가 팔로우한 대상을 찾아냄
      # 있다면 언팔로우를, 있다면 팔로우를 함
      # 나 자신을 팔로우할 수 없게 조건문 적용
      class FollowButton(View):
      def get(self, request, *args, **kwargs):
      if not request.user.is_authenticated:
      return HttpResponseForbidden()
      else:
      if 'user_id' in kwargs:
      user_id = kwargs['user_id']
      follow_user = User.objects.get(pk=user_id)
      login_user = request.user

      if not login_user == follow_user:
      follow_filter = Follow.objects.filter(me=login_user, you=follow_user)

      if follow_filter:
      follow_filter[0].delete()
      else:
      Follow(me=login_user, you=follow_user).save()

      referer_url = request.META.get('HTTP_REFERER')
      path = urlparse(referer_url).path
      return HttpResponseRedirect(path)
    • accounts/urls.py

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      from django.urls import path
      from django.contrib.auth.views import LoginView, LogoutView

      # 모듈 추가
      from .views import UserList, FollowerList, FollowingList, FollowButton

      app_name = 'accounts'

      # 경로 추가
      # 팔로우 버튼은 user_id를 받음
      urlpatterns = [
      path('user/list/', UserList.as_view(), name='user_list'),
      path('follow/<int:user_id>/', FollowButton.as_view(), name='follow_button'),
      path('follower/list/', FollowerList.as_view(), name='follower_list'),
      path('following/list/', FollowingList.as_view(), name='following_list'),
      path('singin/', LoginView.as_view(template_name='accounts/signin.html'), name='signin'),
      path('signout/', LogoutView.as_view(template_name='accounts/signout.html'), name='signout'),
      ]
    • templates 작성

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      <!--// user_list.html //-->
      {% extends 'base.html' %}
      {% block title %}
      Photo Create
      {% endblock %}

      {% block content %}
      <div class="row">
      <div class="col"></div>
      <div class="col-6">
      <!--// 유저 목록을 테이블로 적용 //-->
      <table class="table">
      <thead>
      <tr class="table-light">
      <th scope="col">username</th>
      <th scope="col">name</th>
      <th scope="col">follower</th>
      <th scope="col">following</th>
      <th scope="col">button</th>
      </tr>
      </thead>
      <tbody>
      {% for object in object_list %}
      <tr>
      <td>{{object.username}}</td>
      <td>{{object.firstname}} {{object.lastname}}</td>
      <td>{{object.follower.all.count}}</td>
      <td>{{object.following.all.count}}</td>
      <td><a href="{% url 'accounts:follow_button' object.id %}" class="btn btn-xs btn-sm btn-primary">Follow</a></td>
      </tr>
      {% endfor %}
      </tbody>
      </table>
      </div>
      <div class="col"></div>
      </div>
      {% endblock %}
  • 커스텀 템플릿 필터 사용하여 버튼 이름 변경하기

    • 팔로우일 때는 언팔로우 버튼이 표시되도록 한다.
    • 팔로우가 아닐 때는 팔로우 버튼이 표시되도록 한다.
    • 해당 앱 폴더에서 templatetags 폴더를 생성한다.
    • templatetags 폴더 안에 init.py를 생성한다.(파이썬 패키지라는 것을 명시)
    • 소스 파일을 생성해서 작성한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # follow_filter.py
      from django import template

      register = template.Library()

      # index라는 매개변수에 object를 받음
      # 현재 object의 유저를 팔로워한 유저들을 user__list로 반환
      @register.filter
      def follow_filter(index):
      user_list = []

      for idx in index.follower.all():
      user_list.append(idx.me)

      return user_list
    • templates 파일을 수정한다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <!--//
      user_list.html

      작성한 모듈 로드 : follow_filter.py
      //-->
      {% load follow_filter %}

      <!--//
      팔로워한 유저 목록안에 지금 로그인한 유저가 있다면 UnFollow 버튼을,
      그렇지 않다면 Follow 버튼으로 보이도록 조건문을 추가한다.
      //-->
      <td>
      <a href="{% url 'accounts:follow_button' object.id %}" class="btn btn-xs btn-sm btn-primary">
      {% if user in object|follow_filter %}
      Unfollow
      {% else %}
      Follow
      {% endif %}
      </a>
      </td>
Share