Django Recurrence
django-recurrence is a utility for working with recurring dates in Django. It provides `Recurrence/Rule` objects based on a subset of rfc2445 (wrapping `dateutil.rrule`), a `RecurrenceField` for database storage, and a JavaScript widget for user input. The library is currently at version 1.14 and is actively maintained by the Jazzband community, with regular releases to support new Django and Python versions.
Common errors
-
RecurrenceField is not rendered correctly in Django admin or forms (missing JavaScript widget).
cause The necessary JavaScript and CSS static files, along with the `javascript_catalog` URL, are not correctly included in your project or templates.fix1. Add `'recurrence'` to your `INSTALLED_APPS`. 2. Ensure `django.contrib.staticfiles` is also in `INSTALLED_APPS` and run `python manage.py collectstatic`. 3. In your `urls.py`, include the `javascript_catalog` view: `from django.urls import re_path as url; from django.views.i18n import JavaScriptCatalog; js_info_dict = {'packages': ('recurrence', )}; urlpatterns += [url(r'^jsi18n/$', JavaScriptCatalog.as_view(), js_info_dict), ]`. 4. In your template, include `{{ form.media }}` within the `<head>` section. -
ImportError: No module named 'recurrence.fields'
cause The `django-recurrence` library is not installed, or `recurrence` is not added to `INSTALLED_APPS`.fixFirst, install the package: `pip install django-recurrence`. Then, ensure `'recurrence'` is included in your `INSTALLED_APPS` tuple in your Django project's `settings.py` file. -
I can create recurring events, but how do I delete or edit a *single* occurrence without affecting the whole series?
cause The library works by defining rules, and modifying a single occurrence requires adding an explicit exception to that rule, rather than directly editing an 'instance'.fixTo delete a single occurrence, you must add an `EXDATE` (Exclusion Date) property to the recurrence rule for that specific date. To edit a single occurrence (e.g., change its time or title), you typically treat it as an `EXDATE` and create a new, separate single-day event (or `RDATE`) for the modified occurrence, while leaving the main recurrence rule untouched.
Warnings
- breaking Version 1.13 dropped support for Python <= 3.9 and Django <= 4.2.
- breaking Version 1.12 dropped support for Django < 4 and Python < 3.9.
- gotcha When querying recurrence occurrences using methods like `between()`, `before()`, or `after()`, omitting the `dtstart` parameter can lead to unexpected results, as it may implicitly default to the current time, thus ignoring occurrences before `datetime.now()`.
- gotcha The `RecurrenceField` primarily defines the *pattern* of recurrence and does not inherently manage specific time information (e.g., `start_time`) or timezone awareness for individual occurrences. It wraps `dateutil.rrule`, which generates `datetime` objects. Combining it with naive Django `TimeField`s can lead to timezone-related issues.
Install
-
pip install django-recurrence
Imports
- RecurrenceField
from recurrence.fields import RecurrenceField
- Recurrence
from recurrence.forms import Recurrence
Quickstart
import os
from django.conf import settings
from django.db import models
# Minimal Django setup for demonstration
if not settings.configured:
settings.configure(
INSTALLED_APPS=[
'django.contrib.auth',
'django.contrib.contenttypes',
'recurrence' # Add 'recurrence' to INSTALLED_APPS
],
DATABASES={'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:'}},
SECRET_KEY=os.environ.get('DJANGO_SECRET_KEY', 'a-very-secret-key-for-testing'),
USE_TZ=True # Recommended for date/time handling
)
import django
django.setup()
from recurrence.fields import RecurrenceField
from datetime import date, datetime
class Event(models.Model):
title = models.CharField(max_length=200)
start_date = models.DateField(default=date.today)
recurrences = RecurrenceField()
class Meta:
app_label = 'myapp' # Required for minimal Django setup
def __str__(self):
return self.title
# Create an event (example for admin/programmatic creation)
# In a real app, this would be handled via forms/admin
weekly_event = Event.objects.create(
title="Weekly Meeting",
recurrences=RecurrenceField().compress_rules([
"RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=10",
"RDATE:20260420T090000", # Explicit date/time for specific occurrence
"EXDATE:20260501T090000" # Exclude a specific date
])
)
# Retrieve occurrences for the event
# Note: RecurrenceField itself deals with recurrences, not specific time info from start_date/time.
# When using .between(), it's crucial to set dtstart for past occurrences.
start_range = datetime(2026, 4, 1, 0, 0, 0)
end_range = datetime(2026, 6, 30, 23, 59, 59)
print(f"Occurrences for '{weekly_event.title}' between {start_range.date()} and {end_range.date()}:")
for occ_date in weekly_event.recurrences.between(start_range, end_range, dtstart=start_range, inc=True):
print(f"- {occ_date.date()}")
# Example of getting the next occurrence
next_occurrence = weekly_event.recurrences.after(datetime.now())
if next_occurrence:
print(f"\nThe next occurrence after now is: {next_occurrence.date()}")