Pythonでスケジュール管理表のテンプレートを自動で作成する

## はじめに
PythonでExcelを自動化したい、Pythonでもっと業務を効率するスキルを身につけたい...とふと思ったときにスケジュール管理表を自動で作成するコードを書いてみました。

完成すると下記のようになります。


![スクリーンショット 2024-06-23 11.18.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780659/982234dd-34a7-c367-70bb-cacbdce73b7a.png)


## 事前準備

GoogleDriveにアップロードするため、認証情報を作成する。

https://qiita.com/Statistical_Science/items/1e860fede222b624e754

## Excelファイル作成
openpyxlライブラリをインポートし、Excelファイルを作成します。

```ruby
import datetime
import locale
import openpyxl
from datetime import timedelta
from openpyxl.styles import Alignment, Border, Side, Font
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import FormulaRule
from openpyxl.worksheet.datavalidation import DataValidation
from create_schedule_test import GoogleDriveFacade

# ブックを作成
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
wd = openpyxl.Workbook()
sh = wd.active
sh.title = "スケジュール管理表"
```
openpyxl.Workbook()で新規ブックを作成。
曜日を取得するため、locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')を設定。
activeでsheet1を対象。
titleで選択中のシート名を変更。


## ヘッダの作成
```ruby
# ヘッダ作成
sh.column_dimensions["A"].width = 5
sh["a2"] = "No"
sh["a2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("a2:a3")
sh["b2"] = "タスク"
sh["b2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("b2:d3")
sh.column_dimensions["E"].width = 15
sh["e2"] = "メモ"
sh["e2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("e2:e3")
sh["f2"] = "優先度"
sh["f2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("f2:f3")
sh["g2"] = "担当者"
sh["g2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("g2:g3")
sh.column_dimensions["H"].width = 15
sh["h2"] = "ステータス"
sh["h2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("h2:h3")
sh["i2"] = "作業予定"
sh["i2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("i2:j2")
sh["k2"] = "作業実績"
sh["k2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("k2:l2")
sh["i3"] = "開始"
sh["i3"].alignment = Alignment(horizontal="center", vertical="center")
sh["j3"] = "終了"
sh["j3"].alignment = Alignment(horizontal="center", vertical="center")
sh["k3"] = "開始"
sh["k3"].alignment = Alignment(horizontal="center", vertical="center")
sh["l3"] = "終了"
sh["l3"].alignment = Alignment(horizontal="center", vertical="center")
sh["m2"] = "進捗率"
sh["m2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("m2:m3")
sh["n2"] = "備考"
sh["n2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("n2:n3")

```
column_dimensions["指定列"].widthで列の幅を指定。
column_dimensions["指定行"].heightで行の幅を指定。
["指定セル"] = 文字列で値の挿入。
["指定セル"].alignmentで値の配置を指定。
merge_cells("セル範囲")でセル結合。


## 枠線を作成
```ruby
# 枠線を作成
side1 = Side(style="medium", color="000000")
side2 = Side(style="dotted", color="000000")
side3 = Side(style="double", color="000000")

for rows in sh["A4:N30"]:
    for cell in rows:
        cell.border = Border(left=side2, right=side2, top=side2, bottom=side2)

for rows in sh["A2:N3"]:
    for cell in rows:
        cell.border = Border(left=side3, right=side3, top=side3, bottom=side3)

for rows in sh["O2:AR30"]:
    for cell in rows:
        cell.border = Border(left=side2, right=side2, top=side2, bottom=side2)

```
Side(style="線のスタイル", color="RGB形式の色指定")

線のスタイルについて
・hair:極細
・thin:通常の太さ
・medium:通常と太線の中間
・thick:太線
・double:二重線

Border()オブジェクトで罫線を引くことができます。
Border(left, right, top, bottom)

## 日付と曜日の挿入

