- Outline : 2022๋ 5์ 6์ผ, ์ํ๋ฒ์งธ ๊ดํต ํ๋ก์ ํธ๋ฅผ ์ํํ์๋ค. ์ด์ ์ ๋ง ์ผ๋ง ๋จ์ง ์์๋ค๊ณ ์๊ฐ์ด ๋ ๋ค. ๋ค์์ฃผ์ ๋ง์ง๋ง ๊ดํตํ๋ก์ ํธ๋ฅผ ์ํํ๋ฉด, ์ต์ข ํ๋ก์ ํธ๋ง ์๋๊ณ ์๊ฒ ๋ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ฉด์ ์ค๋ ๋๊ผ๋ ์ ์ ๋ํด ๊ฐ๋จํ๊ฒ ์ฌ๊ธฐ์ ์ ์ด๋ณด๋๋ก ํ๊ฒ ๋ค.
์ค๋ ํ๋ก์ ํธ๋ ์กฐ๊ธ ํน์ดํ๋ค๊ณ ๋๊ปด์ก๋ ์ ์ด ์์๋ค. ์ด๋ ์ ๋ ์์จ์ฑ์ด ๋ณด์ฅ๋์ด ์๋ค๋ ๊ฒ์ด๋ค. ๊ทธ ๋ง์ ๊ณง ๊ธฐ๋ฅ ๋ฉด์ ์์ด์๋ ์๋์ด ๋ ์ ์๊ฒ๋ ์์ฑํ๋, ๊ทธ ์ธ๋ ๋ชจ๋ ๊ฒ์ด ํ ๋ณ๋ก ์์ ๋กญ๊ฒ ๊ตฌ์ฑํ๋ผ๋ ๋ป์ด์๋ค. ์๋ฅผ ๋ค์๋ฉด, ์ ์ ํ ์๊ณ ๋ฆฌ์ฆ์ ์ฌ์ฉํ์ฌ ์ํ๋ฅผ ์ถ์ฒํ ์ ์๊ฒ ํ๋ค๋ ๊ฒ์ด ๊ทธ๋ฌํ์๋ค. ๊ทธ๋ ๊ธฐ์ ๋ ํ์๊ณผ ์ด์ผ๊ธฐ๋ฅผ ๋๋ ์ ์๊ฒ ๋ ๊ฒ ๊ฐ๊ณ , ์๋ก ์๊ฐ์ ๋๋ ์ ์๋ค๋ ๊ฒ์ด ๊ณง ๊ฐ๋ฐ์ด์ง ์์๊น ์๊ฐ์ ํ๋ ๋ ์ด๊ธฐ๋ ํ์๋ค.
์ค๋ ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ๋๋ ์ ๋ถํฐ ํ ์ค๋ก ๊ฐ๋จํ๊ฒ ๋งํ์๋ฉด, '์ฃผ๋ง๋์ ๋ง์ ๋ณต์ต์ ํตํด ๋ ๋ง์ ๋์์ด ๋์ด์ฃผ๊ณ ์ถ๋ค.' ์๋ค. ์ค๋์ ๊ฐ์ ๋ฐ์ '๊ณ ์๋ฏผ' ํ์ฐ์ ํ์ด๋ฅผ ์ด๋ฃจ์ด ํ๋ก์ ํธ๋ฅผ ์ํํ์๊ณ , ๊ทธ ๊ณผ์ ์์์ ๋๋ ์ ์ ์ค์ฌ์ผ๋ก ์์ ํด ๋ณด๊ณ ์ ํ๋ค. ๋ํ ๊ฐ์ ํด์ผ ํ๋ ๋ถ๋ถ๋ ์์ฑํด๋ณด๋ ค ํ๋ค.
(This project was carried out in Python 3.9.9 and Django 3.2.12 environment .)
- ์๊ตฌ์ฌํญ : ์ปค๋ฎค๋ํฐ ์๋น์ค์ ์์ธ ๊ธฐ๋ฅ ๊ฐ๋ฐ์ ์ํ ๋จ๊ณ๋ก, ๋น๋๊ธฐ ํต์ (AJAX)์ ํ์ฉํ์ฌ ์ฌ์ฉ์์ UI/UX๋ฅผ ๊ฐ์ ํ ์ ์์ต๋๋ค.
(ํ๋ก์ ํธ ํ์ผ์ settings.py, urls.py์ base.html ๋ฑ์ ๊ธฐ๋ณธ ์ค์ ์ ์ ์ธํ๊ณ , ์ฑ ํ์ผ์ ๊ดํด์๋ง ์์ฑํฉ๋๋ค.)
-
์ ์ ํ๋ก์ฐ ๊ธฐ๋ฅ ๊ตฌํ
-
accounts/views.py
# follow ๊ธฐ๋ฅ์ ๋ํด์๋ง ์์ฑ. @require_POST def follow(request, user_pk): followed = False if request.user.is_authenticated: person = get_object_or_404(get_user_model(), pk=user_pk) user = request.user if person != user: if person.followers.filter(pk=user.pk).exists(): person.followers.remove(user) else: person.followers.add(user) followed = True context = { 'followed': followed, 'followerCount': user.followers.count(), 'followingCount': user.followings.count(), } return JsonResponse(context) return redirect('accounts:login')
-
accounts/profile.html
{% extends 'base.html' %} {% block content %} <h1>{{ person.username }}์ ํ๋กํ ํ์ด์ง</h1> {% with followings=person.followings.all followers=person.followers.all %} <div> <div id='follow-count'> ํ๋ก์ : {{ followings|length }} / ํ๋ก์ : {{ followers|length }} </div> {% if request.user != person %} <div> <form action="{% url 'accounts:follow' person.pk %}" id="follow-form" method="POST" data-user-id="{{ person.pk }}"> {% if request.user.is_authenticated %} {% csrf_token %} {% endif %} {% if request.user in followers %} <button id="followBtn">์ธํ๋ก์ฐ</button> {% else %} <button id="followBtn">ํ๋ก์ฐ</button> {% endif %} </form> </div> {% endif %} </div> {% endwith %} {% endblock %} {% block script %} <script> const followForm = document.querySelector('#follow-form') const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]') if (followForm) { followForm.addEventListener('submit', event => { if (csrf_token) { event.preventDefault() const userId = event.target.dataset.userId axios({ method: 'post', url: `http://127.0.0.1:8000/accounts/${userId}/follow/`, headers : {'X-CSRFToken': csrftoken.value}, }) .then(response => { const input = document.querySelector('#followBtn') if (response.data.followed) { input.innerText = '์ธํ๋ก์ฐ' } else { input.innerText = 'ํ๋ก์ฐ' } const followCount = document.querySelector('#follow-count') followCount.innerText = `ํ๋ก์ : ${response.data.followingCount} / ํ๋ก์ : ${response.data.followerCount}` }) } }) } </script> {% endblock script %}
-
-
๋ฆฌ๋ทฐ ์ข์์ ๊ธฐ๋ฅ ๊ตฌํ
-
community/views.py
# like ๊ธฐ๋ฅ์ ๋ํด์๋ง ์์ฑ. @require_POST def like(request, review_pk): if request.user.is_authenticated: review = get_object_or_404(Review, pk=review_pk) user = request.user if review.like_users.filter(pk=user.pk).exists(): review.like_users.remove(user) liked = False else: review.like_users.add(user) liked = True context = { 'liked': liked, 'count': review.like_users.count(), } return JsonResponse(context) return redirect('accounts:login')
-
community/index.html
{% extends 'base.html' %} {% block content %} <h1>Community</h1> <hr> {% for review in reviews %} <p>์์ฑ์ : <a href="{% url 'accounts:profile' review.user.username %}">{{ review.user }}</a></p> <p>๊ธ ๋ฒํธ: {{ review.pk }}</p> <p>๊ธ ์ ๋ชฉ: {{ review.title }}</p> <p>๊ธ ๋ด์ฉ: {{ review.content }}</p> <form action="{% url 'community:like' review.pk %}" method="POST" class="d-inline like-form" data-review-id="{{ review.pk }}"> {% csrf_token %} {% if user in review.like_users.all %} <button id="like-{{ review.pk }}">์ข์์ ์ทจ์</button> {% else %} <button id="like-{{ review.pk }}">์ข์์</button> {% endif %} </form> <p> <span id="like-count-{{ review.pk }}"> {{ review.like_users.all|length }} </span> ๋ช ์ด ์ด ๊ธ์ ์ข์ํฉ๋๋ค. </p> <a href="{% url 'community:detail' review.pk %}">[detail]</a> <hr> {% endfor %} {% endblock %} {% block script %} <script> const likeForms = document.querySelector('.like-form') const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]') likeForms.forEach(form => { forms.addEventListener('submit', event => { event.preventDefault() const reviewId = event.target.dataset.reviewId axios({ method: 'post', url: `http://127.0.0.1:8000/community/${reviewId}/like/`, headers : {'X-CSRFToken': csrftoken.value}, }) .then(response => { const likeCount = document.querySelector(`#like-count-${reviewId}`) likeCount.innerText = response.data.count const button = document.querySelector(`#like-${reviewId}`) if (response.data.liked) { button.innerText = '์ข์์ ์ทจ์' } else { button.innerText = '์ข์์' } }) }) }) </script> {% endblock script %}
-
-
์ฌ์ฉ์์ ์ธ์ฆ ์ฌ๋ถ์ ๊ด๊ณ์์ด, ์ ์ฒด ์ํ ๋ชฉ๋ก ์กฐํ ํ์ด์ง์์ ์ํ ๋ชฉ๋ก์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ ๊ตฌํ
-
movies/views.py
# ๋ชฉ์ ์ ๋ง๋ ์ฝ๋๋ง ์์ฑ. @require_safe def index(request): movies = Movie.objects.order_by('-popularity') paginator = Paginator(movies, 10) page_number = request.GET.get('page') # queryset page_obj = paginator.get_page(page_number) context = { 'movies': page_obj, } return render(request, 'movies/index.html', context)
-
movies/index.html
{% extends 'base.html' %} {% load bootstrap5 %} {% block content %} <h1>Movies</h1> <div id="movieList"> {% for movie in movies %} <div class="movie"> <h2>{{ movie.title }}</h2> <p>{{ movie.overview }}</p> <a href="{% url 'movies:detail' movie.pk %}">[DETAIL]</a> <hr> </div> {% endfor %} <!-- ์ฌ๊ธฐ์ JSON ๋ด์ฉ์ ๋ด์ Element append --> </div> {% comment %} <div class="d-flex justify-content-center"> {% bootstrap_pagination movies %} </div> {% endcomment %} {% endblock %} {% block script %} <script> let page = 2 const movieList = document.querySelector('#movieList') document.addEventListener('scroll', function (event) { const { scrollTop, clientHeight, scrollHeight } = document.documentElement // ์์ ๋ฐ๋ฅ์ ๋๋ฌ // if (scrollHeight - scrollTop === clientHeight) { // 1. ์คํฌ๋กค์ด ๋ฐ๋ฅ(์ธ์ ๋ฆฌ)์ ๋๋ฌ ํ์๋, => document / event if (scrollTop + clientHeight >= scrollHeight - 5) { // 2. ์ถ๊ฐ ๋ฐ์ดํฐ 10๊ฐ๋ฅผ ๋ถ๋ฌ์ด(AJAX) => axios / DRF axios({ method: 'get', url: `/movies/ajax/?page=${page}` }) .then(res => { const movies = res.data // 3. ์๋ต JSON ๋ฐ์ดํฐ 10๊ฐ๋ฅผ ํ๋ฉด์ ๋ถ์ movies.forEach(movie => { const movieDiv = document.createElement('div') const h2 = document.createElement('h2') h2.innerText = movie.title const p = document.createElement('p') p.innerText = movie.overview const a = document.createElement('a') a.innerText = '[DETAIL]' a.href = `/movies/${movie.id}/` const hr = document.createElement('hr') movieDiv.append(h2, p, a, hr) movieList.appendChild(movieDiv) }) page++ }) .catch(err => console.error(err)) } }) </script> {% endblock script %}
-
-
์ฌ์ฉ์์ ์ธ์ฆ ์ฌ๋ถ์ ๊ด๊ณ์์ด, ๋จ์ผ ์ํ ์์ธ ์กฐํ ํ์ด์ง์์ ์ํ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ ๊ธฐ๋ฅ ๊ตฌํ
-
movies/views.py
# ๋ชฉ์ ์ ๋ง๋ ์ฝ๋๋ง ์์ฑ. @require_safe def detail(request, movie_pk): movie = get_object_or_404(Movie, pk=movie_pk) context = { 'movie': movie, } return render(request, 'movies/detail.html', context)
-
movies/detail.html
{% extends 'base.html' %} {% block content %} <h2 class="text-center">DETAIL</h2> <h3>{{ movie.pk }} ๋ฒ์งธ ๊ธ</h3> <hr> <p>title: {{ movie.title }}</p> <p>release_date: {{ movie.release_date }}</p> <p>popularity: {{ movie.popularity }}</p> <p>vote_count: {{ movie.vote_count }}</p> <p>vote_average: {{ movie.vote_average }}</p> <p>overview: {{ movie.overview }}</p> <p> <img src="{{ movie.poster_path }}" alt=""> </p> <hr> <a href="{% url 'movies:index' %}">[back]</a> {% endblock %}
-
-
์ฌ์ฉ์๊ฐ ์ธ์ฆ๋์ด ์๋ค๋ฉด, 10๊ฐ์ ์ํ๋ฅผ ์ถ์ฒํ์ฌ ์ ๊ณตํ ์ ์๋ ๊ธฐ๋ฅ ๊ตฌํ
-
movies/views.py
@require_safe def recommended(request): if request.user.is_authenticated: movies = Movie.objects.order_by('-vote_average')[:10] context = { 'movies': movies, } return render(request, 'movies/recommended.html', context) return redirect('accounts:login')
-
movies/recommended.html
{% extends 'base.html' %} {% block content %} <h2 class="text-center">์ํ ์ถ์ฒ</h2> {% for movie in movies %} <a href="{% url 'movies:detail' movie.pk %}">{{ movie.title }}</a> <hr> {% endfor %} {% endblock %}
-