LoginSignup
2
1

CentOSが終わるのでUbuntu22.04に移行する。Python3.11とDjango4とMySQL8のセットアップメモ(pure python版)

Last updated at Posted at 2021-08-29

はじめに

Ubuntu 22.04LTS

インストール

LTS版を入れる
ユーザー名が ubuntu になっていることに注意(最初はrootにパスワードが設定されていなくてrootが使えないようだ。そしてできるだけrootで入らず sudo -s でroot化するのが基本だそうだ)
image.png
スタートアップスクリプトはシンプルに
image.png
パケットフィルタの設定でポートの開閉をいじる
image.png

スワップをちょっと(今回は256MBで)作らないとなぜかMySQLがインストールできなくなるので注意。さくらのVPS限定のエラーかな、、時間溶かしたわ。。。
image.png

ん?まてよ??もしかしていままでスーパーギリギリで動いてたってこと??
image.png

image.png

image.png

image.png

Teraterm

※Linuxの記号の意味

Linux初心者は、コンソール上の「$」とか「#」がよくわかんなかったりする

記号 意味
$ 一般ユーザ権限で操作中
# root権限権限で操作中

ログインチェック

まだ portは22
image.png
image.png
image.png

OK!(一般ユーザでログインした)
image.png

スーパーユーザーになる

最初の設定
$ sudo -s
  [sudo] password for ubuntu:
# exit

公開鍵でログインできるようにする

最初の設定
作った公開鍵を置く(teratermにドラッグアンドドロップでOK)
$ pwd
  /home/ubuntu
$ ls
  id_rsa_henojiya.pub

許可する鍵としてさっきの公開鍵を登録する(先頭にドットがつくのは隠しフォルダ)
$ mkdir ~/.ssh
$ chmod 700 ~/.ssh
$ mv id_rsa_henojiya.pub ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

ターミナルからログインできました!
image.png

※SCPを使う場合(※慣れてから)

console
C:\Users\yoshi\.ssh> scp .\id_rsa_henojiya.pub ubuntu@153.126.200.229:/home/ubuntu
  ubuntu@153.126.200.229's password:

※sshログインで怒られたら

クライアントPC(今回はWin)からknown_hosts C:\Users\yoshi\.ssh\known_hosts を削除。サーバーのOS入れ直したんデショ?

console
$ ssh example.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Apache2

インストール

console
$ sudo -s
# cd ~
# apt -y install apache2 apache2-dev
console(確認)
# systemctl status apache2

設定

console
# vi /etc/apache2/conf-enabled/security.conf
security.conf
:set number
security.conf(25行目)サーバーの情報(バージョン、OSなど)を表示しないように
- ServerTokens OS
+ ServerTokens Prod
console
# vi /etc/apache2/mods-enabled/dir.conf
dir.conf(2行目)ディレクトリ名のみでアクセスできるファイル名を設定
- DirectoryIndex index.html index.cgi index.pl index.php index.xhtml index.htm
+ DirectoryIndex index.html
console
# vi /etc/apache2/sites-enabled/000-default.conf
apache2.conf(9行目)サーバー名追記
- #ServerName www.example.com
+ ServerName www.henojiya.net
console
# systemctl restart apache2

確認

http://www.henojiya.net でつながった!
image.png
image.png

バーチャルホスト

いったんパス もとの記事

console
# vi /etc/apache2/sites-available/virtual.host.conf
virtual.host.conf(新規)
<VirtualHost *:80>
    ServerName www.henojiya.net
    DocumentRoot /var/www/html
</VirtualHost>

自分メモ

<VirtualHost *:80>
    ServerName www.henojiya.net
    DocumentRoot /var/www/html/portfolio
</VirtualHost>
<VirtualHost *:80>
    ServerName app.henojiya.net
    DocumentRoot /var/www/html/soil_analysis
</VirtualHost>
# a2ensite virtual.host
# systemctl restart apache2

ネームサーバーを設定

いったんパス もとの記事

https

ポートをあける

ubuntuの443ポートを開け、ファイアウォールを起動する

# ufw allow in "Apache Full"
# ufw allow in "OpenSSH"
# ufw enable
  Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
# ufw status
  Status: active
  To                         Action      From
  --                         ------      ----
  Apache Full                ALLOW       Anywhere
  OpenSSH                    ALLOW       Anywhere
  Apache Full (v6)           ALLOW       Anywhere (v6)
  OpenSSH (v6)               ALLOW       Anywhere (v6)

サイト設定を有効化する

console
# a2ensite default-ssl
  Enabling site default-ssl. // 設定を読み込む
# a2enmod ssl
  Module setenvif already enabled // apache に SSL モジュールを読み込む
# systemctl restart apache2 // Apache2 を再起動

SSL

Let’s Encryptについて

  • Let’s Encrypt は、できるだけ多くの人がフェアにサービスを利用できるように、レート制限を設けています
  • もしあなたが Let’s Encrypt クライアントの開発やテストを活発に行なっている場合には、本番 API を使用する代わりに、私たちが用意したステージング環境を利用するようにしてください
  • 主なレート制限としては、登録ドメインごとの証明書数 (1週間に50個まで) があります
  • 登録ドメインとは、一般に言うと、あなたがドメイン名レジストラから購入したドメインの一部のことです。たとえば、www.example.com の場合、登録ドメインは example.com です。new.blog.example.co.uk の場合、登録ドメインは example.co.uk です。
  • 証明書ごとのドメイン名はできるだけ少ない方がよいです。

certbotのインストール

# apt -y install certbot python3-certbot-apache

証明書を取得

やりなおすときは証明書を削除してから
# certbot delete --cert-name henojiya.net
# certbot certificates
  No certs found.

dry-runで練習する
# cd ~
# certbot certonly --apache --dry-run
  IMPORTANT NOTES:
   - The dry run was successful.
henojiya.netは取得したドメイン(サブドメインwwwまで厳密に照合されます)
# cd ~
# certbot certonly --apache
  Enter email address: your_cool_email@gmail.com
  (A)gree/(C)ancel: A
  (Y)es/(N)o: Y
  blank to select all options shown (Enter 'c' to cancel): 1

確認

console
# certbot renew

FQDNをメモ

