コンテンツにスキップ

サイトコピー

概要

APIを使用してサイト設定を更新することができます。

制限事項

  1. 期限付きテーブルおよび、記録テーブルのみ使用可能です。

事前準備

APIの操作を行う前にAPIキーの作成を実施してください。また、この機能はテナント管理者でないと行えないため、ユーザ管理からテナント管理者の設定を行ってください。

リクエスト

下記のリクエスト形式で、jsonデータを送信します。

設定項目 値
HTTPメソッド POST
Content-Type application/json
文字コード UTF-8
URL http://{サーバ名}/api/items/{サイトID}/copysitepackage(※1)
Body 以下のjsonデータを参考のこと

(※1){サーバ名}、{サイトID}の部分は、適宜、環境に合わせて編集してください。
  pleasanter.netの場合は以下の形式になります。
  https://pleasanter.net/fs/api/items/{サイトID}/copysitepackage
  {サイトID}には、コピーするサイトのうちタイトル更新対象のサイトIDを指定してください。

JSON
{
    "ApiVersion": "1.1",
    "ApiKey": "345yuAjA6789dA09d8uj6...",
    "TargetSiteId": 466220,
    "SiteTitle": "test",
    "SelectedSites": [
        {
            "SiteId": 466217
        },        
        {
            "SiteId": 466218,
            "IncludeData": false
        }
    ],
    "IncludeSitePermission": false,
    "IncludeRecordPermission": true,
    "IncludeColumnPermission": false,
    "IncludeNotifications": true,
    "IncludeReminders": true
}
カラム 設定内容
TargetSiteId Copy先のフォルダのサイトIDを指定します。省略する場合、トップ画面にコピーします。
SiteTitle URLの{サイトID}に記載したサイトのタイトルを指定します。サイトのタイトルを変更する場合に使用してください。
SelectedSites コピーするサイトを指定します。記載方法は、サイトパッケージをエクスポートした際の"Convertors"パラメータと同等の値を設定してください。

※IncludeSitePermission以降のパラメータについては、サイトパッケージをエクスポートした際の、"HeaderInfo"パラメータと同等の値を設定してください。

レスポンス

下記の形式のjsonデータが返却されます。

