Django Rentals
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 also ships a DRF API, deprecated since 0.4.0 and removed in 1.0.0 (see "Business rules" below).
Installation
pip install django-rentals
Usage
Add the app (and django_filters, used by the catalog/availability filtering below) to
your installed apps:
INSTALLED_APPS = [
...
'django_filters',
'django_rentals',
]
Migrate
python manage.py migrate
Mount its urls under a namespace of your choosing:
urlpatterns = [
...
path('rentals/', include('django_rentals.urls')),
]
This mounts the whole app under your own chosen prefix (rentals/ above) with the lib's
own v1/ version underneath it, e.g. rentals/v1/listings/,
rentals/v1/schema/redoc/. The app versions itself independently of your project's own
API version.
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 create_rental_booking
listings = RentalListing.objects.published() # published, active, verified operator
open_dates = RentalAvailability.objects.bookable() # in stock, on a 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
found = RentalBooking.objects.matching_guest(number, email="ayesha@example.com")
create_rental_booking raises Django's ValidationError when the range ends before it starts,
and doesn't check or reduce units_available yet. matching_guest never matches on the
booking number alone.
Public API
Deprecated: the DRF API below (
django_rentals.api,django_rentals.urls) is removed in 1.0.0. Build your own endpoints on the services and querysets above.
Read-only and unauthenticated (AllowAny) unless noted:
listings/- the published catalog. Filterable via query params:?category=,?location=<id>,?operator=<id>.listings/<slug>/- one listing's detail, including its images and availabilities.operators/- active, verifiedRentalOperators.availabilities/- date-range availability search across active listings. Filterable via?listing=<slug>,?date_from=,?date_to=(any combination; omitting all three returns every upcoming bookable date).bookings/create/- guest booking (no auth required).bookings/lookup/?number=&email=- guest "find my booking".bookings/<number>/- authenticated traveller's own booking (retrieve/update/cancel).schema/,schema/swagger-ui/,schema/redoc/- this app's own OpenAPI schema, scoped to just these endpoints regardless of what else your project mounts.
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 shareLocation's field names.DJANGO_RENTALS_LOCATION_ADAPTER- a dotted path to adjango_rentals.location_adapter .LocationAdaptersubclass telling this app how to read your model's fields as if they wereLocation's (get_name,get_slug,get_lat,get_lng).RentalListingSerializerexposeslocationas a nested object through this adapter, and?location=<id>filters on it directly.
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 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_rentals-0.4.0.tar.gz | 59.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_rentals-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 92.3 kB
Release files / django_rentals-0.4.0.tar.gz
| Download URL | django_rentals-0.4.0.tar.gz |
|---|---|
| Size | 59.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b191b0ad39efb71e8bce196cd69c83f6b3c5c6a2c351f3915ddf46fa19945c90
|
|
BLAKE2b-256 checksum How to use checksums |
de11242c99e938e3dc8334658adcff31550d573828890487ac00cd3d97a8b4b0
|
| 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 24, 2026.
Transparency logRelease files / django_rentals-0.4.0-py3-none-any.whl
| Download URL | django_rentals-0.4.0-py3-none-any.whl |
|---|---|
| Size | 33.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
77018299995597c0d3e99a226cc6b1d3056a90db906c5b65a95c7d2e04b3a295
|
|
BLAKE2b-256 checksum How to use checksums |
efd7da0d556cc04cb58b9f5f9250bc6dc5182eefcb2ba65fc767f804f7446a6c
|
| 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 24, 2026.
Transparency log