Python | Django | Video Clipサイトの実装・deployまでの流れ

2023年3月29日

Pythonには,DjangoというWebアプリケーションフレームワークがある.フレームワークのため,Djangoを利用するとWebアプリを通常よりも短時間で開発することが可能になる.

本記事では,Djangoを利用したWebサービス(Video Clipサイト)を作成し,Herokuを利用した公開するまでの流れを以下に記す.

実施環境

Windows 11
Python 3.11
Django 4.1.7
Visual Studio Code (VS Code) 1.76.0

作成方法

Hello Worldの出力方法

Desktopにディレクトリを作成する.

以下Visual Studio Code (VS Code)を起動させる.

以下画面が表示されるので,"File"をクリックし,"Open Folder"を選択し,上記で作成したディレクトリである"230304_master_Django"を選択する.

選択後,以下画面になるので,"フォルダの選択"をクリックする.

選択後,VS Codeにはディレクトリ名が表示される."Terminal"をクリックする.

以下画面が表示されるので,"New Terminal"を選択する.

以下画面のように,Terminalが表示される.

以下コマンドを実行する.

$ pip install django

Requirement already satisfied: django in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (4.1.5)     
Requirement already satisfied: asgiref<4,>=3.5.2 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from django) (3.6.0)
Requirement already satisfied: sqlparse>=0.2.2 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from django) (0.4.3)
Requirement already satisfied: tzdata in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from django) (2022.7)

[notice] A new release of pip available: 22.3.1 -> 23.0.1      
[notice] To update, run: python.exe -m pip install --upgrade pip

続けて,以下コマンドを実行する.

$ django-admin startproject movieclip

以下のようにプロジェクトディレクトリが作成される.

movieclipという名のディレクトリの下に同じ名前のディレクトリがあるのは紛らわしいので,以下のようにトップのプロジェクトディレクトリの名前を変更する.

以下コマンドを実行すると,"230303_Django_test"ディレクトリの下のファイル構成を確認できる."230303_Django_test"ディレクトリの下には,"movieclip-project"ディレクトリが格納されている.

$ ls


    ディレクトリ: C:\Users\shiro\Desktop\dev_test\230303_Djanto_test 


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----     2023/03/02 木      7:10                movieclip-project  

以下コマンドを実行し,現在位置を"movieclip_project"上に移動する.

$ cd movieclip-project

移動後,以下コマンドを実行する.

$ pip install pipenv

Requirement already satisfied: pipenv in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (2022.11.30)
Requirement already satisfied: certifi in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from pipenv) (2022.12.7)
Requirement already satisfied: setuptools>=36.2.1 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from pipenv) (65.5.0)
Requirement already satisfied: virtualenv-clone>=0.2.5 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from pipenv) (0.5.7)
Requirement already satisfied: virtualenv in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from pipenv) (20.17.1)
Requirement already satisfied: distlib<1,>=0.3.6 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from virtualenv->pipenv) (0.3.6)
Requirement already satisfied: filelock<4,>=3.4.1 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from virtualenv->pipenv) (3.8.2)
Requirement already satisfied: platformdirs<3,>=2.4 in c:\users\shiro\appdata\local\programs\python\python311\lib\site-packages (from virtualenv->pipenv) (2.6.0)

[notice] A new release of pip available: 22.3.1 -> 23.0.1
[notice] To update, run: python.exe -m pip install --upgrade pip 

以下コマンドを実行し,仮想環境を作成する.

$ pipenv install

Creating a virtualenv for this project...
Pipfile: C:\Users\shiro\Desktop\dev_test\230303_Djanto_test\movieclip-project\Pipfile
Using C:/Users/shiro/AppData/Local/Programs/Python/Python311/python.exe (3.11.1) to create virtualenv...
[  ==] Creating virtual environment...created virtual environment CPython3.11.1.final.0-64 in 433ms
  creator Venv(dest=C:\Users\shiro\.virtualenvs\movieclip-project-6xXw4Rco, clear=False, no_vcs_ignore=False, global=False, describe=CPython3Windows)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=C:\Users\shiro\AppData\Local\pypa\virtualenv)
    added seed packages: pip==23.0.1, setuptools==67.1.0, wheel==0.38.4
  activators BashActivator,BatchActivator,FishActivator,NushellActivator,PowerShellActivator,PythonActivator

Successfully created virtual environment!
Virtualenv location: C:\Users\shiro\.virtualenvs\movieclip-project-6xXw4Rco
Creating a Pipfile for this project...
Pipfile.lock not found, creating...
Locking [packages] dependencies...
Locking [dev-packages] dependencies...
Updated Pipfile.lock (ed6d5d614626ae28e274e453164affb26694755170ccab3aa5866f093d51d3e4)!
Installing dependencies from Pipfile.lock (51d3e4)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.

以下のように,"movieclip_project"ディレクトリの下に,"Pipfile"と"Pipfile.lock"が作成される.

以下コマンドを実行し,Djangoをインストールする.

$ pipenv install django

Installing django...
Pipfile.lock (51d3e4) out of date, updating to (8362fc)...           
Locking [packages] dependencies...
Locking [dev-packages] dependencies...                               
Updated Pipfile.lock (af42abefb766e975f7680f10368735353569fb4fe0114e59496a7202658362fc)!
Installing dependencies from Pipfile.lock (8362fc)...
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run. 

以下コマンドを実行し,仮想環境内にsubshellを起動させる.

$ pipenv shell

Launching subshell in virtual environment...
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

新機能と改善のために最新の PowerShell をインストールしてください!https://aka.ms/PSWindows

以下コマンドを実行し,"movieclip_app"を作成する.

$ django-admin startapp movieclipApp

以下のように,"movieclip-project"ディレクトリの下に,"movieclipApp"を作成できた.

“settings.py"の"INSTALLED_APPS"に"movieclipApp"を追記する.

~
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'movieclipApp', # 追記箇所
]
~

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.urls import path
from movieclipApp import views # 追記箇所

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'), # 追記箇所
]

“views.py"を以下のように編集する.

from django.shortcuts import render

def home(request): # 以下追記
    return render(request, 'movieclipApp/home.html')

以下のように,"movieclipApp"ディレクトリ下に,"templates"ディレクトリを作成し,その下に"movieclipApp"ディレクトリを作成する.その中に,"home.html"を作成する.

“home.html"を以下のように編集する.

Hello World!

Terminalに戻り,以下コマンドを実行し,"http://127.0.0.1:8000/"をクリックすると,ブラウザが開く.

$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
March 02, 2023 - 07:25:45
Django version 4.1.7, using settings 'movieclip.settings'    
Starting development server at http://127.0.0.1:8000/        
Quit the server with CTRL-BREAK.

ブラウザには,以下文言が出力される.

Bootstrapの利用

base.htmlを"movieclipApp/templates/movieclipApp"ディレクトリの下に格納する."base.html"は以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="#" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="#" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="#">Dashboard</a>
      <a class="p-2 text-dark" href="#">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="#">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="#">Sign up</a>
    {% endif %}
  </div>

  {% block content %}
  {% endblock %}
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>


</body>

</html>

“home.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
Hello World! This is my website!
{% endblock %}

Terminalに移動し,以下コマンドを実行する.

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying sessions.0001_initial... OK

その後,以下コマンドを実行し,"http://127.0.0.1:8000/"をクリックすると,ブラウザが開く.

$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 02, 2023 - 09:31:52
Django version 4.1.7, using settings 'movieclip.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

ブラウザは以下のように表示され,"Log In"や"Sign up"が表示されるようになった.

“settings.py"を以下のように編集する.

from pathlib import Path
import os # 追記
~
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # 編集
    }
}
~
STATIC_URL = 'static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static') # 追記
~

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static # 追記
from django.conf import settings # 追記

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # 追記

“myfavicon.ico"と"custom.css"を以下のように作成した.なお,"myfavicon.ico"はWindowsのペイントを利用し,サイズは310px * 310pxにした.

“movieclipApp"ディレクトリの下に"static"ディレクトリを作成する.当該ディレクトリの下に"myfavicon.ico"と"custom.css"を格納する.

