1
0
مراية لـ https://github.com/Cesarkassis/booking-Api.git تم المزامنة 2026-01-17 11:19:47 +00:00

Part 1: Booking API with Swagger and custom endpoints

هذا الالتزام موجود في:
2025-09-30 23:09:47 +03:00
التزام e6dc52ca4d
33 ملفات معدلة مع 573 إضافات و0 حذوفات

0
bookings/__init__.py Normal file
عرض الملف

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

11
bookings/admin.py Normal file
عرض الملف

@@ -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")

6
bookings/apps.py Normal file
عرض الملف

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class BookingsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'bookings'

41
bookings/middleware.py Normal file
عرض الملف

@@ -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')),
],
),
]

عرض الملف

ملف ثنائي غير معروض.

ملف ثنائي غير معروض.

32
bookings/models.py Normal file
عرض الملف

@@ -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}"

15
bookings/serializers.py Normal file
عرض الملف

@@ -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"]

3
bookings/tests.py Normal file
عرض الملف

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
bookings/urls.py Normal file
عرض الملف

@@ -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

111
bookings/views.py Normal file
عرض الملف

@@ -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
)