/etc/letsencrypt/live/henojiya.net をメモする。cert.pem privkey.pem chain.pem も覚えとく

console
# ls /etc/letsencrypt/live/
  README  www.henojiya.net
# ls /etc/letsencrypt/live/www.henojiya.net
  README  cert.pem  chain.pem  fullchain.pem  privkey.pem
# openssl x509 -in /etc/letsencrypt/live/www.henojiya.net/fullchain.pem -noout -dates
  notBefore=Aug 29 03:05:54 2021 GMT
  notAfter=Nov 27 03:05:53 2021 GMT

エディタのデフォルトをviに

console(使用エディタをワンショットでviに設定)
export EDITOR=vi

# vi /etc/environment

/etc/environment
VISUAL=/usr/bin/vim
EDITOR=/usr/bin/vim

保存してターミナルに入り直したら恒久的にviになった

https

console
# vi /etc/apache2/sites-available/default-ssl.conf
32,33行目:取得した証明書に変更
- SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
+ SSLCertificateFile /etc/letsencrypt/live/www.henojiya.net/cert.pem
- SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
+ SSLCertificateKeyFile /etc/letsencrypt/live/www.henojiya.net/privkey.pem
42行目:
- #SSLCertificateChainFile /etc/apache2/ssl.crt/server-ca.crt
+ SSLCertificateChainFile /etc/letsencrypt/live/www.henojiya.net/chain.pem
# systemctl restart apache2 // Apache2 を再起動

確認

https://henojiya.net/ でつながった!

image.png
image.png

※httpsの設定をしたらApacheが止まる??

たくさんやり直したでしょ m9(^Д^)プギャー

発行済の証明書が更新 (または複製) の対象とみなされるのは、全く同じホスト名の集合が指定されているときです (大文字・小文字、順序は区別しない)。たとえば、[www.example.com, example.com] というドメイン名に対する証明書をリクエストした場合、同じ週に [www.example.com, example.com] に対する証明書を重複して発行できるのは、追加で 4 つまでです。
レート制限に引っかかった場合、制限を一時的にリセットする方法はありません。レート制限が解消されるまで1週間後まで待つ必要があります。

参考)レート制限がかかると、以下のエラーメッセージが出るので1日待ってから行う。

Obtaining a new certificate
An unexpected error occurred:
There were too many requests of a given type :: Error creating new order :: too many certificates already issued for exact set of domains: yoursite.com: see https://letsencrypt.org/docs/rate-limits/
Please see the logfiles in /var/log/letsencrypt for more details.
 
 
IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.

定例更新化

console
cd /root
vi certbot.sh
certbot.sh
# /bin/sh
/usr/local/bin/certbot-auto renew
today=$(date "+%Y/%m/%d %H:%M:%S")
echo ${today} certbot-auto >> result.log
console(使用エディタをワンショットでviに設定)
export EDITOR=vi

# vi /etc/environment

/etc/environment
VISUAL=/usr/bin/vim
EDITOR=/usr/bin/vim

保存してターミナルに入り直したら恒久的にviになった

console
chmod 755 certbot.sh
crontab -e
crontab
0 0 1 * * /root/certbot.sh
console
crontab -l

MySQL8

mariadbの削除

console
# apt purge mariadb-* mysql-*

インストール

console
# apt -y install mysql-server-8.0
# mysql --version
  mysql  Ver 8.0.26-0ubuntu0.22.04.2 for Linux on x86_64 ((Ubuntu))

# service mysql status
  Active: active (running)

初期設定

console
# mysql_secure_installation
console
# パスワード品質チェックを有効にするか否か
Press y|Y for Yes, any other key for No: y
# パスワード品質チェックを有効にした場合は強度を選択
Please enter 0 = LOW, 1 = MEDIUM and 2 = STRONG: 0
# 匿名ユーザーを削除するか否か
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
# テストデータベースを削除するか否か
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
# 特権情報をリロードするか否か
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y

All done!

確認

console
# mysql -u root -p
  Welcome to the MySQL monitor.

UTF8mb4がデフォルト文字コードみたいなのでそのままでよい
mysql> status
       Server characterset:    utf8mb4
       Db     characterset:    utf8mb4
       Client characterset:    utf8mb4
       Conn.  characterset:    utf8mb4

データベースを作成

console
mysql> CREATE DATABASE portfolio_db DEFAULT CHARACTER SET utf8mb4;
       Query OK, 1 row affected (0.01 sec)
mysql> show databases;

ユーザを作成

% の権限にするとWindowsからのリモートログインができるようになる。本来はこの部分を localhost にして、セキュリティを高める。

pythonユーザを作成
mysql> CREATE USER 'python'@'%' IDENTIFIED BY 'python123';
pythonユーザには「portfolio_db」という名前のデータベースに9種の権限を与える
mysql> grant CREATE, DROP, SELECT, UPDATE, INSERT, DELETE, ALTER, REFERENCES, INDEX on portfolio_db.* to 'python'@'%';

特にローカルパソコンで手順をなぞるときは 'python'@'%' でユーザ作ったのに 'python'@'localhost' で権限を与えようとしてハマることがある(最初の1回しかやらないからパソコン買い替え時にハマった)。'python'@'%' でユーザ作ったら'python'@'%' で権限を与える。まぁローカルパソコンなら'python'@'localhost' でユーザ作ったらええか

console
mysql> exit

DBeaver

最近MySQLWorkbenchよりDBeaver好きなのよ。FK逆追いできるから。
かんたんにできるはず(失敗するとしたらportかぶり起こしてる)
MySQLのPORTを変える理由

SSHタブの設定

image.png

入力箇所 入力値
Host/IP 153.126.200.229
Port 22
User Name ubuntu
Authentication Method Public Key
Private Key (VPSでのログイン時に指定する rsa 秘密鍵)
Passphrase (ubuntu ユーザーのパスワード)

Test tunnel configuration を押して、サーバにつながったことを確認する
image.png

一般タブの設定

image.png

入力箇所 入力値
Server Host 127.0.0.1
Port 3306
Database portfolio_db
ユーザー名 python
パスワード (MySQLのrootユーザーのパスワード)

テスト接続を押して、サーバにつながったことを確認する
image.png

