見出し画像

【Django初心者用】3日で完成するブログ機能の作成まで(作成途中)

こんにちは(@t_kun_kamakiri

Djangoを勉強して3か月が経ちました。
色々な例題で練習して、Djangoの基本的なことが理解できたと思うので、ここらあたりでアウトプットの意味も込めてDjangoの初心者用の教材を作ろうと思います。

【自己紹介】
大学院時代の研究でFortranを使った数値シミュレーションをしていました。大学の専攻は物理です。
現在は、製造業でCAE解析をしています。
Pythonは、2年前(2018年の正月)に「なんか最近流行ってるな~」って思って、ノリで独学で始めました。
Djangoは2020年の8月頃から勉強をしています。
PythonもDjangoも全くの初心者から、家に帰って黙々とひとりで勉強を続けています。

今回は、「3日で作成するブログ機能」をDjangoで作成したいと思います。

今回作成する内容はこちらです。

【対象者】
●Pythonの基礎的な内容を抑えていること
●Djangoのチュートリアルを一通り学んでいること

Python の基礎講座はブログに記載しています。

今回のテーマは「3日で作成するブログ機能」です。

「3日」としてのには理由があります。

「3日坊主」という言葉があるように、3日で終わらせないと、挫折もしくは飽きてしまってだいたい完成までたどり着かないという結末になります。

なので、ある程度こちらでカリキュラムを組んでいて方が良いかなという感じに思っています。

なので、3日で確実に終わらせましょう(^^)/

※こちらは教材として完成したら500円の有料にしようと思います。
完成するまでは、無料で公開しておきます。
教材の完成目標は11月中旬です。

1日目:【目標】

まずは、PythonとDjangoのインストールを行います。

1.1:djangoのインストール

pip install django

上手くいかない人はこちらを試してみてください。

sudo apt-get install python3-django


Djangoがインストールできたら、Djangoのversionの確認します。

python3 -m django --version

3.1

現在のDjangoのバージョンは3.1です。


1.2:ディレクトリの作成

では、今回作成するWebアプリのプロジェクトを作成していきます。

そのために、適当なディレクトリを作成して、作成したディレクトリに移動をします。
今回は、「20201016_blog」という名前のディレクトリを作成しました。

mkdir 20201016_blog
cd 20201016_blog/

1.3:プロジェクトとアプリの作成

Djangoは1つのプロジェクトに複数のアプリを作成する構造となっています。絵にするとこんな感じ。

画像45

●プロジェクト:blog_project
●アプリ:blog_project

まずは、プロジェクトの作成を行います。
以下のコマンドをコマンドプロンプトで打つことでプロジェクトが作成されます。
プロジェクトの作成

django-admin startproject blog_project

pythonのバージョンに問題がある場合は以下のようにするとうまくいくかもしれません。

python3 -m django startproject blog_project

画像1

「blog_project」のディレクトリの中に「blog_project」という名前のディレクトリがあり、その中に色々なファイルがありますね。

※↓以下余談ですが、最後に「.(ドット)」を付けると、「blog_project」の直下にプロジェクトのファイルが作成されます。

django-admin startproject blog_project .

今回は、最後に「.(ドット)」を付けない方法でプロジェクトを作成したとして、「blog_project」のディレクトリの中に「blog_project」ディレクトリにを移動します。

cd blog_project/

現在のディレクトリの中身を見ておきましょう。

画像2


続いて、アプリの作成を行います。
アプリ作成のコマンドは以下のようにコマンドプロンプトで打ちます。

アプリケーションを作成

python3 manage.py startapp blog_app

そうすると以下のようなファイル構造で色々と作成されます。

画像5

ファイル構成はこのようになっています。

1.3:接続の確認

まずは、ローカル環境で接続の確認を行いましょう。

ローカル環境でWebサーバーを立ち上げるには、以下のコマンドを打ちます。
※このコマンドは、Web上での表示を確認するために、頻繁に使います。

 python manage.py runserver

画像3

色々となにやら警告が出ていますが、無視します。
エラーではないので、無視して次に進みます。

http://127.0.0.1:8000/に接続

画像4

「Quit the server with CONTROL-C」とあるように、Ctrl+Cでローカルサーバーを止めることができます。

1.4:ブラウザ上に「Hello World」と表示させる

ちょっと使い方を確認するために、ブラウザ上にプログラミング初学者にお馴染みの「Hello World」を表示させてみましょう。

表示させるための流れは以下となっています。

●プロジェクトのurls.pyにアクセスをしたら、各アプリのurls.pyにつなぎこみを行う。
●各アプリのurls.pyからviews.pyの中の指示を呼び出す。

絵にするとこんな感じ↓

画像46

まずは、プロジェクトの「blog_project/url.py」を以下のようにします。

from django.contrib import admin
from django.urls import path ,include #追加

urlpatterns = [
   path('admin/', admin.site.urls),
   path('index/', include('blog_app.urls') ),#追加
]
●from django.urls import path ,include #追加
●path('index/', include('blog_app.urls') ),#追加

を記述します。
※追加部分は、「#追加」とコメントを書いています。


次に、アプリのurls.pyを書くのですが、「blog_app/urls.py」は存在しないので、「blog_app」の中に「urls.py」を作成して、「blog_app/urls.py」を以下のようにします。

from django.contrib import admin
from django.urls import path 
from . import views #追加

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', views.index, name='index') #追加
]

