Last active
April 29, 2020 17:13
-
-
Save pmburu/05fa49c1fa55596e8a807674d50b9576 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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