How to know if a period overlaps another - python

I'd like to make an application that allows me to reserve an item during a specified period.
I need a function to check if the specified item is already booked during the period i want to use it (so the booking should fail). Can you help me?
models.py
from django.db import models
from datetime import *
from django.db.models import Q
import datetime
from django.core.exceptions import ValidationError
class Reservation(models.Model):
date_debut = models.DateTimeField('debut de la reservation')
date_fin = models.DateTimeField('fin de la reservation')
obj_res = models.ForeignKey('Materiel')
notice = models.CharField(max_length=200)
personne = models.ForeignKey('Personne')
def __int__(self):
return self.id
def save(self, *args, **kwargs):
new_start_date = datetime.datetime(2013, 11, 16, 10, 00)
new_end_date = datetime.datetime(2013, 11, 16, 11, 00)
material = Materiel.objects.get(nom="Bimaire 1")
clashing_reservations = Reservation.objects.filter(obj_res=material).filter(
Q(date_debut__lte=new_start_date, date_fin__gte=new_start_date) |
Q(date_debut__lt=new_end_date, date_fin__gte=new_end_date)
)
if clashing_reservations.exists():
raise ValidationError('Those dates clash with another reservation.')
return super(Reservation, self).save(*args, **kwargs)
class Materiel(models.Model):
nom = models.CharField(max_length=200)
description = models.CharField(max_length=200)
responsable = models.CharField(max_length=200)
modalites = models.CharField(max_length=200)
def __unicode__(self):
return self.nom
class Personne(models.Model):
nom = models.CharField(max_length=200)
prenom = models.CharField(max_length=200)
def __unicode__(self):
return self.nom
views.py
def reservation(request):
if request.POST:
form = ReservationForm(request.POST, request.FILES)
if form.is_valid():
form.save()
else:
form = ReservationForm()
args = {}
args.update(csrf(request))
args["form"] = form
return render_to_response("reservation.html", args)
EDIT
Thanks so far it's seems to work.
But now i want define that new_start_date and new_end_date are the actual values of the form.

This is untested code, but I believe that this logic should test whether any other reservations overlap the one submitted in the form. This should probably be put in a clean method of the form, or some other validation. Perhaps even on the save method of the Reservation model:
from django.db.models import Q
new_start_date = datetime.datetime(2013, 11, 16, 10, 00)
new_end_date = datetime.datetime(2013, 11, 16, 11, 00)
material = Materiel.objects.get(nom='Whatever')
clashing_reservations = Reservation.objects.filter(objet=material).filter(
Q(date_debut__lte=new_start_date, date_fin__gte=new_start_date) |
Q(date_debut__lt=new_end_date, date_fin_gte=new_end_date)
)
if clashing_reservations.exists():
raise ValidationError('Those dates clash with another reservation.')

I don't know the format your dates are in but regardless you can use the module datetime to compare (subtract, add, higher/lower etc.) dates and times with one another.
So I've made a simple example to illustrate its use (I presume your format is months/days/years):
from datetime import *
debut_date = datetime.strptime(date_debut_db, "%m/%d/%y")
fin_date = datetime.strptime(date_fin_db, "%m/%d/%y")
debut_date2 = datetime.strptime(date_debut_form, "%m/%d/%y")
fin_date2 = datetime.strptime(date_fin_form, "%m/%d/%y")
if (debut_date2 > debut_date and debut_date2 < fin_date) or (fin_date2 > debut_date and fin_date2 < fin_date):
print "Impossible!"
else:
print "Possible!"
date_debut_db and date_fin_db are the dates you get out of your database whereas date_debut_form and date_fin_form are the ones that the user fills in.

Related

How to return a list of available time slots via a forms ValidationError

