Skip to main content

Django Rentals

PyPI version Python versions License Unit Tests

A Django app for vehicle/gear rental operators, listings, availability, and bookings: models, querysets, business rules and admin. It's the sibling package to django-trips, part of the DestinationPak platform. It ships no API or URLs: build your own endpoints on the services and querysets described under "Business rules" below. (The DRF API it shipped in 0.x was removed in 1.0.0; see the changelog.)

Installation

pip install django-rentals

Usage

Add the app to your installed apps:

INSTALLED_APPS = [
    ...
    'django_rentals',
]

Migrate

python manage.py migrate

Domain model

RentalOperator (the tenant/owner entity, mirrors django_trips.Host) → RentalListing (one bookable vehicle or gear kit, mirrors Trip) → RentalAvailability (a bookable date, mirrors TripSchedule) → RentalBooking (mirrors TripBooking, but books a start_date/end_date range rather than a single departure date).

There is deliberately no separate tier/package model the way django_trips has TripPackage — a distinct RentalListing per vehicle/kit already serves that purpose.

Like django_trips, this package is tenancy-oblivious: it has no concept of which user is allowed to manage a given RentalOperator. That authorization layer belongs to whichever project installs this app (see destipak's docs/multi-tenancy-design.md for the pattern this is meant to plug into).

Business rules

The booking and availability rules live in django_rentals.services and the model querysets, so any caller (your own API, a management command, the admin) gets the same behaviour:

from django_rentals.models import RentalAvailability, RentalBooking, RentalListing
from django_rentals.services import cancel_rental_booking, create_rental_booking

listings = RentalListing.objects.published()               # published, active, verified operator
open_dates = RentalAvailability.objects.bookable()          # from today, in stock, published listing
booking = create_rental_booking(
    availability, full_name="Ayesha Khan", email="ayesha@example.com",
    phone_number="+923001234567", start_date=start, end_date=end,
)                                                            # per-day price x days, inclusive
cancel_rental_booking(booking)                               # gives the units back
found = RentalBooking.objects.matching_guest(number, email="ayesha@example.com")

create_rental_booking takes the listing's availability row for start_date. Every day in the range needs an open() row (from today, on a published listing) with a unit left, and each of those rows gives up one unit, under a row lock so two bookings can't both take the last unit on a day. It raises Django's ValidationError when the range ends before it starts, and (keyed by availability) when the row isn't the start date's or a day has no unit left. cancel_rental_booking gives one unit back on each day, and raises ValidationError for a booking that is already cancelled or can't be cancelled. matching_guest never matches on the booking number alone.

Custom Location model

django_rentals.Location (a plain name/slug/lat/lng model - no region/parent hierarchy, unlike django_trips.Location) is swappable, the same way Django's own AUTH_USER_MODEL is. RentalListing.location is the only location field on RentalListing now - the original free-text RentalListing.city field has been dropped. If you're upgrading from a version that still had it, a prior migration best-effort backfilled location from each existing city string before city itself was removed.

Two settings, both optional and both defaulting to this package's own bundled model:

  • DJANGO_RENTALS_LOCATION_MODEL - an "app_label.ModelName" string naming which model actually satisfies the FK, e.g. DJANGO_RENTALS_LOCATION_MODEL = "myapp.City". Your model doesn't need to share Location's field names.
  • DJANGO_RENTALS_LOCATION_ADAPTER - a dotted path to a django_rentals.location_adapter .LocationAdapter subclass telling this app how to read your model's fields as if they were Location's (get_name, get_slug, get_lat, get_lng). Read location fields through get_location_adapter() rather than by field name, so your adapter is the only place that needs to know your model's real shape.

Building a brand-new Location model rather than reusing one you already have? Inherit django_rentals.models.AbstractLocation instead of writing an adapter - it's a plain abstract Django model (the same shape AbstractUser is - real fields and concrete methods, not an interface class) already carrying name/slug/lat/lng and their read methods, so you get a working swap with no DJANGO_RENTALS_LOCATION_ADAPTER at all:

# myapp/models.py
from django_rentals.models import AbstractLocation

class MyLocation(AbstractLocation):
    city_code = models.CharField(max_length=10)
# settings.py
DJANGO_RENTALS_LOCATION_MODEL = "myapp.MyLocation"

Reusing an existing model instead - one you can't restructure, or one shared with other libraries - stick with the adapter approach above; that's what it's for.

Set both before your project's first migrate. Like AUTH_USER_MODEL, this is a swappable-model setting - Django resolves it once when the app loads, and a swap made after Location's own table has already been created (and other tables have already foreign-keyed into it) doesn't retroactively move that data; it needs a real data migration instead of a config change.

For a worked example of a real swap: the DestinationPakistan platform (this package's own primary consumer, a private project) points this setting directly at its own public.Location model, with no adapter override at all - public.Location already has name/slug/lat, plus an lng property alias (its own field is lon, matching django-trips' naming), so the default LocationAdapter reads it correctly with no subclass. See docs/location-model-swap-design.md in that project for the full writeup.

Development

All development happens inside Docker (make dev.up, make update_db, make test, make random_rentals) — see the Makefile (make help lists every target).

Documentation

This README is also published as browsable docs (docs/, built with Sphinx). Build it locally with:

pip install -e ".[docs]"
sphinx-build -b html docs docs/_build

Contributing

See CONTRIBUTING.md for the development/release workflow, and the Code of Conduct. Found a security issue? See SECURITY.md rather than opening a public issue.

Release files for django-rentals 1.2.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-rentals 1.2.0
File Size Uploaded
django_rentals-1.2.0.tar.gz 52.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-rentals 1.2.0
File Interpreter ABI Platform
django_rentals-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 80.4 kB

Release files / django_rentals-1.2.0.tar.gz

Download URL django_rentals-1.2.0.tar.gz
Size 52.1 kB
Tags Source
SHA-256 checksum
How to use checksums
4d1cb719304652a8f5cc6af5d020145b8af79e90479e0e9eb068e3808be0eb11
BLAKE2b-256 checksum
How to use checksums
05c4491ac482f8d9614be184c7d910c0ea8a099558400a6f3943ffaf9c1ed308
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 25, 2026.

Transparency log

Release files / django_rentals-1.2.0-py3-none-any.whl

Download URL django_rentals-1.2.0-py3-none-any.whl
Size 28.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2c9efe4a6f8fca05a464f073896ad3fba95ba09887e0719e4258f2d315908121
BLAKE2b-256 checksum
How to use checksums
949e80173af68578d47caf6d8ef6d18eac50da68bb3e5a5efd2fb53231b15b0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

1.3.0

2 release files

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

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