portfolioフォルダー作成
mkdir portfolio
.gitignoreファイルを作成
myvenv
db.sqlite3
.vscode
__pycache__
*.pyc
.DS_Store
media
仮想環境の作成
python3 -m venv myvenv
仮想環境の実行
source myvenv/bin/activate
requirements.txt作成
Django~=3.1.4
Pillow~=8.1.0
django-widget-tweaks~=1.4.8
パッケージのインストール
pip3 install -r requirements.txt
プロジェクトを作成
django-admin startproject mysite .
settings.pyを修正
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
d
python3 manage.py migrate
python3 manage.py runserver
アプリケーション作成
python3 manage.py startapp app
‘widget_tweaks’と’app’を追加する
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'widget_tweaks', # 追加
'app', # 追加
]
'widget_tweaks','app',
モデルの作成
modelsモジュールを読み込み
これは、データベースを簡単に作るために必要な情報が入っています。
from django.db import models
Modelsクラスの継承
Profileという名前でクラスを作成しました。新しいデータベースを作るには、class定義をすることによって作成します。また、modelsモジュールからModelsクラスを継承しています(Modelsクラスで定義されているメソッドや属性を使えるようにしています)。
class Profile(models.Model):
パラメーターの指定
models.Model
はポストがDjango Modelだという意味で、Djangoが、これはデータベースに保存すべきものだと分かるようにしています。models.Modelを継承し、Modelの子クラスとしてのProfileを作っています。
- null
フィールドがnullになっても良いかどうか
例: null=True
ケース: 既存のテーブルに後から、フィールドを追加する場合、null=Trueにすることで、既に存在しているデータに追加された新フィールドの値をnullにすることができる - blank
フィールドが空白になっても良いかどうか
例: blank = True
ケース: 値が空白でもOKかどうかを設定できる。上記のArticle において、カテゴリがなくても、データを作成したい場合、 blank=Trueにすると、実現できる - verbose_name
- フィールドの表示名を変更できる例: verbose_name = “記事タイトル”ケース: Articleテーブルのデータを取得した時に、表示名を設定できる。本来、英語で、 title と返るが、日本語で、タイトル などに設定することができる。
class Profile(models.Model):
title = models.CharField('タイトル', max_length=100, null=True, blank=True)
from django.db import models
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)
linkedin = models.CharField('linkedin', max_length=100, null=True, blank=True)
facebook = models.CharField('facebook', max_length=100, null=True, blank=True)
instagram = models.CharField('instagram', 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.nam
管理画面でデータを登録できるようにします。
from django.contrib import admin
from .models import Profile
admin.site.register(Profile)
マイグレーション実行
モデルを追加したので、マイグレーションをします。
(myvenv) ~$ python3 manage.py makemigrations
(myvenv) ~$ python3 manage.py migrate
プロジェクトURL
URLConf
DjangoのURLディスパッチャ=URLConf=urls.py
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('app.urls')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
トップページのURLを作成
URLタグ
view.pyファイルの中で定義されたIndexViewとして定義されたviewを呼び出す。
IndexViewはviewのas_view()関数を呼び出します。
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
from django.urls import path
from app import views
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
]
View
IndexViewはViewを継承している。そのためViewをインポートしています。
from django.views.generic import View
すべてのprofile_dataを取得します。
idを使ってデータを降順に並び替えて最新のprofile_dataを取得する。
profile_dataをindex.htmlに渡します。
コンテキストを取得して、テンプレートに渡す
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,
})
from django.views.generic import View
from django.shortcuts import render
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,
})
base.html
HTMLの<nav>
要素は、ナビゲーションリンクを持つセクションであることを表します。ナビゲーションリンクとは、そのページ内へのリンクや外部ページへのリンクを指します。
<ul>による複数のリンクのリストを<nav>で囲んでいる。
hrefでリンク先の場所を指定することが役割
href=”/”
テンプレートタグ
この {% ... %}
はDjangoのテンプレートタグを使用していることを意味しています。
「テンプレートタグ」は、「この部分で何かテンプレートの 動作 をさせるよ」という意味になります

