Pre-requisites:
- Knowledge of Django: I will be assuming that you already know how Django works, how APIs are created in Django and how Django URLs work.
- Django project set up: I followed this tutorial to set up my django project.
- Docker: You need to have docker and docker compose installed in order to set up baikal.
- Knowledge of Django Rest Framework (Optional).
- CalDAV Client (I am using ThunderBird)
Set Up Baikal
Installation
To set up baikal, first create a compose.yml
file. The contents of this file will be as follows:
version: "2"
services:
baikal:
image: ckulka/baikal:nginx
restart: always
ports:
- "8001:80"
volumes:
- config:/var/www/baikal/config
- data:/var/www/baikal/Specific
volumes:
config:
data:
This is the same docker compose configuration as the one in baikal github repository with a small change. The port is set to 8001 instead of 80.
Once this file is set up, you can spin up your baikal server by running the following command.
docker compose up -d --build
NOTE:
You must run the command above in the same directory as thecompose.yml
file.
Once your baikal instance is up and running, go to http://localhost:8001/admin/install/
in your browser. You can replace localhost with the ip of the machine your baikal server is running on. After opening this page in your browser, you will be greeted with the following page:
Here is a brief explanation of every field in this form:
- Server Time zone: Sets the default time zone for the server. Affects how times are displayed and interpreted in CalDAV (calendar) events and CardDAV (contacts) when no time zone is explicitly provided.
- Enable CardDAV: Enables CardDAV, which is a protocol for syncing contacts.
- Enable CalDAV: Enables CalDAV, which is a protocol for syncing calendar events.
- Email invite sender address: The email that is used to send email invites (to attendees or user you are sharing a calendar with). If left blank, email invites won't be sent.
-
WebDAV authentication type:
- Basic: Uses standard HTTP Basic Authentication (username and password)
- Digest: Similar to basic authentication, but "applies a hash function to the username and password before sending them over the network" (1kosmos, n.d)
- Apache: Uses the apache web server's authentication.
Here, change the WebDAV authentication type
to 'basic' and set admin password, then proceed. After proceeding, you will be redirected to the following form.
Here, you can configure a database. You have three options: sqlite, mysql, and postgreSQL. For simplicity, we will select sqlite and click on "Save changes" button. After this, you can start using Baikal.
Configuring Users in Baikal
To configure users in Baikal, log into the baikal admin console at http://localhost:8001/admin/
. You can replace "localhost" here with the IP address of your server. Once you are in the Baikal admin console, navigate to the "Users and resources" tab. You should see the following page:
Here, click on the "Add user" button to add a new user. A form will open.
Here, add the Username, Display name, Email, and Password of your new user. Make sure to store these information somewhere as we will be using these credentials to fetch calendar events later on.
For this tutorial, I have configured this user with the following credentials (password: "calendar_user"), but you should come up with something more secure :)
Creating a Calendar
To create a calendar, click on the "calendar" button on the right hand side of the user you just created. You will then be redirected to the user's calendar page. Here click on the "Add Calendar" button. The following form should appear.
Here, enter the Calendar token ID and Display name, then click on "Save changes" to create your new calendar.
You can now click on the "i" icon on the side of the calendar u just created to get the calendar link. Use this calendar link to connect to the calendar in any CalDAV client software you like. I am using thunderbird, so the process for it looked like this:
- Get calendar uri:
- Create a new calendar in thunderbird, select "On the Network".
- Enter the username of the user you previously created (or the user linked to the calendar), and add the calendar uri in the "Location" input box.
- Enter the password of the user and subscribe.
Once you have connected your client to your CalDAV client, create an event. We will check if this event exists in google calendar for verification.
This is everything we need to configure in Baikal. Well done. Take a breather, and come back for the next step!
Creating APIs in Django
Installing Python Libraries
We will be using three python libraries to fetch calendar events, create ics objects, and create an API to serve these data:
- CalDAV
- iCalendar
- djangorestframework
Since I'm using poetry, I installed them using the following command:
poetry add caldav icalendar django-rest-framework
Functions
You will need the following two utility function to fetch calendar events and create ics objects.
import caldav
from datetime import datetime, timedelta, timezone
import caldav.lib
import caldav.lib.error
from icalendar import Calendar, Event, vText
from rest_framework import views
import io
# The following variables should ideally be set in your environment variables.
CALDAV_URL = "http://localhost:8001/dav.php/"
CALDAV_USERNAME = "calendar_user"
CALDAV_PASSWORD = "calendar_user"
def get_caldav_events(calendar_name="Default"):
"""
Fetches events from a specific CalDAV calendar.
"""
events_data = []
try:
with caldav.DAVClient(url=CALDAV_URL, username=CALDAV_USERNAME, password=CALDAV_PASSWORD) as client:
my_principal = client.principal()
target_calendar = my_principal.calendar(calendar_name)
if target_calendar:
# Fetch events for a reasonable time range (e.g., past month to next year)
start_date = datetime.now(timezone.utc) - timedelta(days=30)
end_date = datetime.now(timezone.utc) + timedelta(days=365)
events = target_calendar.search(
start=start_date,
end=end_date,
event=True,
)
for event in events:
# event.data contains the raw iCalendar string
events_data.append(event.data)
else:
print(f"Calendar '{calendar_name}' not found.")
except caldav.lib.error.NotFoundError:
print("CalDAV resource not found. Check URL and credentials.")
except caldav.lib.error.AuthorizationError:
print("CalDAV authorization failed. Check username and password.")
except Exception as e:
print(f"An error occurred: {e}")
return events_data
def generate_ics_from_caldav_events(caldav_event_strings):
"""
Generates an iCalendar (.ics) string from a list of raw CalDAV event strings.
"""
cal = Calendar()
cal.add('prodid', '-//Your Company//Your Calendar API//EN')
cal.add('version', '2.0')
cal.add('name', vText('Test'))
cal.add('x-wr-calname', vText('Test'))
for event_str in caldav_event_strings:
try:
event_cal = Calendar.from_ical(event_str)
for component in event_cal.walk():
if component.name == 'VEVENT':
cal.add_component(component)
except Exception as e:
print(f"Error parsing event: {e}. Skipping event: {event_str[:100]}...")
continue
return cal.to_ical().decode('utf-8')
The get_caldav_events()
function will fetch the calendar event data from the last 30 days to 365 days into the future. If you want to fetch all the events in the calendar, you can simply replace target_calendar.search(...)
with target_calendar.events()
.
Here, CALDAV_USERNAME
and CALDAV_PASSWORD
variables should be set to the creds of the user you created earlier. This will be used to authenticate with the CalDAV server and fetch the user's calendar data in the get_caldav_events()
function. The get_caldav_events()
function simply returns an array of event data. We can now pass this array of event data to generate_ics()
to construct an ics string, which google calendar will use to import the calendar and event information. In the generate_ics()
function, we are simply looping over each event data and adding the VEVENT
component to our calendar object.
API to Sync Calendar Events
class CalendarSyncAPI(views.APIView):
def get(self, request, calendar_name):
events_data = get_caldav_events(calendar_name=calendar_name)
if not events_data:
return HttpResponse("No events found for this calendar.", status=404)
ics_content = generate_ics_from_caldav_events(events_data)
# Use io.BytesIO to create an in-memory file-like object
# This is more efficient than loading the entire string into memory for FileResponse
ics_file = io.BytesIO(ics_content.encode('utf-8'))
response = FileResponse(ics_file, content_type='text/calendar')
response['Content-Disposition'] = f'attachment; filename="{calendar_name}.ics"'
return response
We can use the utility functions we created earlier to generate the ics string and return it in our API as shown above.
You can now link this API to a url in your application as shown below.
urlpatterns = [
path('<str:calendar_name>.ics', apis.CalendarSyncAPI.as_view(), name='sync-calendar'),
]
You can now use this URL to import calendar in google calendar using the "From URL" option. Once you have imported the calendar, you should see the event(s) you created using a CalDAV client earlier.
NOTE:
It may take upto 12 hours for Google to re-sync the calendar. It is also worth noting that you cannot modify or add any events to your CalDAV calendar using this method. It is merely for reading purpose.
Top comments (0)