なお,"custom.css"の内容は以下になる.

.jumbotron {
  background-color: #EBF4FA;
}

.jumbotron-heading {
  font-weight: 300;
}

.jumbotron .container {
  max-width: 51rem;
}

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet"> <!-- # 編集箇所 -->
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" /> <!-- # 編集箇所 -->
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="#">Dashboard</a>
      <a class="p-2 text-dark" href="#">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="#">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="#">Sign up</a>
    {% endif %}
  </div>

  {% block content %}
  {% endblock %}
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>


</body>

</html>

その後,以下コマンドを実行し,"http://127.0.0.1:8000/"をクリックすると,ブラウザが開く.

$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 02, 2023 - 10:24:50
Django version 4.1.7, using settings 'movieclip.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

 ブラウザを開くと以下のようにファビコンが作成される.

“Sign Up", “Log In", “Log Out"の実装

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views # 追記
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'), # 追記
    path('login', auth_views.LoginView.as_view(), name='login'), # 追記
    path('logout', auth_views.LogoutView.as_view(), name='logout'), # 追記
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render
from django.urls import reverse_lazy # 追加(2-4)
from django.views import generic
from django.contrib.auth.forms import UserCreationForm

def home(request):
    return render(request, 'movieclipApp/home.html')

class SignUp(generic.CreateView): # 追加(9-12)
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

“templates"ディレクトリ内に,"registration"ディレクトリを作成し,その中に"signup.html"を作成する."signup.html"は以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h3>Sign Up!</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Sign Up</button>
</form>
{% endblock %}

なお,以下コマンドを実行し,"http://127.0.0.1:8000/"をクリックすると,ブラウザが開く.

$ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 02, 2023 - 11:32:00
Django version 4.1.7, using settings 'movieclip.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

ブラウザで,"http://127.0.0.1:8000/signup"と入力すると以下画面が表示される.

“registration"ディレクトリ内に"login.html"を作成する."login.html"は以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h3>Login!</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Login</button>
</form>
{% endblock %}

ブラウザで,"http://127.0.0.1:8000/login"と入力すると以下画面が表示される.

“models.py"を以下のように編集する.

from django.db import models

class Clip(models.Model):
    title = models.CharField(max_length=255)

class Movie(models.Model):
    title = models.CharField(max_length=255)
    url = models.URLField()
    youtube_id = models.CharField(max_length=255)
    clip = models.ForeignKey(Clip, on_delete=models.CASCADE)

“admin.py"を以下のように編集する.

from django.contrib import admin
from .models import Clip, Movie

admin.site.register(Clip)
admin.site.register(Movie)

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="#">Dashboard</a>
      <a class="p-2 text-dark" href="{% url 'logout' %}">Log Out</a><!-- # 編集箇所 -->
      {% else %}
      <a class="p-2 text-dark" href="{% url 'login' %}">Log In</a><!-- # 編集箇所 -->
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="{% url 'signup' %}">Sign up</a> <!-- # 編集箇所 -->
    {% endif %}
  </div>

  {% block content %}
  {% endblock %}
  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>


</body>

</html>

Terminalに移動し,以下コマンドを実行する.

$ python manage.py makemigrations

Migrations for 'movieclipApp':
  movieclipApp\migrations\0001_initial.py
    - Create model Clip
    - Create model Movie

続けて,以下コマンドを実行する.

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, movieclipApp, sessions
Running migrations:
  Applying movieclipApp.0001_initial... OK

以下コマンドを実行し,入力していく.

$ python manage.py createsuperuser

Username (leave blank to use 'shiro'): shiro
Email address:
Password:
Password (again):
Superuser created successfully.

以下コマンドを実行する.

$ python manage.py runserver

ブラウザで,"http://127.0.0.1:8000/login"と入力すると以下画面が表示される."Sign up"と"Log In"をクリックすると,以下画面に遷移する.

“Sign up"をクリックすると,以下画面に遷移する.

“Log In"をクリックすると,以下画面に遷移する.

“Sign Up"画面に戻り,以下のように情報を入力し,"Sign Up"ボタンをクリックする.

以下画面に遷移するので,"Log In"をクリックする.

以下画面に遷移するので,ログインのための情報を入力し,"Login"ボタンをクリックする.

以下画面に遷移する.URLに"127.0.0.1:8000″を入力して移動する.

以下画面に遷移する.ログインが成功しているのが確認できる."Log Out"をクリックする.

以下画面に遷移する.

“settings.py"を以下のように編集する.

~
ALLOWED_HOSTS = []

LOGIN_URL = 'login' # 以下追記
LOGIN_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
~

“127.0.0.1:8000″の画面に戻り,"Log In"をクリックする.

以下画面に遷移するので,情報を入力し,"Login"をクリックする.

以下のように,ログインされた状態で表示される."Log Out"をクリックする.

以下のように,ログアウトされた状態で表示される.

“127.0.0.1:8000/admin"を入力すると,以下画面が表示されるので,superuserの情報を入力し,"Log in"をクリックする.

以下のように,Django administration画面に入ることができる.

ViewクラスへのCRUD(Create, Read, Update, Delete)の実装

CRUDとは,Webサービスにおける基本機能である以下機能の頭文字から成る言葉である.データを扱う上で重要のため,CRUDに基づき実装をしていく.

  • Create: 作成する
  • Read: 読み込む
  • Update: 更新する
  • Delete: 削除する

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from .models import Clip # 追加

def home(request):
    return render(request, 'movieclipApp/home.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

class CreateClip(generic.CreateView): # 以下追加
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

“movieclipApp/templates/movieclipApp"ディレクトリの下に"create_movieclip.html"を作成する."create_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Create Movieclip">
</form>
{% endblock %}

Terminalにて,以下コマンドを実行する.

$ python manage.py runserver

ブラウザにて,URLに"127.0.0.1:8000/mvclip/create"を入力すると以下画面が表示される.

以下のようにクリップするMovieのタイトルを入力し,"Create Movieclip"をクリックする.

以下画面に遷移する.

上記のタイトルが格納されているかを確認するため,"127.0.0.1:8000/admin"に移動すると,以下画面に遷移する.情報を入力し,ログインを行う.

ログイン後,"Clips"をクリックすると,"Clip object(1)"があるので,クリックする.

以下のように,上記で入力したタイトルが格納されていることが確認できた.

ユーザーと関連付けるため,"models.py"を以下のように編集する.

from django.db import models
from django.contrib.auth.models import User # 追記

class Clip(models.Model):
    title = models.CharField(max_length=255)
    user = models.ForeignKey(User, on_delete=models.CASCADE) # 追記

class Movie(models.Model):
    title = models.CharField(max_length=255)
    url = models.URLField()
    youtube_id = models.CharField(max_length=255)
    clip = models.ForeignKey(Clip, on_delete=models.CASCADE)

その後,Terminalにて以下コマンドを実行し,"movieclipApp/migrations/0001_initial.py"を削除する.

$ rm db.sqlite3

削除後,Terminalにて以下2つのコマンドを実行する.

$ python manage.py makemigrations

Migrations for 'movieclipApp':
  movieclipApp\migrations\0001_initial.py
    - Create model Clip
    - Create model Movie

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, movieclipApp, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying movieclipApp.0001_initial... OK
  Applying sessions.0001_initial... OK

以下コマンドによって,スーパーユーザーを作成し,runserverを実行する.

$ python manage.py createsuperuser

Username (leave blank to use 'shiro'): shiro
Email address:
Password:
Password (again):
Superuser created successfully.

$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 09, 2023 - 07:40:18
Django version 4.1.7, using settings 'movieclip.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect # 編集
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form): # 以下追加
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

Terminalにて以下コマンドを実行し,"http://127.0.0.1:8000/"にアクセスする.

$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
March 09, 2023 - 07:40:18
Django version 4.1.7, using settings 'movieclip.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

サイトにて,"rohan"という名のUsernameを作成し,ログインを実行する."http://127.0.0.1:8000/mvclip/create"に移動し,"JoJo’s Bizarre Adventure"というタイトルのMovieclipを作成する.

