Skip to main content

django-ec-calendar

Python tests JavaScript tests Current version on PyPI PyPI - Python Version PyPI - Django Version Downloads

A calendar for the Django admin and your own pages, with EventCalendar (by vkurko, MIT): a changelist's objects by date — opened with a click, added by selecting the dates, moved and resized by dragging — in Django's admin and in django-unfold, light and dark.

The bookings of a week in the admin's calendar, each in its room's color

Install

uv add django-ec-calendar
# or
pip install django-ec-calendar

Add it to INSTALLED_APPS (so Django finds its templates, and django.contrib.staticfiles its JS and CSS — EventCalendar included, no CDN):

INSTALLED_APPS = [
    ...,
    "django_ec_calendar",
]

In the admin

from django.contrib import admin
from django_ec_calendar import CalendarAdminMixin


@admin.register(Booking)
class BookingAdmin(CalendarAdminMixin, admin.ModelAdmin):
    list_filter = ("room",)
    search_fields = ("title",)
    calendar_start_field = "start"
    calendar_end_field = "end"  # optional
    calendar_color_field = "color"  # optional: a CSS color, a field or property
    calendar_view = "timeGridWeek"  # optional ("dayGridMonth")
    calendar_options = {"slotMinTime": "07:00"}  # optional: any of EventCalendar's

