728x90
Pystagram Project (3)
1. 동적 URL
1.1 URL 경로 변경
1.1.1 URL 경로를 변경할 때 생기는 중복작업
- users/urls.py
urlpatterns = [
...
path("login2/", login_view),
...
]
|
- templates/users/signup.html
<div id="signup">
<form method="POST" enctype="multipart/form-data">
...
<a href="{% url '/users/login2/' %}">로그인 페이지로 이동</a>
</form>
</div>
|
- posts/views.py
def feeds(request):
...
if not request.user.is_authenticated:
return redirect("/users/login2/")
|
- users/views.py
def logout_view(request):
logout(request)
return redirect("/users/login2/")
|
- config/views.py
from django.shortcuts import redirect
def index(request):
if request.user.is_authenticated:
return redirect("/posts/feeds/")
else:
return redirect("/users/login2/")
|
1.2 Template의 동적 URL 변경
1.2.1 동적 URL 생성을 위한 요소 추가
- 동적으로 URL을 생성해서 사용하기 위해서는 app별로 분리된 하위 urls.py에 app_name이라는 속성이 필요함
- 일반적으로 app의 패키지명(디렉토리명)을 사용함
- users/urls.py
...
app_name = "users"
urlpatterns = [
...
]
|
- posts/urls.py
...
app_name = "posts"
urlpatterns = [
...
]
|
1.2.2 Template을 위한 {% url %} 태그
{% url "URL pattern name" %} 태그는 Template에서 urls.py의 내용을 이용해 동적으로 URL을 생성함
{urls.py에 있는 app_name}:{path()에 지정된 name}
|
- users/urls.py
...
app_name = "users"
urlpatterns = [
path("login2/", login_view, name="login"),
path("logout/", logout_view, name="logout"),
path("signup/", signup, name="sighup"),
]
|
- templates/users/signup.html
- 실제로 동적 링크를 만들어보기
<a href="{% url 'users:login' %}">로그인 페이지로 이동</a>
|
- posts/urls.py
app_name - "posts"
urlpatterns = [
path("feeds/", feeds, name="feeds"),
path("comment_add/", comment_add, name="comment_add"),
path("comment_delete/<int:comment_id>/", comment_delete, name="comment_delete"),
path("post_add/", post_add, name="post_add"),
]
|
1.2.3 {% url %} 태그를 사용하도록 기존 Template 코드 수정
- templates/users/login.html
- 로그인 페이지
<a href="{% url 'users:signup' %}">회원가입 페이지로 이동</a>
|
- templates/users/signup.html
- 회원가입 페이지
<a href="{% url 'users:login' %}">로그인 페이지로 이동</a>
|
- templates/posts/post_add.html
- 글 작성 페이지
<nav>
<h1>
<a href="{% url 'posts:feeds' %}">Pystagram</a>
</h1>
<a href="{% url 'users:logout' %}">Logout</a>
</nav>
|
- templates/posts/feeds.html
- 피드 페이지 - 내비게이션 바 부분
<nav>
<h1>
<a href="{% url 'posts:feeds' %}">Pystagram</a>
</h1>
<a href="{% url 'posts:post_add' %}">Add post</a>
<a href="{% url 'users:logout' %}">Logout</a>
</nav>
|
- templates/posts/feeds.html
- 댓글 삭제 부분
<div class="post-comments">
...
{% if user == comment.user %}
<form method="POST" action="{% url 'posts:comment_delete' comment_id=comment.id %}">
|
1.3 View의 동적 URL 변경
1.3.1 View를 위한 reverse 함수
- Terminal
- Template에서 {% url %} 태그를 사용하듯 View에서는 reverse 함수로 동적 URL을 생성할 수 있음
python manage.py shell
from django.urls import reverse
reverse('users:login')
reverse('posts:feeds')
# 추가 인수를 dict에 담아 키워드 인수로 전달
reverse('posts:comment_delete', kwargs={'comment_id': 1})
# 추가 인수를 list에 담아 위치 인수로 전달
reverse('posts:comment_delete', args=[1])
|
1.3.2 reverse 함수를 사용하도록 기존 View 코드 수정
- config/views.py
from django.shortcuts import redirect
def index(request):
if request.user.is_authenticated:
return redirect("posts:feeds")
else:
return redirect("users:login")
|
- users/views.py
from django.urls import reverse
def login_view(request):
if request.user.is_authenticated:
return redirect("posts:feeds")
...
if user:
login(request, user)
return redirect("posts:feeds")
...
def logout_view(request):
logout(request)
return redirect("users:login")
def signup(request):
...
login(request, user)
return redirect("posts:feeds")
...
|
- posts/views.py
from django.urls import reverse
def feeds(request):
...
if not user.is_authenticated:
return redirect("users:login")
...
def comment_add(request):
...
if form.is_valid():
comment = form.save(commit=False)
comment.user = request.user
comment.save()
...
url_next = reverse("posts:feeds") + f"#post-{comment.post.id}"
return HttpResponseRedirect(url_next)
def comment_delete(request, comment_id):
if request.method == "POST":
comment = Comment.objects.get(id=comment_id)
if comment.user == request.user:
comment.delete()
url_next = reverse("posts:feeds") + f"#post-{comment.post.id}"
return HttpResponseRedirect(url_next)
...
def post_add(request):
if request.method == "POST":
...
if form.is_valid():
...
url_next = reverse("posts:feeds") + f"#post-{post.id}"
return HttpResponseRedirect(url_next)
...
|
- users/urls.py
...
app_name = "users"
urlpatterns = [
path("login/", login_view, name="login"),
...
|
. 해시태그
2.1 다대다 관계 모델
2.1.1 다대다 관계모델
- 다대일(Many-to-One, N:1) 관계
- 한 테이블의 한 레코드가 다른 테이블의 여러 레코드와 연관됨을 나타내는 관계
- 다대다(Many-to-Many, M2M, N:N) 관계
- 한 테이블의 여러 레코드가 다른 테이블의 여러 레코드와 연관됨을 나타내는 관계
- 예를 들어
- 학생은 하나의 대학교에만 속할 수 있음: 다대일(학생:학교) 관계
- 한 학생은 여러 개의 수업을 수강신청할 수 있으며 하나의 수업은 그 수업을 수강신청한 여러 명의 학생을 가질 수 있음: 다대다관계
2.1.2 다대다 테이블 구조
2.1.3 해시태그 모델 생성, ManyToMany 연결
- posts/models.py
class HashTag(models.Model):
name = models.CharField("태그명", max_length=50)
|
- posts/models.py
class Post(models.Model):
...
tags = models.ManyToManyField(HashTag, verbose_name="해시태그 목록", blank=True)
|
- Terminal
- posts/models.py에서 HashTag 클래스를 Post 클래스보다 아래쪽에 선언
- Post 클래스가 정의될 때는 HashTag 클래스를 알 수 없음
- ForeignKey, ManyToManyField를 사용할 때 아래쪽에 선언한 모델을 참조하려면 문자열을 사용함
python manage.py runserver
NameError: name 'HashTag' is not defined
|
tags = models.ManyToManyField("posts.HashTag", verbose_name="해시태그 목록", blank=True)
|
python manage.py makemigrations
python manage.py migrate
|
2.2 다대다 모델 admin
2.2.1 admin 구현
- posts/admin.py
from posts.models import Post, PostImage, Comment, HashTag
@admin.register(HashTag)
class HashTagAdmin(admin.ModelAdmin):
pass
|
- posts/models.py
class HashTag(models.Model):
name = models.CharField("태그명", max_length=50)
def __str__(self):
return self.name
|
- posts/admins.py
from django.db.models import ManyToManyField
from django.forms import CheckboxSelectMultiple
...
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
...
formfield_overrides = {
ManyToManyField: {"widget": CheckboxSelectMultiple},
}
|
2.2.2 Template에 Post의 HashTag 표시
- templates/posts/feeds.html
<div class="post-content">
{{ post.content|linebreaksbr }}
<div class="post-tags">
{% for tag in post.tags.all %}
<span>#{{ tag.name }}</span>
{% endfor %}
</div>
</div>
|
2.3 해시태그 검색
2.3.1 해시태그의 사용 예시
2.3.2 기본 구조 구현
- View: posts/views.py → tags
- URL: /posts/tags/{tag의 name}/
- Template: templates/posts/tags.html
- posts/views.py
...
def tags(request, tag_name):
return render(request, "posts/tags.html")
|
- posts/urls.py
from posts.views import ..., tags
...
urlpatterns = [
...
path("tags/<str:tag_name>/", tags, name="tags"),
]
|
- templates/posts/tags.html
{% extends 'base.html' %}
{% block content %}
<nav>
<h1>
<a href="{% url 'posts:feeds' %}">Pystagram</a>
</h1>
<a href="{% url 'posts:post_add' %}">Add Post</a>
<a href="{% url 'users:logout' %}">Logout</a>
</nav>
<div id="tags">
<header class="tags-header">
<h2>#{{ tag_name }}</h2>
<div>게시물 1,094</div>
</header>
<div class="post-grid-container">
def tags(request, tag_name):
tag = HashTag.objects.get(name=tag_name)
posts = Post.objects.filter(tags=tag)
context = {
"tag_name": tag_name,
"posts": posts,
}
return render(request, "posts/tags.html", context)
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
<div class="post-grid"></div>
</div>
</div>
{% endblock %}
|
2.3.3 View에서 해시태그를 찾고 해당하는 Post 목록 돌려주기
- posts/views.py
from posts.models import Post, Comment, PostImage, HashTag
def tags(request, tag_name):
tag = HashTag.objects.get(name=tag_name)
print(tag)
return render(request, "posts/tags.html")
|
- posts/views.py
def tags(request, tag_name):
tag = HashTag.objects.get(name=tag_name)
posts = Post.objects.filter(tags=tag)
context = {
"tag_name": tag_name,
"posts": posts,
}
return render(request, "posts/tags.html", context)
|
2.3.4 Post 목록만큼 Grid 랜더링, tag_name 표시
- templates/posts/tags.html
...
</nav>
<div id="tags">
<header class="tags-header">
<h2>#{{ tag_name }}</h2>
<div>게시물 {{ posts.count }}</div>
</header>
<div class="post-grid-container">
{% for post in posts %}
<div class="post-grid"></div>
{% endfor %}
</div>
</div>
|
2.3.5 각각의 Post가 가진 첫 번째 이미지 보여주기
- templates/posts/tags.html
<div class="post-grid-container">
{% for post in posts %}
{% if post.postimage_set.first and post.postimage_set.first.photo %}
<div class="post-grid">
<img src="{{ post.postimage_set.first.photo.url }}" alt="">
</div>
{% endif %}
{% endfor %}
</div>
|
- posts/views.py
def tags(request, tag_name):
try:
tag = HashTag.objects.get(name=tag_name)
except HashTag.DoesNotExist:
# tag_name에 해당하는 HashTag를 찾지 못한 경우 빈 QuerySet을 돌려준다
posts = Post.objects.none()
else:
posts = Post.objects.filter(tags=tag)
context = {
"tag_name": tag_name,
"posts": posts,
}
return render(request, "posts/tags.html", context)
|
- templates/posts/tags.html
<div class="post-grid-container">
{% for post in posts %}
{% if post.postimage_set.first and post.postimage_set.first.photo %}
<div class="post-grid">
<img src="{{ post.postimage_set.first.photo.url }}" alt="">
</div>
{% endif %}
{% empty %}
<p>검색된 게시물이 없습니다</p>
{% endfor %}
</div>
|
2.3.7 피드 페이지의 글에서 해시태그 링크 생성
- templates/posts/feeds.html
<div class="post-content">
{{ post.content|linebreaksbr }}
<div class="post-tags">
{% for tag in post.tags.all %}
<a href="{% url 'posts:tags' tag_name=tag.name %}">#{{ tag.name }}</a>
{% endfor %}
</div>
</div>
|
2.4 해시태그 생성
2.4.1 ManyToManyField 항목 추가 실습
- Terminal
python manage.py shell
from posts.models import Post, HashTag
tag = HashTag.objects.create(name='테스트해시태그')
tag
from users.models import User
user = User.objects.first()
user
post = Post.objects.create(user=user, content='HashTag 테스트용 Post')
post
post.tags.all
post.tags.add(tag)
post.tags.all()
|
2.4.2 해시태그 추가 Input 구현
- templates/posts/post_add.html
...
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<div>
<!-- label의 for속성에는 가리키는 input의 id값을 입력 -->
<label for="id_images">이미지</label>
<input id="id_images" name="images" type="file" multiple>
</div>
{{ form.as_p }}
<div>
<label for="id_tags">해시태그</label>
<input id="id_tags" name="tags" type="text" placeholder="쉼표(,)로 구분하여 여러 태그 입력">
</div>
<button type="submit">게시</button>
</form>
...
|
2.4.3 쉼표로 구분된 문자열 처리
- Terminal
python manage.py shell
tag_string = 'coffee,latte'
tag_string.split(',')
# 중간에 공백이 있는 경우 공백을 처리하지는 못함
tag_string = 'coffee, latte'
tag_string.split(',')
# 좌우 공백을 없애는 함수는 strip()
' 좌우 공백 포함 '.strip()
# list comprehension으로 리스트 내의 공백 문자열 없애기
tag_string = 'coffee,latte'
tag_list = [tag.strip for tag in tag_string.split(',')]
tag_list
|
- posts/views.py
def post_add(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
...
for image_file in request.FILES.getlist("images"):
...
# "tags"에 전달 된 문자열을 분리해 HashTag생성
tag_string = request.POST.get("tags")
if tag_string:
tag_names = [tag_name.strip() for tag_name in tag_string.split(",")]
for tag_name in tag_names:
tag, _ = HashTag.objects.get_or_create(name=tag_name)
# get_or_create로 생성하거나 가져온 HashTag객체를 Post의 tags에 추가한다
post.tags.add(tag)
# 모든 PostImage와 Post의 생성이 완료되면
# 피드페이지로 이동하여 생성된 Post의 위치로 스크롤되도록 한다
url = reverse("posts:feeds") + f"#post-{post.id}"
return HttpResponseRedirect(url)
...
|
728x90
'PYTHON-BACK' 카테고리의 다른 글
#파이썬 34일차_이커머스 클론코딩5_(2) (0) | 2024.08.22 |
---|---|
#파이썬 34일차_이커머스 클론코딩5_(1) (0) | 2024.08.22 |
#파이썬 34일차_이커머스 클론코딩4_(1) (0) | 2024.08.21 |
#파이썬 33일차_이커머스 클론코딩3 (0) | 2024.08.20 |
#파이썬 32일차_이커머스 클론코딩2 (0) | 2024.08.19 |