<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Webサイト &#8211; 開発記録</title>
	<atom:link href="https://www.kthksgy.com/category/web/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.kthksgy.com</link>
	<description>開発メモです。現在レイアウトが一部崩れている箇所があります。</description>
	<lastBuildDate>Wed, 11 Nov 2020 04:35:19 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.4.3</generator>
	<item>
		<title>HTTP/www無しのURLを自動的にHTTPS/www有りのURLに転送する【.htaccess/WordPress】</title>
		<link>https://www.kthksgy.com/web/redirect-https-www/</link>
					<comments>https://www.kthksgy.com/web/redirect-https-www/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sun, 26 Apr 2020 00:39:35 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<category><![CDATA[Wordpress]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=8</guid>

					<description><![CDATA[様々なURLでアクセスされた場合に、それを1つのURLにまとめるURL正規化の話です。 昨今のインターネットでは、SSLに対応していないWebページにアクセスするとブラウザから警告が出るようになっています。なので、Web&#8230;]]></description>
										<content:encoded><![CDATA[<p>様々なURLでアクセスされた場合に、それを1つのURLにまとめるURL正規化の話です。<br />
<span id="more-8"></span></p>
<p>昨今のインターネットでは、SSLに対応していないWebページにアクセスするとブラウザから警告が出るようになっています。なので、Webページを公開する時は必ずSSLに対応するというのは最早常識の域になりつつあります。</p>
<p>しかし、SSLに対応しただけでユーザーがSSLと非SSLのどちらを選ぶかはまた別の話です。URLの先頭が<code>https://</code>か<code>http://</code>かでSSLと非SSLが振り分けられるようになっていますが、人によっては<code>s</code>を忘れてしまったりする事もあるかもしれませんし、その場合は普通にセキュリティの警告が出ます。</p>
<p>警告が出ないようにそもそも<code>http://</code>で公開しないという手もありますが、それだと上のようなケースではWebサイトが存在しない物として扱われてしまいます。</p>
<p>なので、<code>http://</code>を公開しながらアクセスの全てを<code>https://</code>にリダイレクトするのが良いと思います。</p>
<p>ついでに、SEO対策として<code>www</code>の有無でページの評価が分散しないように、<code>www</code>の有無も統一します。</p>
<p>Webページを公開した最初しかやらなくて毎度忘れているので、自分用のメモとして残しておきます。</p>
<h2><code>.htaccess</code>ファイルを作る</h2>
<p>Webサイトのドキュメントルートに<code>.htaccess</code>を作成します。Wordpress等だと既に作ってあります。</p>
<p>これの先頭に、以下の内容を記述します。</p>
<pre><code class="language-htaccess line-numbers">&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule .* https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
&lt;/IfModule&gt;
</code></pre>
<p>これでOKです。<code>http://</code>且つ<code>www</code>無しのURLへのアクセスが、<code>https://</code>且つ<code>www</code>有りのURLに転送されます。</p>
<h2>解説</h2>
<p><code>.htaccess</code>に記述した内容を解説します。</p>
<p>URLの書き換えを行うモジュールを利用できる場合に、URL書き換えを有効化して<code>&lt;IfModule mod_rewrite.c&gt;</code>から<code>&lt;/IfModule&gt;</code>までの間の処理を実行します。</p>
<pre><code class="line-numbers">&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
...
&lt;/IfModule&gt;
</code></pre>
<h3><code>http://</code>のURLを<code>https://</code>のURLにする</h3>
<p>上行が書き換えの条件、下行が書き換えの内容です。</p>
<pre><code class="line-numbers">RewriteCond %{HTTPS} off
RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</code></pre>
<p>SSLのアクセスではない場合に、URLの先頭を<code>https://</code>に書き換えます。</p>
<p><code>%{HTTP_HOST}</code>と<code>%{REQUEST_URI}</code>はアクセスのあったURLに応じて以下のような文字列に置き換えられます。</p>
<pre><code class="line-numbers">URL = http://www.example.com/index.html?p=123
%{HTTP_HOST} = www.example.com
%{REQUEST_URI} = /index.html?p=123

https://%{HTTP_HOST}%{REQUEST_URI} -&gt; https://www.example.com/index.html?p=123
</code></pre>
<p>末尾の<code>[L,R=301]</code>に関しては詳細を省きますが、リダイレクトが恒久的なリダイレクトであることを表します。</p>
<p><code>R=300</code>とするとそのリダイレクトは一時的なものであると認識されます。</p>
<h3><code>www</code>を付ける</h3>
<p><code>www</code>無しのURLで運用している場合(<code>https://kthksgy.com</code>)や、サブドメインで運用している場合(<code>https://subdomain.kthksgy.com</code>)は以下の2行を省いてください。</p>
<p>上行は『ホスト名の先頭が<code>www.</code>ではない場合』の条件を表します。</p>
<p>下行は<code>https://</code>の時と同じく書き換えを行います。</p>
<pre><code class="language-htaccess line-numbers">RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule .* https://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</code></pre>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/redirect-https-www/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>MariaDBとWordPressを接続する</title>
		<link>https://www.kthksgy.com/web/mariadb-setup/</link>
					<comments>https://www.kthksgy.com/web/mariadb-setup/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Mon, 10 Feb 2020 09:54:07 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=24</guid>

					<description><![CDATA[MariaDBでユーザー作成とデータベース作成を行って、WordPressと接続します。MySQLもほぼ同じように設定可能なので適宜読み替えてください。 環境 今回は、以下の環境で行いました。 Ubuntu 18.04 &#8230;]]></description>
										<content:encoded><![CDATA[<p>MariaDBでユーザー作成とデータベース作成を行って、WordPressと接続します。MySQLもほぼ同じように設定可能なので適宜読み替えてください。</p>
<h2>環境</h2>
<p>今回は、以下の環境で行いました。</p>
<ul>
<li>Ubuntu 18.04 LTS</li>
<li>MariaDB 10.4.12</li>
</ul>
<h2>MariaDBモニターを開く</h2>
<p>MariaDBモニターは、PythonやRubyのインタラクティブシェルのように、対話形式でSQLを実行出来るモードです。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo mariadb
</code></pre>
<h2>ユーザーの作成</h2>
<p>MariaDBモニターで、<code>create user 'ユーザー名'@'localhost' identified by 'パスワード';</code>を行います。</p>
<pre><code class="language-clean line-numbers">MariaDB [(none)]&gt; create user 'myuser'@'localhost' identified by 'mypassword';
Query OK, 0 rows affected (0.004 sec)
</code></pre>
<p>これで、ユーザー<code>myuser</code>が作成され、<code>mypassword</code>というパスワードで接続出来るようになりました。</p>
<h2>データベースの作成</h2>
<p>データベースの作成と言うと少々混乱するかも知れませんが、作成するのはMariaDBやMySQLではありません。MariaDB上に、WordPress専用の領域を設けるというニュアンスが近いです。一般的にリレーショナルデータベースと言うと、次のような構造になっていて、今から作成するのは<code>データベース1</code>や<code>データベース2</code>の部分です。</p>
<pre><code class="language-clean line-numbers">データベースソフトウェア(MariaDB, MySQL, PostgreSQL, ...)
|-- データベース1 (WordPress用)
   |-- テーブル1 (wp_posts)
      |-- レコード1 (初めましての記事)
   |-- テーブル2 (wp_comments)
   |-- ...
|-- データベース2 (個人的なデータ保存用)
|-- ...
</code></pre>
<p>MariaDBモニターで、<code>create database DB名;</code>を行います。</p>
<pre><code class="language-clean line-numbers">MariaDB [(none)]&gt; create database mydatabase;
Query OK, 1 row affected (0.001 sec)
</code></pre>
<p>これで、MariaDB上にWordPressの記事やコメントを保存するための領域が確保されました。</p>
<h2>ユーザーにデータベースの権限を付与</h2>
<p>先ほど作成したユーザーに、先ほど作成したデータベースへの権限を付与します。MariaDBモニターで、<code>grant ALL on データベース名.* to 'ユーザー名'@'localhost';</code>を行います。</p>
<pre><code class="language-clean line-numbers">MariaDB [(none)]&gt; grant ALL on mydatabase.* to 'myuser'@'localhost';
Query OK, 0 rows affected (0.001 sec)
</code></pre>
<h2>MariaDBモニターを閉じる</h2>
<p><code>exit</code>で抜けられます。</p>
<pre><code class="language-clean line-numbers">MariaDB [(none)]&gt; exit
Bye
</code></pre>
<h2>WordPressとデータベースを接続する</h2>
<p>後は、WordPressが公開されているURLにアクセスして、先ほど作ったユーザーでデータベースに接続します。</p>
<h2>まとめ</h2>
<p>今回はWordPressもMariaDBも同じサーバー上にあるので、MariaDB上でのユーザー作成から権限設定までの一連の流れで簡単に出来ました。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/mariadb-setup/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OpenLiteSpeedの初期設定</title>
		<link>https://www.kthksgy.com/web/openlitespeed-initialization/</link>
					<comments>https://www.kthksgy.com/web/openlitespeed-initialization/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sun, 09 Feb 2020 09:53:09 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=23</guid>

					<description><![CDATA[LiteSpeedの初期設定を行います。 LiteSpeed Web Serverとは 第4のウェブサーバーと呼ばれる、nginxよりも更に効率的なウェブサーバーです。 高負荷時にnginxよりも高いパフォーマンスを発揮&#8230;]]></description>
										<content:encoded><![CDATA[<p>LiteSpeedの初期設定を行います。</p>
<h2>LiteSpeed Web Serverとは</h2>
<p>第4のウェブサーバーと呼ばれる、nginxよりも更に効率的なウェブサーバーです。</p>
<p>高負荷時にnginxよりも高いパフォーマンスを発揮出来るようで、一部の共用サーバーではApacheやnginxの代わりにLiteSpeedを導入して「高速」を謳っている所も在ります。</p>
<h2>環境</h2>
<p>今回は、OpenLiteSpeedとLSPHPを使用しています。</p>
<ul>
<li>OpenLiteSpeed 1.6.7</li>
<li>LSPHP 7.3</li>
</ul>
<h2>管理者ユーザーの作成</h2>
<p>まずは管理者ユーザーを作成します。このユーザーを使って、管理コンソールに入れるようになります。LiteSpeedには管理者ユーザー作成用のシェルスクリプトが用意されているので、実行して従うだけです。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo /usr/local/lsws/admin/misc/admpass.sh
[sudo] password for ...:

Please specify the user name of administrator.
This is the user name required to login the administration Web interface.

User name [admin]: ...

Please specify the administrator's password.
This is the password required to login the administration Web interface.

Password:
Retype password:
Administrator's username/password is updated successfully!
</code></pre>
<h2>起動状態の確認</h2>
<p>ユーザー作成が終わったら、ウェブサーバーの起動状態を確認します。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo /usr/local/lsws/bin/lswsctrl status
litespeed is running with PID 10388.
</code></pre>
<p>このように、実行中のPIDが表示されたら既に起動されています。そうでない場合は以下のコマンドを実行して起動します。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo /usr/local/lsws/bin/lswsctrl start
</code></pre>
<h2>ページにアクセスしてみる</h2>
<p>ここからは私の独自ドメインでURLを表記するので、適宜自分の環境で読み替えてください。例えば、ローカル環境なら<code>http://lsws.devmem.info</code>は<code>http://localhost</code>みたいに。</p>
<p>デフォルトでは、<code>http://lsws.devmem.info:8088</code>にページが公開されています。</p>
<p>事前にファイアウォールの設定をしてポートを開放しているので、早速アクセスしてみます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/VMEf6Y45pgkX49pJQo3uu5eBkrIDFXpjmBltX7LFRZoEcBOzZNy6Y30WWwRyJmxZgmbWcj2OLAv4XP33pgDoexXq_JLZa4j7fyfGgHe9kT6IR0ddWCU2boWgrOil7JFXONOEib0Fhg=s0" alt="" /></p>
<p>アクセス出来ました。次は、管理コンソールにアクセスしてみます。</p>
<p>管理コンソールは、<code>https://lsws.devmem.info:7080</code>に在ります。デフォルトでHTTPSで接続するようになっていますが、証明書が無いので危険な状態と表示されます。Chromeはセキュリティが厳しいのでアクセスすら出来ませんでした。なので、Edgeを使って接続します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/2MKfkb99ojN6Aeg3K-nCe-lXJv_mInYypgvx8e7wfkcwFIdGZPSPnteucC7WqaDeF5yHOYx9IHoaDsHpkAklaFiAdG7xyDwqh4lg1JKLkFi--sLGzg-irNBnpzHfTPp170qZARbIfg=s0" alt="" /></p>
<p>管理画面へのログインページが表示されました。ここに、先ほど作ったユーザーでログインします。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/xzUHJqM44U_gzdKRSnagAyhjBuJ6LeJkId3YvZYn9szuqetcRDc5DeJl8PMrIaJDjUrR5bur5uDFKR9XXGl3ca3ak8n3hxKhpI2De1VDUGqIOQytxQtlJNgNUYCjMgxem1i-Z0RywQ=s0" alt="" /></p>
<p>まずログインすると、ダッシュボードが表示されます。ここにアクセス統計情報等が表示されるようです。</p>
<h2>WordPressの構築</h2>
<p>デフォルトページも管理コンソールもログイン出来たのでここで終わりにしても良いのですが、せっかくLSPHPも導入しているのでWordPressを構築します。</p>
<h3>Virtual Hostの設定</h3>
<p>管理コンソール左のメニューから、「Virtual Hosts」を開いてください。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/6zcZC94BgkwEuURCglN2G-I3yjFtxhJKSCG425cSig_X1SNv_M6xw56ftPWZgHl1L8KWpNSU5q0JJnT9xPm3SOMZSRtwqcCKtHZha0OI6oyy1Kh1RRNXMfpyW6mvTlHfuHfApuOicA=s0" alt="" /></p>
<p>デフォルトでは、先ほど見たデフォルトページにアクセスするためのVirtual Hostが定義されています。</p>
<p>ここに別のVirtual Hostを追加する事で、複数サイトを1つのウェブサーバーで運営する事が可能になります。</p>
<p>リストの見出しの右にある追加ボタン(プラスマーク)から、新規にVirtual Hostを追加します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/r4wSh2MOAuLY01D84nkVQxBj2nY26229XFMUN9Eyhsynu5aA7QkUrBqRqGN4f6Ehne9hJ1DxVpEnCo-m0-_CYu0_1YCuZpI6scVdywmtYWHcz4-uSVJ84XOX9UVCxlnV1FPeyvV24w=s0" alt="" /></p>
<p>今はWordPressを構築しているので、<code>Virtual Host Name</code>はシンプルに「WordPress」で、<code>Virtual Host Root</code>は適当に<code>/usr/local/lsws/vhosts/WordPress</code>になるように設定しました。</p>
<p>注意点としては、<code>Virtual Host Root</code>は外部公開用のドキュメントルートではなくVirtual Hostのルートです。ここに外部公開用のドキュメントルートとして<code>html</code>ディレクトリやログ保存用として<code>log</code>ディレクトリを作成します。</p>
<p><code>Config File</code>に関しては、<code>$SERVER_ROOT/conf/vhosts/$VH_NAME/vhconf.conf</code>に設定するのが推奨されています。</p>
<p><code>External App Set UID Mode</code>は<code>Server UID</code>にします。これは、<code>wp-config.php</code>等を変更する時に権限の問題が起きないようにしてくれます。</p>
<p>見出し右の保存ボタンを押しましょう。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/04Qjx7Ztr3yxW5Qkas8fo_B1FnGtSbD2peATjSvd8HquKJd7SNP-t02SbXsZs5x63h58L5n8shqUvaXBMvaPPTJ-VxfBrp2BMpBGIrMGXgIElirjUzDvElFSr1Ic4caqeWDjdUPiOA=s0" alt="" /></p>
<p>保存ボタンを押すと、設定ファイルが存在しない旨のエラーが表示されるので、エラー文の最後の<code>CLICK TO CREATE</code>を押して作成してもらいます。</p>
<p>再度保存ボタンを押すと、Virtual Hostsの一覧に戻って、<code>WordPress</code>というVirtual Hostが追加されている事が確認出来ます。</p>
<p>次に、Virtual Hostのドキュメントルートの設定を行います。</p>
<p>追加した<code>WordPress</code>の設定を開いて、<code>General</code>タブに進みます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/ZQH8QwRKAXpoBPNT2E1r41_-iA7PAymGYtAv4ztHjwvJZpZEMGHHNxyjmFqp0w9aecRmT2iAE8XhydMGNJDK6ukxcHfYFzDZ9h0SYDSdM2KY1SigXWhM1T5yKrOZ3Qw4SPn4_U38Ww=s0" alt="" /></p>
<p>見出し右の編集ボタンをクリックしてください。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/gJ0jiOGNMcWo0-61Kuv5QyWwJ54XJs-YbelV5NaBbej4Odqu21VaavhmNNpyJbfa8IR26x7BG5Buq8uWCCDUbhTwCLP4pqIQGtWOUQTpUsQ2eFH0QAqUWUcV38IyVu7I9vi0WaeUtg=s0" alt="" /></p>
<p><code>Document Root</code>には<code>$VH_ROOT/html</code>が推奨されているようです。編集し終えたら見出し右の保存ボタンから保存してください。</p>
<p>次に、<code>Rewrite</code>タブを開いて、<code>Auto Load from .htaccess</code>を<code>Yes</code>に変更します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/RTuUZ6dRzGKfE8kd2lWjOSdyBixrnGCIriXOUzRj4Aew0wM0OTZ8YINxhmu3DquPnlgH-ImNKRpqKMKWhfJH4oItbpuRg8nXuUojz17lZm_ac3YnM5YRmkOvP-wDPJtaKRxZOaVJLQ=s0" alt="" /></p>
<p>WordPressはページのURLを.htaccessで制御しているので、この設定を行わないと<strong>トップページと管理ページ以外のページが404エラーになる</strong>状態になります。</p>
<h3>Listenerの設定</h3>
<p>リスナーは、アクセスされたURLに応じて、表示するページを分ける事が出来ます。</p>
<p>管理コンソール左のメニューから、<code>Listeners</code>を開いてください。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/GPwyUvf_jFJpbV_DCdLZgdwr1aV6cZTM0yqgFISHg6dIQWKdzhq0eJsdcrYPJAKarrvVR85nhXIEWzSbzddGUOoWnBXCPjamo3541zIWpOUXkui500k7hIivn14yEwXZff0zYUqyPA=s0" alt="" /></p>
<p>デフォルトでは<code>Default</code>という名のListenerが1つだけ定義されているはずです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/1-6WYiLqS52fFOfTMiKxYLIbqAfcIXB_WZKOrllnLZduSjQicnDk53Jp0FCnn2peZ1G24llS53lf97qJ7vamRG_G_vldWcpCyAhW7I0JYyYiFNu0eL1UQbaWKKVCeccBuhxnsOWZ4w=s0" alt="" /></p>
<p>画面上部ではListenerに関する設定が、画面下部ではこのListenerに紐づくVirtual Hostと対応するドメインが定義されています。</p>
<p><code>Default</code>ではポート8088番のアクセスをドメインに関係無く<code>Example</code>に振り分けるように定義されています。</p>
<p>ではまず、ポート8088番ではなく、ポート80番で接続するようにします。見出し右の編集ボタンを押して編集します。例の如く、ポート開放等は既に行っています。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/Fc5u0QGwh5JhcKq4aE1ReVcKW8dx0WMMQMlOs5vhw-tkCKvsMukiQLUFqZTohJgAVEVn26Tw_xUxW6QfCx03RUv7i_IVlZp7Zn2_LidU3lfofsuFE1aG7tRVcAs3HGQVe7F1ZqvEfw=s0" alt="" /></p>
<p>編集が終わったら、次は画面下部にあった<code>Virtual Host Mappings</code>の設定です。<code>Example</code>は不要なので、<code>Example</code>の設定を編集します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/JIiB1u5MuDKucRv72-FiEQoV5LN_jV2E8Sz9X-wic0X-XPLmHZOJc3oploqw_26wWn0EXpbWeAZbs9GlNxq7xBmB03SE8Dli80hR2LNxCjXG1FksKAgHNWdDQ2QBXUYpxxXhCbd_Zw=s0" alt="" /></p>
<p><code>http://lsws.devmem.info</code>でのアクセスで構築中のWordPressを表示したいので、このように設定します。ワイルドカードにも対応していて、<code>*.devmem.info</code>のような事も出来ます。</p>
<p>編集が終わったら保存してください。保存が終わったら、管理コンソール右上にある<code>LSWS PID</code>の表示の隣に在る、<code>Graceful Restart</code>ボタンからLiteSpeedを再起動します。</p>
<h3>WordPressのダウンロードと配置</h3>
<p>先ほど設定した<code>Virtual Host Root</code>ディレクトリを作成します。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo mkdir /usr/local/lsws/vhosts/WordPress
</code></pre>
<p>作成したら、WordPressをダウンロードして、設定した<code>Document Root</code>ディレクトリに合うようにリネームします。また、WordPressからファイルの書き換えが生じた場合に<code>Document Root</code>ディレクトリの所有者とファイルの所有者が異なると追加設定が必要になるので修正します。デフォルトではユーザー<code>nobody</code>、グループ<code>nogroup</code>として書き換えが実行されるようなので、<code>chown -R</code>で再帰的に変更します。</p>
<pre><code class="language-bash line-numbers"># (Bash)
# Virtual Host Rootへ移動
<span class="katex math inline">cd /usr/local/lsws/vhosts/WordPress
# WordPressのダウンロード</span> sudo curl -o - https://ja.wordpress.org/latest-ja.tar.gz | sudo tar xzvf -
# リネーム
<span class="katex math inline">sudo mv wordpress html
# 所有権の設定</span> sudo chown -R nobody:nogroup WordPress/
</code></pre>
<p>これで、WordPressの配置が完了しました。</p>
<h3>確認する</h3>
<p>では、<code>http://lsws.devmem.info</code>にアクセスしてみます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/I8W1Dor81ZcbrE0fu_I1cBYpjzrsiQiaZcis61J5eSgl5LrKVnwXzXXqrwq8Aef_mYq2o7A15j-E0uzdDEOav-BOM72PkHeQTsMxuZdM0Yz5YJp9ncf3ZGjUB1zINK-QSuGHvJIUeA=s0" alt="" /></p>
<p>無事に、お馴染みのWordPressの初期設定画面が表示されました。</p>
<h2>まとめ</h2>
<p>第4のウェブサーバーと呼ばれるLiteSpeed Web ServerでWordPressを構築しました。</p>
<p>今まで、ウェブサーバーは共用サーバーに備えついているApacheやnginxを使っていたので、ウェブサーバー自体の構築は初めてしましたが、管理コンソールのおかげで設定が見易くて助かりました。</p>
<p>nginxと比較してどうかは分かりませんが、アクセスはサクサクなので満足しています。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://openlitespeed.org/kb/setting-up-name-based-virtual-hosting-on-openlitespeed/">Setting up Name-based Virtual Hosting on OpenLiteSpeed &#8211; OpenLiteSpeed</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://www.digitalocean.com/community/tutorials/how-to-install-the-openlitespeed-web-server-on-ubuntu-18-04">How To Install the OpenLiteSpeed Web Server on Ubuntu 18.04 | DigitalOcean</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/cherubim1111/items/265cfbbe91adb44562d5">Ubuntu 18.04 LTS に WordPress 5.3 をインストール</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/openlitespeed-initialization/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>LAMP(Ubuntu18.04+OpenLiteSpeed+MariaDB+LSPHP)環境を作る</title>
		<link>https://www.kthksgy.com/web/make-lamp-with-litespeed/</link>
					<comments>https://www.kthksgy.com/web/make-lamp-with-litespeed/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sat, 08 Feb 2020 09:51:58 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=22</guid>

					<description><![CDATA[最早頭文字が全然LAMPではないですが、UbuntuにLAMP環境を作ります。 LAMP環境とは OS、Webサーバー、データベース、プログラミング言語からなる環境の事です。以下の構成が最もメジャーな環境だったため、その&#8230;]]></description>
										<content:encoded><![CDATA[<p>最早頭文字が全然LAMPではないですが、UbuntuにLAMP環境を作ります。</p>
<h2>LAMP環境とは</h2>
<p>OS、Webサーバー、データベース、プログラミング言語からなる環境の事です。以下の構成が最もメジャーな環境だったため、その頭文字を取ってLAMP環境と呼ばれるようになりました。</p>
<ul>
<li>Linux</li>
<li>Apache</li>
<li>MySQL</li>
<li>PHP</li>
</ul>
<p>一般に、これら以外の構成からなる環境も、LAMP環境と呼ばれるようです。</p>
<h2>環境</h2>
<p>VPS上で構築します。</p>
<ul>
<li>Ubuntu 18.04 LTS</li>
</ul>
<h2>L(Linux)</h2>
<p>VPSのインスタンスを立ち上げた時に既にインストールされているので、省略します。</p>
<h2>A(OpenLiteSpeed)</h2>
<p>今回は、ApacheではなくOpenLiteSpeedを用います。以下のコマンドで、OpenLiteSpeedを<code>apt</code>でインストール出来るようにします。</p>
<pre><code class="language-bash line-numbers"># (Bash)
<span class="katex math inline">curl -o - https://rpms.litespeedtech.com/debian/lst_repo.gpg | sudo apt-key add -</span> sudo add-apt-repository 'deb http://rpms.litespeedtech.com/debian/ bionic main'
</code></pre>
<p>その後、以下のコマンドでインストールします。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo apt install openlitespeed
</code></pre>
<h2>M(MariaDB)</h2>
<p>今回は、MySQLではなくMariaDBを用います。以下のコマンドで、最新のMariaDBを<code>apt</code>でインストール出来るようにします。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | sudo bash
</code></pre>
<p>このコマンドを実行すると、<code>/etc/apt/sources.list.d/mariadb.list</code>が生成されます。最新版以外のMariaDBを利用したい場合は、<code>mariadb-10.4</code>等、この中のバージョンを書き換えます。</p>
<pre><code class="language-clean line-numbers"># MariaDB Server
# To use a different major version of the server, or to pin to a specific minor version, change URI below.
deb http://downloads.mariadb.com/MariaDB/mariadb-10.4/repo/ubuntu bionic main

# MariaDB MaxScale
# To use the latest stable release of MaxScale, use "latest" as the version
# To use the latest beta (or stable if no current beta) release of MaxScale, use "beta" as the version
deb http://downloads.mariadb.com/MaxScale/2.4/ubuntu bionic main

# MariaDB Tools
deb http://downloads.mariadb.com/Tools/ubuntu bionic main
</code></pre>
<p>次に、インストール可能なMariaDBのパッケージを検索します。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo apt list | grep -i mariadb-server
mariadb-server/unknown 1:10.4.12+maria~bionic all
mariadb-server-10.1/bionic-updates,bionic-security 1:10.1.43-0ubuntu0.18.04.1 amd64
mariadb-server-10.4/unknown 1:10.4.12+maria~bionic amd64
mariadb-server-core-10.1/bionic-updates,bionic-security 1:10.1.43-0ubuntu0.18.04.1 amd64
mariadb-server-core-10.4/unknown 1:10.4.12+maria~bionic amd64
</code></pre>
<p><code>mariadb-server-10.4</code>と<code>mariadb-server-10.1</code>が利用出来るようです。今回はMariaDB 10.4をインストールします。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo apt install mariadb-server-10.4
</code></pre>
<p>インストールが終わったら、一度実行して確認します。</p>
<pre><code class="language-bash line-numbers">$ sudo mariadb
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is ..
Server version: 10.4.12-MariaDB-1:10.4.12+maria~bionic-log mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]&gt; exit
Bye
</code></pre>
<p>これでMariaDBのインストールは完了です。</p>
<h2>P(LSPHP)</h2>
<p>今回は、通常のPHPではなくLiteSpeed用のPHPを用います。</p>
<p>デフォルトでOpenLiteSpeedは同時インストールされるLSPHP 7.3を使用するようになっています。</p>
<p>ただ、FastCGIから利用する場合はLSPHP 5になるようなので、此方もLSPHP 7.3を使うように変更します。</p>
<p>OpenLiteSpeedはFastCGIモードでの実行時に<code>/usr/local/lsws/fcgi-bin/lsphp</code>を呼び出していて、デフォルトでは同じディレクトリにある<code>lsphp5</code>にリンクが貼ってあるようです。なので、このファイルに対してLSPHP 7.3へのソフトリンクを作ります。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo ln -sf /usr/local/lsws/lsphp73/bin/lsphp /usr/local/lsws/fcgi-bin/lsphp
</code></pre>
<h3>LSPHPのバージョンを変更する場合</h3>
<p>通常この操作は必要無いですが、デフォルトでインストールされるバージョン以外のバージョンを使いたい場合は以下の通りにしてください。</p>
<p>インストールに必要なリポジトリの登録はOpenLiteSpeedのインストール時に済んでいるので、<code>apt</code>でインストール出来ます。</p>
<p>まず、インストール可能なLSPHPのパッケージを検索します。出力が長いのでここには貼りません。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo apt list | grep -i lsphp
...
</code></pre>
<p><code>lsphp70</code>～<code>lsphp74</code>がインストール出来るようです。今回はLSPHP 7.4をインストールします。</p>
<pre><code class="language-bash line-numbers"># (Bash)
$ sudo apt install lsphp74
</code></pre>
<p>インストールが終わったら、LiteSpeedの管理コンソールから<code>Server Configuration</code>を開き、<code>External App</code>タブにある<code>LiteSpeed SAPI App</code>の<code>Command</code>設定を書き換えてください。</p>
<h2>まとめ</h2>
<p>これで、第4のウェブサーバーと呼ばれるLiteSpeedを軸にした新しめなLAMP環境が構築出来ました。</p>
<p>今回はインストールのみですが、実際に利用する際はLiteSpeedの管理コンソールで様々な設定が必要になります。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://www.digitalocean.com/community/tutorials/how-to-install-the-openlitespeed-web-server-on-ubuntu-18-04">How To Install the OpenLiteSpeed Web Server on Ubuntu 18.04 | DigitalOcean</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/cherubim1111/items/61cbc72673712431d06e">Ubuntu 18.04 LTS に MariaDB 10.4 をインストール</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-lamp-with-litespeed/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React＋Djangoでブログを作る5〈ユーザー認証編〉</title>
		<link>https://www.kthksgy.com/web/make-react-django-blog5/</link>
					<comments>https://www.kthksgy.com/web/make-react-django-blog5/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Fri, 31 Jan 2020 09:45:16 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=18</guid>

					<description><![CDATA[今回は、ログイン・ログアウト等の部分を作ります。 Djangoバックエンドの準備 ログインとログアウトをトークン認証で行うために修正を行います。 Django REST Framework JWTのインストール デフォル&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は、ログイン・ログアウト等の部分を作ります。</p>
<h2>Djangoバックエンドの準備</h2>
<p>ログインとログアウトをトークン認証で行うために修正を行います。</p>
<h3>Django REST Framework JWTのインストール</h3>
<p>デフォルトのdjango-rest-authでもトークン認証はされているのですが、これをJWT版のトークン認証に変更します。</p>
<p>専用のパッケージが用意されていて、これをインストールして設定する事でdjango-rest-authがJWTを利用するようになります。</p>
<pre><code class="language-bash line-numbers">$ pip install djangorestframework-jwt
</code></pre>
<p><code>blogress/settings.py</code>を変更します。ついでにメールアドレス認証にも変更します。</p>
<pre><code class="language-python line-numbers"># 追記
ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_REQUIRED = True   
ACCOUNT_USERNAME_REQUIRED = False

REST_USE_JWT = True # 追記

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 変更
    ],
    'NON_FIELD_ERRORS_KEY': 'detail', # 変更
    'TEST_REQUEST_DEFAULT_FORMAT': 'json' # 変更
}
</code></pre>
<h3>ルーティングの修正</h3>
<p>また、特に理由は無いですがルーティングも少し変更しておきます。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.urls import include, path
from rest_framework.schemas import get_schema_view
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/posts/', include('posts.urls')),
    path('api/users/', include('users.urls')),
    path('api/auth/', include('rest_auth.urls')),
    path('api/auth/register/', include('rest_auth.registration.urls')),
    path('schema/', get_schema_view( # スキーマ表示の追加
        title="Blogress",
        description="API for all things …"
    ), name='openapi-schema'),
    path('docs/', TemplateView.as_view( # ドキュメント表示の追加
        template_name='swagger-ui.html',
        extra_context={'schema_url':'openapi-schema'}
    ), name='swagger-ui'),
]
</code></pre>
<p>これで、DjangoバックエンドのJWTでのトークン認証の準備が整いました。</p>
<h2>Reactフロントエンドの準備</h2>
<p>トークン認証なのでバックエンドからトークンが送られてくるわけですが、それをReactで上手く扱えるようにします。</p>
<h3>Redux</h3>
<p>Reduxは状態管理を行ってくれるフレームワークです。</p>
<p>これがあるとReactが管理するデータが膨大になった時に<code>props</code>でデータを親から子へバケツリレーしなくて良くなるようです。</p>
<p>今回はReduxを使って、得られたトークンを保持します。</p>
<h3>Redux-Thunk</h3>
<p>Redux-ThunkはReduxの機能を非同期的に行ってくれるライブラリです。</p>
<p>トークン取得等の通信を要する処理結果をReduxで管理する場合に必要になるようです。</p>
<h3>ReduxとRedux-Thunkのインストール</h3>
<p>ReduxとRedux-Thunkをインストールして、ReactとReduxの連携を補助するReact-Reduxをインストールします。</p>
<pre><code class="language-bash line-numbers">$ npm install redux
$ npm install react-redux
$ npm install redux-thunk
</code></pre>
<h3>React-Router-DOM</h3>
<p>React-Router-DOMはReactのURLとDOMを関連付けてくれるライブラリです。</p>
<p>これを使って、ログインページとトップページのルーティングを行います。</p>
<h3>React-Router-DOMのインストール</h3>
<p>下記コマンドでインストールを行います。</p>
<pre><code class="language-bash line-numbers">$ npm install react-router-dom
</code></pre>
<h3>リファクタリング</h3>
<p>これまで書いて来たフロントエンドのコードが少々気になり始めたので、ログインページを作る前にリファクタリングを行います。</p>
<p>まず初めに<code>frontend/src/App.js</code>です。</p>
<p>全てのコンポーネントの最上位に位置しているので、ここでは各ページへのルーティングと、ストアやAxiosインスタンス等アプリ内で唯一の物の作成をします。</p>
<p>ついでに<code>frontend/src/App.jsx</code>にリネームして、<code>frontend/src/components/constants.jsx</code>の内容も統合しましょう。</p>
<p>まだページは作成していませんが、ログインページを<code>frontend/src/components/pages/LoginPage.jsx</code>に作ることにしてルーティングを先にします。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/App.jsx
 * @file ルーティングやシングルトン定義をする最上位コンポーネント
 */
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Axios from 'axios';

/* Redux関連 */
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './modules';

/* ページコンポーネント */
import TopPage from './components/pages/TopPage';
import LoginPage from './components/pages/LoginPage';

/**
 * ブログタイトル
 * @type {string}
 */
export const title = 'My Blogress Life';
/**
 * ブログの説明
 * @type {string}
 */
export const description = 'This is my blog made in simple blog system named Blogress.';
/**
 * コピーライト表示
 * @type {string}
 */
export const copyright = 'Copyright © ' + title + ' ' + new Date().getFullYear() + '.';

/**
 * APIエンドポイント
 * @type {string}
 */
export const endpoint = 'http://localhost:8000/api';

/** Redux用のストア */
export const store = createStore(reducer);

/** ベースのURLが定義されたAxiosインスタンスを作成して使い回す */
export const axios = Axios.create({
  baseURL: endpoint,
  timeout: 1000,
  headers: { "Content-Type": "application/json" },
  data: {},
  responseType: 'json',
});

/* 各ページのルーティング */
const App = () =&gt; {
  return(
    &lt;Provider store={store}&gt;
      &lt;BrowserRouter&gt;
        &lt;Switch&gt;
          &lt;Route exact path='/login' component={LoginPage}/&gt;
          &lt;Route exact path='/' component={TopPage}/&gt;
        &lt;/Switch&gt;
      &lt;/BrowserRouter&gt;
    &lt;/Provider&gt;
  );
}

export default App;
</code></pre>
<p>ブログタイトルを保持するファイルが変わったので、<code>frontend/src/components/organisms/Header.jsx</code>を修正します。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/organisms/Header.jsx
 * @file ヘッダーを表示するコンポーネント
 */
import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import { AppBar, Toolbar, Typography } from '@material-ui/core';
import SportsVolleyballIcon from '@material-ui/icons/SportsVolleyball';

import { title } from '../../App';

const useStyles = makeStyles(theme =&gt; ({
    offset: theme.mixins.toolbar,
}))

const Header = () =&gt; {
    const classes = useStyles();
    return(
        &lt;Fragment&gt;
            &lt;AppBar position='fixed'&gt;
                &lt;Toolbar&gt;
                    &lt;SportsVolleyballIcon /&gt;
                    &lt;Typography variant="h6" color="inherit" noWrap&gt;
                        {title}
                    &lt;/Typography&gt;
                &lt;/Toolbar&gt;
            &lt;/AppBar&gt;
            &lt;div className={classes.offset}/&gt;
        &lt;/Fragment&gt;
    );
}

export default Header;
</code></pre>
<p>コピーライトの文字列も一元化したので、表示するコンポーネントも<code>frontend/src/components/atoms/Copyright.jsx</code>を作成します。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/atoms/Copyright.jsx
 * @file 著作権表示をするコンポーネント
 */
import React from 'react';

import { Typography } from '@material-ui/core';

import { copyright } from '../../App';

const Copyright = () =&gt; (
    &lt;Typography variant="body2" color="textSecondary" align="center"&gt;
        {copyright}
    &lt;/Typography&gt;
);

export default Copyright;
</code></pre>
<p>元々コピーライト表示をしていた<code>frontend/src/components/organisms/Footer.jsx</code>も修正します。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/organisms/Footer.jsx
 * @file フッターの表示をするコンポーネント
 */

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';

import Copyright from '../atoms/Copyright';

const useStyles = makeStyles(theme =&gt; ({
    footer: {
        backgroundColor: theme.palette.background.paper,
        padding: theme.spacing(6),
    },
}))

const Footer = () =&gt; {
    const classes = useStyles();
    return(
        &lt;footer className={classes.footer}&gt;
            &lt;Copyright /&gt;
        &lt;/footer&gt;
    );
}

export default Footer;
</code></pre>
<p>Axiosのインスタンスを作成して使い回すようにしたので、<code>frontend/src/components/pages/TopPage.jsx</code>のAxiosがインスタンスを読み込むように修正します。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/pages/TopPage.jsx
 * @file トップページを表示するコンポーネント
 */
import React, { useState, useEffect } from 'react';
import { axios } from '../../App';
import { makeStyles } from '@material-ui/core/styles';

import TopPageTemplate from '../templates/TopPageTemplate';

const useStyles = makeStyles(theme =&gt; ({
    page: {
        margin : 60,
    }
}));

const TopPage = () =&gt; {
    const classes = useStyles();
    const [posts, setPosts] = useState([]);

    useEffect(() =&gt; {
        axios
            .get('/posts/', )
            .then(res=&gt;{setPosts(res.data);})
            .catch(err=&gt;{console.log(err);});
    }, []);

    return(
        &lt;TopPageTemplate className={classes.page} posts={posts} /&gt;
    );
}

export default TopPage;
</code></pre>
<h3>ログインページを作る</h3>
<p>シンプルなログインページを作ります。</p>
<p>まずは、ログイン用のフォームを作成します。</p>
<p>これは後々トップページでも小さく開けるように、<code>frontend/src/components/organisms/LoginForm.jsx</code>に作成します。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/organisms/LoginForm.jsx
 * @file ログイン時の入力パネルを表示するコンポーネント
 */

import React, { useState } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import { Box, TextField, Button, Checkbox, FormControlLabel, Typography } from '@material-ui/core'
import InputIcon from '@material-ui/icons/Input';

const useStyles = makeStyles(theme =&gt; ({
    form: {
        width: '100%',
        marginTop: theme.spacing(1),
    },
    button: {
        width: '100%',
        marginTop: theme.spacing(1),
    },
    checkbox: {
        float: 'right',
    }
}));

const LoginForm = props =&gt; {
    const classes = useStyles();
    const [email, setEmail] = useState('');
    const [password, setPassword] = useState('');
    const [showPassword, setShowPassword] = useState(false);
    return (
        &lt;Box width={256} p={6} border={1}&gt;
            &lt;Typography component="h1" variant="h5"&gt;ログイン&lt;/Typography&gt;
            &lt;form noValidate&gt;
                &lt;TextField className={classes.form} id="emailForm"
                    value={email} onChange={e =&gt; setEmail(e.target.value)}
                    label="メールアドレス"/&gt;
                &lt;TextField className={classes.form} id='passwordForm'
                    value={password} onChange={e =&gt; setPassword(e.target.value)}
                    label="パスワード"
                    type={showPassword ? '' : 'password'}
                    autoComplete='current-password'/&gt;
                &lt;FormControlLabel className={classes.checkbox} label="パスワードを表示" control={
                    &lt;Checkbox
                        checked={showPassword}
                        onChange={e =&gt; setShowPassword(e.target.checked)}
                        value="primary"
                        inputProps={{ 'aria-label': 'primary checkbox' }}/&gt;
                }/&gt;
                &lt;Button className={classes.button}
                    variant='contained' color='primary' startIcon={&lt;InputIcon /&gt;} onClick={e=&gt;props.login(email, password)}&gt;ログイン&lt;/Button&gt;
            &lt;/form&gt;
        &lt;/Box&gt;
    );
}

export default LoginForm;
</code></pre>
<p>そしてこれを使ってログインページのテンプレートを作ります。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/templates/LoginPageTemplate.jsx
 * @file ログインページのテンプレートコンポーネント
 */
import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import Footer from '../organisms/Footer';
import LoginForm from '../organisms/LoginForm';

const useStyles = makeStyles(theme =&gt; ({
    content: {
        display: 'flex',
        justifyContent: 'center',
        marginTop: theme.spacing(4),
    }
}));

const LoginPageTemplate = props =&gt; {
    const classes = useStyles();
    return (
        &lt;Fragment&gt;
            &lt;div className={classes.content}&gt;
                &lt;LoginForm login={props.login}/&gt;
            &lt;/div&gt;
            &lt;Footer /&gt;
        &lt;/Fragment&gt;
    );
}

export default LoginPageTemplate;
</code></pre>
<p>このテンプレートに対して、実際にログインを行う関数を<code>props</code>で渡す事で、<code>frontend/src/components/pages/LoginPage.jsx</code>にページを作ります。</p>
<pre><code class="language-javascript line-numbers">/**
 * src/components/pages/LoginPage.jsx
 * @file ログインページ
 */

import React from 'react';
import { axios } from '../../App';

import { useSelector, useDispatch } from 'react-redux';

import { setToken } from '../../modules/UserModule';

import LoginPageTemplate from '../templates/LoginPageTemplate';

const LoginPage = () =&gt; {
    const dispatch = useDispatch();
    const token = useSelector(state =&gt; state.user.token);

    const login = (email, password) =&gt; {
        axios
            .post('/auth/login/', {
                email: email,
                password: password,
            })
            .then(res=&gt;{dispatch(setToken(res.data.token));axios.defaults.headers.common['Authorization'] = 'JWT ' + token;})
            .catch(err=&gt;{console.log(err);});
    }
    return (
        &lt;LoginPageTemplate login={login}/&gt;
    );
}

export default LoginPage;
</code></pre>
<p>これで、<code>http://localhost:3000/login</code>にアクセスするとログイン画面が表示されたはずです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/8BUxmeaoFOS7bWcdzoiZxyi_GZO7GXXGx3QBrbY296WnoqX_QkxVZt3eCg2fwFUEaxBKJcB6I7hdJIdCWXFbhUHe-roaR0ZSCb9hJpgn6DswIUfdP36sa9YsPlkvAE4Z0CiIZEbuGQ=s0" alt="" /></p>
<p>実際にログイン出来たか確認する実装していないので、ここはブラウザのデバッグ機能でどういうResponseが返って来たのかを見ましょう。</p>
<p>ChromeではF12キーで開けて、メールアドレスとパスワードに関わらずログインを押すと、Networkのタブに何かしらの結果が出て来るはずです。</p>
<p>ステータスコード200で、<code>token</code>がJSON形式で返って来ていれば完了です。</p>
<h3>認証トークンをヘッダに付与する</h3>
<p>JSON Web Tokenでの認証は、HTTPヘッダに<code>Authorization</code>として受け取ったトークンを設定する事で実現出来ます。</p>
<p>具体的には以下のようなヘッダを付与します。</p>
<pre><code class="language-javascript line-numbers">{
  "header": {
    "Content-Type": "application/json"
    "Authorization": "JWT TTTTOOOOKKKKEEEENNNN"
  },
  "data": {},
}
</code></pre>
<p>上記のコードはJWTを取得したと同時にAxiosのヘッダに<code>Authorization</code>として付与するようにしているので、既に認証状態で通信が可能です。</p>
<p>具体的には、<code>axios.defaults.headers.common['Authorization'] = 'JWT ' + token;</code>でデフォルト設定を行っています。</p>
<h2>まとめ</h2>
<p>とりあえずJWTでのトークンの受け取りと認証が出来るようになりました。</p>
<p>現在はバックエンド側の権限設定を適当にやっているので、Django REST Frameworkについて理解を深めて適切に設定したいです。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://react-redux.js.org/next/api/hooks#using-hooks-in-a-react-redux-app">Hooks・React Redux</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/souhei-etou/items/613f6b1a4a796d6f748f">react-hooks で redux を書いてみた話</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/S-Masakatsu/items/94624f845f8f75e3f6bd">挫折しないで！作って学ぶ React + Redux 入門【実践】</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-react-django-blog5/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React＋Djangoでブログを作る4〈トップページ作成編〉</title>
		<link>https://www.kthksgy.com/web/make-react-django-blog4/</link>
					<comments>https://www.kthksgy.com/web/make-react-django-blog4/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Thu, 30 Jan 2020 11:00:26 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=17</guid>

					<description><![CDATA[今回は、Reactでページの表示部分を作っていきます。 モックアップを作る まず初めに、ページのモックアップを作ります。 モックアップとは、工業製品の設計・デザイン段階で試作される、外見を実物そっくりに似せて作られた実物&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は、Reactでページの表示部分を作っていきます。</p>
<h2>モックアップを作る</h2>
<p>まず初めに、ページのモックアップを作ります。</p>
<blockquote><p>
  モックアップとは、工業製品の設計・デザイン段階で試作される、外見を実物そっくりに似せて作られた実物大の模型のこと。ソフトウェアやWebサイト、印刷物などのデザインを確認するための試作品のこともこのように呼ばれることがある。</p>
<p>  ――― IT用語辞典 e-Words
</p></blockquote>
<p>作ろうと思いましたが、この手のモックアップツールは大半が有料みたいなのでペイントで済ませます。</p>
<p>出来ました。シンプルな2カラム右サイドバーのトップページです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/dNFrR2ohkfs_Vs3BJSXpaXzkWRZZf83EDsO_EuEDHC1YqabmlIVV0SB2_BGPbCozZXpdBxZSpypJ8SMIRsZ5eTYqT-z0F1iKvIW6m5HDT7uH_A_iikcN55RCsD0goHoDwe1s8aroGA=s0" alt="" /></p>
<p>少々荒は目立ちますが、こんな感じで作るページのイメージを画像にしておきます。</p>
<p>ヘッダーは左側にブログのヘッダー画像かブログアイコンとブログタイトルが表示され、右側にドロップダウンメニューやリンクが表示されます。</p>
<p>それぞれの記事はアイキャッチ画像の上にタイトルと概要が表示されます。</p>
<p>サイドバーはまだ何を置くか決めていませんが、ウィジェット形式の何かが作成出来ると良いと思います。</p>
<h2>Atomic Design とは</h2>
<p>ウェブページを構成する要素を粒度を基準に分類する事で、要素の再利用性の向上や冗長性の削減を目指すという思想らしいです。</p>
<p>要素ベースでページを作るReact等の最近のフロントエンドフレームワークでウェブアプリケーションを作成する場合に役に立ちます。</p>
<p>分類は5つ有り、それぞれに以下のような名前と基準が付いています。</p>
<ul>
<li>Atoms (原子) : それ以上分割出来ない要素</li>
<li>Molecules (分子) : 2つ以上の原子から構成される要素</li>
<li>Organisms (有機体) : いくつかの分子の集合</li>
<li>Templates (テンプレート) : データを流し込むための枠組み</li>
<li>Pages (ページ) : 実際のデータを含むテンプレート</li>
</ul>
<p>実際にどのような要素が分類されるのかをメモしておきます。</p>
<table>
<thead>
<tr>
<th align="center">分類</th>
<th align="center">要素</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">Atoms</td>
<td align="center">Button, Icon, Text, Title</td>
</tr>
<tr>
<td align="center">Molecules</td>
<td align="center">Card, Box, Form, Popup</td>
</tr>
<tr>
<td align="center">Organisms</td>
<td align="center">Header, Calender, Modal, CardList</td>
</tr>
<tr>
<td align="center">Templates</td>
<td align="center">TopPage, SinglePost, CategoryList, RegistrationPage</td>
</tr>
<tr>
<td align="center">Pages</td>
<td align="center">自己紹介ページとか</td>
</tr>
</tbody>
</table>
<p>入力フォームは入力のためテキストボックスや送信のためボタン等のAtomの集合なのでMolecule、ヘッダーは様々なアイコン付きボタンや見出しテキストドロップダウンメニューから成り立っているのでOrganismsという感じのようです。</p>
<p>少々難しいですが、ウェブ開発を効率化するための思想に捕らわれて効率が落ちるのは本末転倒なので、あまり深くは考えずこの思想に緩く制約されようと思います。</p>
<p>ちなみに、Atomic Designに階層構造を取り入れた<strong>Layered Atomic Design</strong>という物もあるようです。</p>
<h2>Reactでフロントエンドを作る</h2>
<p>では、実際に作っていきます。</p>
<h3>Material-UIのインストール</h3>
<p>作り始める前に、React用のUIフレームワークの1つであるMaterial-UIをインストールします。</p>
<pre><code class="language-bash line-numbers">$ cd frontend
$ npm install @material-ui/core @material-ui/icons
</code></pre>
<p>コマンドを使って暫く待つとインストール完了です。</p>
<h3>Atomic Designのためのディレクトリ構造を作る</h3>
<p>前回は、ページを構成する要素を全て<code>frontend/src/App.js</code>に記述しました。</p>
<p>Atomic Designでは要素に対し分類がされているので、まずはそれぞれのディレクトリを作成します。</p>
<p>まずは要素を保存するための親ディレクトリを<code>frontend/src/components/</code>に作成します。</p>
<p>そしてその中に、<code>atoms</code>、<code>molecules</code>、<code>organisms</code>、<code>templates</code>、<code>pages</code>のディレクトリをそれぞれ作ります。</p>
<p>具体的には、以下のようなディレクトリ構造になります。</p>
<pre><code class="language-clean line-numbers">blogress/
|-- frontend
   |-- src/
      |-- components/
         |-- atoms/ (原子)
         |-- molecules/ (分子)
         |-- organisms/ (有機体)
         |-- templates/ (テンプレート)
         |-- pages/ (ページ)
   |-- ...
|-- ...
</code></pre>
<p>これらのディレクトリに適した物を書く事で、Reactのコンポーネント志向なウェブアプリケーション作成が自然と出来ます。</p>
<h3>定数ファイルを作る</h3>
<p>定数用のファイルを作る設計が良いのかは置いておいて、定数を置いておくために<code>frontend/src/components/constants.jsx</code>を作成してブログ名等を記述しておきます。</p>
<pre><code class="language-javascript line-numbers">export const BLOG_TITLE = 'My Blogress Life';
export const BLOG_DESCRIPTION = 'This is my blog made in simple blog system named Blogress.'
</code></pre>
<p>将来的にはこれをAPIから読み込む形にするので、このように一ヶ所から参照する形にするようにしておくと恐らく便利です。</p>
<h3>ヘッダーを作る</h3>
<p>何処から作っても良いのですが、まずはヘッダーから作る事にします。</p>
<p>ReactはReactコンポーネントと呼ばれる要素単位を併せてページを作成するので、ヘッダーのReactコンポーネントを作成します。</p>
<p><code>frontend/src/components/organisms/Header.jsx</code>に以下のように記述してください。</p>
<pre><code class="language-javascript line-numbers">import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import { AppBar, Toolbar, Typography } from '@material-ui/core';
import SportsVolleyballIcon from '@material-ui/icons/SportsVolleyball';

import { BLOG_TITLE } from '../constants';

const useStyles = makeStyles(theme =&gt; ({
    offset: theme.mixins.toolbar,
}))

const Header = () =&gt; {
    const classes = useStyles();
    return(
      &lt;Fragment&gt;
        &lt;AppBar position='fixed'&gt;
            &lt;Toolbar&gt;
                &lt;SportsVolleyballIcon /&gt;
                &lt;Typography variant="h6" color="inherit" noWrap&gt;
                    {BLOG_TITLE}
                &lt;/Typography&gt;
            &lt;/Toolbar&gt;
        &lt;/AppBar&gt;
        &lt;div className={classes.offset}/&gt;
      &lt;/Fragment&gt;
    );
}

export default Header;
</code></pre>
<p>今の所は特に何の機能も無い、ブログタイトルと謎のアイコンだけが存在している簡易なヘッダーです。</p>
<h3>Atomic Designでファイル分割を行う</h3>
<p>ヘッダーが完成したので早速トップページに設置して確認したい所ですが、ヘッダーのReactコンポーネントを利用する前に、<code>frontend/src/App.js</code>の中身をAtomic Designに従って分割します。</p>
<p>オレオレAtomic Designかも知れませんが、<code>frontend/src/App.js</code>を以下のように分割します。</p>
<p><code>frontend/src/components/templates/TopPageTemplate.jsx</code>では、トップページの骨組みを作成します。</p>
<pre><code class="language-javascript line-numbers">import React, { useState, Fragment } from 'react';

import Header from '../organisms/Header';

const TopPageTemplate = props =&gt; (
    &lt;Fragment&gt;
        &lt;Header /&gt;
        {props.posts.map(post =&gt; (
            &lt;div key={post.id}&gt;
            &lt;h1&gt;{post.title}&lt;/h1&gt;
            &lt;p&gt;{post.body}&lt;/p&gt;
            &lt;/div&gt;
        ))}
    &lt;/Fragment&gt;
);

export default TopPageTemplate;
</code></pre>
<p>このテンプレートに対して、<code>frontend/src/components/pages/TopPage.jsx</code>がブログの記事データを<code>props</code>で流し込むような構造にします。</p>
<pre><code class="language-javascript line-numbers">import React, { useState, useEffect } from 'react';
import axios from 'axios';

import { makeStyles } from '@material-ui/core/styles';

import TopPageTemplate from '../templates/TopPageTemplate';

const useStyles = makeStyles(theme =&gt; ({
    page: {
        margin : 60,
    }
}));

const TopPage = () =&gt; {
    const classes = useStyles();
    const [posts, setPosts] = useState([]);

    useEffect(() =&gt; {
        axios
            .get('http://localhost:8000/api/posts/')
            .then(res=&gt;{setPosts(res.data);})
            .catch(err=&gt;{console.log(err);});
    }, []);

    return(
        &lt;TopPageTemplate className={classes.page} posts={posts} /&gt;
    );
}

export default TopPage;
</code></pre>
<p>従って、<code>frontend/src/App.js</code>では以下のように<code>frontend/src/components/pages/TopPage.jsx</code>の表示のみを行います。</p>
<pre><code class="language-javascript line-numbers">import React from 'react';

import TopPage from './components/pages/TopPage';

const App = () =&gt; {
  return(
    &lt;TopPage /&gt;
  );
}

export default App;
</code></pre>
<p>多少オレオレAtomic Designな気がしますが、これが正しいと言い聞かせながら進みます。</p>
<p>では、<code>http://localhost:3000/</code>にアクセスして確認しましょう。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/TFmgZ7zAW-PyHQuSloF0oZHLoLoXTtjGtTS2aPmIl2UTilUbZ-YUOaWKmNGlYZXzDaQZU0vkDL1UHXnpU6k1xAAKRAj0uDRRoLetEUk3mDIZEpT8C9DPyY5Bs0fyPi8w0ProrYesdQ=s0" alt="" /></p>
<p>きちんとヘッダーが付いているのが確認出来ました。</p>
<h3>記事タイルグリッドを作る</h3>
<p>そのまま記事の一覧表示も作ります。</p>
<p>私のイメージを図にしたモックアップの通り、今回は記事タイルがグリッド表示されている物を作ります。</p>
<p>まずは、Atomic Designに従って、<code>frontend/src/components/PostTile.jsx</code>に単体の記事タイルを作ります。</p>
<pre><code class="language-javascript line-numbers">import React from 'react';
import { makeStyles } from '@material-ui/core/styles';

import { Card, CardActionArea, CardContent, CardMedia, Typography } from '@material-ui/core';

const useStyles = makeStyles({
  card: {
    position: 'relative',
    maxWidth: 345,
  },
  media: {
    height: 345
  },
  content: {
    position: 'absolute',
    bottom: 0,
    width: '100%',
    color: 'white',
    backgroundColor: 'rgba(0,0,0,0.6)',
  },
  excerpt: {
      marginRight: 30,
  }
});

const PostTile = props =&gt; {
    const classes = useStyles();
    return (
        &lt;Card className={classes.card}&gt;
          &lt;CardActionArea&gt;
            &lt;CardMedia
              className={classes.media}
              image={props.thumbnail}
              title={props.title}
            /&gt;
            &lt;CardContent className={classes.content}&gt;
              &lt;Typography gutterBottom variant="h5" component="h2"&gt;
                {props.title}
              &lt;/Typography&gt;
              &lt;Typography variant="body2" component="p" className={classes.excerpt} noWrap&gt;
                {props.body}
              &lt;/Typography&gt;
            &lt;/CardContent&gt;
          &lt;/CardActionArea&gt;
        &lt;/Card&gt;
      );
}

export default PostTile;
</code></pre>
<p>そして、記事タイルグリッドはこの記事タイルを複数用いた物なので、<code>frontend/src/components/organisms/PostTileGrid.jsx</code>に作成します。</p>
<pre><code class="language-javascript line-numbers">import React from 'react';

import { Grid } from '@material-ui/core'

import PostTile from '../molecules/PostTile';

const PostTileGrid = props =&gt; (
    &lt;Grid container spacing={4}&gt;
        {props.posts.map(post =&gt; (
            &lt;Grid item xs={4}&gt;
                &lt;PostTile title={post.title} body={post.body} thumbnail={post.thumbnail}/&gt;
            &lt;/Grid&gt;
        ))}
    &lt;/Grid&gt;
);

export default PostTileGrid;
</code></pre>
<p><code>PostTileGrid</code>が完成したので、<code>frontend/src/components/templates/TopPageTemplate.jsx</code>で利用するように書き換えます。</p>
<pre><code class="language-javascript line-numbers">import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import Header from '../organisms/Header';
import PostTileGrid from '../organisms/PostTileGrid';

const useStyles = makeStyles(theme =&gt; ({
    postTileGrid: {
        margin: theme.spacing(4),
    }
}));

const TopPageTemplate = props =&gt; {
    const classes = useStyles();
    return (
        &lt;Fragment&gt;
            &lt;Header /&gt;
            &lt;div className={classes.postTileGrid}&gt;
                &lt;PostTileGrid posts={props.posts}/&gt;
            &lt;/div&gt;
        &lt;/Fragment&gt;
    );
}

export default TopPageTemplate;
</code></pre>
<p>また、このままでは左右の余白がギリギリまで詰められてしまうので、<code>frontend/src/index.css</code>を編集して、<code>body</code>に<code>min-width: 960px;</code>を追加します。</p>
<pre><code class="language-css line-numbers">body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  min-width: 960px;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
</code></pre>
<p>これで、記事がタイル状でグリッド表示されるようになったはずです。</p>
<p>サムネイル表示にも対応しているため、Djangoの管理画面から<code>posts</code>の<code>thumbnail</code>を設定しながら、今は3つしかないサンプル記事をもう少し増やしてから確認します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/1Y1PBb6Q1QArm6a4t59wtsLYOPF7j0R0Yf6BUI3SRpx9aPQKYwaCI4TF3g6WalSwIAo1vj51FPpNEwIgD0xfPZcsUR4vMsYNWter1VlQK6WhvnzAEH3aRcwL0DnOtFY-L4BCwMlyJQ=s0" alt="" /></p>
<p>これまで見て来た質素な記事リストから、リッチな記事タイルグリッドになったのが確認出来たと思います。</p>
<p>また、<code>frontend/src/index.css</code>でページの最低幅を指定しているので、横幅が小さくなりすぎても表示が崩れないようになっています。</p>
<h3>フッターを作る</h3>
<p>最後にフッターを作りましょう。</p>
<p><code>frontend/src/components/organisms/Footer.jsx</code>を作成してください。</p>
<pre><code class="language-javascript line-numbers">import React from 'react';
import { makeStyles } from '@material-ui/core/styles';

import { Typography } from '@material-ui/core';

import { BLOG_TITLE } from '../constants';

function Copyright() {
    return (
        &lt;Typography variant="body2" color="textSecondary" align="center"&gt;
            {'Copyright © '}{BLOG_TITLE}{' '}{new Date().getFullYear()}{'.'}
        &lt;/Typography&gt;
    );
}

const useStyles = makeStyles(theme =&gt; ({
    footer: {
        backgroundColor: theme.palette.background.paper,
        padding: theme.spacing(6),
    },
}))

const Footer = () =&gt; {
    const classes = useStyles();
    return(
        &lt;footer className={classes.footer}&gt;
            &lt;Copyright /&gt;
        &lt;/footer&gt;
    );
}

export default Footer;
</code></pre>
<p>フッターのReactコンポーネントが完成したので、<code>frontend/src/components/templates/TopPageTemplate.jsx</code>で利用しましょう。</p>
<pre><code class="language-javascript line-numbers">import React, { Fragment } from 'react';
import { makeStyles } from '@material-ui/core/styles';

import Header from '../organisms/Header';
import Footer from '../organisms/Footer';
import PostTileGrid from '../organisms/PostTileGrid';

const useStyles = makeStyles(theme =&gt; ({
    postTileGrid: {
        margin: theme.spacing(4),
    }
}));

const TopPageTemplate = props =&gt; {
    const classes = useStyles();
    return (
        &lt;Fragment&gt;
            &lt;Header /&gt;
            &lt;div className={classes.postTileGrid}&gt;
                &lt;PostTileGrid posts={props.posts}/&gt;
            &lt;/div&gt;
            &lt;Footer /&gt;
        &lt;/Fragment&gt;
    );
}

export default TopPageTemplate;
</code></pre>
<p>これで、記事タイルグリッドの後に今の所コピーライトの表示だけしてくれるフッターが表示されるはずです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/36g8nMy407UOxgwU1nDku5E6i4TIHpsmnHTiSV2PHfLpt1Qt8QZOVt8kpMOPTSsSpfc2APYzvoqNWEtNwPjOe0S2TLKeBxJnKWGJecIGLq5BzKZdslZBK1RO7MYrD7B0KVWlttVDqA=s0" alt="" /></p>
<p>記事が作成日時で昇順になっていたりと気になる点は有りますが、完成です。</p>
<h2>まとめ</h2>
<p>今回で一応なブログのトップページのような形になりました。</p>
<p>成果が目で確認しやすいフロントエンドはやっていて楽しいですね。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://material-ui.com/getting-started/installation/">Installation &#8211; Material-UI</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://material-ui.com/components/app-bar/">App Bar React component &#8211; Material-UI</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://material-ui.com/components/grid/">Grid component &#8211; Material-UI</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://blog.spacemarket.com/code/atomic-design%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6react%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%82%92%E5%86%8D%E8%A8%AD%E8%A8%88%E3%81%97%E3%81%9F%E8%A9%B1/">Atomic Designを使ってReactコンポーネントを再設計した話</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://uxdaystokyo.com/articles/glossary/atomic-design/">アトミックデザイン</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-react-django-blog4/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React＋Djangoでブログを作る3〈Django＋React連携編〉</title>
		<link>https://www.kthksgy.com/web/make-react-django-blog3/</link>
					<comments>https://www.kthksgy.com/web/make-react-django-blog3/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Wed, 29 Jan 2020 09:41:14 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=16</guid>

					<description><![CDATA[今回は、DjangoとReactの連携を実装します。 2020/11/09: コメントで頂いた修正をしました。ありがとうございます。 Djangoでバックエンドを作る 前回でREST Frameworkの基礎は完成したの&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は、DjangoとReactの連携を実装します。</p>
<p><strong>2020/11/09: コメントで頂いた修正をしました。ありがとうございます。</strong></p>
<h2>Djangoでバックエンドを作る</h2>
<p>前回でREST Frameworkの基礎は完成したのですが、Reactとリソースを共有するためにはCORSというものを実現する必要があります。</p>
<h3>CORSの実装</h3>
<p>CORSはHTTP通信にCORSヘッダーを含める事で実装出来るのですが、Djangoではdjango-cors-headersというパッケージを導入して設定するだけで、簡単にCORSが実装出来ます。</p>
<pre><code class="language-bash line-numbers">$ pip install django-cors-headers
</code></pre>
<p>例の如く、新たなアプリを<code>blogress/settings.py</code>にて登録しましょう。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # ローカルアプリ
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig',

    # サードパーティアプリ
    'rest_framework',
    'rest_framework.authtoken',
    'corsheaders', # 追記
    'allauth',
    'allauth.account',
    'allauth.socialaccount',
    'rest_auth',
    'rest_auth.registration',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # 追記
    'django.middleware.common.CommonMiddleware', # 追記
    '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',
]

# 追記
CORS_ORIGIN_WHITELIST = [
    'http://localhost:3000'
]
</code></pre>
<p>これでReactとの連携準備はバッチリです。</p>
<h2>Reactでフロントエンドを作る</h2>
<p>では、フロントエンドを作っていきます。</p>
<h3>Reactのインストール</h3>
<p>Node.jsのインストールについては事前に行っているので、<code>npm</code>コマンドが使える状態からのスタートです。</p>
<pre><code class="language-bash line-numbers"># Reactのインストール
$ npm install -g create-react-app
</code></pre>
<h3>Reactプロジェクトの作成</h3>
<p>Djangoプロジェクトのルートディレクトリ<code>/blogress/</code>にて、<code>create-react-app</code>でプロジェクトを開始します。</p>
<pre><code class="language-bash line-numbers">$ npx create-react-app frontend
</code></pre>
<p>完了すると、以下のようなディレクトリツリーになるはずです。</p>
<pre><code class="language-clean line-numbers">blogress/ (ルートディレクトリ)
|-- blogress/
|-- frontend/
|-- posts/
|-- templates/
|-- users/
|-- db.sqlite3
|-- manage.py
</code></pre>
<p>きちんとインストール出来たか確認するため、Reactのサーバーを立ち上げてみます。</p>
<pre><code class="language-bash line-numbers">$ cd frontend
$ npm start
# http://localhost:3000 にアクセスして確認
</code></pre>
<p>もしかしたら自動でブラウザが立ち上がるかもしれません。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/7lc2uzOmNe6b-UyJ68X4j1s-QFwacT80NG2Q0-GBI889yFDl1wP6k6mkAixdC-rzCyIwsGSFpzsQYB8HP-QaTw6Vo5gI2oxol3nheJER4erIu4yqLPWjZxy08KKkQHgjCUsSZljS9A=s0" alt="" /><br />
無事立ち上がったようです。何故かEdgeでは見られなかったのでChromeで見ています。</p>
<h3>ページの作成</h3>
<p>ではAPIから実際にデータを受け取る前に、まずは仮データを用意してそれをページ上に表示させてみます。</p>
<p>Reactサーバーが実際に描画しているページは<code>frontend/src/App.js</code>にあります。</p>
<pre><code class="language-javascript line-numbers">import React, { Fragment } from 'react';

const App = () =&gt; {
  const posts = [
    {
      id: 1,
      title: "仮データ1",
      description: "仮テキストです。"
    },
    {
      id: 2,
      title: "仮データ2",
      description: "仮テキストです。仮テキストです。"
    },
    {
      id: 3,
      title: "仮データ3",
      description: "仮テキストです。仮テキストです。仮テキストです。"
    }
  ]

  return(
    &lt;Fragment&gt;
      {posts.map(post =&gt; (
        &lt;div key={post.id}&gt;
          &lt;h1&gt;{post.title}&lt;/h1&gt;
          &lt;p&gt;{post.description}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/Fragment&gt;
  );
}

export default App;
</code></pre>
<p>このように変更すると、サーバーを立ち上げている場合は自動で再読み込みしてもらえます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/dQ5x5tM9BYuyUwICTgvieut1bIp2PWYn3fwahMa7st7Ve3NkD3CTicyD4UCgt2ccDrLYB6Wr7pGnyntBCno8CclkoVGLaDOhjIPi4pZLYHJ12-w62WlFOl7-_egQG17YoRKhXrF85A=s0" alt="" /></p>
<p>上手く行くと、このように3つ作った仮データがその説明と共に描画されます。</p>
<h3>Axiosのインストール</h3>
<p>Axiosは、ReactでREST APIへのリクエスト処理を実装するためのライブラリです。</p>
<p>React同様にこちらも<code>npm</code>コマンドでインストールします。</p>
<pre><code class="language-bash line-numbers">$ npm install axios
</code></pre>
<h3>REST APIの利用</h3>
<p>Axiosをインストールし終えたので、REST APIからデータを受け取る事が出来るようになりました。</p>
<p><code>frontend/src/App.js</code>に書いていた仮データを実際のREST APIからのデータ取得に置き換えます。</p>
<pre><code class="language-javascript line-numbers">import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

const App = () =&gt; {
  const [posts, setPosts] = useState(null);

  useEffect(() =&gt; {
    axios
    .get('http://localhost:8000/api/posts/')
    .then(res=&gt;{setPosts(res.data);})
    .catch(err=&gt;{console.log(err);});
  }, []);

  return(
    &lt;Fragment&gt;
      {posts.map(post =&gt; (
        &lt;div key={post.id}&gt;
          &lt;h1&gt;{post.title}&lt;/h1&gt;
          &lt;p&gt;{post.description}&lt;/p&gt;
        &lt;/div&gt;
      ))}
    &lt;/Fragment&gt;
  );
}

export default App;
</code></pre>
<p>では、サーバーを起動しましょう。</p>
<p>この時、Djangoサーバーも立ち上げるのを忘れないように気を付けます。</p>
<p>ターミナルを2つ用意して、それぞれのサーバーを立ち上げてください。</p>
<pre><code class="language-bash line-numbers"># Djangoサーバーの起動
$ python manage.py runserver
</code></pre>
<pre><code class="language-bash line-numbers"># Reactサーバーの起動
<span class="katex math inline">cd frontend</span> npm start
</code></pre>
<p>どちらも起動したら、<code>http://localhost:3000</code>にアクセスして確認しましょう。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/6wtQwjYvMQkj0Li5KlubGYHU0b4wLtR4gqSbylOY42bd7CDbJ1RJQ7-M7G_uWIf20WFe_DvP70mTt0q_HdtbP00VMshZ6yMkl4OYRwML-v6dFSpeMwX5GoYqAhGnA3zfIYUbvly-Zw=s0" alt="" /></p>
<p>無事にデータが受信出来ているようです。</p>
<h2>まとめ</h2>
<p>DjangoとReactの簡単な連携が取れるようになってかなり嬉しいです。</p>
<p>Reactの新機能「React Hooks」を使う事でコードがシンプルになりました。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/__init__/items/f5a5a64a05541fcda713">Django REST Framework の使い方メモ</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://ja.reactjs.org/tutorial/tutorial.html">チュートリアル：React の導入</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://ja.reactjs.org/docs/hooks-state.html">ステートフックの利用法</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-react-django-blog3/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React＋Djangoでブログを作る2〈REST Framework編〉</title>
		<link>https://www.kthksgy.com/web/make-react-django-blog2/</link>
					<comments>https://www.kthksgy.com/web/make-react-django-blog2/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Tue, 28 Jan 2020 09:39:45 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=15</guid>

					<description><![CDATA[今回は、REST Frameworkの実装をします。 Djangoでバックエンドを作る 前回の続きから作ります。 Django REST Frameworkのインストール 以下のコマンドでインストールします。 # バージ&#8230;]]></description>
										<content:encoded><![CDATA[<p>今回は、REST Frameworkの実装をします。</p>
<h2>Djangoでバックエンドを作る</h2>
<p>前回の続きから作ります。</p>
<h3>Django REST Frameworkのインストール</h3>
<p>以下のコマンドでインストールします。</p>
<pre><code class="language-bash line-numbers"># バージョン確認
<span class="katex math inline">python -V
Python 3.7.4

# Django REST Frameworkのインストール</span> pip install djangorestframework
</code></pre>
<h3>REST Frameworkの登録</h3>
<p>Django REST Frameworkもアプリなので、<code>blogress/settings.py</code>に追記して登録します。<br />
ついでに、REST Framework用の権限設定も追加してください。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # ローカルアプリ
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig',

    # サードパーティアプリ
    'rest_framework', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# 追記
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}
</code></pre>
<h3>ルーティングの設定</h3>
<p>次にREST APIにアクセスするためのURLを指定するため、<code>blogress/urls.py</code>にてルーティングの設定を行います。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('posts.urls')),
]
</code></pre>
<p>これで<code>localhost:8000/api/</code>にアクセスすると、<code>posts</code>アプリのルーティングが参照されるようになりました。</p>
<p>APIバージョンをURLに書き込むらしいのですが、私はヘッダにバージョンを付与する方針で行きます。</p>
<h3>アプリレベルのルーティングの設定</h3>
<p>先ほどはプロジェクトレベルのルーティング設定なので、今度はアプリレベルのルーティングを行います。</p>
<p>新たに<code>posts/urls.py</code>を作成して以下を記述してください。</p>
<pre><code class="language-python line-numbers">from django.urls import path

from .views import PostList, PostDetail

urlpatterns = [
    path('&lt;str:pk&gt;/', PostDetail.as_view()),
    path('', PostList.as_view()),
]
</code></pre>
<h3>シリアライザの設定</h3>
<p>次に、データを送信可能な状態に整形してくれるシリアライザの設定を行います。<br />
このシリアライザによって、REST APIでクライアントから要求されたデータがJSON形式に変換されます。</p>
<p>新たに<code>posts/serializers.py</code>を作成して以下を記述してください。</p>
<pre><code class="language-python line-numbers">from rest_framework import serializers
from .models import Post


class PostSerializer(serializers.ModelSerializer):

    class Meta:
        model = Post
        fields = ('id', 'author', 'title', 'slug', 'thumbnail', 'body', 'created_at',)
</code></pre>
<h3>ビューの設定</h3>
<p>次に、データのビューを設定します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics

from .models import Post
from .serializers import PostSerializer


# ListCreateAPIView -&gt; read-write endpoint
class PostList(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer


# RetrieveUpdateDestoryAPIView -&gt; ALlows read, update, delete
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
</code></pre>
<p>一見どちらも同じ内容のクラスに見えますが、継承しているスーパークラスが違っていて、継承するクラスによってどういったビューになるかが異なるようです。</p>
<h3>APIの確認</h3>
<p>ここまで設定したら、実際にREST APIにアクセスしてみましょう。</p>
<pre><code class="language-bash line-numbers">$ python manage.py runserver
# http://localhost:8000/api/ にアクセス
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/pOKKaOiNFSVFaE4PKcxhKAzdY9oTlxTDscPxlvlUvvvt4fMjb6lQ9xt7AzaCWut99J6NTa4OI8bVeT8mHfLrN6AoSeHE9JhhBAJecf3A7e0_VVHPLlw76Fk3mjt1JxpcwRx2-sUXbQ=s0" alt="" /></p>
<p>プロジェクトレベルのルーティングで<code>http://localhost:8000/api/</code>は<code>posts</code>アプリを参照するようになっているので、<code>posts/urls.py</code>に設定した<code>path('', PostList.as_view())</code>というアプリレベルのルーティングで記事の一覧が表示されます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/OJ3GR0NxVEMCrCaIZkZONYh5DN7A68YP3HZ8V4guVweW_9-nKgPNBijC95VlkDMmCMO4EjMD1XVZxvUAR150GMr6EFFebR-l7jO--_eVmPo08n30y00aBmqiGdXIkHEX30gbw-yeFA=s0" alt="" /></p>
<p>適当な記事の<code>id</code>をコピーして<code>api/</code>の後ろに貼り付けると、アプリレベルのルーティングで指定した通り、記事の詳細情報が開かれます。</p>
<p>ちなみに、IDに指定しているUUIDはハイフンを含みますが、ハイフンが無くても同じ情報が参照出来ます。</p>
<pre><code class="language-clean line-numbers"># 以下の2つは同じ情報を参照する
http://localhost:8000/api/c1e373ee-b513-4410-9c0a-b5608e2f2f06/
http://localhost:8000/api/c1e373eeb51344109c0ab5608e2f2f06/
</code></pre>
<h3>ログインの実装</h3>
<p>Django REST Frameworkを用いるとログインが簡単に実装出来ます。</p>
<p>プロジェクトレベルのルーティング<code>blogress/urls.py</code>を次のように変更してください。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('posts.urls')),
    path('api-auth/', include('rest_framework.urls')), # 追記
]
</code></pre>
<p>このように追記する事で、画面右上に先ほどは無かった「Log in」のリンクが表示されるようになったはずです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/txL06W08BZwB0-heoF6z3C26-4krECSh0VD-_ZfHsrE0aA4BrEmjQvGZXRKs5DxOPnPVyLacnBFKyzs5xrcjOlV2Cht9hXmU095o88A-f3ybbQ3DXSRqUoIDNoxm69wwc-hTwRF3Cw=s0" alt="" /></p>
<h3>一般ユーザーの追加</h3>
<p>これまで、ユーザーは前回作成した管理者しか居ませんでしたが、権限の設定と確認を行うため一般ユーザーを追加します。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/UvDZbs6_6TmlqKLmkLjafUH5_3GMSEXwf4JpFsCyw9RA1xRBM79noCrnm7F8JDEk2D1azlGMXHFr1Dvc2g4tzNRCOatMjyQUsk3-Z_6dLtpyWr-vUEUMNRyp71n65bdSrVRX27BbTA=s0" alt="" /></p>
<p>Usersの「+ Add」をクリックしてください。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k1TaGA02krVh4mKlBRwsjOhRzyC9KY4H2P5gHIJLIEZTeyuDkIVPgS5zJ2WheWZXKgrzJ_L1o6Ncc_NLcnBRtknHN2kueHJoC_HqVVy0ttxUG4HPVo1EhvdLkrQBSFTmb6apRGCnTg=s0" alt="" /></p>
<p>適当なメールアドレスとパスワードを記入して、画面右下のSAVEをクリックしてください。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/GvPGvt2_tQj32fIt1z_Qz2yzMLCcYchcPq-FfuaNDB7OkGmFiJ13edMUV5bda9L438OvfKlSCm81d8iF9ptrv4jWfvfUfRr_D8NazkP46x_YPZDy97z4xqnD0fgQ_RU5ua9VLcY7XQ=s0" alt="" /></p>
<p>SAVEを押した後トップページから一覧に戻るとユーザーが作成されているのが分かると思います。STAFF STATUSが管理権限の有無を示しています。</p>
<h3>権限の設定</h3>
<p>現在は全てのAPIに対して登録に関わらず全てのユーザーが記事の投稿・削除・閲覧・更新を出来るようになっています。</p>
<p>ブログシステムとしては未登録ユーザーには閲覧のみ許可をしたいので、権限の設定を行いましょう。</p>
<h4>ビューレベルの権限の設定</h4>
<p>まずはビューレベルでの権限を設定します。<br />
<code>posts/views.py</code>を次のように変更します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions # 追記

from .models import Post
from .serializers import PostSerializer


# ListCreateAPIView -&gt; read-write endpoint
class PostList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticated,) # 追記
    queryset = Post.objects.all()
    serializer_class = PostSerializer


# RetrieveUpdateDestoryAPIView -&gt; ALlows read, update, delete
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAdminUser,) # 追記
    queryset = Post.objects.all()
    serializer_class = PostSerializer
</code></pre>
<p><code>IsAuthenticated</code>は登録済みユーザーのみアクセス可能、<code>IsAdminUser</code>は管理者権限のあるユーザーのみアクセス可能となっています。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/RTlaFyYO85RhRJnZDAbqqKSVk6uhucNy03Rc198LSgz93-Q0qL0OuvLladY0fbNVdIUQJ_tMorURUakhe24FhbrMLbZM53mVeWRmLkDXOPoTUt8Id4-GIzSxcU50x4v7Ga8LErGfeA=s0" alt="" /></p>
<p>このように変更すると、ログインしていない状態では記事の一覧も記事の詳細も見る事が出来なくなります。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/Hanio3Zn3j5BHt9VFjRhbCTJk4-9BQCLy7XBpfD_S2BruNUiBslw8Zb7nBR7_1smqLnooT_l_7pHnmpeWf_pl74R2eGKg388qwg_vHMOq10g8T5nDXik2UxQLHyuZ6KaWXprd5Xb9w=s0" alt="" /></p>
<p>一般ユーザーでログインすると、記事の一覧は見る事が出来ます。今は詳細表示が複数並んだ形になっていて一覧表示では無いですが。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/BXfEcDHFzKIODaPera3rKWF143zV5DXdVvcGePbqi89ur0zk1sy00x7BkEteyjzaqBg4XCR418jfvQyNK5qdxm7RI5658F-EhF5zrZWSL6HVv2bJ1hhHhw1nTlNV3vlRoejeOR2Cgw=s0" alt="" /></p>
<p>しかし、記事の詳細を見ようとすると権限で弾かれます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/ZBD-nw-ne4oSvs8kt8cvevAjG8p0yAF8Se4kUnEgsBJElEAdUvVCpib-QbQUPYe97zHuWVtaC8IY1n6CzAe53bR4gcq0sBu5NrFjNRiDKM3FpqLRcqBbkmmnAV2N2Zswfsm31hEHyw=s0" alt="" /></p>
<p>同じページを管理者で見ると通常通り表示されるのが分かります。</p>
<h4>プロジェクトレベルの権限の設定</h4>
<p>ビューレベルでの権限の設定は、ビューが増える度に権限設定を行わなければならず、もし忘れてしまった場合に大変な事になる可能性が有ります。</p>
<p>Djangoではプロジェクトレベルでの権限の設定を行えるので、安全のために一旦プロジェクトレベルで管理者以外への全ての操作を禁じて、そこから各操作に対して必要な権限を設定していくのが安全かと思います。</p>
<p>プロジェクトレベルでの権限の設定は<code>blogress/settings.py</code>で行います。</p>
<pre><code class="language-python line-numbers">REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAdminUser', # AllowAnyから変更した
    ]
}
</code></pre>
<p>確認のために一時的にビューレベルの権限を削除します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions

from .models import Post
from .serializers import PostSerializer


# ListCreateAPIView -&gt; read-write endpoint
class PostList(generics.ListCreateAPIView):
    # permission_classes = (permissions.IsAuthenticated,) # 一時的にコメントアウト
    queryset = Post.objects.all()
    serializer_class = PostSerializer


# RetrieveUpdateDestoryAPIView -&gt; ALlows read, update, delete
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    # permission_classes = (permissions.IsAdminUser,) # 一時的にコメントアウト
    queryset = Post.objects.all()
    serializer_class = PostSerializer
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/kpxTWi8K_D1KsNW531BcWnzRWsYRhQbx4Vwr_ykLeqV4XQocCiIgi0ff_Q5R6BS2QUm-UMi6Z65FUkLcZYEfKmiigHGl1kBbMSfePVEZv8y3mT0wpGPytDxaDs4Fh7CXQgSuQBCDyw=s0" alt="" /><br />
プロジェクトレベルの権限の設定によって、今は管理者のみ全ての操作が可能なので、一般ユーザーでログインした場合でも見る事が出来ません。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/Hanio3Zn3j5BHt9VFjRhbCTJk4-9BQCLy7XBpfD_S2BruNUiBslw8Zb7nBR7_1smqLnooT_l_7pHnmpeWf_pl74R2eGKg388qwg_vHMOq10g8T5nDXik2UxQLHyuZ6KaWXprd5Xb9w=s0" alt="" /><br />
しかし、先ほどのコメントアウトを元に戻すと一般ユーザーでも見れるようになります。</p>
<p>このように、プロジェクトレベルの権限の設定は、ビューレベルの権限の設定でオーバーライドが可能なので、フェイルセーフのためにプロジェクトレベルの権限を設定するのは有効です。</p>
<h3>カスタムパーミッション</h3>
<p>Django REST Frameworkの<code>BasePermission</code>クラスの<code>has_object_permission</code>をオーバーライドする事で、独自の権限の設定を行う事が出来るようです。<br />
<code>has_object_permission</code>はユーザーのリクエストに対して何等かの判定を行った結果、許可する場合は<code>True</code>を返し、拒否する場合は<code>False</code>を返す関数です。</p>
<p><code>posts/permissions.py</code>に以下のように記述してください。</p>
<pre><code class="language-python line-numbers">from rest_framework import permissions


class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        # 著者がリクエストユーザーか、閲覧リクエストの場合のみ許可
        return obj.author == request.user or request.method in permissions.SAFE_METHODS
</code></pre>
<p>これに応じて、<code>posts/views.py</code>も変更します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions

from .models import Post
from .permissions import IsAuthorOrReadOnly # 追加
from .serializers import PostSerializer


# ListCreateAPIView -&gt; read-write endpoint
class PostList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = Post.objects.all()
    serializer_class = PostSerializer


# RetrieveUpdateDestoryAPIView -&gt; ALlows read, update, delete
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (IsAuthorOrReadOnly,) # IsAuthorOrReadOnlyに変更
    queryset = Post.objects.all()
    serializer_class = PostSerializer
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/H5nDw6QPFoUaJK0Ff_MU2aN2gcraFoVXy5f_ao-HyMNw6VW_YDbjHlFb1Plxt-8DpZdu1pbVCREqCBP-d0x8CeCqxv-ggCcAZWgHjgFld_JMEcNPWDWXkCiqWqxU6HU4AXDCAexL6g=s0" alt="" /></p>
<p>未登録ユーザーは閲覧のみ許可されています。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/oci0wxCTXVZsSOjuGKsIVr14UjrUKXjPZfxSYSowSHFqQcgR3q9vMuSgeN5_jIkdvpbGlLz0Y9TcL5fMEO5bdlAu0gNN2AWdzFjTIajLVbxHrnaYHwp37-iUzbz-C9WvABwr62mZyQ=s0" alt="" /></p>
<p>登録済みの一般ユーザーも編集する事は出来ません。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/hpK8zJOhM_AhcNP3C-hdz0Ol-P9Xrw5Pl2Ut6P18tUuUoHmUgTcN4JXGPObn5-DBhQOfhxBulb47CVPcIMOcZxRQNbBX8JimjwoKBuigzFD-yAscx2TMpBqNK0aHxmty5qIubKBkRQ=s0" alt="" /></p>
<p>著者のみ編集が可能です。</p>
<p>ちなみに、上記のカスタムパーミッションは「リクエストユーザーが著者、またはリクエストが閲覧の場合」を許可しているので、仮にこの一般ユーザーに管理権限を持たせても編集する事は許されません。</p>
<p>カスタムパーミッションを作る際は、管理者のみ絶対に操作出来るようにしておいた方が後々助かるかも知れないです。</p>
<h3>トークン認証の実装</h3>
<p>Django REST Frameworkにはトークンで認証する機能も予め用意されているため、<code>blogress/settings.py</code>を次のように編集し、アプリを登録してから有効化します。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # ローカルアプリ
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig',

    # サードパーティアプリ
    'rest_framework',
    'rest_framework.authtoken', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAdminUser',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [ # 追記
        # HTTPヘッダにセッションIDを付与してAPIに渡す
        'rest_framework.authentication.BasicAuthentication',
        # ブラウザにログイン・ログアウトの機能を提供する
        'rest_framework.authentication.SessionAuthentication',
        # トークン認証の機能を提供する
        'rest_framework.authentication.TokenAuthentication',
    ],
}
</code></pre>
<p>REST Frameworkの認証に関して合計3つの設定を行ったように見えますが、上2つの項目はデフォルトで設定されている物なので、実際に有効化した機能はトークン認証1つです。</p>
<p><code>INSTALLED_APPS</code>に変更を加えたので、マイグレーションを実行します。</p>
<pre><code class="language-bash line-numbers"># 今回はマイグレーションファイルは既に用意されているのでmigrateだけ
$ python manage.py migrate
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/qPRb4JhF1GNTVj90xBiiCUbPvaBHOvgTukXdgL1cTJcZYpb2Z0LgxSomg-GEpDeVkrmtBIaj4_X9BRXm_dh_vctw9AXvGv_iOPgpq4f4SNDxJhUW38JohAx6bqErtmRF9rYiiyNoag=s0" alt="" /></p>
<p>AUTH TOKENという項目が増えていると思います。</p>
<h3>RESTでログイン・ログアウト・パスワードリセットを提供</h3>
<p>これを実装するために、追加のパッケージを導入する。</p>
<pre><code class="language-bash line-numbers"># Django-Rest-Authのインストール
$ pip install django-rest-auth
</code></pre>
<p>例の如く、<code>blogress/settings.py</code>にアプリを登録します。カスタムユーザーモデルを使用しているので、それについての設定も行います。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # ローカルアプリ
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig',

    # サードパーティアプリ
    'rest_framework',
    'rest_framework.authtoken',
    'rest_auth', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

# 追記
REST_AUTH_SERIALIZERS = {
    'USER_DETAILS_SERIALIZER': 'users.serializers.UserSerializer'
}
</code></pre>
<p>そして、上記機能を行うために<code>blogress/urls.py</code>でプロジェクトレベルのルーティングを設定しましょう。</p>
<pre><code class="language-python line-numbers">urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('posts.urls')),
    path('api-auth/', include('rest_framework.urls')),
    path('api/rest-auth/', include('rest_auth.urls')), # 追加
]
</code></pre>
<p>これで、以下のURLでそれぞれの機能が提供されるようになったようです。</p>
<pre><code class="language-clean line-numbers"># ログイン
http://localhost:8000/api/rest-auth/login/
# ログアウト
http://localhost:8000/api/rest-auth/logout/
# パスワードリセット
http://localhost:8000/api/rest-auth/reset/
# パスワードリセットの確認
http://localhost:8000/api/rest-auth/reset/confirm/
</code></pre>
<p>恐らく、先ほどまでのログイン・ログアウトはAPIのサイトというかDjango自体のページでの処理で、APIを扱うような形では利用出来なかったためにこのようなパッケージを導入するようになっていると思います。</p>
<h3>RESTでサインアップを提供</h3>
<p>サインアップとは所謂登録処理です。これも別のパッケージが必要なようです。</p>
<pre><code class="language-bash line-numbers"># Django-AllAuthのインストール
$ pip install django-allauth
</code></pre>
<p><code>blogress/settings.py</code>でアプリの登録を行います。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # ローカルアプリ
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig',

    # サードパーティアプリ
    'rest_framework',
    'rest_framework.authtoken',
    'allauth', # 追記
    'allauth.account', # 追記
    'allauth.socialaccount', # 追記
    'rest_auth',
    'rest_auth.registration', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites', # 追記
]

# メールで認証確認をする時に使うバックエンドの指定
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# sitesフレームワークの1つでいくつかのDjangoプロジェクトから複数のWebサイトをホストするためのID
SITE_ID = 1
</code></pre>
<p><code>EMAIL_BACKEND</code>はともかく、<code>SITE_ID</code>は何なのか良く分かりませんでした。とりあえず使わなくても大丈夫だそうなのでこのまま行きます。</p>
<p><code>blogress/urls.py</code>を編集して、プロジェクトレベルのルーティングの設定を行います。</p>
<pre><code class="language-python line-numbers">urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('posts.urls')),
    path('api-auth/', include('rest_framework.urls')),
    path('api/rest-auth/', include('rest_auth.urls')),
    path('api/rest-auth/registration/', include('rest_auth.registration.urls')), # 追加
]
</code></pre>
<p>そしてマイグレーションを実行します。</p>
<pre><code class="language-bash line-numbers">$ python manage.py migrate
</code></pre>
<p>これで、<code>http://localhost:8000/api/rest-auth/registration/</code>から登録が出来るようになりました。SMTPサーバーの設定をすればメール通知も可能のようです。</p>
<p>ここから登録すると、トークン認証を有効化している場合に同時にトークンが発行されるようです。</p>
<h3>ユーザーのビュー</h3>
<p><code>posts</code>アプリで<code>posts/serializers.py</code>と<code>posts/views.py</code>を作成してビューを作ったように、ユーザーのビューも作成します。</p>
<p><code>users/serializers.py</code>を次のように記述します。</p>
<pre><code class="language-python line-numbers">from rest_framework import serializers
from .models import User


class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('id', 'username',)
</code></pre>
<p>そして、<code>users/views.py</code>を次のように記述します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions

from .models import User
from .serializers import UserSerializer


class UserList(generics.ListCreateAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveUpdateDestroyAPIView):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = User.objects.all()
    serializer_class = UserSerializer
</code></pre>
<p>このビューにアクセス出来るようにルーティングを設定しましょう。</p>
<p>まずはプロジェクトレベルのルーティング<code>blogress/urls.py</code>から設定します。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('posts.urls')),
    path('api/users/', include('users.urls')),
    path('api-auth/', include('rest_framework.urls')),
    path('api/rest-auth/', include('rest_auth.urls')),
    path('api/rest-auth/registration/', include('rest_auth.registration.urls')),
]
</code></pre>
<p>そして、アプリレベルのルーティング<code>users/urls.py</code>を設定します。</p>
<pre><code class="language-python line-numbers">from django.urls import path

from .views import UserList, UserDetail


urlpatterns = [
    path('&lt;str:pk&gt;/', UserDetail.as_view()),
    path('', UserList.as_view()),
]
</code></pre>
<p>全て設定して、<code>http://localhost:8000/api/users/</code>にアクセスするとユーザー一覧が見られます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/7uAexP0ZM3S1yA_pOh-wJI0M8fjLDqS3DuBrSRIHkyF_E_QmcNcXeQhv2umHxr-yfI7vN_0qBrpbI-556G8tlzEgAaPm6_HAW0z9vfLHpRXeU_MuJg9vb97sHNuQTQc835obW9fv8Q=s0" alt="" /></p>
<p>管理者で見ていますが、権限は一般ユーザー以上にしています。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/j2IoAHZ9nbRkEjTCqn-EVfgXo_b8MMI0A1vZ5nm_Hy7gqvqmVm1sU9XbqWdnkO2X2_DdWPZxBmaL2UjGZXJAbTe50kBPENUQQ1kCv7bXLUksOJor0-gzjXPMcZwE92nRBVqhRKtmmw=s0" alt="" /></p>
<p>記事と同じくIDを末尾に付けるとそのユーザーの詳細表示が出来ます。</p>
<h3>ViewSetsでViewを統合</h3>
<p>現在は、<code>User</code>と<code>Post</code>に対してそれぞれ<code>List</code>と<code>Detail</code>が存在しますが、これをViewSetsを使って統合します。</p>
<p><code>posts/views.py</code>は統合を行うと以下のような形になります。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions, viewsets

from .models import Post
from .permissions import IsAuthorOrReadOnly # 追加
from .serializers import PostSerializer


class PostViewSet(viewsets.ModelViewSet):
    permission_classes = (IsAuthorOrReadOnly,)
    queryset = Post.objects.all()
    serializer_class = PostSerializer
</code></pre>
<p>同じように<code>users/views.py</code>も統合します。</p>
<pre><code class="language-python line-numbers">from rest_framework import generics, permissions, viewsets

from .models import User
from .serializers import UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    permission_classes = (permissions.IsAuthenticated,)
    queryset = User.objects.all()
    serializer_class = UserSerializer
</code></pre>
<p>このままではエラーが出るので、次にルーティングを編集します。</p>
<h3>SimpleRouterでURLを統合</h3>
<p>ビューの次はURLの統合です。</p>
<p>リスト表示と詳細表示の2つがあった<code>posts/urls.py</code>をSimpleRouterを用いて統合します。</p>
<pre><code class="language-python line-numbers">from django.urls import path
from rest_framework.routers import SimpleRouter

from .views import PostViewSet

router = SimpleRouter()
router.register('', PostViewSet, basename='posts')

urlpatterns = router.urls
</code></pre>
<p>同じく<code>users/urls.py</code>も統合します。</p>
<pre><code class="language-python line-numbers">from django.urls import path
from rest_framework.routers import SimpleRouter

from .views import UserViewSet

router = SimpleRouter()
router.register('', UserViewSet, basename='users')

urlpatterns = router.urls
</code></pre>
<p>これまで見て来たリストや詳細表示が変わらず見られる事が確認出来ると思います。</p>
<h3>OpenAPIを使ってドキュメント表示</h3>
<p>OpenAPIを使うとDjango REST FrameworkのAPIスキーマを自動生成出来るようです。</p>
<pre><code class="language-bash line-numbers"># OpenAPIRendererに必要なPyYAMLをインストール
$ pip install pyyaml
</code></pre>
<p><code>blogress/urls.py</code>を編集して、プロジェクトレベルのルーティングにスキーマへのルーティングを追記します。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.urls import include, path
from rest_framework.schemas import get_schema_view # 追記
from django.views.generic import TemplateView # 追記

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/posts/', include('posts.urls')),
    path('api/users/', include('users.urls')),
    path('api-auth/', include('rest_framework.urls')),
    path('api/rest-auth/', include('rest_auth.urls')),
    path('api/rest-auth/registration/', include('rest_auth.registration.urls')),
    path('schema/', get_schema_view( # スキーマ表示の追加
        title="Blogress",
        description="API for all things …"
    ), name='openapi-schema'),
    path('docs/', TemplateView.as_view( # ドキュメント表示の追加
        template_name='swagger-ui.html',
        extra_context={'schema_url':'openapi-schema'}
    ), name='swagger-ui'),
]
</code></pre>
<p>加えて、ドキュメント表示では表示用のテンプレートファイルが必要になるので、まずはテンプレートディレクトリを<code>blogress/settings.py</code>で設定しましょう。</p>
<pre><code class="language-python line-numbers">TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['templates'], # リスト内に追記
        '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',
            ],
        },
    },
]
</code></pre>
<p>そしてこれに対応するように<code>templates/</code>ディレクトリを作成してください。</p>
<p>スキーマのドキュメント表示用のテンプレートはいくつか用意されているようですが、今回は<code>swagger-ui.html</code>を利用します。</p>
<p>なので、<code>templates/swagger-ui.html</code>を作成し、以下の内容を記述してください。</p>
<pre data-language=HTML><code class="language-markup line-numbers">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;Blogress API References&lt;/title&gt;
    &lt;meta charset="utf-8"/&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
    &lt;link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css" /&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id="swagger-ui"&gt;&lt;/div&gt;
    &lt;script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"&gt;&lt;/script&gt;
    &lt;script&gt;
    const ui = SwaggerUIBundle({
        url: "{% url schema_url %}",
        dom_id: '#swagger-ui',
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIBundle.SwaggerUIStandalonePreset
        ],
        layout: "BaseLayout"
      })
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>新しくディレクトリを手動作成するのはこのシリーズでは初めてなので、確認も兼ねてディレクトリツリーを図示します。</p>
<pre><code class="language-clean line-numbers">blogress/ (プロジェクトルート)
|-- blogress/
|-- posts/
|-- templates/ (テンプレートディレクトリ)
   |-- swagger-ui.html (テンプレートファイル)
|-- users/
|-- db.sqlite3
|-- manage.py
</code></pre>
<p>これで、<code>http://localhost:8000/schema/</code>や<code>http://localhost:8000/docs/</code>に繋ぐとそれぞれの表示形式でAPIスキーマを見る事が出来ます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/gbgJBzasXAA3asplKrtQwgjr4wrurQpPrNuv0Afa-qeipSVHUskIAuBflvTjhHd9MxHmXgCB8BmqKa8qdRQGOZbuWaEn6yL_8yC2l6a6XWLq-sJCCjOYYP1rnHFTsqNxf6ATh5sqkw=s0" alt="" /></p>
<p>こちらが生のスキーマです。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/IPbdHLIK2NGU2m2wyDBkamrV3iWjzjyAqH2bVqQ451cJakPkn-u_4XF90DbseRtgtkfU-ASasy8yAX6oPnyhASNsDG1w_UfowYzySf9vFCOV_GFYoGQhu0FSpUAbv6cvwoBWxrXH9A=s0" alt="" /></p>
<p>そしてこちらが<code>swagger-ui.html</code>で装飾表示されたスキーマです。</p>
<h2>まとめ</h2>
<p>Reactと連携させるためにはもう少し作業が必要ですが、バックエンド側のRESTful APIの基礎的な実装が終わりました。</p>
<p>ほとんどDjangoの機能で実現出来て正直驚きました。</p>
<p>ただ、ほぼ自動で行ってくれるので内部の理解が難しい部分もあるため、使いこなすには時間が掛かりそうです。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/__init__/items/f5a5a64a05541fcda713">Django REST Framework の使い方メモ</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://www.django-rest-framework.org/api-guide/schemas/">Schema</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://www.django-rest-framework.org/topics/documenting-your-api/">Documenting your API</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://www.django-rest-framework.org/community/3.10-announcement/">Django REST framework 3.10</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-react-django-blog2/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React＋Djangoでブログを作る1〈モデル定義編〉</title>
		<link>https://www.kthksgy.com/web/make-react-django-blog1/</link>
					<comments>https://www.kthksgy.com/web/make-react-django-blog1/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Mon, 27 Jan 2020 09:37:45 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=14</guid>

					<description><![CDATA[WordPressはブログに必要な機能が一から十まで全部用意されていて、自分は記事を書いてテンプレートを選ぶ十一の部分だけやれば良いというお手軽感があります。ブログのカスタマイズは用意されたプラグインを必要なだけ導入すれ&#8230;]]></description>
										<content:encoded><![CDATA[<p>WordPressはブログに必要な機能が一から十まで全部用意されていて、自分は記事を書いてテンプレートを選ぶ十一の部分だけやれば良いというお手軽感があります。ブログのカスタマイズは用意されたプラグインを必要なだけ導入すれば良いので、通常の利用では全く困る事が有りません。</p>
<p>しかしある程度触っていると、何処で何が行われているのかが分からないため、いざプラグインが存在しないような機能が欲しくなった時や、そもそもプラグインでは実現出来ないようなWordPressの根幹の部分が変更したくなった時に、モヤモヤする事が多くなりました。</p>
<p>プラグインで何とかなる場面は自分でプラグインを作れば良いのですが、そのような解決法ではプラグインを作る学習コストはともかく最終的にWordPressがプラグインでゴテゴテになってしまい、精神的・処理コスト的にあまり嬉しくはありません。</p>
<p>そもそも、プラグインではほとんどどうにもならないWordPressのデータ管理方法や記事の下書き等のシステム等色々な面で自分の好みとは違う点がある事に気付いたので、だったらいっその事自分の好みにあったブログシステムを自作してみようと思いました。</p>
<h2>作りたい物</h2>
<p>以下の機能を満たす自分用のブログシステムを作りたいと考えています。</p>
<ul>
<li>記事の表示</li>
<li>Markdownで記事が書ける</li>
<li>記事の執筆</li>
<li>ブログテンプレートをある程度簡単に差し替え</li>
<li>SEO対策としてXMLサイトマップ生成やOGPタグの設定機能</li>
<li>マルチユーザー</li>
</ul>
<h2>構成</h2>
<p>今回はウェブサイトの仕組みを学ぶ目的よりも、ブログシステムを作りたいという意思の方が強いため、一から四くらいまではフレームワークやライブラリに任せて、その先で目標のシステムを構築する事にします。</p>
<p>ウェブサイトについては完全に<strong>素人</strong>なので、自分が考えられる範囲で考えた以下の構成で構築を行います。</p>
<ul>
<li>Django + Django REST Framework + CORS</li>
<li>React + Axios</li>
</ul>
<p>簡単に説明すると、Djangoでバックエンドを実装して、Reactでフロントエンドを実装します。<br />
その時、Django側はREST FrameworkとCORSを導入する事で、React側はAxiosを導入する事で双方の連携が取れるようにします。</p>
<h3>Django : バックエンド</h3>
<p>Djangoは、RailsやLaravelと並ぶPythonの有名なウェブフレームワークの一つです。</p>
<h4>Django REST Framework</h4>
<p>本来のDjangoはバックエンドとしてサーバー機能と、フロントエンドとして実際に表示する画面を生成する機能の両方を備えた総合的なフレームワークですが、今回は<strong>REST Framework</strong>を用いる事で、Djangoにはデータの管理と配信に徹してもらう事にします。</p>
<h4>CORS</h4>
<p><strong>Cross-Origin Resource Sharing</strong>の略で、異なるドメイン間で動作するアプリケーションがリソースを共有するための仕組みだそうです。</p>
<p>今回はDjango(ポート8000番) + React(ポート3000番)で異なる2つのシステムが稼働しているため、これが必要になるようです。</p>
<h3>React : フロントエンド</h3>
<p>Reactは、Facebookによってオープンソースで開発されているJavaScriptのUI構築フレームワークです。</p>
<p>詳しい事は分かりませんが、良い感じのウェブ画面を作ってくれる物だと思っています。</p>
<p>今回は、Djangoから受け取ったデータを元にブログの画面を構成する役割を担ってもらいます。</p>
<h4>Axios</h4>
<p>ReactでREST APIへのリクエスト処理を実装するためのライブラリです。</p>
<p>これを使ってDjangoで用意したREST APIへアクセスし、必要なデータの取得や書き込みを行います。</p>
<h2>Djangoでバックエンドを作る</h2>
<p>早速作っていきます。</p>
<h3>Djangoのインストール</h3>
<p>まずはPythonにDjangoをインストールします。仮想環境等は好みで作成してください。</p>
<pre><code class="language-bash line-numbers"># バージョン確認
<span class="katex math inline">python -V
Python 3.7.4

# Djangoのインストール</span> pip install django djangorestframework django-cors-headers
</code></pre>
<h3>プロジェクトのセットアップ</h3>
<p>きちんとPATHが通っていればDjangoのコマンドが利用出来るはずですので、それを使ってDjangoのプロジェクトを立ち上げます。<br />
プロジェクト名は何でも良いですが、今回はWordPressに感化されたので<strong>blogress</strong>と名付けます。</p>
<pre><code class="language-bash line-numbers"># プロジェクトの作成
<span class="katex math inline">django-admin startproject blogress
# 作成されたディレクトリに移動</span> cd blogress
</code></pre>
<p>以下のような内部構造を持つディレクトリ<code>blogress</code>が生成されたと思います。<br />
以降は、作成されたプロジェクトルート<code>blogress/</code>内で話を進めます。</p>
<pre><code class="language-clean line-numbers">blogress/ (以降何も表記しなければここがルートディレクトリ)
|-- blogress/
    |-- 省略...
    |-- settings.py
|-- manage.py
</code></pre>
<p>Djangoのプロジェクトは、アプリと呼ばれる複数の機能から成り立っています。</p>
<h3>ユーザーモデルの変更</h3>
<p>デフォルトのDjangoでは、ユーザー名とパスワードでログインするユーザーモデルが定義されていますが、まずはこれをメールアドレスとパスワードでログインする仕様に変更します。</p>
<p>まず、カスタムユーザーのアプリを追加します。Djangoでは、それぞれ異なる機能を持った複数のアプリからプロジェクトが成り立っているため、何か特定の機能を追加する場合にはまずアプリを追加します。</p>
<pre><code class="line-numbers"># アプリの追加
$ python manage.py startapp users
</code></pre>
<p>追加すると、プロジェクトルートに<code>users/</code>というディレクトリが作成されたと思います。</p>
<pre><code class="language-clean line-numbers">blogress/
|-- blogress/
|-- users/ # 作成された
|-- manage.py
</code></pre>
<p>アプリを追加したので、まずは<code>blogress/settings.py</code>にアプリを登録しましょう。<br />
また、今から作る<code>User</code>というカスタムユーザーモデルを使用するための設定も記述します。</p>
<pre><code class="language-python line-numbers"># カスタムユーザーモデルの設定
AUTH_USER_MODEL = 'users.User' # 追記

INSTALLED_APPS = [
    # アプリを登録
    'users.apps.UsersConfig', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
</code></pre>
<p>アプリを登録したら、<code>users/models.py</code>にモデルを定義します。</p>
<pre><code class="language-python line-numbers">import uuid
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    """ユーザーマネージャー."""

    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        """Create and save a user with the given username, email, and
        password."""
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)

        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class User(AbstractBaseUser, PermissionsMixin):
    """カスタムユーザーモデル."""
    id = models.UUIDField(default=uuid.uuid4,
                            primary_key=True, editable=False)

    email = models.EmailField(_('email address'), unique=True)
    username = models.CharField(_('ニックネーム'), max_length=150, blank=True)

    is_staff = models.BooleanField(
        _('staff status'),
        default=False,
        help_text=_(
            'Designates whether the user can log into this admin site.'),
    )
    is_active = models.BooleanField(
        _('active'),
        default=True,
        help_text=_(
            'Designates whether this user should be treated as active. '
            'Unselect this instead of deleting accounts.'
        ),
    )
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')

    def get_full_name(self):
        full_name = '%s' % (self.username)
        return full_name.strip()

    def get_short_name(self):
        """Return the short name for the user."""
        return self.username

    def email_user(self, subject, message, from_email=None, **kwargs):
        """Send an email to this user."""
        send_mail(subject, message, from_email, [self.email], **kwargs)
</code></pre>
<p>Djangoの管理画面でもこのカスタムユーザーモデルを扱うために、<code>users/admin.py</code>を以下の通り記述します。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User


class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'


class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email',)


class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        (_('Personal info'), {'fields': ('username',)}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'username', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'username')
    ordering = ('email',)

admin.site.register(User, MyUserAdmin)
</code></pre>
<p>これらの実装によって、Djangoのユーザー及び管理画面でのユーザーが定義したカスタムユーザーモデルに置き換えられます。</p>
<h3>記事に関するアプリの追加</h3>
<p>カスタムユーザーモデルを設定したら、今度は記事の投稿や表示を行うアプリ<code>posts</code>を追加しましょう。</p>
<pre><code class="line-numbers"># アプリの追加
$ python manage.py startapp posts
</code></pre>
<p>アプリを追加したので、<code>blogress/settings.py</code>に登録しましょう。</p>
<pre><code class="language-python line-numbers">INSTALLED_APPS = [
    # アプリを登録
    'users.apps.UsersConfig',
    'posts.apps.PostsConfig', # 追記

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
</code></pre>
<p>次に記事のモデルを<code>posts/models.py</code>に実装します。一般的なブログ記事が持つような情報を含む以下のようなモデルを作成しました。</p>
<pre><code class="language-python line-numbers">import uuid
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()


class Post(models.Model):
    # 記事ID(PRIMARY KEY)
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    # 著者
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    # 記事タイトル
    title = models.CharField(max_length=64)
    # スラッグ(URLに使う記事識別子)
    slug = models.SlugField(unique=True)
    # サムネイルURL
    thumbnail = models.URLField(blank=True)
    # 記事本文
    body = models.TextField(blank=True)
    # 記事の作成日時と更新日時
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title
</code></pre>
<p>記事の管理を出来るように管理者へ記事の管理権限を登録します。<br />
<code>posts/admin.py</code>に以下を記述してください。</p>
<pre><code class="language-python line-numbers">from django.contrib import admin
from .models import Post

admin.site.register(Post)
</code></pre>
<p>ここまで実装したら、マイグレーションを行います。</p>
<pre><code class="language-bash line-numbers">$ python manage.py makemigrations
$ python manage.py migrate
</code></pre>
<h4>データベースマイグレーション</h4>
<p>マイグレーションとは、データベースの定義を自動的に管理する機能です。</p>
<p>Djangoでは新たなアプリを登録した際や、アプリの持つデータ構造が変わった際にこの操作が必要になります。</p>
<p>マイグレーションの情報が記載されたファイルを用意して順番に適用するので、そのファイルをもとにしたロールバック(変更を元に戻す)機能も付属しているみたいですが、今の所はよく分かっていません。</p>
<p>実際にマイグレーションを行う場合は、以下のコマンドを実行します。</p>
<pre><code class="language-bash line-numbers"># マイグレーションの情報を生成
<span class="katex math inline">python manage.py makemigrations
# マイグレーションの実行(IDは入力しなくても良い)</span> python manage.py migrate マイグレーションID
</code></pre>
<h3>管理者の作成</h3>
<p>Djangoの管理パネルにログインするための管理者を作成します。<br />
デフォルトではユーザー名、メールアドレス、パスワードを聞かれますが、カスタムユーザーモデルを適用しているのでメールアドレスとパスワードのみ登録します。</p>
<pre><code class="language-bash line-numbers">$ python manage.py createsuperuser
Email address: test@mail.com
Password:
Password (again):
# パスワードが単純過ぎたりすると聞かれる
# Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.
</code></pre>
<p>確認のため、管理コンソールへアクセスしてみましょう。</p>
<pre><code class="line-numbers"># Djangoサーバーの起動
$ python manage.py runserver
# http://localhost:8000/admin/ へアクセス
</code></pre>
<p>ちなみに、Djangoではサーバーを起動した状態でアプリ等のスクリプトに変更が有ると自動的に再読み込みを行うので、開発中にいちいちサーバーを落とす必要はありません。</p>
<h4>Postsのデータの作成</h4>
<p>管理画面からPostsのデータを作成してみます。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/boPySwbMjVylYQ9-nUpKBU9SL5aDQNYM671CcItwMdHcGdalaNSB3QEXfSgcxslnUhnp7mNyHRanm8nrnSYvYghxPdNrBIjQkt94T1Zcv5vW65spFTZcSTSiATVGXqqmLMlFFna1wA=s0" alt="" /></p>
<p>管理画面にまずログインします。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/UvDZbs6_6TmlqKLmkLjafUH5_3GMSEXwf4JpFsCyw9RA1xRBM79noCrnm7F8JDEk2D1azlGMXHFr1Dvc2g4tzNRCOatMjyQUsk3-Z_6dLtpyWr-vUEUMNRyp71n65bdSrVRX27BbTA=s0" alt="" /></p>
<p>Postsの[+ Add]から、タイトルや本文のデータを入力して保存しましょう。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/ltS94NxMeF5ioeLvHF2G6W_XPIJOFuZJT7bTdopGOoxredh114MpBxBoZ1ThCs_a9wqu9k1HNm3bJUi9vUluSoCa7-SmPxyOfz2Vd7xZAlbq8ol7dgaPkUIZwaTA5eHIba7bC81Slg=s0" alt="" /></p>
<p>適当に項目を埋めてSAVEを押します。太字になっている項目は必須です。今回は<code>thumbnail</code>のみ空欄を許可しているので、それ以外の部分を埋める必要があります。</p>
<p><img decoding="async" src="https://lh3.googleusercontent.com/yvuXtf3p5lxj2xxS4kt23zdb6v_ZIo01LL7TrTFIaLcLeMpAPAI_w1Ojxsh1B339425nvhtdh61n8KS00KFoOIIB6Nsr19Fz3nLu2KrGg3w6BKXuutkMl8MwqeI2ee595KFT-M4yMQ=s0" alt="" /></p>
<p>ひとまず3つほど記事を作成しました。</p>
<h3>テストコードの記述</h3>
<p>せっかく記事のモデルを作ったので、Djangoのテスト機能を使ってテストを行いましょう。</p>
<p>適当なユーザーがタイトル等諸々の情報を入力して投稿出来るかというテストです。</p>
<pre><code class="language-python line-numbers">from django.test import TestCase
from django.contrib.auth import get_user_model

from .models import Post
User = get_user_model()

class BlogTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        # Create a User
        testuser1 = User.objects.create_user(
            email='testuser1@example.com',
            password='abc123!'
        )
        testuser1.save()

        # Create a blog post
        test_post = Post.objects.create(
            author=testuser1,
            title='Blog title',
            slug='test-post1',
            thumbnail='https://test.com/dog.jpg',
            body='Body content...'
        )
        test_post.save()

    def test_blog_content(self):
        post = Post.objects.get()
        expected_author = f'{post.author}'
        expected_title = f'{post.title}'
        expected_slug = f'{post.slug}'
        expected_thumbnail = f'{post.thumbnail}'
        expected_body = f'{post.body}'
        self.assertEquals(expected_author, 'testuser1@example.com')
        self.assertEquals(expected_title, 'Blog title')
        self.assertEquals(expected_slug, 'test-post1')
        self.assertEquals(expected_thumbnail, 'https://test.com/dog.jpg')
        self.assertEquals(expected_body, 'Body content...')
</code></pre>
<p>書き終えたらCtrl + Cでいったんサーバーを終了して以下のコマンドで実行します。</p>
<pre><code class="language-bash line-numbers">$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.117s

OK
Destroying test database for alias 'default'...
</code></pre>
<p>どうやら上手くテストが通ったようです。</p>
<h2>まとめ</h2>
<p>Djangoのインストールから、ブログシステムの基本となる記事の管理の基礎を作成しました。</p>
<p>Djangoはフルスタックなフレームワークなので覚える事が多いですが、徐々に慣れていきたいです。</p>
<h2>参考</h2>
<ul>
<li><a class="wp-editor-md-post-content-link" href="https://qiita.com/__init__/items/f5a5a64a05541fcda713">Django REST Framework の使い方メモ</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://docs.djangoproject.com/en/3.0/topics/auth/customizing/#substituting-a-custom-user-model">Customizing authentication in Django</a></li>
<li><a class="wp-editor-md-post-content-link" href="https://hombre-nuevo.com/python/python0052/">DjangoのUserモデルカスタマイズ（UUIDを使う）</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/make-react-django-blog1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Googleフォトの画像をブログに利用する</title>
		<link>https://www.kthksgy.com/web/google-photo-for-website/</link>
					<comments>https://www.kthksgy.com/web/google-photo-for-website/#respond</comments>
		
		<dc:creator><![CDATA[kthksgy]]></dc:creator>
		<pubDate>Sun, 12 Jan 2020 09:35:50 +0000</pubDate>
				<category><![CDATA[Webサイト]]></category>
		<guid isPermaLink="false">https://www.kthksgy.com/?p=13</guid>

					<description><![CDATA[写真ブログや画像を多用するウェブサイトを運用しているとどうしても避けては通れない壁があります。 何でしょうか、はい。容量です。 ウェブサイトの画像の容量の問題を解決するために、既にimgurやその他の画像クラウドストレー&#8230;]]></description>
										<content:encoded><![CDATA[<p>写真ブログや画像を多用するウェブサイトを運用しているとどうしても避けては通れない壁があります。</p>
<p>何でしょうか、はい。<strong>容量</strong>です。</p>
<p>ウェブサイトの画像の容量の問題を解決するために、既に<a class="wp-editor-md-post-content-link" href="https://imgur.com/">imgur</a>やその他の画像クラウドストレージサービスに画像を置いている方も居ると思います。</p>
<p><a class="wp-editor-md-post-content-link" href="https://www.google.com/intl/ja/photos/about/">Google フォト</a>も他と同じく画像用クラウドストレージサービスの1つなのですが、Google フォトにはURLを変更する事で自動でサイズ変更やサムネイル作成のような事が出来て、他のサービスよりも使い勝手が良いです。</p>
<p>そこで、今回はGoogle フォトを使ったブログやウェブサイトの容量問題を解決する方法をご紹介します。</p>
<h1>環境</h1>
<p>以下の環境を想定して解説しますが、他のブラウザでも同じように出来るはずです。</p>
<ul>
<li>Google Chrome</li>
</ul>
<h1>Google フォト</h1>
<p>Google フォトとは、Googleが運営する画像に特化した無料のクラウドストレージサービスです。Androidユーザーには割とお馴染みの機能で、最近のAndroidは特に設定しなければ自動でGoogle フォトに写真がバックアップされます。</p>
<blockquote><p>
  <a class="wp-editor-md-post-content-link" href="https://www.google.com/intl/ja/photos/about/">Google フォト</a> 思い出をすべて無料で保存でき、ファイルは自動的に整理されます。
</p></blockquote>
<p>利用にはGoogleアカウントが必要で、Google Driveの容量を使って画像を保存します。</p>
<h2>高画質モードで容量無制限</h2>
<p>他の画像用クラウドストレージサービスと1つ異なる点が有って、Google フォトではなんと「高画質モード」では保存容量が無制限なのです。</p>
<p>これは、画像を保存する際にGoogle側の指定した形式に画像を変換する代わりに、保存容量をカウントしないというモードです。最近、「高画質モード」の設定が更に高画質化したため、今ではほとんどオリジナルの画像と見分けのつかない画質で無制限に保存出来るようになりました。</p>
<p>ちなみに、「元のサイズモード」では通常通りGoogle Driveの容量を消費します。</p>
<h1>Google フォトで容量問題を解決しよう</h1>
<p>Google フォトを開いたら、まず左上の<code>{≡}</code>メニューから<code>設定</code>を開きます。</p>
<p><code>設定</code>の上の方にある<code>写真と動画のアップロード サイズ</code>から<code>高画質</code>を選べば、次回からアップロードされる画像が高画質モード(=容量無制限)で保存されます。</p>
<p>後は、右上の<code>{写真を追加}</code>や<code>ドラッグアンドドロップ</code>で画像をアップロードして、アップロードした画像を<code>{左クリック}</code>して拡大します。</p>
<p>拡大したら、同じく右上にある<code>{共有}</code>ボタンから、共有用のリンクを取得します。</p>
<p>共有用のリンクが取得出来たら、<strong>その画像を所有するGoogleアカウントにログインしていないブラウザ</strong>からリンクを開き、先ほどと同じように画像を<code>{左クリック}</code>して拡大したら、<code>{右クリック}</code>して<code>画像アドレスをコピー</code>しましょう。</p>
<p>コピーされた画像アドレスは以下のようになります。この画像アドレスをブログに挿入すると、画像を表示する事が可能です。</p>
<p>また、Google フォトにはURLを変更する事で簡単に画像のリサイズを行ってくれる機能があるため、以降で説明します。</p>
<pre><code class="language-plaintext line-numbers">https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=w958-h639-no
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=s400-no" alt="" /> サンプル画像: <a class="wp-editor-md-post-content-link" href="https://www.pakutaso.com/20191212357post-24985.html">豚の貯金箱に入らなかった万札のフリー画像（写真）</a></p>
<p>(<strong>その画像を所有するGoogleアカウントにログインしていないブラウザ</strong>は、Google Chromeやその他のブラウザに搭載されているシークレットウィンドウで十分です。これを行わないと、コピーした画像アドレスが固定の物では無いため、ブログに貼っても後々見れなくなります。)</p>
<h1>画像アドレスの編集</h1>
<p>Google フォトでは画像アドレスの末尾のパラメータを編集する事で簡単にリサイズやサムネイル作成が行えます。</p>
<pre><code class="language-plaintext line-numbers"># =の末尾を編集すると様々な画像が生成出来る
https://lh3.googleusercontent.com/k...g=w958-h639-no
</code></pre>
<h2>画像をリサイズする</h2>
<p>画像をリサイズする方法は大きく分けて3つあります。</p>
<h3>縦幅か横幅の大きい方を基準にリサイズ</h3>
<p>末尾のパラメータを<code>=sN</code>(Nは整数)とする事で、縦幅と横幅の大きい方の幅をNピクセルとして画像を取得します。</p>
<p>サンプル画像は横幅の方が大きいので、<code>=s200</code>とすると横幅を200ピクセルとして縦幅が自動調整されます。</p>
<pre><code class="language-plaintext line-numbers">https://lh3.googleusercontent.com/k...g=s200
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=s200" alt="=s200" /></p>
<h3>縦幅か横幅のどちらかを指定してリサイズ</h3>
<p>末尾のパラメータを<code>=wN</code>(横幅, Nは整数)か<code>=hN</code>(縦幅, Nは整数)とすると、指定した方の長さをNピクセルとして画像を取得します。</p>
<p>サンプル画像は横幅の方が大きいので、<code>=s200</code>は大きい方の長さを指定するため、<code>=w200</code>と同じ結果になります。</p>
<pre><code class="language-plaintext line-numbers">https://lh3.googleusercontent.com/k...g=w200
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=w200" alt="=w200" /></p>
<pre><code class="language-plaintext line-numbers">https://lh3.googleusercontent.com/k...g=h200
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=h200" alt="=h200" /></p>
<h3>縦幅と横幅を指定してリサイズ</h3>
<p>末尾のパラメータを<code>=wN-hM</code>と指定する事で、縦横のどちらも指定された長さの上限を超えないように画像を取得します。</p>
<p>結果は<code>=wN</code>と<code>=hM</code>を単体で指定した時のどちらかとなりますが、縦幅と横幅はどちらも指定された長さを超えないような画像が取得されます。</p>
<pre><code class="language-plaintext line-numbers"># =w200とした時に縦幅hが50を超えるので、=h50の画像が取得される
https://lh3.googleusercontent.com/k...g=w200-h50
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=w200-h50" alt="=w200-h50" /></p>
<pre><code class="language-plaintext line-numbers"># =h50とした時に横幅wが50を超えるので、=w50の画像が取得される
https://lh3.googleusercontent.com/k...g=w50-h50
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=w50-h50" alt="=w50-h50" /></p>
<h2>切り出し</h2>
<p>先ほどの末尾のパラメータに<code>-c</code>を追加する事で、画像を正方形に切り出します。</p>
<pre><code class="language-plaintext line-numbers"># 300x300の画像が取得される
https://lh3.googleusercontent.com/k...g=s200-c
# 同じ結果が得られます
https://lh3.googleusercontent.com/k...g=w200-c
https://lh3.googleusercontent.com/k...g=h200-c
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=s200-c" alt="=s200-c" /></p>
<h3>サイズを指定して切り出し</h3>
<p>末尾のパラメータを<code>=wN-hM-c</code>と指定する事で、指定されたサイズの画像を切り出します。</p>
<pre><code class="language-plaintext line-numbers"># 200x50の画像が取得される
https://lh3.googleusercontent.com/k...g=w200-h50-c
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=w200-h50-c" alt="=w200-h50-c" /></p>
<h2>サムネイル作成</h2>
<p>末尾のパラメータを<code>-c</code>ではなく<code>-p</code>とすると、画像中の注目領域が自動的に選択されて、その周辺が切り抜かれます。</p>
<pre><code class="language-plaintext line-numbers"># pを指定した場合
https://lh3.googleusercontent.com/k...g=s200-p
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=s200-p" alt="=s200-p" /></p>
<pre><code class="language-plaintext line-numbers"># cを指定した場合
https://lh3.googleusercontent.com/k...g=s200-c
</code></pre>
<p><img decoding="async" src="https://lh3.googleusercontent.com/k9Zdgzk_LX8vB4XmGX8f_n_a3vtDPzUpmE6eZ2TvoTVoPeXJ0H0SBM5q7yiIedi5r51uPnUEvEX44VdxeI8anbxFEOA4IQ-T_SF1uNZkR4PCYrP8dZZum8zG0lfvI3k2JXuhl9qf2g=s200-c" alt="=s200-c" /></p>
<h2>フルサイズで表示</h2>
<p><code>=s0</code>、<code>=w0</code>、<code>=h0</code>のように、長さにゼロを指定すると保存されている最大の長さに自動的に調整されます。</p>
<h2>不明なパラメータ</h2>
<p>一応、<code>-no</code>というパラメータもあるようですが、効果が良く分かりません。画像が少し大きく表示される。</p>
<h2>まとめ</h2>
<p>以下が今回紹介したパラメータのまとめです。</p>
<table>
<thead>
<tr>
<th align="center">パラメータ</th>
<th align="center">効果</th>
<th align="center">例</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">=sN</td>
<td align="center">長辺をN画素としてリサイズ</td>
<td align="center">=s200</td>
</tr>
<tr>
<td align="center">=wN</td>
<td align="center">横辺をN画素としてリサイズ</td>
<td align="center">=w200</td>
</tr>
<tr>
<td align="center">=hN</td>
<td align="center">縦辺をN画素としてリサイズ</td>
<td align="center">=h200</td>
</tr>
<tr>
<td align="center">=wN-hM</td>
<td align="center">縦辺と横辺がそれぞれN,M画素を超えないようリサイズ</td>
<td align="center">=w200-h100</td>
</tr>
<tr>
<td align="center">=sN-c</td>
<td align="center">長辺をN画素としてもう片方を切り出し</td>
<td align="center">=s200-c</td>
</tr>
<tr>
<td align="center">=wN-hM-c</td>
<td align="center">サイズを指定して切り出し</td>
<td align="center">=w200-h100-c</td>
</tr>
<tr>
<td align="center">=sN-p</td>
<td align="center">注目領域を中心に切り出し</td>
<td align="center">=s200-p</td>
</tr>
<tr>
<td align="center">=wN-hM-p</td>
<td align="center">注目領域を中心にサイズを指定して切り出し</td>
<td align="center">=w200-h100-p</td>
</tr>
</tbody>
</table>
<p>Google フォトを使いこなして、容量制限とは無縁のウェブページ作成を。</p>
<h4>参考文献</h4>
<p><a class="wp-editor-md-post-content-link" href="https://sites.google.com/site/picasaresources/Home/Picasa-FAQ/google-photos-1/how-to/how-to-get-a-direct-link-to-an-image">How to get a direct link to an image (of a specific size)</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.kthksgy.com/web/google-photo-for-website/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