※Public Key Retrieval is not allowedのエラーが出力される

DBeaverからローカルのMySQLに接続できない問題への対処法

サンプルのhtmlを作ってみる

デフォルトの「ドキュメントルート」が /var/www/html/ なんだってさ。httpd.conf を探すとあるよ。

console
# vi /var/www/html/index.html

あ、Ubuntuはあのスタートページが(CentOSと違って)ここにあるのね。

index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1
-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

Python

起動確認

いままでは python ってコマンドでバージョンとか見てたけど python3 ってバージョン付きコマンドでなれたほうがよさそう

python 3.11

console
# apt update && apt upgrade
# apt install wget build-essential libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev zlib1g-dev
# add-apt-repository ppa:deadsnakes/ppa
# apt install Python3.11 python3.11-dev

venv

インストール

console
# apt -y install python3.11-venv

仮想環境の作成

console
# cd /var/www/html
# python3.11 -m venv venv (※ここに venv フォルダができて3.11がinstallされる)

activate

console
# source /var/www/html/venv/bin/activate
# python -V
  Python 3.11.7

deactivate

console
# deactivate

Git

バージョン確認

そのままでいいと思うけどね

console
$ git --version
  git version 2.34.1

公開鍵の作成

rootで ~/.ssh のフォルダはない。詳しく調べてないけど root で ssh ログインはするなっていう理解にしておこう
image.png

console
$ cd ~/.ssh
$ ls
  authorized_keys known_hosts
$ git config --global user.name "yoshi"
$ git config --global user.email "your_cool_email@gmail.com"
$ ssh-keygen -t ed25519 -C "my-email@gmail.com"
$ ls
  authorized_keys  id_ed25519  id_ed25519.pub  known_hosts

公開鍵をgitHubにアップ

