Compare commits

...

6 Commits

9 changed files with 1565 additions and 5 deletions

329
.gitignore vendored
View File

@@ -3,6 +3,7 @@
.venv .venv
*.txt *.txt
*temp/ *temp/
*.xlsx
######################################################################################## ########################################################################################
# project specific gitignore entries # project specific gitignore entries
@@ -175,3 +176,331 @@
marimo/_static/ marimo/_static/
marimo/_lsp/ marimo/_lsp/
__marimo__/ __marimo__/
########################################################################################
## Core latex/pdflatex auxiliary files:
*.aux
*.lof
*.log
*.lot
*.fls
*.out
*.toc
*.fmt
*.fot
*.cb
*.cb2
.*.lb
## Intermediate documents:
*.dvi
*.xdv
*-converted-to.*
# these rules might exclude image files for figures etc.
# *.ps
# *.eps
# *.pdf
## Generated if empty string is given at "Please type another file name for output:"
.pdf
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bbl-SAVE-ERROR
*.bcf
*.bcf-SAVE-ERROR
*.blg
*-blx.aux
*-blx.bib
*.run.xml
## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
*.rubbercache
rubber.cache
## Build tool directories for auxiliary files
# latexrun
latex.out/
## Auxiliary and intermediate files from other packages:
# algorithms
*.alg
*.loa
# achemso
acs-*.bib
# amsthm
*.thm
# attachfile2
*.atfi
# beamer
*.nav
*.pre
*.snm
*.vrb
# changes
*.soc
*.loc
# comment
*.cut
# cprotect
*.cpt
# elsarticle (documentclass of Elsevier journals)
*.spl
# endnotes
*.ent
# fixme
*.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
*.[1-9]
*.[1-9][0-9]
*.[1-9][0-9][0-9]
*.[1-9]R
*.[1-9][0-9]R
*.[1-9][0-9][0-9]R
*.eledsec[1-9]
*.eledsec[1-9]R
*.eledsec[1-9][0-9]
*.eledsec[1-9][0-9]R
*.eledsec[1-9][0-9][0-9]
*.eledsec[1-9][0-9][0-9]R
# glossaries
*.acn
*.acr
*.glg
*.glg-abr
*.glo
*.glo-abr
*.gls
*.gls-abr
*.glsdefs
*.lzo
*.lzs
*.slg
*.slo
*.sls
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
# *.ist
# gnuplot
*.gnuplot
*.table
# gnuplottex
*-gnuplottex-*
# gregoriotex
*.gaux
*.glog
*.gtex
# htlatex
*.4ct
*.4tc
*.idv
*.lg
*.trc
*.xref
# hypdoc
*.hd
# hyperref
*.brf
# knitr
*-concordance.tex
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
# *.tikz
*-tikzDictionary
# latexindent will create succesive backup files by default
#*.bak*
# listings
*.lol
# luatexja-ruby
*.ltjruby
# makeidx
*.idx
*.ilg
*.ind
# minitoc
*.maf
*.mlf
*.mlt
*.mtc[0-9]*
*.slf[0-9]*
*.slt[0-9]*
*.stc[0-9]*
# minted
_minted*
*.data.minted
*.pyg
# morewrites
*.mw
# newpax
*.newpax
# nomencl
*.nlg
*.nlo
*.nls
# pax
*.pax
# pdfpcnotes
*.pdfpc
# sagetex
*.sagetex.sage
*.sagetex.py
*.sagetex.scmd
# scrwfile
*.wrt
# spelling
*.spell.bad
*.spell.txt
# svg
svg-inkscape/
# sympy
*.sout
*.sympy
sympy-plots-for-*.tex/
# pdfcomment
*.upa
*.upb
# pythontex
*.pytxcode
pythontex-files-*/
# tcolorbox
*.listing
# thmtools
*.loe
# TikZ & PGF
*.dpth
*.md5
*.auxlock
# titletoc
*.ptc
# todonotes
*.tdo
# vhistory
*.hst
*.ver
# easy-todo
*.lod
# xcolor
*.xcp
# xmpincl
*.xmpi
# xindy
*.xdy
# xypic precompiled matrices and outlines
*.xyc
*.xyd
# endfloat
*.ttt
*.fff
# Latexian
TSWLatexianTemp*
## Editors:
# WinEdt
*.bak
*.sav
# latexindent.pl
*.bak[0-9]*
# Texpad
.texpadtmp
# LyX
*.lyx~
# Kile
*.backup
# gummi
.*.swp
# KBibTeX
*~[0-9]*
# TeXnicCenter
*.tps
# auto folder when using emacs and auctex
./auto/*
*.el
# expex forward references with \gathertags
*-tags.tex
# standalone packages
*.sta
# Makeindex log files
*.lpz
# xwatermark package
*.xwm
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored.
#*Notes.bib

BIN
LaTex/Ruderbeschriftung.pdf Normal file

Binary file not shown.

116
LaTex/Ruderbeschriftung.tex Normal file
View File

@@ -0,0 +1,116 @@
\documentclass[a0,landscape]{sciposter}
\usepackage{amsmath}
\usepackage{booktabs}
\usepackage{multicol}
\usepackage{graphicx}
\usepackage{multirow}
\usepackage{tabularx}
\usepackage[center]{titlesec} % Add this line
% Customize section font size
\titleformat{\section}
{\centering\normalfont\Huge\bfseries}{\thesection}{1em}{}
\title{Ruder Labeling System}
\author{Georg Brantegger}
\date{\today}
\begin{document}
% \maketitle
\begin{center}
\begin{minipage}{0.9\textwidth}
\section*{Ruder ID Format}
% \begin{table}[]
% \centering
% \setlength{\tabcolsep}{40pt} % Adjust column spacing
% \LARGE
% \begin{tabular}{ccccccc}
% \textbf{S1} & \textbf{S2} & \textbf{S3} & \textbf{S4} & \textbf{S5} & \textbf{S6} & \textbf{S7} \\
% \textbf{Typ} & \textbf{Marke} & \textbf{Blatttyp} & \textbf{Anschaffungsjahr} & \textbf{Nummer} & \textbf{Seite} & \textbf{Rest}
% \end{tabular}
% \end{table}
\begin{table}[]
\centering
\setlength{\tabcolsep}{20pt}
\LARGE
\begin{tabular}{c@{ -- }c@{ -- }c@{ -- }c@{ -- }c@{ -- }c@{ -- }c}
\textbf{S1} & \textbf{S2} & \textbf{S3} & \textbf{S4} & \textbf{S5} & \textbf{S6} & \textbf{S7} \\
\textbf{Typ} & \textbf{Marke} & \textbf{Blatttyp} & \textbf{Anschaffungsjahr} & \textbf{Nummer} & \textbf{Seite} & \textbf{Rest}
\end{tabular}
\end{table}
\section*{Erklärung der Segmente}
\begin{table}[h!]
\centering
\setlength{\tabcolsep}{40pt} % Adjust column spacing
\LARGE
\begin{tabular}{ccccc} % Remove extra space on the sides
\toprule
\textbf{Segmentnummer} & \textbf{Segmentname} & \textbf{Anzahl Stellen} & \textbf{Wert} & \textbf{Langtext} \\
\midrule
\multirow{2}{*}{\textbf{S1}} &
\multirow{2}{*}{\textbf{Typ}} &
\multirow{2}{*}{1}
& S: & Skull \\
& & & R: & Riemen \\
\midrule
\multirow{4}{*}{\textbf{S2}} &
\multirow{4}{*}{\textbf{Marke}} &
\multirow{4}{*}{2}
& C2: & Concept 2 \\
& & & Cr: & Croker \\
& & & Em: & Empacher \\
& & & Sw: & Swift Racing \\
\midrule
\multirow{2}{*}{\textbf{S3}} &
\multirow{2}{*}{\textbf{Blatttyp}} &
\multirow{2}{*}{1}
& M: & Macon \\
& & & H: & Hacke \\
\midrule
\multirow{4}{*}{\textbf{S4}} &
\multirow{4}{*}{\textbf{Anschaffungsjahr}} &
\multirow{4}{*}{4}
& 19XX: & irgendwann vor 2000 \\
& & & 200X: & zwischen 2000-2009 \\
& & & 201X: & zwischen 2010-2019 \\
& & & 202X: & zwischen 2020-2025 \\
\midrule
\multirow{1}{*}{\textbf{S5}} &
\multirow{1}{*}{\textbf{Nummer}} &
\multirow{1}{*}{2}
& 00-99: & fortlaufend, wenn \textbf{S1}-\textbf{S4} gleich \\
\midrule
\multirow{2}{*}{\textbf{S6}} &
\multirow{2}{*}{\textbf{Seite}} &
\multirow{2}{*}{1}
& B: & Backbord \\
& & & S: & Steuerbord \\
\midrule
\multirow{2}{*}{\textbf{S7}} &
\multirow{2}{*}{\textbf{Rest}} &
\multirow{2}{*}{\textless \ 5}
& sk: & Concept2 Skinny Schaft \\
& & & \dots & \dots \\
\bottomrule
\end{tabular}
\end{table}
\section*{Beispiele}
\begin{center}
\Huge
\textbf{S-C2-H-201X-99-B-sk}
\end{center}
\end{minipage}
\end{center}
\end{document}

View File

@@ -0,0 +1,291 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "1293f184",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import os\n",
"import requests\n",
"import urllib\n",
"import io\n",
"import base64\n",
"\n",
"from matplotlib import pyplot as plt\n",
"import matplotlib.image as mpimg"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2946897b",
"metadata": {},
"outputs": [],
"source": [
"for file in os.listdir('temp'):\n",
" os.remove(os.path.join('temp',file))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb57d754",
"metadata": {},
"outputs": [],
"source": [
"user = \"Georg Brantegger\"\n",
"with open('app_pw.txt','r') as f:\n",
" app_pw = f.readline()\n",
"\n",
"base_url = \"https://nextcloud.karnelegger.eu/remote.php/webdav\"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "234e6a70",
"metadata": {},
"outputs": [],
"source": [
"folders = [\"Forms\",\"6 - Kontaktdatenerhebung RV Villach\",\"Ergebnisse CSV\"]\n",
"folder_string = '/'.join([urllib.parse.quote(folder) for folder in folders])\n",
"filename_csv = \"Kontaktdatenerhebung RV Villach (Antworten).csv\"\n",
"\n",
"url_csv = '/'.join([base_url,folder_string,urllib.parse.quote(filename_csv)])\n",
"\n",
"\n",
"r = requests.get(url_csv, auth=(user, app_pw))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a74d9129",
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv(io.StringIO(r.text),usecols=[3,4,5,6,7,8],header=0,names=['Vorname','Nachname','rower','Jahrgang','Foto','Telefonnummer'])\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "28dbb7e7",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc8568d6",
"metadata": {},
"outputs": [],
"source": [
"def extract_tel(row,col):\n",
" # remove the first ' and all whitespaces within number\n",
"\n",
" tel = str(row[col])\n",
"\n",
" replace_dict = {\"'\":'',' ':''}\n",
" \n",
" for key, value in replace_dict.items():\n",
" tel = tel.replace(key,value)\n",
"\n",
" # handle people who don't know how country codes work\n",
" if tel[0] == '0':\n",
" tel = '+43'+tel[1:]\n",
" if tel[0] == '6':\n",
" tel = '+43'+tel\n",
" if tel[0:4] == '+430':\n",
" tel = '+43'+tel[4:]\n",
" if tel[0:3] == '+49':\n",
" tel = '+43'+tel[3:]\n",
" if tel[0:2] == '43':\n",
" tel = '+43'+tel[2:]\n",
" if tel[0:2] == '+6':\n",
" tel = '+43'+tel[1:]\n",
" \n",
" return tel\n",
"\n",
"def evaluate_rower(row,col='rower'):\n",
" if row[col] == 'Ruder:in':\n",
" return True\n",
" else:\n",
" return False"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "68eaead1",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e868df47",
"metadata": {},
"outputs": [],
"source": [
"df['Vorname'] = df['Vorname'].str.strip()\n",
"df['Nachname'] = df['Nachname'].str.strip()\n",
"df['rower'] = df.apply(evaluate_rower,axis=1,args=('rower',))\n",
"df['Telefonnummer'] = df.apply(extract_tel,axis=1,args=('Telefonnummer',))\n",
"df['Foto'] = df['Foto'].str.replace(' ','-').fillna('-')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "614ac3b3",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df2c876c",
"metadata": {},
"outputs": [],
"source": [
"def get_photo_for_row(row):\n",
"\n",
" filename = row['Foto']\n",
"\n",
" if filename != '-':\n",
"\n",
" found_photo = False\n",
" i = 30\n",
" while found_photo is False:\n",
" print(f\"trying folder {i}\")\n",
" folders = [\"Forms\",\"6 - Kontaktdatenerhebung RV Villach\",f\"{i}\",\"23 - photo_name\"]\n",
" folder_string = '/'.join([urllib.parse.quote(folder) for folder in folders])\n",
" filename_photo = urllib.parse.quote(filename)\n",
"\n",
" url_photo = '/'.join([base_url,folder_string,urllib.parse.quote(filename_photo)])\n",
"\n",
" r = requests.get(url_photo, auth=(user, app_pw))\n",
" if r.status_code != 200:\n",
" i+=1\n",
" if i > 200:\n",
" break\n",
" continue\n",
" else:\n",
" photo_bytes = r.content\n",
" found_photo = True\n",
" photo_b64 = base64.b64encode(photo_bytes).decode(\"utf-8\")\n",
" \n",
" i = base64.b64decode(photo_b64)\n",
" i = io.BytesIO(i)\n",
" i = mpimg.imread(i, format='JPG')\n",
"\n",
" plt.imshow(i, interpolation='nearest')\n",
" plt.show()\n",
"\n",
" return photo_b64\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c9db8d6",
"metadata": {},
"outputs": [],
"source": [
"def convert_rows_to_vcf(row):\n",
" # Map CSV columns to vCard fields\n",
" first_name = row['Vorname']\n",
" last_name = row['Nachname']\n",
" phone = row['Telefonnummer']\n",
" year = row['Jahrgang']\n",
" rower = row['rower']\n",
" cat = 'RVV Athlet:innen' if rower is True else 'RVV Eltern'\n",
" address_book = 'rv-villach-athletinnen' if rower is True else 'rv-villach-eltern'\n",
" photo_b64 = get_photo_for_row(row)\n",
"\n",
" lines = [\n",
" \"BEGIN:VCARD\",\n",
" \"VERSION:3.0\",\n",
" f\"FN:{last_name} {first_name}\",\n",
" f\"N:{last_name};{first_name};;;\",\n",
" f\"CATEGORIES:{cat}\",\n",
" f\"TEL;TYPE=CELL:{phone}\"]\n",
" \n",
" if rower is True:\n",
" lines.append(f'NOTE:JG {year}')\n",
"\n",
" if photo_b64:\n",
" lines.append(f\"PHOTO;ENCODING=b;TYPE=JPEG:{photo_b64}\")\n",
"\n",
" lines.append(\"END:VCARD\")\n",
"\n",
" vcard_content = \"\\n\".join(lines)\n",
"\n",
" # Save to file\n",
" filename = f\"{last_name}_{first_name}.vcf\".replace(\" \", \"_\")\n",
" with open(os.path.join('temp', filename), 'w') as f:\n",
" f.write(vcard_content)\n",
"\n",
" # upload file to correct address book\n",
"\n",
" base_url_contacts = \"https://nextcloud.karnelegger.eu/remote.php/dav/addressbooks/users/Georg%20Brantegger/\"\n",
" url = base_url_contacts + address_book + '/' + filename\n",
"\n",
" with open(f'temp/{filename}', \"rb\") as f:\n",
" r = requests.put(\n",
" url,\n",
" data=f,\n",
" auth=(user, app_pw),\n",
" headers={\"Content-Type\": \"text/vcard\"}\n",
" )\n",
"\n",
" print(filename, r.status_code)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61659f5b",
"metadata": {},
"outputs": [],
"source": [
"df.apply(convert_rows_to_vcf,axis=1)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,383 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "1293f184",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import os\n",
"import requests\n",
"import urllib\n",
"import io\n",
"import base64\n",
"\n",
"from matplotlib import pyplot as plt\n",
"import matplotlib.image as mpimg"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2946897b",
"metadata": {},
"outputs": [],
"source": [
"for file in os.listdir('temp'):\n",
" os.remove(os.path.join('temp',file))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb57d754",
"metadata": {},
"outputs": [],
"source": [
"user = \"Georg Brantegger\"\n",
"with open('app_pw.txt','r') as f:\n",
" app_pw = f.readline()\n",
"\n",
"\n",
"base_url_csv = \"https://nextcloud.karnelegger.eu/remote.php/webdav\"\n",
"folders = [\"Forms\",\"2 - Kontaktdatenerhebung Ruder-innen RV Villach\",\"Ergebnisse CSV\"]\n",
"folder_string = '/'.join([urllib.parse.quote(folder) for folder in folders])\n",
"filename_csv = \"Kontaktdatenerhebung Ruder-innen RV Villach (Antworten).csv\"\n",
"\n",
"url_csv = '/'.join([base_url_csv,folder_string,urllib.parse.quote(filename_csv)])\n",
"\n",
"\n",
"r = requests.get(url_csv, auth=(user, app_pw))\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "543d5103",
"metadata": {},
"outputs": [],
"source": [
"def extract_tel(row,col):\n",
" # remove the first ' and all whitespaces within number\n",
"\n",
" tel = str(row[col])\n",
"\n",
" replace_dict = {\"'\":'',' ':''}\n",
" \n",
" for key, value in replace_dict.items():\n",
" tel = tel.replace(key,value)\n",
"\n",
" # handle people who don't know how country codes work\n",
" if tel[0] == '0':\n",
" tel = '+43'+tel[1:]\n",
" if tel[0] == '6':\n",
" tel = '+43'+tel\n",
" if tel[0:4] == '+430':\n",
" tel = '+43'+tel[4:]\n",
" if tel[0:3] == '+49':\n",
" tel = '+43'+tel[3:]\n",
" if tel[0:2] == '43':\n",
" tel = '+43'+tel[2:]\n",
" if tel[0:2] == '+6':\n",
" tel = '+43'+tel[1:]\n",
" \n",
" return tel"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a74d9129",
"metadata": {},
"outputs": [],
"source": [
"df = pd.read_csv(io.StringIO(r.text))\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ba590c1",
"metadata": {},
"outputs": [],
"source": [
"name_replacement_dict = {',':'',\n",
" 'Christoph Spath-Glantschnig': 'Spath-Glantschnig Christoph',\n",
" 'Felix Assmann-Hafenscherer': 'Assmann-Hafenscherer Felix',\n",
" 'Elias Assmann': 'Assmann Elias',\n",
" 'Bernadette Assmann ': 'Assmann Bernadette',\n",
" 'Tropea Giuseppe Manfredi': 'Tropea Manfredi Giuseppe',\n",
" 'Sabrina': 'Bellotti Sabrina',\n",
" 'Ich bin 18': None,\n",
" }\n",
"\n",
"df['Mein Name ist:'] = df['Mein Name ist:'].replace(name_replacement_dict,regex=True).str.strip()\n",
"df['Name Kontaktperson/Kind 1'] = df['Name Kontaktperson/Kind 1'].replace(name_replacement_dict,regex=True).str.strip()\n",
"df['Name Kontaktperson/Kind 2'] = df['Name Kontaktperson/Kind 2'].replace(name_replacement_dict,regex=True).str.strip()\n",
"df['Name Kontaktperson/Kind 3'] = df['Name Kontaktperson/Kind 3'].replace(name_replacement_dict,regex=True).str.strip()\n",
"\n",
"df['Meine Telefonnummer ist:'] = df.apply(extract_tel,axis=1,args=('Meine Telefonnummer ist:',))\n",
"df['Telefonnummer Kontaktperson 1'] = df.apply(extract_tel,axis=1,args=('Telefonnummer Kontaktperson 1',))\n",
"df['Telefonnummer Kontaktperson 2'] = df.apply(extract_tel,axis=1,args=('Telefonnummer Kontaktperson 2',))\n",
"df['Telefonnummer Kontaktperson 3'] = df.apply(extract_tel,axis=1,args=('Telefonnummer Kontaktperson 3',))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3b664ba7",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58057da5",
"metadata": {},
"outputs": [],
"source": [
"# manually fix_Felix row\n",
"row = df.loc[df['Mein Name ist:'] == 'Hafenscherer Rita',:]\n",
"row['Mein Name ist:'] = 'Assmann-Hafenscherer Felix'\n",
"row['Ich bin:'] = 'Ruder:in'\n",
"row['Meine Telefonnummer ist:'] = '+4367764409018'\n",
"row['Name Kontaktperson/Kind 1'] = 'Hafenscherer Rita'\n",
"row['Telefonnummer Kontaktperson 1'] = '+436509623400'\n",
"df.loc[df['Mein Name ist:'] == 'Hafenscherer Rita',:] = row"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "562a911b",
"metadata": {},
"outputs": [],
"source": [
"df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc8568d6",
"metadata": {},
"outputs": [],
"source": [
"rowers_columns = ['Mein Name ist:',\n",
" 'Mein Jahrgang ',\n",
" 'Lade ein nettes Selfy hoch!',\n",
" 'Meine Telefonnummer ist:',\n",
" 'Name Kontaktperson/Kind 1',\n",
" 'Telefonnummer Kontaktperson 1',\n",
" 'Name Kontaktperson/Kind 2',\n",
" 'Telefonnummer Kontaktperson 2',\n",
" 'Name Kontaktperson/Kind 3',\n",
" 'Telefonnummer Kontaktperson 3']\n",
"\n",
"rowers_dict = {key: [] for key in rowers_columns}\n",
"\n",
"parents_columns = ['Mein Name ist:',\n",
" 'Lade ein nettes Selfy hoch!',\n",
" 'Meine Telefonnummer ist:',\n",
" 'Name Kontaktperson/Kind 1',\n",
" 'Telefonnummer Kontaktperson 1',\n",
" 'Name Kontaktperson/Kind 2',\n",
" 'Telefonnummer Kontaktperson 2',\n",
" 'Name Kontaktperson/Kind 3',\n",
" 'Telefonnummer Kontaktperson 3']\n",
"\n",
"parents_dict = {key: [] for key in parents_columns}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d44beb8",
"metadata": {},
"outputs": [],
"source": [
"for _,row in df.iterrows():\n",
" if (row['Ich bin:'] == 'Ruder:in') | (row['Mein Name ist:']=='Assmann-Hafenscherer Felix'):\n",
" for col in rowers_columns:\n",
" rowers_dict[col].append(row[col])\n",
" \n",
" elif row['Ich bin:'] == 'Elternteil/erwachsene Ansprechperson':\n",
" for col in parents_columns:\n",
" parents_dict[col].append(row[col])\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "118dd1a7",
"metadata": {},
"outputs": [],
"source": [
"rowers_df = pd.DataFrame(rowers_dict)\n",
"parents_df = pd.DataFrame(parents_dict)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1b7dd6b",
"metadata": {},
"outputs": [],
"source": [
"rowers_df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f1e3f59d",
"metadata": {},
"outputs": [],
"source": [
"parents_df"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df2c876c",
"metadata": {},
"outputs": [],
"source": [
"# def get_photo_for_row(row):\n",
"\n",
"# filename = row['Foto']\n",
"\n",
"# found_photo = False\n",
"# i = 0\n",
"# while found_photo is False:\n",
"# print(f\"trying folder {i}\")\n",
"# base_url_photo = \"https://nextcloud.karnelegger.eu/remote.php/webdav\"\n",
"# folders = [\"Forms\",\"4 - Kontaktdatenerhebung Trainerteam\",f\"{i}\",\"19 - Foto\"]\n",
"# folder_string = '/'.join([urllib.parse.quote(folder) for folder in folders])\n",
"# filename_photo = urllib.parse.quote(filename)\n",
"\n",
"# url_photo = '/'.join([base_url_photo,folder_string,urllib.parse.quote(filename_photo)])\n",
"\n",
"# r = requests.get(url_photo, auth=(user, app_pw))\n",
"# if r.status_code != 200:\n",
"# i+=1\n",
"# if i > 100:\n",
"# break\n",
"# continue\n",
"# else:\n",
"# photo_bytes = r.content\n",
"# found_photo = True\n",
"# photo_b64 = base64.b64encode(photo_bytes).decode(\"utf-8\")\n",
" \n",
"# i = base64.b64decode(photo_b64)\n",
"# i = io.BytesIO(i)\n",
"# i = mpimg.imread(i, format='JPG')\n",
"\n",
"# plt.imshow(i, interpolation='nearest')\n",
"# plt.show()\n",
"\n",
"# return photo_b64\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c9db8d6",
"metadata": {},
"outputs": [],
"source": [
"# def convert_rows_to_vcf(row):\n",
"# # Map CSV columns to vCard fields\n",
"# first_name = row['Vorname']\n",
"# last_name = row['Nachname']\n",
"# phone = row['Telefonnummer']\n",
"# org = 'Ruderverein Villach von 1881'\n",
"# photo_b64 = get_photo_for_row(row)\n",
" \n",
"\n",
"# lines = [\n",
"# \"BEGIN:VCARD\",\n",
"# \"VERSION:3.0\",\n",
"# f\"FN:{first_name} {last_name}\",\n",
"# f\"N:{last_name};{first_name};;;\",\n",
"# f\"ORG:{org}\",\n",
"# f\"TEL;TYPE=CELL:{phone}\"]\n",
"\n",
"# if photo_b64:\n",
"# lines.append(f\"PHOTO;ENCODING=b;TYPE=JPEG:{photo_b64}\")\n",
"\n",
"# lines.append(\"END:VCARD\")\n",
"\n",
"# vcard_content = \"\\n\".join(lines)\n",
"\n",
"# # Save to file\n",
"# filename = f\"{last_name}_{first_name}.vcf\".replace(\" \", \"_\")\n",
"# with open(os.path.join('temp', filename), 'w') as f:\n",
"# f.write(vcard_content)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61659f5b",
"metadata": {},
"outputs": [],
"source": [
"# df.apply(convert_rows_to_vcf,axis=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37b2a51b",
"metadata": {},
"outputs": [],
"source": [
"# base_url = \"https://nextcloud.karnelegger.eu/remote.php/dav/addressbooks/users/Georg%20Brantegger/rv-villach-trainer/\"\n",
"\n",
"# for vcf_file in os.listdir('temp'):\n",
"\n",
"# url = base_url + vcf_file\n",
"\n",
"# with open(f'temp/{vcf_file}', \"rb\") as f:\n",
"# r = requests.put(\n",
"# url,\n",
"# data=f,\n",
"# auth=(user, app_pw),\n",
"# headers={\"Content-Type\": \"text/vcard\"}\n",
"# )\n",
"\n",
"# print(vcf_file, r.status_code)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -136,6 +136,7 @@
" first_name = row['Vorname']\n", " first_name = row['Vorname']\n",
" last_name = row['Nachname']\n", " last_name = row['Nachname']\n",
" phone = row['Telefonnummer']\n", " phone = row['Telefonnummer']\n",
" cat = 'RVV Trainer:innen'\n",
" org = 'Ruderverein Villach von 1881'\n", " org = 'Ruderverein Villach von 1881'\n",
" photo_b64 = get_photo_for_row(row)\n", " photo_b64 = get_photo_for_row(row)\n",
" \n", " \n",
@@ -143,8 +144,9 @@
" lines = [\n", " lines = [\n",
" \"BEGIN:VCARD\",\n", " \"BEGIN:VCARD\",\n",
" \"VERSION:3.0\",\n", " \"VERSION:3.0\",\n",
" f\"FN:{first_name} {last_name}\",\n", " f\"FN:{last_name} {first_name}\",\n",
" f\"N:{last_name};{first_name};;;\",\n", " f\"N:{last_name};{first_name};;;\",\n",
" f\"CATEGORIES:{cat}\",\n",
" f\"ORG:{org}\",\n", " f\"ORG:{org}\",\n",
" f\"TEL;TYPE=CELL:{phone}\"]\n", " f\"TEL;TYPE=CELL:{phone}\"]\n",
"\n", "\n",

View File

@@ -0,0 +1,184 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 8,
"id": "152c4ce0",
"metadata": {},
"outputs": [],
"source": [
"import requests"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "041b0ab6",
"metadata": {},
"outputs": [],
"source": [
"# --- Configuration ---\n",
"NEXTCLOUD_URL = \"https://nextcloud.karnelegger.eu\"\n",
"USERNAME = \"Georg Brantegger\"\n",
"with open('app_pw.txt','r') as f:\n",
" APP_PASSWORD = f.readline()\n",
"\n",
"\n",
"# Modern Nextcloud WebDAV endpoint format:\n",
"WEBDAV_BASE_URL = f\"{NEXTCLOUD_URL}/remote.php/dav/files/{USERNAME}/Shared/Ruderverein/Videoanalyse\""
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "19ec4c3e",
"metadata": {},
"outputs": [],
"source": [
"def create_nextcloud_folder(folder_url):\n",
" \"\"\"Sends a MKCOL request to Nextcloud to create a folder.\"\"\"\n",
" response = requests.request(\"MKCOL\", folder_url, auth=(USERNAME, APP_PASSWORD))\n",
" \n",
" if response.status_code in [201, 207]:\n",
" print(f\"✅ Success: Created folder at {folder_url}\")\n",
" elif response.status_code == 405:\n",
" # WebDAV returns 405 Method Not Allowed if the folder already exists\n",
" print(f\"⏩ Skipped: Folder already exists at {folder_url}\")\n",
" elif response.status_code == 409:\n",
" print(f\"❌ Error: Parent folder doesn't exist for {folder_url}\")\n",
" else:\n",
" print(f\"❌ Error {response.status_code}: Could not create folder.\")\n",
" print(response.text)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "b4c04efb",
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"import vobject\n",
"\n",
"\n",
"# This is the \"URL slug\" of your address book. \n",
"# Usually, if you name it \"Athletes\", the slug is \"athletes\" (lowercase).\n",
"# You can find the exact name by going to Contacts -> Settings (bottom left) -> \n",
"# Click the 3 dots next to your address book -> \"Copy link\". \n",
"# The link will end with /addressbooks/users/username/THIS_PART/\n",
"ADDRESSBOOK_NAME = \"rv-villach-athletinnen\" \n",
"\n",
"\n",
"# The ?export parameter tells Nextcloud to dump the whole address book at once\n",
"CARDDAV_URL = f\"{NEXTCLOUD_URL}/remote.php/dav/addressbooks/users/{USERNAME}/{ADDRESSBOOK_NAME}/?export\"\n",
"\n",
"def get_athlete_names():\n",
" print(f\"Fetching contacts from the '{ADDRESSBOOK_NAME}' address book...\")\n",
" response = requests.get(CARDDAV_URL, auth=(USERNAME, APP_PASSWORD))\n",
" \n",
" if response.status_code != 200:\n",
" print(f\"❌ Error {response.status_code}: Could not fetch address book.\")\n",
" print(\"Check your URL, credentials, and make sure the ADDRESSBOOK_NAME is exactly right.\")\n",
" return []\n",
"\n",
" vcf_data = response.text\n",
" athletes = []\n",
"\n",
" # Parse the multi-contact VCF data\n",
" for vcard in vobject.readComponents(vcf_data):\n",
" # The 'n' property in a vCard holds the structured Name (First, Last, etc.)\n",
" if hasattr(vcard, 'n'):\n",
" first_name = vcard.n.value.given\n",
" last_name = vcard.n.value.family\n",
" \n",
" # Combine them. The .strip() helps if someone only has a first or last name\n",
" full_name = f\"{last_name} {first_name}\".strip()\n",
" \n",
" if full_name:\n",
" athletes.append(full_name)\n",
" \n",
" # Fallback just in case a contact only has a \"Formatted Name\" (fn) saved\n",
" elif hasattr(vcard, 'fn'):\n",
" athletes.append(vcard.fn.value)\n",
" \n",
" return athletes"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "306aef25",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"⏩ Skipped: Folder already exists at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse\n",
"Fetching contacts from the 'rv-villach-athletinnen' address book...\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Aufegger%20Tobias\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Praschnig%20Flora\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Buchacher%20Eva\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Gigacher%20Olivia\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/McGrath%20Samuel\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Magdalena%20Truppe\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Yang%20Ruien\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Yang%20Xiaoen\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Kofler%20Hannah\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Tropea%20Manfredi\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Spath-Glantschnig%20%20Christoph\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Acerbi%20Sofia\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Aufegger%20Emilia\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Henriks%20Alva\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Tscherne%20Tobias\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Pressinger%20Laurin\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Bodner%20Emma\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Jop%20Nicolas\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Assmann-Hafenscherer%20Felix\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Henriks%20Konstantin\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Loidl%20Lieselotte\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Poga%C4%8Dar%20Anamary\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Polessnig%20Valentina\n",
"✅ Success: Created folder at https://nextcloud.karnelegger.eu/remote.php/dav/files/Georg Brantegger/Shared/Ruderverein/Videoanalyse/Paulik%20Elena\n"
]
}
],
"source": [
"# 1. Create the main base folder first (just in case it doesn't exist)\n",
"create_nextcloud_folder(WEBDAV_BASE_URL)\n",
"\n",
"# 2. Loop through your athletes and create a folder for each\n",
"for athlete in get_athlete_names():\n",
" # It's usually good practice to replace spaces with underscores for web URLs, \n",
" # but Nextcloud handles spaces fine if we format the URL correctly.\n",
" # We use requests.utils.quote to properly encode spaces and special characters.\n",
" safe_folder_name = requests.utils.quote(athlete)\n",
" athlete_folder_url = f\"{WEBDAV_BASE_URL}/{safe_folder_name}\"\n",
" \n",
" create_nextcloud_folder(athlete_folder_url)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,251 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 11,
"id": "efde98a5",
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"import datetime as dt\n",
"import openpyxl.styles"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "8e84cb92",
"metadata": {},
"outputs": [],
"source": [
"def get_week_boundary(target_date, boundary='first'):\n",
" \"\"\"\n",
" Returns the Monday (first) or Sunday (last) of the week for a given date.\n",
" \n",
" :param target_date: datetime object\n",
" :param boundary: 'first' or 'last'\n",
" :return: datetime object\n",
" \"\"\"\n",
" # .weekday() returns 0 for Monday, 6 for Sunday\n",
" days_since_monday = target_date.weekday()\n",
" \n",
" if boundary == 'first':\n",
" return target_date - dt.timedelta(days=days_since_monday)\n",
" elif boundary == 'last':\n",
" days_until_sunday = 6 - days_since_monday\n",
" return target_date + dt.timedelta(days=days_until_sunday)\n",
" else:\n",
" raise ValueError(\"Boundary must be 'first' or 'last'\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "7a9ce99f",
"metadata": {},
"outputs": [],
"source": [
"year = 2026\n",
"\n",
"date_index = pd.date_range(get_week_boundary(dt.datetime(year,1,1),'first'),get_week_boundary(dt.datetime(year,12,31),'last'),freq='1D')\n",
"\n",
"columns = ['KW','date','weekday','location','trainer','meetup_time','start_time','warmup','main','cooldown','post_workout','approx_end']\n",
"columns = columns + [f'padding_{i}' for i in range(5)]\n",
"header_names = ['KW','Datum','Tag','Ort','Trainer:in','Treffpunkt','Trainingsbeginn','Warm-Up','Programm','Cool-Down','Post-Workout','Trainingsende']\n",
"header_names = header_names + ['' for i in range(5)]\n",
"\n",
"\n",
"df = pd.DataFrame(index=date_index,columns=columns)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "7be00de3",
"metadata": {},
"outputs": [],
"source": [
"weekday_dict_short = {0: 'Mo',\n",
" 1: 'Di',\n",
" 2: 'Mi',\n",
" 3: 'Do',\n",
" 4: 'Fr',\n",
" 5: 'Sa',\n",
" 6: 'So'}\n",
"\n",
"weekday_dict_long = {0: 'Montag',\n",
" 1: 'Dienstag',\n",
" 2: 'Mittwoch',\n",
" 3: 'Donnerstag',\n",
" 4: 'Freitag',\n",
" 5: 'Samstag',\n",
" 6: 'Sonntag'}"
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "3f25dd5f",
"metadata": {},
"outputs": [],
"source": [
"df['KW'] = df.index.isocalendar().week\n",
"df['date'] = df.index.strftime('%d.%m.%Y')\n",
"df['weekday'] = df.index.weekday\n",
"df['weekday'] = df['weekday'].replace(weekday_dict_short)\n",
"df['location'] = 'RVV'"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "2b98908c",
"metadata": {},
"outputs": [],
"source": [
"def get_month_slice(df,month_number):\n",
"\n",
" week_numbers_of_month = df.loc[df.index.month==month_number,'KW'].unique()\n",
"\n",
" if month_number == 1:\n",
" week_numbers_of_month = [week_number if (week_number < 6) else None for week_number in week_numbers_of_month]\n",
" elif month_number == 12:\n",
" week_numbers_of_month = [week_number if (week_number > 40) else None for week_number in week_numbers_of_month]\n",
"\n",
" df_slice = df.loc[df['KW'].isin(week_numbers_of_month),:]\n",
" return df_slice"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "69339931",
"metadata": {},
"outputs": [],
"source": [
"def highlight_weekends(row):\n",
" # .weekday() > 4 means Saturday (5) or Sunday (6)\n",
" if row.name.weekday() > 4:\n",
" return ['background-color: #ffcccc'] * len(row) # Light red/pink\n",
" return [''] * len(row)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "379682df",
"metadata": {},
"outputs": [],
"source": [
"def format_rows(row):\n",
" # .weekday() > 4 means Saturday (5) or Sunday (6)\n",
" if row.name.weekday() == 0:\n",
" bg_style = 'background-color: #D1D1D1'\n",
" border_style = 'border-bottom: 1pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 1:\n",
" bg_style = ''\n",
" border_style = 'border-bottom: 1pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 2:\n",
" bg_style = 'background-color: #D1D1D1'\n",
" border_style = 'border-bottom: 1pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 3:\n",
" bg_style = ''\n",
" border_style = 'border-bottom: 1pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 4:\n",
" bg_style = 'background-color: #D1D1D1'\n",
" border_style = 'border-bottom: 2pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 5:\n",
" bg_style = 'background-color: ##C5D9F1'\n",
" border_style = 'border-bottom: 1pt solid black; border-left: 1pt solid black'\n",
" elif row.name.weekday() == 6:\n",
" bg_style = 'background-color: ##C5D9F1'\n",
" border_style = 'border-bottom: 3pt solid black; border-left: 1pt solid black'\n",
"\n",
" # Combine them (separated by semicolon)\n",
" combined = f\"{bg_style}; {border_style}\"\n",
" return [combined] * len(row)"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "ae3bf250",
"metadata": {},
"outputs": [],
"source": [
"months = range(1,12+1,1)\n",
"file_name = f\"Trainingsplan_{year}.xlsx\"\n",
"\n",
"with pd.ExcelWriter(file_name, engine='openpyxl') as writer:\n",
" for month in months:\n",
" df_month = get_month_slice(df, month)\n",
" \n",
" # 1 format the cells\n",
" \n",
" # 1.1 format the rows (background color and borders)\n",
" styled_df = df_month.style.apply(format_rows, axis=1)\n",
" \n",
" # 1.2 center all cells\n",
" styled_df = styled_df.set_properties(**{'text-align': 'center'})\n",
" \n",
" # 2 Write to xlsx\n",
" styled_df.to_excel(writer, sheet_name=f\"{month}_{year}\",index=False,header=header_names)\n",
"\n",
" # 3 after writing, change other formats\n",
" # --- Access the openpyxl worksheet object ---\n",
" worksheet = writer.sheets[f\"{month}_{year}\"]\n",
"\n",
" # 3.1 set the widths\n",
" worksheet.column_dimensions['A'].width = 18*0.35\n",
" worksheet.column_dimensions['B'].width = 18*0.89\n",
" worksheet.column_dimensions['C'].width = 18*0.35\n",
" worksheet.column_dimensions['D'].width = 18*0.40\n",
" worksheet.column_dimensions['E'].width = 18*0.73\n",
" worksheet.column_dimensions['F'].width = 18*0.81\n",
" worksheet.column_dimensions['G'].width = 18*1.05\n",
" worksheet.column_dimensions['H'].width = 18*0.76\n",
" worksheet.column_dimensions['I'].width = 18*2.50\n",
" worksheet.column_dimensions['J'].width = 18*0.76\n",
" worksheet.column_dimensions['K'].width = 18*1.05\n",
" worksheet.column_dimensions['L'].width = 18*1.05\n",
"\n",
" # 3.2 format the header\n",
" # Define a bold, larger font\n",
" header_font = openpyxl.styles.Font(name='Arial', size=12, bold=True, color=\"000000\")\n",
" \n",
" # Iterate through the first row (Header)\n",
" # worksheet[1] returns all cells in the first row\n",
" for cell in worksheet[1]:\n",
" cell.font = header_font\n",
" cell.alignment = openpyxl.styles.Alignment(horizontal='center', vertical='center')\n",
" cell.border = openpyxl.styles.Border(bottom=openpyxl.styles.Side(border_style='medium', color='000000'))\n",
"\n",
" # 3.3 freeze the first row\n",
" worksheet.freeze_panes = 'A2' "
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -15,8 +15,13 @@ def get_list_of_packages_to_load():
packages.append('numpy') packages.append('numpy')
packages.append('pandas') packages.append('pandas')
packages.append('pyarrow') packages.append('pyarrow')
packages.append('odfpy')
packages.append('fsspec') packages.append('fsspec')
# packages.append('openpyxl') packages.append('vobject')
packages.append('requests')
packages.append('openpyxl')
packages.append('jinja2') # for styling
# packages.append('matplotlib') # packages.append('matplotlib')
# packages.append('pyqt5') # packages.append('pyqt5')
# packages.append('tirex-ts') # packages.append('tirex-ts')
@@ -25,7 +30,6 @@ def get_list_of_packages_to_load():
# packages.append('pyinstaller') # packages.append('pyinstaller')
# packages.append('oracledb') # packages.append('oracledb')
# packages.append('sqlalchemy') # packages.append('sqlalchemy')
packages.append('requests')
# packages.append('requests_negotiate_sspi ') # packages.append('requests_negotiate_sspi ')
# packages.append('ldap3') # packages.append('ldap3')
# packages.append('mplcursors') # packages.append('mplcursors')