Skip to content

Commit

Permalink
Merge pull request #160 from andersinno/ttva-169-reservation-time-bugfix
Browse files Browse the repository at this point in the history
Ttva 169 reservation time bugfix
  • Loading branch information
danjacob-anders authored Oct 5, 2023
2 parents e18df36 + 61c6e63 commit ed41acb
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 102 deletions.
16 changes: 13 additions & 3 deletions resources/models/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .permissions import RESOURCE_GROUP_PERMISSIONS, UNIT_ROLE_PERMISSIONS
from .unit import Unit
from .utils import (
add_days,
create_datetime_days_from_now,
get_translated,
get_translated_name,
Expand Down Expand Up @@ -988,9 +989,18 @@ def get_reservable_max_days_in_advance(self):
)

def get_reservable_before(self):
return create_datetime_days_from_now(
self.get_reservable_max_days_in_advance(), exclude_extra_day=True
)
"""Return the max date up to which you can reserve
the resource.
Should include the number of days equal to
resource max days in advance (or unit max days in advance)
(inclusive).
If max days is None, returns None.
"""
num_days = self.get_reservable_max_days_in_advance()
# add an extra day so we include that day as well
return add_days(num_days + 1) if num_days else None

def get_reservable_min_days_in_advance(self):
return (
Expand Down
242 changes: 149 additions & 93 deletions resources/models/utils.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
import arrow
import base64
import csv
import datetime
import struct
import time
import io
import logging

import arrow
import struct
import time
import xlsxwriter
from django.conf import settings
from django.utils import formats
from django.utils.translation import ungettext
from django.core.mail import EmailMultiAlternatives
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from django.core.mail import EmailMultiAlternatives
from django.utils import formats, timezone
from django.utils.timezone import localtime
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ungettext
from icalendar import Calendar, Event, vDatetime, vGeo, vText
from rest_framework.reverse import reverse
from icalendar import Calendar, Event, vDatetime, vText, vGeo
import xlsxwriter


DEFAULT_LANG = settings.LANGUAGES[0][0]

Expand Down Expand Up @@ -51,13 +48,13 @@ def get_translated(obj, attr):

# Needed for slug fields populating
def get_translated_name(obj):
return get_translated(obj, 'name')
return get_translated(obj, "name")


def generate_id():
t = time.time() * 1000000
b = base64.b32encode(struct.pack(">Q", int(t)).lstrip(b'\x00')).strip(b'=').lower()
return b.decode('utf8')
b = base64.b32encode(struct.pack(">Q", int(t)).lstrip(b"\x00")).strip(b"=").lower()
return b.decode("utf8")


def time_to_dtz(time, date=None, arr=None):
Expand All @@ -66,7 +63,9 @@ def time_to_dtz(time, date=None, arr=None):
if date:
return tz.localize(datetime.datetime.combine(date, time))
elif arr:
return tz.localize(datetime.datetime(arr.year, arr.month, arr.day, time.hour, time.minute))
return tz.localize(
datetime.datetime(arr.year, arr.month, arr.day, time.hour, time.minute)
)
else:
return None

Expand Down Expand Up @@ -94,57 +93,71 @@ def humanize_duration(duration):
"""
hours = duration.days * 24 + duration.seconds // 3600
mins = duration.seconds // 60 % 60
hours_string = ungettext('%(count)d hour', '%(count)d hours', hours) % {'count': hours} if hours else None
mins_string = ungettext('%(count)d minute', '%(count)d minutes', mins) % {'count': mins} if mins else None
return ' '.join(filter(None, (hours_string, mins_string)))
hours_string = (
ungettext("%(count)d hour", "%(count)d hours", hours) % {"count": hours}
if hours
else None
)
mins_string = (
ungettext("%(count)d minute", "%(count)d minutes", mins) % {"count": mins}
if mins
else None
)
return " ".join(filter(None, (hours_string, mins_string)))


notification_logger = logging.getLogger('respa.notifications')
notification_logger = logging.getLogger("respa.notifications")


def send_respa_mail(email_address, subject, body, html_body=None, attachments=None):
if not getattr(settings, 'RESPA_MAILS_ENABLED', False):
if not getattr(settings, "RESPA_MAILS_ENABLED", False):
return

from_address = (getattr(settings, 'RESPA_MAILS_FROM_ADDRESS', None) or
'noreply@%s' % Site.objects.get_current().domain)
from_address = (
getattr(settings, "RESPA_MAILS_FROM_ADDRESS", None)
or "noreply@%s" % Site.objects.get_current().domain
)

notification_logger.info('Sending notification email to %s: "%s"' % (email_address, subject))
notification_logger.info(
'Sending notification email to %s: "%s"' % (email_address, subject)
)

text_content = body
msg = EmailMultiAlternatives(subject, text_content, from_address, [email_address], attachments=attachments)
msg = EmailMultiAlternatives(
subject, text_content, from_address, [email_address], attachments=attachments
)
if html_body:
msg.attach_alternative(html_body, 'text/html')
msg.attach_alternative(html_body, "text/html")
msg.send()


def generate_reservation_csv(reservations):
output = io.StringIO()
csv_writer = csv.writer(output)
headers = [
'Unit',
'Resource',
'Begin time',
'End time',
'Created at',
'User',
'Comments',
'Staff event',
'State',
"Unit",
"Resource",
"Begin time",
"End time",
"Created at",
"User",
"Comments",
"Staff event",
"State",
]
csv_writer.writerow([_(header) for header in headers])

for reservation in reservations:
row_data = [
reservation['unit'],
reservation['resource'],
localtime(reservation['begin']).replace(tzinfo=None),
localtime(reservation['end']).replace(tzinfo=None),
localtime(reservation['created_at']).replace(tzinfo=None),
reservation['user'] if reservation['user'] else '',
reservation['comments'] if reservation['comments'] else '',
reservation['staff_event'],
reservation['state'],
reservation["unit"],
reservation["resource"],
localtime(reservation["begin"]).replace(tzinfo=None),
localtime(reservation["end"]).replace(tzinfo=None),
localtime(reservation["created_at"]).replace(tzinfo=None),
reservation["user"] if reservation["user"] else "",
reservation["comments"] if reservation["comments"] else "",
reservation["staff_event"],
reservation["state"],
]
csv_writer.writerow(row_data)
return output.getvalue()
Expand All @@ -166,46 +179,57 @@ def generate_reservation_xlsx(reservations, exclude_reservation_extra_fields=Fal
:rtype: bytes
"""
from resources.models import Reservation, RESERVATION_EXTRA_FIELDS
from resources.models import RESERVATION_EXTRA_FIELDS, Reservation

output = io.BytesIO()
workbook = xlsxwriter.Workbook(output)
worksheet = workbook.add_worksheet()

headers = [
('Unit', 30),
('Resource', 30),
('Begin time', 15),
('End time', 15),
('Created at', 15),
('User', 30),
('Comments', 30),
('Staff event', 10),
('State', 15),
("Unit", 30),
("Resource", 30),
("Begin time", 15),
("End time", 15),
("Created at", 15),
("User", 30),
("Comments", 30),
("Staff event", 10),
("State", 15),
]

if not exclude_reservation_extra_fields:
for field in RESERVATION_EXTRA_FIELDS:
headers.append((Reservation._meta.get_field(field).verbose_name, 20))

header_format = workbook.add_format({'bold': True})
header_format = workbook.add_format({"bold": True})
for column, header in enumerate(headers):
worksheet.write(0, column, str(_(header[0])), header_format)
worksheet.set_column(column, column, header[1])

date_format = workbook.add_format({'num_format': 'dd.mm.yyyy hh:mm', 'align': 'left'})
date_format = workbook.add_format(
{"num_format": "dd.mm.yyyy hh:mm", "align": "left"}
)
for row, reservation in enumerate(reservations, 1):
worksheet.write(row, 0, reservation['unit'])
worksheet.write(row, 1, reservation['resource'])
worksheet.write(row, 2, localtime(reservation['begin']).replace(tzinfo=None), date_format)
worksheet.write(row, 3, localtime(reservation['end']).replace(tzinfo=None), date_format)
worksheet.write(row, 4, localtime(reservation['created_at']).replace(tzinfo=None), date_format)
if 'user' in reservation:
worksheet.write(row, 5, reservation['user'])
if 'comments' in reservation:
worksheet.write(row, 6, reservation['comments'])
worksheet.write(row, 7, reservation['staff_event'])
worksheet.write(row, 8, reservation['state'])
worksheet.write(row, 0, reservation["unit"])
worksheet.write(row, 1, reservation["resource"])
worksheet.write(
row, 2, localtime(reservation["begin"]).replace(tzinfo=None), date_format
)
worksheet.write(
row, 3, localtime(reservation["end"]).replace(tzinfo=None), date_format
)
worksheet.write(
row,
4,
localtime(reservation["created_at"]).replace(tzinfo=None),
date_format,
)
if "user" in reservation:
worksheet.write(row, 5, reservation["user"])
if "comments" in reservation:
worksheet.write(row, 6, reservation["comments"])
worksheet.write(row, 7, reservation["staff_event"])
worksheet.write(row, 8, reservation["state"])
for i, field in enumerate(RESERVATION_EXTRA_FIELDS, 9):
if field in reservation:
worksheet.write(row, i, reservation[field])
Expand All @@ -221,44 +245,74 @@ def get_object_or_none(cls, **kwargs):


def create_datetime_days_from_now(days_from_now, exclude_extra_day=False):
"""DEPRECATED: use add_days().
If days_from_now is None, returns None.
If exclude_extra_day is True, returns current datetime + number of days,
otherwise returns from start of day + number of days + 1.
"""
if days_from_now is None:
return None
if exclude_extra_day:
return timezone.now() + datetime.timedelta(days=days_from_now)

dt = timezone.now() + datetime.timedelta(days=days_from_now + 1)
dt = dt.replace(hour=0, minute=0, second=0, microsecond=0)
return (
add_days(days_from_now, from_start_of_day=False)
if exclude_extra_day
else add_days(days_from_now + 1, from_start_of_day=True)
)

return dt

def add_days(days_from_now, *, from_start_of_day=True):
"""
Adds days to current time.
If from_start_of_day is True, count from start of today.
"""

now = timezone.now()

if from_start_of_day:
now = now.replace(hour=0, minute=0, second=0, microsecond=0)

return now + datetime.timedelta(days=days_from_now)


def localize_datetime(dt):
return formats.date_format(timezone.localtime(dt), 'DATETIME_FORMAT')
return formats.date_format(timezone.localtime(dt), "DATETIME_FORMAT")


def format_dt_range(language, begin, end):
if language == 'fi':
if language == "fi":
# ma 1.1.2017 klo 12.00
begin_format = r'D j.n.Y \k\l\o G.i'
begin_format = r"D j.n.Y \k\l\o G.i"
if begin.date() == end.date():
end_format = 'G.i'
sep = '–'
end_format = "G.i"
sep = "–"
else:
end_format = begin_format
sep = ' – '

res = sep.join([formats.date_format(begin, begin_format), formats.date_format(end, end_format)])
sep = " – "

res = sep.join(
[
formats.date_format(begin, begin_format),
formats.date_format(end, end_format),
]
)
else:
# default to English
begin_format = r'D j/n/Y G:i'
begin_format = r"D j/n/Y G:i"
if begin.date() == end.date():
end_format = 'G:i'
sep = '–'
end_format = "G:i"
sep = "–"
else:
end_format = begin_format
sep = ''
sep = ""

res = sep.join([formats.date_format(begin, begin_format), formats.date_format(end, end_format)])
res = sep.join(
[
formats.date_format(begin, begin_format),
formats.date_format(end, end_format),
]
)

return res

Expand All @@ -273,14 +327,16 @@ def build_reservations_ical_file(reservations):
event = Event()
begin_utc = timezone.localtime(reservation.begin, timezone.utc)
end_utc = timezone.localtime(reservation.end, timezone.utc)
event['uid'] = 'respa_reservation_{}'.format(reservation.id)
event['dtstart'] = vDatetime(begin_utc)
event['dtend'] = vDatetime(end_utc)
event["uid"] = "respa_reservation_{}".format(reservation.id)
event["dtstart"] = vDatetime(begin_utc)
event["dtend"] = vDatetime(end_utc)
unit = reservation.resource.unit
event['location'] = vText('{} {} {}'.format(unit.name, unit.street_address, unit.address_zip))
event["location"] = vText(
"{} {} {}".format(unit.name, unit.street_address, unit.address_zip)
)
if unit.location:
event['geo'] = vGeo(unit.location)
event['summary'] = vText('{} {}'.format(unit.name, reservation.resource.name))
event["geo"] = vGeo(unit.location)
event["summary"] = vText("{} {}".format(unit.name, reservation.resource.name))
cal.add_component(event)
return cal.to_ical()

Expand All @@ -290,5 +346,5 @@ def build_ical_feed_url(ical_token, request):
Return iCal feed url for given token without query parameters
"""

url = reverse('ical-feed', kwargs={'ical_token': ical_token}, request=request)
return url[:url.find('?')]
url = reverse("ical-feed", kwargs={"ical_token": ical_token}, request=request)
return url[: url.find("?")]
Loading

0 comments on commit ed41acb

Please sign in to comment.