[Python_django]_4_ 인스타 앱 구현 (M:N관계 기반 댓글등록Comment, 좋아요기능)

Hyejin Beck·2024년 1월 21일

Python

목록 보기
16/23

Python 부트캠프(멀티잇 데이터 분석&엔지니어링 캠프) 에서 배운 django수업을 기반으로 직접 2023년 8월경 다른 블로그에 작성한 글을 가져왔습니다.

게시글아래 댓글창 구현

Comment모델링

1:N 연결

posts>models.py

class Comment(models.Model): 
    content = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    # 1:N 연결
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    

db번역

python manage.py makemigrations
python manage.py migrate

댓글창 모델폼

postsforms.py


from .models import Post , Comment # models.py에 있는 class2가지



class CommentForm(forms.ModelForm): 
    class Meta: 
        model = Comment 
        fields = ('content',) # 하나만 입력시에도 , 를 꼭 붙혀야! 

postsviews.py

from .forms import PostForm, CommentForm #모델폼 만든거 추가 


def index(request): 
    # posts = Post.objects.all() 
    # 모든 값을 보여줘 
    posts = Post.objects.all().order_by('-id')
    # id값(작성순서)를 역순으로 보여줘
    # 최신작성된 거 먼저 보여줘
    comment_form = CommentForm()

    context = {
        'posts' : posts, 
        'comment_form' : comment_form
    }

_card.html
맨 위

{% load bootstrap5 %} 

맨 하단

      <div class="card-footer">
        <form action="" method="POST">
          {% csrf_token %}
          {% bootstrap_form comment_form %}
          <input type="submit">
        </form>
      </div>

댓글 입력값 DB저장

_card.html

<form action="{% ulr 'posts:comment_create' post_id=post.id %}" method="POST">

post_id = 변수화
post.id 는
post가 가지고 있는 id 번호

postsurls.py : 경로 생성

path('<int:post_id>/comments/create/', views.comment_create, name='comment_create'),

postsviews.py : 경로를 실행할 기능
작성자가 comment_form에 작성된 상태로 입력되면,
그 입력한 데이터를 처리


def comment_create(request, post_id): 
    comment_form = CommentForm(request.POST) 
    # 작성자가 댓글칸에 입력값(request.POST)을 CommentForm빈 폼에 기재한 것을 
    # comment_form이라고 한다. 

    if comment_form.is_valid():
        comment = comment_form.save(commit=False)
        # commit 은 데이터베이스에 저장하는 단위중 하나 
        # commit=False는 저장하기전까지만 해! 

        # comment에는 로그인유저 (댓글작성자) 와 작성한내용이 담긴다. 
        comment.user = request.user 
        # 그리고 그 유저가 작성한 내용 
        post = Post.objects.get(id=post_id)
        comment.post = post

        comment.save()
        return redirect('posts:index')

이제 로그인된 상황에서 댓글작성해보자

로그아웃된 상태에서 작성하려면, error나온다.

Anonymouse 어쩌구

제출 누르면 db로 보내지긴 한다. 아직 저장값을 돌려받진 않는다!

댓글입력값 출력

_card.html
제일 하단

      <hr>
      {% for comment in post.comment_set.all %}
      <!--커맨트가 가지고있는 모든 집합들을 하나하나-->
      <li>{{comment.user}}:{{comment.content}}</li>
      {% endfor %}
    </div>
  </div>

로그인아웃->댓글입력시error발생

애초에 로그인하지않으면, 댓글창 안보이고
애초에 로그인하지 않으면, 실행할수 없도록 해보자.

로그아웃상태시, 댓글창 안보이게

_card.html

      <div class="card-footer">
        {% if user.is_authenticated %}
        <form action="{% url 'posts:comment_create' post_id=post.id %}" method="POST">
          {% csrf_token %}
          {% bootstrap_form comment_form %}
          <input type="submit">
        </form>
      </div>
      <hr>
      {% endif %}

아예 실행안되도록

views.py

from django.contrib.auth.decorators import login_required




@login_required
def create(request): 

@login_required
def comment_create(request, post_id): 

M:N 구조에 대해

M의 데이터가 저장되어있는 테이블
N의 데이터가 저장되어있는 테이블

그리고 M기준으로 N을 가지고있는 연결된 테이블 하나 더 추가

잠깐 MN이라는 폴더를 만들어보자

M:N이라는 폴더 생성후 VS코드로 들어가자

python -m venv venv
source venv/bin/activate
pip install django
django-admin startproject MN .
django-admin startapp movies

settings.py

INSTALLED_APPS = [
	
    "movies",
]

models.py

from django.db import models

# Create your models here.
class Actor(models.Model): 
    pass

