๐ํ์ ์ ๋ณด ๋ฐ ์ ๋ฌด ๋ถ๋ด ๋ด์ญ(์ํคํ ์ณ: Django์ Vanilla Js ์ฌ์ฉ)
๐์ํฌ์ฌ(ํ์ฅ, back-end): Django๋ฅผ ์ด์ฉํ ์ํ์ถ์ฒ ์น์๋ฒ ๊ตฌ์ถ
๐์ ์ด์(ํ์, front-end): Vanilla Js์ HTML JavaScript bootstrap5 ๋ฅผ ์ด์ฉํ ํ๋ฉด ๊ตฌ์ฑ
๐๋ชฉํ
์ํ์ ๋ณด
- ๋ก๊ทธ์ธ ๋ ์ ์ ๋ ์ํ์ ๋ํ ํ์ ๋ฑ๋ก/ ์์ /์ญ์ ๋ฑ์ ํ ์ ์์ด์ผ ํ๋ค.
์ปค๋ฎค๋ํฐ
- ์ํ ์ ๋ณด์ ๊ด๋ จ๋ ์ํต์ ํ ์ ์๋ ์ปค๋ฎค๋ํฐ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผ ํ๋ค.
- ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ง ๊ธ์ ์กฐํ/ ์์ฑํ ์ ์์ผ๋ฉฐ, ์์ฑ์ ๋ณธ์ธ๋ง ๊ธ์ ์์ / ์ญ์ ํ ์ ์์ด์ผ ํ๋ค.
- ์ฌ์ฉ์๋ ์์ฑ๋ ๊ฒ์ ๊ธ์ ๋๊ธ์ ์์ฑํ ์ ์์ด์ผ ํ๋ฉฐ ์์ฑ์ ๋ณธ์ธ๋ง ๋๊ธ์ ์ญ์ ํ ์ ์์ด์ผ ํ๋ค.
- ๊ฐ ๊ฒ์๊ธ ๋ฐ ๋๊ธ์ ์์ฑ ๋ฐ ์์ ์๊ฐ ์ ๋ณด๊ฐ ํฌํจ๋์ด์ผ ํ๋ค.
๊ธฐํ
- 5๊ฐ ์ด์์ URL ํ์ด์ง
๐ํ์ ์๊ตฌ ์ฌํญ ์ค์ ๊ตฌํ ์ฌ๋ถ
A. ๊ด๋ฆฌ์ ๋ทฐ
- i. ๊ด๋ฆฌ์ ๊ถํ์ ์ ์ ๋ง ์ํ ๋ฑ๋ก / ์์ / ์ญ์ ๊ถํ์ ๊ฐ์ง๋ค ( O )
- ii. ๊ด๋ฆฌ์ ๊ถํ์ ์ ์ ๋ง ์ ์ ๊ด๋ฆฌ ๊ถํ์ ๊ฐ์ง๋ค ( O )
- iii. Django์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ admin ๊ธฐ๋ฅ์ ์ด์ฉํ์ฌ ๊ตฌํํ๋ค ( O )
B. ์ํ ์ ๋ณด
- i. ์ํ ๋ฐ์ดํฐ๋ Database Seeding์ ํ์ฉํ์ฌ ์ต์ 50๊ฐ ์ด์์ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ค ( O )
- movie.jsonํ์ผ์ ์์ฒด์ ์ผ๋ก ์ ์ํ์ฌ, loaddataํ์ฌ ๋ฐ์ดํฐ ์ถ์ถ
- ii. ๋ก๊ทธ์ธ ๋ ์ ์ ๋ ์ํ์ ๋ํ ํ์ ๋ฑ๋ก / ์์ / ์ญ์ ๋ฑ์ ํ ์ ์์ด์ผ ํ๋ค ( O )
C. ์ํ ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ
- i. ์ํ๋ฅผ ํ๊ฐํ ์ ์ ๋ ํด๋น ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ํ๋ฅผ ์ถ์ฒ ๋ฐ์ ์ ์์ด์ผ ํ๋ค ( x )
- ์ ์ ๊ฐ 8์ ์ด์์ ์ ์๋ฅผ ์ค ์ํ์ ์ฅ๋ฅด ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ ,
- ๊ทธ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ํ์ ์ด ์ข์ ์์ผ๋ก 10๊ฐ์ฉ ์ถ์ฒํ๋ ์๊ณ ๋ฆฌ์ฆ์ ์ง๋ ค๊ณ ํ์์ผ๋,
- ์ค๋ฅ๋ฅผ ํด๊ฒฐํ์ง ๋ชปํ์ฌ -> ๋จ์ ๋๋ค ์ถ์ฒ ์๊ณ ๋ฆฌ์ฆ ์ฝ๋๋ฅผ ์์ฑ
- ii. ์ฌ์ฉ์๋ ๋ฐ๋์ ์ต์ 1๊ฐ ์ด์์ ๋ฐฉ์์ผ๋ก ์ํ๋ฅผ ์ถ์ฒ ๋ฐ์ ์ ์์ด์ผ ํ๋ค ( O )
- iii. ์ด๋ ํ ๋ฐฉ์์ผ๋ก ์ถ์ฒ ์์คํ ์ ๊ตฌํ ํ๋์ง ๊ธฐ์ ์ ์ผ๋ก ์ค๋ช ํ ์ ์์ด์ผ ํ๋ค ( O )
D. ์ปค๋ฎค๋ํฐ
- i. ์ํ ์ ๋ณด์ ๊ด๋ จ๋ ์ํต์ ํ ์ ์๋ ์ปค๋ฎค๋ํฐ ๊ธฐ๋ฅ์ ๊ตฌํํด์ผ ํ๋ค ( O )
- ii. ๋ก๊ทธ์ธ ์ฌ์ฉ์๋ง ๊ธ์ ์กฐํ / ์์ฑ ํ ์ ์์ผ๋ฉฐ, ์์ฑ์ ๋ณธ์ธ๋ง ๊ธ์ ์์ / ์ญ์ ํ ์ ์๋ค ( O )
- iii. ์ฌ์ฉ์๋ ์์ฑ๋ ๊ฒ์ ๊ธ์ ๋๊ธ์ ์์ฑํ ์ ์์ผ๋ฉฐ, ์์ฑ์ ๋ณธ์ธ๋ง ๋๊ธ์ ์ญ์ ํ ์ ์๋ค ( O )
- iv. ๊ฐ ๊ฒ์๊ธ ๋ฐ ๋๊ธ์ ์์ฑ ๋ฐ ์์ ์๊ฐ ์ ๋ณด๊ฐ ํฌํจ๋์ด์ผ ํ๋ค ( O )
E. ๊ธฐํ
- i. ์ต์ํ 5๊ฐ ์ด์์ URL ๋ฐ ํ์ด์ง๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ค ( O )
- ii. HTTP Method์ HTTP response status codes๋ ์ํฉ์ ๋ง๊ฒ ์ ์ ํ๊ฒ ๋ฐํ๋์ด์ผ ํ๋ฉฐ, ํ์์ ๋ฐ๋ผ ๋ฉ์์ง ํ๋ ์์ํฌ ๋ฑ์ ์ฌ์ฉํ์ฌ ์๋ฌ ํ์ด์ง๋ฅผ ๊ตฌ์ฑํด์ผ ํ๋ค ( O )
3. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ธ๋ง (ERD)