作成後,"http://127.0.0.1:8000/admin"に移動し,superuser nameを使ってログインする.以下画面に遷移するので,"Clip object (1)"をクリックする.

以下のように,作成したTitleとUserが紐づいていることが確認できる.

サインアップ後に自動的にログインする実装を行う."views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login # 追加
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form): # 追加(16~21行目)
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

Terminalにて"python manage.py runserver"を実行し,"http://127.0.0.1:8000/"にアクセスし,サインアップを利用して新たなユーザーを作成すると,以下のように自動的にログインすることができる.

Detail Viewの利用するため,"urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'), # 追加
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request): # 以下追加(11-12行目)
    return render(request, 'movieclipApp/dashboard.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

“movieclipApp/templates/movieclipApp"に"dashboard.html"を作成し,以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
Hello World! This is dashboard page!
{% endblock %}

“views.py"にDetailViewを追加するため,以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView): # 以下追加
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

“movieclipApp/templates/movieclipApp"に"detail_movieclip.html"を作成し,以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>{{ clip.title }}</h2>
<h4 class="tesst-muted">{{ clip.user.username }}</h4>
{% endblock %}

Terminalにて"python manage.py runserver"を実行し,"http://127.0.0.1:8000/mvclip/1″にアクセスすると,以下のように作成したタイトルと作成者が表示された.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView): # 以下追加
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

“create_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Create Movieclip</h2> {% comment %} 追記箇所 {% endcomment %}
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Create</button> {% comment %} 追記箇所 {% endcomment %}
</form>
{% endblock %}

“http://127.0.0.1:8000/mvclip/create"にアクセスすると,以下のように"Create"ボタンが実装された.

“movieclipApp/templates/movieclipApp"に"update_movieclip.html"を作成し,以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Edit Movieclip</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'),
    path('mvclip/<int:pk>/update', views.UpdateClip.as_view(), name='update_movieclip'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“http://127.0.0.1:8000/mvclip/1/update"にアクセスすると,以下のように編集ページが実装された."Title"の内容を変更し,"Save"をクリックすると変更を更新することができる.

Titleを以下のように変更することができる.

“update_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Edit Movieclip</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Save</button>
</form>

<a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete</a>

{% endblock %}

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'),
    path('mvclip/<int:pk>/update', views.UpdateClip.as_view(), name='update_movieclip'),
    path('mvclip/<int:pk>/delete', views.DeleteClip.as_view(), name='delete_movieclip'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView): # 以下追加
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“movieclipApp/templates/movieclipApp"に"delete_movieclip.html"を作成し,以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Are you sure that you want to delete this MovieClip?</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-danger">Delete</button>
</form>

<br>
<h2><a href="{% url 'detail_movieclip' clip.id %}">{{ clip.title }}</a></h2>

{% endblock %}

Terminalにて"python manage.py runserver"を実行し,"http://127.0.0.1:8000/mvclip/1/update"にアクセスすると,以下のように編集ページが実装され,"Delete"ボタンが追加された."Delete"ボタンをクリックする.

“Delete"ボタンをクリックすると以下最終確認画面に遷移する.本当に削除をするならば,"Delete"ボタンをクリックする.

上記にて"Delete"ボタンをクリックすると,Movieclipの1が削除され,以下Dashborad画面に遷移する.

Formの作成

ブラウザで,ログインした状態で"http://127.0.0.1:8000/mvclip/create"に移動し,Movieclipを作成する.

以下のように,"127.0.0.1:8000/mvclip/2″に移動すると作成したMovie Clipが表示される.

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'),
    path('mvclip/<int:pk>/update', views.UpdateClip.as_view(), name='update_movieclip'),
    path('mvclip/<int:pk>/delete', views.DeleteClip.as_view(), name='delete_movieclip'),
    # Movie
    path('mvclip/<int:pk>/addmovie', views.add_movie, name='add_movie'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“movieclipApp"ディレクトリ下に"forms.py"を作成し,以下のように編集する.

from .models import Movie
from django import forms

class MovieForm(forms.ModelForm):
    class Meta:
        model = Movie
        fields = ['title', 'url', 'youtube_id']

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip
from .forms import MovieForm # 追加

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk): # 以下追加(15-18行目)
    form = MovieForm()

    return render(request, 'movieclipApp/add_movie.html', {'form':form})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“movieclipApp/templates/movieclipApp"に"add_movie.html"を作成し,以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Add Movie</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Create</button>
</form>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のようにAdd Movieのページが実装される.

“forms.py"を以下のように編集する.

from .models import Movie
from django import forms

class MovieForm(forms.ModelForm):
    class Meta:
        model = Movie
        fields = ['title', 'url', 'youtube_id']
        labels = {'youtube_id':'YouTube ID'} # 追加

“127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のように表記がYouTube IDに変わった.

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Add Movie</h2>
<form method="post">
    {% csrf_token %}
    <table>
    {{ form.as_table }}
    </table>
    <button type="submit" class="btn btn-primary">Create</button>
</form>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のような表記に変えることができる.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie # Edit
from .forms import MovieForm

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()

    if request.method == 'POST': # Add (18 to 27 lines)
        # Create
        filled_form = MovieForm(request.POST)
        if filled_form.is_valid():
            movie = Movie()
            movie.url = filled_form.cleaned_data['url']
            movie.title = filled_form.cleaned_data['title']
            movie.youtube_id = filled_form.cleaned_data['youtube_id']
            movie.clip = Clip.objects.get(pk=pk)
            movie.save()

    return render(request, 'movieclipApp/add_movie.html', {'form':form})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

以下ブラウザにて,YouTubeのタイトル,URL,YouTube IDを入力し,"Create"をクリックする."Create"のクリック後,以下画面から入力部分がすべてブランクになれば成功となる.なお,YouTubeのURLは"youtube.com…v=xxx&…"という構成になっているので,xxx部分がYoutube IDとなる.

“127.0.0.1:8000/admin"に移動し,ログインし,"Movie"をクリック後,"Movie object (1)"をクリックすると,以下画面のように,入力したTitle, URL, YouTube IDの詳細が表示される.

“forms.py"を以下のように編集する.

from .models import Movie
from django import forms

class MovieForm(forms.ModelForm):
    class Meta:
        model = Movie
        fields = ['title', 'url', 'youtube_id']
        labels = {'youtube_id':'YouTube ID'}

class SearchForm(forms.Form): # Add (10 to 11 lines)
    search_term = forms.CharField(max_length=255, label='Search for Movies')

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm # Edit 

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm() # Add

    if request.method == 'POST':
        # Create
        filled_form = MovieForm(request.POST)
        if filled_form.is_valid():
            movie = Movie()
            movie.url = filled_form.cleaned_data['url']
            movie.title = filled_form.cleaned_data['title']
            movie.youtube_id = filled_form.cleaned_data['youtube_id']
            movie.clip = Clip.objects.get(pk=pk)
            movie.save()

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form}) # Edit

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Add Movie</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-primary">Create</button>
</form>

<h2>OR</h2> {% comment %} Add (10 to 13 lines) {% endcomment %}
<form>
    {{ search_form }}
</form>

{% endblock %}

userを"rohan"でログインし,"127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のように検索バーを実装することができる.

以下コマンドを実行する.

$ pipenv install django-widget-tweaks

$ python manage.py runserver

“settings.py"を以下のように編集する.

~
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'movieclipApp',
    'widget_tweaks', #追記
]
~

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Create</button>
</form>

<h2>OR</h2>
<form>
    {{ search_form }}
</form>
</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のように入力項目を長くすることができた.

Google APIの利用

“forms.py"を以下のように編集する.

from .models import Movie
from django import forms

class MovieForm(forms.ModelForm):
    class Meta:
        model = Movie
        fields = ['url'] # Edit
        labels = {'url':'YouTube URL'}

class SearchForm(forms.Form):
    search_term = forms.CharField(max_length=255, label='Search for Movies')

“127.0.0.1:8000/mvclip/2/addmovie"に移動すると,以下のように表示を変えた.

