I need help properly implementing a booking scheduler and availability in Django - django

This question has been asked many times before on StackOverflow and in the Django forums, but none of the answers I've found are appropriate or complete enough for my situation.
First, the brief:
I'm creating a web application for a car rental business. In addition to helping them organize and centralize their fleet, it will also help them collect orders directly from customers. As with most rentals, the logistics of it all can be somewhat confusing.
Someone may place an order for a car today (December 12th) but actually take the car over the Christmas to New Year's period.
A renter may borrow a car for just two days, and then extend the booking at the last minute. When this happens (often very frequently), the business usually has to scramble to find another one for a different customer who was scheduled to get that car the next day.
Adding to that, an individual car can only be rented to one client at a time, so it can't have multiple bookings for the same period.
Most answers advocate for a simple approach that looks like this:
models.py
class Booking(models.Model):
car = models.ForeignKey(Car, ...)
start_date = models.dateField(...)
end_date = models.dateField(...)
is_available = models.booleanField(default=True)
forms.py
import datetime
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from . import models
class PlaceOrderForm(forms.Form):
"""Initial order forms for customers."""
start_date = forms.DateField(help_text='When do you want the car?')
end_date = forms.DateField(help_text='When will you return the car?')
def clean_data(self, date):
data = self.cleaned_data(date)
# Check that start date is not in the past
if data < datetime.date.today():
raise ValidationError(_('Invalid date: Start in past.'))
# Ensure that start date is not today (to avoid last_minute bookings.)
if data == datetime.date.today():
raise ValidationError(_('Invalid date: Please reserve your car at least 24 hours in advance.'))
return data
cleaned_start_date = clean_data(start_date)
cleaned_end_date = clean_data(end_date)
('_' is for )
The booking has a start_date and an end_date. When a current date is within the start_date and end_date, the car is marked as unavailable. If the boolean field is_available (not represented in forms.py above) is set to "False", the car is unavailable completely.
Again, because of the unique nature of car rentals, this may be a problem. Some people book a car for six months, and others book it for two days. If someone wants a long-term rental but there's another short interlude during their expected duration, this validation would prevent them from placing the order completely!
But this is a problem: Going back to the rental model, someone may be booking a car in the future. A car that's unavailable now should still be able to be reserved for a future date.
Adding to that, an individual car can only be rented to one person at a time, so it can't have multiple bookings for the same period. Again, because of the unique nature of car rentals, this may be a problem. Some people book a car for six months, and others book it for two days. If someone wants a long-term rental but there's another short interlude during their expected duration, this validation would prevent them from placing the order completely!
So if a conflict arises, rather than blocking the booking entirely (which, again, would be a bad UX decision), it should notify the business so they can assign another car and plan ahead.
Other clients should not be able to book it for the time in which it is borrowed, but they should be able to book it for other times when it is free.
So if someone places an order now for, let's say the 24-31st of December. Those days should be blocked off. However, another person should be able to book it from today to the 23rd, and from the 31st onwards. And if the person renting it should extend, it should notify the rental business so they can assign another car to the user well in advance.
Possible idea to move forward
The core assumption in all those answers is that the booking unavailability has to be handled in Django itself, in the backend. However, I'm building this project with REST framework, and will use a Js based front-end (currently learning Javascript for this purpose).
I think that this would be better handled in a more holistic way with the in-built form validation and save functions.
The workflow would go something like this:
The User selects a car and selects the start and end dates from a drop-down calendar on the website.
The form will then check to see if the absolute basic checks (can't book a car in the past) are fine. If those work, then the order is placed and saved in the database.
If there's a scheduling conflict, the order is not blocked, but passed to the business that can assign them a different car for the period. (Generally, people don't care much for receiving particular cars--mostly the price, space and the fuel economy. Everything else is interchangeable.
Once that happens, the deposit can be collected, and the order can be set in the system.
Anyhow, that's my preliminary idea that would bring together the best of all worlds
and create a great experience for both the business and customer.
So my question is: How could this actually be set up? What would need to be on the front-end and what would go in the back-end? I'm learning programming as I go, so this may be simple, but I've been struggling with this for a week, I would appreciate any help on this!
Thanks!

Sounds like you have two processes - the customer order and the car assignment. You need to plan out your data structure and then your process flow. This will help you get things straight before you start.
Models
Using (Brackets) for foreign keys, Customer_order collects things like:
Customer(User)
desired_start_date
desired_end_date
car_type - this could be many fields
A car model
car_type
rental (many to many Rental with through table Rental)
A rental model
car (Car)
customer_order(Customer_order)
start_date
end_date
We have kept the rental model with its own start and end dates as the user may change their desired period but it shouldn't change the rental dates without checking if others exist in that time period for that car.
So the flow should go:
user passes js validation and form submitted to backend
backend checks for car availability based on type and dates
if available, creates rental
if not available, alert user and passes to staff
if a user changes a rental period (via an edit screen for existing customer_orders)
backend checks for same car availability based on existing rentals
if not available, alert user and pass to staff
You'd also create a staff only view, that lists customer_orders that can't be matched (without rental models) along with cars of the requested type that don't have rentals for that period.
Seeing as you have that view, it strikes me your backend process could use something similar to also look for and assign a different car of the same type automatically if you wanted to extend the availability check process, notifying staff it has occurred, while only referring back to staff if that type of car is unavailable.
Actually programming all this is left as an exercise for the reader.

Related

How to add fields dynamically to a form? - Implementing a django app to track order status

After a basic introduction to Python thanks to an edX course and a chat with a friend who told me about Django, I thought I could implement a solution for my laboratory. My goal is to keep track of every reagent order made by everyone of us researchers to the suppliers.
After one month I have a pretty decent version of it which I'm very proud of (I also have to thank a lot of StackOverFlow questions that helped me). Nonetheless, there's one requirement of the ordering flow that I haven't been able to translate to the Django app. Let me explain:
Users have a form to anotate the reagent (one per form) they need, and then it is passed to the corresponding manufacturer for them to send us an invoice. It's convenient that each invoice has several products, but they all have to: a) be sold by the same manufacturer, b) be sent to the same location and c) be charged to the same bank account (it's actually more complicated, but this will suffice for the explanation).
According to that, administrators of the app could process different orders by different users and merge them together as long as they meet the three requirements.
How would you implement this into the app regarding tables and relationships?
What I have now is an Order Model and an Order Form which has different CharFields regarding information of the product (name, reference, etc.), and then the sending direction and the bank account (which are ForeingKeys).
When the administrators (administratives) process the orders, they asign several of them that meet the requirements to an invoice, and then the problem comes: all the data has to be filled repeatedly for each of the orders.
The inmediate solution for this would be to create a Products Model and then each Order instance could have various products as long as they meet the three requirements, but this presents two problems:
1) The products table is gonna be very difficult to populate properly. Users are not gonna be concise about references and important data.
2) We would still have different Orders that could be merged into the same invoice.
I thought maybe I could let the users add fields dynamically to the Order model (adding product 1, product 2, product 3). I read about formsets, but they repeat whole Forms as far as I understood, and I would just need to repeat fields. Anyway, that would solve the first point, but not the second.
Any suggestions?
Thanks, and sorry for the long block!

