feat: Aufgaben (Tasks) mit CalDAV VTODO-Sync
Neuer Menuepunkt "Aufgaben" unterhalb Kontakte. Backend: - TaskList + Task + TaskListShare Models - REST-API: CRUD, Teilen, my-color, Import/Export (.ics mit VTODO, CSV) - CalDAV: Task-Listen tauchen als Calendar-Collection mit supported-calendar-component-set=VTODO im autodiscovery auf - PROPFIND/REPORT/GET/PUT/DELETE/PROPPATCH/MKCOL fuer /dav/<user>/tl-<id>/ - SSE-Notifications bei Aenderungen Frontend: - TasksView mit Listen-Sidebar, Suche, "Erledigte ausblenden" - Mehrfachauswahl + Bulk-Loeschen, Status-Toggle per Checkbox - Editor mit Titel/Beschreibung/Faellig/Prioritaet/Status/Fortschritt - Teilen, Farbe persoenlich anpassen, Import/Export Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -284,11 +284,11 @@ def propfind(subpath=''):
|
||||
multistatus.append(_principal_response(user))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/calendars/ : only calendar collections
|
||||
# /dav/<username>/calendars/ : Kalender + Aufgabenlisten (DAVx5 erkennt
|
||||
# VTODO-Listen automatisch an supported-calendar-component-set).
|
||||
if len(parts) == 2 and parts[1] == 'calendars':
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
# A plain collection container
|
||||
container = ET.Element(_qn('d', 'response'))
|
||||
ET.SubElement(container, _qn('d', 'href')).text = f'/dav/{user.username}/calendars/'
|
||||
propstat = ET.SubElement(container, _qn('d', 'propstat'))
|
||||
@@ -301,6 +301,9 @@ def propfind(subpath=''):
|
||||
if depth != '0':
|
||||
for cal in _user_calendars(user):
|
||||
multistatus.append(_calendar_response(user, cal))
|
||||
from .taskdav import user_lists, list_response
|
||||
for tl in user_lists(user):
|
||||
multistatus.append(list_response(user, tl))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/addressbooks/ : only addressbook collections
|
||||
@@ -322,10 +325,13 @@ def propfind(subpath=''):
|
||||
multistatus.append(_addressbook_response(user, ab))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/cal-<id>/ : calendar + events
|
||||
# /dav/<username>/cal-<id>/ : calendar + events (auch tl-N delegieren)
|
||||
if len(parts) == 2:
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
if parts[1].startswith('tl-'):
|
||||
from .taskdav import tl_propfind
|
||||
return tl_propfind(username=parts[0], tl_part=parts[1])
|
||||
cal_id = _parse_calendar_path(parts[1])
|
||||
if cal_id is None:
|
||||
return Response('Not found', 404)
|
||||
@@ -338,10 +344,13 @@ def propfind(subpath=''):
|
||||
multistatus.append(_event_response(user, cal, ev))
|
||||
return _xml_response(multistatus)
|
||||
|
||||
# /dav/<username>/cal-<id>/<uid>.ics : single event
|
||||
# /dav/<username>/cal-<id>/<uid>.ics : single event (tl-N delegieren)
|
||||
if len(parts) == 3:
|
||||
if parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
if parts[1].startswith('tl-'):
|
||||
from .taskdav import tl_task_propfind
|
||||
return tl_task_propfind(username=parts[0], tl_part=parts[1], filename=parts[2])
|
||||
cal_id = _parse_calendar_path(parts[1])
|
||||
cal = _calendar_for(user, cal_id) if cal_id else None
|
||||
if not cal:
|
||||
@@ -367,6 +376,9 @@ def report(subpath):
|
||||
parts = [p for p in subpath.split('/') if p]
|
||||
if len(parts) < 2 or parts[0] != user.username:
|
||||
return Response('', 403)
|
||||
if parts[1].startswith('tl-'):
|
||||
from .taskdav import tl_report
|
||||
return tl_report(username=parts[0], tl_part=parts[1])
|
||||
cal_id = _parse_calendar_path(parts[1])
|
||||
cal = _calendar_for(user, cal_id) if cal_id else None
|
||||
if not cal:
|
||||
@@ -449,6 +461,9 @@ def get_event(username, cal_part, filename):
|
||||
if cal_part.startswith('ab-'):
|
||||
from .carddav import ab_get
|
||||
return ab_get(username=username, ab_part=cal_part, filename=filename)
|
||||
if cal_part.startswith('tl-'):
|
||||
from .taskdav import tl_get
|
||||
return tl_get(username=username, tl_part=cal_part, filename=filename)
|
||||
user: User = request.dav_user
|
||||
if username != user.username:
|
||||
return Response('', 403)
|
||||
@@ -477,6 +492,9 @@ def put_event(username, cal_part, filename):
|
||||
if cal_part.startswith('ab-'):
|
||||
from .carddav import ab_put
|
||||
return ab_put(username=username, ab_part=cal_part, filename=filename)
|
||||
if cal_part.startswith('tl-'):
|
||||
from .taskdav import tl_put
|
||||
return tl_put(username=username, tl_part=cal_part, filename=filename)
|
||||
user: User = request.dav_user
|
||||
if username != user.username:
|
||||
return Response('', 403)
|
||||
@@ -536,6 +554,9 @@ def delete_event(username, cal_part, filename):
|
||||
if cal_part.startswith('ab-'):
|
||||
from .carddav import ab_delete
|
||||
return ab_delete(username=username, ab_part=cal_part, filename=filename)
|
||||
if cal_part.startswith('tl-'):
|
||||
from .taskdav import tl_delete
|
||||
return tl_delete(username=username, tl_part=cal_part, filename=filename)
|
||||
user: User = request.dav_user
|
||||
if username != user.username:
|
||||
return Response('', 403)
|
||||
@@ -561,6 +582,9 @@ def delete_calendar(username, cal_part):
|
||||
if cal_part.startswith('ab-'):
|
||||
from .carddav import ab_delete_collection
|
||||
return ab_delete_collection(username=username, ab_part=cal_part)
|
||||
if cal_part.startswith('tl-'):
|
||||
from .taskdav import tl_delete_collection
|
||||
return tl_delete_collection(username=username, tl_part=cal_part)
|
||||
user: User = request.dav_user
|
||||
if username != user.username:
|
||||
return Response('', 403)
|
||||
@@ -587,6 +611,9 @@ def delete_calendar(username, cal_part):
|
||||
@dav_bp.route('/<username>/<cal_part>', methods=['PROPPATCH'])
|
||||
@basic_auth
|
||||
def proppatch_calendar(username, cal_part):
|
||||
if cal_part.startswith('tl-'):
|
||||
from .taskdav import tl_proppatch
|
||||
return tl_proppatch(username=username, tl_part=cal_part)
|
||||
user: User = request.dav_user
|
||||
if username != user.username:
|
||||
return Response('', 403)
|
||||
|
||||
Reference in New Issue
Block a user