본문 바로가기
카테고리 없음

Youtube 사이트 만들어보기 Django 활용편

by gosikoca 2024. 2. 29.

오늘은 django를 가지고 완전히 같은 형태는 아니지만 내가 직접 해 볼 수 있는 상태에서 Yotube의 형태를 만들면서 공부를 해보려 한다. 지금까지 django를 공부하면서 배웠던 내용을 최대한 많이 활용하고 단계별로 정리해보려 한다.

 

차례대로 정리를 하면서 오늘의 프로젝트를 진행해보려 한다.

사이트를 구현하기 위해 조건들과 URL을 구성할 형태를 아래와 같이 정하였다.

/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/          # 로그인한 사용자만 보기 가능

 

이대로 프로젝트를 진행하기 위해 아래 2가지의 명령어를 통하여 App 을 추가해주고 

 

python manage.py startapp accounts,

python manage.py startapp tube

 

settings.pyINSTALLED_APPS 아래와 같이 변경하여 새로 만든 App 을 등록하여 줍니다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts',
    'tube',
]

 

settings.py  에 TEMPLATES 을 아래와 같이 변경하여 templates 의 경로를 정의해준다.

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]

 

정적 파일(static files)과 미디어 파일(media files)을 다루기 위한 설정

STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]

MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"

 

파일 경로 설정에 맞추어 파일 생성

mkdir static
mkdir media

 

tube > models.py 수정 

 

admin 페이지의 db를  Post 하는 창을 구성하는 요소들을 만들어 줍니다.  그 중 기억해야하는 요소들을 보자면

 

author: 글의 저자를 나타내는 외래 키(ForeignKey) 필드로, Django의 기본 사용자 모델(User)과 관련되어 있습니다. on_delete=models.CASCADE는 관련된 사용자가 삭제될 경우 해당 글도 함께 삭제되도록 설정합니다.

 

tags: 글에 할당된 태그들을 나타내는 다대다(ManyToMany) 관계 필드입니다. "Tag"라는 모델과의 관계를 나타내며, blank=True는 이 필드가 비어 있어도 되도록 합니다.

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


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


class Tag(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

 

tube > admin.py

admin 페이지의 구성요소가 잘 나타나도록 설정

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

 

admin 페이지에서 로그인 해주기 위해 회원가입을 진행한다.

createsuperuser 아래의 명령어를  기입하면 순서대로 요구하는 4가지의 항목이 뜨는데 차례로 기입하여 회원가입을 완료하면 된다.

python manage.py createsuperuser

name
email
password
password 확인

 

서버를 열어서 admin페이지에서 로그인해보자 

http://127.0.0.1:8000/ 

위의 주소로 들어가지면서 django의 페이지가 보일 것이다. 위치를 admin 창으로 옮기기 위해 

 

http://127.0.0.1:8000/admin

여기로 이동하면 Django 관리 페이지로 이동할 것이다. 이동 후 아까 가입한 이름과 비밀번호로 로그인한다.

python manage.py runserver

 

 

로그인 후 화면을 보면 우리가 관리창을 만들기 위해 코드로 작성해 준 models.py 의 내용과 admin.py 의 코드가 합쳐져 잘 반영되어 있는 것을 확인 할 수 있다.

 

페이지에서 Post 를 클릭해서 오른쪽 상단에 있는 Post 추가를 눌러

 

우리가 꾸며 작성해 놓은 modelse.py 의 Post Class내용처럼 잘 구성이 되어 있다. 이를 이용해 게시글 3개를 만들어보자 

 

아래처럼 각 항목을 설정해서 만들어 보았다. 

다음 config > urls.py 에서 각 app urls.py 와 include 를 사용해 연결해준다.

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 > urls.py

우리가 구성하고자 하는 page를 하나씩 구현해보기 위해 <int:pk> 를 활용하여 값을 받아오는 형식으로 tube_detail 이라는 페이지를 만들준비를 하고 

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

urls.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})


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})

 

tube > forms.py

웹 애플리케이션에서 댓글을 입력받기 위한 폼을 정의하고, 이를 통해 데이터를 수집하고 처리할 수 있도록 forms.py 라는 파일을 tube라는 app안에 만들고 tube_detail 이라는 함수에서 작성한 댓글기능이 잘 작동할 수 있도록 class 를 작성한다.

from django import forms


class CommentForm(forms.Form):
    message = forms.CharField(widget=forms.Textarea)

 

mkdir templates 라는 명령어를 통해 tube라는 전체 파일 아래 templates 파일을 만들고 그 안에 2가지의 app 과 연동할 수 있도록 tube, accounts 라는 폴더를 만든다. 그리고 앞으로 만들 함수와 관련된 templates를 미리 작성하면 이렇게 총 6개의 html 파일을 만들어 준다.

📦templates
 ┣ 📂accounts
 ┃ ┣ 📜form.html
 ┃ ┗ 📜profile.html
 ┗ 📂tube
 ┃ ┣ 📜tube_create.html
 ┃ ┣ 📜tube_detail.html
 ┃ ┣ 📜tube_list.html
 ┃ ┗ 📜tube_update.html

templates > tube > tube_list.html

tube_list.html 안에 코드를 작성해 줌으로써 admin Post 에서 만들어 두었던 파일들의 간단한 내용들이 눈에 보이도록 한다.

{% 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 %}

templates > blog > tube_detail.html

아래와 같이 상세페이지도 작성해 준다. 서버를 보면 아직  accounts에 url이 없기 때문에 돌아가지 않기에, 보고 싶다면 urls.py를 잠시 비워두고 실행하면 된다. 

<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>

accounts > urls.py

accoounts 의 urls.py 와 views.py 도 우리가 만들고자 하는 형식에 맞추어 코드를 작성해준다.

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

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
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')

 

2개의 파일을 작성하고 나면 각 코드에 필요한 html이 필요핟. tube 때 했던 방식과 그대로 아까 만들어 놓았던 html파일에 코드를 채워준다.

form.html

<form action="" method="post">
    {% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit">
</form>

profile.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>

part2 로그인한 사용자만 create, update, delete 구현
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 > 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")


@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)


@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")

 

forms.py를 만들지 않고 이렇게 html로 바로 전송할 수도 있습니다!
다만 이런 하드코딩은 좋은 선택지는 아닙니다!
tube_update.html, tube_create.html

<form action="" method="post">
    {% csrf_token %}
    <input type="text" name="title">
    <input type="text" name="content">
    <input type="submit">
</form>

 

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>

<!-- 로그인을 했고, 내가 이 글에 글쓴이라고 한다면 삭제와 업데이트 버튼 노출 -->
{% 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 %}

{% 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>

 

tube_list.html 에 내용추가

- part3 검색 기능 구현

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

{% 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 %}

 

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})


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


@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)


@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")

 

delete

삭제버튼!

tag

update

 

create 

검색기능 시연

 

회고

간단하게 만든만큼 UI도 를 신경을 쓰지 않았고 백엔드적인 요소로 django 만 집중을 해보았는데 아직 익숙치 않아서 어려운 부분이 있다. 계속 복습과 작은 이런 실습으로 내 것으로 만들겠다.

 

직접해보다 보니 UI와 프론트엔드 백엔드 간의 흐름이나 구조들이 눈에 조금씩 들어오고 개발에 대한 흥미가 더욱 생겼다. models.py 나 views.py 와 같은 로직을 구현하는 것에서 아직 많은 시간이 걸린다. 그러나 점차 속도가 붙을 것이라는 확신이 이번에 직접 해보면서 생겼다.