hikaru’s diary

Django Engineer

【Django】formsの属性を変更する

global_numberという変数を複数のフォームクラスで使いまわしているとします。
form2のラベルと最大値を変更します。

forms.py

from django import forms
import datetime


global_year = forms.IntegerField(label='年', max_value=3000, min_value=1, initial=datetime.datetime.now().year)
global_number = forms.IntegerField(label='番号', max_value=100000, min_value=1)


class form1(forms.Form):
    year = global_year
    number = global_number


class form2(forms.Form):
    year = global_year
    number = global_number
    number.label = 'form2番号'  # 変更
    number.max_value = 10  # 変更

【Django】出勤表ポートフォリオ

出勤表アプリを作っていきます。製作期間は3日ほど。
誤字脱字とかかなり多いと思うので先にgit貼っておきます。開発環境までのgitです。
https://github.com/hikaru1444/django_portfolio_work


インストール

pythonをインストール
Windows版Pythonのインストール: Python環境構築ガイド - python.jp

Djangoとbootstrap5をインストール

pip install Django
pip install django-bootstrap5
pip list  # 確認

サーバー起動まで

django-admin startproject work
cd work  # manage.pyがあるフォルダに移動
python manage.py runserver

ロケットが打ちあがったら成功。

python manage.py startapp app1

アプリ作成。

settings

コーディングしていきます。


settings.py

from pathlib import Path
import os
import sys
from datetime import datetime
import psycopg2.extras


ALLOWED_HOSTS = ['*']  # 修正


# MAIL
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.googlemail.com'
EMAIL_USE_SSL = True
EMAIL_PORT = 465
EMAIL_HOST_USER = os.environ.get('EMAIL_ADDRESS')
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASSWORD')

# INSTALLED_APPS
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 追加
    'app1.apps.App1Config',  # apps.pyのクラス
    'django_bootstrap5',
]

# MIDDLEWARE
MIDDLEWARE = [
    # 追加
    'django.middleware.common.BrokenLinkEmailsMiddleware',
    # 後で使う
    # 'app1.middleware.middleware.CustomAttrMiddleware',
]


# DATABASES 必要に応じて
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': os.environ.get('DB_HOST'),
        'PORT': '',
        'TEST': {
            'MIRROR': "default",
        },
    }
}

# バックアップしたデータベース等を使用した時に警告が出る
connection = psycopg2.connect(
    database=os.environ.get('DB_NAME'),
    user=os.environ.get('DB_USER'),
    password=os.environ.get('DB_PASSWORD'),
    host=os.environ.get('DB_HOST')
)

if DATABASES['default']['NAME'] != 'postgres':
    print("データベースが間違っている可能性があります。", DATABASES['default']['NAME'])
if connection.get_dsn_parameters()['dbname'] != 'postgres':
    print("データベースが間違っている可能性があります。", connection.get_dsn_parameters()['dbname'])
if connection.get_dsn_parameters()['user'] != 'postgres':
    print("データベースが間違っている可能性があります。", connection.get_dsn_parameters()['user'])


LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'



本番環境で実行するにはipconfigでIPv4アドレスを調べて適当なポートを付けてサーバー起動してください。
色々設定していますが無くても大丈夫です。詳細についてはこのブログのどこかで説明していると思います。
settings.pyは_devなどを付けて環境ごとにファイルを分けるのが一般的だと思います。


app1

他のチュートリアルではHttpResponseを使って一旦表示をするっていうのをやっていますが、飛ばしてHTML作っていきます。
HTMLはログイン者、プログラム実行結果などを出力するbase.htmlを使用します。
あと、退勤時に時間の入力と備考を書くことを想定しています。

urls

work/urls.py

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
from app1.views import top


urlpatterns = [
    path('admin/', admin.site.urls),
    path('app1/', include('app1.urls')),
    path('',  top, name='top'),
]

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


app1/urls.py

from django.urls import path
from app1.views import top, work


urlpatterns = [
    path('top/', top, name='top'),
    path('work/', work, name='work'),
]
templates

app1にtemplatesを作成
templates/base.html

{% load static %}
{% load django_bootstrap5 %}
<html lang="ja">
<head>
    <meta charset="utf-8" name="viewport" content="width=1024">
    <title>{% block title %}Django Boards{% endblock %}</title>
    {% bootstrap_css %}
    {% bootstrap_javascript %}
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    <link rel="icon" href="{% static 'img/favicon.ico' %}">
</head>
<body>
<h1>
    H1