class Movie(models.Model): 
    pass

이제 각자 가지고있는걸 추가해주자

class Actor(models.Model): 
    name = models.CharField(max_length=100)

class Movie(models.Model): 
    title = models.CharField(max_length=100)

db접근

python manage.py makemigrations
python manage.py migrate
python manage.py shell  
>>> from movies.models import Actor
>>> a = Actor() 

a를 누르면

>>> a
<Actor: Actor object (None)>

'정우성'을 a 에 저장해보자

>>> a = Actor(name='정우성')
>>> a.save()
>>> a
<Actor: Actor object (1)>

movies에도 뭔가 넣어보자

from movies.models import Movies 
m = Movie(title='더킹')
m.save()
m

나갈땐

>>> exit()

django seed

이렇게 하나하나 입력하기 귀찮아서 외부 라이브러리 이용해보자.

설치

pip install django-seed
pip install psycopg2-binary   # 알아서설치안되니까 이것도

settins.py

INSTALLED_APPS = [

    "django_seed",
]

10개의 가상데이터를 입력해주자

python manage.py seed movies --number=10

python manage.py shell
>>from movies.models import Actor, Movie

models.py

class Movie(models.Model): 
    title = models.CharField(max_length=100)
    actors = models.ManyToManyField(Actor)

db접근

python manage.py makemigrations
python manage.py migrate

다시 shell

python manage.py shell
>>> from movies.models import Actor, Movie
>>> a1 = Actor.objects.get(id=1)
>>> m1 = Movie.objects.get(id=1)
>>> a1
<Actor: Actor object (1)>
>>> m1
<Movie: Movie object (1)>
>>> m1.actors
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x7fb9c8272100>
>>> m1.actors.all()
<QuerySet []>
>>> m1.actors.add(a1)
>>> m1.actors.all()
<QuerySet [<Actor: Actor object (1)>]>

배우
name
영화
title
A1
B2
C3
D4
>>> m2 = Movie.objects.get(id=2)
>>> m2.actors
<django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager object at 0x7fb9c8272760>
>>> a1 = Actor.objects.get(id=1)
>>> m2.actors.add(a1)

models.py

class Movie(models.Model): 
    title = models.CharField(max_length=100)
    actors = models.ManyToManyField(Actor, related_name='movies')

모델링 수정되었으니

python manage.py makemigrations
python manage.py migrate

models.py

from django.db import models

# Create your models here.
class Actor(models.Model): 
    name = models.CharField(max_length=100)
    # movie_set = 생성됨 

class Movie(models.Model): 
    title = models.CharField(max_length=100)
    actors = models.ManyToManyField(Actor, related_name='movies')
    # related_name : 기본적으로 관계설정을 하는 위치(Movie)의 
    #               반대편 모델(Actor)에 생기는 기본 컬럼 이름 (Movie_set)
    #               을 movies로 변경 

models.py

class Actor(models.Model): 
    name = models.CharField(max_length=100)
    # movie_set = 생성되나, 아래 related_name = movies로 된 순간
    # movies 
    # 로 이름만 바뀐다. 

class Movie(models.Model): 
    title = models.CharField(max_length=100)
    actors = models.ManyToManyField(Actor, related_name='movies')
    # related_name : 기본적으로 관계설정을 하는 위치(Movie)의 
    #               반대편 모델(Actor)에 생기는 기본 컬럼 이름 (Movie_set)
    #               을 movies로 변경 

다시 shell

python manage.py shell

>>> from movies.models import Actor, Movie
>>> a1 = Actor.objects.get(id=1)
>>> a1.movies.all()
<QuerySet [<Movie: Movie object (1)>, <Movie: Movie object (2)>]>
>>> m1 = Movie.objects.get(id=1)
>>> m1.actors.all()
<QuerySet [<Actor: Actor object (1)>]>
>>> 

다시 M:N

hospital이라는 새로운 앱을 만들자

django-admin startapp hospital 

그리고 settings.py에 해당app이름 추가 

models.py

from django.db import models

# Create your models here.

class Doctor(models.Model): 
    name = models.CharField(max_length=10)

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

class Reservation(models.Model): 
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    date = models.DateField()

models.py

from django.db import models

# Create your models here.

class Doctor(models.Model): 
    name = models.CharField(max_length=10)
    # reservation_set 이 생김 (ForeignKey로 인해서)

class Patient(models.Model): 
    name = models.CharField(max_length=10)
    # reservation_set 이 생김 (ForeignKey로 인해서)
    doctors = models.ManyToManyField(Doctor, through='Reservation')

class Reservation(models.Model): 
    doctor = models.ForeignKey(Doctor, on_delete=models.CASCADE)
    patient = models.ForeignKey(Patient, on_delete=models.CASCADE)
    date = models.DateField(auto_now_add=True)

