Fehlerbehebungen

Fehlervermeidung
Favicon ersetzt
This commit is contained in:
Alexander Malzkuhn 2025-06-05 12:34:29 +02:00
parent 1330fd569e
commit b6a1db63bc
5 changed files with 91 additions and 142 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
**/*.pyc
Testplan.md
.idea
.nicegui
.venv

View File

@ -7,8 +7,8 @@ services:
environment:
- PYTHONUNBUFFERED=1
volumes:
#- /home/alexander/Dokumente/Python/Zeiterfassung/lib:/app/lib
#- /home/alexander/Dokumente/Python/Zeiterfassung/main.py:/app/main.py
- /home/alexander/Dokumente/Python/Zeiterfassung/lib:/app/lib
- /home/alexander/Dokumente/Python/Zeiterfassung/main.py:/app/main.py
- /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/users:/users
- /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/backup:/backup
- /home/alexander/Dokumente/Python/Zeiterfassung/docker-work/settings:/settings

View File

@ -1,4 +1,4 @@
from datetime import datetime
from datetime import datetime, timedelta
import dateutil.easter
from dateutil.easter import *
@ -20,6 +20,7 @@ import hashlib
import calendar
import locale
import segno
import shutil
@ui.page('/admin')
def page_admin():
@ -524,6 +525,7 @@ def page_admin():
if str(actual_date.day) in list(absences):
current_user.del_absence(actual_date.year, actual_date.month, actual_date.day)
ui.notify(f"Eintrag {absence_entries[absences[str(actual_date.day)]]['name']} am {actual_date.day}.{actual_date.month}.{actual_date.year} überschrieben.")
if current_user.get_day_workhours(actual_date.year, actual_date.month, actual_date.day) > 0:
current_user.update_absence(actual_date.year, actual_date.month, actual_date.day, absence_type)
actual_date = actual_date + datetime.timedelta(days=1)
@ -589,12 +591,20 @@ def page_admin():
with ui.button(icon='menu').props('square') as menu_button:
with ui.menu() as menu:
no_contract = False
start_of_contract = current_user.get_starting_day()
if datetime.datetime(int(select_year.value), int(select_month.value), day) < datetime.datetime(int(start_of_contract[0]), int(start_of_contract[1]), int(start_of_contract[2])):
no_contract = True
menu_item = ui.menu_item("Zeiteintrag hinzufügen", lambda day=day: add_entry(day))
if archive_status:
menu_item.disable()
if datetime.datetime.now().day < day:
menu_item.disable()
menu_item.tooltip("Kann keine Zeiteinträge für die Zukunft vornehmen.")
if no_contract:
menu_item.disable()
menu_item.tooltip("Kann keine Zeiteinträge für Zeit vor der Einstellung vornehmen")
ui.separator()
menu_item = ui.menu_item("Notizen bearbeiten", lambda day=day: edit_notes(day))
if archive_status:
@ -606,6 +616,10 @@ def page_admin():
menu_item.disable()
if str(day) in list(user_absent):
menu_item.disable()
if no_contract:
menu_item.disable()
menu_item.tooltip(
"Kann keine Zeiteinträge für Zeit vor der Einstellung vornehmen")
if archive_status:
menu_button.disable()
@ -984,8 +998,21 @@ def page_admin():
holiday_buttons_grid.refresh()
with ui.column():
starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr")
end_year = ui.number(value=starting_year.value, label="Endjahr")
end_year_binder = ValueBinder()
start_year_binder = ValueBinder()
def correct_end_year():
if starting_year.value > end_year_binder.value:
end_year_binder.value = starting_year.value
def correct_start_year():
if starting_year.value > end_year_binder.value:
starting_year.value = end_year_binder.value
start_year_binder.value = datetime.datetime.now().year
starting_year = ui.number(value=datetime.datetime.now().year, label="Startjahr", on_change=correct_end_year).bind_value(start_year_binder, 'value')
end_year_binder.value = starting_year.value
end_year = ui.number(value=starting_year.value, label="Endjahr", on_change=correct_start_year).bind_value(end_year_binder, 'value')
with ui.row():
ui.button("Anwenden", on_click=enter_holidays)
ui.button("Abbrechen", on_click=dialog.close)
@ -1044,7 +1071,9 @@ def page_admin():
reset_visibility.value = False
timetable.refresh()
ui.button("Speichern", on_click=save_admin_settings).tooltip("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.")
with ui.button("Speichern", on_click=save_admin_settings):
with ui.tooltip():
ui.label("Hiermit werden sämtliche oben gemachten Einstellungen gespeichert.\nGgf. müssen Sie die Seite neu laden um die Auswirkungen sichtbar zu machen.").style('white-space: pre-wrap')
with ui.tab_panel(users):
ui.label("Benutzerverwaltung").classes(h3)
@ -1341,7 +1370,7 @@ def page_admin():
sum = float(days[i].value) + sum
except:
pass
workhours_sum.value = str(sum)
workhours_sum.text = str(sum)
with ui.grid(columns='auto auto auto').classes('items-baseline'):
ui.label("gültig ab:")
@ -1371,9 +1400,19 @@ def page_admin():
def add_workhours_entry():
workhours_dict = { }
if not use_last_entries_chkb.value:
for i in range(1, 8):
workhours_dict[i] = 0
workhours_dict["vacation"] = 0
else:
validity_date_dt = datetime.datetime.strptime(date_picker.value, "%Y-%m-%d")
for i in range (1, 8):
check_date_dt = validity_date_dt - datetime.timedelta(days=i)
weekday_of_check_date = check_date_dt.weekday() + 1
workhours_of_check_date = current_user.get_day_workhours(check_date_dt.year, check_date_dt.month, check_date_dt.day)
workhours_dict[weekday_of_check_date] = workhours_of_check_date
workhours_dict["vacation"] = current_user.get_vacation_claim(validity_date_dt.year, validity_date_dt.month, validity_date_dt.day)
current_user.workhours[date_picker.value] = workhours_dict
current_user.write_settings()
@ -1393,7 +1432,7 @@ def page_admin():
with ui.dialog() as dialog, ui.card():
ui.label("Geben Sie das Gültigkeitsdatum an, ab wann die Einträge gültig sein sollen.")
date_picker = ui.date()
use_last_entries_chkb = ui.checkbox("Werte von letztem gültigen Eintrag übernehmen.")
with ui.row():
ui.button("OK", on_click=add_workhours_entry)
ui.button("Abbrechen", on_click=dialog.close)
@ -1497,11 +1536,13 @@ def page_admin():
else:
ui.label(f'{round(size / 1_000, 2)} kB')
ui.label(version)
from lib.definitions import scriptpath
ui.button(icon='download', on_click=lambda file=date_string: ui.download.file(
os.path.join(scriptpath, backupfolder, f'{file}.zip'))).tooltip(
"Backup herunterladen")
def del_backup_dialog(file):
from lib.definitions import scriptpath
def del_backup():
os.remove(os.path.join(scriptpath, backupfolder, f'{file}.zip'))
dialog.close()
@ -1518,9 +1559,30 @@ def page_admin():
dialog.open()
def restore_backup_dialog(file):
from lib.definitions import scriptpath
def restore_backup():
if is_docker():
folder_to_delete = "/users"
else:
folder_to_delete = os.path.join(scriptpath, userfolder)
for file_path in os.listdir(folder_to_delete):
delete_item = os.path.join(folder_to_delete, file_path)
if os.path.isfile(delete_item) or os.path.islink(delete_item):
os.unlink(delete_item)
elif os.path.isdir(delete_item):
shutil.rmtree(delete_item)
with zipfile.ZipFile(os.path.join(scriptpath, backupfolder, f'{file}.zip'), 'r') as source:
if not is_docker():
source.extractall(scriptpath)
os.unlink(os.path.join(scriptpath, "app_version.txt"))
else:
filelist = source.namelist()
print(filelist)
for file_list_item in filelist:
if file_list_item.startswith("users"):
source.extract(file_list_item, "/")
source.extract("settings.json", "/settings/")
with ui.dialog() as confirm_dialog, ui.card():
ui.label("Das Backup wurde wiederhergestellt. Um Änderungen anzuzeigen, muss die Seite neu geladen werden.")
with ui.grid(columns=2):
@ -1547,6 +1609,7 @@ def page_admin():
ui.separator()
async def make_backup():
from lib.definitions import scriptpath
n = ui.notification("Backup wird erzeugt...")
compress = zipfile.ZIP_DEFLATED
filename = os.path.join(searchpath, datetime.datetime.now().strftime(date_format) + '.zip')

View File

@ -227,13 +227,7 @@ def homepage():
ui.label("Fehlzeitenübersicht").classes('col-span-2 font-bold')
absences_select = ui.select(list(reversed(available_years)), on_change=activate_absence)
absences_button = ui.button("Anzeigen", on_click=lambda: ui.navigate.to(f"api/absence/{current_user.username}/{absences_select.value}", new_tab=True)).bind_enabled_from(binder_absence, 'value')
ui.separator().classes('col-span-2')
def logout():
app.storage.user.pop("active_user", None)
ui.navigate.to("/")
ui.button("Logout", on_click=logout).classes('col-span-2')
with ui.tab_panel(absence):
ui.label("Urlaub für folgenden Zeitraum beantragen:")
vacation_date = ui.date().props('range today-btn')
@ -307,6 +301,16 @@ def homepage():
ui.button("Speichern", on_click=save_new_password)
ui.button("Zurücksetzen", on_click=revert_pw_inputs)
ui.space()
ui.space()
with ui.column():
ui.separator()
def logout():
app.storage.user.pop("active_user", None)
ui.navigate.to("/")
ui.button("Logout", on_click=logout).classes('w-full')
ui.space()
else:
login_mask()

127
main.py
View File

@ -54,130 +54,11 @@ def main():
ui.toggle.default_props('rounded')
ui.row.default_classes('items-baseline')
favicon = '''<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ns1="http://sozi.baierouge.fr"
xmlns:cc="http://web.resource.org/cc/"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:dc="http://purl.org/dc/elements/1.1/"
id="svg1"
sodipodi:docname="timeedit.svg"
viewBox="0 0 60 60"
sodipodi:version="0.32"
_SVGFile__filename="oldscale/actions/todo.svg"
version="1.0"
y="0"
x="0"
inkscape:version="0.40"
sodipodi:docbase="/home/danny/work/flat/SVG/mono/EXTRA/kexi"
>
<sodipodi:namedview
id="base"
bordercolor="#666666"
inkscape:pageshadow="2"
inkscape:window-y="0"
pagecolor="#ffffff"
inkscape:window-height="698"
inkscape:zoom="5.5342875"
inkscape:window-x="0"
borderopacity="1.0"
inkscape:current-layer="svg1"
inkscape:cx="36.490405"
inkscape:cy="19.560172"
inkscape:window-width="1024"
inkscape:pageopacity="0.0"
/>
<g
id="g1113"
transform="matrix(1.8141 0 0 1.8141 -24.352 -32.241)"
>
<path
id="path1894"
style="stroke-linejoin:round;stroke:#ffffff;stroke-linecap:round;stroke-width:5.5124;fill:none"
d="m43.399 34.31c0 7.418-6.02 13.438-13.438 13.438s-13.438-6.02-13.438-13.438 6.02-13.438 13.438-13.438 13.438 6.02 13.438 13.438z"
/>
<path
id="path741"
style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:#ffffff"
d="m43.399 34.31c0 7.418-6.02 13.438-13.438 13.438s-13.438-6.02-13.438-13.438 6.02-13.438 13.438-13.438 13.438 6.02 13.438 13.438z"
/>
<path
id="path743"
sodipodi:nodetypes="cc"
style="stroke-linejoin:round;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:none"
d="m29.961 34.169v-8.723"
/>
<path
id="path744"
style="stroke-linejoin:round;stroke:#000000;stroke-linecap:round;stroke-width:2.7562;fill:none"
d="m30.185 34.066l5.869 3.388"
/>
<path
id="path742"
style="stroke-linejoin:round;fill-rule:evenodd;stroke:#000000;stroke-linecap:round;stroke-width:1.7226;fill:#000000"
d="m31.178 34.31c0 0.672-0.545 1.217-1.217 1.217s-1.217-0.545-1.217-1.217 0.545-1.218 1.217-1.218 1.217 0.546 1.217 1.218z"
/>
</g
>
<metadata
>
<rdf:RDF
>
<cc:Work
>
<dc:format
>image/svg+xml</dc:format
>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
/>
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
/>
<dc:publisher
>
<cc:Agent
rdf:about="http://openclipart.org/"
>
<dc:title
>Openclipart</dc:title
>
</cc:Agent
>
</dc:publisher
>
</cc:Work
>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/"
>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution"
/>
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
/>
</cc:License
>
</rdf:RDF
>
</metadata
>
</svg
>
'''
if not os.path.exists(os.path.join(scriptpath, "favicon.svg")):
with open(os.path.join(scriptpath, "favicon.svg"), 'w') as favicon_file:
favicon_file.write(favicon)
#if not os.path.exists(os.path.join(scriptpath, "favicon.svg")):
# with open(os.path.join(scriptpath, "favicon.svg"), 'w') as favicon_file:
# favicon_file.write(favicon)
ui.run(favicon=os.path.join(scriptpath, "favicon.svg"), port=port, storage_secret=secret, language='de-DE', show_welcome_message=False)
ui.run(favicon='', port=port, storage_secret=secret, language='de-DE', show_welcome_message=False)
if __name__ in ("__main__", "__mp_main__"):
parser = argparse.ArgumentParser(description=f'{app_title} {app_version}')