The changelist gets a "Calendar" link, next to "Add":

  • What it shows is what the changelist would: its get_queryset(), and the filters and search it had when the link was followed. One query per range the calendar shows.
  • A click on an event opens its change page (read-only, for those who may only view it).
  • Selecting dates (or clicking one) opens the add page with them, for those who may add (calendar_selectable = False: not). A click starts at its slot's start (9:20 in half-hour slots: 9:00).
  • Dragging an event, or its end, saves its dates, for those who may change it (calendar_editable = False: not). The move is checked by the model (full_clean() of its dates, and its clean()), saved through save_model() and logged as the admin's changes are; what can't be saved goes back, saying why.
  • Its views: calendar_views — by default month, week, day and list (dayGridMonth, timeGridWeek, timeGridDay, listMonth; any of EventCalendar's).

In the menu

The calendar's URL is admin:<app>_<model>_calendar; in Unfold's sidebar, for instance:

UNFOLD = {
    "SIDEBAR": {
        "navigation": [
            {
                "title": "Agenda",
                "items": [
                    {
                        "title": "Calendar",
                        "icon": "calendar_month",
                        "link": reverse_lazy("admin:agenda_booking_calendar"),
                    },
                ],
            },
        ],
    },
}

Its fields

Option
calendar_start_field A DateTimeField (events at a time) or a DateField (all-day events). Required.
calendar_end_field Where it ends (none: it lasts no time, or its day; it can't be resized).
calendar_all_day_field A BooleanField: true, an all-day event of a DateTimeField.
calendar_title_field Its title (none: str(obj)).
calendar_color_field Its color, a CSS color (none: the admin's).

get_calendar_event(request, obj) makes the whole event (any of EventCalendar's event properties), calling super().

A DateField's end is the last day (EventCalendar's is the day after: it's converted both ways).

Whole days: a month of holidays

Time zones

Times show as the wall-clock time of Django's current time zone, and are saved so, whatever the browser's: an event at 10:00 in Madrid shows at 10:00 to someone in New York too. "Today" and the time now are the site's.

Unfold, dark

With Unfold, in its colors, and next to its buttons. In both, the calendar follows the page's theme, light or dark (the admin's theme switch, Unfold's), else the system's.

The same week in Unfold

In the admin's dark theme

In Unfold's dark theme

On your pages

A view with the events, as the calendar asks for them:

from django_ec_calendar import EventFeedView


class BookingEvents(EventFeedView):
    queryset = Booking.objects.select_related("room")  # or model, get_queryset()
    calendar_start_field = "start"
    calendar_end_field = "end"
    calendar_color_field = "color"
urlpatterns = [path("events/", BookingEvents.as_view(), name="booking-events")]

Each event leads to the object's get_absolute_url() (override get_calendar_event_url(obj)). Who may see them is the view's own business: LoginRequiredMixin, get_queryset()…

And the calendar, in a template:

{% load ec_calendar %}
<head>
  ...
  {% ec_calendar_media %}
</head>
<body>
  {% url "booking-events" as events %}
  {% ec_calendar events view="timeGridWeek" views="dayGridMonth timeGridWeek listWeek" slotMinTime="07:00" %}
</body>

views are separated by spaces (empty: none to switch to); any other keyword is one of EventCalendar's options. On a page it's read-only.

A calendar on a page

Its events, for doing otherwise

Before it does what it does, the calendar fires an event on its element, which bubbles and can be cancelled — preventDefault(), and it isn't done — with the dates as the server reads them:

Event detail Otherwise
django-ec-calendar:event-click event (EventCalendar's), url, jsEvent Opens url.
django-ec-calendar:date-click start, allDay, jsEvent Opens the add page.
django-ec-calendar:select start, end, allDay, jsEvent Opens the add page.
django-ec-calendar:event-change event, saved, error (After a move; not cancellable.)
document.addEventListener("django-ec-calendar:event-click", (event) => {
    event.preventDefault();
    openDialog(event.detail.url);  // A dialog, instead of a page.
});

They fire without an add page too (on your pages, selectable: true in the options for selections).

The script makes every [data-django-ec-calendar] on the page; window.djangoEcCalendar.setup() makes those added later, and window.djangoEcCalendar.go is where a click leads (a single-page app's router, instead of a page load).

Content Security Policy

It works under a strict policy (script-src 'self'; style-src 'self', no 'unsafe-inline'): each calendar's configuration is a JSON data block (json_script, which script-src doesn't run), read by the package's own script, and EventCalendar sets its elements' styles through the CSSOM (which style-src doesn't restrict).

Translations

Ships a Spanish (es) translation; the days' and months' names come from the browser, in the request's language, and the first day of the week from Django's FIRST_DAY_OF_WEEK.

EventCalendar

EventCalendar 5.17.1's standalone build is included as it's published (static/django_ec_calendar/vendor/event-calendar/, with its MIT license). Its options are documented in its README.

Supported versions

Django 4.2 – 6.1, Python 3.10+.

Development

See CONTRIBUTING.md for setup, running the example project, and the test/lint/type-check commands.

Metadata

Release files for django-ec-calendar 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-ec-calendar 0.1.0
File Size Uploaded Provenance Actions
django_ec_calendar-0.1.0.tar.gz 572.5 kB With provenance Details for django_ec_calendar-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-ec-calendar 0.1.0
File Interpreter ABI Platform Actions
django_ec_calendar-0.1.0-py3-none-any.whl Python 3 none any Details for django_ec_calendar-0.1.0-py3-none-any.whl

Total release size: 645.8 kB

Release files / django_ec_calendar-0.1.0.tar.gz

Download URL django_ec_calendar-0.1.0.tar.gz
Size 572.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a36acf6cded2d38a78418f9b5b72ed95ed10af62e7b4cec37b1bf877e1fc89f8
BLAKE2b-256 checksum
How to use checksums
97e59d8b3a75b6c6f2002e9a39223336672df0aa4327251de7dca5005724c2bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Release files / django_ec_calendar-0.1.0-py3-none-any.whl

Download URL django_ec_calendar-0.1.0-py3-none-any.whl
Size 73.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bff67319c1da9082f4bbaa83c3a191d326a1e26aa0db9e7ca3e246300583ff07
BLAKE2b-256 checksum
How to use checksums
60d24c0db0ca20dd7f51bc56f4a747edfdbb4c839797b90f26f1ba9d241dca24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

Security for django-ec-calendar 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_ec_calendar-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page