以下サイトにアクセスすると,以下画面に遷移するので,スクロールダウンする.

Google Developers | Google API Services User Data Policy

最下部までスクロールダウンすると,以下画面になるので,"Google API Console"をクリックする.

以下画面に遷移するので,下矢印をクリックする.

以下画面に遷移する.新しいプロジェクトを作成するため,"NEW PROJECT"をクリックする.

以下画面に遷移する.プロジェクト名をつけ,"CREATE"をクリックする.

以下画面がひらくので,作成したプロジェクトに移動するため,"SELECT PROJECT"をクリックする.

以下画面に遷移する.作成したプロジェクトにいるかどうかを確認し,"ENABLE API AND SERVICES"をクリックする.

以下画面に遷移する.検索バーに"youtube data"を入力し,移動する.

以下画面に遷移するので,"YouTube Data API v3″をクリックする.

以下画面に遷移するので,"ENABLE"をクリックする.

以下画面に遷移するので,"CREATE CREDENTIALS"をクリックする.

以下画面に遷移するので,以下のように選択およびチェックをし,"NEXT"をクリックする.

以下画面に遷移する.API Keyをコピーし,"DONE"をクリックする.

“views.py"に,コピーしたAPI Keyを利用して,以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm

YOUTUBE_API_KEY = 'AIxxxxxx' # Add

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):

    form = MovieForm()
    search_form = SearchForm()

    if request.method == 'POST':
        # Create
        filled_form = MovieForm(request.POST)
        if filled_form.is_valid():
            movie = Movie()
            movie.url = filled_form.cleaned_data['url']
            movie.title = filled_form.cleaned_data['title']
            movie.youtube_id = filled_form.cleaned_data['youtube_id']
            movie.clip = Clip.objects.get(pk=pk)
            movie.save()

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

以下画面に遷移するので,"YouTube API Data v3″をクリックする.

以下画面に遷移するので,"TRY IN EXPLORER"をクリックする.

以下サイトにアクセスすると,以下画面に遷移する.

Google Developers | YouTube | Data API

以下画面の"Videos"をクリックし,"list"をクリックする.YouTubeで好きな動画のURL(“youtube.com/…v=abc&…")のabcを抽出する."Try this method"が表示されるので,partには"snippet"を入力し,idには"abc"を入力する.私の場合,idが"H3qTpd4v1aw"だったので,以下のように入力し,スクロールダウンする.

以下画面が表示されるので,"EXECUTE"をクリックする.

以下画面が表示されるので,"SHOW CODE"をクリックする.

以下のように大きく表示されるので,赤枠のURLをコピーする.

“views.py"を以下のように編集する.上記でコピーしたURLは加工し,39行目で利用した.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404 # Add (8-11 lines)
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):

    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk) # Add & Edit (25-42 lines)
    if not clip.user == request.user:
        raise Http404

    if request.method == 'POST':
        filled_form = MovieForm(request.POST)
        if filled_form.is_valid():
            movie = Movie()
            movie.clip = clip # Edit
            movie.url = filled_form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')
            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['idems'][0]['snippet']['title']
                print(title)

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip}) # Edit

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

Terminalを開き,以下コマンドを実行する.

$ pipenv install requests

$ python manage.py runserver

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,上記でコピーしたYouTubeのURLを以下のように貼り付け,"Create"をクリックする.

Terminalを確認すると,YouTubeのタイトルが以下のように出力される.

$ 王族とは真逆の境遇。くまが幼少期に味わった筆舌に尽くしがたい差別【ワンピース 1074話 考察 解説】

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):

    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404

    if request.method == 'POST': # Add & Edit (29-47 lines)
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')
            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                movie.title = title
                movie.save()
                return redirect('clip_detail', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('Needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})
class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,YouTube URL以外のURLを以下のように貼り付け,"Create"をクリックすると,間違いであることが指摘される.

YouTube URLを以下のように貼り付け,"Create"をクリックする.

以下画面に遷移することができた.

追加されているか確認するため,"127.0.0.1:8000/admin"に移動し,ログインする.以下"Movies"をクリックすると,今回追加したYouTubeのURLとタイトル,YouTube IDが表示される.

Ajaxの利用

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
    });
</script>

</div>
{% endblock %}

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="#">Dashboard</a>
      <a class="p-2 text-dark" href="{% url 'logout' %}">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="{% url 'login' %}">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="{% url 'signup' %}">Sign up</a>
    {% endif %}
  </div>

  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  {% block content %}
  {% endblock %}


</body>

</html>

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下のように検索バーに適当な文字を入力すると,"Loading…"と出力された.

さらに,"add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
    });
</script>

</div>
{% endblock %}

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'),
    path('mvclip/<int:pk>/update', views.UpdateClip.as_view(), name='update_movieclip'),
    path('mvclip/<int:pk>/delete', views.DeleteClip.as_view(), name='delete_movieclip'),
    # Movie
    path('mvclip/<int:pk>/addmovie', views.add_movie, name='add_movie'),
    path('movie/search', views.movie_search, name='movie_search'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse # Edit
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxxxxxxxxxxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST': # Add & Edit (29-47 lines)
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request): # Add(51-52 lines)
    return JsonResponse({'':''})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

以下サイトにアクセスすると,以下ページに遷移する.赤線のコードをコピーする.

Google Hosted Libraries

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="#">Dashboard</a>
      <a class="p-2 text-dark" href="{% url 'logout' %}">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="{% url 'login' %}">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="{% url 'signup' %}">Sign up</a>
    {% endif %}
  </div>

  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script><!-- # Edit -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  {% block content %}
  {% endblock %}


</body>

</html>

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,検索バーに適当な文字を入力すると,"AJAX OK"が出力される.

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>{% comment %} Add & Edit (38, 42 line) {% endcomment %}
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
        delayTimer = setTimeout(function() {
            var text = $('#id_search_term').val();
            $.ajax({
                url: '/movie/search',
                data: {
                    'search_term': text
                },
                dataType: 'json',
                success: function(data) {
                    $('#search_results').text(data['Hello']);
                }
            });
        }, 1000);
    });
</script>

</div>
{% endblock %}

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse # Efit
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request): # Add (52-55 Lines)
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        return JsonResponse({'Hello':search_form.cleaned_data['search_term']})
    return JsonResponse({'Hello':'Not Working'})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下画面にて,検索バーに適当な文字を入力する.キャプチャをすると,"Loading…"となっているが,入力した文字と同じ文字が出力される.

以下サイトにアクセスすると,以下画面に遷移する.

Google Developers | YouTube | Data API

以下のように,"Search"をクリックし,"list"を選択すると以下画面になる.右側の"Try this method"には,以下の値を入力し,最下部までスクロールダウンし,"EXECUTE"をクリックする.

  • part: snippet
  • q: one piece
  • maxResults: 10

以下画面になるので,"SHOW CODE"をクリックする.

以下画面が表示されるので,赤枠のURLをコピーする.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid(): # Edit (54-57 Lines)
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>{% comment %} Add & Edit (46 to 54 line) {% endcomment %}
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
        delayTimer = setTimeout(function() {
            var text = $('#id_search_term').val();
            $.ajax({
                url: '/movie/search',
                data: {
                    'search_term': text
                },
                dataType: 'json',
                success: function(data) {

                    var results = '';
                    $('#search_results').text('');

                    data['items'].forEach(function(movie) {
                        results += movie['snippet']['title']
                    });

                    $('#search_results').append(results);
                }
            });
        }, 1000);
    });
</script>

</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下画面にて,検索バーに適当な文字を入力する.キャプチャをすると,"Loading…"となっているが,入力した文字の検索結果が出力される.

YouTubeにアクセスし,共有をクリックする.

以下画面が表示されるので,"埋め込む"をクリックする.

以下画面になるので,赤枠のコードをコピーする.