次に、「blog_app/views.py」を変更します。

from django.shortcuts import render
from django.http import HttpResponse #追加

# Create your views here.
def hellofunction(request):
   return HttpResponse('Hello World')

ここでは、HttpResponseを使って文字列「Hello World」を表示を返すようにしています。
HttpResponseなどは適宜、Djangoのドキュメントを見て理解を深めればよいでしょう。


ここまでで、ブラウザ上の表示を確認してみましょう。

 python manage.py runserver

画像6

このような画面になりますが、URLを「index」にしてアクセスします。

http://127.0.0.1:8000/index/にアクセスします。

画像7

これで、ブラウザ上に「Hello World」が表示されましたね(^^)

HttpResponse('Hello World')の文字列を好きな言葉に変えて遊んでみてください。

もう一度、仕組みを絵に示しておきます。

画像46

アプリ内の「urls.py」と「views.py」はセットになって、どのURLに対してどの処理を行うかをしていることを意識しましょう。

●urls.py:どのURLに対して
●views.py:どの処理を行うか

1.5:テンプレートのhtmlファイルを用意

先ほどは、HttpResponse('Hello World')で文字列を書いて直接ブラウザ上に表示するということをしましたが、この時htmlで書かれたファイルを呼び出すのが普通です。

まずは、現在いるディレクトリで「templates」というディレクトリを作成します。

mkdir templates

templatesという名前でないといけないので、つづり間違いや「s」を忘れないようにしましょう。

さらに、そのtemplatesディレクトリの中にblog_appという名前のディレクトリを作成します。

mkdir ./templates/blog_app

そして、「templates/blog_app」の中に以下の2つのhtmlファイルを用意します。

●layout.html :枠組みのhtmlファイル
●index.html :トップページ用のhtmlファイル

※なぜ「layout.html」を用意するのかというと、ひとつのサイトで見た目(ヘッダーやフッターなど)は基本的に同じ見た目であることが多いですよね。
そうなったときに、ページごとに同じ記述が増えてしまうので、テンプレートとなるファイルを用意して、それを使いまわすというのをすれば楽ですよね。
そのための「layout.html」です。

「layout.html」「index.html」は後でコーディングするため、中身は書かずに次に行きましょう。

1.6:cssファイルを用意

今度は、ブラウザ上の表示の装飾を整えるためのstylesheetを用意します。
CSSファイルのことですね。

現在のディレクトリで「static」という名前でディレクトリを作成します。

mkdir static

これもつづり間違いに注意しましょう。

次に、「static/blog_app」を作成します。

mkdir ./static/blog_app

この中に、CSSファイルを用意すればよいということになります。

「static/blog_app」の中に以下の2つのstyle.cssファイルを用意します。
style.cssは何も書かずに次に行きましょう。

全体の構成を今一度チェックしておきます。
※この時点で手元で作成したプロジェクト内で違う構成になっている場合は、手順を見直しましょう。

画像8

1.7:ブラウザ上にhtmlファイルを表示する

先ほど作成した「index.html」を表示させるための下準備をします。

