Agile育成ブログ
未来を変える喜びを
Django

ポートフォリオサイト構築


Warning: count(): Parameter must be an array or an object that implements Countable in /home/xs638785/agile-software.site/public_html/wp-content/plugins/rich-table-of-content/functions.php on line 490

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

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)
https://www.agile-software.site/2021/04/17/%e3%83%a2%e3%83%87%e3%83%ab/
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ディスパッチャURLConfurls.py

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
https://www.agile-software.site/2021/04/04/django/
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'),
]
https://www.agile-software.site/2021/04/25/django-%e9%80%86%e5%bc%95%e3%81%8d/
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,
        })
https://www.agile-software.site/2021/04/17/django_view/
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>
https://www.agile-software.site/2021/04/08/bootstrap/
{% 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 %}

/* 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;
}

You cannot copy content of this page