console
$ vi id_ed25519.pub
id_rsa.pub(この公開鍵をコピーしてgithubのSSHKeysに貼り付ける
ssh-rsa AAAAB3N ...

image.png

確認

このコマンドを打つことで認証が済むのでこれをやらないと push できない。

$ ssh -T git@github.com
  Warning: Permanently added the RSA host key for IP address '13.114.40.48' to the list of known hosts.
  Hi duri0214! You've successfully authenticated, but GitHub does not provide shell access.

Django4

※Cloneから始めるときの前提になっている。新規は下の方に書いた

Cloneする

console
# chown -R ubuntu:ubuntu /var/www/html
# exit
console
$ cd /var/www/html
$ git clone git@github.com:duri0214/portfolio.git
$ service apache2 restart
$ cd portfolio
$ sudo apt-get install libmysqlclient-dev
$ source /var/www/html/venv/bin/activate
$ pip install -r requirements.txt

自分メモ
.env を FTP で移すのを忘れずに

※Cloneできないときはこれをチェック

  • ls ~/.ssh に github 登録したカギがあるか(rootに .ssh フォルダはない。 $だとある)

  • gitに公開鍵を登録していない 対git用公開鍵の作成

mod_wsgi

インストール

apache2-dev が入ってないとだめみたいね。でもそれでも赤字が出てくるのはきになったが、一応successのようだ

console
$ source /var/www/html/venv/bin/activate
$ pip install mod_wsgi
  Requirement already satisfied: mod_wsgi in /var/www/html/venv/lib/python3.11/site-packages (5.0.0)

LoadModule

000-default.conf に書き込む

console
# find / -name mod_wsgi*.so
  /var/www/html/venv/lib/python3.11/site-packages/mod_wsgi/server/mod_wsgi-py311.cpython-311-x86_64-linux-gnu.so

python-home

000-default.conf に書き込む

console
# find /var/www/html -name '*venv*'
  /var/www/html/venv

httpd.conf

console
# vi /etc/apache2/sites-available/000-default.conf
000-default.conf
# VirtualHostは変更しません
<VirtualHost *:80>
・・・省略・・・
</VirtualHost>

# 以下に書き込む
LoadModule wsgi_module /var/www/html/venv/lib/python3.11/site-packages/mod_wsgi/server/mod_wsgi-py311.cpython-311-x86_64-linux-gnu.so
WSGIScriptAlias / /var/www/html/portfolio/config/wsgi.py
WSGIDaemonProcess wsgi_app python-home=/var/www/html/venv python-path=/var/www/html/portfolio
WSGIProcessGroup wsgi_app
WSGISocketPrefix /var/run/wsgi
WSGIApplicationGroup %{GLOBAL}

# css, javascript etc
Alias /static/ /var/www/html/portfolio/static/
<Directory /var/www/html/portfolio/static>
  Require all granted
</Directory>
# service apache2 restart

LoadModule: mod_wsgiの本体ファイルの位置。Apacheがwsgiを認識するために必要
WSGIScriptAlias: 1つめの引数のURLでアクセスされたら、2つめの引数のwsgiスクリプトに移動する
WSGIDaemonProcess: Linuxではデーモン(=サービス)として動かすのが推奨されている
WSGIProcessGroup: 「サービス」に名前をつける
WSGISocketPrefix: 「※Socketの問題」を参照

※numpy: Interpreter change detected

こいつにトドメをさしたい。
image.png

どうもこの記事にたどり着いた。
仮説

  • numpyはインタープリタ(実行環境)の変化を許容しない
  • wsgiはバーチャルホストの違いでインタープリタを分ける仕組みがある

console
# vi /etc/apache2/sites-enabled/000-default.conf
000-default.conf
LoadModule wsgi_module /var/www/html/venv/lib/python3.8/site-packages/mod_wsgi/server/mod_wsgi-py38.cpython-38-x86_64-linux-gnu.so
WSGIScriptAlias / /var/www/html/portfolio/config/wsgi.py
WSGIDaemonProcess wsgi_app python-home=/var/www/html/venv python-path=/var/www/html/portfolio
WSGIProcessGroup wsgi_app
+ WSGIApplicationGroup %{GLOBAL}
WSGISocketPrefix /var/run/wsgi
console
# service apache2 restart

エラーが発生した場合はApacheのログをみれば原因がわかります

console
# vi /var/log/apache2/error.log

Cron(タスクスケジューラ)

CronはWindowsでいうタスクスケジューラだ。決まった時間に決まったコマンドを実行してくれる。CentOSとの操作の差はないみたい。
Cron(crontab)の設定

定期実行するプログラムの作成

console
# cd /var/www/html
# vi hello-cron.py
hello-cron.py
import codecs
from datetime import datetime

txt = datetime.now().strftime("%Y/%m/%d %H:%M:%S") + ' hello-cron.py'
with codecs.open('hello-cron.log', 'a', 'utf-8') as f:
    f.writelines('\n' + txt)
console
# python3 hello-cron.py
# vi hello-cron.log
TEST:hello-cron.log(時刻と書き込み元のプログラム名が記録された)
2021/08/29 16:10:39 hello-cron.py

Cronの設定

10分ごとに Hello-cron.py を実行するスケジュールを作成します。気をつける点は、pythonプログラムが書き込む場所の権限とpythonプログラム自体へのパス(フルパスなんよね)。あとは相対パスでプログラムを書いている場合の「カレントディレクトリ」に注意。windowsとlinuxのディレクトリ構造は違うことが多いし。

console(使用エディタをワンショットでviに設定)
export EDITOR=vi

# vi /etc/environment

/etc/environment
VISUAL=/usr/bin/vim
EDITOR=/usr/bin/vim

保存してターミナルに入り直したら恒久的にviになった

console(設定画面へ)
- # crontab -e
+ # cp /etc/crontab /etc/cron.d/cron_portfolio
+ # vi /etc/cron.d/cron_portfolio
crontab(10分ごとと毎時0分ごとと毎分。タスクスケジューラでも実行ファイルへのパスとプログラムのパスを併記するよね)
*/10 * * * * root /var/www/html/venv/bin/python /var/www/html/hello-cron.py
0 * * * * root /var/www/html/venv/bin/python /var/www/html/hello-cron.py
* * * * * root /var/www/html/venv/bin/python /var/www/html/hello-cron.py
crontab(自分用メモ)
0 0 * * * root /root/certbot.sh
0 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py daily_import_from_vietkabu
5 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py daily_import_from_sbi
6 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py daily_import_from_bloomberg
15 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py daily_industry_chart_and_uptrends       
20 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py daily_industry_stacked_bar_chart        
30 18 * * * root /var/www/html/venv/bin/python /var/www/html/portfolio/manage.py collectstatic
console(仕掛けたらログを見てみると)
# vi hello-cron.log
確認:hello-cron.log(どんどん追記されている)
2020/03/28 02:18:26 hello-cron.py
2020/03/28 02:28:26 hello-cron.py
console(自分用メモ:バッチファイルには実行権限を忘れずに与える)
# chmod 774 /var/www/html/portfolio/daily_industry.py
# chmod 774 /var/www/html/portfolio/daily_sbi.py
# chmod 774 /var/www/html/portfolio/daily_vnindex.py
# chmod 774 /var/www/html/portfolio/daily_chart.py

# ls -l /var/www/html/portfolio
  合計 52
  -rw-rw-r-- 1 ubuntu ubuntu 4343  1月  6 19:41 README.md
  drwxrwxr-x 2 ubuntu ubuntu 4096  1月  6 19:41 config
  drwxrwxr-x 5 ubuntu ubuntu 4096  1月  6 19:41 docs
  drwxrwxr-x 6 ubuntu ubuntu 4096  1月  6 19:41 gmarker
  drwxrwxr-x 3 ubuntu ubuntu 4096  1月  6 19:41 linebot
  -rw-rw-r-- 1 ubuntu ubuntu  538  1月  6 19:41 manage.py
  drwxrwxr-x 4 ubuntu ubuntu 4096  1月  6 19:41 register
  -rw-rw-r-- 1 ubuntu ubuntu 1238  1月  6 19:41 requirements.txt
  drwxrwxr-x 6 ubuntu ubuntu 4096  1月  6 19:41 shopping
  drwxrwxr-x 7 ubuntu ubuntu 4096  1月  6 19:41 taxonomy
  drwxrwxr-x 9 ubuntu ubuntu 4096  1月  6 19:41 vietnam_research
  drwxrwxr-x 6 ubuntu ubuntu 4096  1月  6 19:41 warehouse
console(権限をまとめてubuntu扱いに)
# chown -R ubuntu:ubuntu /var/www/html
  • cronで失敗するのは、staticを置き換える(python manage.py collectstatic)ときに置き換え先のpermissionがrootになってて上書きミスってるのとかがありそう。権限をまとめてubuntu扱いに、を忘れずに

Reset(※必要に応じて)

  1. まず各appディレクトリのmigrationsディレクトリを消してまわります
  2. dbを消します(=portfolio_db)
  3. dbを作ります

migrationとcreatesuperuser

最初に register を指定しながらの makemigrations をしないとだめ

console
# python3 manage.py makemigrations register
  Migrations for 'register':
    register\migrations\0001_initial.py
      - Create model User
# python3 manage.py migrate
  Operations to perform:
    Apply all migrations: admin, auth, contenttypes, register, sessions
  Running migrations:
    Applying contenttypes.0001_initial... OK
    Applying contenttypes.0002_remove_content_type_name... OK
    Applying auth.0001_initial... OK
    Applying auth.0002_alter_permission_name_max_length... OK
    Applying auth.0003_alter_user_email_max_length... OK
    Applying auth.0004_alter_user_username_opts... OK
    Applying auth.0005_alter_user_last_login_null... OK
    Applying auth.0006_require_contenttypes_0002... OK
    Applying auth.0007_alter_validators_add_error_messages... OK
    Applying auth.0008_alter_user_username_max_length... OK
    Applying auth.0009_alter_user_last_name_max_length... OK
    Applying auth.0010_alter_group_name_max_length... OK
    Applying auth.0011_update_proxy_permissions... OK
    Applying register.0001_initial... OK
    Applying admin.0001_initial... OK
    Applying admin.0002_logentry_remove_auto_add... OK
    Applying admin.0003_logentry_add_action_flag_choices... OK
    Applying sessions.0001_initial... OK
Console(ほかにアプリケーションがある場合の追加migration)
# python3 manage.py makemigrations vietnam_research
# python3 manage.py makemigrations gmarker
# python3 manage.py makemigrations shopping
# python3 manage.py makemigrations linebot
# python3 manage.py makemigrations warehouse
# python3 manage.py makemigrations taxonomy
# python3 manage.py migrate
Console(管理ユーザーも消えるので、必要な場合はもう一度作ります)
# python3 manage.py createsuperuser
  Email address: 
  Password:
  Password (again):
  Superuser created successfully.

確認

djangoがシステム的に作ったテーブルと、アプリケーションを作っていればアプリケーション名が先頭についたテーブルが作成される(赤枠)
image.png

MySQLデータのインポート

https://qiita.com/YoshitakaOkada/items/45ebdc00cc923d970638
リストアのみならこれ
※自分メモ(ローカルからVPSのインポートは事故るから、マイグレーション済んだらsuperuserを手で追加したあと、ダンプからDrop・Create命令を除外したものを作って)

console
vi /etc/my.cnf
/etc/my.cnf
[mysqld]
wait_timeout            = 86400
max_allowed_packet      = 1G
innodb_buffer_pool_size = 1G
console
service mysql restart
console(Ubuntu)SCPした場所にcdしてから
# mysql -u root -p
mysql> use portfolio_db
mysql> source mysql_dump.sql

collectstaticの定例更新化

console
# cd /root
# vi collectstatic.sh
collectstatic.sh(新規)
# /bin/sh
cd /root
source /var/www/html/venv/bin/activate
python3 /var/www/html/portfolio/manage.py collectstatic --noinput

(※検証中)
+ # /bin/sh
+ LOG_FILE="/var/log/collectstatic.log"
+ echo "$(date '+%Y/%m/%d %H:%M:%S') - collectstatic.sh started" >> "$LOG_FILE"     
cd /root
source /var/www/html/venv/bin/activate
python3 /var/www/html/portfolio/manage.py collectstatic --noinput
+ echo "$(date '+%Y/%m/%d %H:%M:%S') - collectstatic.sh completed" >> "$LOG_FILE"
console(使用エディタをワンショットでviに設定)
export EDITOR=vi

# vi /etc/environment

/etc/environment
VISUAL=/usr/bin/vim
EDITOR=/usr/bin/vim

保存してターミナルに入り直したら恒久的にviになった

console
# chmod 755 collectstatic.sh
# crontab -e
crontab(毎日19時にcollectstaticする)
0 19 * * * /root/collectstatic.sh
console
# crontab -l

権限 chown -R ubuntu:ubuntu /var/www/html

さんざん root のままディレクトリとか作りまくってると access denied というか permission error になってることがあるので注意

FTP

いったんパス もとの記事

Djangoのバッチをつくる

Djangoで自動テストをする

バッチのテストもこっち

Ubuntuのmatplotlib、日本語問題

console
# sudo apt install -y fonts-ipafont
# ls ~/.cache/matplotlib/
# rm ~/.cache/matplotlib/fontlist-v330.json
# fc-cache -fv
# fc-list | grep -i ipa
  /usr/share/fonts/opentype/ipafont-mincho/ipam.ttf: IPAMincho,IPA明朝:style=Regular
  /usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf: IPAPGothic,IPA Pゴシック:style=Regular
  /usr/share/fonts/opentype/ipafont-mincho/ipamp.ttf: IPAPMincho,IPA P明朝:style=Regular
  /usr/share/fonts/opentype/ipafont-gothic/ipag.ttf: IPAGothic,IPAゴシック:style=Regular
  /usr/share/fonts/truetype/fonts-japanese-mincho.ttf: IPAMincho,IPA明朝:style=Regular
  /usr/share/fonts/truetype/fonts-japanese-gothic.ttf: IPAGothic,IPAゴシック:style=Regular
vietnam_research/management/commands/daily_industry_stacked_bar_chart.py
font_path = '/usr/share/fonts/opentype/ipafont-mincho/ipam.ttf'
if Path.exists(Path(font_path).resolve()):
    # for ubuntu jp font
    plt.legend(loc='upper left', labels=df.columns, prop={"family": "IPAMincho"})
else:
    plt.legend(loc='upper left', labels=df.columns, prop={"family": "MS Gothic"})

PdfMiner

  • SBI topics で使用している
  • pdfminer.six へライブラリを変更したら解決した
console
pip install pdfminer.six

CI環境を整える

Djangoプロジェクトを新規で始める場合

Djangoインストール

console(venvをアクティベートしてからね)
# source /var/www/html/venv/bin/activate
# pip3 install django
# django-admin --version
  4.0.2

pip list

console
# pip list
  Package       Version
  ------------- -------
  asgiref       3.4.1
  Django        4.0.2
  mod-wsgi      4.9.0
  pip           20.0.2
  pkg-resources 0.0.0
  pytz          2021.1
  setuptools    44.0.0
  sqlparse      0.4.1

よく使うライブラリ

console
# pip3 install wheel numpy pandas sqlalchemy beautifulsoup4 matplotlib pillow lxml stripe
console(権限をまとめてubuntu扱いに)
# chown -R ubuntu:ubuntu /var/www/html

わかりやすいプロジェクト構成

新規作成時のみ

  • ベースディレクトリ名と設定ディレクトリ名が同じでややこしい
  • テンプレートと静的ファイルがアプリケーションごとにバラバラに配置されてしまう

これらを解決する。ベースディレクトリを作成したあとにベースディレクトリの下に移動し、設定ディレクトリ名と . を指定する

console
$ mkdir mypage
$ cd mypage
$ django-admin startproject config .
$ python manage.py startapp hoge
$ python manage.py runserver

image.png

settings.py

ALLOWED_HOSTS(許可するドメイン)を編集する

console
# vi /var/www/html/portfolio/config/settings.py
settings.py
- ALLOWED_HOSTS = []
+ ALLOWED_HOSTS = ['.henojiya.net', '127.0.0.1', 'localhost', '153.126.200.229']

mysqlclient

(venv)# apt -y install python3-dev build-essential libssl1.1 libssl1.1=1.1.1f-1ubuntu2 libssl-dev libmysqlclient-dev
(venv)# pip3 install mysqlclient environ

settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}
DATABASES = {
    'default': {
-       'ENGINE': 'django.db.backends.sqlite3',
-       'NAME': BASE_DIR / 'db.sqlite3',
+       'ENGINE': 'django.db.backends.mysql',
+       'NAME': 'portfolio_db',
+       'USER': 'python',
+       'PASSWORD': 'python123',
    }
}
console
# service apache2 restart

