LoginSignup
45
54

More than 3 years have passed since last update.

Djangoで勤怠管理Webアプリケーションを作ってみた

Last updated at Posted at 2019-05-24

はじめに

現在アルバイトしているバーではタイムカードを表計算ソフトで管理しているのですが、これをもうちょっと楽にできないかなと思い、勤怠管理Webアプリケーションを作ってみました。

まだまだ改善の余地があり、追加したい機能などもありますが、行き詰まりまして...
大枠は完成したので、一度ここでアウトプットして整理してみます。

ソースコードはこちら↓↓
https://github.com/TaroNoguchi/attendance-app

[追記]
:point_up: いつの間にかリポジトリをPrivateにしてしまっていたのでPublicに戻しました。

環境など

OS macOS Mojave
使用言語 Python 3.6.7
フレームワーク Django 2.2
バージョン管理 Git
確認用ブラウザ Google Chrome
制作期間 約2週間

大枠を作成

最初の準備

conda仮想環境構築

Anacondaで仮想環境を作成して、そこで動かしていくので作成します。
詳しくはこちらの方が記事で書いておられますので読んでみてください。→【初心者向け】Anacondaで仮想環境を作ってみる
conda create -n(または--name) 環境名 python(=バージョン指定)
でpythonのバージョンを指定して仮想環境を作成できます。
また、conda activate 指定した環境名で作成した環境をアクティベートできます。

conda create -n py36 python=3.6.7
conda activate py36

Djangoのインストール

この仮想環境を立ち上げた状態でインストールします。

(py36) TaronoMacBook-Pro:clock taro$ conda install django

これで環境構築できました。
今後はここで作業していくので、以降シェルコマンドはこの環境上で動かしているとみなしてください。
実際に環境構築で一番手間取ったかもしれません。最終的に動いているのはこの状態なのですが、ここまで来るのに右往左往してしまいました。

Djangoでの最初の作業

startproject

プロジェクトのディレクトリを保存したい場所でターミナルを開き、以下のコマンドを入力。
clockは勝手につけた名前。

django-admin startproject clock

次のような構造のディレクトリができます。

clock
├── clock
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   ├── settings.cpython-36.pyc
│   │   ├── urls.cpython-36.pyc
│   │   └── wsgi.cpython-36.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

外側のclockディレクトリはただの入れ物なのでリネーム可能。

startapp

以下のコマンドを入力し、外側のディレクトリ内(このプロジェクトのルートディレクトリ)でattendanceという名前のアプリの入れ物を作ります。

python manage.py startapp attendance

次のようにattendanceディレクトリができます。

clock
├── attendance
│   ├── __init__.py
│   ├── __pycache__
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── clock
└── manage.py

よく使うDjangoコマンド

  • Djangoのデータベースをマイグレーションコマンドで作成。
python manage.py makemigrations
python manage.py migrate

2段階のコマンドで、一度マイグレーションする物のリストを作ってから、実際にマイグレーションしています。(git add から git commit の流れに似ています)

  • サーバーを動かす。
python manage.py runserver

Djangoの全体像

django_archi.png

超ざっくりした絵ですみません笑

とっても簡略化すると、

1.ブラウザからリクエストされたURLをDjangoのURLディスパッチャなるコアモジュールが受ける
2.そのディスパッチャが、urls.pyに記述したURLconf(URLのパターンとそれぞれのパターンに対するリクエストの送り先を示した地図のようなもの)に従って、ビューにリクエストを送る
3.ビューがモデルオブジェクトを取得してビジネスロジックを実行する(状況に応じてmodel.pyの内容とデータベースが同期される)
4.最終的にビューが情報をテンプレートにレンダリングしてレスポンスを作成する
5.作成されたレスポンスをディスパッチャがブラウザに送る

という流れです。
主にコードを書いていくのはmodels.pyとviews.pyになります。あとテンプレートファイルとしてのHTMLファイルですね。

setting.pyの編集

Djangoの設定ファイルを編集します。

setting.py
INSTALLED_APPS = [
    'attendance.apps.AttendanceConfig', # djangoにattendanceアプリの存在を知らせる
    #...
]

#...

LANGUAGE_CODE = 'ja' # 言語を日本語に  

TIME_ZONE = 'Asia/Tokyo' # タイムゾーンを日本時間に

URLconfの編集