Django custom creation manager logic for temporal database

I am trying to develop a Django application that has built-in logic around temporal states for objects. The desire is to be able to have a singular object representing a resource, while having attributes of that resource be able to change over time. For example, a desired use case is to query the owner of a resource at any given time (last year, yesterday, tomorrow, next year, ...).
Here is what I am working with...
class Resource(models.Model):
id = models.AutoField(primary_key=True)
class ResourceState(models.Model):
id = models.AutoField(primary_key=True)
# Link the resource this state is applied to
resource = models.ForeignKey(Resource, related_name='states', on_delete=models.CASCADE)
# Track when this state is ACTIVE on a resource
start_dt = models.DateTimeField()
end_dt = models.DateTimeField()
# Temporal fields, can change between ResourceStates
owner = models.CharField(max_length=100)
description = models.TextField(max_length=500)
I feel like I am going to have to create a custom interface to interact with this state. Some example use cases (interface is completely up in the air)...
# Get all of the states that were ever active on resource 1 (this is already possible)
Resource.objects.get(id=1).states.objects.all()
# Get the owner of resource 1 from the state that was active yesterday, this is non-standard behavior
Resource.objects.get(id=1).states.at(YESTERDAY).owner
# Create a new state for resource 1, active between tomorrow and infinity (None == infinity)
# This is obviously non standard if I want to enforce one-state-per-timepoint
Resource.objects.get(id=1).states.create(
start_dt=TOMORROW,
end_dt=None,
owner="New Owner",
description="New Description"
)
I feel the largest amount of custom logic will be required to do creates. I want to enforce that only one ResourceState can be active on a Resource for any given timepoint. This means that to create some ResourceState objects, I will need to adjust/remove others.
>> resource = Resource.objects.get(id=1)
>> resource.states.objects.all()
[ResourceState(start_dt=None, end_dt=None, owner='owner1')]
>> resource.states.create(start_dt=YESTERDAY, end_dt=TOMORROW, owner='owner2')
>> resource.states.objects.all()
[
ResourceState(start_dt=None, end_dt=YESTERDAY, owner='owner1'),
ResourceState(start_dt=YESTERDAY, end_dt=TOMORROW, owner='owner2'),
ResourceState(start_dt=TOMORROW, end_dt=None, owner='owner1')
]
I know I will have to do most of the legwork around defining the logic, but is there any intuitive place where I should put it? Does Django provide an easy place for me to create these methods? If so, where is the best place to apply them? Against the Resource object? Using a custom Manager to deal with interacting with related 'ResourceState' objects?
Re-reading the above it is a bit confusing, but this isnt a simple topic either!! Please let me know if anyone has any ideas for how to do something like the above!
Thanks a ton!
too long for a comment, and purely some thoughts, not a full answer, but having dealt with many date effective records in financial systems (not in Django) some things come to mind:
My gut would be to start by putting it on the save method of the resource model. You are probably right in needing a custom manager as well.
I'd probably also flirt with the idea of a is_current boolean field in the state model but certain care would need to be considered with future date effective state records. If there is only one active state at a time, I'd also examine the need for an enddate. Having both start and end definitely makes the raw sql queries (if ever needed) easier: date() between state.start and state.end <- this would give current record, sub in any date to get that date's effective record. Also, give some consideration to the open ended end date where you don't know the end date date. Your queries will have to handle the nulls properly. YOu probably also may need to consider the open ended start date (say for a load of historical data where the original start date is unknown). I'd suggest staying away from using some super early date as a fill in (same for date far in the future for unknown end dates) - If you end up with lots of transactions, your query optimizer may thank you, however, I may be old and this doesn't matter anymore.
If you like to read about this stuff, I'd recommend a look at 1.8 in https://www.amazon.ca/Art-SQL-Stephane-Faroult/dp/0596008945/ and chapter 6:
"But before settling for one solution, we must acknowledge that
valuation tables come in all shapes and sizes. For instance, those of
telecom companies, which handle tremendous amounts of data, have a
relatively short price list that doesn't change very often. By
contrast, an investment bank stores new prices for all the securities,
derivatives, and any type of financial product it may be dealing with
almost continuously. A good solution in one case will not necessarily
be a good solution in another.
Handling data that both accumulates and changes requires very careful
design and tactics that vary according to the rate of change."