</h1>
{% if check != None %}
<h2>{{ check }}</h2>
{% else %}
<h2>ここにプログラムメッセージが表示されます</h2>
{% endif %}
<div class="content">
    {% block content %}
    {% endblock %}
</div>
</body>
</html>


templates/top.html

{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% block title %}メインページ{% endblock %}
{% block content %}
<p>I am top. Please write.</p>
{% endblock %}


work.html

{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% block title %}出勤表{% endblock %}
{% block content %}
<p>I am work. Please write.</p>
{% endblock %}

static,cssを設定
飛ばしても大丈夫です。

現段階で3つページにアクセスできるはずです。
http://127.0.0.1:8000/

http://127.0.0.1:8000/app1/top/

http://127.0.0.1:8000/app1/work/


ページの移動が面倒なのでヘッダーにリンクを張る
base.html

<body>
<h1>
    <a>ログイン:{{ user }}</a>
    <a href="{% url 'top' %}">top</a>
    <a href="{% url 'work' %}">work</a>
</h1>
{% if check != None %}
<h2>{{ check }}</h2>
{% else %}
<h2>ここにプログラムメッセージが表示されます</h2>
{% endif %}
<div class="content">
    {% block content %}
    {% endblock %}
</div>
</body>
models

命名とか型宣言とかすごく苦手です。参考にしないでください。

models.py

from django.db import models

# Create your models here.


class Works(models.Model):
    name = models.CharField('名前', max_length=100)
    date = models.DateField('日付')
    in_time = models.TimeField('出勤')
    out_time = models.TimeField('退勤')
    rest_time = models.TimeField('休憩')
    other = models.CharField('備考', max_length=1000, default='', blank=True)

    def __str__(self):
        return self.name

adminで確認したいのでユーザー作成します。
コマンドプロンプトでmanage.pyのあるディレクトリですること
パスワード8桁以下だと警告がでます。

python manage.py createsuperuser

admin.py

from django.contrib import admin
from app1.models import Works
# Register your models here.

admin.site.register(Works)

adminサイトにアクセス後、出勤表で使用するユーザーを作成。

ログイン機能

forms.py

from django import forms
from app1.models import Works
import datetime


class TopForm(forms.Form):
    name = forms.CharField(label='名前')
    password = forms.CharField(label='パスワード', widget=forms.PasswordInput)

views.py

def top(request):
    params = {'form': None}
    if request.method == 'POST':
        form = TopForm(request.POST)
        if 'login' in request.POST:
            name = request.POST['name']
            password = request.POST['password']
            user = authenticate(request, username=name, password=password)
            # 認証に成功したらuserに名前が入り、失敗したらuserはnoneになる
            if user is not None:
                login(request, user)
                params['check'] = "ログインしました!"
            else:
                params['check'] = "ユーザー名またはパスワードが間違っています。"
        elif 'logout' in request.POST:
            logout(request)
            params['check'] = "ログアウトしました!"
        print("POST")
        params['form'] = form
    else:
        params['form'] = TopForm()
    return render(request, 'top.html', params)

top.html

{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% block title %}メインページ{% endblock %}
{% block content %}
<p>I am top. Please write.</p>
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    {% bootstrap_button "ログイン" name="login" button_type="submit" %}
    {% bootstrap_button "ログアウト" name="logout" button_type="submit" %}
</form>
{% endblock %}


出勤表

forms
forms.py

from django import forms
from app1.models import Works


class WorksForm(forms.Form):
    dt_now = datetime.datetime.now()
    name = forms.CharField(label='名前')
    date = forms.DateField(label='日付', widget=forms.DateInput(attrs={"type": "date"}))
    in_time = forms.TimeField(label='出勤時間',
                              widget=forms.TimeInput(attrs={"type": "time"}, format='%H:%M:%S'))
    out_time = forms.TimeField(label='退勤時間',
                               widget=forms.TimeInput(attrs={"type": "time"}, format='%H:%M:%S'))
    rest_time = forms.TimeField(label='休憩時間',
                                widget=forms.TimeInput(attrs={"type": "time"}, format='%H:%M:%S'))
    other = forms.CharField(label='備考', required=False)


work.html

{% extends 'base.html' %}
{% load django_bootstrap5 %}
{% block title %}出勤表{% endblock %}
{% block content %}
<p>I am work. Please write.</p>
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    {% bootstrap_button "表示" name="btn2" button_type="submit" %}
    {% bootstrap_button "入力" name="btn" button_type="submit" %}
