djangoでポートフォリオサイトを作成する

#◆Note

In this article, I study English and programming.
So, poor English is written above Japanese.
Note that.

本記事ではプログラミングの勉強に加えて英語の勉強を行います。
そのため、日本語の上に拙い英語が記載されています。
ご了承ください。

#◆djangoでポートフォリオサイトを作成する(Creating my portfolio site using django.)

In this article, I will create my portfolio site using django.
本記事ではdjangoを用いてポートフォリオサイトを作成していきます。

#◆ポートフォリオサイトを作成する目的(Purpose of creating my portfolio site.)

1. To create a page that describes my skills and achievements.
2. In the future, to present the site for career advancement.
3. To study django through the creation of my portfolio site.

1. 自身のスキルや実績を記載するページを作るため。
2. 将来、キャリアアップのためにサイトを提示するため。
3. ポートフォリオサイトの制作を通じてdjangoの勉強をするため。 

#◆実際の手順(Actual produce)

I referred to the following video that creates a portfolio site using django.
I introduce it without premission.
Please refer to it.

djangoでポートフォリオサイトを作成する以下の動画を参考にしました。
勝手ながら紹介させていただきます。
是非、ご参考ください。

https://www.youtube.com/watch?v=BxQY3D9s_Mc

#◆仮想環境の作成(Creating a virtual environment)

pythonの公式サイトを参考。
https://www.python.jp/install/windows/venv.html

```
[sample.py]

import requests

print(requests.get("https://www.python.jp").text)

```

#◆仮想環境の構築

```
pip3 install virtualenv
```

```
$ virtualenv
-p python3 myvenv
created virtual environment CPython3.8.2.final.0-64 in 10197ms
  creator CPython3Posix(dest=/mnt/c/Users/tetsu/git/django-portfolio/myvenv, clear=False, no_vcs_ignore=False, global=False)
  seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/tetsuya/.local/share/virtualenv)
    added seed packages: pip==21.0.1, setuptools==52.0.0, wheel==0.36.2
  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator
```

```
$ source myvenv/bin/activate
(myvenv) tetsuya@Surface:/mnt/c/Users/tetsu/git/django-portfolio$
```

#◆requirement.txtの作成

```
Django~=2.2.0
Pillow~=8.1.0
django-widget-tweaks~=1.4.8
```


```
$ pip3 install -r requirements.txt
```

```
$ django-admin startproject mysite .
```

#◆settings.pyを書き換える

```
LANGUAGE_CODE = 'en-us'
↓
LANGUAGE_CODE = 'ja'
```

```
TIME_ZONE = 'UTC'
↓
TIME_ZONE = 'Asia/Tokyo'
```

以下を追記。

```
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
```

#◆マイグレートをしてデータベースを構築

```
$ 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 sessions.0001_initial... OK
```

#◆開発用サーバの立ち上げ

http://127.0.0.1:8000/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/0c9bf2c2-0e25-17a9-8af5-0b1cb305db1e.png)

#◆アプリの作成

```
$ python manage.py startapp app
```

settings.pyをいじる

```
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'widget_tweaks',
    'app',
]
```

#◆自己紹介画面の作成

```
models.py

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.name
```

#◆管理画面の作成

```
admin.py

from django.contrib import admin
from .models import Profile

admin.site.register(Profile)
```

#◆データベースを再構築

モデルを追加したのでマイグレーションを行い、データベースを再構築する。
```
$ python manage.py makemigrations

Migrations for 'app':
  app/migrations/0001_initial.py
    - Create model Profile
```

```
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, app, auth, contenttypes, sessions
Running migrations:
  Applying app.0001_initial... OK
```

#◆管理ユーザを作成する

```
$ python manage.py createsuperuser
```

#◆管理画面を確認する

```
python manage.py runserver
```

管理画面にアクセスする。
http://127.0.0.1:8000/admin

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/21586c6b-da9a-75d7-1727-70830a585e33.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/ef87162e-51e8-c359-6d8e-d376683141bd.png)

Profileを登録します。

#URLを設定する

プロジェクトのurls.pyを設定する。

```
urls.py
```

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

アプリケーション用のurls.pyを作成する。

```
from django.urls import path
from app import views

urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]
```

#◆トップページ用のviewを作成

```
from django.shortcuts import render
from django.views.generic import View
from .models import Profile

class IndexView(View):
    """トップページのビュー

    トップページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        すべてのプロフィールデータを取得。
        """
        profile_data = Profile.objects.all()
        if profile_data.exists():
            #idを降順に並べ替え、最新のプロフィールデータを取得
            profile_data = profile_data.order_by('-id')[0]
        #プロフィールデータをindex.htmlに渡す
        return render(request, 'app/index.html', {
            'profile_data': profile_data
        })
```

#◆テンプレートの作成