“add_movie.html"を以下のように編集する.なお,51行目のコードは上記でコピーしたコードを編集して貼り付けをする.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>{% comment %} Add & Edit (48 to 55 line) {% endcomment %}
<script>
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
        delayTimer = setTimeout(function() {
            var text = $('#id_search_term').val();
            $.ajax({
                url: '/movie/search',
                data: {
                    'search_term': text
                },
                dataType: 'json',
                success: function(data) {
                    var results = '';
                    $('#search_results').text('');
                    results += '<div class="row">';
                    data['items'].forEach(function(movie) {
                        results += '<div class="col-md-4 mt-3"><div class="card mb-4 shadow-sm">';
                            results += '<iframe width="100%" height="225" src="https://www.youtube.com/embed/' + movie['id']['videoId'] + '" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>';
                        results += '</div></div>';
                    });
                    results += '</div>';
                    $('#search_results').append(results);
                }
            });
        }, 1000);
    });
</script>

</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下画面にて,検索バーに適当な文字を入力する.入力した文字の検索結果が以下のように出力される.

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post">
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>{% comment %} Add & Edit (52 to 53 line) {% endcomment %}
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
        delayTimer = setTimeout(function() {
            var text = $('#id_search_term').val();
            $.ajax({
                url: '/movie/search',
                data: {
                    'search_term': text
                },
                dataType: 'json',
                success: function(data) {
                    var results = '';
                    $('#search_results').text('');
                    results += '<div class="row">';
                    data['items'].forEach(function(movie) {
                        results += '<div class="col-md-4 mt-3"><div class="card mb-4 shadow-sm">';
                            results += '<iframe width="100%" height="225" src="https://www.youtube.com/embed/' + movie['id']['videoId'] + '" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>';
                        results += '</div class="card-body"><p class="card-text">' + movie['snippet']['title'] + '</p>';
                        results += '<a href="#" class="btn btn-primary" onclick="addMovie()">Add</a></div></div></div>';
                    });
                    results += '</div>';
                    $('#search_results').append(results);
                }
            });
        }, 1000);
    });
</script>

</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下画面にて,検索バーに適当な文字を入力する.入力した文字の検索結果が以下のように出力され,各々の動画には"Add"ボタンが実装された.

“add_movie.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Add Movie to {{ clip.title }}</h2>
<form method="post" id="submit_movie">{% comment %} Edit {% endcomment %}
    {% csrf_token %}
    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<h2>OR</h2>
<form>
    {% for field in search_form %}
    <div class="form-group">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}
</form>

<div id="search_results"></div>
<script>{% comment %} Edit( 53, 61-65 lines) {% endcomment %}
    var delayTimer;
    $('#id_search_term').keyup(function() {
        clearTimeout(delayTimer);
        $('#search_results').text('Loading...');
        delayTimer = setTimeout(function() {
            var text = $('#id_search_term').val();
            $.ajax({
                url: '/movie/search',
                data: {
                    'search_term': text
                },
                dataType: 'json',
                success: function(data) {
                    var results = '';
                    $('#search_results').text('');
                    results += '<div class="row">';
                    data['items'].forEach(function(movie) {
                        results += '<div class="col-md-4 mt-3"><div class="card mb-4 shadow-sm">';
                            results += '<iframe width="100%" height="225" src="https://www.youtube.com/embed/' + movie['id']['videoId'] + '" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>';
                        results += '<div class="card-body"><p class="card-text">' + movie['snippet']['title'] + '</p>';
                        results += '<a href="#" class="btn btn-primary" onclick="addMovie(\'' + movie['id']['videoId'] + '\')">Add</a></div></div></div>';
                    });
                    results += '</div>';
                    $('#search_results').append(results);
                }
            });
        }, 1000);
    });

    function addMovie(movie_id) {
        $('#id_url').val('https:www.youtube.com/watch?v=' + movie_id);
        $('#submit_movie').submit();
    }
</script>

</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2/addmovie"に移動し,以下画面にて,検索バーに適当な文字を入力する.各動画の"Add"ボタンをクリックすると,赤枠の検索バーに移動し,自動的にMovie Clipに追加されるように実装した.

“urls.py"を以下のように編集する.

from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path
from movieclipApp import views
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.home, name='home'),
    path('dashboard', views.dashboard, name='dashboard'),
    # AUTH
    path('signup', views.SignUp.as_view(), name='signup'),
    path('login', auth_views.LoginView.as_view(), name='login'),
    path('logout', auth_views.LogoutView.as_view(), name='logout'),
    # movieclip
    path('mvclip/create', views.CreateClip.as_view(), name='create_movieclip'),
    path('mvclip/<int:pk>', views.DetailClip.as_view(), name='detail_movieclip'),
    path('mvclip/<int:pk>/update', views.UpdateClip.as_view(), name='update_movieclip'),
    path('mvclip/<int:pk>/delete', views.DeleteClip.as_view(), name='delete_movieclip'),
    # Movie
    path('mvclip/<int:pk>/addmovie', views.add_movie, name='add_movie'),
    path('movie/search', views.movie_search, name='movie_search'),
    path('movie/<int:pk>/delete', views.DeleteMovie.as_view(), name='delete_movie'), # 追加
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    return render(request, 'movieclipApp/dashboard.html')

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):# Add (59-63 lines)
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“movieclilpApp/templates/movieclipApp"ディレクトリ下に"delete_movie.html"を作成し,以下のように記述する.10~18行目は,"add_movie.html"の48~55行目を利用した.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<h2>Are you sure that you want to delete this movie?</h2>
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit" class="btn btn-danger">Delete</button>
</form>

<div class="row">
<div class="col-md-4 mt-3"><div class="card mb-4 shadow-sm">
<iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<div class="card-body">
<p class="card-text">{{ movie.title }}</p>
</div>
</div>
</div>
</div>

{% endblock %}

“127.0.0.1:8000/movie/12/delete"に移動すると,以下画面にて,削除ボタンとともに対象となる動画を付け加えることができた."Delete"ボタンをクリックする.

以下画面に遷移した."127.0.0.1:8000/admin"で確認すると12番目の動画は削除されていた.

JSONを利用したデータ移行

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user) # Add & Edit (19-20 lines)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('home')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“dashboard.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<div class="text-center">
<h1>{{ user.username }}'s Movie Clip</h1>
<a href="{% url 'create_movie' %} class="btn btn-primary">Create New Movie Clip</a>
</div>
</div>
{% endblock %}

“127.0.0.1:8000/dashboard"に移動すると,以下画面に移動した."Create New Movie Clip"をクリックする.

以下画面に遷移する.

“dashboard.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
    <div class="text-center">
        <h1>{{ user.username }}'s Movie Clip</h1>
        <a href="{% url 'create_movieclip' %}" class="btn btn-primary">Create New Movie Clip</a>
    </div>

        {% for clip in movieclipApp %}

            <h2><a href="{% url 'detail_movieclip' clip.id %}">{{ clip.title }}</a></h2>

            <div class="row">
                {% for movie in clip.movie_set.all %}
                <div class="col-md-4 mt-3">
                    <div class="card mb-4 shadow-sm">
                    <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                        <div class="card-body">
                            <p class="card-text">{{ movie.title }}</p>
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        {% endfor %}
</div>
{% endblock %}

“127.0.0.1:8000/dashboard"に移動すると,以下画面に移動する.Movie Clip名である"One Piece"が表示され,Clipに登録した動画を表示することができた.

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title></title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="{% url 'dashboard' %}">Dashboard</a>{% comment %} Edit {% endcomment %}
      <a class="p-2 text-dark" href="{% url 'logout' %}">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="{% url 'login' %}">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="{% url 'signup' %}">Sign up</a>
    {% endif %}
  </div>

  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script><!-- # Edit -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  {% block content %}
  {% endblock %}


</body>

</html>

“127.0.0.1:8000″に移動すると,以下画面に移動する."Dashboard"をクリックする.

“127.0.0.1:8000/dashboard"に移動することができた.