Django

ログイン機能

セットアップのタイミング的にここに書いておくけどアプリケーション作るのに慣れてからやること。ログイン機能は、ログイン機能としてのアプリケーションを別個につくるのがベスト・プラクティスだ。

Reset(※必要に応じて)

  1. まず各appディレクトリのmigrationsディレクトリを消してまわります
  2. dbを消します(=portfolio_db)
  3. dbを作ります

startapp

DjangoでUserモデルのカスタマイズ

console
# cd /var/www/html/portfolio
# python3 manage.py startapp register
/var/www/html/portfolio/register/models.py
from django.db import models
from django.core.mail import send_mail
from django.contrib.auth.models import PermissionsMixin, UserManager
from django.contrib.auth.base_user import AbstractBaseUser
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone


class CustomUserManager(UserManager):
    """ユーザーマネージャー"""
    use_in_migrations = True

    def _create_user(self, email, password, **extra_fields):
        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):
    """カスタムユーザーモデル."""

    email = models.EmailField(_('email address'), unique=True)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), 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 = CustomUserManager()

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

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

    def get_full_name(self):
        """Return the first_name plus the last_name, with a space in
        between."""
        full_name = '%s %s' % (self.first_name, self.last_name)
        return full_name.strip()

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

    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)

    @property
    def username(self):
        """username属性のゲッター

        他アプリケーションが、username属性にアクセスした場合に備えて定義
        メールアドレスを返す
        """
        return self.email