appの下の階層にtemplatesフォルダを作成。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/b8d778b4-099e-86b5-d1ff-083f89f4e0ce.png)

templates直下にappフォルダを作成。
さらにappフォルダ内にbase.htmlを作成する。

Bootstrapのcssを読み込む。
リンクは以下から取得。
https://getbootstrap.jp/docs/4.5/getting-started/introduction/

FontawesomeのCSSを読み込む
###Fontawesomeとは
商用利用可能なWebアイコンフォント。

<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css">

```
base.html
```

```
{% load static %}

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css">
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
    <title>ポートフォリオサイト</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-link nav-color">HOME</a>
                </li>
                <li class=""nav-item mr-3>
                    <a href="/" class="nav-link nav-color">ABOUT</a>
                </li>
                <li class=""nav-item mr-3>
                    <a href="/" class="nav-link nav-color">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">&copy; 2021 Tett</small>
    </footer>

    {% block extra_js %}
    {% endblock %}

</body>
</html>
```

##トップページ用のindex.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">
        <h1 class="title">{{ profile_data.title }}</h1>
        <h5 class="subtitle">{{ profile_data.subtitle }}</h5>
    </div>

</div>
.card.top.d-flex.flex-column.justify-content-end 

{% endblock %}
```

#◆cssファイルを追加

app直下にstaticフォルダを作成する。
さらにその下にcssフォルダを作成する。
その中に"style.css"を作成。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/4d7795c4-ecb3-cfd1-76e6-ad0874a21133.png)

```
style.css
```

```
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: #f1f1f1;
    display: flex;
    flex-flow: column;
    min-height: 100vh;
}

.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: translate(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;
}
```

#◆ロゴ画像を配置

以下の階層にロゴ画像を配置しました。
形式は.svgとしています。
ロゴの作成はパワーポイントで行いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/d438de76-e412-aca3-56c6-4003096875c6.png)

#◆現時点でのサイトの確認

以下のコマンドで開発用サーバを立ち上げ、サイトを確認します。

```
$ python manage.py runserver
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/063f0aea-e9d6-a347-8ea1-55af3967e277.png)

#◆作品やアプリを記載する

ここからは開発した作品やアプリを記載する方法を記していきます。

#◆モデルの作成

app直下のmodels.pyにモデルを作成します。
以下を追記する。

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

#◆管理画面で作品用データを登録できるようにする

```
admin.py
```

```
from django.contrib import admin
from .models import Profile, Work

admin.site.register(Profile)
admin.site.register(Work)
```

#◆マイグレーション
モデルを変更したため、マイグレーションを行う。

```
$ python manage.py makemigrations
```

```
$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, app, auth, contenttypes, sessions
Running migrations:
  Applying app.0002_work... OK
```

#◆管理サイトで作品を登録

レイアウト等の確認を行うため、作品を4つ登録しておきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/830b5a64-167e-dc56-52ae-291b63450ece.png)

#◆トップページに作品データを表示

app直下のviews.pyを変更する。

```
views.py
```

```
from django.shortcuts import render
from django.views.generic import View
from .models import Profile, Work

class IndexView(View):
    """トップページのビュー

    トップページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        すべてのプロフィールデータを取得。
        """
        profile_data = Profile.objects.all()
        if profile_data.exists():
            #idを降順に並べ替え、最新のプロフィールデータを取得
            profile_data = profile_data.order_by('-id')[0]
        work_data = Work.objects.order_by('-id')
        #プロフィールデータをindex.htmlに渡す
        return render(request, 'app/index.html', {
            'profile_data': profile_data
            'work_data':work_data
        })
```

#◆トップページの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>

<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.image.url }}" alt="" class="work-img">
                {% endif %}
                <div class="work-title text-center">
                    <a class="mb-0">
                        {{ work.title }}
                    </a>
                </div>
                <a class="streched-link work"></a>
            </div>
        </div>
    {% endfor%}
</div>

{% endblock %}


```

"◆cssを変更

```
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background: #f1f1f1;
    display: flex;
    flex-flow: column;
    min-height: 100vh;
}

.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: translate(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;
}

.work-thumbnail {
    overflow: hidden;
    position: relative;
}

.work-img {
    object-fit: cover;
    height: 240px;
}

.work-title {
    width: 100%;
    position: absolute;
    bottom: 0;
    left: 0;
    color: white;
    font-weight: bold;
    padding: 0;
    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;
}
```

#◆トップページを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/2bc94779-d7d3-c17f-9493-881ce0f95951.png)

#◆作品詳細ページを作成する
ここから、作品の詳細ページを作成していきます。
app直下のurs.pyを編集する。

```
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'),
]
```

#◆作品詳細ページ用のビューを追記

```
views.py
```

```
class DetailView(View):
    """作品詳細ページのビュー

    作品詳細ページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        作品データをを取得。
        """
        work_data = Work.objects.get(id=self.kwargs['pk'])
        return rendar(request, 'app/detail.html', {
            'work_data': work_data
        })