“dashboard.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
    <div class="text-center">
        <h1>{{ user.username }}'s Movie Clip</h1>
        <a href="{% url 'create_movieclip' %}" class="btn btn-primary">Create New Movie Clip</a>
    </div>

        {% for clip in movieclipApp %}

            <h2><a href="{% url 'detail_movieclip' clip.id %}">{{ clip.title }}</a></h2>
            <a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete</a>{% comment %} # Add (12-14 lines) {% endcomment %}
            <a href="{% url 'update_movieclip' clip.id %}" class="btn btn-primary">Edit</a>
            <a href="{% url 'add_movie' clip.id %}" class="btn btn-primary">Add Movie</a>

            <div class="row">
                {% for movie in clip.movie_set.all %}
                <div class="col-md-4 mt-3">
                    <div class="card mb-4 shadow-sm">
                    <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                        <div class="card-body">
                            <p class="card-text">{{ movie.title }}</p>
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        {% endfor %}
</div>
{% endblock %}

“127.0.0.1:8000/dashboard"に移動する.Dashboardに,"Delete"ボタン,"Edit"ボタン,"Add Movie"ボタンを実装することができた.

“127.0.0.1:8000/mvclip/create"に移動すると以下画面が表示される.Movie Clipの作成画面を変える.

“create_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Create Movieclip</h2>
<form method="post">
    {% csrf_token %}

    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Create</button>
</form>
</div>
{% endblock %}

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    return render(request, 'movieclipApp/home.html')

def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('dashboard') # Edit

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('dashboard') # Edit

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“127.0.0.1:8000/mvclip/create"に移動すると以下画面が表示される.画面のフォーマットを変えた.また,タイトルを入力し,"Create"をクリックする.

以前はhomeに戻っていたが,dashboardに戻るように変更した.

“detail_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>{{ clip.title }}</h2>
<h4 class="text-muted">{{ clip.user.username }}</h4>

<a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete</a>{% comment %} # Add (12-14 lines) {% endcomment %}
<a href="{% url 'update_movieclip' clip.id %}" class="btn btn-primary">Edit</a>
<a href="{% url 'add_movie' clip.id %}" class="btn btn-primary">Add Movie</a>

    <div class="row">
        {% for movie in clip.movie_set.all %}
        <div class="col-md-4 mt-3">
            <div class="card mb-4 shadow-sm">
            <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                <div class="card-body">
                    <p class="card-text">{{ movie.title }}</p>
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2″に移動すると以下画面が表示される.ログイン,ログアウトの状態に関係なく,"Delete"ボタン,"Edit"ボタン,"Add Movie"ボタンが加わっている以下画面になる.

“detail_movieclip.html"を以下のように,7行目と11行目を編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>{{ clip.title }}</h2>
<h4 class="text-muted">{{ clip.user.username }}</h4>

{% if user.id == clip.user.id %}
<a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete</a>{% comment %} # Add (12-14 lines) {% endcomment %}
<a href="{% url 'update_movieclip' clip.id %}" class="btn btn-primary">Edit</a>
<a href="{% url 'add_movie' clip.id %}" class="btn btn-primary">Add Movie</a>
{% endif %}

    <div class="row">
        {% for movie in clip.movie_set.all %}
        <div class="col-md-4 mt-3">
            <div class="card mb-4 shadow-sm">
            <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                <div class="card-body">
                    <p class="card-text">{{ movie.title }}</p>
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
</div>
{% endblock %}

“127.0.0.1:8000/mvclip/2″に移動すると以下画面が表示される.ログアウト状態の場合,以下画面になる.ログインの場合,One PieceのMovie Clipを編集できるように,"Delete"ボタン,"Edit"ボタン,"Add Movie"ボタンが加わる.

“detail_movieclip.html"を以下のように,20~22行目を追加する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>{{ clip.title }}</h2>
<h4 class="text-muted">{{ clip.user.username }}</h4>

{% if user.id == clip.user.id %}
<a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete</a>{% comment %} # Add (12-14 lines) {% endcomment %}
<a href="{% url 'update_movieclip' clip.id %}" class="btn btn-primary">Edit</a>
<a href="{% url 'add_movie' clip.id %}" class="btn btn-primary">Add Movie</a>
{% endif %}

    <div class="row">
        {% for movie in clip.movie_set.all %}
        <div class="col-md-4 mt-3">
            <div class="card mb-4 shadow-sm">
            <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                <div class="card-body">
                    <p class="card-text">{{ movie.title }}</p>
                    {% if user.id == clip.user.id %}
                    <a href="{% url 'delete_movie' movie.id %}" class="btn btn-danger">Delete</a>
                    {% endif %}
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
</div>
{% endblock %}

ログインした状態で,"127.0.0.1:8000/mvclip/2″に移動すると以下画面が表示される.各動画の下に"Delete"ボタンが実装された.

上記にて"Edit"ボタンをクリックすると,以下画面に遷移する.この画面を変更する.

