웹 프로그래밍/[ Django ]

[ Django ] 07. Django relation M:N (profile, follow, paging)

kim.svadoz 2020. 8. 11. 10:04
728x90
반응형

M:N 관계

ex) 의사-환자 모델링

  • models.py
from django.db import models

# Create your models here.
class Doctor(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return f'{self.pk}번 의사 {self.name}'

class Patient(models.Model):
    name = models.CharField(max_length=20)

    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)

    def __str__(self):
        return f'{self.doctor}의 {self.patient}'
  • shell_plus
In [1]: doctor = Doctor.objects.create(name='KIM')

In [2]: patient = Patient.objects.create(name='TOM')

In [3]: doctor
Out[3]: <Doctor: 1번 의사 KIM>

In [4]: patient
Out[4]: <Patient: 1번 환자 TOM>

# reservation을 활용해서 의사와 환자를 연결한다.
In [5]: Reservation.objects.create(doctor=doctor, patient=patient)
Out[5]: <Reservation: 1번 의사 KIM의 1번 환자 TOM>

# 의사 입장에서 예약정보를 가져오기
# 1은 N을 보장할 수 없기 때문에
In [6]: doctor.reservation_set.all()
Out[6]: <QuerySet [<Reservation: 1번 의사 KIM의 1번 환자 TOM>]>

# 환자 입장에서 예약정보를 가져오기
In [7]: patient.reservation_set.all()
Out[7]: <QuerySet [<Reservation: 1번 의사 KIM의 1번 환자 TOM>]>

# 2번환자 생성하고 연결하기
In [8]: patient2 = Patient.objects.create(name='KANG')

In [9]: Reservation.objects.create(doctor=doctor, patient=patient2)
Out[9]: <Reservation: 1번 의사 KIM의 2번 환자 KANG>

In [10]: doctor.reservation_set.all()
Out[10]: <QuerySet [<Reservation: 1번 의사 KIM의 1번 환자 TOM>, <Reservation: 1번 의사 KIM의 2번 환자 KANG>]>

# 의사1의 환자이름
In [11]: for reservation in doctor.reservation_set.all():
    ...:     print(reservation.patient.name)
    ...: 
TOM
KANG

# 의사1의 환자번호
In [12]: for reservation in doctor.reservation_set.all():
    ...:     print(reservation.patient.pk)
    ...: 
1
2
  • 위의 중개모델 말고도 조금 더 쉽게 가져올 수 있는 방법이 존재한다.(ManyToManyField)

image-20200625103231363

# 환자 입장에서 의사 가져오기
In [1]: patient = Patient.objects.get(pk=1)
In [2]: patient.doctors.all()
Out[2]: <QuerySet [<Doctor: 1번 의사 KIM>]>

# 의사 입장에서 환자 가져오기
doctor = Doctor.objects.get(pk=1)
In [10]: doctor.patient_set.all()
Out[10]: <QuerySet [<Patient: 1번 환자 TOM>, <Patient: 2번 환자 KANG>]>

image-20200625103949825

# 이제는 patient_set.all()로 가져올 수 없고 지정한 patients로만 가져올 수 있다.
In [1]: doctor = Doctor.objects.get(pk=1)
In [2]: doctor.patients.all()
Out[2]: <QuerySet [<Patient: 1번 환자 TOM>, <Patient: 2번 환자 KANG>]>

= > relate_name을 쓸거면 reservation 클래스가 필요없다.

image-20200625104225288

In [1]: doctor = Doctor.objects.create(name='KIM')
In [2]: patient = Patient.objects.create(name='TOM')

# 환자 추가
In [3]: doctor.patients.add(patient)
# 환자 제거
In [4]: doctor.patients.remove(patient)

In [5]: doctor.patients.all()
Out[5]: <QuerySet []>

!! 추가 필드가 요구될 시에는 중개모델(ex. Reservaion class)을 만들어줘야한다.!!

  • 언제 related_name이 반드시 필요한가?
    • ?

게시글 좋아요 만들기

image-20200625111435406

# urls.py
path('<int:article_pk>/like/', views.like, name="like"),

