helpers.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import datetime
  2. from django.utils.translation import gettext as _
  3. from django.utils.html import escape
  4. from .models import Participant, Date, Event
  5. def slots2string( participant : Participant | None, n_slots : int ) -> str :
  6. """ Convert the slots of a participant to a string. """
  7. if not participant:
  8. return '0' * n_slots
  9. # Get the slots of the participant
  10. byte_array = participant.slots
  11. # Convert the slots to a string
  12. string_value = bin(int.from_bytes(byte_array, byteorder='big'))[2:]
  13. # Pad the string with 0s if necessary
  14. return '0' * (n_slots - len(string_value)) + string_value
  15. def string2slots( string : str, n_slots : int ) -> bytes :
  16. """ Convert a string to a byte array. """
  17. if len(string) != n_slots:
  18. raise ValueError(_("Invalid string length: {0} (expected: {1})").format(len(string), n_slots))
  19. # Convert the string to a byte array
  20. return int(string, 2).to_bytes((n_slots + 7) // 8, byteorder='big')
  21. def get_slot_count( event ) -> int :
  22. """ Get the number of slots in an event. """
  23. # Get the timespan of the event
  24. start_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
  25. end_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
  26. if(start_time>end_time):
  27. end_time += datetime.timedelta(days=1)
  28. timespan = end_time - start_time
  29. # Get the number of slots in the event
  30. days = event.date_set.count()
  31. slots_per_day = int(timespan.total_seconds() // event.slot_interval.total_seconds())
  32. return days * slots_per_day
  33. def slots2grid( event : Event, participants : list[Participant], is_input : bool) -> dict :
  34. """ Convert the slots of an event to data for the grid. """
  35. # Get the number of slots in the event
  36. n_slots = get_slot_count(event)
  37. # Get the timespan of the event
  38. start_time = datetime.datetime.combine(datetime.date.today(), event.start_time)
  39. end_time = datetime.datetime.combine(datetime.date.today(), event.end_time)
  40. if(start_time>end_time):
  41. end_time += datetime.timedelta(days=1)
  42. timespan = end_time - start_time
  43. # Get the slots in a day
  44. slots_per_day = int(timespan.total_seconds() // event.slot_interval.total_seconds())
  45. # Get the slots of each day
  46. participant_slot_strings = [slots2string(participant, n_slots) for participant in participants]
  47. if is_input:
  48. html = []
  49. for n, date in reversed(list(enumerate(event.date_set.all()))):
  50. day_offset = n * slots_per_day
  51. dt = datetime.datetime.combine(date.date, datetime.time())
  52. html += f'<div class="slot-column"><div class="day">{ dt.strftime("%b %d") }</div>'
  53. slots = []
  54. # Fill the slots of the day
  55. for j in range(slots_per_day):
  56. slot_begin = (start_time + j * event.slot_interval).strftime('%H:%M')
  57. slot_end = (start_time + (j+1) * event.slot_interval).strftime('%H:%M')
  58. current_time = start_time + j * event.slot_interval
  59. if current_time.minute == 0:
  60. time_label = f'<div class=\"time-label\">{current_time.strftime("%H:%M")}</div>'
  61. classes = " full-hour"
  62. elif current_time.minute == 30:
  63. time_label = ""
  64. classes = " half-hour"
  65. else:
  66. time_label = ""
  67. classes = ""
  68. checked = ""
  69. for i in range(len(participant_slot_strings)):
  70. if participant_slot_strings[i][day_offset + j] == '1':
  71. checked = 'checked'
  72. break
  73. slots.append(
  74. f'<div class="slot{classes}" title="{slot_begin} - {slot_end}">{ time_label }<input class="checkable" type="checkbox" id="slot_picker_{ day_offset + j }" name="slot_{day_offset + j}" { checked } /><label class="checkable" for="slot_picker_{day_offset + j}"></label></div>')
  75. html.append(''.join(slots))
  76. html.append('</div>')
  77. return "".join(html)
  78. else:
  79. max_occupancy = 1
  80. html = []
  81. for n, date in reversed(list(enumerate(event.date_set.all()))):
  82. day_offset = n * slots_per_day
  83. dt = datetime.datetime.combine(date.date, datetime.time())
  84. html.append(f'<div class="slot-column"><div class="day">{ dt.strftime("%b %d") }</div>')
  85. slots = []
  86. # Get participants for each slot
  87. slot_participants = [[] for i in range(slots_per_day)]
  88. for j in range(slots_per_day):
  89. for i in range(len(participant_slot_strings)):
  90. if participant_slot_strings[i][day_offset + j] == '1':
  91. slot_participants[j].append(i)
  92. # Fill the slots of the day
  93. for j, ps in enumerate(slot_participants):
  94. slot_begin = (start_time + j * event.slot_interval).strftime('%H:%M')
  95. slot_end = (start_time + (j+1) * event.slot_interval).strftime('%H:%M')
  96. current_time = start_time + j * event.slot_interval
  97. if current_time.minute == 0:
  98. time_label = f'<div class=\"time-label\">{current_time.strftime("%H:%M")}</div>'
  99. classes = " full-hour"
  100. elif current_time.minute == 30:
  101. time_label = ""
  102. classes = " half-hour"
  103. else:
  104. time_label = ""
  105. classes = ""
  106. slots.append(
  107. f'<div class="slot{classes}" id="grid_slot_{ day_offset + j }" title="{slot_begin} - {slot_end} \n{escape(", ".join([participants[p].name for p in ps]))}" style="--color-index:{ len(ps) }">{ time_label }</div>')
  108. max_occupancy = max(max_occupancy, len(ps))
  109. html.append(''.join(slots))
  110. html.append('</div>')
  111. return f'<div class="occupancy-grid" style="--color-count:{ max_occupancy }">{"".join(html)}</div>'