db접근

python manage.py makemigrations
python manage.py migrate

10개의 가상데이터를 넣어보자

python manage.py seed hospital --number=10 

다시 shell

python manage.py shell
>>> from hospital.models import Doctor, Patient, Reservation

좋아요기능

어떤 유저가 좋아요를 눌렀는지 user_id
유저가 좋아요 누른 게시글은 뭔지 post_id
그리고 좋아요 취소하면 다 삭제될수 있는지

모델링

두 앱 연결하기

posts의 models.py
accounts의 models.py

postsmodels.py

class Post(models.Model): 
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # image = models.ImageField(upload_to='image/%Y/%m')
    # 그리고 이걸 추가 
    image = ResizedImageField(
        size=[500,500],
        crop=['middle', 'center'], 
        upload_to='image/%Y/%m', 
    )  
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    # user 추가함으로서, accounts의 Modes.py와 1:N 연결 
    # user_id가 생성된거다. 

    like_users = models.ManyToManyField(settings.AUTH_USER_MODEL)

error 발생

지금 user , like_user 둘다
accounts의 models.py 에서 class User 를 불러옴으로써, post_set 으로 둘 다 쓰고 있어서,
에러

postsmodels.py
하나를 이름 바꿔준다.

    like_user = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='like_posts')

ForeighnKey는 1:N 이라서 on_delete가 필요 (부모관계)

하나가 삭제되면 끊기니께...

ManyToMany는 M:N 은 불필요 (동등관계)

하나가 삭제되도 또하나가 있을테니께,...

db재접근

python manage.py makemigrations
python manage.py migrate

좋아요 버튼

bootstrap의 아이콘

하단의 Install 의 CDN 코드의 링크

basem.html
하단 link 추가

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
</head>

_card.html : 버튼 디자인 생성

      <a href="{% url 'posts:like' post_id=post.id %}">
        <i class="bi bi-heart"></i>
      </a>

postsurls.py

path('<int:post_id>/like/',views.like, name='like'),

views.py : 버튼 기능 구현

@login_required # 로그인해야만 작동 
def like(request, post_id): 

    # 조아요 버튼 누른 유저 = 로그인한사람 
    user = request.user 
    post = Post.objects.get(id=post_id)

    # post 내가 좋아요 누른 게시글 
    # like_users 좋아요 누른 사람
    post.like_users.add(user)
    # post.like_posts.add(post)
    
    # 사실 이 두 개는 동일하다. 
    # 유저에 게시글을 넣어주나, 
    # 게시글에 유저를 넣어주나, 
    # 그냥 연결 연결 
    return redirect('posts:index')

db체크


좋아요 버튼을 눌렀을때 error나면 안됌

좋아요->취소 관련 db저장

postsviews.py

@login_required # 로그인해야만 작동 
def like(request, post_id): 

    # 좋아요 버튼을 누른 유저 
    user = request.user 
    post = Post.objects.get(id=post_id)

    # 좋아요 버튼을 누른경우, 
    # 이 유저가, 좋아요누른사람들 목록에 있나요? 
    if post in user.like_posts.all(): 
        post.like_users.remove(user)
        # user.like_posts.remove(post)와 같다. 
    
    # 좋아요 버튼을 아직 안누른 경우, 
    else: 
        post.like_users.add(user)
        # user.like_posts.add(post)와 같다. 

    return redirect('posts:index')

M:N 은 remove와 add 사용 후 save() 안해도 됨
새로운 테이블을 따로 만들어 저장하기 때문에
save를 안해도 자동저장된다.

좋아요->취소 시각적 구현

_card.html

<div class="card-body">
      <!--<h5 class="card-title">Card title</h5>-->
      <a href="{% url 'posts:like' post_id=post.id %}">
        <!--좋아요 눌렀으면 하트색상변경-->
        {% if post in user.like_posts.all  %} 
        <i class="bi bi-heart-fill"></i>
        <!--한번더 누르면 좋아요 취소-->
        {% else %}
        <i class="bi bi-heart"></i>
        {% endif %}
      </a>


좋아요 누른 유저 갯수


또는

    <div class="card-body">
      <!--<h5 class="card-title">Card title</h5>-->
      <a href="{% url 'posts:like' post_id=post.id %}">  
        <!--좋아요 눌렀으면 하트색상변경-->
        {% if post in user.like_posts.all %} 
        <i class="bi bi-heart-fill"></i>
        <!--한번더 누르면 좋아요 취소-->
        {% else %}
        <i class="bi bi-heart"></i> 
        {% endif %}
      </a>{{post.like_users.all|length}}명이 좋아합니다.