「blog_project/setting.py」にアプリ名「blog_app:を追加します。


# Application definition

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'blog_app',#追加
]

このようにすることで、先ほど作成したhtmlファイルとCSSファイルが呼び出せるということですね。

次に、「blog_app/templates/blog_app/layout.html」を以下のようにします。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>新しいブログ</title>
   <link rel="stylesheet" type="text/css" href="{% static 'blog_app/style.css' %}">
</head>
<body>
   {% block header %}
   {% endblock header %}
   
   {% block content %}
   {% endblock content %}
</body>
</html>

この「layout.html」がブラウザ上のベースとなるhtmlです。

{% block header %}
{% endblock header %}

{% block content %}
{% endblock content %}

で囲んでいる部分の内容を「index.html」に書けば「layout.html」をテンプレートにして使いまわすことができます。


「blog_app/templates/blog_app/index.html」を以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1>ようこそ</h1>
{% endblock header %}

{% block content %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

「blog_app/static/blog_app/style.css」を以下のようにします。

h1{
   color: red;
   opacity: 0.2;
}


画像9

以下のように「検証ツール」を使って、htmlのコードを見ながら、どのように記述がされているのかを確認してみると良いでしょう。

画像10


データベースの作成

「blog_app/models.py」を以下のようにします。

from django.db import models

# Create your models here.
class Blog(models.Model):
   title = models.CharField(max_length=100)
   date = models.DateTimeField()
   img = models.ImageField(upload_to='media/')
   content = models.TextField(max_length=100)


python3 manage.py makemigrations


Pillowがインストールされていないと、エラーが出るかもしれません。

その場合は、

pip install pillow

としてPillowをインストールをしてください。

エラーなく進めば以下のようにファイルが作成されます。

画像11

「migrations/0001_initial.py」

# Generated by Django 3.1 on 2020-10-17 13:45

from django.db import migrations, models


class Migration(migrations.Migration):

   initial = True

   dependencies = [
   ]

   operations = [
       migrations.CreateModel(
           name='Blog',
           fields=[
               ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
               ('title', models.CharField(max_length=100)),
               ('date', models.DateTimeField()),
               ('img', models.ImageField(upload_to='media/')),
               ('content', models.TextField(max_length=100)),
           ],
       ),
   ]


python3 manage.py migrate


画像12

例えば、以下のように「content」の変数名を「contents」に変えてみましょう。

python3 manage.py makemianage.py makemigrations


画像14

「migrations/0002_auto_20201017_1355.py」ができて修正箇所が記述されています。

# Generated by Django 3.1 on 2020-10-17 13:55

from django.db import migrations


class Migration(migrations.Migration):

   dependencies = [
       ('blog_app', '0001_initial'),
   ]

   operations = [
       migrations.RenameField(
           model_name='Blog',
           old_name='content',
           new_name='contents',
       ),
   ]


python3 manage.py migrate


管理画面からレコードを作成する。

管理者を追加する。

python3 manage.py createsuperuser

画像13

「blog_app/admin.py」の中を以下のようにします。

from django.contrib import admin
from .models import Blog #追加

# Register your models here.
admin.site.register(Blog) #追加



画像15


画像16


画像17

画像18


何個か作成してみましょう。


画像19


4つブログ記事を投稿しておきました。

本番環境ではないので、文章は適当にしておいて良いでしょう。

画像20

画像をレコードに入れたので、「media」の中に画像ファイルが生成されているのが確認できます。

画像21


ただ、↓これだと何のブログ記事かわからないですよね。

画像20

できれば、タイトルを表示させてほしい。

そういう場合は、class Bolgからタイトルを返すようにしてやれば良いです。

from django.db import models

# Create your models here.
class Blog(models.Model):
   title = models.CharField(max_length=100)
   date = models.DateTimeField()
   img = models.ImageField(upload_to='media/')
   contents = models.TextField(max_length=100)

   #追加
   def __str__(self):
       return self.title


すると以下のようになります。
タイトルが表示されてわかりやすいですよね。

画像23


「blog_app/templates/blog_app/index.html」を以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1>ようこそ</h1>
{% endblock header %}

{% block content %}
{% for item in blogs %}
<p>{{ item }}</p>    
<p>{{ item.id }}</p>    
<p>{{ item.title }}</p>    
<p>{{ item.date }}</p> 
<img src="{{ item.img.url }}" alt="">
<p>{{ item.contents }}</p>    
   
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}


