はじめに
Djangoで機械学習アプリケーションを作成するために,ポートフォリオの作成を用いてDjangoの勉強をする.また,環境構築は仮想環境ではなく,Dockerを用いる.少しでも,モダンな開発方法に近づいていると思いたい.
目次
1. 環境構築
2. プロジェクト作成
3. 作品紹介ページの作成
4. プロフィールの作成
5. スキルと問い合わせページの追加
6. おまけに
7. 参考
#1. 環境構築
GitHubでリポジトリを作成し,.gitignoreを作成する.リポジトリ作成時に,gitignoreを自動で作成してくれるので,それに追加.
*.pyc
.DS_Store
media
まずは,Dockerfileを作成する.ポートフォリオはそんなに機能が必要ではないので,凄くシンプル.
FROM python:3
RUN mkdir /code
WORKDIR /code
COPY /requirements.txt /code
RUN python3 -m pip install -r requirements.txt
COPY . /code/
requirements.txtの中身は以下のようにします.django-widget-tweaksはフォームの作成に便利.
Django~=3.1.4
Pillow~=8.1.0
django-widget-tweaks~=1.4.8
それでは,コンテナを起動させてDjangoを使ってみる.ターミナルを用いて,Dockerfileが入っているフォルダまで移動.そして,以下を入力.mysiteはDockerのimageタグである.
docker build -t mysite .
ここまで来たら,コンテナの中に入ってポートフォリオを作成していく.まずはコンテナを起動.mysiteはimage名
docker run -v C:\Users\user\Desktop\mysite:/code -d -p 8000:8000 mysite
次にコンテナ内に入って作業を始める.
docker exec -it [コンテナID] /bin/bash
#2. プロジェクト作成
設定の変更を行う. mysite/settings.pyの一番下に以下を追加.
LANGUAGE_CODE= 'ja'
TIME_ZONE = 'Asia/Tokyo'
ロゴの写真の追加を設定するため以下もmysite/settings.pyに追加
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
実際にサーバを起動するためデータベースをmigrateする.ターミナルに以下を入力.
python3 manage.py migrate
次にサーバを起動する.Dockerでサーバを起動するためには0.0.0.0:8000が必要.
python3 manage.py runserver 0.0.0.0:8000
#3. トップ画面の作成
ここまでで,サーバを起動させることができた.ここからはアプリケーションの作成をしていく.ターミナルに以下を入力.appはアプリ名に設定.
python3 manage.py startapp app
アプリケーションとの接続を行うため,設定にアプリケーションを追加する.
mysite/settings.pyのINSTALLED_APPSに以下を追加する
'widget_tweaks',
'app',
widget_tweaksはホーム画面を簡単に作成することができるappは先ほど作成したアプリケーション名である.
トップ画面に必要なモデルを作成していく.トップ画面にプロフィールクラスを作成.app/models.pyに以下を追加.
class Profile(models.Model):
title = models.CharField('タイトル', max_length=100, null=True, blank=True)
subtitle = models.CharField('サブタイトル', max_length=100, null=True, blank=True)
name = models.CharField('名前', max_length=100)
job = models.TextField('仕事')
introduction = models.TextField('自己紹介')
github = models.CharField('github', max_length=100, null=True, blank=True)
twitter = models.CharField('twitter', max_length=100, null=True, blank=True)
topimage = models.ImageField(upload_to='images', verbose_name='トップ画面')
subimage = models.ImageField(upload_to='images', verbose_name='サブ画面')
def __str__(self):
return self.name
次に管理画面を作成する.app/admin.pyに書いていく.importと登録を行う.
from .models import Profile
admin.site.register(Profile)
モデルを追加したらデータベースを再構築して,migrateする.ターミナルに入力.
python3 manage.py makemigrations
python3 manage.py migrate
データを登録するためのスーパーユーザの作成をする.ターミナルに入力.
python3 manage.py createsuperuser
ここで,ユーザ名,メールアドレス,パスワードを入力する.
サーバを起動して,adminに移動する.URLに次を入力.http://localhost:8000/admin
新しくモデルを作成して,情報を登録する.
次にURLを接続する.mysite/urls.pyに記入を続ける
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
さらに続けて,urlpatternsに以下を追加
path('', include('app.urls')),
さらに下記を追加することでメディアを追加する設定をする.
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
ここで,mysiteからappの方にpathを繋ぐ.app/urls.pyに以下を記入する.
from django.urls import path
from app import views
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
それでは,次にapp/views.pyにviewを作成する.最初にプロフィールのviewを作成.
from django.views.generic import View
from .models import Profile
class IndexView(View):
def get(self, request, *args, **kwargs):
profile_data = Profile.objects.all()
if profile_data.exists()
profile_data = profile_data.order_by('-id')[0]
return render(request, 'app/index.html', {
'profile_data': profile_data
})
このデータをテンプレートに渡すことでページが出来上がる.テンプレートの作成をする.app/templates/appを作成
ベースとなるbase.htmlをapp/templates/appの下に作成.bootstrapやcdnjsでロゴを用いる.は横並びに用いる.html/cssについては,使い回しやその度に調べたほうが良いっぽい.
{% load static %}
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css" integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="shortcut icon" href="favicon.ico" type="{% static 'img/logo.ico' %}">
<title>Portfolio</title>
</head>
<body>
<nav class="navbar navbar-expand-lg">
<div class="container">
<a href="/" class="navbar-brand">
<img src="{% static 'img/logo.svg' %}" alt="" width="80" height="80">
</a>
<ul class="navbar-nav">
<li class="nav-item mr-3">
<a href="/" class="nav-motion nav-link nav-color">HOME</a>
</li>
<li class="nav-item mr-3">
<a href="{% url 'about' %}" class="nav-motion nav-link nav-color">ABOUT</a>
</li>
<li class="nav-item">
<a href="{% url 'contact' %}" class="nav-motion nav-link nav-color">CONTACT</a>
</li>
</ul>
</div>
</nav>
<main class="container">
{% block content %}
{% endblock %}
</main>
<footer class="py-4 bg-dark text-center">
<small class="text-white">© 2021 Namba Koya</small>
</footer>
{% block extra_js %}
{% endblock %}
</body>
</html>
次に最初(index)ページのhtmlを作成.
{% extends 'app/base.html' %}
{% block content %}
<div class="card top d-flex flex-column justify-content-end mb-4">
<img src="{{ profile_data.topimage.url }}" alt="">
<div class="overlay text-white p-5">
<h4 class="title">{{ profile_data.title }}</h4>
<h5 class="subtitle">{{ profile_data.subtitle }}</h5>
</div>
</div>
<div class="row mb-5">
{% for work in work_data %}
<div class="col-6 col-md-3 mb-3">
<div class="card work-thumbnail">
{% if work.thumbnail %}
<img src="{{ work.thumbnail.url }}" alt="" class="work-img">
{% else %}
<img src="{{ work.img.url }}" alt="" class="work-img">
{% endif %}
<div class="work-title text-center">
<p class="mb-0">
{{ work.title }}
</p>
</div>
<a href="{% url 'detail' work.id %}" class="stretched-link work"></a>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
#3. 作品紹介ページの作成
作品紹介ページの作成をしていく.まずはモデルの作成をする.app/models.pyに以下を追加
class Work(models.Model):
title = models.CharField('タイトル', max_length=100)
image = models.ImageField(upload_to='images', verbose_name='イメージ画像')
thumbnail = models.ImageField(upload_to='images', verbose_name='サムネイル', null=True, blank=True)
skill = models.CharField('スキル', max_length=100)
url = models.CharField('URL', max_length=100, null=True, blank=True)
created = models.DateField('作成日')
description = models.TextField('説明')
def __str__(self):
return self.title
次に登録を行うために管理に追加.app/admin.pyに以下を追加
import Work
admin.site.register(Work)
データベースを新たに作成したので,以下のコマンドを実行.
python3 manage.py makemigrations
python3 manage.py migrate
サーバを起動して,adminに入り,データをテスト的に入力.
表示するためのviewを作成.app/viewsにviewを追加.
IndexViewに追加してあげる.
import Work
class IndexView(View):
def get(self, request, *args, **kwargs):
profile_data = Profile.objects.all()
if profile_data.exists():
profile_data = profile_data.order_by('-id')[0]
work_data = Work.objects.order_by('-id')
return render(request, 'app/index.html', {
'profile_data': profile_data,
'work_data': work_data
})
ページとして表示するために,テンプレートの作成.
app/template/app/index.htmlに以下を追加.
<div class="row mb-5">
{% for work in work_data %}
<div class="col-6 col-md-3 mb-3">
<div class="card work-thumbnail">
{% if work.thumbnail %}
<img src="{{ work.thumbnail.url }}" alt="" class="work-img">
{% else %}
<img src="{{ work.img.url }}" alt="" class="work-img">
{% endif %}
<div class="work-title text-center">
<p class="mb-0">
{{ work.title }}
</p>
</div>
<a href="" class="stretched-link work"></a>
</div>
</div>
{% endfor %}
</div>
app/static/css/style.cssを作成し,以下を追加.
.work-thumbnail {
overflow: hidden;
position: relative;
}
.work-img {
object-fit: cover;
height: 240px;
}
.work-title {
width: 100%;
postion: absolute;
bottom: 0;
left: 0;
color: white;
font-weight: bold;
padding: 10px 15px;
opacity: 0;
background-color: rgb(238, 108, 77, 0.8);
transform: translateY(70px);
transition: all 0.2s ease-in-out;
}
.work-thumbnail:hover .work-title {
transform: translateY(0px);
opacity: 1;
}
作品詳細ページの作成を行なっていく.
app/urls.pyにurlを追加.
int:pkをつけることでURLで作品ページを移動できる.
path('detail/<int:pk>', views.DetailView.as_view(), name='detail'),
app/views.pyに以下を追加
id=self.kwargs['pk']を追加することでページ遷移できる.
class DetailView(View):
def get(self, request, *args, **kwargs):
work_data = Work.objects.get(id=self.kwargs['pk'])
return render(request, 'app/detail.html', {
'work_data': work_data
})
def __init__(self, arg):
superDetailView, self).__init__()
self.arg = arg
さらに,app/templates/app/index.htmlに以下を追加
これを追加することでページの移動ができるようになる.
<a href="{% url 'detail' work.id %} ...
作品詳細ページを作成する.app/templates/app/detail.htmlを作成し,以下を入力.
{% extends 'app/base.html' %}
{% block content %}
<h3 class="mb-4">{{ work_data.title }}</h3>
<div class="card top mb-4">
<img src="{{ work_data.image.url }}" alt="">
</div>
<div class="row">
<div class="col-sm-4 mb-4">
<h4 class="mb-3">INFORMATION</h4>
<p>
<i clas="fas fa-laptop-code mr-2"></i>
<span class="font-weight-bolder">SKILLS : </span>
{{ work_data.skill }}
</p>
<hr>
<p>
<i clas="fas fa-github mr-2"></i>
<span class="font-weight-bolder">GITHUB : </span>
{{% if work_data.url %}}
<a href="{{ work_data.url }}" target="_blank" class="link-color">Link</a>
{% else %}
Private
{% endif %}
</p>
<hr>
<p>
<i clas="fas fa-calendar-alt mr-2"></i>
<span class="font-weight-bolder">CREATED : </span>
{{ work_data.created }}
</p>
<hr>
</div>
<div class="col-sm-8 mb-5">
<h4 class="mb-3">PROJECT DESCRIPTION</h4>
<p>{{ work_data.description|linebreaksbr }}</p>
</div>
</div>
{% endblock %}
static/css/style.cssに以下を追加し,記入.
.line-color {
color: #EE6C4D
}
.line-color:hover {
color: #C56C55
text-decoration: none;
}
#4. プロフィールの作成
プロフィールのモデルはできているため,app/urls.pyに以下を追加.
path('about', views.AboutView.as_view(), name='about'),
さらにapp/views.pyにAboutViewを追加
class AboutView(View):
def get(self, request, *args, **kwargs):
profile_data = Profile.objects.all()
if profile_data.exists():
profile)data = profile_data.order_by('-id')[0]
return render(request, 'app/about.html'), {
'profile_data': profile_data,
}
表示させるためのtemplateの作成.about.htmlを作成.
{% extends 'app/base.html' %}
{% block content %}
<h3 class="mb-4">Profile</h3>
<div class="mb-5">
<div class="row">
<div class="col-md-8">
<p>{{ profile_data.introduction|linebreaksbr }}</p>
</div>
<div class="col-md-4">
<div class="card text-center px-5 py-4">
<div class="avatar mb-3">
<img src="{{ profile_data.subimage.url}}" alt="" class="card-img-top rounded-circle">
</div>
<h5 class="font-weight-bolder">{{ profile_data.name }}</h5>
<p class="mb-3 small text-center">{{ profile_data.job }}</p>
<div class="d-flex justify-content-around">
{% if profile_data.github %}
<a href="{{ profile_data.github }}" target="_blank"><i class="fab fa-github fa-lg rounded btn-dark icon"></i></a>
{% endif %}
{% if profile_data.github %}
<a href="{{ profile_data.twitter }}" target="_blank"><i class="fab fa-twitter fa-lg rounded btn-primary icon"></i></a>
{% endif %}
{% if profile_data.github %}
<a href="{{ profile_data.qiita }}" target="_blank"><i class="fab fa-qiita fa-lg rounded btn-dark icon"></i></a>
{% endif %}
{% if profile_data.github %}
<a href="{{ profile_data.github }}" target="_blank"><i class="fab fa-github fa-lg rounded btn-dark icon"></i></a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
続いて,cssファイルの追加
.avatar img {
max-width: 150px;
max-height: 150px;
}
.icon {
padding: 10px 8px;
}
基本情報とは別に,職歴・学歴の追加する.まずはモデルの作成をする.app/models.pyに追加
class Experience(models.Model):
occupation = models.CharField('職歴', max_length=100)
company = models.CharField('会社', max_length=100)
description = models.TextField('説明')
place = models.CharField('場所', max_length=100)
period = models.CharField('期間', max_length=100)
def __str__(self):
return self.occupation
class Education(models.Model):
course = models.CharField('コース', max_length=100)
school = models.CharField('学校', max_length=100)
place = models.CharField('場所', max_length=100)
period = models.CharField('期間', max_length=100)
def __str__(self):
return self.Education
app/admin.pyに追加する.
from .models import Profile, Work, Experience, Education
admin.site.register(Experience)
admin.site.register(Education)
新たにモデルを追加したので,マイグレーションをする.ターミナルに入力.
python3 manage.py makemigrations
python3 manage.py migrate
ここで,サーバを起動して,adminに移動しデータを入力する.
app/viwes.pyの変更と追加を行なっていく.
importを行なって,AboutViewに追加.
from .models import Profile, Work, Experience, Education
experience_data = Experience.objects.order_by('-id')
education_data = Education.objects.order_by('-id')
'experience_data': experience_data,
'education_data': education_data
追加した情報を表示するためテンプレートに追加.app/template/app/about.htmlに追加
<h3 class="mb-4">Experiences</h3>
<div class="mb-5">
{% for experience in experience_data %}
<div class="d-flex justify-content-between">
<h5>{{ experience.occupation }} <span class="small text-secondary">- {{ experience.company }}</span> </h5>
<p class="mb-1">{{ experience.period }}</p>
</div>
<p class="mb-1">{{ experience.description }}</p>
<p>{{ experience.place }}</p>
<hr>
{% endfor %}
</div>
<h3 class="mb-4">Education</h3>
<div class="mb-5">
{% for education in education_data %}
<div class="d-flex justify-content-between">
<h5>{{ education.course }} <span class="small text-secondary">- {{ experience.school }}</span> </h5>
<p class="mb-1">{{ education.period }}</p>
</div>
<p>{{ education.place }}</p>
<hr>
{% endfor %}
</div>
モデル名の変更だけでもmigrationsが必要
#5. スキルと問い合わせページの作成
スキルとお問い合わせページの作成をしていく.まずはスキルからモデルの作成.
app/models.pyに追加.
class Software(models.Model):
name = models.CharField('ソフトウェア', max_length=100)
level = models.CharField('レベル', max_length=100)
percentage = models.IntegerField('パーセンテージ')
def __str__(self):
return self.name
class Technical(models.Model):
name = models.CharField('テクニカル', max_length=100)
level = models.CharField('レベル', max_length=100)
percentage = models.IntegerField('パーセンテージ')
def __str__(self):
return self.name
app/admin.pyに追加をする.
from .models import Profile, Work, Experience, Education, Software, Technical
admin.site.register(Software)
admin.site.register(Technical)
adminに追加をしたら,マイグレーションする.ターミナルで以下を実行.
python3 manage.py makemigrations
python3 manage.py migrate
今まで通り,サーバを起動して,データを入力.
では,viewの追加を追加していく.インポートして,AboutViewに追加.
from .models import Profile, Work, Experience, Education, Software, Technical
class AboutView(View):
def get(self, request, *args, **kwargs):
profile_data = Profile.objects.all()
if profile_data.exists():
profile_data = profile_data.order_by('-id')[0]
qualification_data = Qualification.objects.order_by('-id')
experience_data = Experience.objects.order_by('-id')
education_data = Education.objects.order_by('-id')
technical_data = Technical.objects.order_by('-id')
return render(request, 'app/about.html', {
'profile_data': profile_data,
'qualification_data': qualification_data,
'experience_data': experience_data,
'education_data': education_data,
'technical_data': technical_data
})
テンプレートの作成.app/templates/about.htmlに追加.
<h3 class="mb-4">Software Skills</h3>
<div class="mb-5">
<div class="row">
{% for software in software_data %}
<div class="col-6 col-md-3">
<h5 class="col-0">{{ software.name }}<span class="small text-secondary"> - {{ software.level}}</span></h5>
<div class="d-flex flex-row">
<div class="star-rating">
<div class="star-rating-front" style="width: {{ software.percentage }}%">★★★★★</div>
<div class="star-rating-back">★★★★★</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<h3 class="mb-4">Technical Skills</h3>
<div class="mb-5">
<div class="row">
{% for technical in technical_data %}
<div class="col-6 col-md-3">
<h5 class="col-0">{{ technical.name }}<span class="small text-secondary"> - {{ technical.level}}</span></h5>
<div class="d-flex flex-row">
<div class="star-rating">
<div class="star-rating-front" style="width: {{ technical.percentage }}%">★★★★★</div>
<div class="star-rating-back">★★★★★</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
app/static/css/style.cssの変更をする.
star-rating {
position: relative;
font-size: 30px;
word-wrap: normal!important;
}
.star-rating-front {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
color: #EE6C4D
}
.star-rating-back {
color: #ccc;
}
最後にお問い合わせの作成をしていく.
まずは以下のようにmysite/setting.pyに追加する.こうすることで,自分のメールアドレスに通知がくる.
# 開発
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# 本番
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = True
app/urls.py に追加を追加.
path('contact/', views.ContactView.as_view(), name='contact'),
フォームの作成を行なっていく.新たにapp/forms.pyを作成.以下を記入.
from django import forms
class ContactForm(forms.From):
name = forms.CharField(max_length=100, label='名前')
email = forms.EmailField(max_length=100, label='メールアドレス')
message = forms.CharField(label='メッセージ', widget=forms.Textarea)
viewを作成していく.まずは追加するものを追加する.
from django.shortcuts import render, redirect
from .forms import ContactForm
from django.conf import settings
from django.coer.mail import BadHeaderError, EmailMessage
from django.http import HttpResponse
import textwrap
getはページを表示するとき,postはサーバに送信するときにコールされる
textwrapは何かと便利みたい
さらに,追加していく.
class ContactView(View):
def get(self, request, *args, **kwargs):
form = ContactForm(request.POST or None)
return render(request, 'app/contact.html', {
'form': form
})
def post(self, request, *args, **kwargs):
form = ContactForm(request.POST or None)
if form.is_valid():
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
subject = 'お問い合わせありがとうございます.'
contact = textwrap.dedent('''
※このメールはシステムからの自動返信です.
{name} 様
お問い合わせありがとうございました.
以下の内容でお問い合わせを受け付けいたしました.
内容を確認させていただき,ご返信させていただきますので,少々お待ちください.
--------------------
■お名前
{name}
■メールアドレス
{email}
■メッセージ
{message}
-------------------
''').format(
name = name,
email = email,
message = message
)
to_list = [email]
bcc_list = [settings.EMAIL_HOST_USER]
try:
message = EmailMessage(subject=subject, body=contact, to=to_list, bcc=bcc_list)
message.send()
except BadHeaderError:
return HttpResponse('無効なヘッダが検出されました.')
return redirect('index')
return render(request, 'app/contact.html', {
'form': form
})
ページ遷移できるように,base.htmlを変更
<a href="{% url 'contact' %}" class="nav-link nav-color">CONTACT</a>
それではページの作成をする.contact.htmlを書いていく.
widget_tweaksを用いると楽ができる.
{% csrf_token %}は安全な送信をするために必ず必要である.
{% extends 'app/base.html' %}
{% load widget_tweaks %}
{% block content %}
<div class="card text-center contact mx-auto rounded">
<div class="card-body">
<h2 class="mb-4">Contact Me</h2>
<form class="" action="index.html" method="post">
{% csrf_token %}
<div class="mb-3">
{% render_field form.name class='form-control' placeholder='名前' %}
</div>
<div class="mb-3">
{% render_field form.email class='form-control' placeholder='メールアドレス' %}
</div>
<div class="mb-3">
{% render_field form.message class='form-control' placeholder='メッセージ' %}
</div>
<div class="mb-3">
<button class="btn btn-warning" type="submit"><i class="fas fa-paper-plane"></i></button>
</div>
</form>
</div>
</div>
{% endblock %}
今度はcssを書いていく
.contact {
max-width: 500px;
}
.form-control:focus {
border-color: #EE6C4D
box-shadow: none;
}
.card {
border: none;
}
送信完了viewの作成をする.
app/urls.pyに追加をする.ページを移動するため.
path('thanks/', views.ThanksView.as_view(), name='thanks'),
app/views.pyに追加
class ThanksView(View):
def get(self, request, *args, **kwargs):
return render(request, 'app/thanks.html')
thanks.htmlを作成する.
{% extends 'app/base.html' %}
{% block content %}
<div class="text-center my-5">
<h1 class="mb-5">お問い合わせありがとうございました.</h1>
<p class = "mb-2">内容を確認いたしまして,ご連絡させて頂きます.</p>
<p class = "mb-4">しばらくお待ちください.</p>
<a href="/" class="btn btn-warning">ホームに戻る</a>
</div>
{% endblock %}
#6. おまけに
githubでコードの管理をするときにはsecret-passwordには注意しなければいけない.もし,リモートにpushしてしまったら以下が参考になる.
#7. 参考
Djangoを用いたポートフォリオの作成.
DockerとDjango
ページ内リンク作成