urls.pyを編集します。
ルートのURLconfにコードがたくさん書かれるのを防ぐために、アプリケーションごとにURLconfを用意し、ルートには各アプリのURLconfへの道を教えてあげます。

↓ルート

clock/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('attendance.urls')),
    path('admin/', admin.site.urls),
]

↓attendanceアプリ(新しくurls.pyファイルを作成してください)

attendance/urls.py
from django.urls import path
from . import views # ←これ忘れないようにする!!

app_name = 'attendance'
urlpatterns = [
    path('', views.index, name='index'),
    path('result/', views.result, name='result'),
]

他の場所からの呼び出しが簡単になるように、app_name変数を定義してください。
まだviews.pyには何もありません。これから記述していきます。

モデルの作成

勤怠時間の打刻モデルということで、SubmitAttendanceクラスを作成し、モデルとします。

attendance/models.py
from django.db import models
from django.contrib.auth import get_user_model

# Create your models here.
class SubmitAttendance(models.Model):

    class Meta:
        db_table = 'attendance'

    PLACES = (
        (1, 'Bar Foo'),
        (2, 'Bar Baz'),
        (3, 'Bar Qux'),
        (4, 'Bar Quux'),
        (5, 'Bar Corge'),
        (6, 'Bar Grault'),
    )
    IN_OUT = (
        (1, 'IN'),
        (0, 'OUT'),
    )

    staff = models.ForeignKey(get_user_model(), verbose_name="スタッフ", on_delete=models.CASCADE, default=None)
    place = models.IntegerField(verbose_name='出勤場所名', choices=PLACES, default=None)
    in_out = models.IntegerField(verbose_name='IN/OUT', choices=IN_OUT, default=None)
    time = models.TimeField(verbose_name="打刻時間")
    date = models.DateField(verbose_name='打刻日')
  • models.Modelを継承したクラスを定義します。
  • データベースに格納された時のメタデータとして、テーブル名を与えています。
  • バーの店舗と、出勤or退勤の選択肢を作るために、ここでタプルに格納しています。(もちろん店舗名は架空の名前に置き換えています。笑)
  • 変数は以下の通り↓
    • staff:ログイン中のユーザー名
    • place:PLACESタプルから選ぶ
    • in_out:IN_OUTタプルから選ぶ
    • time:打刻した時間
    • date:打刻した日付
  • verbose_nameはデータベースのcolumnの名前

フォームの作成

最初のページで、バーの店舗名と出勤か退勤かを選択させるフォームを作ります。
コードはforms.pyを作り、そこに記述します。

attendance/forms.py
from django import forms
from .models import SubmitAttendance

class SubmitAttendanceForm(forms.ModelForm):

    class Meta:
        model = SubmitAttendance
        fields = ('place', 'in_out')

indexビューの作成

最初の画面を出すためのIndexViewと、結果画面を出すためのResultViewに分けています。

attendance/views.py
from django.shortcuts import render, redirect
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import SubmitAttendance
from .forms import SubmitAttendanceForm
from datetime import datetime
from django.utils import timezone
# Create your views here.
class IndexView(LoginRequiredMixin, View):
    def get(self, request):
        form = SubmitAttendanceForm
        context = {
            'form': form,
            "user": request.user,
        }
        return render(request, 'attendance/index.html', context)
index = IndexView.as_view()

※ログインに関する記述がありますが、これは後ほど記述します。

クラスベースのビューを記述しています。
先ほど作成したフォームを、オブジェクトとして変数formに代入。
ログイン中のユーザーを変数userに代入。
context辞書に、テンプレートへ送る変数をまとめて、render関数をつかってリクエストをテンプレートへ送ります。
最後にこのクラスをas_view()を用いて、ビューとしてインスタンス化します。

indexテンプレートの作成

まずはテンプレートファイルを保存するディレクトリを作ります。
attendance/template/attendanceとなるようにディレクトリを新たに2つ追加し、内側のattendanceの中に、base.htmlとindex.htmlを作ります。
templatesディレクトリの中にattendanceディレクトリをもう一度作っていますが、これは「お約束」みたいなものです。

テンプレートの継承

全てのhtmlファイルに共通な部分を、テンプレートファイルを作るごとに何回も何回も書いていたら大変なので、そういった部分はbase.htmlに書いて、index.htmlで継承するという手続きを踏みます。