Django: Handling discount codes

I am currently building a Django application where visitors can buy an online course. I now want to implement the possibility to provide discount codes. As these discount codes should be limited by quantity I now have the following implementation idea:
Guest visits www.page.com?discount=TEST
The model discount contains the fields discount_codes & max qty. I will check here, if the code exists. Also, I have to count all entries in my order model that used the discount code TEST. My order model contains the foreign_key field 'redeemed_discounts').
As soon the user clicks on Pay (via Stripe) I'll once again count all the orders in my order model which contain 'TEST' to make sure, the 'max_qty' is not reached meanwhile.
Now I can charge the visitor.
Would you consider this as good implemented or do you see any problems with the way I am planning to do it?
instead of using max_qty why don't you use something like use_left and max_use
so whenever someone uses that code you can reduce the count accordingly and when count hits zero you can stop using that with this approach you don't have to scan order table every time to see if the coupon code is still available.

Django Model Table temporary data vs. permanent data

I am writing a trip planner, and I have users. For the purposes of this question, lets assume my models are as simple as having a "Trip" model and having a "UserProfile" model.
There is a functionality of the site that allows to search for routes (via external APIs), and then dynamically assembles those into "trips", which we then display. A new search deletes all the old "trips" and figures out new ones.
My problem is this: I want to save some of these trips to the user profile. If the user selects a trip, I want it to be permanently associated with that profile. Currently I have a ManyToMany field for Trips in my UserProfile, but when the trips are "cleaned/flushed", all trips are deleted, and that association is useless. I need a user to be able to go back a month later and see that trip.
I'm looking for an easy way to duplicate that trip data, or make it static once I add it to a profile . .. I don't quite know where to start. Currently, the way it is configured is there is a trips_profile datatable that has a foreign key to the "trips" table . . . which would be fine if we weren't deleting/flushing the trips table all the time.
Help appreciated.
It's hard to say exactly without your models, but given the following layout:
class UserProfile(models.Model):
trips = models.ManyToManyField(Trip)
You can clear out useless Trips by doing:
Trip.objects.filter(userprofile__isnull=True).delete()
Which will only delete Trips not assigned to a UserProfile.
However, given the following layout:
class Trip(models.Model):
users = models.ManyToManyField(User)
You could kill the useless trips with:
Trip.objects.filter(users__isnull=True).delete()
The second method has the side benefit of not requiring any changes to UserProfile or even a UserProfile at all, since you can then just get a Users trips with:
some_user.trip_set.all()

Monthly Reports in Django - Enforce uniqueness for month?

This is a fairly quick question regarding schema design for a Django project.
Basically, we have a series of monthly reports from different departments that are aggregated into a single report with some pretty charts (we're probably going to use Google Visualization API for that, but I'm open to other suggestions, if you think something else integrates nicely with Django).
Each department is responsible for filing their own figures for their part of the report. We'll probably be using the Django admin for entering in those figures, since it doesn't have to be pretty, it's just to get some numbers in each month.
I'm assuming the better way here is to have an abstract Report model, inherit this for each department having a separate Model, with any specific fields overridden, and then have a DateField on each one as well.
Having a month as the parent object, and descending the reports from that - that's a silly approach, right?
Also, what's the best way of enforcing it so that they can only submit their figures once? I could have a separate month and year field, I guess and enforce a unique on that field, but I was hoping to use the inbuilt DateField, but what's the best way to enforce month and year uniqueness? Should I use the new model validation feature for that?
Cheers,
Victor
A Django model field can have an option unique_for_month and unique_for_year. The value of that option is a DateTime or Date field in the model.
E.g.
class Report(models.Model):
submit_date = models.DateField()
department_id = models.IntegerField(unqiue_for_month=date)