画像24

しかし、画像が出ていないですね。

画像25


「blog_project/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('index/', include('blog_app.urls') ),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

「blog_project/settings.py」の最後に以下を追加します。


#追加
MEDIA_URL = '/pics/'
MEDIA_ROOT = BASE_DIR

一応全体も示しておきます。

"""
Django settings for blog_project project.

Generated by 'django-admin startproject' using Django 3.1.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""

from pathlib import Path
import os#追加

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'cxjezdbn%r8l=#i3wk3lsy!+*a4m1ab0lr9rp1llz78-ik=cwi'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'blog_app',#追加
]

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

ROOT_URLCONF = 'blog_project.urls'

TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [],
       'APP_DIRS': True,
       'OPTIONS': {
           'context_processors': [
               'django.template.context_processors.debug',
               'django.template.context_processors.request',
               'django.contrib.auth.context_processors.auth',
               'django.contrib.messages.context_processors.messages',
           ],
       },
   },
]

WSGI_APPLICATION = 'blog_project.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
   'default': {
       'ENGINE': 'django.db.backends.sqlite3',
       'NAME': BASE_DIR / 'db.sqlite3',
   }
}


# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
   {
       'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
   },
   {
       'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
   },
]


# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'

#追加
MEDIA_URL = '/pics/'
MEDIA_ROOT = BASE_DIR

画像26



bootstrapを使う

画像28

画像27


「blog_app/templates/blog_app/layout.html」を以下のようにします。

{% load static %}
<!doctype html>
<html lang="en">
 <head>
   <!-- Required meta tags -->
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

   <!-- Bootstrap CSS -->
   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">

   <title>新しいブログ</title>
   <link rel="stylesheet" type="text/css" href="{% static 'blog_app/style.css' %}">

 </head>
 <body>
   {% block header %}
   {% endblock header %}
   
   {% block content %}
   {% endblock content %}

   <!-- Optional JavaScript; choose one of the two! -->

   <!-- Option 1: jQuery and Bootstrap Bundle (includes Popper) -->
   <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>

   <!-- Option 2: jQuery, Popper.js, and Bootstrap JS
   <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
   -->
 </body>
</html>


画像29

これらをhmtlファイルにコピーしながら装飾をしていきます。

これらを使って色々と変更をしましょう。

bootstrapを色々使ってやったのが、こちらです。

「blog_app/templates/blog_app/layout.html」を以下のようにします。

{% load static %}
<!doctype html>
<html lang="en">
 <head>
   <!-- Required meta tags -->
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

   <!-- Bootstrap CSS -->
   <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">

   <title>新しいブログ</title>
   <link rel="stylesheet" type="text/css" href="{% static 'blog_app/style.css' %}">

 </head>
 <body>
     <div class="container">
         
     </div>
     <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
       <a class="navbar-brand text-white" href="#">宇宙に入ったカマキリ</a>
       <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
           <span class="navbar-toggler-icon"></span>
       </button>

       <div class="collapse navbar-collapse" id="navbarSupportedContent">
           <ul class="navbar-nav mr-auto">
           <li class="nav-item active">
               <a class="nav-link" href="#">物理サポート <span class="sr-only">(current)</span></a>
           </li>
           <li class="nav-item">
               <a class="nav-link" href="#">プログラミング</a>
           </li>
           <li class="nav-item dropdown">
               <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
               大学物理
               </a>
               <div class="dropdown-menu" aria-labelledby="navbarDropdown">
               <a class="dropdown-item" href="#">力学</a>
               <a class="dropdown-item" href="#">熱力学</a>
               <div class="dropdown-divider"></div>
               <a class="dropdown-item" href="#">物理数学</a>
               </div>
           </li>
           </ul>
           <form class="form-inline my-2 my-lg-0">
           <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
           <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
           </form>
       </div>
   </nav>
   <div class="container">
       {% block header %}
       {% endblock header %}
       
       {% block content %}
       {% endblock content %}    
   </div>
   <!-- Optional JavaScript; choose one of the two! -->

   <!-- Option 1: jQuery and Bootstrap Bundle (includes Popper) -->
   <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ho+j7jyWK8fNQe+A12Hb8AhRq26LrZ/JpcUGGOn+Y7RsweNrtN/tE3MoK7ZeZDyx" crossorigin="anonymous"></script>

   <!-- Option 2: jQuery, Popper.js, and Bootstrap JS
   <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
   <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
   -->
 </body>
</html>

「blog_app/templates/blog_app/index.html」を以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
{% for item in blogs %}    
<h2>{{ item.id }}:{{ item.title }} </h2>    
<p>{{ item.date }}</p>
<img src="{{ item.img.url }}" class="img-fluid" alt="img">
<p>{{ item.contents }}</p>    
<hr>
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

画像30

ちょっとブログらしくなってきたのではないでしょうか。

あとは、CRUDというデータベースの基本機能を使って、ブログの詳細記事にいって更新(編集)したり、ブログ記事を追加したり、ブログを削除したりする機能を追加すれば、ブログの最低限の機能ができますね。


データベースを利用するための、CRUD機能について実装を行います。

まずは、Create(新規作成)の機能を追加します。

from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Blog
from django.views.generic import CreateView
from django.urls import reverse_lazy

# Create your views here.
def index(request):
   #追加
   blogs = Blog.objects.all()
   params = {
       'blogs':blogs
   }
   return render(request, 'blog_app/index.html' ,params)#追加

#追加
class ContentCreate(CreateView):
   template_name = 'blog_app/create.html'
   model = Blog
   fields = ('title', 'date', 'img', 'contents')
   success_url = reverse_lazy('index')

CreateViewクラスの継承を行います。

rom django.views.generic import CreateView
from django.urls import reverse_lazy

「blog_app/templates/blog_app/create.html」を作成して、以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<form action=''  method='POST' enctype="multipart/form-data" >{% csrf_token %}
   <p>タイトル:<input type='text' name='title'></p>
   <p>日付:<input type='text' name='date'></p>
   <p>画像:<input type='file' name='img'></p>
   <p>記事:</p>
   <textarea name="contents" id="" cols="30" rows="10"></textarea>
   
   <p><input type='submit' value='作成する'></p>
</form>
{% endblock content %}

nameタグは、CreateViewクラスの
fields = ('title', 'date', 'img', 'contents')を書きます。

「blog_app/urls.py」を以下のようにします。

from django.contrib import admin
from django.urls import path 
from . import views

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', views.index, name='index'),
   path('create/', views.ContentCreate.as_view(), name='create')#追加
]

http://127.0.0.1:8000/index/create/


画像32

「作成をする」を押すと、新記事で記事内容が作成できています。

画像32

「http://127.0.0.1:8000/index/create/」としなくても「新規作成ボタン」からページを飛ぶようにしておきます。

「blog_app/templates/blog_app/index.html」を以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<!-- 追加 -->
<button type="button" class="btn btn-dark"><a class="text-white" href="{% url 'create' %}">新規作成</a></button>
<p></p>
{% for item in blogs %}    
<h2>{{ item.id }}:{{ item.title }} </h2>    
<p>{{ item.date }}</p>
<img src="{{ item.img.url }}" class="img-fluid" alt="img">
<p>{{ item.contents }}</p>    
<hr>
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}


aタグで「新規作成」ボタンを作り、「href="{% url 'create' %}」とすることで、createにページが飛ぶようにしています。

画像33

左上に「新規ボタン」ができていますね。

画像34

これで記事の新規作成ができます。


「blog_app/templates/blog_app/detail.html」を作成して、以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<!-- 追加 -->
<button type="button" class="btn btn-secondary"><a class="text-white" href="{% url 'index' %}">戻る</a></button>
<p></p>
<h2>{{ obj.id }}:{{ obj.title }} </h2>    
<p>{{ obj.date }}</p>
<img src="{{ obj.img.url }}" class="img-fluid" alt="img">
<p>{{ obj.contents }}</p>    
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

「blog_app/views.py」を以下のようにします。

from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Blog
from .forms import BlogForm 
from django.views.generic import CreateView
from django.urls import reverse_lazy

# Create your views here.
def index(request):
   blogs = Blog.objects.all()
   params = {
       'blogs':blogs
   }
   return render(request, 'blog_app/index.html' ,params)#追加

class ContentCreate(CreateView):
   template_name = 'blog_app/create.html'
   model = Blog
   fields = ('title', 'date', 'img', 'contents')
   success_url = reverse_lazy('index')

#追加
def detail(request, num):
   obj = Blog.objects.get(pk=num)
   return render(request, 'blog_app/detail.html', {'obj' :obj })

「blog_app/urls.py」を以下のようにします。

from django.contrib import admin
from django.urls import path 
from . import views

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', views.index, name='index'),
   path('create/', views.ContentCreate.as_view(), name='create'),
   path('detail/<int:num>', views.detail, name='detail'),#追加
]

http://127.0.0.1:8000/index/detail/1

画像35

左上の「戻る」で戻ることもできます。

「blog_app/templates/blog_app/index.html」

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<button type="button" class="btn btn-dark"><a class="text-white" href="{% url 'create' %}">新規作成</a></button>
<p></p>
{% for item in blogs %}
<!-- 追加 -->
<a href="{% url 'detail' item.id %}">
<h2>{{ item.id }}:{{ item.title }} </h2></a>   
<p>{{ item.date }}</p>
<img src="{{ item.img.url }}" class="img-fluid" alt="img">
<p>{{ item.contents }}</p>    
<hr>
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

画像36


ここまででできたものをの全体を見ておきましょう。

結構いい感じですね。


次に、詳細ページに行ったときに、削除できるようにしておきましょう。

「blog_app/views.py」を以下のようにします。

from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Blog
from .forms import BlogForm 
from django.views.generic import CreateView
from django.urls import reverse_lazy

# Create your views here.
def index(request):
   blogs = Blog.objects.all()
   params = {
       'blogs':blogs
   }
   return render(request, 'blog_app/index.html' ,params)#追加

class ContentCreate(CreateView):
   template_name = 'blog_app/create.html'
   model = Blog
   fields = ('title', 'date', 'img', 'contents')
   success_url = reverse_lazy('index')


def detail(request, num):
   obj = Blog.objects.get(pk=num)
   return render(request, 'blog_app/detail.html', {'obj' :obj })

#追加
def delete(request, num):
   obj = Blog.objects.get(pk=num)
   if (request.method == 'POST'):
       obj.delete()
       return redirect(to='/index')
   
   return render(request, 'blog_app/detail.html', {'obj' :obj })

削除するdelete()関数を追加しました。

内容は単純で、「GET」でリクエストが贈られたときは、「blog_app/detail.html」に飛んで詳細ページに行き、
「POST」でリクエストが来たときは「 if (request.method == 'POST'):」の中の処置が行われます。

obj.delete()で「obj = Blog.objects.get(pk=num)」で代入したオブジェクトを削除しています。
その後に、「'index'」(index()関数)に返すようにしています。


「blog_app/urls.py」を以下のようにします。

from django.contrib import admin
from django.urls import path 
from . import views

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', views.index, name='index'),
   path('create/', views.ContentCreate.as_view(), name='create'),
   path('detail/<int:num>', views.detail, name='detail'),
   path('delete/<int:num>', views.delete, name='delete'),#追加
]

「blog_app/templates/blog_app/detail.html」を作成して、以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<button type="button" class="btn btn-secondary"><a class="text-white" href="{% url 'index' %}">戻る</a></button>
<p></p>
<h2>{{ obj.id }}:{{ obj.title }} </h2>

<!-- 追加 -->
<form action="{% url 'delete' obj.id  %}" method="POST">{% csrf_token %}
   <input type="submit" class="btn btn-danger text-white" value="記事を削除"></input>
</form>

<p>{{ obj.date }}</p>
<img src="{{ obj.img.url }}" class="img-fluid" alt="img">
<p>{{ obj.contents }}</p>    
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

「記事を削除」が押されると、POSTとして「{% url 'delete' obj.id %}」に返しています。

http://127.0.0.1:8000/index/


画像37


いかの「記事を削除」を押すと「http://127.0.0.1:8000/index/」に戻ってきます。
「削除した記事」は無くなっているのが確認できると思います。

画像38


次に編集についてです。

「blog_app/views.py」を以下のようにします。

更新には「UpdateView」を使います。

from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import redirect
from .models import Blog
from .forms import BlogForm 
from django.views.generic import CreateView, UpdateView
from django.urls import reverse_lazy

# Create your views here.
def index(request):
   blogs = Blog.objects.all()
   params = {
       'blogs':blogs
   }
   return render(request, 'blog_app/index.html' ,params)#追加

class ContentCreate(CreateView):
   template_name = 'blog_app/create.html'
   model = Blog
   fields = ('title', 'date', 'img', 'contents')
   success_url = reverse_lazy('index')


def detail(request, num):
   obj = Blog.objects.get(pk=num)
   return render(request, 'blog_app/detail.html', {'obj' :obj })


def delete(request, num):
   obj = Blog.objects.get(pk=num)
   if (request.method == 'POST'):
       obj.delete()
       return redirect(to='/index')
   
   return render(request, 'blog_app/detail.html', {'obj' :obj })

#追加
class ContentUpdate(UpdateView):
   template_name = 'blog_app/update.html'
   model = Blog
   fields = ('title', 'date', 'img', 'contents')
   success_url = reverse_lazy('index')


「blog_app/urls.py」を以下のようにします。

from django.contrib import admin
from django.urls import path 
from . import views

urlpatterns = [
   path('admin/', admin.site.urls),
   path('', views.index, name='index'),
   path('create/', views.ContentCreate.as_view(), name='create'),
   path('detail/<int:num>', views.detail, name='detail'),
   path('delete/<int:num>', views.delete, name='delete'),
   path('update/<int:pk>', views.ContentUpdate.as_view(), name='update'),#追加
]


{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<button type="button" class="btn btn-dark"><a class="text-white" href="{% url 'create' %}">新規作成</a></button>
<p></p>
{% for item in blogs %}
<a href="{% url 'detail' item.id %}">
<h2>{{ item.id }}:{{ item.title }} </h2></a>
<!-- 追加 -->
<button type="button" class="btn btn-success"><a class="text-white" href="{% url 'update' item.id %}">編集</a></button>
<p>{{ item.date }}</p>
<img src="{{ item.img.url }}" class="img-fluid" alt="img">
<p>{{ item.contents }}</p>    
<hr>
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

画像40

「blog_app/templates/blog_app/update.html」を作成して、以下のようにします。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">編集中</h1>
{% endblock header %}

{% block content %}

<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %} 
   {{form.as_p}}
   <input type="submit" value="更新する">
</form>

{% endblock content %}


画像39

画像41


ほぼ完成しました。

あとは、画像がちょっと大きすぎるので少し小さくします。

「blog_app/templates/blog_app/index.html」のimgタグに
「index-img」クラスを追加しておきました。

{% extends 'blog_app/layout.html' %}

{% block header %}
<h1 class="text-center">ようこそ</h1>
{% endblock header %}

{% block content %}
<button type="button" class="btn btn-dark"><a class="text-white" href="{% url 'create' %}">新規作成</a></button>
<p></p>
{% for item in blogs %}
<a href="{% url 'detail' item.id %}">
<h2>{{ item.id }}:{{ item.title }} </h2></a>
<!-- 追加 -->
<button type="button" class="btn btn-success"><a class="text-white" href="{% url 'update' item.id %}">編集</a></button>
<p>{{ item.date }}</p>
<img src="{{ item.img.url }}" class="img-fluid index-img" alt="img">
<p>{{ item.contents }}</p>    
<hr>
{% endfor %}
<p>こちらにメッセージを書きます。</p>
{% endblock content %}

「static/blog_app/style.css」を以下のようにします。

h1{
   color: red;
   opacity: 0.2;
}

.index-img{
   max-height:200px
}

画像の高さを200pxにします。

そして更新をすると画像サイズが変わっているかと思います。

もし、cssが効いていないと感じたら、マウスの右クリック➡「検証」を押して、左上の「キャッシュの削除とハードの再読み込み」を押してください。

画像42

画像43

これで画像サイズがいい感じになりました。

全体の見た目はこちらです。

画像44

動きはこんな感じです。


この記事が気に入ったらサポートをしてみませんか?