[day-38] 유튜브 클론 코딩(간단)

Joohyung Park·2024년 2월 29일
0

[모두연] 오름캠프

목록 보기
72/95

설계

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
  • settings.py 설정 변경하기
    • ALLOWED_HOSTS
    • INSTALLED_APPS
    • TEMPLATES
    • LANGUAGE_CODE = "ko-kr"
    • TIME_ZONE = "Asia/Seoul"
    • STATIC_URL, STATICFILES_DIRS
    • MEDIA_ROOT, MEDIA_URL

tube(앱) > models.py 선언

  • 게시물 클래스, 객체 선언
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 옵션으로 태그의 이름이 유일하도록 설정하였다.

관리자 페이지 연동(tube > admin.py)

from django.contrib import admin
from .models import Post, Comment, Tag

admin.site.register(Post)
admin.site.register(Comment)
admin.site.register(Tag)

모델 변경사항 추적 및 DB 적용

python manage.py makemigrations
python manage.py migrate

관리자 계정 생성

python manage.py createsuperuser

leehojun
leehojun@gmail.com
이호준1234!

서버 실행 후 게시물 업로드

python manage.py runserver

config(프로젝트 명) > 사용할 앱의 기초 url 설정

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)

tube앱 세부 url 설정

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앱 화면에 보여줄 내용 설정

# 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> 태그를 통해 사용자가 올린 비디오를 보여준다.

accounts앱 url 설정

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"),
]

accounts앱 화면에 보여줄 내용 설정

# 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앱 CRUD url 추가

# 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"),
]

tube앱 CRUD 화면에 보여줄 내용 추가

# 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앱 세부사항 템플릿에 삭제, 수정 버튼 추가

# 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앱 포스트 목록 템플릿에 검색 기능 추가

# tube_list

<form action="" method="get">
    <input type="text" name="q" value="{{ request.GET.q }}">
    <input type="submit" value="검색">
</form>

tube앱 화면에 보여줄 내용에 검색 추가

# 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})
profile
익숙해지기 위해 기록합니다

0개의 댓글