/var/www/html/portfolio/config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+    'register.apps.RegisterConfig',
]
/var/www/html/portfolio/config/settings.py(最下段に追記)
+ # login
+ LOGIN_URL = 'register:login'
+ LOGIN_REDIRECT_URL = 'vnm:index'  #ログイン後にリダイレクトしたい先
+ LOGOUT_REDIRECT_URL = "vnm:index" #ログアウト後にリダイレクトしたい先
+ AUTH_USER_MODEL = 'register.User'
/var/www/html/portfolio/register
# mkdir -p templates/register
# vi templates/register/base.html
/var/www/html/portfolio/register/templates/register/base.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-43097095-9"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'UA-43097095-9');
    </script>

    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>VNMビューア</title>

    <!-- css -->
    <link rel="stylesheet" href="{% static 'register/css/reset.css' %}"> 
    <link rel="stylesheet" href="{% static 'register/css/index.css' %}"> 

    <!-- font -->
    <link href="https://fonts.googleapis.com/css?family=Sawarabi+Gothic" rel="stylesheet">
    <!-- fontawesome -->
    <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">

    <!-- favicon -->
    <link rel="shortcut icon" href="{% static 'register/images/c_r.ico' %}">

</head>
<body>
    <!-- nav -->
    <h1></h1>

    <div id="main">
        {% block content %}{% endblock %}
    </div>

    <footer>
        <p>© 2019 henojiya. / <a href="https://github.com/duri0214" target="_blank">github portfolio</a></p>
    </footer>

</body>
</html>
/var/www/html/portfolio/register/views.py
"""views.py"""
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.sites.shortcuts import get_current_site
from django.core.signing import BadSignature, SignatureExpired, loads, dumps
from django.http import HttpResponseBadRequest
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.views import generic
from .forms import UserCreateForm

# signup
class UserCreate(generic.CreateView):
    """ユーザー仮登録"""
    template_name = 'register/user_create.html'
    form_class = UserCreateForm

    def form_valid(self, form):
        """仮登録と本登録用メールの発行."""
        # 仮登録と本登録の切り替えは、is_active属性を使うと簡単です。
        # 退会処理も、is_activeをFalseにするだけにしておくと捗ります。
        user = form.save(commit=False)
        user.is_active = False
        user.save()

        # アクティベーションURLの送付
        current_site = get_current_site(self.request)
        domain = current_site.domain
        context = {
            'protocol': self.request.scheme,
            'domain': domain,
            'token': dumps(user.pk),
            'user': user,
        }
        folder = settings.BASE_DIR + '/register/templates/register/mail_template/'
        subject = render_to_string(folder + 'subject.txt', context)
        message = render_to_string(folder + 'message.txt', context)

        user.email_user(subject, message)
        return redirect('register:user_create_done')


class UserCreateDone(generic.TemplateView):
    """ユーザー仮登録したよ"""
    template_name = 'register/user_create_done.html'


class UserCreateComplete(generic.TemplateView):
    """メール内URLアクセス後のユーザー本登録"""
    template_name = 'register/user_create_complete.html'
    timeout_seconds = getattr(settings, 'ACTIVATION_TIMEOUT_SECONDS', 60*60*24)  # デフォルトでは1日以内

    def get(self, request, *args, **kwargs):
        """tokenが正しければ本登録."""
        token = kwargs.get('token')
        try:
            user_pk = loads(token, max_age=self.timeout_seconds)

        # 期限切れ
        except SignatureExpired:
            return HttpResponseBadRequest()

        # tokenが間違っている
        except BadSignature:
            return HttpResponseBadRequest()

        # tokenは問題なし
        else:
            try:
                user = get_user_model().objects.get(pk=user_pk)
            except get_user_model().DoesNotExist:
                return HttpResponseBadRequest()
            else:
                if not user.is_active:
                    # 問題なければ本登録とする
                    user.is_active = True
                    user.save()
                    return super().get(request, **kwargs)

        return HttpResponseBadRequest()


class Login(LoginView):
    """ログインページ"""
    template_name = 'register/login.html'
/var/www/html/portfolio/register/forms.py
"""forms.py"""
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth import get_user_model

class UserCreateForm(UserCreationForm):
    """ユーザー登録用フォーム"""

    class Meta:
        model = get_user_model()
        fields = ('email',)

    def clean_email(self):
        """clean_email"""
        email = self.cleaned_data['email']
        get_user_model().objects.filter(email=email, is_active=False).delete()
        return email
