youtube를 클론하는 프로젝트를 하고 있습니다. 참고로 영상은 file입니다. 유튜브 재생도 되어야 합니다! 다음 프로젝트를 완성하세요.
1. 작동되는 models.py를 각 반에 업로드 하세요.
2. 작동되는 views.py를 각 반에 업로드 하세요.
/tube
/tube/1 # 영상 재생이 되어야 합니다. 뎃글을 달 수 있어야 합니다.
/tube/create/ # 로그인한 사용자만 보기 가능
/tube/update/<int:pk>/ # 로그인한 사용자만 보기 가능, 자신의 글만 업데이트 할 수 있습니다.(수정하기 버튼은 자신의 글에서만 나옵니다.)
/tube/delete/<int:pk>/ # 로그인한 사용자만 보기 가능, 자신의 글만 지울 수 있습니다.(삭제하기 버튼은 자신의 글에서만 나옵니다.)
/tube/tag/<str:tag>/ # 해당 태그가 달린 목록을 가져와야 합니다.
/tube/?q='keyword' # 해당 키워드가 포함된 title, content가 있는 목록을 가져와야 합니다.
/accounts/signup/
/accounts/login/
/accounts/logout/ # 로그인한 사용자만 보기 가능
/accounts/profile/ # 로그인한 사용자만 보기 가능
mkdir tube
cd tube
python -m venv venv
.\venv\Scripts\activate
pip install django
pip install pillow
django-admin startproject config .
pip freeze > requirements.txt -> 현재 가상환경 정보 저장
python manage.py migrate
python manage.py startapp accounts
python manage.py startapp tube
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
thumbnail_image = models.ImageField(upload_to="blog/images/%Y/%m/%d/", blank=True)
video_file = models.FileField(upload_to="blog/files/%Y/%m/%d/", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField("Tag", blank=True)
def __str__(self):
return self.title
Tag라는 별도의 모델과 N : M으로 게시물 클래스와 연결시켰다.
class Comment(models.Model):
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.message
작성자 객체를 외래키(ForeignKey)로 1 : N에서 1에 해당하는 User를 댓글(N)에 연결시켰다.
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
unique=True
옵션으로 태그의 이름이 유일하도록 설정하였다.
from django.contrib import admin
from .models import Post, Comment, Tag
admin.site.register(Post)
admin.site.register(Comment)
admin.site.register(Tag)
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
leehojun
leehojun@gmail.com
이호준1234!
python manage.py runserver
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path("admin/", admin.site.urls),
path("tube/", include("tube.urls")),
path("accounts/", include("accounts.urls"))
]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
from django.urls import path
from . import views
urlpatterns = [
path("", views.tube_list, name="tube_list"),
path("<int:pk>/", views.tube_detail, name="tube_detail"),
]
# tube > views.py
from django.shortcuts import render
from .models import Post, Comment, Tag
from .forms import CommentForm
def tube_list(request):
posts = Post.objects.all()
return render(request, "tube/tube_list.html", {"posts": posts})
템플릿은 tube_list를 참조한다.
def tube_detail(request, pk):
post = Post.objects.get(pk=pk)
form = CommentForm()
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
author = request.user
message = form.cleaned_data["message"]
c = Comment.objects.create(author=author, message=message, post=post)
c.save()
return render(request, "tube/tube_detail.html", {"post": post, "form": form})
특정 Post 객체를 가져오고, 사용자가 댓글을 입력하고 제출(Post 요청)하면 해당 내용의 유효성을 검사하고 통과된 데이터로 Comment 객체를 생성해 DB에 저장하는 코드이다.
최종적으로 tube/tube_detail.html
템플릿을 렌더링하고, 이 템플릿에서 사용할 수 있도록 post와 form 객체를 전달한다.
사용자가 특정 게시글의 상세 페이지를 열람하거나, 해당 페이지에서 댓글을 작성할 때 호출된다.
def tube_tag(request, tag):
posts = Post.objects.filter(tags__name__iexact=tag)
return render(request, "tube/tube_list.html", {"posts": posts})
Url에서 전달받은 tag 값을 가지는 Post 객체들을 DB에서 가져온다.
사용자가 특정 태그를 클릭하여 그 태그에 해당하는 게시글의 목록을 보고자 할 때 호출된다.
from django import forms
class CommentForm(forms.Form):
message = forms.CharField(widget=forms.Textarea)
사용자로부터 데이터를 안전하게 입력받고, 이를 검증한 후 클린한 데이터를 제공한다.
# tube > tube_list.html
{% for post in posts %}
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p>{{ post.author }}</p>
<p>{{ post.comments }}</p>
<p>{{ post.tags }}</p>
{% for comment in post.comments.all %}
<p>{{ comment.message }}</p>
{% endfor %}
{% for tag in post.tags.all %}
<p>{{ tag.name }}</p>
{% endfor %}
<hr>
{% endfor %}
# tube > tube_detail.html
<h1>{{ post.title }}</h1>
<p>{{ post.content|linebreaks }}</p>
<p>{{ post.author }}</p>
<video controls>
<source src="{{ post.video_file.url }}"></source>
</video>
{% for tag in post.tags.all %}
<a href="/tube/tag/{{ tag.name }}">#{{ tag.name }}</a>
{% endfor %}
{% for comment in post.comments.all %}
<p>{{ comment.message }}</p>
{% endfor %}
<form action="" method="post">
{% csrf_token %}
{{ form }}
<input type="submit">
</form>
<video controls>
태그를 통해 사용자가 올린 비디오를 보여준다.
from django.urls import path
from . import views
urlpatterns = [
path("signup/", views.signup, name="signup"),
path("login/", views.login, name="login"),
path("logout/", views.logout, name="logout"),
path("profile/", views.profile, name="profile"),
]
# views.py
# Django 프로젝트의 설정값 불러오기
from django.conf import settings
# 로그인 필요시에 사용하는 데코레이터
from django.contrib.auth.decorators import login_required
# 사용자 생성 폼
from django.contrib.auth.forms import UserCreationForm
# 사용자 인증에 필요한 기능들
from django.contrib.auth.views import LoginView, LogoutView
# 템플릿을 렌더링하여 HTTP 응답을 생성하는 함수
from django.shortcuts import render
# 객체를 생성하는 폼을 표시하고, 유효한 데이터가 제출되면 이를 저장
from django.views.generic import CreateView
signup = CreateView.as_view(
form_class = UserCreationForm,
template_name = 'form.html',
success_url = settings.LOGIN_URL,
)
login = LoginView.as_view(
template_name = 'form.html',
)
logout = LogoutView.as_view(
next_page = settings.LOGIN_URL,
)
@login_required
def profile(request):
return render(request, 'accounts/profile.html')
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit">
</form>
폼 필드들을 HTML 테이블 요소로 출력한다.
폼 제출 후 서버에서는 이 데이터를 처리하여 새 객체를 생성하거나, 기존 객체를 업데이트하게 된다.
<h1>login page</h1>
<p>{{ user }}님 환영합니다!</p>
<p>email: {{ user.email }}</p>
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<input type="submit" value="로그아웃">
</form>
현재 로그인한 사용자의 정보를 보여주고, 로그아웃 버튼을 제공한다.
# tube > urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.tube_list, name="tube_list"),
path("<int:pk>/", views.tube_detail, name="tube_detail"),
path("create/", views.tube_create, name="tube_create"),
path("<int:pk>/update/", views.tube_update, name="tube_update"),
path("<int:pk>/delete/", views.tube_delete, name="tube_delete"),
path("tag/<str:tag>/", views.tube_tag, name="tube_tag"),
]
# views.py
from django.shortcuts import render
from .models import Post, Comment, Tag
from .forms import CommentForm
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
def tube_list(request):
posts = Post.objects.all()
return render(request, "tube/tube_list.html", {"posts": posts})
def tube_detail(request, pk):
post = Post.objects.get(pk=pk)
form = CommentForm()
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
author = request.user
message = form.cleaned_data["message"]
c = Comment.objects.create(author=author, message=message, post=post)
c.save()
return render(request, "tube/tube_detail.html", {"post": post, "form": form})
def tube_tag(request, tag):
posts = Post.objects.filter(tags__name__iexact=tag)
return render(request, "tube/tube_list.html", {"posts": posts})
@login_required
def tube_create(request):
if request.method == "POST":
title = request.POST["title"]
content = request.POST["content"]
# author_id를 추가
author = request.user
post = Post.objects.create(title=title, content=content, author=author)
post.save()
return redirect("tube_list")
return render(request, "tube/tube_create.html")
새 게시물 작성 요청이 제출된 경우(Post 요청) 사용자가 입력한 내용을 가져온다.
가져온 데이터로 Post 객체를 생성하고, DB에 저장한다.
POST 요청이 아닌 경우 (예: 처음 페이지를 방문하는 경우) tube/tube_create.html
템플릿을 렌더링하여 보여준다.
@login_required
def tube_update(request, pk):
post = Post.objects.get(pk=pk)
# 내가 쓴 게시물만 업데이트 가능
if post.author != request.user:
return redirect("tube_list")
if request.method == "GET":
return render(request, "tube/tube_update.html", {"post": post})
if request.method == "POST":
title = request.POST["title"]
content = request.POST["content"]
post.title = title
post.content = content
post.save()
return redirect("tube_detail", pk)
현재 로그인한 사용자가 게시글의 작성자가 아니라면, 수정이 불가능하므로 게시글 목록 페이지로 리다이렉트한다.
요청이 GET 방식인 경우(게시글 수정 폼을 처음 불러올 때) 게시글 수정 폼을 렌더링하고, 폼에서 사용할 수 있도록 post 객체를 전달한다.
게시글 수정 폼이 제출된 경우(Post 방식) 사용자가 입력한 제목과 내용으로 Post 객체의 내용을 수정하고, 이를 데이터베이스에 저장한다.
Post 객체 수정 후, 해당 게시글의 상세 페이지로 리다이렉트한다.
@login_required
def tube_delete(request, pk):
post = Post.objects.get(pk=pk)
# 내가 쓴 게시물만 삭제 가능
if post.author != request.user:
return redirect("tube_list")
if request.method == "POST":
post.delete()
return redirect("tube_list")
현재 로그인한 사용자가 게시글의 작성자가 아니라면, 삭제가 불가능하므로 게시글 목록 페이지로 리다이렉트한다.
요청이 POST 방식인 경우, 즉 게시글 삭제를 요청했을 때 Post 객체 삭제 후, 게시글 목록 페이지로 리다이렉트한다.
# tube_detail.html
...
<!-- 로그인을 했고, 내가 이 글에 글쓴이라고 한다면 삭제와 업데이트 버튼 노출 -->
{% if user.is_authenticated and user == post.author %}
<a href="{% url 'tube_update' post.pk %}">수정</a>
<form action="{% url 'tube_delete' post.pk %}" method="post">
{% csrf_token %}
<input type="submit" value="삭제">
</form>
{% endif %}
...
# tube_list
<form action="" method="get">
<input type="text" name="q" value="{{ request.GET.q }}">
<input type="submit" value="검색">
</form>
# tube > views.py
from django.shortcuts import render
from .models import Post, Comment, Tag
from .forms import CommentForm
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
def tube_list(request):
# 검색 q가 있을 경우 title과 content에서 해당 내용이 있는지 검색
q = request.GET.get("q", "")
if q:
posts = Post.objects.filter(title__contains=q) | Post.objects.filter(
content__contains=q
)
return render(request, "tube/tube_list.html", {"posts": posts, "q": q})
posts = Post.objects.all()
return render(request, "tube/tube_list.html", {"posts": posts})