Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jamilraichouni committed Nov 16, 2024
0 parents commit 1f68f44
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/build-and-publish-hkt-day-lists.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
on:
schedule:
# Explanation of the Cron Expression:
# `*/5`: Every 5 minutes.
# `6-8`: From 06:00 AM to 08:59 AM.
# `*`: Every day of the month.
# `*`: Every month.
# `1-5`: Monday to Friday.
- cron: '*/5 6-8 * * 1-5'
pull_request:
branches: [main]
push:
branches: [main]
jobs:
build-and-publish-hkt-day-lists:
name: Setup, build, and publish HKT day lists
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Checkout code
uses: actions/checkout@v4
- name: Install Python packages
run: pip install -r requirements.txt
- name: Install wkhtmltopdf
run: |
sudo apt-get update
sudo apt-get install wkhtmltopdf
- name: Build HKT day lists
env:
SERVICE_ACCOUNT_JSON_BASE64: ${{ secrets.SERVICE_ACCOUNT_JSON_BASE64 }}
id: build-hkt-day-lists
run: |
echo "$SERVICE_ACCOUNT_JSON_BASE64" | base64 --decode > /tmp/elternvertretung-b7713037bac6.json
python build-hkt-day-lists.py
28 changes: 28 additions & 0 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
# SPDX-License-Identifier: CC0-1.0
on:
push:
branches:
- main
- "*/v*.*.*" # Match version tags for releases
pull_request:
branches:
- main

jobs:
code-quality:
runs-on: ubuntu-latest
name: Check code quality
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install pre-commit
run: python -m pip install pre-commit
- name: Run pre-commit
run: pre-commit run --all-files
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Home Kita Tage (HKT)

## Preconditions

### Google account

There is a Google account for the parent representatives. The login user name
is `elternvertretung@bluetezeit-berlin.de` and the password shall be the same
as for the Microsoft (email) account given by the leadership of the
kindergarten.

### Input Excel file stored on Google Drive

Home Kita days are to be named by parents and need to be stored in an Excel
file on Google Drive at <https://drive.google.com/drive/u/3/my-drive>.

The Excel file is named `HomeKitaTage.xlsx` and must be located at top level
in the Google Drive.

The first sheet in the Excel file must come with the following columns:

- `Name`
- `Group`
- `Monday\nmorning`
- `Monday\nafternoon`
- `Tuesday\nmorning`
- `Tuesday\nafternoon`
- `Wednesday\nmorning`
- `Wednesday\nafternoon`
- `Thursday\nmorning`
- `Thursday\nafternoon`
- `Friday\nmorning`
- `Friday\nafternoon`
- `Comments`

Above, the characters `\n` are to be understood as a newline character.

The column `Name` contains the name of the child, `Group` the group to which
the child belongs to in the kindergarten. The entries in the day columns shall
be empty or filled with a number `1` meaning that the child is absent (at home)
at this day and time.
A `1` chas been chosen to be able to sum up the number children that are absent
at a given day and time to see, if the absence times are equally distributed.
211 changes: 211 additions & 0 deletions build-hkt-day-lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import io
import pathlib
import typing as t

import google.oauth2.credentials
import google.oauth2.service_account
import googleapiclient.errors # type: ignore
import googleapiclient.http # type: ignore
import pandas as pd
import pdfkit # type: ignore
from docx import Document
from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls
from docx.shared import Inches
from googleapiclient.discovery import build # type: ignore

HKT_FILE_NAME = "HomeKitaTage.xlsx"
HKT_FILE_PATH = pathlib.Path(f"/tmp/{HKT_FILE_NAME}")
SCOPES = ["https://www.googleapis.com/auth/drive"]
SERVICE_ACCOUNT_FILE = pathlib.Path("/tmp/elternvertretung-b7713037bac6.json")
if not SERVICE_ACCOUNT_FILE.is_file():
SERVICE_ACCOUNT_FILE = pathlib.Path(
"/data/elternvertretung-b7713037bac6.json"
)
CREDENTIALS = (
google.oauth2.service_account.Credentials.from_service_account_file(
SERVICE_ACCOUNT_FILE, scopes=SCOPES
)
)


def dataframe_to_word(df, docx_file_path):
document = Document()

# Set custom margins (e.g., 0.5 inches for top and bottom)
sections = document.sections
for section in sections:
section.top_margin = Inches(0.5)
section.bottom_margin = Inches(0.5)

# Add a table with borders
table = document.add_table(rows=1, cols=len(df.columns))
table.style = "Table Grid" # Use a built-in style with borders

# Add header row
hdr_cells = table.rows[0].cells
for i, column in enumerate(df.columns):
hdr_cells[i].text = str(column)

# Add data rows
for index, row in df.iterrows():
row_cells = table.add_row().cells
for i, value in enumerate(row):
row_cells[i].text = str(value)