/var/www/html/portfolio/register/templates/register/login.html
{% extends "register/base.html" %}
{% block content %}
<div class="card col-md-6">
    <div class="card-body">
        <form action="{% url 'register:login' %}" method="POST">
            {{ form.non_field_errors }}
            {% for field in form %}
                {{ field }}
                {{ field.errors }}
                <hr>
            {% endfor %}
            <button type="submit" class="btn btn-success btn-lg btn-block" >ログイン</button>
            <input type="hidden" name="next" value="{{ next }}" />
            {% csrf_token %}
        </form>
    </div>
</div>
<div class="">
    <div class="card-body">
        <a href="{% url 'register:user_create' %}" class="" >会員登録</a>
    </div>
</div>
{% endblock %}
/var/www/html/portfolio/register/urls.py
"""urls.py"""
from django.urls import path
from . import views
from django.contrib.auth.views import LoginView, LogoutView

app_name = 'register'

urlpatterns = [
    path('login/', LoginView.as_view(template_name='register/login.html'), name='login'),
    path('logout/', LogoutView.as_view(), name='logout'),
    path('user_create/', views.UserCreate.as_view(), name='user_create'),
    path('user_create/done', views.UserCreateDone.as_view(), name='user_create_done'),
    path('user_create/complete/<str:token>/', views.UserCreateComplete.as_view(), name='user_create_complete'),
]

admin管理画面にテーブルを追加表示する

実体としてどれだけテーブルがあろうと、管理画面には表示されないので設定する必要がある。
image.png

shopping/admin.py
from django.contrib import admin
+ from .models import Staff, Store, Products, BuyingHistory

# Register your models here.
+ admin.site.register(Staff)
+ admin.site.register(Store)
+ admin.site.register(Products)
+ admin.site.register(BuyingHistory)

表示された!アプリケーションごとにやる必要があるね
image.png

Djangoアプリケーションの新規作成

console
# cd /var/www/html/portfolio
# python3 manage.py startapp vietnam_research

非公開情報を.envに移す(GitGuardian対策)

view作成

console
# vi vietnam_research/views.py
views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world.")

settings.py

console
# vi config/settings.py
settings.py(追記)
INSTALLED_APPS = [
    ...,
    'vietnam_research.apps.VietnamResearchConfig'
]

httpd.conf(wsgi.conf)

staticディレクトリ配下は開放。
※あくまで DEBUG = True のときの設定です。慣れてきて DEBUG = False にするときは こっち を参照

console
# vi /etc/apache2/sites-enabled/000-default.conf
000-default.conf(あくまでDEBUG=False用の設定。collectstaticでこのフォルダにコピーされるから)
+ # css, javascript etc
+ Alias /static/ /var/www/html/portfolio/static/
+ <Directory /var/www/html/portfolio/static>
+   Require all granted
+ </Directory>
console
# systemctl restart apache2

vietnam_research/urls.py を編集

URLの紐づけをロケットアニメのHelloWorldから変えるために、urls.py を新規作成する(作る場所は「vietnam_research」フォルダ)。便宜上「子供のurls.py」と呼ぶことがある。「urls.py」には「s」をつけろよデコ助野郎
image.png

vietnam_research/urls.py
from django.urls import path

# 現在のフォルダの「views.py」を import する!さっき "Hello, world." したやつ!
from . import views

# views.py には「index」という関数を作りましたね!それを呼んでます
urlpatterns = [
    path('', views.index, name='index'),
]

urls.py(共通Configのほう)

NTTの配電盤みたいなイメージね。便宜上「親のurls.py」と呼ぶことがある。(※この英語部分もよく読むと実はさっき子供のurls.pyに書いたようなことをやれって書いてあったりする)