좋아요 디테일 수정

bootstrap의 text를 이용해서 좀 더 글자 꾸미기

_card.html

      <a href="{% url 'posts:like' post_id=post.id %}" class="text-reset text-decoration-none" >  



        <i class="bi bi-heart-fill" style="color: orchid;"></i>


좋아요 누르면, 최신게시물로 이동되서 좀 짜증
근데 이건 JavaScript코드에 대해 배워야함
이건 다음주에 개선

팔로우

모델링

accountsmodels.py

class User(AbstractUser): 
    profile_image = ResizedImageField(
        size=[500,500], 
        crop=['middle','center'],
        upload_to='profile',
    ) 
    # post_set = 생성
    # like_posts = 생성 
    followings = models.ManyToManyField('self', related_name='followers', symmetrical=False)
    # 여기서 self 는 자기자신 user이긴한데, user라고 적으면 자기자신재귀가 빠져서 self라고 
    # 여기서 makemigration,migrate 작업하게되면 user_set이 db 컬럼으로 생성되는데, 
    # 우리는 헷갈리니까 자동생성되는 이 이름 수정할거야. followers 로! 
    # symmetrical 은 대칭이란 뜻으로, 
    # 유저 <---둘다팔로우---> 유저  로 쌍방향이 아닌, 
    # 유저 <--한사람만팔로우-- 유저  로 한방향으로 ! 

db업데이트

python manage.py makemigrations
python manage.py migrate

기능구현

profile.html

            <!--user는 로그인 유저와 
            user_info는 보고있는 프로필페이지 유저 
            가 다르다면, -->
            {% if user != user_info%} 
            <div class="col-4"><a href="">팔로우</a></div>
            {% endif %}


내 프로필페이지를 보고있자니,
팔로우 버튼이 보여지지 않는다.
나니까!
나를 어떻게 팔로우해!!

            <!--user는 로그인 유저와 
            user_info는 보고있는 프로필페이지 유저 
            가 다르다면, -->
            {% if user != user_info%} 
            <div class="col-4">
                <a href="" class="btn btn-secondary btn-sm">팔로우</a>
            </div>
            {% endif %}
            <!--user는 로그인 유저와 
            user_info는 보고있는 프로필페이지 유저 
            가 다르다면, -->
            {% if user != user_info%} 
            <div class="col-4">
                <a href="{% url 'accounts:follow' username=user_info.username %}" class="btn btn-secondary btn-sm">팔로우</a>
            </div>
            {% endif %}

accountsurls.py

    path('<str:username>/follow',views.follow,name='follow'),

accountsviews.py

from django.contrib.auth.decorators import login_required





@login_required  # 로그인한사람만 보여지게
def follow(request,username): 
    User = get_user_model()

    me = request.user  # 현재 로그인한사람(나자신)
    # you를 하려면 get_user_model()을 윗칸에다 불러와서 저정해줘야함 
    you = User.objects.get(username=username)
    # 지금 프로필사진 누른 계정 
    
    # 팔로잉이 이미 되어있는 경우 
      # 내가 그 계정의 팔로워들 목록에 있나요? 
      # follower 는 따르는 사람 목록 
      # following 은 따르는 사람 
    if you in me.followings.all(): 
       # if me in you.followers.all() : 와 같다. 
        me.followings.remove(you)
    else: 
        me.followings.add(you)
    return redirect('accounts:profile', username=username)

profile.html

            {% if user != user_info%} 
            <div class="col-4">
                {% if user in user_info.followers.all %} <!--if user_info in user.이하 동일-->
                <a href="{% url 'accounts:follow' username=user_info.username %}" class="btn btn-primary btn-sm">팔로잉</a>
                {% else %}
                <a href="{% url 'accounts:follow' username=user_info.username %}" class="btn btn-secondary btn-sm">팔로우</a>
                {% endif %}
            </div>
            {% endif %}

내 계정일때, 내 프로필 페이지

내 계정일때, 다른사람 프로필 페이지

그리고 다른사람 프로필페이지 [팔로우]누르면 -> [팔로잉]-> [팔로우]

게시물 갯수

현재 1:N 과 M:N 형성됨

profile.html

        <div class="row">
            <div class="col">게시물 {{user_info.post_set.all|length}} </div>
            <div class="col">팔로워 {{user.info.followers.all|length}}</div>
            <div class="col">팔로우 {{user_info.followings.all|length}}</div>
        </div>

postsforms.py

class PostForm(forms.ModelForm): 
    class Meta: 
        model = Post 
        #fields = '__all__'
        exclude = ('user','like_users',)

create 누르게되면


profile
데이터기반 스토리텔링을 통해 인사이트를 얻습니다.

0개의 댓글