django-hotels 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_hotels-0.1.0/AUTHORS +0 -0
- django_hotels-0.1.0/LICENSE +21 -0
- django_hotels-0.1.0/MANIFEST.in +3 -0
- django_hotels-0.1.0/PKG-INFO +114 -0
- django_hotels-0.1.0/README.md +63 -0
- django_hotels-0.1.0/django-hotels/__init__.py +0 -0
- django_hotels-0.1.0/django-hotels/asgi.py +11 -0
- django_hotels-0.1.0/django-hotels/urls.py +9 -0
- django_hotels-0.1.0/django-hotels/wsgi.py +13 -0
- django_hotels-0.1.0/django_hotels/__init__.py +3 -0
- django_hotels-0.1.0/django_hotels/admin.py +46 -0
- django_hotels-0.1.0/django_hotels/api/__init__.py +0 -0
- django_hotels-0.1.0/django_hotels/api/serializers.py +107 -0
- django_hotels-0.1.0/django_hotels/api/urls.py +30 -0
- django_hotels-0.1.0/django_hotels/api/views/__init__.py +0 -0
- django_hotels-0.1.0/django_hotels/api/views/booking.py +31 -0
- django_hotels-0.1.0/django_hotels/api/views/hotel.py +37 -0
- django_hotels-0.1.0/django_hotels/apps.py +9 -0
- django_hotels-0.1.0/django_hotels/choices.py +30 -0
- django_hotels-0.1.0/django_hotels/management/__init__.py +0 -0
- django_hotels-0.1.0/django_hotels/management/commands/__init__.py +0 -0
- django_hotels-0.1.0/django_hotels/management/commands/generate_hotels.py +69 -0
- django_hotels-0.1.0/django_hotels/managers.py +16 -0
- django_hotels-0.1.0/django_hotels/migrations/0001_initial.py +294 -0
- django_hotels-0.1.0/django_hotels/migrations/__init__.py +0 -0
- django_hotels-0.1.0/django_hotels/models.py +225 -0
- django_hotels-0.1.0/django_hotels/urls.py +12 -0
- django_hotels-0.1.0/django_hotels.egg-info/PKG-INFO +114 -0
- django_hotels-0.1.0/django_hotels.egg-info/SOURCES.txt +37 -0
- django_hotels-0.1.0/django_hotels.egg-info/dependency_links.txt +1 -0
- django_hotels-0.1.0/django_hotels.egg-info/entry_points.txt +2 -0
- django_hotels-0.1.0/django_hotels.egg-info/requires.txt +20 -0
- django_hotels-0.1.0/django_hotels.egg-info/top_level.txt +3 -0
- django_hotels-0.1.0/settings/__init__.py +0 -0
- django_hotels-0.1.0/settings/common.py +95 -0
- django_hotels-0.1.0/settings/test.py +2 -0
- django_hotels-0.1.0/setup.cfg +58 -0
- django_hotels-0.1.0/setup.py +90 -0
|
File without changes
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 The Python Packaging Authority
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-hotels
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Django Rest API for managing hotels, room types, availability, and bookings.
|
|
5
|
+
Home-page: https://github.com/DestinationPak/django-hotels
|
|
6
|
+
Author: Awais Jibran
|
|
7
|
+
Author-email: awaisdar001@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: Django hotels
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
License-File: AUTHORS
|
|
19
|
+
Requires-Dist: mysqlclient>=2.2.1
|
|
20
|
+
Requires-Dist: setuptools>=80.9.0
|
|
21
|
+
Requires-Dist: django-filter>=23.2
|
|
22
|
+
Requires-Dist: Django<6.0,>=5.2
|
|
23
|
+
Requires-Dist: djangorestframework<3.17,>=3.16
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
26
|
+
Requires-Dist: docutils==0.23; extra == "dev"
|
|
27
|
+
Requires-Dist: pylint-plugin-utils==0.9.0; extra == "dev"
|
|
28
|
+
Requires-Dist: Faker==40.31.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pre-commit==4.0.1; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-django==4.12.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pylint==4.0.6; extra == "dev"
|
|
32
|
+
Requires-Dist: isort==8.0.1; extra == "dev"
|
|
33
|
+
Requires-Dist: factory-boy==3.3.3; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
35
|
+
Requires-Dist: twine==6.2.0; extra == "dev"
|
|
36
|
+
Requires-Dist: wheel; extra == "dev"
|
|
37
|
+
Requires-Dist: pylint-django==2.8.0; extra == "dev"
|
|
38
|
+
Dynamic: author
|
|
39
|
+
Dynamic: author-email
|
|
40
|
+
Dynamic: classifier
|
|
41
|
+
Dynamic: description
|
|
42
|
+
Dynamic: description-content-type
|
|
43
|
+
Dynamic: home-page
|
|
44
|
+
Dynamic: keywords
|
|
45
|
+
Dynamic: license
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
Dynamic: provides-extra
|
|
48
|
+
Dynamic: requires-dist
|
|
49
|
+
Dynamic: requires-python
|
|
50
|
+
Dynamic: summary
|
|
51
|
+
|
|
52
|
+
# Django Hotels API
|
|
53
|
+
|
|
54
|
+
This is a Django REST API for managing and retrieving hotels, room types, availability, and
|
|
55
|
+
bookings.
|
|
56
|
+
|
|
57
|
+
This service is a sibling of [django-trips](https://github.com/awaisdar001/django-trips), and
|
|
58
|
+
is a core component of the [DestinationPak](https://destinationpak.com) project — a platform
|
|
59
|
+
designed to make exploring and booking adventures across Pakistan easier and more accessible.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
Simply do:
|
|
63
|
+
```bash
|
|
64
|
+
pip install django-hotels
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
Add the app into your installed apps in your project's settings file.
|
|
69
|
+
```
|
|
70
|
+
INSTALLED_APPS = [
|
|
71
|
+
...
|
|
72
|
+
'django_hotels',
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
## Migrate
|
|
76
|
+
```
|
|
77
|
+
python manage.py migrate
|
|
78
|
+
```
|
|
79
|
+
Add the following to your root `urls.py` or to your desired file location.
|
|
80
|
+
```
|
|
81
|
+
urlpatterns = [
|
|
82
|
+
...
|
|
83
|
+
path('hotels/', include('django_hotels.urls')),
|
|
84
|
+
]
|
|
85
|
+
```
|
|
86
|
+
This mounts the whole app under your own chosen namespace (`hotels/` above - replace with
|
|
87
|
+
whatever prefix you like) with the lib's own `v1/` version underneath it, e.g.
|
|
88
|
+
`hotels/v1/hotels/`. The app versions itself independently of your project's own API version.
|
|
89
|
+
|
|
90
|
+
## Domain model
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
HotelOwner (the business/brand)
|
|
94
|
+
└── Hotel (one bookable property)
|
|
95
|
+
├── HotelImage
|
|
96
|
+
└── HotelRoomType (a room category/tier)
|
|
97
|
+
└── HotelAvailability (a dated, priced, bookable instance)
|
|
98
|
+
└── HotelBooking (a guest or logged-in traveller's booking)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`HotelOwner`/`Hotel` deliberately carry no login/auth/permission fields — this package is
|
|
102
|
+
tenancy-oblivious, the same way `django-trips` is. A consuming project owns the membership
|
|
103
|
+
layer (who may manage which `HotelOwner`), not this library.
|
|
104
|
+
|
|
105
|
+
## Local development
|
|
106
|
+
|
|
107
|
+
All commands assume Docker (`make dev.up`, `make update_db`, `make test`) — see the Makefile
|
|
108
|
+
for the full command list (`make help`).
|
|
109
|
+
|
|
110
|
+
## Generate demo data
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
./manage.py generate_hotels --batch_size=10
|
|
114
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Django Hotels API
|
|
2
|
+
|
|
3
|
+
This is a Django REST API for managing and retrieving hotels, room types, availability, and
|
|
4
|
+
bookings.
|
|
5
|
+
|
|
6
|
+
This service is a sibling of [django-trips](https://github.com/awaisdar001/django-trips), and
|
|
7
|
+
is a core component of the [DestinationPak](https://destinationpak.com) project — a platform
|
|
8
|
+
designed to make exploring and booking adventures across Pakistan easier and more accessible.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
Simply do:
|
|
12
|
+
```bash
|
|
13
|
+
pip install django-hotels
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
Add the app into your installed apps in your project's settings file.
|
|
18
|
+
```
|
|
19
|
+
INSTALLED_APPS = [
|
|
20
|
+
...
|
|
21
|
+
'django_hotels',
|
|
22
|
+
]
|
|
23
|
+
```
|
|
24
|
+
## Migrate
|
|
25
|
+
```
|
|
26
|
+
python manage.py migrate
|
|
27
|
+
```
|
|
28
|
+
Add the following to your root `urls.py` or to your desired file location.
|
|
29
|
+
```
|
|
30
|
+
urlpatterns = [
|
|
31
|
+
...
|
|
32
|
+
path('hotels/', include('django_hotels.urls')),
|
|
33
|
+
]
|
|
34
|
+
```
|
|
35
|
+
This mounts the whole app under your own chosen namespace (`hotels/` above - replace with
|
|
36
|
+
whatever prefix you like) with the lib's own `v1/` version underneath it, e.g.
|
|
37
|
+
`hotels/v1/hotels/`. The app versions itself independently of your project's own API version.
|
|
38
|
+
|
|
39
|
+
## Domain model
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
HotelOwner (the business/brand)
|
|
43
|
+
└── Hotel (one bookable property)
|
|
44
|
+
├── HotelImage
|
|
45
|
+
└── HotelRoomType (a room category/tier)
|
|
46
|
+
└── HotelAvailability (a dated, priced, bookable instance)
|
|
47
|
+
└── HotelBooking (a guest or logged-in traveller's booking)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`HotelOwner`/`Hotel` deliberately carry no login/auth/permission fields — this package is
|
|
51
|
+
tenancy-oblivious, the same way `django-trips` is. A consuming project owns the membership
|
|
52
|
+
layer (who may manage which `HotelOwner`), not this library.
|
|
53
|
+
|
|
54
|
+
## Local development
|
|
55
|
+
|
|
56
|
+
All commands assume Docker (`make dev.up`, `make update_db`, `make test`) — see the Makefile
|
|
57
|
+
for the full command list (`make help`).
|
|
58
|
+
|
|
59
|
+
## Generate demo data
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
./manage.py generate_hotels --batch_size=10
|
|
63
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
|
|
3
|
+
from django_hotels.models import (
|
|
4
|
+
Hotel,
|
|
5
|
+
HotelAvailability,
|
|
6
|
+
HotelBooking,
|
|
7
|
+
HotelImage,
|
|
8
|
+
HotelOwner,
|
|
9
|
+
HotelRoomType,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@admin.register(HotelOwner)
|
|
14
|
+
class HotelOwnerAdmin(admin.ModelAdmin):
|
|
15
|
+
list_display = ("name", "email", "verified", "is_active")
|
|
16
|
+
search_fields = ("name", "email")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@admin.register(Hotel)
|
|
20
|
+
class HotelAdmin(admin.ModelAdmin):
|
|
21
|
+
list_display = ("name", "owner", "city", "status", "is_active")
|
|
22
|
+
list_filter = ("status", "is_active", "city")
|
|
23
|
+
search_fields = ("name", "city")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@admin.register(HotelRoomType)
|
|
27
|
+
class HotelRoomTypeAdmin(admin.ModelAdmin):
|
|
28
|
+
list_display = ("hotel", "name", "base_price", "max_occupancy", "is_active")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@admin.register(HotelAvailability)
|
|
32
|
+
class HotelAvailabilityAdmin(admin.ModelAdmin):
|
|
33
|
+
list_display = ("room_type", "date", "effective_price", "rooms_available")
|
|
34
|
+
list_filter = ("date",)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@admin.register(HotelImage)
|
|
38
|
+
class HotelImageAdmin(admin.ModelAdmin):
|
|
39
|
+
list_display = ("hotel", "order")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@admin.register(HotelBooking)
|
|
43
|
+
class HotelBookingAdmin(admin.ModelAdmin):
|
|
44
|
+
list_display = ("number", "full_name", "email", "status", "created_at")
|
|
45
|
+
list_filter = ("status",)
|
|
46
|
+
search_fields = ("number", "full_name", "email")
|
|
File without changes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
|
|
3
|
+
from django_hotels.models import (
|
|
4
|
+
Hotel,
|
|
5
|
+
HotelAvailability,
|
|
6
|
+
HotelBooking,
|
|
7
|
+
HotelImage,
|
|
8
|
+
HotelOwner,
|
|
9
|
+
HotelRoomType,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HotelOwnerSerializer(serializers.ModelSerializer):
|
|
14
|
+
class Meta:
|
|
15
|
+
model = HotelOwner
|
|
16
|
+
fields = (
|
|
17
|
+
"id",
|
|
18
|
+
"name",
|
|
19
|
+
"slug",
|
|
20
|
+
"description",
|
|
21
|
+
"email",
|
|
22
|
+
"mobile",
|
|
23
|
+
"verified",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HotelImageSerializer(serializers.ModelSerializer):
|
|
28
|
+
class Meta:
|
|
29
|
+
model = HotelImage
|
|
30
|
+
fields = ("id", "image", "caption", "order")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class HotelRoomTypeSerializer(serializers.ModelSerializer):
|
|
34
|
+
class Meta:
|
|
35
|
+
model = HotelRoomType
|
|
36
|
+
fields = ("id", "name", "base_price", "max_occupancy")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class HotelListSerializer(serializers.ModelSerializer):
|
|
40
|
+
owner = HotelOwnerSerializer(read_only=True)
|
|
41
|
+
|
|
42
|
+
class Meta:
|
|
43
|
+
model = Hotel
|
|
44
|
+
fields = ("id", "name", "slug", "owner", "city", "status")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class HotelDetailSerializer(serializers.ModelSerializer):
|
|
48
|
+
owner = HotelOwnerSerializer(read_only=True)
|
|
49
|
+
images = HotelImageSerializer(many=True, read_only=True)
|
|
50
|
+
room_types = HotelRoomTypeSerializer(many=True, read_only=True)
|
|
51
|
+
|
|
52
|
+
class Meta:
|
|
53
|
+
model = Hotel
|
|
54
|
+
fields = (
|
|
55
|
+
"id",
|
|
56
|
+
"name",
|
|
57
|
+
"slug",
|
|
58
|
+
"owner",
|
|
59
|
+
"city",
|
|
60
|
+
"description",
|
|
61
|
+
"status",
|
|
62
|
+
"images",
|
|
63
|
+
"room_types",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class HotelAvailabilitySerializer(serializers.ModelSerializer):
|
|
68
|
+
class Meta:
|
|
69
|
+
model = HotelAvailability
|
|
70
|
+
fields = ("id", "room_type", "date", "effective_price", "rooms_available")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class HotelBookingCreateSerializer(serializers.ModelSerializer):
|
|
74
|
+
class Meta:
|
|
75
|
+
model = HotelBooking
|
|
76
|
+
fields = (
|
|
77
|
+
"id",
|
|
78
|
+
"number",
|
|
79
|
+
"availability",
|
|
80
|
+
"full_name",
|
|
81
|
+
"email",
|
|
82
|
+
"phone_number",
|
|
83
|
+
"guests",
|
|
84
|
+
"message",
|
|
85
|
+
"total_price",
|
|
86
|
+
)
|
|
87
|
+
read_only_fields = ("id", "number", "total_price")
|
|
88
|
+
|
|
89
|
+
def create(self, validated_data):
|
|
90
|
+
availability = validated_data["availability"]
|
|
91
|
+
guests = validated_data.get("guests", 1)
|
|
92
|
+
validated_data["total_price"] = availability.effective_price * guests
|
|
93
|
+
return super().create(validated_data)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class HotelBookingLookupSerializer(serializers.ModelSerializer):
|
|
97
|
+
class Meta:
|
|
98
|
+
model = HotelBooking
|
|
99
|
+
fields = (
|
|
100
|
+
"number",
|
|
101
|
+
"full_name",
|
|
102
|
+
"email",
|
|
103
|
+
"guests",
|
|
104
|
+
"status",
|
|
105
|
+
"total_price",
|
|
106
|
+
"created_at",
|
|
107
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from django.urls import path
|
|
2
|
+
from rest_framework.routers import DefaultRouter
|
|
3
|
+
|
|
4
|
+
from django_hotels.api.views import booking, hotel
|
|
5
|
+
|
|
6
|
+
app_name = "hotels-api"
|
|
7
|
+
|
|
8
|
+
router = DefaultRouter()
|
|
9
|
+
router.register(r"hotels", hotel.HotelViewSet, basename="hotel")
|
|
10
|
+
|
|
11
|
+
app_urlpatterns = [
|
|
12
|
+
path(
|
|
13
|
+
"owners/",
|
|
14
|
+
hotel.ActiveHotelOwnersListAPIView.as_view(),
|
|
15
|
+
name="owners",
|
|
16
|
+
),
|
|
17
|
+
path(
|
|
18
|
+
"bookings/create/",
|
|
19
|
+
booking.HotelBookingCreateView.as_view(),
|
|
20
|
+
name="bookings-create",
|
|
21
|
+
),
|
|
22
|
+
path(
|
|
23
|
+
"bookings/lookup/",
|
|
24
|
+
booking.HotelBookingLookupView.as_view(),
|
|
25
|
+
name="bookings-lookup",
|
|
26
|
+
),
|
|
27
|
+
*router.urls,
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
urlpatterns = app_urlpatterns
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from rest_framework import generics
|
|
2
|
+
from rest_framework.exceptions import NotFound
|
|
3
|
+
from rest_framework.permissions import AllowAny
|
|
4
|
+
|
|
5
|
+
from django_hotels.api.serializers import (
|
|
6
|
+
HotelBookingCreateSerializer,
|
|
7
|
+
HotelBookingLookupSerializer,
|
|
8
|
+
)
|
|
9
|
+
from django_hotels.models import HotelBooking
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class HotelBookingCreateView(generics.CreateAPIView):
|
|
13
|
+
"""Guest booking - no auth required, mirrors django-trips' TripBookingCreateView."""
|
|
14
|
+
|
|
15
|
+
permission_classes = [AllowAny]
|
|
16
|
+
serializer_class = HotelBookingCreateSerializer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HotelBookingLookupView(generics.RetrieveAPIView):
|
|
20
|
+
"""Anonymous booking lookup by `number` + `email` - never by number alone."""
|
|
21
|
+
|
|
22
|
+
permission_classes = [AllowAny]
|
|
23
|
+
serializer_class = HotelBookingLookupSerializer
|
|
24
|
+
|
|
25
|
+
def get_object(self):
|
|
26
|
+
number = self.request.query_params.get("number")
|
|
27
|
+
email = self.request.query_params.get("email")
|
|
28
|
+
booking = HotelBooking.objects.filter(number=number, email=email).first()
|
|
29
|
+
if not booking:
|
|
30
|
+
raise NotFound("No booking found for that reference number and email.")
|
|
31
|
+
return booking
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from rest_framework import generics
|
|
2
|
+
from rest_framework.permissions import AllowAny
|
|
3
|
+
from rest_framework.viewsets import ReadOnlyModelViewSet
|
|
4
|
+
|
|
5
|
+
from django_hotels.api.serializers import (
|
|
6
|
+
HotelDetailSerializer,
|
|
7
|
+
HotelListSerializer,
|
|
8
|
+
HotelOwnerSerializer,
|
|
9
|
+
)
|
|
10
|
+
from django_hotels.models import Hotel, HotelOwner
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HotelViewSet(ReadOnlyModelViewSet):
|
|
14
|
+
"""Public, read-only catalog of published hotels - no create/update/destroy.
|
|
15
|
+
|
|
16
|
+
Management (create/update/archive) is a consuming project's operator-surface
|
|
17
|
+
job, the same architectural split django-trips uses for TripViewSet.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
permission_classes = [AllowAny]
|
|
21
|
+
lookup_field = "slug"
|
|
22
|
+
|
|
23
|
+
def get_queryset(self):
|
|
24
|
+
return Hotel.objects.active().select_related("owner")
|
|
25
|
+
|
|
26
|
+
def get_serializer_class(self):
|
|
27
|
+
if self.action == "retrieve":
|
|
28
|
+
return HotelDetailSerializer
|
|
29
|
+
return HotelListSerializer
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ActiveHotelOwnersListAPIView(generics.ListAPIView):
|
|
33
|
+
permission_classes = [AllowAny]
|
|
34
|
+
serializer_class = HotelOwnerSerializer
|
|
35
|
+
|
|
36
|
+
def get_queryset(self):
|
|
37
|
+
return HotelOwner.objects.active()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DjangoHotelsConfig(AppConfig):
|
|
5
|
+
name = "django_hotels"
|
|
6
|
+
verbose_name = "Django Hotels"
|
|
7
|
+
# Pinned per-app so this package's models always get BigAutoField
|
|
8
|
+
# regardless of the consuming project's own DEFAULT_AUTO_FIELD.
|
|
9
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HotelStatus(models.TextChoices):
|
|
5
|
+
"""
|
|
6
|
+
Editorial state of a Hotel, independent of `is_active` (which is
|
|
7
|
+
soft-delete/visibility, not workflow).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
DRAFT = "DRAFT", "Draft"
|
|
11
|
+
PUBLISHED = "PUBLISHED", "Published"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HotelBookingStatus(models.TextChoices):
|
|
15
|
+
"""Lifecycle states of a booking - mirrors django_trips.choices.BookingStatus."""
|
|
16
|
+
|
|
17
|
+
PENDING = "PENDING", "Pending"
|
|
18
|
+
WAITING_PAYMENT = "WAITING_PAYMENT", "Awaiting Payment"
|
|
19
|
+
CONFIRMED = "CONFIRMED", "Confirmed"
|
|
20
|
+
PARTIAL_PAYMENT = "PARTIAL_PAYMENT", "Partial Payment"
|
|
21
|
+
COMPLETED = "COMPLETED", "Completed"
|
|
22
|
+
CANCELLED = "CANCELLED", "Cancelled"
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def is_cancelled(cls, status):
|
|
26
|
+
return status == cls.CANCELLED
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def can_be_cancelled(cls, status):
|
|
30
|
+
return status in (cls.PENDING, cls.WAITING_PAYMENT)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from datetime import timedelta
|
|
3
|
+
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.core.management.base import BaseCommand
|
|
6
|
+
from django.utils import timezone
|
|
7
|
+
from django.utils.text import slugify
|
|
8
|
+
from faker import Faker
|
|
9
|
+
|
|
10
|
+
from django_hotels.models import Hotel, HotelAvailability, HotelOwner, HotelRoomType
|
|
11
|
+
|
|
12
|
+
fake = Faker()
|
|
13
|
+
User = get_user_model()
|
|
14
|
+
|
|
15
|
+
CITIES = ("Hunza", "Skardu", "Naran", "Murree", "Fairy Meadows")
|
|
16
|
+
ROOM_TYPES = (("Standard Double", 8000, 2), ("Deluxe Suite", 15000, 4))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Command(BaseCommand):
|
|
20
|
+
"""
|
|
21
|
+
Generate a batch of hotels with random demo data.
|
|
22
|
+
|
|
23
|
+
EXAMPLE USAGE:
|
|
24
|
+
./manage.py generate_hotels --batch_size=10
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
help = "Generate batches of hotels with pre-populated random data"
|
|
28
|
+
|
|
29
|
+
def add_arguments(self, parser):
|
|
30
|
+
parser.add_argument("--batch_size", type=int, default=10, dest="batch_size")
|
|
31
|
+
|
|
32
|
+
def handle(self, *args, **options):
|
|
33
|
+
batch_size = options["batch_size"]
|
|
34
|
+
user, _ = User.objects.get_or_create(
|
|
35
|
+
username="hotels-seed-bot", defaults={"email": "seed@example.com"}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
for _ in range(batch_size):
|
|
39
|
+
owner_name = fake.company()
|
|
40
|
+
owner = HotelOwner.objects.create(
|
|
41
|
+
name=owner_name,
|
|
42
|
+
slug=f"{slugify(owner_name)}-{random.randint(1000, 9999)}",
|
|
43
|
+
email=fake.company_email(),
|
|
44
|
+
verified=True,
|
|
45
|
+
)
|
|
46
|
+
hotel_name = f"{fake.city()} {random.choice(['Inn', 'Resort', 'Lodge'])}"
|
|
47
|
+
hotel = Hotel.objects.create(
|
|
48
|
+
name=hotel_name,
|
|
49
|
+
slug=f"{slugify(hotel_name)}-{random.randint(1000, 9999)}",
|
|
50
|
+
owner=owner,
|
|
51
|
+
city=random.choice(CITIES),
|
|
52
|
+
description=fake.paragraph(),
|
|
53
|
+
created_by=user,
|
|
54
|
+
)
|
|
55
|
+
for room_name, price, occupancy in ROOM_TYPES:
|
|
56
|
+
room_type = HotelRoomType.objects.create(
|
|
57
|
+
hotel=hotel,
|
|
58
|
+
name=room_name,
|
|
59
|
+
base_price=price,
|
|
60
|
+
max_occupancy=occupancy,
|
|
61
|
+
)
|
|
62
|
+
for day in range(7):
|
|
63
|
+
HotelAvailability.objects.create(
|
|
64
|
+
room_type=room_type,
|
|
65
|
+
date=timezone.now().date() + timedelta(days=day),
|
|
66
|
+
rooms_available=random.randint(1, 5),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
self.stdout.write(self.style.SUCCESS(f"Generated {batch_size} hotels"))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ActiveQuerySet(models.QuerySet):
|
|
5
|
+
def active(self):
|
|
6
|
+
return self.filter(is_active=True)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HotelOwnerQuerySet(ActiveQuerySet):
|
|
10
|
+
def active(self):
|
|
11
|
+
return super().active().filter(verified=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class HotelQuerySet(ActiveQuerySet):
|
|
15
|
+
def active(self):
|
|
16
|
+
return super().active().filter(owner__verified=True)
|