Metadata-Version: 2.4
Name: django-hotels
Version: 1.3.0
Summary: A Django app for hotels, room types, availability and bookings: models, business rules and admin.
Author-email: Awais Jibran <awaisdar001@gmail.com>
License: MIT License
        
        Copyright (c) 2026 Awais Jibran
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Project-URL: Homepage, https://github.com/DestinationPak/django-hotels
Project-URL: Changelog, https://github.com/DestinationPak/django-hotels/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/DestinationPak/django-hotels/issues
Project-URL: Source, https://github.com/DestinationPak/django-hotels
Keywords: django,hotels,travel,bookings
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: AUTHORS
Requires-Dist: Django<6.0,>=4.2
Requires-Dist: swapper>=1.3.0
Provides-Extra: dev
Requires-Dist: factory-boy>=3.3; extra == "dev"
Requires-Dist: Faker; extra == "dev"
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-django>=4.5.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: pylint>=2.0; extra == "dev"
Requires-Dist: pylint-django>=2.5.0; extra == "dev"
Requires-Dist: pylint-plugin-utils; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=7.0; extra == "docs"
Requires-Dist: myst-parser>=2.0; extra == "docs"
Requires-Dist: furo; extra == "docs"
Dynamic: license-file

# Django Hotels

[![PyPI version](https://img.shields.io/pypi/v/django-hotels.svg)](https://pypi.org/project/django-hotels/)
[![Python versions](https://img.shields.io/pypi/pyversions/django-hotels.svg)](https://pypi.org/project/django-hotels/)
[![License](https://img.shields.io/pypi/l/django-hotels.svg)](https://github.com/DestinationPak/django-hotels/blob/main/LICENSE)
[![Unit Tests](https://github.com/DestinationPak/django-hotels/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/DestinationPak/django-hotels/actions/workflows/unit-tests.yml)

A Django app for hotels, room types, availability, and bookings: models, querysets, business
rules and admin. 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.)

This service is a sibling of [django-trips](https://github.com/awaisdar001/django-trips), and
is a core component of the [DestinationPak](https://destinationpak.com) project — a platform
designed to make exploring and booking adventures across Pakistan easier and more accessible.

## Installation
Simply do:
```bash
pip install django-hotels
```

## Usage
Add the app into your installed apps in your project's settings file.
```
INSTALLED_APPS = [
    ...
    'django_hotels',
]
```
## Migrate
```
python manage.py migrate
```

## Domain model

```
HotelOwner (the business/brand)
  └── Hotel (one bookable property)
        ├── HotelImage
        └── HotelRoomType (a room category/tier)
              └── HotelAvailability (a dated, priced, bookable instance)
                    └── HotelBooking (a guest or logged-in traveller's booking)
```

`HotelOwner`/`Hotel` deliberately carry no login/auth/permission fields — this package is
tenancy-oblivious, the same way `django-trips` is. A consuming project owns the membership
layer (who may manage which `HotelOwner`), not this library.

## Business rules

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

```python
from django_hotels.models import HotelAvailability, HotelBooking
from django_hotels.services import cancel_hotel_booking, create_hotel_booking

open_dates = HotelAvailability.objects.bookable()          # from today, in stock, active, verified owner
booking = create_hotel_booking(
    availability, full_name="Ayesha Khan", email="ayesha@example.com",
    phone_number="+923001234567", guests=2,
)                                                            # priced per guest, takes one room
cancel_hotel_booking(booking)                                # gives the room back
found = HotelBooking.objects.matching_guest(number, email="ayesha@example.com")
```

`matching_guest` never matches on the booking number alone: it needs the `otp` or the `email`
as well, so a guessed number can't reveal someone else's booking. A booking takes one room:
`create_hotel_booking` raises Django's `ValidationError` (keyed by `availability`) when
the date isn't in `HotelAvailability.objects.open()` (past, or its hotel isn't bookable) or
`rooms_available` is already 0, and otherwise lowers it by one under a row lock, so two bookings
can't both take the last room. `cancel_hotel_booking` raises `ValidationError` for a booking
that is already cancelled or past `PENDING`/`WAITING_PAYMENT`, and otherwise gives the room
back.

## Custom Location model

`django_hotels.Location` (a plain `name`/`slug`/`lat`/`lng` model, no region/parent hierarchy)
is swappable, the same way Django's own `AUTH_USER_MODEL` is - if your project already has its
own location/city model, you don't have to duplicate location data into a second table just to
install this app.

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

- **`DJANGO_HOTELS_LOCATION_MODEL`** - an `"app_label.ModelName"` string naming which model
  actually satisfies `Hotel.location`, e.g. `DJANGO_HOTELS_LOCATION_MODEL = "myapp.City"`. Your
  model doesn't need to share `Location`'s field names.
- **`DJANGO_HOTELS_LOCATION_ADAPTER`** - a dotted path to a `django_hotels.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`). Every place this app reads a
  location goes through `django_hotels.location_adapter.get_location_adapter()`, never by field
  name directly, 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_hotels.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_HOTELS_LOCATION_ADAPTER` at all:

```python
# myapp/models.py
from django_hotels.models import AbstractLocation

class MyLocation(AbstractLocation):
    city_code = models.CharField(max_length=10)
```

```python
# settings.py
DJANGO_HOTELS_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 doesn't retroactively move existing data.

`Hotel.location` is the only location field on `Hotel` now - the original free-text `Hotel.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 (matching or creating a
`Location` by name) before `city` itself was removed.

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.

## Local development

All commands assume Docker (`make dev.up`, `make update_db`, `make test`) — see the Makefile
for the full command list (`make help`).

## Generate demo data

```
./manage.py generate_hotels --batch_size=10
```

## Documentation

This README is also published as browsable docs (`docs/`, built with Sphinx). Build it
locally with:
```bash
pip install -e ".[docs]"
sphinx-build -b html docs docs/_build
```

## Contributing

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