๐์ค์ ๊ตฌํ ์ ๋ ๋ฐ ํ์ ๊ธฐ๋ฅ์ ๋ํ ์์ธ ์ค๋ช (ํ๋ก ํธ์๋ - ๋)
# ๊ตฌํ ์์ฐ ์์
/movies/index.html
- Bootstrap5์ Card Component๋ฅผ ์ด์ฉํ์ฌ ํ๋ฉด์ ๊ตฌ์ฑํ๊ธฐ ์ํด movie์ title๊ณผ poster image๊ฐ ํ์
- views.py์ index ๋ทฐํจ์๋ฅผ ์ฐธ๊ณ ํ๋ฉด models.py์ Movie์์ ๋ชจ๋ ๊ฒ์ ๋ถ๋ฌ์ค๊ณ ์๊ธฐ ๋๋ฌธ์ context์์ ์ ์ํ movie๋ก models.py์ ์์๋ค์ ์ ๊ทผํด ์ด๋ฅผ ๋ถ๋ฌ์๋ค.
- ๋ํ ์ํ ์ด๋ฏธ์ง๋ฅผ ๋๋ฅด๋ฉด movie detailํ์ด์ง๋ก ์ด๋ ํด์ผ ํ๊ธฐ ๋๋ฌธ์ aํ๊ทธ๋ฅผ ์ด์ฉํด detail ํ์ด์ง์ ์ ๊ทผํ ์ ์๊ฒ ํ์๋ค.
<!--bootstrap card code start-->
<div class="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-4">
{% for movie in movies %}
<div class="col">
<div class="card h-100 text-white bg-dark">
<a href="{% url 'movies:detail' movie.pk %}"><img src="{{ movie.poster_path }}" class="card-img-top" alt="..."></a>
<div class="card-body text-center">
{{ movie.title }}
</div>
</div>
</div>
{% endfor %}
</div>
<!--bootstrap card code end-->
/movies/detail.html
- form ์์ฑ ์ ์ ์ ํ์ ์ ๋จ๊ธด ์ ์ ๋ฅผ ๋น๊ตํด ์ญ์ ๋ฒํผ ํ์
- form ํ๊ทธ๋ฅผ POSTํ๊ธฐ ๋๋ฌธ์ CSRF๋ฅผ ๋ง๊ธฐ ์ํด ๋์๋ฅผ ์์ฑํด ์ฃผ๋ csrf token์ ์์ฑ
- ํ์ ์ด ์๋ ๊ฒฝ์ฐ(empty) ํ์ ์ด ์์ฑ๋์ง ์์๋ค๋ ๋ฌธ๊ตฌ ์ถ๋ ฅ
{% for rating in ratings %} <li> {{ rating.user }} / {{ rating.score }}์ - {{ rating.comment }} - {{ rating.created_at }} {% if request.user == rating.user %} <form action="{% url 'movies:delete_rating' movie.pk rating.pk %}" method="POST" class="d-inline"> {% csrf_token %} <input class="btn rounded-pill" style="font-size:0.75rem; background-color:#F48FB1;" type="submit" value="์ญ์ "> </form> {% endif %} </li> {% empty %} <p>์์ง ์์ฑ๋ ํ์ ์ด ์์ต๋๋ค! โฏ๏ธฟโฐ</p> {% endfor %}
- form ์์ฑ ์ ์ ๊ฐ ์ด๋ฏธ ํ์ ์ ๋จ๊ฒผ์ ๊ฒฝ์ฐ
- ์ด๋ฏธ ์์ฑ ์๋ฃํ๋ค๋ ๋ฌธ๊ตฌ๋ฅผ ์ถ๋ ฅ
- ๋ง์ฝ ํ์ ์ ๋จ๊ธฐ์ง ์์ ๊ฒฝ์ฐ is_authenticated๋ฅผ ์ด์ฉํด ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ๋ค.
{% if request.user.id in user_ids %} <strong>ํ์ ์ ์ด๋ฏธ ๋ฑ๋กํ์ จ์ต๋๋ค!</strong> {% else %} {% if request.user.is_authenticated %} <form action="{% url 'movies:create_rating' movie.pk %}" method="POST"> {% csrf_token %} {% bootstrap_form rating_form %} <div class="text-left">โ ํ์ (1 ~ 10์ )์ ์ ๋ ฅํด์ฃผ์ธ์!</div><br> <div style="text-align: center;"> <input class="btn rounded-pill" style="background-color : #B3E5FC;" type="submit" value="๋๊ธ + "> </div> </form> {% else %} <a href="{% url 'accounts:login' %}" class="btn btn-dark rounded-pill">ํ์ ์ ๋จ๊ธฐ์๋ ค๋ฉด ๋ก๊ทธ์ธํ์ธ์.</a> {% endif %} {% endif %}
/accounts/signup.html
- bootstrap5๋ฅผ ์ด์ฉํด ์ฝ๊ฒ form์ ๊ตฌํํ๋ค.
- bootstrap5๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด {% load django_bootstrap5 %} ๋ฅผ block content ์์ ์์ฑํ์๋ค.
- formํ๊ทธ๊ฐ POST ์์ฒญ ์ด๋ฏ๋ก CSRF๋ฅผ ๋ง๊ธฐ ์ํด csrft token ์์ฑํ์๋ค.
{% block content %}
{% load django_bootstrap5 %}
<div style="text-align: center; max-width:500px; margin:4rem auto">
<div>
<h2>
<span class="badge rounded-pill col-10 mt-3" style="background-color : #B3E5FC;">ํ์๊ฐ์
</span>
</h2>
</div>
<hr>
<div>
<form action="" method="POST">
{% csrf_token %}
<!-- {{ form }} -->
{% bootstrap_form form %}
<br>
<hr>
<input type="submit" class="btn rounded-pill col-6 mt-3" style="background-color : #E1F5FE;"value="๊ฐ์
ํ๊ธฐ">
</form>
</div>
</div>
{% endblock %}
/accounts/login.html
- singup.html๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก bootstrap5 form์ ์ฌ์ฉํ๊ฒ ์ํด {% load django_bootstrap5 %}
- formํ๊ทธ๊ฐ POST ์์ฒญ ์ด๋ฏ๋ก CSRF๋ฅผ ๋ง๊ธฐ ์ํด csrft token ์์ฑํ์๋ค.
- ํ์์ด ์๋ ๊ฒฝ์ฐ ํ์ ๊ฐ์ ์ฐฝ์ ์ฐพ๊ธฐ ์ฝ๊ฒ ์๋์ button์ ๋ง๋ค์๋ค.
{% extends 'base.html' %}
{% block content %}
{% load django_bootstrap5 %}
<div style="text-align: center; max-width:500px; margin:4rem auto">
<div>
<h2>
<span class="badge rounded-pill col-10 mt-3" style="background-color : #B3E5FC;">๋ก๊ทธ์ธ</span>
</h2>
<br>
<hr>
</div>
<br>
<div>
<form action="" method="POST">
{% csrf_token %}
<!-- {{ form }} -->
{% bootstrap_form form %}
<input type="submit" class="btn rounded-pill col-6 mt-3" style="background-color : #E1F5FE;"value="LogIn">
</form>
</div>
<br>
<hr>
<p>์์ง ํ์์ด ์๋์ ๊ฐ์?</p>
<a class="btn rounded-pill btn-primary" href="{% url 'accounts:signup' %}">ํ์ ๊ฐ์
ํ๊ธฐ</a>
</div>
{% endblock content %}
accounts/follow.html
- follow.html์ views.py์์ ํ์ธํ ์ ์๋ฏ์ด follow์ํ๋ฅผ HttpRequest ์ธ์คํด์ค๋ฅผ ์ธ์๋ก ๋ฆฌํดํ๊ธฐ ์ํด FBV(Function Based View)์ ๋ฐฉ๋ฒ ์ค ํ๋ ์ธ JSON ํ์ response๋ฅผ ํด์ฃผ์๋ค.
- ๋ฐ๋ผ์, follow.html์ bootstrap5์ ๊ธฐ๋ฅ ์ค ํ๋ ์ธ jumbotron ์ ์ด์ฉํด profile.html์์ includeํด ์ถ๋ ฅํ๋ ๋ฐฉ์์ ํํ๋ค.
- follow ์ํ๊ฐ ์๋๋ผ๋ฉด follow ๋ฒํผ์ด ํ์์ผ๋ก ์ฒ๋ฆฌ ๋๊ฒ ๊ตฌํํ๋ค. (fontawesome CDN ํ์ฉ)
- ๋ค์ ๊ตฌํ ํ์ด์ง ์ค๋ช ์์ ์ด follow.html์ JS์ axios๋ฅผ ํตํด ๋น๋๊ธฐํ ํ๋ค.
<div class="container jumbotron text-left text-black ">
<h1 class="display-4">@ {{ person.username }}</h1>
<hr>
{% with followings=person.followings.all followers=person.followers.all %}
<div class="d-flex justify-content ">
<!-- ํ๋ก์ฐ ๋ฒํผ -->
{% if request.user != person %}
<form id="follow-form" data-user-id="{{ person.pk }}">
{% csrf_token %}
{% if request.user in followers %}
<button class='btn btn-link btn-outline-light' role="button">
<i id='follow-{{ person.pk }}' class='fa-solid fa-ticket' style='color:crimson;'> Follow</i>
</button>
{% else %}
<button class='btn btn-link btn-outline-light' role="button">
<i id='follow-{{ person.pk }}' class='fa-solid fa-ticket' style='color: gray;'> Follow</i>
</button>
{% endif %}
</form>
{% endif %}
</div>
<div class="d-flex justify-content-end ">
<p class="lead">
ํ๋ก์ :
<span id='followings-count-{{ person.pk }}'>{{ followings|length }}</span>
| ํ๋ก์ :
<span id='followers-count-{{ person.pk }}'>{{ followers|length }}</span>
</p>
</div>
{% endwith %}
</div>
accounts/profile.html
- follow.html์์ ์์ฑ๋ Bootstrap์ jumbtron์ {% include 'accounts/follow.html' %} ํตํด ์๋จ์ ํ์ํ๋ค.
- Bootstrap์ Component์ค ํ๋ ์ธ Accordion์ ํ์ฉํด ํ๋กํ ์ ์ ์ ํ์ ๋ชฉ๋ก๊ณผ ์์ฑํ ๊ฒ์๊ธ ๋ฐ ๋๊ธ, ์ข์์ ํ ๊ฒ์๊ธ์ ํํํ๋ค.
- {% include 'accounts/follow.html' %}ํด์ ๋ถ๋ฌ์จ follow.html์์ follow๋ฒํผ์ ๋๋ ์ ๋ ์๋ก ๊ณ ์นจ์ด ์ผ์ด๋์ง ์๊ฒ ํ๊ธฐ ์ํด JS๋ฅผ ์ฌ์ฉํด ์ด๋ฅผ ๊ตฌํํ๋ค.
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const form = document.querySelector('#follow-form')
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
form.addEventListener('submit', function (event) {
event.preventDefault()
console.log(event)
const userId = event.target.dataset.userId
axios({
method: 'post',
url: `/accounts/follow/${userId}/`,
headers: {'X-CSRFToken': csrftoken},
}).then((response) => {
const followed = response.data.followed
const followersCount = response.data.followers_count
const followingsCount = response.data.followings_count
const followButton = document.querySelector(`#follow-${userId}`)
if (followed) {
followButton.style.color = 'crimpson'
} else {
followButton.style.color = 'gray'
}
const followingsNum = document.querySelector(`#followings-count-${userId}`)
followingsNum.innerText = followingsCount
const followersNum = document.querySelector(`#followers-count-${userId}`)
followersNum.innerText = followersCount
})
})
</script>
- addEventListener์ ํตํด follow ๋ฒํผ์ ํด๋ฆญํ๋ฉด POST ์์ฒญ์ด ๊ฐ๋๋ก ์์ฑํ๋ค.
form.addEventListener('submit', function (event) {
event.preventDefault()
console.log(event)
const userId = event.target.dataset.userId
...
POST์์ฒญ์ด๊ธฐ ๋๋ฌธ์ CSRF๋ฅผ ๋ง๊ธฐ ์ํด csrf token์ ์ฌ์ฉํด์ผ ํ๊ธฐ ๋๋ฌธ์ axios์ ์ง์ csrf token์ ์ง์ ํด ์ฃผ์๋ค. (์ ์ญ ๋ณ์๋ก csrf token์ ์์ฑํ๋ ๋ฐฉ๋ฒ๋ ์๋ค.)
axios({
method: 'post',
url: `/accounts/follow/${userId}/`,
headers: {'X-CSRFToken': csrftoken},
}).then((response) => {
...
- follow ๋ฒํผ์ ํด๋ฆญํ๋ฉด ๋ฒํผ์ ํ๋ก์ฐ ์ํ๊ฐ ๋๊ณ (ํ์์์ ๋ค๋ฅธ ์์ผ๋ก), ํ๋ก์ฐ ๋ฒํผ์ ๋๋ฅธ ์ฌ๋์ ํ๋ก์์ด +1 ๋๊ฒ ํ๊ณ , ํ๋ก์๊ฐ ์๊ธด ์ฌ๋์ ํ๋ก์๊ฐ +1 ๋๊ฒ ๊ตฌํํ์๋ค.
- ๋ฐ๋์ ๊ฒฝ์ฐ์ -1์ด ๋๊ณ ๋ฒํผ ์๋ ๋ค์ ํ์์ด ๋๊ฒ ๋๋ค.
.then((response) => {
const followed = response.data.followed
const followersCount = response.data.followers_count
const followingsCount = response.data.followings_count
const followButton = document.querySelector(`#follow-${userId}`)
if (followed) {
followButton.style.color = 'crimson'
} else {
followButton.style.color = 'gray'
}
const followingsNum = document.querySelector(`#followings-count-${userId}`)
followingsNum.innerText = followingsCount
const followersNum = document.querySelector(`#followers-count-${userId}`)
followersNum.innerText = followersCount
})
community/index.html
- ๊ธ์ ์์ฑํ๊ธฐ ์ํด์๋ ๋ก๊ทธ์ธ์ ํ๋๋ก is_authenticated ํด ๋ก๊ทธ์ธ ์ฌ๋ถ๋ฅผ ์ฒดํฌํด ์ฃผ์๋ค.
- ๋ง์ฝ ๋ก๊ทธ์ธ์ด ๋์ด ์๋ค๋ฉด +new ๋ฒํผ์ด ๋ณด์ด๋๋ก ์ค์ ํ๋ค.
{% if user.is_authenticated %}
<a class="btn rounded-pill text-black" style="background-color : #E1F5FE;" href="{% url 'community:create' %}">+ New</a>
{% else %}
<a href="{% url 'accounts:login' %}">์ ๊ธ์ ์์ฑํ๋ ค๋ฉด ๋ก๊ทธ์ธํ์ธ์</a>
{% endif %}
- ์ํ ์ ๋ชฉ, ์์ฑ์, ํ์ , ์์ฑ ์๊ฐ, ์ข์์ ๋ฒํผ, ์ข์์ ํ ์ฌ๋์ ์๊ฐ ๋์ค๋๋ก ๊ตฌํํ๋ค.
- ์ฌ๊ธฐ๋ ๋ง์ฐฌ๊ฐ์ง๋ก POST ์์ฒญ์ด๊ธฐ ๋๋ฌธ์ csrf token์ ์์ฑํ๋ค.
- ์ข์์ ๋ฒํผ์ ํํธ๋ก ๋ฐ๊พธ์์ผ๋ฉฐ ์ด๋ฅผ ๋๋ฅด๋ฉด ํํธ๊ฐ ๋นจ๊ฐ์(์ข์์ ์ํ), ๋ค์ ํ๋ฒ ๋ ๋๋ฅด๋ฉด ๊ฒ์์(์ข์์ ์ทจ์ ์ํ)์ผ๋ก ๋ํ๋๊ฒ ํ์๋ค.
- ๋๋ถ์ด ๋ฒํผ์ ๋๋ฅผ ๋ ์ข์ํ๋ ์ฌ๋์ ์๊ฐ ์ฆ๊ฐ๋๋๋ก ํ๋ค.
{% for review in reviews %}
<a class="fw-bold text-decoration link-dark" href="{% url 'community:detail' review.pk %}">{{ review.title }}</a>
<br>
<a>์ํ ์ ๋ชฉ : {{ review.movie_title }}</a>
<br>
์์ฑ์ : <a class=" link-secondary" href="{% url 'accounts:profile' review.user.username %}">{{ review.user }}</a>
<br>
<a>ํ์ : {{review.rank}}</a>
<br>
<a>์์ฑ์๊ฐ : {{review.created_at}}</a>
<!-- ์ข์์ start -->
<div class="d-flex justify-content-end ">
<form class="like_form" data-id='{{ review.pk }}'>
{% csrf_token %}
{% if user in review.like_users.all %}
<button class="btn btn-link">
<i id="like-{{ review.pk }}" class="fas fa-heart fa-lg" style="color:crimson;"></i>
</button>
{% else %}
<button class="btn btn-link">
<i id="like-{{ review.pk }}" class="fas fa-heart fa-lg" style="color:black;"></i>
</button>
{% endif %}
</form>
<span id="like-count-{{ review.pk }}">{{ review.like_users.all|length }}</span>๋ช
์ด ์ด ๊ธ์ ์ข์ํฉ๋๋ค.
</div>
<hr>
{% endfor %}
- accounts/profile.html๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ์ข์์ ๋ฒํผ์ axios์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
const forms = document.querySelectorAll('.like_form')
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value
forms.forEach(function (form) {
form.addEventListener('submit', function (event) {
event.preventDefault()
const reviewId = event.target.dataset.id
axios.post(`http://127.0.0.1:8000/community/${reviewId}/like/`, {},
{headers: {'X-CSRFToken': csrftoken}
}).then(function (response) {
const liked = response.data.liked
const count = response.data.count
const likeButton = document.querySelector(`#like-${reviewId}`)
if (liked) {
likeButton.style.color = 'crimson'
} else {
likeButton.style.color = 'black'
}
const likeCount = document.querySelector(`#like-count-${reviewId}`)
likeCount.innerText = `${count}`
})
})
})
</script>
community/create.html
- create.html ํ์ด์ง๋ bootstrap5์ form์ ํ์ฉํ๋ ๋ฐฉ์์ ํํ๋ค.
{% extends 'base.html' %}
{% block content %}
{% load django_bootstrap5 %}
<div style="max-width:500px; margin:4rem auto">
<h1 class="text-center">Create</h1>
<br>
<form action="" method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<div style="text-align: right;">
<input class="btn rounded-pill " style="background-color : #B3E5FC;" type="submit" value="create!">
<hr>
<a class="btn rounded-pill " style="background-color : #E1F5FE;" href="{% url 'community:index' %}">Back</a>
</div>
</form>
</div>
{% endblock %}
community/detail.html
- ๋ก๊ทธ์ธ ๋ ์ ์ ์ ๋ฆฌ๋ทฐ์์ฑ์๊ฐ ์ผ์นํ๋ฉด ์ญ์ ๋ฒํผ์ด ๋ณด์ด๊ณ ์ญ์ ๋ ๊ฐ๋ฅํ๊ฒ ๊ตฌํํ๋ค.
{% if request.user == review.user %}
<a href="{% url 'community:update' review.pk %}" class="btn rounded-pill" style="font-size:0.75rem; background-color: #BBDEFB;">์์ </a>
<form action="{% url 'community:delete' review.pk %}" method="POST">
{% csrf_token %}
<button class="btn rounded-pill" style="font-size:0.75rem; background-color:#F48FB1;">์ญ์ </button>
</form>
{% endif %}
community/update.html
- ๋ค๋ฅธ ํ์ด์ง์ ๋ง์ฐฌ๊ฐ์ง๋ก update.html๋ bootstrap5์ form์ ํ์ฉํ๋ ๋ฐฉ์์ ํํ๋ค.
{% extends 'base.html' %}
{% block content %}
{% load django_bootstrap5 %}
<div style="max-width:500px; margin:4rem auto">
<h1>Update</h1>
<br>
<form action="" method="POST">
{% csrf_token %}
<div style="text-align: left;">
{% bootstrap_form form %}
</div>
<div style="text-align: right;">
<input class="btn rounded-pill " style="background-color : #B3E5FC;" type="submit" value="update!">
<hr>
<a class="btn rounded-pill " style="background-color : #E1F5FE;" href="{% url 'community:detail' review.pk %}">Back</a>
</div>
</form>
</div>
{% endblock %}
ํ๊ธฐ
์ง๋ ํ์ด ํ๋ก์ ํธ ์ดํ๋ก ๋ ๋ฒ์งธ๋ก ๋ค๋ฅธ ์ฌ๋๊ณผ ํ์
ํ์ฌ ์งํํ๋ ํ๋ก์ ํธ์๋๋ฐ,
์ข์ ํ์์ ๋ง๋์ ์์ํ๊ฒ ํ๋ก์ ํธ๋ฅผ ์์ฑํด๋ธ ๊ฑฐ ๊ฐ์ต๋๋ค.
ํ๋ก์ ํธ๋ฅผ ๊ตฌ์ํ ๋ ๋ชฉํํ๋ ๋ฐ์ ๊ตฌํ ํ์ด์ง ๊ตฌ์์ ๋ํ ์๊ฒฌ์ด ๋น์ทํด์ ๋น ๋ฅด๊ฒ ์งํํ ์ ์์์ต๋๋ค.
๋ํ ์ธํผ ๊ณผ์ ์์ ๋ฐฐ์ ๋ ๊ฒ์ ์ด์ฉํด ํด๋ณด๋ ์ ์์ ๋ป๊น์์ต๋๋ค.
์ง๋ ๊ดํต ํ๋ก์ ํธ๋ค์์๋ ํ๋ฃจ ๋ง์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ๋ง์ ๊ตฌํํ๋ ๋ฐ์ ํ ์์
์ด๋๊ฐ ์์ฑํ๊ณ ๊ฐ๋
์ฑ ์๋ ํ์ด์ง๋ค์ ๋ง๋ค์์๋๋ฐ,
์ด๋ฒ์๋ ๊ธฐ๋ฅ๋ค์ ๋ฉฐ์น ๊ฐ ํ์ด์ ๋๋์ด ๊ตฌํํ๊ณ ํํ์ด์ง๋ฅผ
์ข ๋ ๊ฐ๋
์ฑ ์๊ณ ์ฌํํ๊ฒ ๊ตฌํํด ๋ผ ์ ์์ด์ ๋ฟ๋ฏํ์ต๋๋ค!
๋ํ ํผ์๊ฐ ์๋ ๋ค๋ฅธ ์ฌ๋๊ณผ ํจ๊ป ํ๋ก์ ํธ๋ฅผ ํด๋ด์ด ์๋ฏธ์๋ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค์๋ค๋ ์ ์์
5๊ฐ์ ๋์ ๋ง์ ์ฑ์ฅ์ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค๐
๋๊ธ