</form>
{% endblock %}

カスタムテンプレート

出勤表を見やすくするために使用します。
work/project/templatetags/tags.py

from django import template

register = template.Library()


@register.filter
def modulo(num, val):
    return num % val

work.html

{% load tags %}
{% if total != None %}
<h2>{{ total|safe }}</h2>
{% else %}
<h2>ここにプログラムメッセージが表示されます</h2>
{% endif %}
        <table class="table">
            <tbody>
            <tr>
                <th>名前</th>
                <th>日付</th>
                <th>曜日</th>
                <th>出勤</th>
                <th>退勤</th>
                <th>休憩</th>
                <th>備考</th>
            </tr>
            </tbody>
            {% for i in table %}
    {% if forloop.counter|modulo:2 == 1 %}
        <tr class="table-primary">
    {% else %}
            <tr class="table-light">
        {% endif %}
            <td>{{ i.name }}</td>
            <td>{{ i.date|date:"n-d" }}</td>
            <td>{{ i.date|date:"l" }}</td>
            <td>{{ i.in_time }}</td>
            <td>{{ i.out_time }}</td>
            <td>{{ i.rest_time }}</td>
            <td>{{ i.other }}</td>
    </tr>
    {% endfor %}
        </table>

settings.py

INSTALLED_APPS = [
    # 追加
    'project',
]


views.py

def work(request):
    params = {'form': WorksForm()}
    initial_dict = dict(name=request.user, date=datetime.date.today(), in_time='09:00:00',
                        out_time=datetime.datetime.now().strftime('%H:%M:%S'), rest_time='01:00:00')
    if request.method == 'POST':
        form = WorksForm(request.POST)
        """
        送信を押して名前日付の条件で
        0件は追加1件は変更それ以外は削除
        最後にログイン情報と現在月でグラフを表示
        
        """
        if 'btn' in request.POST:
            w = Works.objects.filter(name=request.POST['name'], date=request.POST['date'])
            if w.count() == 0:  # 追加
                w1 = Works(name=request.POST['name'], date=request.POST['date'],
                           in_time=request.POST['in_time'], out_time=request.POST['out_time'],
                           rest_time=request.POST['out_time'], other=request.POST['other'])
                w1.save()
                params['check'] = "追加しました!"

            elif w.count() == 1:  # 変更
                w = Works.objects.get(name=request.POST['name'], date=request.POST['date'])
                w.in_time = request.POST['in_time']
                w.out_time = request.POST['out_time']
                w.rest_time = request.POST['rest_time']
                w.other = request.POST['other']
                w.save()
                params['check'] = "変更しました!"
            else:
                params['check'] = "エラーがあります。件数=" + str(w.count())
        # 表示する
        params['table'] = Works.objects.filter(
            name=request.POST['name'],
            date__year=request.POST['date'][:4],
            date__month=request.POST['date'][5:7]
        )
        params['form'] = form

        else:
        params['date'] = str(datetime.date.today())
        params['form'] = WorksForm(initial=initial_dict)

    # 表示する
    params['table'] = Works.objects.filter(
        name=request.user,
        date__year=params['date'][:4],
        date__month=params['date'][5:7]
    )
    params['test1'] = Works.objects.filter(
        name=request.user,
        date__year=params['date'][:4],
        date__month=params['date'][5:7]
    ).values_list('in_time', 'out_time', flat=False)
    time_sum = datetime.timedelta()
    datetime.datetime.strptime(datetime.time(9, 0, 0).strftime('%H:%M:%S'), '%H:%M:%S')
    for i in params['test1']:
        a = datetime.datetime.combine(datetime.date(2022, 1, 1), i[0])
        b = datetime.datetime.combine(datetime.date(2022, 1, 1), i[1])
        time_sum += b - a
    time_sum = time_sum / datetime.timedelta(hours=1)
    params['total'] = "名前:" + str(request.user) + "<br>出勤:" + str(round(time_sum, 1)) + "時間"
    return render(request, 'work.html', params)


以前はとりあえずrequest.POSTをparamsという辞書に入れていたのですが、どっちが良いのだろう...


git

あんまり詳しくないので下記のサイトを参考にしてください。
【Windows】Gitの環境構築をしよう! | プログラミングの入門なら基礎から学べるProgate[プロゲート]

gitignoreは.ideaに入っているんですけど私は分かりづらいので一つ上の階層に新しく作っています。


.gitignore