“update_movieclip.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h2>Edit Movieclip</h2>
<form method="post">
    {% csrf_token %}

    {% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    </div>
    {% endfor %}

    <button type="submit" class="btn btn-primary">Save</button>
</form>
<br>
<a href="{% url 'delete_movieclip' clip.id %}" class="btn btn-danger">Delete this Movie Clip</a>

</div>
{% endblock %}

以下のように変更できた.

Homeページを編集するため,"home.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<section class="jumbotron text-center">
<div>
<h1 class="junbotron-heading">You can show your personal Movie Clip♪</h1>
<p class="lead text-muted">Let's share your clip with the world.<br> Click the button below to get started.</p>
<p>
<a href="{% url 'create_movieclip' %}" class="btn btn-primary">Create your Movie Clip</a>
</p>
</div>
</section>
<div class="container">
Hello World! This is homepage of Movie Clip!
<div>
{% endblock %}

ログインした状態で,"127.0.0.1:8000/"に移動すると以下画面が表示される.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    recent_movieclipApp = Clip.objects.all().order_by('-id')[:3] # Edit(16-18 lines)
    popular_movieclipApp = [Clip.objects.get(pk=2),Clip.objects.get(pk=3),Clip.objects.get(pk=4)]
    return render(request, 'movieclipApp/home.html', {'recent_movieclipApp':recent_movieclipApp, 'popular_movieclipApp':popular_movieclipApp})

def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('dashboard')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('dashboard')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“home.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<section class="jumbotron text-center">
<div>
<h1 class="junbotron-heading">You can show your personal Movie Clip♪</h1>
<p class="lead text-muted">Let's share your clip with the world.<br> Click the button below to get started.</p>
<p>
<a href="{% url 'create_movieclip' %}" class="btn btn-primary">Create your Movie Clip</a>
</p>
</div>
</section>
<div class="container">

    <h1 class="test-center">Popular Movie Clip</h1>

        {% for clip in popular_movieclipApp %}

            <h2><a href="{% url 'detail_movieclip' clip.id %}">{{ clip.title }}</a></h2>
 
            <div class="row">
                {% for movie in clip.movie_set.all %}
                <div class="col-md-4 mt-3">
                    <div class="card mb-4 shadow-sm">
                    <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                        <div class="card-body">
                            <p class="card-text">{{ movie.title }}</p>
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        {% endfor %}

    <h1 class="test-center">Recent Movie Clip</h1>

        {% for clip in recent_movieclipApp %}

            <h2><a href="{% url 'detail_movieclip' clip.id %}">{{ clip.title }}</a></h2>
 
            <div class="row">
                {% for movie in clip.movie_set.all %}
                <div class="col-md-4 mt-3">
                    <div class="card mb-4 shadow-sm">
                    <iframe width="100%" height="225" src="https://www.youtube.com/embed/{{ movie.youtube_id }}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
                        <div class="card-body">
                            <p class="card-text">{{ movie.title }}</p>
                        </div>
                    </div>
                </div>
                {% endfor %}
            </div>
        {% endfor %}


<div>
{% endblock %}

ログインした状態で,"127.0.0.1:8000/"に移動すると以下画面が表示される.Popular Movie Clipが表示される.

スクロールダウンすると,以下Recent Movie Clipが表示される.

“signup.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h3>Sign Up!</h3>
<form method="post">
{% csrf_token %}
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
{% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    {% if field.help_text %}<small class="text-muted">{{ field.help_text|safe }}</small>{% endif %}
    </div>
    {% endfor %}

<button type="submit" class="btn btn-primary">Sign Up</button>
</form>
</div>
{% endblock %}

“127.0.0.1:8000/signup"に移動すると以下画面が表示される.

“login.html"を以下のように編集する.

{% extends 'movieclipApp/base.html' %}
{% block content %}
<div class="container">
<h3>Login!</h3>
<form method="post">

{% csrf_token %}
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
{% load widget_tweaks %}

    {% for field in form %}
    <div class="form-group {% if field.errors %}alert alert-danger{% endif %}">
    {{ field.errors }}
    {{ field.label_tag }}
    {% render_field field class="form-control" %}
    {% if field.help_text %}<small class="text-muted">{{ field.help_text|safe }}</small>{% endif %}
    </div>
    {% endfor %}

<button type="submit" class="btn btn-primary">Login</button>
</form>
</div>
{% endblock %}

“127.0.0.1:8000/login"に移動すると以下画面が表示される.適当なユーザー名とパスワードを入力すると,以下画面のように注意書きが表示される.

コマンド"ls"を実行し,ディレクトリの一覧を確認し,コマンド"rm db.sqlite3″を実行し,削除する.

$ ls


    ディレクトリ: C:\Users\shiro\Desktop\dev_test\230303_Djanto_test\movieclip-project


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----     2023/03/02 木      7:10                movieclip
d-----     2023/03/09 木     16:43                movieclipApp
-a----     2023/03/18 土     15:52         147456 db.sqlite3
-a----     2023/03/02 木      7:10            687 manage.py
-a----     2023/03/14 火     18:29            194 Pipfile
-a----     2023/03/14 火     18:29          10480 Pipfile.lock

$ rm db.sqlite3

続けて,以下コマンドを実行し,マイグレートする.その後,"python manage.py runserver"を実行する.

$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, movieclipApp, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying movieclipApp.0001_initial... OK
  Applying sessions.0001_initial... OK

$ python manage.py runserver

“127.0.0.1:8000″に移動すると以下画面が表示される.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    recent_movieclipApp = Clip.objects.all().order_by('-id')[:3]
    popular_movieclipApp = [] # Edit
    return render(request, 'movieclipApp/home.html', {'recent_movieclipApp':recent_movieclipApp, 'popular_movieclipApp':popular_movieclipApp})

def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('home')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('dashboard')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('dashboard')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“127.0.0.1:8000″に移動すると以下画面が表示される.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
import urllib
import requests

YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    recent_movieclipApp = Clip.objects.all().order_by('-id')[:3]
    popular_movieclipApp = [Clip.objects.get(pk=1),Clip.objects.get(pk=2),Clip.objects.get(pk=3)] # Edit
    return render(request, 'movieclipApp/home.html', {'recent_movieclipApp':recent_movieclipApp, 'popular_movieclipApp':popular_movieclipApp})

def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(generic.DeleteView):
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('dashboard') # Edit
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(generic.CreateView):
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('dashboard')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('dashboard')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(generic.UpdateView):
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

class DeleteClip(generic.DeleteView):
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

“http://127.0.0.1:8000/signup"に移動し,ユーザー(ユーザー名: rohan)を作成する.その後,Movie Clipを作成していく."http://127.0.0.1:8000″に移動すると,作成した動画を含む以下画面が表示される.

以下コマンドを実行し,スーパーユーザーを作成する.

$ python manage.py createsuperuser

Username (leave blank to use 'shiro'): shiro
Email address:
Password:
Password (again):
Superuser created successfully.

その後,以下コマンドを実行し,"seed_data.json"にデータを保存する.

$ python -Xutf8 manage.py dumpdata -o seed_data.json

以下コマンドを実行すると,"seed_data.json"が作成されたことが確認できる.

$ ls

    ディレクトリ: C:\Users\shiro\Desktop\dev_test\230303_Djanto_test\movieclip-project


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----     2023/03/02 木      7:10                movieclip
d-----     2023/03/09 木     16:43                movieclipApp
-a----     2023/03/19 日      1:31         147456 db.sqlite3
-a----     2023/03/02 木      7:10            687 manage.py
-a----     2023/03/14 火     18:29            194 Pipfile
-a----     2023/03/14 火     18:29          10480 Pipfile.lock
-a----     2023/03/19 日      1:33          15354 seed_data.json

以下コマンドを実行し,sqlite3の削除とマイグレートを実行し,"runserver"を実行する.

$ rm db.sqlite3

$ python manage.py migrate

$ python manage.py runserver

“http://127.0.0.1:8000″に移動すると,データが消えているので,以下画面になる.

以下コマンドを実行する.

$ python manage.py loaddata seed_data.json

$ python manage.py runserver

“http://127.0.0.1:8000″に移動すると,seed_data.jsonに保存されていた情報に基づき,以下画面が出力される.

“http://127.0.0.1:8000/admin"にログインし,"Clips"をクリックすると以下のように3つのMovie Clipを確認できる.

“Movies"をクリックすると,Movie Clipに各々2つ追加したので計6つのMovie情報を確認できる.

“http://127.0.0.1:8000″に移動すると,以下のように赤枠にURLが出力される.

“base.html"を以下のように編集する.

<!doctype html>
<html lang="en">
{% load static %}

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <title>Movie Clip</title>{% comment %}  # Edit {% endcomment %}
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
  <link href="{% static 'custom.css' %}" rel="stylesheet">
  <link rel="shortcut icon" type="image/png" href="{% static 'myfavicon.ico' %}" />
</head>

<body>
  <div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 bg-white border-bottom shadow-sm">
    <h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'home' %}" class="text-dark">Movie Clip</a></h5>
    <nav class="my-2 my-md-0 mr-md-3">
      {% if user.is_authenticated %}
      <a class="p-2 text-dark" href="{% url 'dashboard' %}">Dashboard</a>
      <a class="p-2 text-dark" href="{% url 'logout' %}">Log Out</a>
      {% else %}
      <a class="p-2 text-dark" href="{% url 'login' %}">Log In</a>
      {% endif %}
    </nav>
    {% if user.is_authenticated == False %}
    <a class="btn btn-outline-primary" href="{% url 'signup' %}">Sign up</a>
    {% endif %}
  </div>

  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

  {% block content %}
  {% endblock %}


</body>

</html>

“http://127.0.0.1:8000″に移動すると,以下のように赤枠にWebサービスのタイトルを出力することができた.

現状,異なるユーザーでもMovie ClipやMovie Clipに追加した動画を削除することができる.そのため,作成したユーザーのみMovie ClipやMovie Clipに追加した動画を削除できるように実装していく.

“views.py"を以下のように編集する.

from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import authenticate, login
from .models import Clip, Movie
from .forms import MovieForm, SearchForm
from django.http import Http404, JsonResponse
from django.forms.utils import ErrorList
from django.contrib.auth.decorators import login_required # Add
from django.contrib.auth.mixins import LoginRequiredMixin # Add
import urllib
import requests


YOUTUBE_API_KEY = 'AIxxxxxx'

def home(request):
    recent_movieclipApp = Clip.objects.all().order_by('-id')[:3]
    popular_movieclipApp = [Clip.objects.get(pk=1),Clip.objects.get(pk=2),Clip.objects.get(pk=3)]
    return render(request, 'movieclipApp/home.html', {'recent_movieclipApp':recent_movieclipApp, 'popular_movieclipApp':popular_movieclipApp})

@login_required # Add
def dashboard(request):
    movieclipApp = Clip.objects.filter(user=request.user)
    return render(request, 'movieclipApp/dashboard.html', {'movieclipApp':movieclipApp})

@login_required # Add
def add_movie(request, pk):
    form = MovieForm()
    search_form = SearchForm()
    clip = Clip.objects.get(pk=pk)
    if not clip.user == request.user:
        raise Http404
    if request.method == 'POST':
        form = MovieForm(request.POST)
        if form.is_valid():
            movie = Movie()
            movie.clip = clip
            movie.url = form.cleaned_data['url']
            parsed_url = urllib.parse.urlparse(movie.url)
            movie_id = urllib.parse.parse_qs(parsed_url.query).get('v')

            if movie_id:
                movie.youtube_id = movie_id[0]
                response = requests.get(f'https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id={ movie_id[0] }&key={ YOUTUBE_API_KEY }')               
                json = response.json()
                title = json['items'][0]['snippet']['title']
                print(title)
                movie.title = title
                movie.save()
                return redirect('detail_movieclip', pk)
            else:
                errors = form._errors.setdefault('url', ErrorList())
                errors.append('needs to be a YouTube URL')

    return render(request, 'movieclipApp/add_movie.html', {'form':form, 'search_form':search_form, 'clip':clip})

@login_required # Add
def movie_search(request):
    search_form = SearchForm(request.GET)
    if search_form.is_valid():
        encoded_search_term = urllib.parse.quote(search_form.cleaned_data['search_term'])
        response = requests.get(f'https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=10&q={ encoded_search_term }&key={ YOUTUBE_API_KEY }')
        return JsonResponse(response.json())
    return JsonResponse({'error':'Not able to validate form'})

class DeleteMovie(LoginRequiredMixin, generic.DeleteView): # Edit
    model = Movie
    template_name = 'movieclipApp/delete_movie.html'
    success_url = reverse_lazy('dashboard')

    def get_object(self): # Add (73-77 lines)
        movie = super(DeleteMovie, self).get_object()
        if not movie.clip.user == self.request.user:
            raise Http404
        return movie

class SignUp(generic.CreateView):
    form_class = UserCreationForm
    success_url = reverse_lazy('dashboard')
    template_name = 'registration/signup.html'

    def form_valid(self, form):
        view = super(SignUp, self).form_valid(form)
        username, password = form.cleaned_data.get('username'), form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return view

class CreateClip(LoginRequiredMixin, generic.CreateView): # Edit
    model = Clip
    fields = ['title']
    template_name = 'movieclipApp/create_movieclip.html'
    success_url = reverse_lazy('dashboard')

    def form_valid(self, form):
        form.instance.user = self.request.user
        super(CreateClip, self).form_valid(form)
        return redirect('dashboard')

class DetailClip(generic.DetailView):
    model = Clip
    template_name = 'movieclipApp/detail_movieclip.html'

class UpdateClip(LoginRequiredMixin, generic.UpdateView): # Edit
    model = Clip
    template_name = 'movieclipApp/update_movieclip.html'
    fields = ['title']
    success_url = reverse_lazy('dashboard')

    def get_object(self): # Add (112-116 lines)
        clip = super(UpdateClip, self).get_object()
        if not clip.user == self.request.user:
            raise Http404
        return clip

class DeleteClip(LoginRequiredMixin, generic.DeleteView): # Edit
    model = Clip
    template_name = 'movieclipApp/delete_movieclip.html'
    success_url = reverse_lazy('dashboard')

    def get_object(self): # Add (123-127 lines)
        clip = super(DeleteClip, self).get_object()
        if not clip.user == self.request.user:
            raise Http404
        return clip

Herokuを利用したDeployment

以下サイトにアクセスすると,以下画面に遷移する.スクロールダウンする.

Heroku Dev Center | The Heroku CLI

以下画面が現れるので,インストールする.

VS CodeのTerminalに戻り,以下コマンドを実行する.

$ heroku login

ブラウザが以下画面に自動的に遷移するので,ログインを実行する.

Terminalに以下コードが出力されれば,ログイン成功となる.

Logging in... done
Logged in as xxx@xxx.com

以下コマンドを実行し,gunicornとpsycopg2-binaryをインストールする.

$ pipenv install gunicorn psycopg2-binary

Installing gunicorn...
Installing psycopg2-binary...  

以下コマンドを実行し,Gitのレポジトリを作成する.

$ git init

以下コマンドを実行し,すべての変更をステージに追加する.

$ git add -A

以下コマンドを実行し,変更を記録する.

$ git commit -m "First one"

以下コマンドを実行し,新たなプロジェクトを作成する.

$ heroku create

 »   Warning: heroku update available from 7.53.0 to 7.67.2.
Creating app... done, 

以下コマンドを実行し,heroku masterにコードを送る.

$ git push heroku master

実行後,以下のように出力されるので,赤線の箇所(URL)をコピーする.

ブラウザに貼り付け,移動すると以下画面に遷移する.赤枠をコピーする.

Terminalに貼り付けて,実行するとログを確認することができる.

$ heroku logs --tail

以下コマンドを実行する.

$ pipenv install dj-database-url

“settings.py"を以下のように編集する.

from pathlib import Path
import os
import dj_database_url # Add
~
DEBUG = False # Edit
~
DATABASES = { # Edit(84-86 lines)
    'default': dj_database_url.config()
}
~

Terminalに戻り,以下コマンドを実行する.

$ pipenv install whitenoise

“settings.py"に戻り,以下のように編集する.

~
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware' # Add
]
~
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # Add
~