```

#◆トップページの作品画像にリンクを埋め込む

```
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.image.url }}" alt="" class="work-img">
                {% endif %}
                <div class="work-title text-center">
                    <a class="mb-0">
                        {{ work.title }}
                    </a>
                </div>
                <a href="{% url 'detail' work.id %}" class="stretched-link work"></a>
            </div>
        </div>
    {% endfor%}
</div>
```

#◆作品詳細ページ用のhtmlファイルを作成する

```
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 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 href="{{ work_data.url }}" target="_blank" class="link-color">Link</a>
            {% else %}
                <!--URLがない場合、Privateと表示-->
                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>
```

#◆cssを編集

以下を追記

```
.link-color {
    color: #EE6C4D;
}

.link-color:hover {
    color: #C56C55;
    text-decoration: none;
}
```

#◆作品詳細ページを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/b24c80be-0c5a-c60e-14a5-0a8b86da575e.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/8c23b6e0-c25f-7a18-8f79-3cf367ad57b0.png)

#◆プロフィールページを作成
ここからはプロフィールページを作成していきます。

#◆プロフィールページ用のurlを追加

```
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'),
    path('about/', views.AboutView.as_view(), name='about'),
]
```

#◆プロフィール用のビューを作成

```
class AboutView(View):
    """プロフィールページのビュー

    プロフィールページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        プロフィールデータを取得
        """
        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,
        })
```

#◆baseファイルのリンクを変更する

ABOUTボタンをクリックすると、プロフィールページに遷移するように変更。

```
base.html
```

```
                <li class=""nav-item mr-3>
                    <a href="{% url 'about' %}" class="nav-link nav-color">ABOUT</a>
                </li>
```

#◆プロフィールページ用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|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 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 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%}
```

#◆cssを変更

```
.avatar img {
    max-width: 150px;
    max-height: 150px;
}

.icon {
    padding: 10px 8px;
}
```

#◆プロフィールページを確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/d5a53624-6198-e931-7675-e84ef7039864.png)

#◆職歴と学歴モデルを作成

```
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.course
```

#◆管理画面で職歴と学歴を登録できるようにする

```
admin.py
```

```
from django.contrib import admin
from .models import Profile, Work, Experience, Education

admin.site.register(Profile)
admin.site.register(Work)
admin.site.register(Experience)
admin.site.register(Education)
```

#◆マイグレーション

```
$ python manage.py makemigrations
```

```
$ python manage.py migrate
```

#◆管理サイトで職歴・学歴を登録

テスト用として登録しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/2d3ffc84-7f01-c37f-861c-378a95eae332.png)

#◆ビューを作成

```
views.py
```

```
class AboutView(View):
    """プロフィールページのビュー

    プロフィールページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        プロフィールデータを取得
        """
        profile_data = Profile.objects.all()
        if profile_data.exists():
            profile_data = profile_data.order_by('-id')[0]
        experience_data = Experience.objects.order_by('-id')
        education_data = Education.objects.order_by('-id')
        return render(request, 'app/about.html', {
            'profile_data': profile_data,
            'experience_data': experience_data,
            'education_data' :education_data,
        })
```

#◆プロフィール用ページのhtmlファイルに追記

```
about.html
```

```
<h3 class="mb-4">Experience</h3>
<div class="mb-4">
    {% 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|linebreaksbr }}</p>
        <p class="mb-1"> {{ experience.place }}</p>
        <hr>
    {% endfor %}
</div>

<h3 class="mb-4">Education</h3>
<div class="mb-4">
    {% for education in education_data %}
    <div class="d-flex justify-content-between">
        <h5>{{ education.course }} <span class="small text-secondary"> - {{ education.school }}</span></h5>
        <p class="mb-1">{{ education.period }}</p>
    </div>
        <p class="mb-1"> {{ education.place }}</p>
        <hr>
    {% endfor %}
</div>
```

#◆スキルと問い合わせページの作成

これ以降でスキルとお問い合わせページを作っていきます。

#◆モデルの作成

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

#◆管理ページに登録

```
admin.py
```

```
from django.contrib import admin
from .models import Profile, Work, Experience, Education, Software, TextField

admin.site.register(Profile)
admin.site.register(Work)
admin.site.register(Experience)
admin.site.register(Education)
admin.site.register(Software)
admin.site.register(TextField)
```

#◆マイグレーション

```
$ python manage.py makemigrations
```

```
$ python manage.py migrate
```

#◆管理画面でデータを登録

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/73313cf5-a40e-5d9e-cac8-0241e67b7027.png)

#◆ビューを変更

```
views.py
```