base.html
{% load static %}
<html>
<head>
    <meta charset='utf-8'>
    <link rel="stylesheet" type="text/css" href="{% static 'attendance/css/style.css' %}">
    <title>勤怠打刻ページ</title>
</head>    
<body>
    {% block content  %}
    {% endblock  %}
</body>
</html>

冒頭で{% extends 継承元 %}と記述することで継承元のファイルを継承し、{% block content %}{% endblock %}の中に内容を記述します。
static(静的)ファイルに関する記述があります。Bootstrapのstyle.cssをstaticディレクトリの中に保存して使っています。

index.html
{% extends "attendance/base.html" %}
{% block content %}

    {% if user.is_authenticated %}
        <div class="container">
            <form method="post" action="{% url 'attendance:result' %}">
            {% csrf_token %}
                <h1>{{  user  }} さん</h1>
                <h2 id="time"></h2>
                    <script>
                    time();
                    function time(){
                        var now = new Date();
                        document.getElementById("time").innerHTML = now.toLocaleString();
                    }
                    setInterval('time()',1000);
                    </script>
                <p>出勤場所:<input type="hidden" name="place">{{ form.place }}</p>
                <p>IN/OUT:<input type="hidden" name="in_out">{{ form.in_out }}</p>
                <p><input class="btn btn-primary" type="submit" value="Submit"></p>
            </form>
            <a href="../accounts/logout">ログアウト</a>
        </div>
    {% endif %}
{% endblock  %}
  • {%%}はテンプレートタグ。HTML上にPythonのようなコードを書くことができるタグです。
  • scriptタグで囲まれた部分は、現在時刻を表示しています。
  • {{ }}で囲まれた部分には、先ほどビューから送った変数です。
  • formタグ内で入力された値はview.resultに送られるようaction属性を指定します。
  • {% csrf_token %}はこのページのように入力データを送るときに記述しなければならない、セキュリティのお約束のようなものです。

resultビューの作成

attendance/views.py
#...
class ResultView(View):
    def post(self, request):
        form = SubmitAttendanceForm(request.POST)
        now = datetime.now()
        month = now.month
        day = now.day
        hour = now.hour
        minute = now.minute

        obj = form.save(commit=False)
        obj.place = request.POST["place"]
        obj.in_out = request.POST["in_out"]
        obj.staff = request.user
        obj.date = datetime.now().date()
        obj.time = datetime.now().time()
        obj.save()
        if request.POST["in_out"] == '1':
            comment = str(month) + "月" + str(day) +"日" + str(hour) + "時" + str(minute) + "分\n" + "出勤確認しました。今日も頑張りましょう!"
        else:
            comment = str(month) + "月" + str(day) +"日" + str(hour) + "時" + str(minute) + "分\n" + "退勤確認しました。お疲れ様でした(^-^)!"
        context = {
            'place': SubmitAttendance.PLACES[int(obj.place)-1][1],
            'comment': comment,
        }
        return render(request, 'attendance/result.html', context)
result = ResultView.as_view()
  • 入力値とともに送られてきたPOSTメソッドのリクエストをformに代入。
  • objが並んでいるところは入力値をモデルの変数に代入し、データベースに保存しています。
  • 出勤か退勤かで変数commentに代入する文字列を変えて、result.htmlに送ります。

resultテンプレートの作成

result.html
{% extends 'attendance/base.html' %}
{% block content %}
    <p>出勤場所:{{ place }}</p>
    <p>{{ comment }}</p>
    <a href="../accounts/logout">ログアウト</a>
{% endblock  %}

実際の作業2(ログインページの作成)

こちらのサイトを参考にしました↓↓
Django2 でユーザー認証(ログイン認証)を実装するチュートリアル -2- サインアップとログイン・ログアウト

設定とURLconf

accountsアプリを作り、setting.pyのINSTALLED_APPに、

accounts.app.AccountsConfig

を記述してaccountsアプリの存在を示してあげます。
また、accountsアプリとルートのURLconfも編集します。

clock/urls.py
#...
#以下を追加
    path('accounts/', include('django.contrib.auth.urls')),
#...
accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    path('login/', views.login, name='login'),
    path('logout/', views.logout, name='logout')
]

Djangoが用意してくれているログインフォームがあるので、ビューの編集はしません。

テンプレート

Djangoではログインページのテンプレートファイルは、accounts/templates/registration/login.htmlというところに書くと決まっていますので、素直にこの構造を作ります。