# views.py
@login_required
def like(request, article_pk):
    # 특정 게시물에 대한 정보
    article = get_object_or_404(Article, pk=article_pk)
    # 좋아요를 누른 유저에 대한 정보
    user = request.user
    # 사용자가 게시글의 좋아요 목록에 있으면 지우고 없으면 추가한다.
    if user in article.like_users.all():
        article.like_users.remove(user)
    else:
        article.like_users.add(user)
    return redirect('articles:index')

# index.html
{% if user in article.like_users.all %}
<a href="{% url 'articles:like' article.pk %}"> 좋아요 취소 </a>
{% else %}
<a href="{% url 'articles:like' article.pk %}"> 좋아요 </a>
{% endif %}
  • font awesome 사용하기
    • base.html에 Kit code를 붙여넣는다.

image-20200625130756735

  • 좋아요를 하나의 모듈로 만들기
# _like.html
{% if user in article.like_users.all %}
<a href="{% url 'articles:like' article.pk %}"> 좋아요취소<i class="fas fa-thumbs-down"></i> </a>
{% else %}
<a href="{% url 'articles:like' article.pk %}"> 좋아요<i class="fas fa-thumbs-up"></i> </a>
{% endif %}
</div>

<div class="col-lg-2">
{% if user in article.recommend_users.all %}
<a href="{% url 'articles:recommend' article.pk %}"> 추천 취소 </a>
{% else %}
<a href="{% url 'articles:recommend' article.pk %}"> 추천 </a>
{% endif %}

# index.html
## 넣고 싶은 부분에 include를 이용
{% include 'articles/_like.html' %}      

프로필만들기

# urls.py
path('<str:username>/', views.profile, name="profile"),

# views.py
from django.shortcuts import get_object_or_404
from django.contrib.auth import get_user_model

def profile(request, username):
    person = get_object_or_404(get_user_model(), username=username)
    context={
        'person' : person
    }
    return render(request, 'accounts/profile.html', context)

# profile.html
{% extends 'base.html' %}
{% block body %}
<h3>{{ person.username }}</h3>
<!-- 유저가 작성한 모든 게시물 -->
<p>유저가 작성한 게시글들</p>
<ul>
  {% for article in person.article_set.all %}
    <li>{{ article.title }}</li>
    <li>{{ article.content }}</li>
  {% endfor %}
</ul>

<p>유저가 작성한 게시글들</p>
<ul>
  {% for comment in person.comment_set.all %}
    <li>{{ comment.content }}</li>
  {% empty %}
    <p>댓글을 단 적이 없습니다.</p>
  {% endfor %}
</ul>

<p>유저가 좋아요 누른 게시글들</p>
<ul>
  {% for comment in person.like_articles.all %}
    <li>{{ like.content }}</li>
  {% empty %}
    <p>좋아요를 누른 적이 없습니다.</p>
  {% endfor %}
</ul>

{% endblock %}

팔로우하기

  • get_user_model vs AUTH_USER_MODEL
    • 전자는 객체를 반환하기때문에 accounts를 바라보게된다? => 활성화된 객체를 찾아간다.
    • 후자는 스트링값 반환=> migrations -> migrate 과정에서 문자열이기 때문에 원활하게 진행
  • articles에 있는 models.py에서는 AUTH_USER_MODEL을 사용
  • accounts에 있는 models.py에서는 get_user_model을 사용
  • Models.py에서 정의할 때 빼고는 전부 get_user_model을 사용하면된다!!
# admin.py ( accounts )
###### ^^^^^^ 여기서만 예외 ########
from django.contrib.auth.admin import UserAdmin
from .models import User

admin.site.register(User, UserAdmin)
# models.py ( accounts )
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
    followers = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name="followings",
        blanck=True
    )
  • 추가로 forms.py도 커스터마이징해줘야한다!!
# forms.py ( accounts )
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model
class CustomUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        model = get_user_model()
        # fileds = 

# views.py ( accounts )        
from .forms import CustomUserCreationForm

UserCreationForm.Meta를 써주면 기존에 있는 Meta를 쓰기때문에 필드를 생략해도 된다.