<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">
<img src="{% static 'img/logo.svg' %}" width="80" height="80">
</a>
<ul class="navbar-nav">
<li class="nav-item mr-3">
<a class="nav-link nav-color" href="/">HOME</a>
</li>
<li class="nav-item mr-3">
<a class="nav-link nav-color" href="">ABOUT</a>
</li>
<li class="nav-item">
<a class="nav-link nav-color" href="">CONTACT</a>
</li>
</ul>
</div>
</nav>
{% 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://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<title>Portfolio</title>
</head>
<body>
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">
<img src="{% static 'img/logo.svg' %}" width="80" height="80">
</a>
<ul class="navbar-nav">
<li class="nav-item mr-3">
<a class="nav-link nav-color" href="/">HOME</a>
</li>
<li class="nav-item mr-3">
<a class="nav-link nav-color" href="">ABOUT</a>
</li>
<li class="nav-item">
<a class="nav-link nav-color" href="">CONTACT</a>
</li>
</ul>
</div>
</nav>
<main>
<div class="container">
{% block content %}
{% endblock %}
</div>
</main>
<footer class="py-4 bg-dark text-center">
<small class="text-white">© 2021 perpetualtraveler</small>
</footer>
{% block extra_js %}
{% endblock %}
</body>
</html>
index.html
{{ }}はHTMLに表示させる
{%%}は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">
<h1 class="title">{{ profile_data.title }}</h1>
<h5 class="subtitle">{{ profile_data.subtitle }}</h5>
</div>
</div>
{% endblock %}
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #F1F1F1;
display: flex;
flex-flow: column;
min-height: 100vh;
}
main {
flex: 1;
}
/* index */
.navbar-nav {
flex-direction: row!important;
}
.nav-color {
color: black;
}
.nav-color:hover {
color: #EE6C4D;
}
.nav-color:after {
content: "";
display: block;
height: 2px;
background: #EE6C4D;
margin-top: 6px;
opacity: 0;
transform: translateY(12px);
transition: all 0.3s ease-in-out;
}
.nav-color:hover:after {
transform: translateY(0px);
opacity: 1;
}
.top img {
object-fit: cover;
height: 500px;
}
.overlay {
position: absolute;
}
.title {
font-size: 4rem;
}
.subtitle {
font-size: 2rem;
}
管理者から追加
新たにmediaフォルダーができ画像が保存されています。


上記を作成するためmodels.pyにProfileのクラスを作成します。

work


experience


education


software


Technical


作品リスト
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
管理画面でデータを登録できるようにする
from django.contrib import admin
from .models import Profile, Work
admin.site.register(Profile)
admin.site.register(Work)
新たにWorksの

詳細画面のURLを作成します。
<int:pk>
は、WorkデータのIDを示しています。
from django.urls import path
from app import views
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('detail/<int:pk>/', views.DetailView.as_view(), name='detail'),
詳細画面のビューを作成
DetailViewクラスを追加します。self.kwargs['pk']
はURLで指定した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
})
詳細画面へのリンクを追加
<a class="stretched-link work" href="{% url 'detail' work.id %}"></a>
詳細画面のテンプレートを作成
{% 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 class="fas fa-laptop-code mr-2"></i>
<span class="font-weight-bolder">SKILLS : </span>
{{ work_data.skill }}
</p>
<hr>
<p>
<i class="fab fa-github mr-2"></i>
<span class="font-weight-bolder">GITHUB : </span>
{% if work_data.url %}
<a class="link-color" href="{{ work_data.url }}" target="_blank">Link</a>
{% else %}
Private
{% endif %}
</p>
<hr>
<p>
<i class="far 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 %}
f
/* detail */
.link-color {
color: #EE6C4D;
}
.link-color:hover {
color: #c56c55;
text-decoration: none;
}
プロフィールのビューを作成
Profileモデルを読み込んで、テンプレートにデータを渡します。
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,
})
プロフィール画面へのリンクを作成
<a class="nav-link nav-color" href="{% url 'about' %}">ABOUT</a>
プロフィール画面のテンプレートを作成
{% 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 class="card-img-top rounded-circle" src="{{ profile_data.subimage.url }}" alt="">
</div>
<h5 class="font-weight-bolder">{{ profile_data.name }}</h5>
<p class="mb-3 small text-center">{{ profile_data.job|linebreaksbr }}</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.twitter %}
<a href="{{ profile_data.twitter }}" target="_blank"><i class="fab fa-twitter fa-lg rounded btn-primary icon"></i></a>
{% endif %}
{% if profile_data.linkedin %}
<a href="{{ profile_data.linkedin }}" target="_blank"><i class="fab fa-linkedin-in fa-lg rounded btn-info icon"></i></a>
{% endif %}
{% if profile_data.facebook %}
<a href="{{ profile_data.facebook }}" target="_blank"><i class="fab fa-facebook-f fa-lg rounded btn-primary icon"></i></a>
{% endif %}
{% if profile_data.instagram %}
<a href="{{ profile_data.instagram }}" target="_blank"><i class="fab fa-instagram fa-lg rounded btn-danger icon"></i></a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
index.html
トップページに画像を表示させます。
d-flex flex-column で縦並びにします。
justify-content-endで右寄せ
{% 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">
<h1 class="title">{{ profile_data.title }}</h1>
<h5 class="subtitle">{{ profile_data.subtitle }}</h5>
</div>
</div>
{% endblock %}
CSS
常にフッターが下。
main {
flex: 1;
}