accounts/templates/registration/login.html
{% extends 'admin/base.html' %}

{% block content %}

{% if form.errors %}
<p>ユーザー名とパスワードが一致しません。</p>
{% endif %}

{% if next %}
    {% if user.is_authenticated %}
    <p>アクセス権のあるアカウントでログインしてください。</p>
    {% else %}
    <p>ログインしてください。</p>
    {% endif %}
{% endif %}

<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table class="container">
<tr>
    <td>{{ form.username.label_tag }}</td>
    <td>{{ form.username }}</td>
</tr>
<tr>
    <td>{{ form.password.label_tag }}</td>
    <td>{{ form.password }}</td>
</tr>
</table>

<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{% endblock %}

これで、以下のようなテンプレートが作成されます。

スクリーンショット 2019-05-24 14.15.33.png

ここまでで、先ほどつくったindex.htmlやresult.htmlは動くと思います。

ただ、ユーザーが登録されていないので、登録します。

実際の作業3(管理者アカウントの作成と管理者ページ)

admin.pyの編集

モデルをDjango管理画面で管理できるようにするには、アプリのディレクトリ内のadmin.pyを次のように編集します。

attendance/admin.py
from django.contrib import admin
from .models import SubmitAttendance
# Register your models here.
admin.site.register(SubmitAttendance)

管理者アカウントの作成

ターミナルでpython manage.py createsuperuserと入力すると、管理者ユーザーの登録内容を求められるので好きな値を入力してください。

管理者ページ

http://127.0.0.1:8000/admin
にアクセスして、先ほど入力したIDとパスワードを入力します。

管理者ページは次のような感じになっています。
スクリーンショット 2019-05-24 14.25.01.jpeg

認証と認可のところのユーザーをクリックすると、ユーザーの登録・削除ができます。

実際の作業4(デモ)

ここまでで作ったものを表示してみます。
http://127.0.0.1:8000
へアクセスし、先ほど登録したユーザーでログインしてみましょう。
以下のような画面が表示されるはずです。

スクリーンショット 2019-05-24 14.42.24.png

出勤場所と、IN/OUTを選択してSubmitすると...

スクリーンショット 2019-05-24 14.42.43.png

こんな感じになります!(hogehogeのところには設定した店舗名が表示されます。)

管理者ページで見てみる(5/28編集)

先ほどの管理者ページ内にあったSubmitAttendanceをクリックすると、登録されたオブジェクトが見れます。

スクリーンショット 2019-05-24 14.25.06.jpeg

index.htmlで値が入力されて生成されたオブジェクトもしっかりあります。
1つクリックしてみると...

スクリーンショット 2019-05-24 14.25.10.jpeg

はい、こんな感じです。

これではみにくいので、同時生成されるデータベースファイルdb.sqlite3をのぞいてみます。

スクリーンショット 2019-05-24 14.37.30.png

見やすいとは言えないですが、何がどこにあるかはわかります。

models.pyの追加編集(5/28)

以下の項目を追加することで、管理者画面のオブジェクト一覧でそれぞれのオブジェクトにと表示されていてわかりにくかったものが、<ユーザー名 : 出勤場所名 IN/OUT>と表示され、わかりやすくなります。

attendance/models.py
#辞書化
    place_dict = dict(PLACES)
    in_out_dict = dict(IN_OUT)

    def __str__(self):
        return  str(User.objects.get(id=self.staff_id)) + ' : ' + str(self.place_dict[self.place]) + ' ' + str(self.in_out_dict[self.in_out])

モデル編集後はターミナルで、makemigrationsとmigrateを実行します。

最後に

長々とまとまりのない文章だったかもしれませんが、最後まで読んでいただいた方、ありがとうございます。

これから

  • 上にあるデータベースファイルから「ユーザー」ごとの「勤務時間」を計算し、お給料を計算するモデルを作成中です。←ここで詰まりました...
  • 出勤場所からしかアクセスできないように、アクセス可能なIPアドレスを制限する。(不正打刻防止のために、これは必須)

参考にしたWebサイトや本

未経験からやってみたのでおそらく累計30~50個(もっとかな?)ほどのwebサイトを参考にさせていただいたと思うのですが、流石に覚えていないので、よく拝見したものを掲載しておきます。

45
54
1

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
45
54