Django 09

·2023년 9월 14일
0

웹 프로젝트

목록 보기
3/18

팔로워 / 팔로우 구현

추가 기능 중 팔로워, 팔로우 기능을 구현하는 과제가 있어 구현해보았다.

우선 팔로워 팔로우는 둘 다 유저 모델이고 M : N 관계이니 모델 내의 속성 중 하나로 ManyToManyField 를 통해 참조하게 넣어주자.

user/models.py

class UserModel(AbstractUser):
    class Meta:
        db_table = "my_user"

    nickname = models.CharField(max_length=256, default='')
    image = models.ImageField(upload_to='', blank=True, null=True)
    # 새로 추가한 속성
    follow = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followee') 

이후 views.py 에서 클릭한 유저가 내 follow 에 있다면 follow 목록에서 지우고, 없다면 add 해주도록 user_follow 함수를 만들어준다.

@login_required(login_url='/login/')
def user_follow(request, id):
    me = request.user
    click_user = UserModel.objects.get(id=id)
    if me in click_user.followee.all():
        click_user.followee.remove(me)
    else:
        click_user.followee.add(me)
    return redirect('/user')

팔로우 / 팔로우 취소창에서 views.user_follow 를 처리하도록 url 에도 연결해준 다음

user/urls.py

urlpatterns = [
    path('', views.home, name='home'),
    path('signup/', views.signup, name='signup'),
    path('login/', views.login, name='login'),
    path('logout/', views.logout, name='logout'),
    path('test/', views.test, name='test'),
    path('mypage/<int:id>', views.MyPage, name='myPage'),
    path('mypage/update/<int:id>', views.update_profile, name='update-profile'),
    path('myfeed/<int:id>', views.Myfeed, name='my-feed'),
    # 새로 추가한 부분
    path('user/', views.user_view, name='user-view'),
    path('user/follow/<int:id>/', views.user_follow, name='follow'),
]

user/ 에서 유저 리스트를 get 으로 보여준다.

user/views.py

@login_required(login_url='/login/')
def user_view(request):
    user_list = UserModel.objects.all().exclude(username=request.user.username)
    return render(request, 'user/follow.html', {'user_list': user_list})

그런 다음 유저 리스트를 보여줘 팔로우 / 팔로우 취소를 한 번에 할 수 있는 html 을 만들어준 뒤 렌더링 해주면 팔로우 / 팔로우 취소창이 완성된다.

<!-- templates/user/user_list.html -->
{% extends 'base.html' %}
{% block title %}
사용자 리스트
{% endblock %}

{% block content %}
<div class="container timeline-container">
    <div class="row">
        <!-- 왼쪽 컬럼 -->
        {% if request.user.is_authenticated %}
        <div class="profile_card" style="margin-top: 80px; margin-left: 50px; margin-right: 30px;">
            {% if request.user.image %}
            <div>
                <img src="{{ request.user.image.url }}" class="img">
            </div>
            {% endif %}
            <div class="textBox">
                <div class="textContent">
                    <p class="h1" style="margin-top: 10px;">{{ request.user.nickname }}</p>
                </div>
                <p class="p">@{{ request.user.username }}</p>
                <div>
                </div>
            </div>
        </div>
        {% endif %}
        <!-- 오른 쪽 컬럼-->
        <div class="col-md-7" style="margin: 70px auto 10px auto;">
            <div class="row">
                <div class="alert alert-success" role="alert" style="margin-bottom: 10px;">
                    나를 팔로우 하는 사람 수 : {{ user.followee.count }} 명 / 내가 팔로우 하는 사람 수 : {{ user.follow.count }} 명
                </div>
            </div>
            <div class="row">
                <!-- 사용자 리스트 반복문 -->
                {% for ul in user_list %}
                <div class="card">
                    <div class="card-body">
                        <h5 class="card-title">{{ ul.username }}</h5>
                        <h6 class="card-subtitle mb-2 text-muted">{{ ul.email }}</h6>
                        <p class="card-text">
                            팔로잉 {{ ul.follow.count }} 명 / 팔로워 {{ ul.followee.count }} 명
                        </p>
                        {% if ul in user.follow.all %}
                        <a href="/user/follow/{{ ul.id }}" class="card-link">[팔로우 취소]</a>
                        {% else %}
                        <a href="/user/follow/{{ ul.id }}" class="card-link">[팔로우]</a>
                        {% endif %}
                    </div>
                </div>
                <hr>
                {% endfor %}
            </div>
        </div>
        <div class="col-md-2"></div>
    </div>
</div>
{% endblock %}

좋아요 기능 구현

팔로우 / 팔로우 취소가 있으면 좋아요 기능도 있어야하지 않을까 싶어서 구현해봤다.

우선 feed/models.py 의 Feed 에 like(좋아요 수) 를 추가해준 뒤 user/models.py 의 UserModel 에 liked_feed(좋아요 누른 피드) 를 fk 로 추가해준다.

  • 좋아요 누른 피드와 유저는 M : N 관계이니 ManyToManyField 로 추가해준다.

feed/models.py

class Feed(models.Model):
    class Meta:
        db_table = "feed"

    # user값이 들어온다면 null을 지워줘야함
    author = models.ForeignKey(UserModel, on_delete=models.CASCADE)
    content = models.CharField(max_length=256)
    title = models.CharField(max_length=256, default='')
    image = models.ImageField(upload_to='', null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    # 좋아요 수의 디폴트는 0개
    like = models.IntegerField(default=0)

user/models.py

class UserModel(AbstractUser):
    class Meta:
        db_table = "my_user"

    nickname = models.CharField(max_length=256, default='')
    image = models.ImageField(upload_to='', blank=True, null=True)
    follow = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='followee')
    # 좋아한 게시물이 없을 수도 있으니 null 을 허용해준다
    liked_feed = models.ManyToManyField("feed.Feed", default=None, null=True)
  • 처음에 from feed/models import Feed 로 피드를 참조하게 하려 했는데 circular independency 에러가 났다. 이름에서 알 수 있듯이 참조가 순환 구조로 되어 결국 정확한 참조를 하지 못하는 에러이다.
    이럴 때에는 모델을 import 하지 말고 "feed.Feed" 처럼 문자열로 써주자.

