구독 버튼을 누르면 그 페이지에서 다른 페이지로 넘어가는 게 아닌 구독 버튼만 바뀐다. 별 다른 정보를 넘겨주지도 않는다. 요청을 받자마자 처리할 것들을 하고 바로 Redirect하도록 한다.
같은 게시판을 여러번 구독할 수 없으므로 user과 project를 연결하는 쌍이 가지는 구독 정보가 하나여야 한다.
Meta 정보로 unique_together를 이용하여 둘을 한 쌍으로 묶어준다.
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
from projectapp.models import Project
class Subscription(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='subscription')
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='subscription')
class Meta:
unique_together = ('user', 'project')
model 작업을 했으므로 makemigration, migrate를 해준다.
get_redirect_url 메소드를 이용해 projectapp:detail로 향하게 한다.
GET방식으로 project_pk를 받아서 이 pk를 가지고 있는 detail 페이지로 돌아가는 것이다.
project: get_object_or_404 단축함수를 이용해 project_pk를 가지고 있는 Project를 찾는데 없다면 404 반응을 낸다.
user: self.request.user
project, user 두 정보를 찾아낸 후 subscription을 찾는다.
만약 있다면 삭제해주고 없다면 만들고 저장해준다.
@method_decorator(login_required, 'get')
class SubscriptionView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse('projectapp:detail', kwargs={'pk': self.request.GET.get('project_pk')})
def get(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.request.GET.get('project_pk'))
user= self.request.user
subscription = Subscription.objects.filter(user=user,
project=project)
# 구독했으면 없애고 안했으면 해주고
if subscription.exists():
subscription.delete()
else:
Subscription(user=user, project=project).save()
return super(SubscriptionView, self).get(request, *args, **kwargs)
from django.urls import path
from subscribeapp.views import SubscriptionView
app_name = 'subscribeapp'
urlpatterns = [
path('subscribe/', SubscriptionView.as_view(), name='subscribe')
]
projectapp/detail.html
SubscriptionView에서 GET방식으로 project_pk라는 데이터를 넘겨주기로 했으니까 detail에서 target_project.pk를 project_pk로 넘겨준다.
<a href="{% url 'subscribeapp:subscribe'%}?project_pk={{ target_project.pk }}"
class="btn btn-primary rounded-pill px-4">
</a>
이렇게 하면 작동은 되지만 구독을 했는지 안했는지 알 방법이 없다.
projectapp/views.py의 DetailView에서 지금 접속한 유저이 이 게시판에 대해 구독 정보가 있는 지 없는지 확인을 해주는 작업이 필요하다.
get_context_data 안에서 현재 project와 user 정보를 얻어온다. 그런데 여기서 그 user가 로그인을 했는지 안했는 지 확인하는 작업부터 해줘야 한다.
유저가 로그인이 되어 있으면 subscription 변수에 얻어온 user와 project를 넣어줘서 찾아준다. 그 후 찾은 subscription을 return 해준다.
def get_context_data(self, **kwargs):
project = self.object
user= self.request.user
# user의 로그인 여부
if user.is_authenticated:
subscription = Subscription.objects.filter(user=user, project=project)
object_list = Article.objects.filter(project=self.get_object())
return super(ProjectDetailView, self).get_context_data(object_list=object_list,
detail에서 토글이 되는 것을 눈으로 확인할 수 있도록 수정해준다.
<!--구독버튼-->
<div class="text-center mb-5">
{% if user.is_authenticated %}
{% if not subscription %}
<a href="{% url 'subscribeapp:subscribe'%}?project_pk={{ target_project.pk }}"
class="btn btn-primary rounded-pill px-4">
Subscribe
</a>
{% else %}
<a href="{% url 'subscribeapp:subscribe'%}?project_pk={{ target_project.pk }}"
class="btn btn-dark rounded-pill px-4">
Unsubscribe
</a>
{% endif%}
{% endif %}
</div>
field를 사용할 때 다음과 같이 특정 조건을 넣는 형식으로 했었다.
Model.objects.filter(pk=xxx, user=xxx)
(pk=xxx, user=xxx)는 pk와 user 값의 AND function이다. 그렇다면 OR function, WHERE 구문은 어떻게 사용할까?
다음 작업을 진행하면서 알아보자
Find user Subscripted projects.
유저가 구독하고 있는 프로젝트들을 확인하기
Find articles in projects.
그 프로젝트 안 모든 게시글 가져오기
기존에 사용하던 방식에서 다음과 같이 바꾼다.
Model.objects.filter(project__in=projects)
이 project__in=projects같이 double underscore로 이루어진 것들이 장고에서 제공하는 field lookup이다.
SELECT ... WHERE project IN (''');
목적 : 조금 더 복잡한 DB 쿼리를 사용자가 구현할 수 있도록 사용한다. For advanced DB query
종류
특정 조건을 만족하는 게시글을 가져와야 하기 때문에 SubscriptionListView안에서 queryset 관련 함수를 새로 작성한다.
value_list('project')는 값들을 list화 시키는 함수인데 우리는 Subscription 모델에서 user와 project를 지정해줬다. 이 project에 대해 list화 시킨다는 것이다.
이제 여기서 field lookup을 사용해 article_list를 만든다.
@method_decorator(login_required, 'get')
class SubscriptionListView(ListView):
model = Article
context_object_name = 'article_list'
template_name = 'subscribeapp/list.html'
paginate_by = 5
# 특정 조건을 만족하는 게시글을 가져와야 하기 때문에 queryset 관련 함수를 새로 작성한다.
def get_queryset(self):
# user가 구독하고 있는 프로젝트 찾기
projects = Subscription.objects.filter(user=self.request.user).values_list('project')
# 프로젝트 안 모든 게시글 가져오기
article_list = Article.objects.filter(project__in=projects)
return article_list
이전에 list_fragment.html로 조각화해놓은 것을 그대로 가져온다.
{% extends 'base.html' %}
{% block content %}
<div>
{% include 'snippets/list_fragment.html' with article_list=article_list %}
</div>
{% endblock%}
path('list/', SubscriptionListView.as_view(), name='list'),
header.html에서 버튼을 생성해준다.
<a href="{% url 'subscribeapp:list' %}" class="leebook_header_nav">
<span>Subscription</span>
</a>