mirror of
https://github.com/Cesarkassis/booking-Api.git
synced 2026-10-19 18:56:17 +00:00
Part 1: Booking API with Swagger and custom endpoints
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for booking_system project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'booking_system.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
"""
|
||||||
|
Django settings for booking_system project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.2.6.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-i9ut%*0nx@(z7ta7)5uly(3xxjp(5d%172$rk5b0on6ch9f1_r'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# third-party
|
||||||
|
'rest_framework',
|
||||||
|
'drf_yasg',
|
||||||
|
|
||||||
|
# our app
|
||||||
|
'bookings',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
"bookings.middleware.RequestLoggingMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'booking_system.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'booking_system.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# Django REST Framework default
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
"DEFAULT_RENDERER_CLASSES": (
|
||||||
|
"rest_framework.renderers.JSONRenderer",
|
||||||
|
),
|
||||||
|
"DEFAULT_PARSER_CLASSES": (
|
||||||
|
"rest_framework.parsers.JSONParser",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# -------------------------
|
||||||
|
# Logging config: write to api.log
|
||||||
|
# -------------------------
|
||||||
|
LOG_DIR = BASE_DIR / "logs"
|
||||||
|
LOG_DIR.mkdir(exist_ok=True)
|
||||||
|
LOGGING = {
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"formatters": {
|
||||||
|
"standard": {
|
||||||
|
"format": "[%(asctime)s] %(levelname)s %(name)s: %(message)s"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"file": {
|
||||||
|
"level": "INFO",
|
||||||
|
"class": "logging.FileHandler",
|
||||||
|
"filename": str(LOG_DIR / "api.log"),
|
||||||
|
"formatter": "standard",
|
||||||
|
},
|
||||||
|
"console": {
|
||||||
|
"class": "logging.StreamHandler",
|
||||||
|
"formatter": "standard",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
# root logger
|
||||||
|
"": {
|
||||||
|
"handlers": ["file", "console"],
|
||||||
|
"level": "INFO",
|
||||||
|
"propagate": True,
|
||||||
|
},
|
||||||
|
"django.request": {
|
||||||
|
"handlers": ["file", "console"],
|
||||||
|
"level": "ERROR",
|
||||||
|
"propagate": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for booking_system project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
# booking_api/urls.py
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from rest_framework import permissions
|
||||||
|
from drf_yasg.views import get_schema_view
|
||||||
|
from drf_yasg import openapi
|
||||||
|
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi.Info(
|
||||||
|
title="Booking API",
|
||||||
|
default_version="v1",
|
||||||
|
description="Demo booking API with Venues and Bookings",
|
||||||
|
contact=openapi.Contact(email="dev@example.com"),
|
||||||
|
),
|
||||||
|
public=True,
|
||||||
|
permission_classes=(permissions.AllowAny,),
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("admin/", admin.site.urls),
|
||||||
|
path("api/", include("bookings.urls")),
|
||||||
|
# Swagger UI:
|
||||||
|
path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"),
|
||||||
|
path("swagger.json", schema_view.without_ui(cache_timeout=0), name="schema-json"),
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for booking_system project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'booking_system.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Venue, Booking
|
||||||
|
|
||||||
|
@admin.register(Venue)
|
||||||
|
class VenueAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "name", "capacity", "address")
|
||||||
|
|
||||||
|
@admin.register(Booking)
|
||||||
|
class BookingAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "venue", "customer_name", "start_time", "end_time", "status")
|
||||||
|
list_filter = ("status", "venue")
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BookingsConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'bookings'
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# bookings/middleware.py
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class RequestLoggingMiddleware:
|
||||||
|
"""
|
||||||
|
Logs request start, method, path, body (if small), response status and duration.
|
||||||
|
Place this middleware after common Django middleware as configured in settings.py.
|
||||||
|
"""
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
# basic request info
|
||||||
|
method = request.method
|
||||||
|
path = request.get_full_path()
|
||||||
|
# try to capture small JSON bodies safely
|
||||||
|
body = None
|
||||||
|
try:
|
||||||
|
if method in ("POST", "PUT", "PATCH") and request.body:
|
||||||
|
raw = request.body.decode("utf-8")[:2000]
|
||||||
|
body = raw
|
||||||
|
except Exception:
|
||||||
|
body = "<could not decode>"
|
||||||
|
|
||||||
|
logger.info(f"Request start: {method} {path} body={body}")
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
duration = time.time() - start
|
||||||
|
logger.info(f"Request finished: {method} {path} status={response.status_code} time={duration:.3f}s")
|
||||||
|
return response
|
||||||
|
except Exception as ex:
|
||||||
|
# log exception details
|
||||||
|
logger.exception(f"Unhandled exception during request: {ex}")
|
||||||
|
raise
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-09-30 18:07
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Venue',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=200)),
|
||||||
|
('address', models.CharField(blank=True, max_length=300)),
|
||||||
|
('capacity', models.PositiveIntegerField(default=0)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Booking',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('customer_name', models.CharField(max_length=200)),
|
||||||
|
('customer_email', models.EmailField(max_length=254)),
|
||||||
|
('start_time', models.DateTimeField()),
|
||||||
|
('end_time', models.DateTimeField()),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('status', models.CharField(choices=[('CONFIRMED', 'Confirmed'), ('CANCELLED', 'Cancelled')], default='CONFIRMED', max_length=20)),
|
||||||
|
('venue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bookings', to='bookings.venue')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,32 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.# bookings/models.py
|
||||||
|
|
||||||
|
class Venue(models.Model):
|
||||||
|
name = models.CharField(max_length=100)
|
||||||
|
city = models.CharField(max_length=50)
|
||||||
|
capacity = models.IntegerField()
|
||||||
|
description = models.TextField(blank=True, null=True)
|
||||||
|
image = models.ImageField(upload_to="venues/", blank=True, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name} ({self.city})"
|
||||||
|
|
||||||
|
|
||||||
|
class Booking(models.Model):
|
||||||
|
venue = models.ForeignKey(Venue, related_name="bookings", on_delete=models.CASCADE)
|
||||||
|
customer_name = models.CharField(max_length=200)
|
||||||
|
customer_email = models.EmailField()
|
||||||
|
start_time = models.DateTimeField()
|
||||||
|
end_time = models.DateTimeField()
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
("CONFIRMED", "Confirmed"),
|
||||||
|
("CANCELLED", "Cancelled"),
|
||||||
|
]
|
||||||
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="CONFIRMED")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Booking {self.id} at {self.venue.name} for {self.customer_name}"
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# bookings/serializers.py
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Venue, Booking
|
||||||
|
|
||||||
|
class VenueSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Venue
|
||||||
|
fields = "__all__"
|
||||||
|
class BookingSerializer(serializers.ModelSerializer):
|
||||||
|
venue = serializers.PrimaryKeyRelatedField(queryset=Venue.objects.all())
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Booking
|
||||||
|
fields = ["id", "venue", "customer_name", "customer_email", "start_time", "end_time", "status", "created_at"]
|
||||||
|
read_only_fields = ["created_at", "status"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# bookings/urls.py
|
||||||
|
from rest_framework import routers
|
||||||
|
from .views import VenueViewSet, BookingViewSet
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r"venues", VenueViewSet, basename="venues")
|
||||||
|
router.register(r"bookings", BookingViewSet, basename="bookings")
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
# bookings/views.py
|
||||||
|
from rest_framework import viewsets, filters, status
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from .models import Venue, Booking
|
||||||
|
from .serializers import VenueSerializer, BookingSerializer
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework import viewsets, status
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from .models import Venue, Booking
|
||||||
|
from .serializers import VenueSerializer, BookingSerializer
|
||||||
|
|
||||||
|
class VenueViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Venue.objects.all()
|
||||||
|
serializer_class = VenueSerializer
|
||||||
|
|
||||||
|
# Custom endpoint: /venues/findByCity?city=Paris
|
||||||
|
@action(detail=False, methods=["get"])
|
||||||
|
def findByCity(self, request):
|
||||||
|
city = request.query_params.get("city")
|
||||||
|
venues = Venue.objects.filter(city__iexact=city) if city else Venue.objects.all()
|
||||||
|
serializer = self.get_serializer(venues, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
# Custom endpoint: /venues/{id}/availability
|
||||||
|
@action(detail=True, methods=["get"])
|
||||||
|
def availability(self, request, pk=None):
|
||||||
|
venue = self.get_object()
|
||||||
|
bookings = Booking.objects.filter(venue=venue, status="active")
|
||||||
|
return Response({
|
||||||
|
"venue": venue.name,
|
||||||
|
"city": venue.city,
|
||||||
|
"capacity": venue.capacity,
|
||||||
|
"active_bookings": bookings.count(),
|
||||||
|
"available": venue.capacity - bookings.count()
|
||||||
|
})
|
||||||
|
|
||||||
|
# Custom endpoint: /venues/{id}/uploadImage
|
||||||
|
@action(detail=True, methods=["post"])
|
||||||
|
def uploadImage(self, request, pk=None):
|
||||||
|
venue = self.get_object()
|
||||||
|
file = request.FILES.get("file")
|
||||||
|
if not file:
|
||||||
|
return Response({"error": "No file uploaded"}, status=400)
|
||||||
|
venue.image = file
|
||||||
|
venue.save()
|
||||||
|
return Response({"status": "Image uploaded", "venue": venue.name})
|
||||||
|
|
||||||
|
# Custom endpoint: /venues/{id}/bookings
|
||||||
|
@action(detail=True, methods=["get"])
|
||||||
|
def bookings(self, request, pk=None):
|
||||||
|
venue = self.get_object()
|
||||||
|
bookings = Booking.objects.filter(venue=venue)
|
||||||
|
serializer = BookingSerializer(bookings, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BookingViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Booking.objects.all().order_by("-created_at")
|
||||||
|
serializer_class = BookingSerializer
|
||||||
|
filter_backends = [filters.SearchFilter]
|
||||||
|
search_fields = ["customer_name", "customer_email"]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
venue_id = self.request.query_params.get("venue")
|
||||||
|
if venue_id:
|
||||||
|
qs = qs.filter(venue_id=venue_id)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
# Example custom action: cancel booking (POST /api/bookings/{id}/cancel/)
|
||||||
|
@action(detail=True, methods=["post"])
|
||||||
|
def cancel(self, request, pk=None):
|
||||||
|
booking = self.get_object()
|
||||||
|
booking.status = "cancelled"
|
||||||
|
booking.save()
|
||||||
|
return Response(
|
||||||
|
{"status": "Booking cancelled", "id": booking.id},
|
||||||
|
status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
# Custom endpoint: /bookings/findByStatus?status=active
|
||||||
|
@action(detail=False, methods=["get"])
|
||||||
|
def findByStatus(self, request):
|
||||||
|
status_param = request.query_params.get("status", None)
|
||||||
|
if status_param:
|
||||||
|
bookings = Booking.objects.filter(status=status_param)
|
||||||
|
else:
|
||||||
|
bookings = Booking.objects.all()
|
||||||
|
serializer = self.get_serializer(bookings, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
# Custom endpoint: /bookings/{id}/uploadDocument
|
||||||
|
@action(detail=True, methods=["post"])
|
||||||
|
def uploadDocument(self, request, pk=None):
|
||||||
|
booking = self.get_object()
|
||||||
|
file = request.FILES.get("file")
|
||||||
|
if not file:
|
||||||
|
return Response({"error": "No file uploaded"}, status=400)
|
||||||
|
|
||||||
|
# In a real app, save file to booking record or storage
|
||||||
|
return Response(
|
||||||
|
{"status": "File uploaded", "filename": file.name, "booking_id": booking.id},
|
||||||
|
status=200
|
||||||
|
)
|
||||||
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
|||||||
|
[2025-09-30 21:08:56,616] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 21:09:36,669] INFO bookings.middleware: Request start: GET /api/ body=None
|
||||||
|
[2025-09-30 21:09:36,693] INFO bookings.middleware: Request finished: GET /api/ status=200 time=0.024s
|
||||||
|
[2025-09-30 21:10:00,790] INFO bookings.middleware: Request start: GET /swagger/ body=None
|
||||||
|
[2025-09-30 21:10:00,883] INFO bookings.middleware: Request finished: GET /swagger/ status=200 time=0.093s
|
||||||
|
[2025-09-30 21:10:02,120] INFO bookings.middleware: Request start: GET /swagger/?format=openapi body=None
|
||||||
|
[2025-09-30 21:10:02,144] INFO bookings.middleware: Request finished: GET /swagger/?format=openapi status=200 time=0.024s
|
||||||
|
[2025-09-30 21:11:08,908] INFO bookings.middleware: Request start: GET /swagger.json body=None
|
||||||
|
[2025-09-30 21:11:08,936] INFO bookings.middleware: Request finished: GET /swagger.json status=200 time=0.027s
|
||||||
|
[2025-09-30 22:20:11,279] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\views.py changed, reloading.
|
||||||
|
[2025-09-30 22:20:12,596] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 22:20:16,338] INFO bookings.middleware: Request start: GET /swagger/ body=None
|
||||||
|
[2025-09-30 22:20:16,365] INFO bookings.middleware: Request finished: GET /swagger/ status=200 time=0.028s
|
||||||
|
[2025-09-30 22:20:17,138] INFO bookings.middleware: Request start: GET /swagger/?format=openapi body=None
|
||||||
|
[2025-09-30 22:20:17,230] INFO bookings.middleware: Request finished: GET /swagger/?format=openapi status=200 time=0.092s
|
||||||
|
[2025-09-30 22:21:44,602] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\views.py changed, reloading.
|
||||||
|
[2025-09-30 22:21:45,825] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 22:21:46,318] INFO bookings.middleware: Request start: GET /swagger/ body=None
|
||||||
|
[2025-09-30 22:21:46,345] INFO bookings.middleware: Request finished: GET /swagger/ status=200 time=0.027s
|
||||||
|
[2025-09-30 22:21:47,206] INFO bookings.middleware: Request start: GET /swagger/?format=openapi body=None
|
||||||
|
[2025-09-30 22:21:47,299] INFO bookings.middleware: Request finished: GET /swagger/?format=openapi status=200 time=0.093s
|
||||||
|
[2025-09-30 22:22:30,864] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\views.py changed, reloading.
|
||||||
|
[2025-09-30 22:22:31,796] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 22:22:34,154] INFO bookings.middleware: Request start: GET /swagger/ body=None
|
||||||
|
[2025-09-30 22:22:34,183] INFO bookings.middleware: Request finished: GET /swagger/ status=200 time=0.029s
|
||||||
|
[2025-09-30 22:22:34,674] INFO bookings.middleware: Request start: GET /swagger/?format=openapi body=None
|
||||||
|
[2025-09-30 22:22:34,710] INFO bookings.middleware: Request finished: GET /swagger/?format=openapi status=200 time=0.036s
|
||||||
|
[2025-09-30 22:48:48,940] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\models.py changed, reloading.
|
||||||
|
[2025-09-30 22:48:50,022] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 22:49:30,550] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\serializers.py changed, reloading.
|
||||||
|
[2025-09-30 22:49:31,697] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
|
[2025-09-30 22:50:34,260] INFO django.utils.autoreload: C:\Users\Cesar\Desktop\Apighaymah\Apighaymah\booking_system\bookings\views.py changed, reloading.
|
||||||
|
[2025-09-30 22:50:35,299] INFO django.utils.autoreload: Watching for file changes with StatReloader
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'booking_system.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user