JSON
{
    "StatusCode": 200,
    "Response": {
        "Data": "[
            {\"OldSiteId\":466217,\"NewSiteId\":466221,\"ReferenceType\":\"Sites\",\"Title\":\"test\"},
            {\"OldSiteId\":466218,\"NewSiteId\":466222,\"ReferenceType\":\"Results\",\"Title\":\"記録テーブル\"}
        ]"
    }
}

サンプルコード

Note

コード内の【 ... 】は適宜修正してください。

1. 案件一覧(CSV)から、案件ごとのフォルダをテンプレートから作る

案件一覧(CSV)の1行ごとに、テンプレートのフォルダをサイトコピーAPIで複製し、フォルダ名を「案件コード 案件名」にします。フォルダの中の「作業区分」はレコードごと、「作業明細」は空で複製します。複製したフォルダとサイトのIDは、結果のCSVに出力します。

実務での使いどころ

案件・プロジェクト・顧客ごとに、同じ構成のテーブル一式を用意する運用に使えます。画面のサイトコピーでは、案件ごとにコピーの操作と名前の変更を繰り返し、権限や通知を含めるかをその都度選ぶ必要があります。このサンプルでは、CSVに案件を並べて実行すれば、含める設定をそろえたまま、まとめて作れます。テンプレートの中のリンク(作業明細の「作業区分」)は、同じフォルダに複製された作業区分を指すように付け替わります。

事前準備

1:テンプレートとして、フォルダを1つ作り、その中に以下のテーブルを作成してください。

テーブル 種類 内容
作業区分 記録テーブル 「設計」「テスト」などの区分をレコードとして登録しておく。レコードごと複製する
作業明細 期限付きテーブル 分類A(ClassA)を作業区分へのリンクにする。空で複製する

2:複製先のフォルダを作成してください。案件ごとのフォルダは、この中に作られます。

3:案件を、1行目を見出し案件コード,案件名にしたCSV(projects.csv、UTF-8)に書きます。

案件コード,案件名
PRJ-001,A社システム更改
PRJ-002,B社保守

4:サンプルコードの【 】を書き替えてください。APIキーは、テナント管理者のものを使います。まずDRY_RUN = Trueで、作るフォルダの名前を確かめてから実行すると安全です。

Python
# ==========================================================
# 案件ごとのサイト作成:案件一覧(CSV)の1行ごとに、テンプレートのフォルダをサイトコピーAPIで
# 複製し、フォルダ名を「案件コード 案件名」にします。作業区分のようなマスタはレコードごと、
# 作業明細は空で複製します。複製したフォルダとサイトのIDは、結果のCSVに出力します。
# ==========================================================
import csv
import json
from pathlib import Path

import requests

# ===== 設定 =====
# APIキーは、テナント管理者のものを使ってください(サイトコピーAPIの前提)
BASE_URL = "【URL】"
API_KEY = "【APIキー】"
TEMPLATE_FOLDER_ID = "【テンプレートのフォルダのサイトID】"
# テンプレートのフォルダの中で、複製するサイト。IncludeData が True のサイトはレコードも複製する
TEMPLATE_SITES = [
    {"SiteId": "【作業区分のサイトID】", "IncludeData": True},
    {"SiteId": "【作業明細のサイトID】", "IncludeData": False},
]
TARGET_FOLDER_ID = "【複製先のフォルダのサイトID】"
INPUT_CSV = Path("./projects.csv")           # 1行目は見出し「案件コード,案件名」
OUTPUT_CSV = Path("./project_sites.csv")
# True のときは複製せず、作るフォルダの名前だけを表示する
DRY_RUN = False

# 権限・通知・リマインダーを複製に含めるか。全案件で同じ設定にする
COPY_OPTIONS = {
    "IncludeSitePermission": False,
    "IncludeRecordPermission": False,
    "IncludeColumnPermission": False,
    "IncludeNotifications": True,
    "IncludeReminders": True,
}


def api_post(path, payload=None):
    """Pleasanter APIへPOSTする。レスポンスのJSONを返す(HTTPエラーでも本文を返す)"""
    response = requests.post(
        f"{BASE_URL}{path}",
        json={"ApiVersion": 1.1, "ApiKey": API_KEY, **(payload or {})},
        headers={"Content-Type": "application/json"},
        timeout=60,
    )
    return response.status_code, response.json()


def check_templates():
    """複製するサイトが、テンプレートのフォルダの中の期限付き・記録テーブルかを確かめ、タイトルを返す。
    存在しないサイトIDを指定すると、エラーにならず空のフォルダが作られるため、複製の前に確かめる"""
    titles = []
    for site in TEMPLATE_SITES:
        status, body = api_post(f"/api/items/{int(site['SiteId'])}/getsite")
        if status != 200:
            raise SystemExit(f"サイトID {site['SiteId']} を取得できません: {body.get('Message')}")
        data = body["Response"]["Data"]
        if data["ParentId"] != int(TEMPLATE_FOLDER_ID) or data["ReferenceType"] not in ("Issues", "Results"):
            raise SystemExit(f"サイトID {site['SiteId']} は、テンプレートのフォルダの中の期限付き・記録テーブルではありません")
        titles.append(data["Title"])
    return titles


def copy_project(folder_title):
    """テンプレートのフォルダを複製し、フォルダ名を folder_title にする。複製元→複製先のIDの対応を返す"""
    status, body = api_post(f"/api/items/{int(TEMPLATE_FOLDER_ID)}/copysitepackage", {
        "TargetSiteId": int(TARGET_FOLDER_ID),
        # SiteTitle は、URLに指定したサイト(テンプレートのフォルダ)の複製先のタイトルになる
        "SiteTitle": folder_title,
        "SelectedSites": [{"SiteId": int(TEMPLATE_FOLDER_ID)}]
        + [{"SiteId": int(site["SiteId"]), "IncludeData": site["IncludeData"]} for site in TEMPLATE_SITES],
        **COPY_OPTIONS,
    })
    # 指定の誤りで複製できなかったときも HTTP 200 で返り、Data に画面向けのメッセージが入る
    rows = json.loads(body["Response"]["Data"]) if status == 200 else []
    if not rows or any("NewSiteId" not in row for row in rows):
        raise RuntimeError(f"「{folder_title}」を複製できませんでした: {body}")
    return {row["OldSiteId"]: row["NewSiteId"] for row in rows}


def read_projects(path):
    with open(path, encoding="utf-8-sig", newline="") as f:
        return [(row["案件コード"].strip(), row["案件名"].strip()) for row in csv.DictReader(f) if row["案件コード"].strip()]


def main():
    projects = read_projects(INPUT_CSV)
    if DRY_RUN:
        for code, name in projects:
            print(f"[DRY_RUN] 作るフォルダ: {code} {name}")
        return
    titles = check_templates()
    with open(OUTPUT_CSV, "w", encoding="utf-8-sig", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(["案件コード", "案件名", "フォルダのサイトID"] + [f"{title}のサイトID" for title in titles])
        for code, name in projects:
            # 失敗したら、そこで止める(それまでに複製した案件は結果のCSVに残る)
            new_ids = copy_project(f"{code} {name}")
            writer.writerow([code, name, new_ids[int(TEMPLATE_FOLDER_ID)]]
                            + [new_ids[int(site["SiteId"])] for site in TEMPLATE_SITES])
            f.flush()
            print(f"複製しました: {code} {name}(フォルダ {new_ids[int(TEMPLATE_FOLDER_ID)]})")


if __name__ == "__main__":
    main()

出力の例(project_sites.csv)

案件コード,案件名,フォルダのサイトID,作業区分のサイトID,作業明細のサイトID
PRJ-001,A社システム更改,4927,4928,4929
PRJ-002,B社保守,4930,4931,4932

注記

  • SelectedSitesにテンプレートのフォルダも含め、URLのサイトIDにテンプレートのフォルダを指定すると、SiteTitleが複製先のフォルダのタイトルに使われます。フォルダの中のサイトは、複製先のフォルダの中に入ります。
  • SelectedSitesは、{"SiteId": サイトID, "IncludeData": true}のようなオブジェクトの配列で指定します。サイトIDの数値の配列で指定すると複製されませんが、HTTPステータスは200のまま、Dataに「ファイルの読込に失敗しました。」のメッセージが入ります。このサンプルでは、Dataに複製先のサイトID(NewSiteId)が無ければ失敗として止めます。
  • SelectedSitesに存在しないサイトIDを入れると、エラーにならず、タイトルの無いフォルダが作られます。このサンプルでは、複製の前にサイト取得APIで、テンプレートのフォルダの中の期限付きテーブル・記録テーブルであることを確かめます。
  • 同じCSVで2回実行すると、同じ名前のフォルダがもう1つ作られます。実行し終えた案件は、CSVから消してください。
  • 権限・通知・リマインダーを複製に含めるかは、COPY_OPTIONSで決めます。全案件に同じ設定を使います。

対応バージョン

対応バージョン 内容
1.2.2.8 以降 機能追加