# Apply borders to each cell (if needed)
for row in table.rows:
for cell in row.cells:
cell._element.get_or_add_tcPr().append(
parse_xml(r'<w:shd {} w:fill="FFFFFF"/>'.format(nsdecls("w")))
)
cell._element.get_or_add_tcPr().append(
parse_xml(
(
r'<w:tcBorders %s><w:top w:val="single" w:sz="4"/>'
r'<w:left w:val="single" w:sz="4"/>'
r'<w:bottom w:val="single" w:sz="4"/>'
r'<w:right w:val="single" w:sz="4"/>'
r"</w:tcBorders>"
)
% nsdecls("w")
)
)

document.save(docx_file_path)


def export_excel_file_from_google_drive(filename) -> None:
HKT_FILE_PATH.unlink(missing_ok=True)
file: t.Optional[io.BytesIO] = None
try:
service = build("drive", "v3", credentials=CREDENTIALS)
results = (
service.files()
.list(fields="nextPageToken, files(id, name)")
.execute()
)
items = results.get("files", [])

if not items:
print("No files found.")
for item in items:
if item["name"] != filename:
continue
file_id = item["id"]
request = service.files().get_media(fileId=file_id)
file = io.BytesIO()
downloader = googleapiclient.http.MediaIoBaseDownload(
file, request
)
done = False
while done is False:
status, done = downloader.next_chunk()
HKT_FILE_PATH.write_bytes(file.getvalue())
if not HKT_FILE_PATH.is_file():
print(f"File {filename} not found.")
except googleapiclient.errors.HttpError as error:
print(f"An error occurred: {error}")


def upload_overview_files_to_google_drive(
daily_overview_file_paths: list[pathlib.Path],
) -> None:
try:
service = build("drive", "v3", credentials=CREDENTIALS)
for file_path in daily_overview_file_paths:
print(f"Upload {file_path}…")
file_metadata = {"name": file_path.stem}
media = googleapiclient.http.MediaFileUpload(
file_path, chunksize=-1
)
file = (
service.files()
.create(
body=file_metadata,
media_body=media,
fields="id,name,webViewLink",
)
.execute()
)
print(f'File ID: {file.get("id")}')
print(f'Link: {file.get("webViewLink")}')
permission = {
"type": "user",
"role": "writer",
"emailAddress": "elternvertretung@bluetezeit-berlin.de"
}
# permission = {
# "type": "user",
# "role": "owner",
# "emailAddress": "elternvertretung@bluetezeit-berlin.de"
# }
# https://developers.google.com/drive/api/reference/rest/v3/permissions/create
service.permissions().create(
fileId=file.get("id"),
body=permission,
transferOwnership=False,
sendNotificationEmail=False,
supportsAllDrives=True,
# moveToNewOwnersRoot=True,
).execute()
except googleapiclient.errors.HttpError as error:
print(f"An error occurred: {error}")


def generate_daily_overview_files() -> list[pathlib.Path]:
daily_overview_file_paths: list[pathlib.Path] = []
df = pd.read_excel(HKT_FILE_PATH, sheet_name="HKT Erfassung")
for group_name, group_df in df.groupby("Group"):
for no, day in enumerate(
(
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
),
start=1,
):
day_df = group_df[
(group_df[f"{day}\nmorning"] == 1.0)
| (group_df[f"{day}\nafternoon"] == 1.0)
]
day_df = day_df.replace(1.0, "Stay at home")
html_file_path = pathlib.Path(f"/tmp/{group_name}_{no}_{day}.html")
df = day_df[
[
"Name",
"Group",
f"{day}\nmorning",
f"{day}\nafternoon",
]
].fillna("")
df.to_html(html_file_path, index=False)
pdf_file_path = html_file_path.with_suffix(".pdf")
options = {"encoding": "UTF-8", "user-style-sheet": "style.css"}
pdfkit.from_file(
input=str(html_file_path),
output_path=str(pdf_file_path),
options=options,
verbose=False,
)
docx_file_path = html_file_path.with_suffix(".docx")
dataframe_to_word(df, docx_file_path)
# html_file_path.unlink(missing_ok=True)
for file_path in (
pdf_file_path,
docx_file_path,
):
if file_path.is_file():
daily_overview_file_paths.append(file_path)
return daily_overview_file_paths


if __name__ == "__main__":
export_excel_file_from_google_drive(filename=HKT_FILE_NAME)
if not HKT_FILE_PATH.is_file():
print(f"File {HKT_FILE_NAME} not found.")
raise SystemExit(1)
daily_overview_file_paths = generate_daily_overview_files()
upload_overview_files_to_google_drive([daily_overview_file_paths[0]])
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
google-api-python-client
google-auth-httplib2
google-auth-oauthlib
openpyxl
pandas
pandas-stubs
pdfkit # Needs system pkg wkhtmltopdf
python-docx
8 changes: 8 additions & 0 deletions style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
body {
font-family: 'Noto Emoji', sans-serif;
}
table {
border-collapse: collapse;
width: 100%;
}

0 comments on commit 1f68f44

Please sign in to comment.