```
class AboutView(View):
    """プロフィールページのビュー

    プロフィールページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        プロフィールデータを取得
        """
        profile_data = Profile.objects.all()
        if profile_data.exists():
            profile_data = profile_data.order_by('-id')[0]
        experience_data = Experience.objects.order_by('-id')
        education_data = Education.objects.order_by('-id')
        software_data = Software.objects.order_by('-id')
        technical_data = Technical.objects.order_by('-id')
        return render(request, 'app/about.html', {
            'profile_data': profile_data,
            'experience_data': experience_data,
            'education_data' :education_data,
            'software_data': software_data,
            'technical_data': technical_data,
        })
```

#◆プロフィールページの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-md3">
                <h5 class="mb-0">{{ software.name }}<span class="small.secondary"> - {{ software.level }}</span></h5>
                <div class="d-flex flex-row">
                    <div class="star-rating">
                        <div class="star-rating-star" style="width: {{ software.percemtage }}%">★★★★★</div>
                        <div class="star-rating-back">★★★★★</div>
                    </div>
                </div>
            </div>
        { endfor }
    </div>
</div>

<h3 class="mb-4">Technical</h3>
<div class="mb-5">
    <div class="row">
        { for Technical in Technical_data }
            <div class="col-6 col-md3">
                <h5 class="mb-0">{{ Technical.name }}<span class="small.secondary"> - {{ Technical.level }}</span></h5>
                <div class="d-flex flex-row">
                    <div class="star-rating">
                        <div class="star-rating-star" style="width: {{ Technical.percemtage }}%">★★★★★</div>
                        <div class="star-rating-back">★★★★★</div>
                    </div>
                </div>
            </div>
        { endfor }
    </div>
</div>
```

#◆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;
}
```


#◆画面を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/9f408581-c6c7-bbe6-27ac-0732c8edc4b7.png)

#◆お問い合わせページを作成

mysiteのsettings.pyを変更する

```
settings.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 = 'xxxxx@gmail.com'
# EMAIL_HOST_PASSWORD = 'xxxxxxxxx'
# EMAIL_USE_TLS = True
```

#◆お問い合わせページ用URLを作成

app直下のurls.pyに追記

```
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'),
    path('about/', views.AboutView.as_view(), name='about'),
    path('contact/', views.ContactView.as_view(), name='contact'),
]
```

#◆お問い合わせフォームを作成

forms.pyは用意されていないため、app直下に新規作成する。

```
forms.py
```

```
from django import forms



class ContactForm(forms.Form):
    """ お問い合わせページ用フォーム
    """
    name = forms.CharField(max_length=100, label='名前')
    email = forms.EmailField(max_length=100, label='メールアドレス')
    message = forms.CharField(label='メッセージ', widget=foms.Textarea())
```


#◆お問い合わせページ用ビューを作成

```
from django.shortcuts import render, redirect
from django.views.generic import View
from .forms import ContactForm
from .models import Profile, Work, Experience, Education, Software, Technical
from django.conf import settings
from django.core.mail import BadHeaderError, EmailMessage
from django.http import HttpResponse
import textwrap
```

```
class ContactView(View):
    """お問い合わせページのビュー

    お問い合わせページのビューを記載。
    Attributes:
    """
    def get(self, request, *args, **kwargs):
        """get関数

        お問い合わせデータを取得
        ページ表示にコールされる
        """
        form = ContactForm(request.POST or None)
        return render(request, 'app/contact.html', {
            'form': form
        })


    def post(self, request, *args, **kwargs):
        """post関数

        お問い合わせデータをサーバに送信
        """
        form = ConatctForm(request.POST or None)

        #フォーム内容が正しいかを判断
        if forms.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}
                ---------------
                
                ※This email is an automatic reply from the system.

                Dear {name}

                Thank you for your inquiry.
                We have received inquiries with the above contents.
                We will check the contents and reply to you, so please be patient.
                ''').format(
                    name=name,
                    email=email,
                    message=message
                )
            to_list = [email]
            #自分のメールアドレスをBccに追加
            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
```

```

                <li class=""nav-item mr-3>
                    <a href="{% url 'contact' %}" class="nav-link nav-color">CONTACT</a>
                </li>
```

#◆お問い合わせページ用HTMLファイルを作成

```
{% 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 action="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"></div>
                <button class="btn btn-warning" type="submit"><i class="fas fa-paper-plane"></i>送信する</button>
            
        </form>
    </div>
</div>

{% endblock %}
```


#◆Cssを追加

```
.contact {
    max-width: 500px;
}

.form-control:focus {
    border-color: #EE6C4D;
    box-shadow: none;
}

.card {
    border: none;
}
```

#◆画面を確認

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/764778/b37d142a-37a0-cdd7-2c3d-69b53dbda5c5.png)