#コメント
*.pyc
*.log
.idea/

requirements.txtの出力

pip list  # 確認
pip freeze > requirements.txt
公開

AWSでやります。

EC2インスタンスを作成

windowsであればPuTTYを使用して接続
PuTTY を使用した Windows から Linux インスタンスへの接続 - Amazon Elastic Compute Cloud

設定
タイムゾーンとか言語などの初期設定をした後、pythonをインストール

本番環境用のsettings.pyを作成。
sqlite3のバージョンが古くて起動できなかったのでバージョンアップしました。
Django2.2で開発サーバー起動時にSQLite3のエラーが出た場合の対応 - Qiita
出来なかったのでPostgreSQLを使うことにしました。(pip install pysqlite3で出来たかも)

NginxとGunicornをインストールしてアクセス確認。
あとはドメイン変更してHTTPS化して完了。


以上、お疲れさまでした。

【PostgreSQL】バックアップとリストア

バックアップ
例
pg_dump -U ユーザー名 --format=出力形式 --file=出力先 バックアップを取るDB名

私の環境
pg_dump -U postgres --format=p --file=C:\Users\省略\20220427.db postgres

パスワード聞かれる

出力されたファイルはメモ帳で内容が分かる

リストア
createdb -U postgres newdb  # 今回はnewdbを作成 

psql -h ホスト名 -U ユーザー名 -d リストア先のDB -f .sqlファイルのパス
psql -h localhost -U postgres -d newdb -f C:\Users\省略\20220427.db postgres


確認

psql -U postgres

\c newdb

\dt


newdbは使わないので削除

DROP DATABASE newdb;


以上、お疲れさまでした。


追記:5/3
ちょっとわかりづらかったのでまとめる

# 退避や削除してから行う
ALTER DATABASE postgres rename to postgres_bk_date;
DROP DATABASE postgres;

# 移行
CREATE DATABASE -U postgres postgres
psql -h localhost -U postgres -d newdb -f 20220427.db postgres

# 確認
\l


追記:5/16
リストアするのがすごく面倒くさいので

CREATE DATABASE postgres_bk_20220516 TEMPLATE postgres;

【Django】shellでユーザーを作成

よく忘れるのでメモ

from django.contrib.auth.models import User
  
user = User.objects.create_user('name','mailaddress','password')
user.save()


viewsでの使い方

if request.user.is_authenticated:
    print("ログイン者:", request.user)
else:
    print("ログインしていません")

【Django】ロギングでサーバーのログをファイルに記録する

はじめに

コンソールに出ていたログをファイルにも同時に出力して後から見返すために作成しました。
エラーはメールで通知するようにしているんですけどエラー以外も記録したほうが良いのかなって思ってファイルに記録します。
【Django】500エラーをメールで通知しよう! - hikaru2323’s diary

方法

ロギング | Django ドキュメント | Django

ロギングの設定を一切してなかったのでドキュメントにある例をコピペしてみる

BASE_DIR = Path(__file__).resolve().parent.parent

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'file': {
            'level': 'INFO',  # 変更前DEBUG
            'class': 'logging.FileHandler',
            # 'filename': '/path/to/django/debug.log',
            'filename': str(BASE_DIR) + '/settings/debug.log'
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file'],
            'level': 'INFO',  # 変更前DEBUG
            'propagate': True,
        },
    },
}


levelとfilenameを変更して私の環境ではこんな感じで無事ファイル書き込み成功。
ただ、これだとファイルが大きくなりそう&gitにアップロードしないようにもしたい。

# 上で宣言
import os
from datetime import datetime

month = str(BASE_DIR) + '/static/hide/' + datetime.now().strftime('%Y-%m') + '.log'
# month= (省略)/static/hide/2022-03.log
if not os.path.exists(month):
    f = open(month, 'w')
    f.write('')
    f.close()
    print("ログファイルを作成しました!", month)

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
        },
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': month,
        },
    },
    'loggers': {
        'django': {
            'handlers': ['file', 'console'],
            'level': 'INFO',
            'propagate': True,
        },
    },
}


2022-03.logを作ったり書き込みをする変数monthを用意し、gitにアップロードしない方法は以下を参照してください。
【Django】個人情報をgitにアップしないようにする - hikaru2323’s diary

'handlers': ['file', 'console'],のところはドキュメントの最後の例を真似しました。(ここ時間かかった。)
作り終わってから気づいたんですけど、filenameは「"filename": os.path.join(LOG_BASE_DIR, "django.log"),」こんな感じのが良いかもしれません。