이렇게 추가해준 뒤에는 feed/views.py 에서 좋아요를 눌렀을 시 실행되는 함수를 만들어준다. liked_feed 에 누른 피드가 없으면 add() 하고, 있으면 remove() 할 것이다. 더하여 피드의 좋아요 수 또한 변화시킬 것이다.

user/views.py

def feed_like(request, id):
    re_address = reverse('read', args=(id,))
    current_feed = Feed.objects.get(id=id)
    user = request.user
    # 피드의 좋아요 수가 0 이하로는 떨어지지 않게 방지
    if current_feed.like < 0:
        current_feed.like = 0
        current_feed.save()
	
    # 현재 피드가 유저의 좋아한 피드에 없으면
    if current_feed not in user.liked_feed.all():
        user.liked_feed.add(current_feed)
        current_feed.like += 1
        current_feed.save()
    else:
        user.liked_feed.remove(current_feed)
        current_feed.like -= 1
        current_feed.save()
    print(current_feed.like)

    return redirect(re_address)

이 뒤 피드 상세 페이지에 유저의 liked_feed 에 피드가 있으면 체크박스가 checked 로, 없으면 그냥 보여준 뒤 좋아요 클릭시 처리되는 페이지와 함수가 나타나게 하는 부분을 추가해주면 된다.

templates/feed/detail.html

<!-- 좋아요 버튼 -->
        {% if feed in request.user.liked_feed.all %}
            <div class="heart-container" title="Like" style="margin-bottom: -40px;">
                <input type="checkbox" class="checkbox" id="Give-It-An-Id" checked onclick="location.href='/feed/read/like/{{ feed.id }}'" method="post">
                <div class="svg-container">
                    <svg viewBox="0 0 24 24" class="svg-outline" xmlns="http://www.w3.org/2000/svg">
                        <path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Zm-3.585,18.4a2.973,2.973,0,0,1-3.83,0C4.947,16.006,2,11.87,2,8.967a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,11,8.967a1,1,0,0,0,2,0,4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,22,8.967C22,11.87,19.053,16.006,13.915,20.313Z">
                        </path>
                    </svg>
                    <svg viewBox="0 0 24 24" class="svg-filled" xmlns="http://www.w3.org/2000/svg">
                        <path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Z">
                        </path>
                    </svg>
                    <svg class="svg-celebrate" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
                        <polygon points="10,10 20,20"></polygon>
                        <polygon points="10,50 20,50"></polygon>
                        <polygon points="20,80 30,70"></polygon>
                        <polygon points="90,10 80,20"></polygon>
                        <polygon points="90,50 80,50"></polygon>
                        <polygon points="80,80 70,70"></polygon>
                    </svg>
                </div>
            </div>
        {% else %}
        <div class="heart-container" title="Like" style="margin-bottom: -40px;">
            <input type="checkbox" class="checkbox" id="Give-It-An-Id" onclick="location.href='/feed/read/like/{{ feed.id }}'" method="post">
            <div class="svg-container">
                <svg viewBox="0 0 24 24" class="svg-outline" xmlns="http://www.w3.org/2000/svg">
                    <path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Zm-3.585,18.4a2.973,2.973,0,0,1-3.83,0C4.947,16.006,2,11.87,2,8.967a4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,11,8.967a1,1,0,0,0,2,0,4.8,4.8,0,0,1,4.5-5.05A4.8,4.8,0,0,1,22,8.967C22,11.87,19.053,16.006,13.915,20.313Z">
                    </path>
                </svg>
                <svg viewBox="0 0 24 24" class="svg-filled" xmlns="http://www.w3.org/2000/svg">
                    <path d="M17.5,1.917a6.4,6.4,0,0,0-5.5,3.3,6.4,6.4,0,0,0-5.5-3.3A6.8,6.8,0,0,0,0,8.967c0,4.547,4.786,9.513,8.8,12.88a4.974,4.974,0,0,0,6.4,0C19.214,18.48,24,13.514,24,8.967A6.8,6.8,0,0,0,17.5,1.917Z">
                    </path>
                </svg>
                <svg class="svg-celebrate" width="100" height="100" xmlns="http://www.w3.org/2000/svg">
                    <polygon points="10,10 20,20"></polygon>
                    <polygon points="10,50 20,50"></polygon>
                    <polygon points="20,80 30,70"></polygon>
                    <polygon points="90,10 80,20"></polygon>
                    <polygon points="90,50 80,50"></polygon>
                    <polygon points="80,80 70,70"></polygon>
                </svg>
            </div>
        </div>
        {% endif %}
        <div class="textContent">
            <p class="h6" style="margin-top: 16px; margin-left: 40px; color: gray;">
                {{ feed.like }} 명이 좋아합니다</p>
        </div>
  • 좋아요 버튼의 css 는 여기서 가져왔다.

좋아요 버튼을 처음 만들었을 때 자기가 쓴 글이 아니면 체크박스 체크가 안 되는 에러가 있었는데, 내가 views.py/user_liked 의 user 부분을 request.user 가 아니라 current_feed.author 로 해서 그 글을 적은 작성자의 좋아한 피드에 피드가 추가되어서 그런 에러가 난 것이었다. user = request.user 로 바꿔준 결과 제대로 작동하였다.

profile
공부 중

0개의 댓글