【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化して完了。
以上、お疲れさまでした。