まとめ

・ログのファイル出力をした
・ファイルが膨張しないように年月ごとにログファイルを作成した
・ログ出力に個人情報も含まれてるのでgitを使う際は注意

BIG5性格診断をやってみた3

4か月ぶり3回目の性格診断です。
前回:BIG5性格診断をやってみた2 - hikaru2323’s diary

3回ともマイペースで知的感性が低いというのは一貫しているのでそこら辺対策したほうが良いかもしれないですね。
プログラムエラーでストレス感じることが少なくなってきたせいか「良いストレス状況」と結果に出ていました。

f:id:hikaru2323:20220316130702p:plain

テストの信頼性

この診断に対する姿勢、自己理解
≫この検査の信頼性の偏差値は「S」です。(S~Cまでの4段階評価)

この結果はやや不自然な所があります。この検査に対し、建前で回答し、結果には普段より高く評価した自分が反映されている可能性があります。従って得点がやや高くなっているかもしれません。自分の欠点を少し認めすぎて回答した可能性があります。防衛的、否定的で、自分の欠点を認めないように回答した可能性があります。以上のことを考慮し、以下の文章を読んで下さい。

交流力

社会に対する向き合い方
≫あなたの交流力の偏差値は「35」です。

総合的に内向的な人と言えるでしょう。心は外向きで、打たれ強く、対人関係は出過ぎることもなく、消極的なこともありません。また、社会的な接触を好む方で、人前が全く気にならず、社交的な場所では人並みに振る舞うことができます。

調和力

他者に対する接し方
≫あなたの調和力の偏差値は「76」です。

総合的に自分より他人を優先する、とても協調性がある人と言えるでしょう。人に対して思いやりがある方で、他人に敵意を感じたときは、その敵意が表に出ないように努力し、過剰なまでに敵意がないことを強調します。また、他人を疑いやすく、頑固で怒りっぽいところがあります。自分の心理的問題や精神的な不安をあまり認めようとせず、やや心に余裕がないため、人に対して融通が利かないところがあります。他人には肯定的に接することを心がけているため、人に協力的です。やや反社会的行動を起こす傾向があり、法律などの社会的規範をやや尊重しない傾向にあります。

適応力

社会に対する適応力
≫あなたの適応力の偏差値は「55」です。

総合的に、真面目な方で、平均的な社会性を持っています。勤勉さは標準的であり、良識性があり、物事に対する判断は、あまり感情に流されることはありません。人の考えや意見は素直に受け入れることができ、責任感は人並みに持ち、人から頼られることもあります。一方、自分に対して、あまり劣等感を感じることはありません。

精神力

メンタリティー、心の安定性
≫あなたの精神力の偏差値は「35」です。

総合的に感情の起伏が激しく、落ち着きのない人と言えるでしょう。ときどき感情的になることがあり、かなりのんきで受動的な性格で、攻撃的な感情がわき起こったとしても、それを認めようとしません。また、人に対する不信感は標準的で、不安や緊張が高く、神経質になりがちです。

創造力

物事に対するクリエイティビティ
≫あなたの創造力の偏差値は「44」です。

総合的に知的感性の少し低い人と言えるでしょう。物事に対してかなり洗練した考え方をします。議論の場では、自分の意見を持ち、適度に自己を主張します。自分の能力に自信があり、あまり他人には頼りません。また、決断力はやや低く自分の知識や経験を人に伝えることが苦手で、指導力は少し低いでしょう。思考方法は一般的で、特に物事を論理的に考える方でも、頭が固い方でもありません。

ストレス

今のストレス状況
≫あなたのストレス状況の偏差値は「53」です。

総合的に、現在程良いストレス状況にあります。楽観的な傾向にあり、不安や心配があったとしてもそれを認めて、克服できています。また、緊張状態であり、心が落ち着きません。強いストレスを感じている兆候はありません。また、心労の水準は普通で、ほどよい精神状況です。心に問題を抱えている可能性は低いでしょう。

ストレス耐性

ストレスの受け止め方
≫あなたのストレス耐性の偏差値は「49」です。

総合的に、ストレス耐性は平均的です。人並みに問題対処能力をもち、ストレス時に混乱することはあまりありません。また、感受性はほどよく、不安や緊張に捕らわれても普通に対処できます。ストレスの原因から逃げることなく、受けとめることができます。また、精神的に異常な部分はほとんどみられません。