urls.py(共通Configのほう)
"""portfolio URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.2/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('vietnam_research/', include('vietnam_research.urls')),
    path('admin/', admin.site.urls),
]
console
# systemctl restart apache2

HelloWorld!

image.png
image.png

model.py

ここはデータベースとテーブルの話だからね。好きにやってちょうだい

model.py
"""このファイル内に、必要なテーブルがすべて定義されます"""
from django.db import models

class Industry(models.Model):
    """
    viet-kabuで取得できる業種つき個社情報
    closing_price: 終値(千ドン)
    volume: 出来高(株)
    trade_price_of_a_day: 売買代金(千ドン)
    marketcap: 時価総額(億円)
    """
    market_code = models.CharField(max_length=4)
    symbol = models.CharField(max_length=10)
    company_name = models.CharField(max_length=50)
    industry1 = models.CharField(max_length=10)
    industry2 = models.CharField(max_length=20)
    open_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    high_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    low_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    closing_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    volume = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    trade_price_of_a_day = models.DecimalField(max_digits=20, decimal_places=2, default=0.00)
    marketcap = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    per = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
    pub_date = models.DateField()

migrations

makemigrationsは台帳登録みたいなイメージ

console
# python3 manage.py makemigrations vietnam_research

migrate

migrateは「実効」みたいなイメージ

console
# python3 manage.py migrate
  Operations to perform:
    Apply all migrations: admin, auth, contenttypes, sessions
  Running migrations:
    Applying contenttypes.0001_initial... OK
    Applying auth.0001_initial... OK
    Applying admin.0001_initial... OK
    Applying admin.0002_logentry_remove_auto_add... OK
    Applying admin.0003_logentry_add_action_flag_choices... OK
    Applying contenttypes.0002_remove_content_type_name... OK
    Applying auth.0002_alter_permission_name_max_length... OK
    Applying auth.0003_alter_user_email_max_length... OK
    Applying auth.0004_alter_user_username_opts... OK
    Applying auth.0005_alter_user_last_login_null... OK
    Applying auth.0006_require_contenttypes_0002... OK
    Applying auth.0007_alter_validators_add_error_messages... OK
    Applying auth.0008_alter_user_username_max_length... OK
    Applying auth.0009_alter_user_last_name_max_length... OK
    Applying auth.0010_alter_group_name_max_length... OK
    Applying auth.0011_update_proxy_permissions... OK
    Applying sessions.0001_initial... OK

アプリケーションとして認識させる

(このステップ忘れるべからず)
このsettingでアプリケーションとして認識させることで、index.htmlを開きにいったときの「templates/{アプリケーション名}/index.html」を自動的に識別して読みにいってくれる。
DjangoでTemplateDoesNotExistと言われたら

「各アプリケーションの配下にあるtemplatesディレクトリ」を探索するということは、アプリケーションと認識されていなければ探索されないということだ。
今回はそもそもここに原因があった。settings.pyのINSTALLED_APPSにmyappを登録するのを忘れていた。

INSTALLED_APPS に設定を追加するんだが、、え?VietnamResearchConfigに覚えがないって?
そうなんだよ、アプリケーションフォルダ(test_chartjs)配下にある、「apps.py」を開いてみると書いてあるんだよね。わかりにくいなぁこれ。

config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'test_chartjs.apps.VietnamResearchConfig',
]

templates ディレクトリを作成

最初に、アプリケーションフォルダの中に「templates」フォルダを作成。さらにその中に、(Djangoのテンプレート読み込みルールに則り)アプリケーションフォルダと同じ名前のフォルダを作成してから index.html というファイルを作成する。
image.png

つまり、テンプレートは「vietnam_research/templates/vietnam_research/index.html」に書く必要がある。「テンプレートフォルダのなかにアプリケーション名がある」というの自体はほかのWeb言語にもあったような気がする?:thinking:
これは文化的なもので「名前空間」という意味合いに過ぎない。
「templates」には「s」をつけろよデコ助野郎

テンプレートを編集

vscodeのhtmlファイル上で「!」って入力すると、vscodeのちょっとした機能でこのテンプレートが出てくる。すごい

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>vietnam_research</title>
</head>
<body>
    <h1>vietnam_research</h1>
</body>
</html>

static ディレクトリを作成

  • static(黄色の四角)
  • static/vietnam_research(オレンジ)
  • static/vietnam_research/js

などのフォルダやファイルは、手で作る必要があります。
(templateと同じ階層にstaticをつくります)
image.png

  • htmlの最初に {% load static %} を忘れるな!
  • javascript を読み込むときのパスは {% static 'vietnam_research/js/script.js' %} だ
  • このフォルダの「指定方法」と「そしてどうなる」を脳筋になるまで繰り返して感覚をつかめ!

google analytics

headタグの一番最初に取り付ける

index.html
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-43097095-9"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'UA-43097095-9');
</script>

index.html

index.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
    <head>
        <!-- Global site tag (gtag.js) - Google Analytics -->
        <script async src="https://www.googletagmanager.com/gtag/js?id=UA-43097095-9"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', 'UA-43097095-9');
        </script>

        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>shopping</title>

        <!-- css -->
        <link rel="stylesheet" href="{% static 'vietnam_research/css/reset.css' %}">
        <link rel="stylesheet" href="{% static 'vietnam_research/css/index.css' %}">
        <!-- favicon -->
        <link rel="icon" href="{% static 'vietnam_research/c_v.ico' %}">

        <!-- javascript -->
        <script src="{% static 'vietnam_research/js/script.js' %}"></script>

        <!-- font -->
        <link href="https://fonts.googleapis.com/css?family=Sawarabi+Gothic" rel="stylesheet">
        <!-- fontawesome -->
        <link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">

        <!-- for ajax -->
        <script>let myurl = {"base": "{% url 'vnm:index' %}"}</script>

    </head>

    <body>
        <h1>vietnam_research</h1>
    </body>
</html>

views.pyがテンプレートへ向けて置換をかけて返却する流れを作る

vietnam_research/views.py
"""views.py"""
from django.shortcuts import render

def index(request):
    """いわばhtmlのページ単位の構成物です"""
    # htmlとして返却します
    return render(request, 'vietnam_research/index.html')

ローカル環境でのテスト

image.png

image.png

フォームでのファイルアップロードを実装する

いやー stackoverflowで質問しても 回答つかなくて困った困った。settings.pyの MEDIA の役割がわかってなかったんだよね。

settings.py
# これの追記で permissionerror 回避を確認ok
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'

上記の settings.py の追記に加えて、下記のように models.py で upload_to='shopping/ にすると、[example.com]/media/shopping/xxx.jpg と保存されるようになる

models.py
class Products(models.Model):
    """商品"""
    code = models.CharField('商品コード', max_length=200)
    name = models.CharField('商品名', max_length=200)
    price = models.IntegerField('金額', default=0)
    description = models.TextField('説明')
    picture = models.ImageField('商品写真', upload_to='shopping/')
views.py(あとはフォームの内容を料理するだけや)
class UploadSingleView(FormView):
    """UploadSingleView"""
    form_class = SingleRegistrationForm
    success_url = reverse_lazy('shp:index')

    def form_valid(self, form):
        # prepare
        code = form.cleaned_data.get('code')
        Products.objects.filter(code=code).delete()
        # save
        form.save()
        # delete if file is exists as same.
        orgname, ext = os.path.splitext(form.cleaned_data["picture"].name)
        mvfilepath = settings.BASE_DIR + '/shopping/static/shopping/img/' + code + ext.lower()
        if os.path.exists(mvfilepath):
            os.remove(mvfilepath)
        # move file as rename
        uploadfilepath = settings.BASE_DIR + '/media/shopping/' + orgname + ext.lower()
        os.rename(uploadfilepath, mvfilepath)
        return super().form_valid(form)

DEBUGをFalseにしてみて?

公式:本番環境における静的ファイルの配信
DEBUGをTrueにしているあいだは気にすることはないが、本番環境にしようとしてDEBUGをFalseにすると /static/ (settings.pyのSTATIC_URL)は各アプリケーション内のstaticディレクトリを読みにいきません。
(非効率であったり、セキュリティ上の理由らしい)

settings.py(デバッグモードをオフ!)
DEBUG = False
settings.py(STATIC_URLのあたりが良いよ)
パス参考:/var/www/html/portfolio/static/
STATIC_ROOT = os.path.join(BASE_DIR, "static")
consols(/var/www/html/portfolio/staticに静的ファイルをコピーする)
# python3 manage.py collectstatic
# chown -R ubuntu:ubuntu /var/www/html
console
# systemctl restart apache2
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1