models.py
from django.db import models
from django.utils import timezone
from django.urls import reverse
from django.contrib.auth.models import User
class Customer(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
name = models.CharField(max_length=20,null=True)
def __str__(self):
return self.name
# Create your models here.
class Booking(models.Model):
customer_name = models.ForeignKey(Customer,on_delete=models.CASCADE,null=True)
username = models.ForeignKey(User,on_delete=models.CASCADE)
qty_plts = models.PositiveSmallIntegerField(default=1)
cbm = models.PositiveSmallIntegerField(default=1)
created_date = models.DateTimeField(default=timezone.now())
delivery_date = models.DateField(null=True)
delivery_time = models.TimeField(null=True)
booking_number = models.CharField(max_length=50,unique=True)
def __str__(self):
return self.booking_number
def save(self, **kwargs):
if not self.booking_number:
self.booking_number = f"{self.delivery_date:%Y%m%d}{self.delivery_time:%H%M}"
super().save(**kwargs)
def get_absolute_url(self):
return reverse('bookmyslot:detail',kwargs={'pk':self.pk})
forms.py
from django import forms
from bookmyslot.models import Booking,Customer
from bootstrap_datepicker_plus import DatePickerInput
import datetime as dt
from django.utils import timezone
HOUR_CHOICES = [(dt.time(hour=x), '{:02d}:00'.format(x)) for x in range(7, 13)]
class BookingForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
user = kwargs.pop('username',None)
super(BookingForm,self).__init__(*args,**kwargs)
self.fields['qty_plts'].label = "Quantity Of Pallets"
self.fields['cbm'].label = "Shipment CBM"
self.fields['delivery_date'].label = "Delivery Date"
self.fields['delivery_time'].label = "Delivery Time"
self.fields['customer_name'].label = "Customer Name"
self.fields['customer_name'].queryset = Customer.objects.filter(username=user)
def clean(self):
cleaned_data = super(BookingForm,self).clean()
booking_number = f"{cleaned_data.get('delivery_date'):%Y%m%d}{cleaned_data.get('delivery_time'):%H%M}"
if Booking.objects.filter(booking_number=booking_number).exists():
raise forms.ValidationError("Requested slot is already booked, please choose another time")
class Meta:
model = Booking
fields = ('customer_name','qty_plts','cbm','delivery_date','delivery_time')
widgets = {'delivery_date':DatePickerInput(options={"daysOfWeekDisabled":[0,6],"minDate":timezone.now().date().strftime('%Y-%m-%d')}),
'delivery_time':forms.Select(choices=HOUR_CHOICES)}
views.py
from django.shortcuts import render
# Create your views here.
from .models import Booking,Customer
from .forms import BookingForm
from django.urls import reverse,reverse_lazy
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import (ListView,DetailView,CreateView,UpdateView,DeleteView,TemplateView)
class BookingCreate(LoginRequiredMixin,CreateView):
login_url = '/login'
redirect_field_name = 'bookmyslot/booking_detail.html'
model = Booking
form_class = BookingForm
def get_form_kwargs(self, **kwargs):
form_kwargs = super(BookingCreate,self).get_form_kwargs(**kwargs)
form_kwargs['username'] = self.request.user
return form_kwargs
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.username = self.request.user
self.object.save()
return super().form_valid(form)
I am trying to figure out how to return a list of available time slots, using a ValidationError.
There are 6 time slots [delivery_time] to choose from on any given date -> [7,8,9,10,11,12]
The booking_number field is a unique id that is a concatenation of the delivery_date and delivery_time, which is generated each time a user successfully creates a booking.
So, let's assume there are 3 existing bookings for 2021-10-21 at 7:00,08:00 & 10:00, which are saved in the Booking model with the following booking numbers:
202110210700
202110210800
202110211000
Assuming a user tries to book over a slot that already exists e.g. 202110210700,
the validation error should return "Requested slot is already booked, please choose another from one of these available slots:
09:00
11:00
12:00
How can I achieve this?
You can use an utility function like this to return the remained time slot in the form validation like this :
app_name/utilities.py
def list_diff(l1: list, l2: list):
""" Return a list of elements that are present in l1
or in l2 but not in both l1 & l2.
IE: list_diff([1, 2, 3, 4], [2,4]) => [1, 3]
"""
return [i for i in l1 + l2 if i not in l1 or i not in l2]
def check_free_time(time_slot: list, exist_list: list):
""" Return the list of available time slot if exist,
according to a given exist slot list.
Return the remained time slot, or empty list if all are used
IE: ([7, 12], [7, 8, 9, 10, 11, 12]) => [8, 9, 10, 11]
"""
remain_slot = list_diff(time_slot, exist_list)
return remain_slot
Now import the check_free_time in your forms.py file and use it if the booking_number exist.
from datetime import datetime
from .utilities import check_free_time
if Booking.objects.filter(booking_number=booking_number).exists():
today = datetime.today()
d = today.day
m = today.month
y = today.year
# Retrieve today's bookings
today_bookings = Booking.objects.filter(delivery_date__year=y,delivery_date__month=m delivery_date__day=d)
# A list of today's bookings time slot (take only hours)
# Return something like <QuerySet [{'delivery_date__hour': 11}, ...]>
today_time_slot = today_bookings.values('delivery_date__hour')
# Convert it to list of hours values since the utility function accept list.
today_time_slot_list = [h['delivery_date__hour'] for h in list(today_time_slot)]
# The line above return something like [9, 11, ...]
all_time_slot = [7, 8, 9, 10, 11, 12]
# Now we can call the utility function `check_free_time`
available_slot = check_free_time(all_time_slot, today_time_slot_list)
if available_slot: # The are some available slot (list not empty)
# I use python3.6 f-string to format the message
# Note that the list is in a raw format ([8,11,12]), you can do better like ['8h:00', '11h:00', '12h:00']
message = f"Requested slot is already booked, please choose another time in {available slot}."
raise forms.ValidationError(message)
else: # The list is empty, all slot are taken
message = "The are not available slot for this booking today."
raise forms.ValidationError(message)
NB : I tested only in a python an Django shell, if some errors try to add in comments.

add time when we save a Timefield in django

I have a dateTime field in a model. The dateTime field named breakfast_start_time takes an input.
I have to save another variable or timefield(whichever is better) named breakfast_attendence_start_time whose value should be automatically saved 15 minutes less than the breakfast_start_time.
For this we use
def save(self, *args, **kwargs):
#do something
super().save(*args, *kwargs)
I am trying to do
breakfast_attendence_start_time = breakfast_start_time - time(15,0)
but it is giving error that
class TimeField does not define '_sub_', so the '-' operator cannot be used on its instances
Edited:
Here is the full code
class Hostel(models.Model):
name = models.CharField(max_length=15)
breakfast_start_time = models.TimeField()
lunch_start_time = models.TimeField()
snacks_start_time = models.TimeField()
dinner_start_time = models.TimeField()
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
I am taking start time from admin panel and I want to add 4 more variable/field like breakfast_attendence_start_time whose value should be saved automatically 15 minutes earlier than breakfast_start_time how can I achive that.
You should use timedelta to sub specific time with DateTime field. Such as
import datetime
time_before_15_minute = datetime.datetime.now() - datetime.timedelta(minutes=15)
Use DateTimeField instead of TimeField and use timedelta to make substractions
from datetime import datetime, timedelta
n = datetime(2019, 10, 4, 12, 30)
m = n - timedelta(minutes = 15) # m is now datetime(2019, 10, 4, 12, 15)
You can play with the DateTimeField but this will return time of when this function was called or used. Hope it helps
from django.utils import timezone
class AKA(models.Model):
create_time = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.create_time

How to test input to a Django MultiValueField?

I'm currently working in a Django project which defines a custom DateTimeField as follows (in dashboard/forms/fields):
import pytz
from datetime import date, datetime
from django import forms
from django.core.exceptions import ValidationError
from dashboard.forms.widgets import DateTimeWidget
class DateTimeField(forms.MultiValueField):
widget = DateTimeWidget
DATE_FORMAT = '%B %d, %Y'
TIME_FORMAT = '%I:%M %p'
DATETIME_FORMAT = f'{DATE_FORMAT} {TIME_FORMAT}'
def __init__(self, timezone_choices=None, timezone=None, **kwargs):
fields = (forms.CharField(), forms.CharField(), forms.CharField())
super().__init__(fields=fields, **kwargs)
self.timezone_choices = timezone_choices
self.timezone = timezone
#property
def timezone_choices(self):
return self._timezone_choices
#timezone_choices.setter
def timezone_choices(self, value):
self._timezone_choices = self.widget.timezone_choices = value
#property
def timezone(self):
return self._timezone
#timezone.setter
def timezone(self, value):
self._timezone = self.widget.timezone = value
def compress(self, data_list):
try:
date, time, zone = data_list
tz = pytz.timezone(zone)
dt = datetime.strptime(f'{date} {time}', self.DATETIME_FORMAT)
return tz.localize(dt)
except ValueError:
return None
This field is used in a form called SessionForm like so:
class SessionForm(forms.ModelForm):
class Meta:
model = Session
fields = [
'scheduled_for',
]
scheduled_for = DateTimeField(
required=False,
timezone_choices=Family.TIMEZONE_CHOICES
)
This form includes the following clean() method, which I'd like to test:
def clean(self):
cleaned_data = super().clean()
status = cleaned_data.get('status')
location = cleaned_data.get('location')
if status in [Session.SCHEDULED, Session.SCHEDULED_CALENDARED] and not cleaned_data.get('scheduled_for'):
self.add_error(
'scheduled_for',
f"This field is required if the status is '{Session.SCHEDULED}' or '{Session.SCHEDULED_CALENDARED}'.")
return cleaned_data
To this end, I've tried writing the following test:
class SessionCreateTest(TestCase):
def test_scheduled_session_with_scheduled_time_and_expert_and_location_is_valid(self):
scheduled_time = dateutil.parser.parse("5 January 2019 at 1:30 PM")
date = scheduled_time.strftime(DateTimeField.DATE_FORMAT)
time = scheduled_time.strftime(DateTimeField.TIME_FORMAT)
zone = pytz.country_timezones('US')[20] # 'America/Los_Angeles'
scheduled_for = (date, time, zone)
self.data.update(
status=Session.SCHEDULED,
scheduled_for=scheduled_for,
expert=ExpertFactory().id,
location=Session.AT_HOME)
form = SessionForm(data=self.data)
import ipdb; ipdb.set_trace()
Unfortunately, when I drop into the debugger, I see that the form still has errors:
> /Users/kurtpeek/Documents/Dev/lucy2/lucy-web/dashboard/tests/test_sessions.py(666)test_scheduled_session_with_scheduled_time_and_expert_and_location_is_valid()
665 import ipdb; ipdb.set_trace()
--> 666 self.assertTrue(form.is_valid())
667
ipdb> form.errors
{'scheduled_for': ["This field is required if the status is 'Scheduled' or 'Scheduled & Calendared'."]}
However, if I pass my input for this field to the field's compress() method, it seems to be 'parsed' correctly:
ipdb> field = DateTimeField()
ipdb> field.compress(scheduled_for)
datetime.datetime(2019, 1, 5, 13, 30, tzinfo=<DstTzInfo 'America/Los_Angeles' PST-1 day, 16:00:00 STD>)
I don't understand why this test is not passing? How could I 'break this down' further?
I suggest you put the break point inside the clean method of the form. Check the structure of scheduled_for inside cleaned_data dict. It should give you the answer you need to test it.

DJANGO avoid repeating date on DateTime calculation

I have managed to create a very simple model which allows me to subtract 2 DateTime fields, like so:
class Log(models.Model):
date = models.DateField()
take = models.DateTimeField()
land = models.DateTimeField()
tot = models.CharField(max_length=200, blank=True, default='00000')
def __str__(self):
return str(self.date)
def time_delta(self):
tdelta = self.land - self.take
return str(tdelta)
def save(self, *args, **kwargs):
self.tot = self.time_delta()
super(Log, self).save(*args, **kwargs)
My problem is the user would have to specify the date on every field. How could I make the fields take and land refer to date once and for all?
I don’t know how to do that in your Django model. I think you have to calculate the dates and times in your controller instead, and then register the values in your database.
You can do something like that with datetime.datetime.combine() function:
Return a new datetime object whose date components are equal to the given date object’s, and whose time components are equal to the given time object’s.
You have a reference date, for instance: today.
import datetime
date = datetime.date.today()
The user enter the takeoff time, you can combine this time with the reference date.
takeoff_time = datetime.time(8, 12)
takeoff_datetime = datetime.datetime.combine(date, takeoff_time)
print(takeoff_datetime.isoformat(" "))
# -> 2016-12-21 08:12:00
If the landing date is the same as the takeoff date, you can calculate the landing date/time with the same date reference:
landing_time = datetime.time(12, 37)
landing_datetime = datetime.datetime.combine(date, landing_time)
print(landing_datetime.isoformat(" "))
# -> 2016-12-21 12:37:00
Then, you can register the date, _takeoff_datetime_ and _landing_datetime_ in your database.
Note: you can do the same with the flight duration
I eventually managed to find a solution largely based on Laurent's answer so here it is,if that can ever help someone else:
from datetime import datetime
def calculation(self):
calc_take_off = datetime.combine(self.date, self.take)
calc_land = datetime.combine(self.date, self.land)
result = calc_land - calc_take_off
return str(result)
and then to save this in models:
def save(self, *args, **kwargs):
self.tot = self.calculation()
super(Log, self).save(*args, **kwargs)

Can't save datetime.now() to DateTimeField

I've defined a model as follows (Shortened it for the question)
from datetime import datetime, date, timedelta
class Case(models.Model):
received_email_sent = models.DateTimeField(null=True, blank=True, default=None)
def send_received_email(self):
message = settings.EMAIL_HEADER + self.case_received_email() + settings.EMAIL_FOOTER
send_mail('Subject here', message, settings.EMAIL_HOST_USER, ['xxx#xxx.com'], fail_silently=False)
self.received_email_sent = datetime.now()
and in the view I call send_received_email on an existing object. I know that the send_received_email block is being entered because I'm receiving the emails every time I test this out, but the self.received_email_sent = datetime.now() part is leaving that field as its default value (None) every time. Here's the relevant part of the view:
from logbook.models import Case
def job_email(request, case_id):
case = get_object_or_404(Case,pk=case_id)
case.send_received_email()
return HttpResponseRedirect('/jobs/'+str(case.case_id))
I have also tried an alternative method, where saving the field is done in the view instead of the model, like so:
models:
class Case(models.Model):
received_email_sent = models.DateTimeField(null=True, blank=True, default=None)
def send_received_email(self):
message = settings.EMAIL_HEADER + self.case_received_email() + settings.EMAIL_FOOTER
send_mail('Subject here', message, settings.EMAIL_HOST_USER, ['xxx#xxx.com'], fail_silently=False)
#self.received_email_sent = datetime.now()
views:
from datetime import datetime
from logbook.models import Case
def job_email(request, case_id):
case = get_object_or_404(Case,pk=case_id)
case.send_received_email()
case.received_email_sent = datetime.now()
return HttpResponseRedirect('/jobs/'+str(case.case_id))
I have also tried both of the above routes with various tweaks like removing the brackets on now() and changing from datetime import datetime to just import datetime. No joy. Thanks for having a look at this.
You need to call self.save() at the end of send_received_email().

Categories

Resources