ファイルを更新したので,以下コマンドを実行する.

$ git add -A

以下コマンドを実行し,gitの状況を確認する.

$ git status
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   Pipfile
        modified:   Pipfile.lock
        modified:   movieclip/settings.py

以下コマンドを実行し,commitを実行する.

$ git commit -m "Database and settings"
[master b98c8c9] Database and settings
 3 files changed, 28 insertions(+), 10 deletions(-)

以下コマンドを実行し,heroku masterにpushする.

$ git push heroku master

“settings.py"の"ALLOWED_HOSTS"には,以下のように上記でコピーした赤線の箇所(URL; ただし,"https://"は不要)と実際のドメインを貼り付ける.

“movieclip-project"プロジェクトディレクトリに"Procfile"を作成し,以下のように編集する.gunicornの後ろは,"wsgi.py"に記載されている’movieclip.settings’から引用している.

release: python manage.py migrate
web: gunicorn movieclip.wsgi

もしも,特定のPythonのバージョンを利用したい場合には,"movieclip-project"プロジェクトディレクトリに"runtime.txt"を作成し,以下のように編集する.

※pythonはすべて小文字であることに注意!

python-3.11.1

上記の変更を加えたので,Terminalにて以下コマンドを実行する.

$ git status

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   movieclip/settings.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        Procfile
        runtime.txt

続けて,以下コマンドを実行する.

$ git add -A

$ git commit -m "Heroku files"

[master 428b4d3] Heroku files
 3 files changed, 5 insertions(+), 2 deletions(-)
 create mode 100644 Procfile
 create mode 100644 runtime.txt

以下コマンドを実行する.

$ git push heroku master

以下コマンドを実行する.

$ heroku open

ブラウザが開き,以下画面になれば成功となる.

上記のブラウザのURLの後ろに"/admin"と入力し,Enterボタンをクリックすると以下画面に遷移する.

以下コマンドを実行し,jsonに格納されているデータをロードする.

$ heroku run python manage.py loaddata seed_data.json

ブラウザで,"xxx.herokuapp.com"(Server Error 500と表示されたページ)に戻ると以下のように表示される.作成したデータが表示されれば成功となる.

“sample.com"というドメインを追加するのであれば,以下コマンドを実行する.

$ heroku domains:add sample.com

$ heroku domains:wait sample.com

以下コマンドを実行すると,現在のドメイン状況を確認することができる.

$ heroku domains

なお,以下コマンドを実行すると,現在のWebアプリの状況を確認できる.稼働がある場合とない場合で以下のような結果になる.

$ heroku ps 

#1:稼働あり
»   Warning: heroku update available from 7.53.0 to 7.67.2.      
=== web (Basic): gunicorn movieclip.wsgi (1)
web.1: up 2023/03/30 16:34:49 +0900 (~ 44m ago)

#2:稼働なし
»   Warning: heroku update available from 7.53.0 to 7.67.2.
No dynos on

以下コマンドを実行すると,現在のWebアプリを停止することができる.

$ heroku ps:scale web=0

 »   Warning: heroku update available from 7.53.0 to 7.67.2.
Scaling dynos... done, now running web at 0:Basic

参照

udemy | Mastering Django

以上

PythonDjango,Heroku,JSON

Posted by クマガイ