How Do I Get The Events In Between Start and End Time To Display On My Calendar
Question:
I’m using a tutorial I found online…and it’s mostly working…I’m stuck when I’m trying to loop through the list of events and show them on a daily basis. If I have an event that starts on June 1 and ends on June 5th…I’d like to be able to show the event on June 2 on my list and June 3 and June 4. Currently I can only get it to say it starts on June 1 and ends on June 5. I’ve tried multiple different filters utilizing GTE and LTE and that doesn’t seem to help.
Here’s my Calendar utility…
class Calendar(HTMLCalendar):
def __init__(self, year=None, month=None, dropdown=None):
self.dropdown = dropdown
self.year = year
self.month = month
super(Calendar, self).__init__()
# formats a day as a td
# filter events by day
def formatday(self, day, events):
events_per_day = events.filter(start_time__day__lte=day, end_time__day__gte=day)
d = ''
for event in events_per_day:
d += f'<li> {event.get_html_url} </li>'
if day != 0:
return f"<td><span class='date'>{day}</span><ul> {d} </ul></td>"
return '<td></td>'
# formats a week as a tr
def formatweek(self, theweek, events):
week = ''
for d, weekday in theweek:
week += self.formatday(d, events)
return f'<tr> {week} </tr>'
# formats a month as a table
# filter events by year and month
def formatmonth(self, withyear=True):
events = VacationRequest.objects.filter(vacation_calendar=self.dropdown,start_time__year=self.year,start_time__month=self.month).order_by('start_time')
cal = f'<table border="0" cellpadding="0" cellspacing="0" class="calendar">n'
cal += f'{self.formatmonthname(self.year, self.month, withyear=withyear)}n'
cal += f'{self.formatweekheader()}n'
for week in self.monthdays2calendar(self.year, self.month):
cal += f'{self.formatweek(week, events)}n'
cal += f'</table>n'
cal += f'<h1 class="title">Vacation Schedule For {self.formatmonthname(self.year, self.month, withyear=withyear)}</h1>n'
return cal
Here’s my view…
class VacationRequestCalendarView(generic.ListView):
model = VacationRequest
template_name = 'vacation_request_calendar_view.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dropdown = (self.request.GET.get("dropdown", None))
d = get_date(self.request.GET.get('month', None))
cal = Calendar(d.year, d.month, dropdown)
cal.setfirstweekday(calendar.SUNDAY)
html_cal = cal.formatmonth(withyear=True)
vacation_calendar = VacationCalendar.objects.get(id=self.request.GET.get("dropdown", None))
vacation_calendar_requests = VacationRequest.objects.filter(start_time__year = d.year,start_time__month = d.month)
context['calendar'] = mark_safe(html_cal)
context['dropdown'] = dropdown
context['next_month'] = next_month(d)
context['prev_month'] = prev_month(d)
context['vacation_calendar_requests'] = vacation_calendar_requests
context['vacation_calendar'] = vacation_calendar
return context
def get_object(self, queryset=None):
return get_object_or_404(VacationCalendar, id=self.request.GET.get("dropdown"))
def get_date(req_month):
if req_month:
year, month = (int(x) for x in req_month.split('-'))
return date(year, month, day=1)
return datetime.today()
def prev_month(d):
first = d.replace(day=1)
prev_month = first - timedelta(days=1)
month = 'month=' + str(prev_month.year) + '-' + str(prev_month.month)
return month
def next_month(d):
days_in_month = calendar.monthrange(d.year, d.month)[1]
last = d.replace(day=days_in_month)
next_month = last + timedelta(days=1)
month = 'month=' + str(next_month.year) + '-' + str(next_month.month)
return month
And here’s my Django Template Logic…
{% if vacation_calendar_requests %}
{% for request in vacation_calendar_requests %}
<div class="leftwidth87">
{% ifchanged request.start_time|date:"M d, Y" %}
<h2>{{ request.start_time|date:"M d, Y" }}</h2>
{% endifchanged %}
<div>
<h2><a href="{% url 'VacationRequests:vacation_request_detail' pk=request.pk %}">{{ request.vacation_request_name }}
{{ request.start_time }} -
{{ request.end_time }}</a></h2>
</div>
</div>
{% endfor ifchanged %}
{% else %}
<div class="title">
<h2>No Vacation Scheduled</h2>
</div>
{% endif %}
The code above will properly display events that span multiple days on a calendar…That’s attributed to the formatday function. I see how it correctly filters the events by day…I just can’t figure out how to do the same thing when I try to print out the events by day via my template logic.
I get that I’m only finding 2 events with my template loop…the way that I’m going about it…but how could I loop through and find events by day instead of just their range ( start time and end time ? )
Add on…
I found an example online of how to do what I am trying to do..but I can’t figure out how to incorporate it into my code above…the example I found is…
from datetime import timedelta, date
def daterange(date1, date2):
for n in range(int ((date2 - date1).days)+1):
yield date1 + timedelta(n)
start_dt = date(2015, 12, 20)
end_dt = date(2016, 1, 11)
for dt in daterange(start_dt, end_dt):
print(dt.strftime("%Y-%m-%d"))
And it came from https://www.w3resource.com/python-exercises/date-time-exercise/python-date-time-exercise-50.php
But I can’t figure out how to incoporate it…
Update…
I’ve gotten a bit closer by updating the Calendar utility…by adding the following code to the format month.
for week in self.monthdays2calendar(self.year, self.month):
if events:
# cal += f'{self.formatmonthname(self.year, self.month)}n'
cal += f'{self.formatweek(week, events)}n'
return cal
I’m almost there…except it’s printing an additional number from the calendar format…I’m also still trying to get the month and date and year to print out in a reasonable format.
Answers:
There’s a library named datetimerange . When an event is created, you can get the start and end datetime (which you are already doing), and pass it into the DateTimeRange function and store it as a string in DB.
When you get the event back. You can get the datetime values again. Here’s a piece of code from the documentation of library:
import datetime
from datetimerange import DateTimeRange
time_range = DateTimeRange("2015-01-01T00:00:00+0900", "2015-01-04T00:00:00+0900")
for value in time_range.range(datetime.timedelta(days=1)):
print(value)
I would calculate the duration (number of days), and then use that duration as the number of times to loop. Then to get the date for each day of the duration, I would use a counter variable (enumerator maybe) and add it to the date. For example when the counter is 2, then I would add 2 days to the start date to get the current date in the loop.
Hopefully this would give you an idea.
Using your example, add this to your view:
from datetime import timedelta, date
# Add this function outside the View, just like you did with the other ones
def daterange(date1, date2):
for n in range(int ((date2 - date1).days)+1):
yield date1 + timedelta(n)
def get_context_data(self, **kwargs):
## other code here ##
vacation_calendar_requests = VacationRequest.objects.filter(start_time__year = d.year,start_time__month = d.month)
# this will add a new field, "dates", to each event
for request in vacation_calendar_requests:
start_date = request.start_time
end_date = request.end_time
start_dt = date(start_date.year,start_date.month,start_date.day) # not sure if this is how you get your event start date
end_dt = date(end_date.year,end_date.month,end_date.day) # not sure if this is how you get your event end date
request.dates = [date for date in daterange(start_dt, end_dt)]
## rest of code here ##
Then in your template, add this nested loop to show the dates for each event:
{% for date in request.dates %}
{{ request.vacation_request_name }}
{{ date }} /* format the date accordingly */
{% endfor %}
I’m using a tutorial I found online…and it’s mostly working…I’m stuck when I’m trying to loop through the list of events and show them on a daily basis. If I have an event that starts on June 1 and ends on June 5th…I’d like to be able to show the event on June 2 on my list and June 3 and June 4. Currently I can only get it to say it starts on June 1 and ends on June 5. I’ve tried multiple different filters utilizing GTE and LTE and that doesn’t seem to help.
Here’s my Calendar utility…
class Calendar(HTMLCalendar):
def __init__(self, year=None, month=None, dropdown=None):
self.dropdown = dropdown
self.year = year
self.month = month
super(Calendar, self).__init__()
# formats a day as a td
# filter events by day
def formatday(self, day, events):
events_per_day = events.filter(start_time__day__lte=day, end_time__day__gte=day)
d = ''
for event in events_per_day:
d += f'<li> {event.get_html_url} </li>'
if day != 0:
return f"<td><span class='date'>{day}</span><ul> {d} </ul></td>"
return '<td></td>'
# formats a week as a tr
def formatweek(self, theweek, events):
week = ''
for d, weekday in theweek:
week += self.formatday(d, events)
return f'<tr> {week} </tr>'
# formats a month as a table
# filter events by year and month
def formatmonth(self, withyear=True):
events = VacationRequest.objects.filter(vacation_calendar=self.dropdown,start_time__year=self.year,start_time__month=self.month).order_by('start_time')
cal = f'<table border="0" cellpadding="0" cellspacing="0" class="calendar">n'
cal += f'{self.formatmonthname(self.year, self.month, withyear=withyear)}n'
cal += f'{self.formatweekheader()}n'
for week in self.monthdays2calendar(self.year, self.month):
cal += f'{self.formatweek(week, events)}n'
cal += f'</table>n'
cal += f'<h1 class="title">Vacation Schedule For {self.formatmonthname(self.year, self.month, withyear=withyear)}</h1>n'
return cal
Here’s my view…
class VacationRequestCalendarView(generic.ListView):
model = VacationRequest
template_name = 'vacation_request_calendar_view.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
dropdown = (self.request.GET.get("dropdown", None))
d = get_date(self.request.GET.get('month', None))
cal = Calendar(d.year, d.month, dropdown)
cal.setfirstweekday(calendar.SUNDAY)
html_cal = cal.formatmonth(withyear=True)
vacation_calendar = VacationCalendar.objects.get(id=self.request.GET.get("dropdown", None))
vacation_calendar_requests = VacationRequest.objects.filter(start_time__year = d.year,start_time__month = d.month)
context['calendar'] = mark_safe(html_cal)
context['dropdown'] = dropdown
context['next_month'] = next_month(d)
context['prev_month'] = prev_month(d)
context['vacation_calendar_requests'] = vacation_calendar_requests
context['vacation_calendar'] = vacation_calendar
return context
def get_object(self, queryset=None):
return get_object_or_404(VacationCalendar, id=self.request.GET.get("dropdown"))
def get_date(req_month):
if req_month:
year, month = (int(x) for x in req_month.split('-'))
return date(year, month, day=1)
return datetime.today()
def prev_month(d):
first = d.replace(day=1)
prev_month = first - timedelta(days=1)
month = 'month=' + str(prev_month.year) + '-' + str(prev_month.month)
return month
def next_month(d):
days_in_month = calendar.monthrange(d.year, d.month)[1]
last = d.replace(day=days_in_month)
next_month = last + timedelta(days=1)
month = 'month=' + str(next_month.year) + '-' + str(next_month.month)
return month
And here’s my Django Template Logic…
{% if vacation_calendar_requests %}
{% for request in vacation_calendar_requests %}
<div class="leftwidth87">
{% ifchanged request.start_time|date:"M d, Y" %}
<h2>{{ request.start_time|date:"M d, Y" }}</h2>
{% endifchanged %}
<div>
<h2><a href="{% url 'VacationRequests:vacation_request_detail' pk=request.pk %}">{{ request.vacation_request_name }}
{{ request.start_time }} -
{{ request.end_time }}</a></h2>
</div>
</div>
{% endfor ifchanged %}
{% else %}
<div class="title">
<h2>No Vacation Scheduled</h2>
</div>
{% endif %}
The code above will properly display events that span multiple days on a calendar…That’s attributed to the formatday function. I see how it correctly filters the events by day…I just can’t figure out how to do the same thing when I try to print out the events by day via my template logic.
I get that I’m only finding 2 events with my template loop…the way that I’m going about it…but how could I loop through and find events by day instead of just their range ( start time and end time ? )
Add on…
I found an example online of how to do what I am trying to do..but I can’t figure out how to incorporate it into my code above…the example I found is…
from datetime import timedelta, date
def daterange(date1, date2):
for n in range(int ((date2 - date1).days)+1):
yield date1 + timedelta(n)
start_dt = date(2015, 12, 20)
end_dt = date(2016, 1, 11)
for dt in daterange(start_dt, end_dt):
print(dt.strftime("%Y-%m-%d"))
And it came from https://www.w3resource.com/python-exercises/date-time-exercise/python-date-time-exercise-50.php
But I can’t figure out how to incoporate it…
Update…
I’ve gotten a bit closer by updating the Calendar utility…by adding the following code to the format month.
for week in self.monthdays2calendar(self.year, self.month):
if events:
# cal += f'{self.formatmonthname(self.year, self.month)}n'
cal += f'{self.formatweek(week, events)}n'
return cal
I’m almost there…except it’s printing an additional number from the calendar format…I’m also still trying to get the month and date and year to print out in a reasonable format.
There’s a library named datetimerange . When an event is created, you can get the start and end datetime (which you are already doing), and pass it into the DateTimeRange function and store it as a string in DB.
When you get the event back. You can get the datetime values again. Here’s a piece of code from the documentation of library:
import datetime
from datetimerange import DateTimeRange
time_range = DateTimeRange("2015-01-01T00:00:00+0900", "2015-01-04T00:00:00+0900")
for value in time_range.range(datetime.timedelta(days=1)):
print(value)
I would calculate the duration (number of days), and then use that duration as the number of times to loop. Then to get the date for each day of the duration, I would use a counter variable (enumerator maybe) and add it to the date. For example when the counter is 2, then I would add 2 days to the start date to get the current date in the loop.
Hopefully this would give you an idea.
Using your example, add this to your view:
from datetime import timedelta, date
# Add this function outside the View, just like you did with the other ones
def daterange(date1, date2):
for n in range(int ((date2 - date1).days)+1):
yield date1 + timedelta(n)
def get_context_data(self, **kwargs):
## other code here ##
vacation_calendar_requests = VacationRequest.objects.filter(start_time__year = d.year,start_time__month = d.month)
# this will add a new field, "dates", to each event
for request in vacation_calendar_requests:
start_date = request.start_time
end_date = request.end_time
start_dt = date(start_date.year,start_date.month,start_date.day) # not sure if this is how you get your event start date
end_dt = date(end_date.year,end_date.month,end_date.day) # not sure if this is how you get your event end date
request.dates = [date for date in daterange(start_dt, end_dt)]
## rest of code here ##
Then in your template, add this nested loop to show the dates for each event:
{% for date in request.dates %}
{{ request.vacation_request_name }}
{{ date }} /* format the date accordingly */
{% endfor %}