Skip to content

Instantly share code, notes, and snippets.

@pmburu
Last active April 29, 2020 17:13
Show Gist options
  • Save pmburu/05fa49c1fa55596e8a807674d50b9576 to your computer and use it in GitHub Desktop.
Save pmburu/05fa49c1fa55596e8a807674d50b9576 to your computer and use it in GitHub Desktop.
from django.urls import reverse
from django.db import models
from django.utils import timezone
from django.core.exceptions import ValidationError
# import datetime as dt
# from django.db.models.signals import pre_save, post_save, post_delete
import datetime
from wash_authentication.models import Client, Driver
# SERVICES
# _________________________________________________________________________#
class VehicleType(models.Model):
""" Represents vehicle categories """
name = models.CharField(max_length=30, help_text='E.g. SUV')
parent = models.ForeignKey('VehicleType', on_delete=models.CASCADE,
null=True, blank=True)
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.name
class PackageDetails(models.Model):
""" Represents vehcile details """
vehicle_category = models.ForeignKey(
VehicleType, on_delete=models.CASCADE, related_name='wash_package',
)
package_name = models.CharField(max_length=30, help_text='E.g. Deluxe')
package_description = models.TextField(null=True, blank=True)
package_cost = models.DecimalField(max_digits=10, decimal_places=2)
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(blank=True, null=True)
def __str__(self):
return self.package_name
def get_absolute_url(self):
return reverse("product_detail", kwargs={"pk": self.pk})
def get_price(self):
return self.package_cost
class ExtraService(models.Model):
""" Represents extra services details """
service_name = models.CharField(max_length=30,
help_text='E.g. Engine Wash')
service_cost = models.DecimalField(max_digits=10, decimal_places=2)
# BOOKINGS
# _________________________________________________________________________#
class BookingState(models.TextChoices):
PENDING = 'PENDING', 'Pending'
CONFIRMED = 'CONFIRMED', 'Confirmed'
ON_THE_WAY = 'ON_THE_WAY', 'On The Way'
IN_PROGRESS = 'IN_PROGRESS', 'In Progress'
COMPLETE = 'COMPLETE', 'Complete'
class Booking(models.Model):
driver = models.OneToOneField(Driver, on_delete=models.CASCADE,
null=True, blank=True)
client = models.ForeignKey(Client, on_delete=models.CASCADE,
related_name='customer')
booking_location = models.CharField(max_length=250, blank=True, null=True)
booking_address = models.CharField(max_length=100, blank=True, null=True)
booking_date = models.DateField()
booking_time = models.TimeField()
service = models.ForeignKey(PackageDetails,
on_delete=models.CASCADE,
related_name='packages')
extra_service = models.ForeignKey(ExtraService, on_delete=models.CASCADE,
related_name='extra_services',
null=True, blank=True)
booking_status = models.CharField(
max_length=15, choices=BookingState.choices,
default=BookingState.PENDING
)
booked_at = models.DateTimeField(default=timezone.now)
finishing_at = models.TimeField()
booking_total = models.DecimalField(decimal_places=2, max_digits=10)
def __str__(self):
return f'{self.booking_status} order containing: \
{len(self.service.all())} service'
def calc_finishing_at(self):
duration = datetime.timedelta(hours=1)
booking_time = self.booking_time
self.finishing_at = (datetime.datetime.combine(
datetime.date(1, 1, 1), booking_time) + duration).time()
return self.finishing_at
def calc_booking_total(self):
if self.extra_service and self.service:
self.booking_total = self.service.package_cost + \
self.extra_service.service_cost
elif self.service:
self.booking_total = self.service.package_cost
else:
self.booking_total = 0.00
def check_time_overlap(self, fixed_start, fixed_end, new_start, new_end):
time_overlap = False
if new_start == fixed_end or new_end == fixed_start: # edge case
time_overlap = False
elif (new_start >= fixed_start and new_start <= fixed_end) or \
(new_end >= fixed_start and new_end <= fixed_end): \
# innner limits
time_overlap = True
elif new_start <= fixed_start and new_end >= fixed_end: \
# outter limits
time_overlap = True
return time_overlap
def assign_driver_to_booking(self):
if self.finishing_at <= self.booking_time:
raise ValidationError(
'Finishing times must be after booking times'
)
bookings = Booking.objects.filter(
booking_date=self.booking_date, driver_id=self.driver
)
# drivers = Booking.objects.filter(driver_id=self.driver)
if bookings.exists():
for booking in bookings:
""" Check whether date and time overlaps """
if self.check_time_overlap(
booking.booking_time, booking.finishing_at,
self.booking_time, self.finishing_at
):
# if booking.driver is None:
# """ If time overlaps, check whether there is \
# a driver assigned to that booking. If there is none, \
# assign one."""
# Booking.objects\
# .filter(id=booking.id)\
# .update(driver=booking.driver)
# return booking.driver
# else:
""" If all drivers are allocated, raise an error \
message. """
raise ValidationError(
'All our drivers are booked at: ' +
str(booking.booking_date) + ', ' + str(
booking.booking_time) + '-' +
str(booking.finishing_at))
def save(self, *args, **kwargs):
self.calc_booking_total()
self.calc_finishing_at()
self.assign_driver_to_booking()
super(Booking, self).save(*args, **kwargs)
{
"id": 27,
"package": "Premium",
"vehicle_type": "SUV",
"client_name": "Test Client",
"client_phone": "0722345678",
"booking_location": "-538978, 56251817",
"booking_address": "ABC Place, Hse 41",
"booking_date": "2020-05-28",
"booking_time": "12:00:00",
"booking_status": "PENDING",
"booked_at": "2020-04-29T15:17:23.660080",
"finishing_at": "13:00:00",
"booking_total": "3700.00",
"driver": null,
"client": 3,
"service": 8,
"extra_service": 3
}
from rest_framework import serializers
from .models import (
Booking, PackageDetails, VehicleType, ExtraService
)
from wash_authentication.models import (
Client, Driver
)
class PackageSerializer(serializers.ModelSerializer):
package_id = serializers.IntegerField(source='id')
class Meta:
model = PackageDetails
fields = (
'package_id',
'package_name',
'package_description',
'package_cost',
)
class VehicleTypeSerializer(serializers.ModelSerializer):
wash_package = PackageSerializer(many=True, read_only=True)
class Meta:
model = VehicleType
fields = (
'id',
'name',
'wash_package'
)
class ExtraServiceSerializer(serializers.ModelSerializer):
extra_service_id = serializers.IntegerField(source='id')
class Meta:
model = ExtraService
fields = (
'extra_service_id',
'service_name',
'service_cost',
)
# BOOKING SERIALIZER
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––#
class BookingClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ("id", "name", "phone")
class BookingDriverSerializer(serializers.ModelSerializer):
class Meta:
model = Driver
fields = ("id", "name", "phone")
class BookingSerializer(serializers.ModelSerializer):
package = serializers.SerializerMethodField()
vehicle_type = serializers.SerializerMethodField()
client_name = serializers.SerializerMethodField()
client_phone = serializers.SerializerMethodField()
class Meta:
model = Booking
fields = '__all__'
# exclude = ('client',)
read_only_fields = (
'booking_total', 'booking_status',
'booked_at', 'finishing_at', 'client'
)
def get_package(self, obj):
return obj.service.package_name
def get_vehicle_type(self, obj):
return obj.service.vehicle_category.name
def get_client_name(self, obj):
return obj.client.name
def get_client_phone(self, obj):
return str(obj.client.phone)
def create(self, validated_data):
validated_data['client'] = self.context['request'].user
booking = Booking.objects.create(**validated_data)
return booking
import json
from django.utils import timezone
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.decorators import api_view
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.decorators import permission_classes
# from django_filters.rest_framework import DjangoFilterBackend
from .serializers import (
PackageSerializer, VehicleTypeSerializer, ExtraServiceSerializer,
BookingSerializer
)
from .models import (
PackageDetails, VehicleType, ExtraService, Booking, BookingState
)
# PACKAGE & SERVICE VIEWS
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––#
class PackageViewSet(viewsets.ModelViewSet):
serializer_class = PackageSerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
return PackageDetails.objects.filter(
pk=self.kwargs['pk'])
class VehicleTypeViewSet(viewsets.ModelViewSet):
queryset = VehicleType.objects.all()
serializer_class = VehicleTypeSerializer
permission_classes = (IsAuthenticated,)
class ExtraServiceViewSet(viewsets.ModelViewSet):
queryset = ExtraService.objects.all()
serializer_class = ExtraServiceSerializer
permission_classes = (IsAuthenticated,)
# BOOKING & CART VIEWS
# ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––#
class BookingViewSet(viewsets.ModelViewSet):
serializer_class = BookingSerializer
# permission_classes = (IsAuthenticated,)
queryset = (
Booking.objects
.select_related('client')
.prefetch_related('service')
)
# filter_backends = (DjangoFilterBackend,)
# filterset_fields = ('booking_status', 'client__name',)
def dispatch(self, *args, **kwargs):
response = super().dispatch(*args, **kwargs)
return response
@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def driver_get_ready_bookings(request):
bookings = BookingSerializer(
Booking.objects.filter(booking_status=BookingState.PENDING,
driver=None).order_by("-id"),
many=True
).data
return JsonResponse({"driver_bookings": bookings})
@csrf_exempt
# POST
# params: access_token, order_id
def driver_pick_booking(request):
if request.method == "POST":
# Get driver
driver = request.user.driver
# Check that driver only has one order
if Booking.objects.filter(driver=driver).exclude(
booking_status=BookingState.ON_THE_WAY
):
return JsonResponse({
"status": "failed", "error":
"You can only cater for one booking at a time."
})
try:
booking = Booking.objects.get(
id=request.POST["booking_id"],
driver=None,
booking_status=BookingState.IN_PROGRESS
)
booking.driver = driver
booking.booking_status = BookingState.ON_THE_WAY
booking.pickedup_at = timezone.now()
booking.save()
return JsonResponse({"status": "success"})
except Booking.DoesNotExist:
return JsonResponse({
"status": "failed", "error":
"This order has been picked up by another driver."
})
return JsonResponse({})
# GET params: access_token
@api_view(['GET'])
@permission_classes((IsAuthenticated, ))
def driver_get_latest_booking(request):
# Get driver
driver = request.user.driver
booking = BookingSerializer(
Booking.objects.filter(driver=driver).order_by("booked_at").last()
).data
return JsonResponse({"next_jobs": booking})
# POST params: access_token, order_id
@csrf_exempt
def driver_complete_booking(request):
# Get driver
driver = request.user.driver
booking = Booking.objects.get(id=request.POST["booking_id"], driver=driver)
booking.status = BookingState.COMPLETE
booking.save()
return JsonResponse({"status": "success"})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment