1. SubscribeApp 시작
app 시작 및 View 작성
이번에는 어떤 Projecet를 구독할 수 있는 SubscribeApp을 만들어본다. app을 시작하고 기본 View를 만든다. 여기서 적용하는 RedirectView는 View 작업 후 바로 redirect가 일어나도록 해주는 view이다. 구독 후에는 구독 정보 및 버튼 모양만 변경되는 것이지, 별다른 로직이 없기 때문에 RedirectView를 적용한다.
@method_decorator(login_required, 'get')
class SubscriptionView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse('proejctapp:detail', kwargs={'pk': self.request.GET.get('proeject_pk')})
def get(self, request, *args, **kwargs):
return super(SubscriptionView, self).get(request, *args, **kwargs)
Model 작성 : Unique Key 만들기
Subscription model을 만들고 migrate 한다. 구독은 project와 user가 일치해야만 할 수 있는 것으로 정의하였다. 여기서 어떤 속성값을 튜플로 묶어서 Meta 클래스에 정의하면 unique Key로 잡아준다.
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')
migration이 완료된 subscribeapp_subscription 테이블의 DDL 일부
create index subscribeapp_subscription_project_id_7e6a4889
on subscribeapp_subscription (project_id);
create index subscribeapp_subscription_user_id_0c0929e2
on subscribeapp_subscription (user_id);
create unique index subscribeapp_subscription_user_id_project_id_3c6a6a34_uniq
on subscribeapp_subscription (user_id, project_id);
View 로직 작성
get 요청이 왔을 때, Subscription이 토글 방식으로 작동되도록 로직을 작성한다. 여기서 get_object_or_404
메서드는 import 해오는 method로, 찾고자 하는 객체가 있으면 반환하고 없으면 404 에러를 띄워주는 단축 메서드이다.
user, project 정보를 바탕으로 subscription을 가져오고, if문을 사용하여 토글 방식으로 구독/구독 해제 기능을 만든다.
@method_decorator(login_required, 'get')
class SubscriptionView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
return reverse('proejctapp: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)
projectapp/detail.html
프로젝트 상세 화면에서 subscription 접근이 가능하도록 링크를 넣어준다.
{# 중략... #}
<hr>
{# --- #}
<div class="text-center">
{% if user.is_authenticated %}
<a href="{% url 'subscribeapp:subscribe' %}?project_pk={{ target_project.pk }}"
class="btn btn-primary rounded_pill px-4 mb-3" style="border-radius: 20rem;">
Subscribe
</a>
{% endif %}
</div>
{# --- #}
{% include 'snippets/list_fragment.html' with article_list=object_list %}
2. Subscribe 버튼 구현, Field Lookup 과 queryset
Subscribe 버튼 토글 구현
프로젝트 detail 페이지에서 로그인 유저가 해당 프로젝트에 대한 구독 정보가 어떤지 판별하여 구독 중일때와 아닐 때 버튼을 다르게 표시한다. ProjectDetailView의 get_context_data 부분을 수정한다.
self.object, self.request.user를 통해서 project와 user 객체를 가져왔다. 그리고 Subscription.objects.filter()로 user, project에 해당하는 subscription만 가져온 후, get_context_data의 return 부분에 kwargs로 subscription = subscription으로 정보를 넘겨줬다. 이렇게 get_context_data의 반환값 정보가 template 쪽으로 넘어간다.
None : super() 에서 return 할 때 subscription 정보를 받으므로, 반드시 else: 구문을 사용하여 사용자가 로그인 하지 않았을때는 subscription에 None 값이 넘어가도록 해줘야한다.
def get_context_data(self, **kwargs):
project = self.object
user = self.request.user
if user.is_authenticated:
subscription = Subscription.objects.filter(user=user, project=project)
else:
subscription = None
object_list = Article.objects.filter(project=self.get_object())
return super(ProjectDetailView, self).get_context_data(object_list=object_list,
subscription=subscription,
**kwargs)
이제 넘어온 데이터를 바탕으로 버튼 토글 기능을 구현할 수 있도록 proejctapp/detail.html을 다시 수정한다.
SubscriptionListView : Field Lookup
구독 페이지에 들어가면, 사용자가 구독한 projects안에 있는 모든 article들을 보여주도록 SubscriptionListView를 작성한다.
여기서는 object에서 filter 조건을 좀 더 상세히 줄 수 있는 Field Lookup 문법을 적용해본다. Field Lookup은 SQL의 쿼리문을 적용했을 때와 동일한 결과가 나올 수 있도록 하는 django의 문법이다.
https://docs.djangoproject.com/en/3.1/ref/models/querysets/#field-lookups
기본적으로 언더바 2개를 연속으로 사용한다. 위 링크의 공식 문서에 나온 예제를 아래에 기록한다. 아래 예시는 exact에 대한 구문이며 이외에도 like와 유사한 iexact, contains, in, gte, lte 등 다양한 문법들이 있다.
SubscriptionListView 생성
article들의 list가 출력되도록 해야하므로, model = Article으로 설정한다. 그리고 context_object_name='article_list'로 설정해서 이를 template에 넘겨준다.
get_queryset 메서드를 활용해서 template 에 넘겨줄 객체 리스트를 field lookup으로 찾는다. 여기서 values_list는 filter로 가져온 objects 중 특정한 속성을 지정하여 그 값들만 반환해준다.
@method_decorator(login_required, 'get')
class SubscriptionListView(ListView):
model = Article
context_object_name = 'article_list'
template_name = 'subscribeapp/list.html'
paginate_by = 5
def get_queryset(self):
user = self.request.user
projects = Subscription.objects.filter(user=user).values_list('project')
return Article.objects.filter(project__in=projects)
list.html 및 url, header 설정
subscription 페이지 내부에서 article_list를 표시할 수 있도록 list.html을 작성하고 subscriptionapp에서 url을 등록한다.
subscriptionapp/templates/subscriptionapp/list.html
{% extends 'base.html' %}
{% block content %}
{% include 'snippets/list_fragment.html' with article_list=article_list %}
{% endblock %}
-url 등록 코드 생략
header에 subscription/list.html로 이동할 수 있는 link를 추가한다.
<a href="{% url 'subscribeapp:list' %}">
<span>Subscription</span>
</a>
참조
1. 작정하고 장고! Django로 Pinterest 따라만들기 : 바닥부터 배포까지-박형석님 인프런 강의