Django(14) - 좋아요 기능 구현하기
2022, Oct 24
좋아요 기능 구현하기
예시- 의사와 환자 모델 Django ManyToManyField
hospitals/models.py
class Patient(models.Model):
# ManyToManyField 작성
doctors = models.ManyToManyField(Doctor)
name = models.TextField()
def __str__(self):
return f'{self.pk}번 환자 {self.name}'
# Reservation Class 주석 처리
terminal
$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py shell_plus
의사 1명과 환자 2명 생성
doctor = Doctor.objects.create(name='alice')
patient1 = Patient.objects.create(name='carol')
patient2 = Patient.objects.create(name='dane')
예약 생성(환자가 의사에게 예약)
# patient1이 doctor1에게 예약
patient1.doctors.add(doctor1)
# patient1 - 자신이 예약한 의사목록 확인
patient1.doctors.all()
<QuerySet [<Doctor: 1번 의사 alice>]>
# doctor1 - 자신의 예약된 환자목록 확인
doctor1.patient_set.all()
<QuerySet [<Patient: 1번 환자 carol>]>
예약 생성(의사가 환자를 예약)
doctor1이 patient2을 예약
# doctor1이 patient2을 예약
doctor1.patient_set.add(patient2)
# doctor1 - 자신의 예약 환자목록 확인
doctor1.patient_set.all()
<QuerySet [<Patient: 1번 환자 carol>, <Patient: 2번 환자 dane>]>
# patient1, 2 - 자신이 예약한 의사목록 확인
patient1.doctors.all()
<QuerySet [<Doctor: 1번 의사 alice>]>
patient2.doctors.all()
<QuerySet [<Doctor: 1번 의사 alice>]>
예약 취소하기(삭제)
- .remove()로 삭제
# doctor1이 patient1 진료 예약 취소
doctor1.patient_set.remove(patient1)
doctor1.patient_set.all()
<QuerySet [<Patient: 2번 환자 harry>]>
patient1.doctors.all()
<QuerySet []>
# patient2가 doctor1 진료 예약 취소
patient2.doctors.remove(doctor1)
patient2.doctors.all()
<QuerySet []>
doctor1.patient_set.all()
<QuerySet []>
doctor1.patient_set.all()
<QuerySet []>
‘related_name’ argument
- target model이 source model을 참조할 때 사용할 manager name
- ForeignKey()의 related_name과 동일
class Patient(models.Model):
# ManyToManyField - related_name 작성
doctors = models.ManyToManyField(Doctor, related_name='patients')
name = models.TextField()
def __str__(self):
return f'{self.pk}번 환자 {self.name}'
‘through’ argument
- through 설정 및 Reservation Class 수정
- 예약 정보에 증상과 예약일이라는 추가 데이터가 생김
class Patient(models.Model):
doctor = models.ManyToManyField(Doctor, through='Reservation')
name = models.TextField()
def __str__(self):
return f'{self.pk}번 환자 {self.name}'
class Reservation(models.Model):
doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
symptom = models.TextField()
reserved_at = models.DateTimeField(quto_now_add=True)
def __str__(self):
return f'{self.doctor.pk}번 의사의 {self.patient.name}번 환자'
정리
- M:N 관계로 맺어진 두 테이블에는 변화가 없음
- Django의 ManyToManyField는 중개 테이블을 자동으로 생성함
- Django의 ManyToManyField는 M:N 관계를 가진 모델 어디에 위치해도 상관 없음
- 대신 필드 작성 위치에 따라 참조와 역참조 방향을 주의할 것
- 만약 테이블에서 예약정보들을 더 기록하는 형태가 된다면 중개 모델을 만들어서 through옵션을 만들어야함
좋아요 기능 구현
- DB 좋아요 기록할 것인지?
- Article(M) - User(N)
- Article은 0명 이상의 User에게 좋아요를 받는다.
- User는 0개 이상의 글에 좋아요를 누를 수 있다.
- 로직
- 상세보기 페이지에서 좋아요 링크를 누르면(URL : /articles/
<int:pk>
/like/) - 좋아요를 DB에 추가하고(add 메서드) 다시 상세보기 페이지로 redirect
- 상세보기 페이지에서 좋아요 링크를 누르면(URL : /articles/
articles > models.py
# 1. 모델 설계 (DB 스키마 설계)
class Article(models.Model):
title = models.CharField(max_length=20)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
image = ProcessedImageField(upload_to='images/', blank=True,
processors=[ResizeToFill(400, 300)],
format='JPEG',
options={'quality': 80})
image_thumbnail = ProcessedImageField(
upload_to = 'images/', blank=True,# settings.py 원본 ImageField 명
processors = [Thumbnail(100, 100)], # 처리할 작업목록
format = 'JPEG', # 최종 저장 포맷
options = {'quality': 60}) # 저장 옵션
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, null=True)
# 이 부분을 추가, 여기서 related_name을 설정하지 않으면 같은 모델을 참조하는 상황에서 문제가 생기기 때문에 역참조설정을 꼭 해줘야한다.
like_users = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="like_articles")
terminal
$ python manage.py makemigration
$ python manage.py migrate
articles > urls.py
from django.urls import path
from . import views
app_name = 'articles'
urlpatterns = [
path('', views.index, name='index'),
path('create/', views.create, name='create'),
path('<int:pk>/', views.detail, name='detail'),
path('<int:pk>/update/', views.update, name='update'),
path('<int:pk>/delete/', views.delete, name='delete'),
path('<int:pk>/comments/', views.comment_create, name='comment_create'),
path('<int:article_pk>/comments/<int:comment_pk>/delete/', views.comment_delete, name='comment_delete'),
path('<int:pk>/like/', views.like, name='like'),
]
views.py
@login_required
def like(request, pk):
article = Article.objects.get(pk=pk)
# 만약에 로그인한 유저가 이 글을 좋아요를 눌렀다면,
# like_users.all() 안에 그 안에 유저가 있으면 (apple 에 a가 포함되어있나? 정도 느낌)
if request.user in article.like_users.all():
# 좋아요 삭제
article.like_users.remove(request.user)
else:
# 좋아요 추가
article.like_users.add(request.user)
# 상세 페이지로 redirect
return redirect('articles:detail', pk)
articles > detail.html
{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% block body %}
<h1>{{ article.title }}</h1>
<h2>{{ article.pk }}번 게시글</h2>
<h3><a href="{% url 'accounts:detail' article.user.pk %}">{{ article.user.username }}</a></h3>
<p>{{ article.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
{{ article.updated_at|date:"y-m-d D" }}</p>
{% if request.user in article.like_users.all %}
<a class="btn btn-secondary" href="{% url 'articles:like' article.pk %}">
<i class="bi bi-balloon-heart-fill"></i> 좋아요 취소</a>
{% else %}
<a class="btn btn-danger" href="{% url 'articles:like' article.pk %}">
<i class="bi bi-balloon-heart"></i> 좋아요</a>
{% endif %}
<span>{{ article.like_users.count }}</span>
<p>작성자: {{ article.user }}</p>
<p>{{ article.content }}
</p>