image-20200625154026206

# 기존 에서
def signup(request):    
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            auth_login(request, user)
            return redirect('articles:index')
    else:
        form = UserCreationForm()
    context = {
        'form' : form
    }
    return render(request, 'accounts/signup.html', context)

# 이렇게 바꾼다.
def signup(request):    
    if request.method == 'POST':
        form = CustomUserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            auth_login(request, user)
            return redirect('articles:index')
    else:
        form = CustomUserCreationForm()
    context = {
        'form' : form
    }
    return render(request, 'accounts/signup.html', context)

# accounts/urls.py
urlpatterns = [
    path('signup/', views.signup, name="signup"),
    path('login/', views.login, name="login"),
    path('logout', views.logout, name="logout"),
    path('delete/', views.delete, name="delete"),
    path('update/', views.update, name="update"),
    path('password/', views.password, name="password"),
    path('follow/<int:user_pk>/', views.follow, name="follow"),  # 여기에 추가했다
    path('<str:username>/', views.profile, name="profile"), # 문자열 하나만 받을 친구는 밑에 둬야한다.(오류생김)
]

# accounts/views.py
def follow(request, user_pk):
    # person에 담긴 user_pk값을 가진 유저는
    # 프로필의 주인이다.
    # request.user는 나. 요청을 보내온 사용자이다
    person = get_object_or_404(get_user_model(), pk=user_pk)
    if request.user in person.followers.all():
        person.followers.remove(request.user)
    else :
        person.followers.add(request.user)
    return redirect('accounts:profile',person.username)
<!--profile.html-->
{% extends 'base.html' %}
{% block body %}
<h3>{{ person.username }}</h3>
<!-- 팔로우 로직 구현-->
{% if user != person %} <!-- 본인일때는 팔로우 안보이도록-->
{% if user in person.followers.all %}
<a href="{% url 'accounts:follow' person.pk %}">팔로우 취소</a>
{% else %}
<a href="{% url 'accounts:follow' person.pk %}">팔로우</a>
{% endif %}
{% endif %}
...
...
...

DB다 지우고 makemigrations와 migrate한다

페이징 구현

# articles/views.py
from django.core.paginator import Paginator

def index(request):  #index 부분을 수정한다.(paging 추가)
    #embed()
    articles = Article.objects.all()
    # 1. Paginator(전체 리스트, 한 페이지당 개수)
    paginator = Paginator(articles, 3)
    # 2. 몇 번째 페이지를 보여줄 것인지 GET으로 받
    # 'articles/?page=3'
    page = request.GET.get('page')
    # 해당하는 페이지의 게시글만 가져오기
    articles = paginator.get_page(page)
    context = {
        'articles': articles
    }
    return render(request, 'articles/index.html', context)
  • index.html
{% extends 'base.html' %}
{% block body %}
<h1>메인 페이지 입니다.</h1>
<hr>
<a href="{% url 'articles:create' %}">[CREATE]</a>
<hr>
<p>{{ articles.all|length }}개의 글</p>
<hr>
{% for article in articles %}
 <p>{{ article.pk }}번째 글</p>
 <h2>{{ article.title }}</h2>
<p>좋아요 개수 : {{ article.like_users.all|length }}</p>
<p>추천 개수 : {{ article.recommend_users.all|length }}</p>
<p>댓글 개수 : {{ article.comment_set.all|length }}</p>

<div class="container">
  <div class="row">
    <div class="col-lg-2">
      {% include 'articles/_like.html' %}
    </div>

    <div class="col-lg-2">
      <a href="{% url 'articles:detail' article.pk %}">[DETAIL]</a>
    </div>
  </div>
</div>
 <hr>

{% endfor %}
{% for num in articles.paginator.page_range %}
<a href="{% url articles:index' %}?page={{ num }}">{{ num }}</a>
{% endfor %}
{% endblock %}

아이디 2개를 만들어 주소로 내 아이디 말고 다른 사람 아이디를 들어가보면

85811879-2ada0800-b79a-11ea-9c99-8b654ea9ddf0

728x90
반응형