```ruby
# 日付を入力
start_date = datetime.date.today()
num_days = 30

headers = [(start_date + timedelta(days=i)) for i in
           range(num_days)]

weekdays = [(start_date + timedelta(days=i)).strftime("%a") for i in
            range(num_days)]

# スタイルの設定
header_font = Font(bold=True)
# 日付と曜日の設定
for col in range(1, len(headers) + 1):
    date_cell = sh.cell(row=2, column=14 + col)
    date_cell.number_format = 'm/d'
    date_cell.value = headers[col - 1]
    weekday_cell = sh.cell(row=3, column=14 + col)
    weekday_cell.number_format = 'aaa'
    weekday_cell.value = weekdays[col - 1]
    date_cell.alignment = Alignment(horizontal="center", vertical="center")
    weekday_cell.alignment = Alignment(horizontal="center", vertical="center")
```
まずは、30日間の日付と曜日をリストに格納します。
作成したリストをfor文で繰り返し、指定セル.valueで値を入れます。
セル.number_formatで表示形式を変更できます。日付:m/d, 曜日:aaa
Alignment()で値の配置位置を設定しています。

## 条件付き書式
```ruby
# 条件付き書式
def colormake(types, start, end):
    return PatternFill(fill_type=types, start_color=start, end_color=end)


grayfill = colormake('solid', 'd3d3d3', 'd3d3d3')
goldfill = colormake('solid', 'ffd700', 'ffd700')
bluefill = colormake('solid', '87cefa', '87cefa')
redfill = colormake('solid', 'ffc0cb', 'ffc0cb')

for rows in sh.iter_rows(min_row=13, min_col=2, max_row=100, max_col=7):
    for cell in rows:
        cell.number_format = "m/d"
sh.conditional_formatting.add('A4:AR30', FormulaRule(
    formula=['AND(NOT($L4=""), $H4="完了")'], stopIfTrue=True, fill=grayfill))
sh.conditional_formatting.add('O2:AR30', FormulaRule(
    formula=['OR(O$3="土", O$3="日")'], stopIfTrue=True, fill=grayfill))
sh.conditional_formatting.add('O4:AR30', FormulaRule(
    formula=['AND($K4<=O$2, $L4>=O$2)'], stopIfTrue=True, fill=goldfill))
sh.conditional_formatting.add('O4:AR30', FormulaRule(
    formula=['AND($I4<=O$2, $J4>=O$2)'], stopIfTrue=True, fill=bluefill))

```
PatternFill(fill_type="塗りつぶしパターン", start_color, end_color)で条件を満たした場合の色を設定します。
選択シート.conditional_formatting.add("適用範囲", FormulaRule(formula=[適用条件], stopIfTrue=True, fill=色))


## プルダウンリスト
```ruby
# プルダウン選択リストの作成
# Data validationオブジェクトの初期化
# formula1引数でドロップダウンリストの項目を指定
dv_status = DataValidation(
    type="list",
    formula1='"着手,実施中,レビュー中,修正中,完了"',
    allow_blank=True,
    showErrorMessage=True,
    errorStyle="warning",
    errorTitle="選択リストにない場合のみ、入力してください",
    error="続けますか？"
)

dv_percentage = DataValidation(
    type="list",
    formula1='"25%,50%,70%,90%,100%"',
    allow_blank=True,
    showErrorMessage=True,
    errorStyle="warning",
    errorTitle="選択リストにない場合のみ、入力してください",
    error="続けますか？"
)
# セルに適用する
# このように指定すると、カラムにプルダウンリストが設定されます
dv_status.add(f"H4:H30{sh.max_row}")
dv_percentage.add(f"M4:M30{sh.max_row}")

# シートに追加する
sh.add_data_validation(dv_status)
sh.add_data_validation(dv_percentage)
```
DataValidation()オブジェクトを生成し、.addで指定列にプルダウンリストを設定。
選択シート.add_data_validation()で反映されます。

## 保存とアップロード
```ruby
wd.save(
    "/Users/xxxx/Project/2024/20240604_Excelスケジュール管理表自動作成/プログラム/src/【テンプレート】yyyymmdd_スケジュール管理表.xlsx")

g = GoogleDriveFacade()

g.upload(
        local_file_path='【テンプレート】yyyymmdd_スケジュール管理表.xlsx',
        save_folder_name="2024_プロジェクト",
        is_convert=True,
)
```
ブック.save("保存先フルパス")で任意のファイル名で保存されます。
下記の記事で作成したGoogleDriveFacadeクラスのインスタンスを作成し、upload()関数を呼び出すことでGoogleDriveに作成したExcelをアップロードできます。

https://qiita.com/Statistical_Science/items/1e860fede222b624e754

## 全体コード
ここまでご覧いただきましてありがとうございました。少しでもみなさまのお役に立てたら幸いです。
最後に全体のコードになります。

```ruby
import datetime
import locale
import openpyxl
from datetime import timedelta
from openpyxl.styles import Alignment, Border, Side, Font
from openpyxl.styles import PatternFill
from openpyxl.formatting.rule import FormulaRule
from openpyxl.worksheet.datavalidation import DataValidation
from create_schedule_test import GoogleDriveFacade


# ブックを作成
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
wd = openpyxl.Workbook()
sh = wd.active

# ヘッダ作成
sh.column_dimensions["A"].width = 5
sh["a2"] = "No"
sh["a2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("a2:a3")
sh["b2"] = "タスク"
sh["b2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("b2:d3")
sh.column_dimensions["E"].width = 15
sh["e2"] = "メモ"
sh["e2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("e2:e3")
sh["f2"] = "優先度"
sh["f2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("f2:f3")
sh["g2"] = "担当者"
sh["g2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("g2:g3")
sh.column_dimensions["H"].width = 15
sh["h2"] = "ステータス"
sh["h2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("h2:h3")
sh["i2"] = "作業予定"
sh["i2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("i2:j2")
sh["k2"] = "作業実績"
sh["k2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("k2:l2")
sh["i3"] = "開始"
sh["i3"].alignment = Alignment(horizontal="center", vertical="center")
sh["j3"] = "終了"
sh["j3"].alignment = Alignment(horizontal="center", vertical="center")
sh["k3"] = "開始"
sh["k3"].alignment = Alignment(horizontal="center", vertical="center")
sh["l3"] = "終了"
sh["l3"].alignment = Alignment(horizontal="center", vertical="center")
sh["m2"] = "進捗率"
sh["m2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("m2:m3")
sh["n2"] = "備考"
sh["n2"].alignment = Alignment(horizontal="center", vertical="center")
sh.merge_cells("n2:n3")

# タスク生成
for i in range(1, 27):
    sh.append([i])

sh["B4"] = "仕様検討"
sh["C5"] = "仕様確定"
sh["B7"] = "設計書"
sh["C8"] = "設計書作成"
sh["C9"] = "設計書レビュー"
sh["B11"] = "製造"
sh["C12"] = "PG作成"
sh["B14"] = "単体テスト"
sh["C15"] = "単体テストケース作成"
sh["C16"] = "単体テストケースレビュー"
sh["C17"] = "単体テスト実施"
sh["C18"] = "単体テストレビュー"
sh["B20"] = "結合テスト"
sh["C21"] = "結合テストケース作成"
sh["C22"] = "結合テストケースレビュー"
sh["C23"] = "結合テスト準備"
sh["C24"] = "結合テスト実施"
sh["C25"] = "結合テストレビュー"
sh["B27"] = "本番"
sh["C28"] = "資材準備"
sh["C29"] = "手順書作成"
sh.column_dimensions["C"].width = 20


# 枠線を作成
side1 = Side(style="medium", color="000000")
side2 = Side(style="dotted", color="000000")
side3 = Side(style="double", color="000000")

for rows in sh["A4:N30"]:
    for cell in rows:
        cell.border = Border(left=side2, right=side2, top=side2, bottom=side2)

for rows in sh["A2:N3"]:
    for cell in rows:
        cell.border = Border(left=side3, right=side3, top=side3, bottom=side3)

for rows in sh["O2:AR30"]:
    for cell in rows:
        cell.border = Border(left=side2, right=side2, top=side2, bottom=side2)

# 日付を入力
start_date = datetime.date.today()
num_days = 30

headers = [(start_date + timedelta(days=i)) for i in
           range(num_days)]

weekdays = [(start_date + timedelta(days=i)).strftime("%a") for i in
            range(num_days)]

# スタイルの設定
header_font = Font(bold=True)
# 日付と曜日の設定
for col in range(1, len(headers) + 1):
    date_cell = sh.cell(row=2, column=14 + col)
    date_cell.number_format = 'm/d'
    date_cell.value = headers[col - 1]
    weekday_cell = sh.cell(row=3, column=14 + col)
    weekday_cell.number_format = 'aaa'
    weekday_cell.value = weekdays[col - 1]
    date_cell.alignment = Alignment(horizontal="center", vertical="center")
    weekday_cell.alignment = Alignment(horizontal="center", vertical="center")


# 条件付き書式
def colormake(types, start, end):
    return PatternFill(fill_type=types, start_color=start, end_color=end)


grayfill = colormake('solid', 'd3d3d3', 'd3d3d3')
goldfill = colormake('solid', 'ffd700', 'ffd700')
bluefill = colormake('solid', '87cefa', '87cefa')
redfill = colormake('solid', 'ffc0cb', 'ffc0cb')

for rows in sh.iter_rows(min_row=13, min_col=2, max_row=100, max_col=7):
    for cell in rows:
        cell.number_format = "m/d"
sh.conditional_formatting.add('A4:AR30', FormulaRule(
    formula=['AND(NOT($L4=""), $H4="完了")'], stopIfTrue=True, fill=grayfill))
sh.conditional_formatting.add('O2:AR30', FormulaRule(
    formula=['OR(O$3="土", O$3="日")'], stopIfTrue=True, fill=grayfill))
sh.conditional_formatting.add('O4:AR30', FormulaRule(
    formula=['AND($K4<=O$2, $L4>=O$2)'], stopIfTrue=True, fill=goldfill))
sh.conditional_formatting.add('O4:AR30', FormulaRule(
    formula=['AND($I4<=O$2, $J4>=O$2)'], stopIfTrue=True, fill=bluefill))

# プルダウン選択リストの作成
# Data validationオブジェクトの初期化
# formula1引数でドロップダウンリストの項目を指定
dv_status = DataValidation(
    type="list",
    formula1='"着手,実施中,レビュー中,修正中,完了"',
    allow_blank=True,
    showErrorMessage=True,
    errorStyle="warning",
    errorTitle="選択リストにない場合のみ、入力してください",
    error="続けますか？"
)

dv_percentage = DataValidation(
    type="list",
    formula1='"25%,50%,70%,90%,100%"',
    allow_blank=True,
    showErrorMessage=True,
    errorStyle="warning",
    errorTitle="選択リストにない場合のみ、入力してください",
    error="続けますか？"
)
# セルに適用する
# このように指定すると、列全体にプルダウンリストが設定されます
dv_status.add(f"H4:H30{sh.max_row}")
dv_percentage.add(f"M4:M30{sh.max_row}")

# シートに追加する
sh.add_data_validation(dv_status)
sh.add_data_validation(dv_percentage)

wd.save(
    "/Users/xxxx/Project/2024/20240604_Excelスケジュール管理表自動作成/プログラム/src/【テンプレート】yyyymmdd_スケジュール管理表.xlsx")

g = GoogleDriveFacade()

g.upload(
        local_file_path='【テンプレート】yyyymmdd_スケジュール管理表.xlsx',
        save_folder_name="2024_プロジェクト",
        is_convert=True,
)
```