bornhack-website/src/program/utils.py

305 lines
11 KiB
Python
Raw Normal View History

SpeakerAvailability, EventSession, autoscheduler, and other goodies (#497) * fix old bug where the get_days() method would return the wrong number of days, this was not discovered because our bootstrap script has been creating 9 day camps instead of 8 day camps (this has been fixed in a different commit) * remove stray debug print * output camp days in local timezone (CEST usually), not UTC * speakeravailability commit of doom, originally intended for #385 but goes a bit further than that. Adds SpeakerAvailability and EventSession models, and models for the new autoscheduler. Update bootstrap script and more. New conference_autoscheduler dependency. Work in progress, but ready for playing around! * add conference-scheduler to requirements * rework migrations, work at bit with postgres range fields and bounds, change how speakeravailability is saved (continuous ranges instead of 1 hour chunks), add tests for utils/range_fields.py including adding hypothesis to requirements/dev.txt, add a test which runs our bootstrap script * catch name collision in the right place, and load missing postgres extension in the migration * add some verbosity to see what the travis issue might be * manually create btree_gist extension in postgres, not sure why the BtreeGistExtension() operation in program/migrations/0085... isn't working in travis? * create extension in the right database maybe * lets try this then * ok so the problem is not that the btree_gist extension isn't getting loaded, the problem is that GIST indexes do not work with uuid fields in postgres 9.6, lets take another stab at getting pg10 with postgis to work with in travis * lets try normal socket connection * add SPEAKER_AVAILABILITY_DAYCHUNK_HOURS=3 to travis environment_settings.py * rework migrations, change so an autoschedule can work with multiple eventtypes, change AutoSlot model to use a DateTimeRangeField so we can use the database for more efficient lookups, add 'conflicts' self m2m for EventLocation to indicate when a room conflicts with another room, add a support_autoscheduling bool to EventType, add workshops to bootstrap script, add timing output to bootstrap script * update README a bit, move some functionality to model methods, update jquery and jquery.datatables, include datatables in base.html instead of in each page, start adding backoffice schedule management views (unfinished), yolo commit so I can show valberg something * Switch to a more simple way of using the autoscheduler, meaning we can remove the whole autoscheduler app and all models. All autoscheduler code is now in program/autoscheduler.py and a bit in backoffice views. Add more backoffice CRUD views for schedule management. Add datatables moment.js plugin to help table sorting of dates. Add Speaker{Proposal}EventConflict model to allow speakers to inform us which events they want to attend so we dont schedule them at the same time. Add EventTag model. New models not hooked up to anything yet. * handle cases where there is no solution without failing, also dont return anything here * wrong block kiddo * switch from EventInstance to EventSlot as the way we schedule events. Finish backoffice content team views (mostly). Many small changes. Prod will need data migration of EventInstances -> EventSlots when the time comes. * keep speakeravailability stuff a bit more DRY by using the AvailabilityMatrixViewMixin everywhere, add event_duration_minutes to EventSession create/update form, reverse the order we delete/create EventSlot objects when updating an EventSession * go through all views, fix various little bugs here and there * add missing migration * add django-taggit, add tags for Events, add tags in bootstrap script, make AutoScheduler use tags. Add tags in forms and templates. * fix taggit entry in requirements * Fix our iCal view: Add uuid field to Event, add uuid property to EventSlot which calculates a consitent UUID for an event at a start time at a location. Use this as the schedule uuid. While here fix so our iCal export is valid, a few fields were missing, the iCal file now validates 100% OK. * fix our FRAB xml export view * comment the EventSlot.uuid property better * typo in comment * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * Update src/backoffice/templates/autoschedule_debug_events.html Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * add a field to make this form look less weird. No difference in functionality. * remove stray print and refactor this form init a bit * fix ScheduleView * only show slots where all speakers are available when scheduling events manually in backoffice * make event list sortable by video recording column * update description on {speaker|event}proposal models reason field * remove badge showing number of scheduled slots for each event in backoffice eventlist. it was unclear what the number meant and it doesn't really fit * remember to consider events in the same location when deciding whether a slot is available or not * add is_available() method to EventLocation, add clean_location() method to EventSlot, call it from EventSlot.clean(), update a bit of text in eventslotunschedule template * fix EventSession.get_available_slots() so it doesnt return busy slots as available, and since this means we can no longer schedule stuff in the lunchbreak lower the number of talks in the bootstrap script a bit so we have a better chance of having a solvable problem * fix the excludefilter in EventSession.get_available_slots() for real this time, also fix an icon and add link in event schedule template in backoffice * show message when no slots are available for manual scheduling in backoffice * add event_conflicts to SpeakerUpdateView form in backoffice * fix link to speaker object in speakerproposal list in backoffice * allow blank tags * make duration validation depend on the eventtype event_duration_minutes if we have one. fix help_text and label and placeholder for all duration fields * allow music acts up to 180 mins in the bootstrap data * fix wrong eventtype name for recreational events in speakerproposalform * stretch the colspan one cell more * save event_conflicts m2m when submitting speaker and event together * form not self, and add succes message * move js function toggleclass() to bornhack.js and rename to toggle_sa_form_class(), function is used in several templates and was missing when submitting combined proposals * move the no-js removal to the top of ready() function This will allow other javascript initialization (eg. DataTable) to see the elements and initialize accordingly (eg. column width for tables) * Fixed problem with event feedback detail view * Fixed problem with event feedback list view * introduce a get_tzrange_days() function and use that to get the relevant days for the matrix instead of camp.get_days(), thereby fixing some display issues when eventsessions cross dates * show submitting user and link to proposal on backoffice event detail page, change User to Submitter in backoffice speaker list table * show warning by the buttons when a proposal cannot be approved, and show better text on approve/reject buttons * disable js schedule, save m2m, prefetch some stuff * fix broken date header in table * remove use of djangos regular slugify function, use the new utils.slugs.unique_slugify() instead Co-authored-by: Thomas Steen Rasmussen <tykling@bornhack.org> Co-authored-by: Benjamin Balder Bach <benjamin@overtag.dk> Co-authored-by: Thomas Flummer <tf@flummer.net>
2020-06-03 19:18:06 +00:00
import datetime
import logging
from collections import OrderedDict
from datetime import timedelta
import pytz
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from psycopg2.extras import DateTimeTZRange
logger = logging.getLogger("bornhack.%s" % __name__)
def get_daychunks(day):
"""
Given a DateTimeTZRange day returns a list of "daychunks" which are
DateTimeTZRanges of length settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS
starting from day.lower. If day.lower is midnight and day.upper is 10 AM and
settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS=2 then a list of 5 daychunks
would be returned.
"""
chunks = []
daychunk = DateTimeTZRange(
day.lower,
day.lower + timedelta(hours=settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS),
)
i = 0
while daychunk.upper < day.upper:
# append this chunk
chunks.append(daychunk)
# increase our counter
i += 1
daychunk = DateTimeTZRange(
day.lower
+ timedelta(hours=settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS * i),
day.lower
+ timedelta(hours=settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS * (i + 1)),
)
# cap the final chunk to be equal to the end of the day
if daychunk.upper > day.upper:
daychunk.upper = day.upper
# append the final chunk and return
chunks.append(daychunk)
return chunks
def get_speaker_availability_form_matrix(sessions):
"""
Create a speaker availability matrix of columns, rows and checkboxes for the HTML form.
Returns a "matrix" - a dict of dicts, where the outer dict keys are DateTimeTZRanges
representing a full camp "day" (as returned by camp.get_days("camp")), and the
value is an OrderedDict of chunks based on settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS
with the daychunk DateTimeTZRange as key and a value which is None if we don't want a checkbox,
or a dict with "fieldname" (string) and "event_types" (list) and "available" (bool) if we do.
For example, with a 2 day camp and settings.SPEAKER_AVAILABILITY_DAYCHUNK_HOURS=12
and 24h EventSessions for both days he matrix dict would have 2 members (one per day),
and each would be an OrderedDict with 2 members (one per 12 hour daychunk).
"""
# start with an empty dict
matrix = OrderedDict()
if not sessions:
return matrix
# loop over days in the camp
for day in get_tzrange_days(tzranges=[s.when for s in sessions]):
# loop over the daychunks in this day
for daychunk in get_daychunks(day):
event_types = set()
for session in sessions:
# add the event_type if this session overlaps with daychunk
if daychunk & session.when:
event_types.add(session.event_type)
# make sure we already have an OrderedDict for this day in the matrix
if day not in matrix:
matrix[day] = OrderedDict()
# skip this chunk if we found no sessions
if event_types:
# build the dict for this daychunk
matrix[day][daychunk] = dict()
matrix[day][daychunk][
"fieldname"
] = f"availability_{daychunk.lower.strftime('%Y_%m_%d_%H_%M')}_to_{daychunk.upper.strftime('%Y_%m_%d_%H_%M')}"
matrix[day][daychunk]["event_types"] = []
# pass a list of dicts instead of the queryset to avoid one million lookups
for et in event_types:
matrix[day][daychunk]["event_types"].append(
{
"name": et.name,
"icon": et.icon,
"color": et.color,
}
SpeakerAvailability, EventSession, autoscheduler, and other goodies (#497) * fix old bug where the get_days() method would return the wrong number of days, this was not discovered because our bootstrap script has been creating 9 day camps instead of 8 day camps (this has been fixed in a different commit) * remove stray debug print * output camp days in local timezone (CEST usually), not UTC * speakeravailability commit of doom, originally intended for #385 but goes a bit further than that. Adds SpeakerAvailability and EventSession models, and models for the new autoscheduler. Update bootstrap script and more. New conference_autoscheduler dependency. Work in progress, but ready for playing around! * add conference-scheduler to requirements * rework migrations, work at bit with postgres range fields and bounds, change how speakeravailability is saved (continuous ranges instead of 1 hour chunks), add tests for utils/range_fields.py including adding hypothesis to requirements/dev.txt, add a test which runs our bootstrap script * catch name collision in the right place, and load missing postgres extension in the migration * add some verbosity to see what the travis issue might be * manually create btree_gist extension in postgres, not sure why the BtreeGistExtension() operation in program/migrations/0085... isn't working in travis? * create extension in the right database maybe * lets try this then * ok so the problem is not that the btree_gist extension isn't getting loaded, the problem is that GIST indexes do not work with uuid fields in postgres 9.6, lets take another stab at getting pg10 with postgis to work with in travis * lets try normal socket connection * add SPEAKER_AVAILABILITY_DAYCHUNK_HOURS=3 to travis environment_settings.py * rework migrations, change so an autoschedule can work with multiple eventtypes, change AutoSlot model to use a DateTimeRangeField so we can use the database for more efficient lookups, add 'conflicts' self m2m for EventLocation to indicate when a room conflicts with another room, add a support_autoscheduling bool to EventType, add workshops to bootstrap script, add timing output to bootstrap script * update README a bit, move some functionality to model methods, update jquery and jquery.datatables, include datatables in base.html instead of in each page, start adding backoffice schedule management views (unfinished), yolo commit so I can show valberg something * Switch to a more simple way of using the autoscheduler, meaning we can remove the whole autoscheduler app and all models. All autoscheduler code is now in program/autoscheduler.py and a bit in backoffice views. Add more backoffice CRUD views for schedule management. Add datatables moment.js plugin to help table sorting of dates. Add Speaker{Proposal}EventConflict model to allow speakers to inform us which events they want to attend so we dont schedule them at the same time. Add EventTag model. New models not hooked up to anything yet. * handle cases where there is no solution without failing, also dont return anything here * wrong block kiddo * switch from EventInstance to EventSlot as the way we schedule events. Finish backoffice content team views (mostly). Many small changes. Prod will need data migration of EventInstances -> EventSlots when the time comes. * keep speakeravailability stuff a bit more DRY by using the AvailabilityMatrixViewMixin everywhere, add event_duration_minutes to EventSession create/update form, reverse the order we delete/create EventSlot objects when updating an EventSession * go through all views, fix various little bugs here and there * add missing migration * add django-taggit, add tags for Events, add tags in bootstrap script, make AutoScheduler use tags. Add tags in forms and templates. * fix taggit entry in requirements * Fix our iCal view: Add uuid field to Event, add uuid property to EventSlot which calculates a consitent UUID for an event at a start time at a location. Use this as the schedule uuid. While here fix so our iCal export is valid, a few fields were missing, the iCal file now validates 100% OK. * fix our FRAB xml export view * comment the EventSlot.uuid property better * typo in comment * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * Update src/backoffice/templates/autoschedule_debug_events.html Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * add a field to make this form look less weird. No difference in functionality. * remove stray print and refactor this form init a bit * fix ScheduleView * only show slots where all speakers are available when scheduling events manually in backoffice * make event list sortable by video recording column * update description on {speaker|event}proposal models reason field * remove badge showing number of scheduled slots for each event in backoffice eventlist. it was unclear what the number meant and it doesn't really fit * remember to consider events in the same location when deciding whether a slot is available or not * add is_available() method to EventLocation, add clean_location() method to EventSlot, call it from EventSlot.clean(), update a bit of text in eventslotunschedule template * fix EventSession.get_available_slots() so it doesnt return busy slots as available, and since this means we can no longer schedule stuff in the lunchbreak lower the number of talks in the bootstrap script a bit so we have a better chance of having a solvable problem * fix the excludefilter in EventSession.get_available_slots() for real this time, also fix an icon and add link in event schedule template in backoffice * show message when no slots are available for manual scheduling in backoffice * add event_conflicts to SpeakerUpdateView form in backoffice * fix link to speaker object in speakerproposal list in backoffice * allow blank tags * make duration validation depend on the eventtype event_duration_minutes if we have one. fix help_text and label and placeholder for all duration fields * allow music acts up to 180 mins in the bootstrap data * fix wrong eventtype name for recreational events in speakerproposalform * stretch the colspan one cell more * save event_conflicts m2m when submitting speaker and event together * form not self, and add succes message * move js function toggleclass() to bornhack.js and rename to toggle_sa_form_class(), function is used in several templates and was missing when submitting combined proposals * move the no-js removal to the top of ready() function This will allow other javascript initialization (eg. DataTable) to see the elements and initialize accordingly (eg. column width for tables) * Fixed problem with event feedback detail view * Fixed problem with event feedback list view * introduce a get_tzrange_days() function and use that to get the relevant days for the matrix instead of camp.get_days(), thereby fixing some display issues when eventsessions cross dates * show submitting user and link to proposal on backoffice event detail page, change User to Submitter in backoffice speaker list table * show warning by the buttons when a proposal cannot be approved, and show better text on approve/reject buttons * disable js schedule, save m2m, prefetch some stuff * fix broken date header in table * remove use of djangos regular slugify function, use the new utils.slugs.unique_slugify() instead Co-authored-by: Thomas Steen Rasmussen <tykling@bornhack.org> Co-authored-by: Benjamin Balder Bach <benjamin@overtag.dk> Co-authored-by: Thomas Flummer <tf@flummer.net>
2020-06-03 19:18:06 +00:00
)
matrix[day][daychunk]["initial"] = None
else:
# no sessions for this chunk, no checkbox needed
matrix[day][daychunk] = None
# Due to the way we build the matrix it is not trivial to avoid adding days
# where none of the chunks need a checkbox. Loop over and remove any days with
# 0 checkboxes before returning
new_matrix = matrix.copy()
for date in matrix.keys():
for chunk in matrix[date].keys():
if matrix[date][chunk]:
# we have at least one checkbox on this date, keep it
break
else:
# we looped over all chunks on this day and we need 0 checkboxes
del new_matrix[date]
return new_matrix
def save_speaker_availability(form, obj):
"""
Called from SpeakerProposalCreateView, SpeakerProposalUpdateView,
and CombinedProposalSubmitView to create SpeakerProposalAvailability
objects based on the submitted form.
Also called from SpeakerUpdateView in backoffice to update
SpeakerAvailability.
Starts out by deleting all existing availability before saving form.
"""
if hasattr(obj, "proposal"):
# obj is a Speaker
AvailabilityModel = apps.get_model("program", "SpeakerAvailability")
kwargs = {"speaker": obj}
else:
# obj is a SpeakerProposal
AvailabilityModel = apps.get_model("program", "SpeakerProposalAvailability")
kwargs = {"speaker_proposal": obj}
# start with a clean slate
AvailabilityModel.objects.filter(**kwargs).delete()
# all the entered data is in the users local TIME_ZONE, interpret it as such
tz = pytz.timezone(settings.TIME_ZONE)
# count availability form fields
fieldcounter = 0
for field in form.cleaned_data.keys():
if field[:13] == "availability_":
fieldcounter += 1
# loop over form fields, and make sure we get them in sorted order
formerchunk = None
fields = list(form.cleaned_data.keys())
fields.sort()
for field in fields:
if field[:13] != "availability_":
continue
# this is a speaker_availability field, first split the
# fieldname to get the tzrange for this daychunk
elements = field.split("_")
# format is "availability_2020_08_28_18_00_to_2020_08_28_21_00"
daychunk = DateTimeTZRange(
tz.localize(
datetime.datetime(
int(elements[1]),
int(elements[2]),
int(elements[3]),
int(elements[4]),
int(elements[5]),
)
),
tz.localize(
datetime.datetime(
int(elements[7]),
int(elements[8]),
int(elements[9]),
int(elements[10]),
int(elements[11]),
)
),
)
available = form.cleaned_data[field]
if fieldcounter == 1:
# we only have one field in the form, no field merging to be done
AvailabilityModel.objects.create(
when=daychunk, available=available, **kwargs
)
continue
# we have more than one form field, but we want to save continuous ranges
# as one SpeakerAvailability object, so we might need to merge this field with
# the next one, so we can't save it yet
if not formerchunk:
# this is the first loop or we changed availability,
# remember the current chunk for the next loop
formerchunk = daychunk
formeravailable = available
continue
# this is not the first chunk
if formeravailable == available and formerchunk.upper == daychunk.lower:
# we have the same value for "available" and adjacent times,
# merge with the former chunk
formerchunk = formerchunk + daychunk
else:
# "available" changed or daychunk is not adjacent to formerchunk
AvailabilityModel.objects.create(
when=formerchunk,
available=formeravailable,
**kwargs,
SpeakerAvailability, EventSession, autoscheduler, and other goodies (#497) * fix old bug where the get_days() method would return the wrong number of days, this was not discovered because our bootstrap script has been creating 9 day camps instead of 8 day camps (this has been fixed in a different commit) * remove stray debug print * output camp days in local timezone (CEST usually), not UTC * speakeravailability commit of doom, originally intended for #385 but goes a bit further than that. Adds SpeakerAvailability and EventSession models, and models for the new autoscheduler. Update bootstrap script and more. New conference_autoscheduler dependency. Work in progress, but ready for playing around! * add conference-scheduler to requirements * rework migrations, work at bit with postgres range fields and bounds, change how speakeravailability is saved (continuous ranges instead of 1 hour chunks), add tests for utils/range_fields.py including adding hypothesis to requirements/dev.txt, add a test which runs our bootstrap script * catch name collision in the right place, and load missing postgres extension in the migration * add some verbosity to see what the travis issue might be * manually create btree_gist extension in postgres, not sure why the BtreeGistExtension() operation in program/migrations/0085... isn't working in travis? * create extension in the right database maybe * lets try this then * ok so the problem is not that the btree_gist extension isn't getting loaded, the problem is that GIST indexes do not work with uuid fields in postgres 9.6, lets take another stab at getting pg10 with postgis to work with in travis * lets try normal socket connection * add SPEAKER_AVAILABILITY_DAYCHUNK_HOURS=3 to travis environment_settings.py * rework migrations, change so an autoschedule can work with multiple eventtypes, change AutoSlot model to use a DateTimeRangeField so we can use the database for more efficient lookups, add 'conflicts' self m2m for EventLocation to indicate when a room conflicts with another room, add a support_autoscheduling bool to EventType, add workshops to bootstrap script, add timing output to bootstrap script * update README a bit, move some functionality to model methods, update jquery and jquery.datatables, include datatables in base.html instead of in each page, start adding backoffice schedule management views (unfinished), yolo commit so I can show valberg something * Switch to a more simple way of using the autoscheduler, meaning we can remove the whole autoscheduler app and all models. All autoscheduler code is now in program/autoscheduler.py and a bit in backoffice views. Add more backoffice CRUD views for schedule management. Add datatables moment.js plugin to help table sorting of dates. Add Speaker{Proposal}EventConflict model to allow speakers to inform us which events they want to attend so we dont schedule them at the same time. Add EventTag model. New models not hooked up to anything yet. * handle cases where there is no solution without failing, also dont return anything here * wrong block kiddo * switch from EventInstance to EventSlot as the way we schedule events. Finish backoffice content team views (mostly). Many small changes. Prod will need data migration of EventInstances -> EventSlots when the time comes. * keep speakeravailability stuff a bit more DRY by using the AvailabilityMatrixViewMixin everywhere, add event_duration_minutes to EventSession create/update form, reverse the order we delete/create EventSlot objects when updating an EventSession * go through all views, fix various little bugs here and there * add missing migration * add django-taggit, add tags for Events, add tags in bootstrap script, make AutoScheduler use tags. Add tags in forms and templates. * fix taggit entry in requirements * Fix our iCal view: Add uuid field to Event, add uuid property to EventSlot which calculates a consitent UUID for an event at a start time at a location. Use this as the schedule uuid. While here fix so our iCal export is valid, a few fields were missing, the iCal file now validates 100% OK. * fix our FRAB xml export view * comment the EventSlot.uuid property better * typo in comment * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * language Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * Update src/backoffice/templates/autoschedule_debug_events.html Co-Authored-By: Benjamin Balder Bach <benjamin@overtag.dk> * add a field to make this form look less weird. No difference in functionality. * remove stray print and refactor this form init a bit * fix ScheduleView * only show slots where all speakers are available when scheduling events manually in backoffice * make event list sortable by video recording column * update description on {speaker|event}proposal models reason field * remove badge showing number of scheduled slots for each event in backoffice eventlist. it was unclear what the number meant and it doesn't really fit * remember to consider events in the same location when deciding whether a slot is available or not * add is_available() method to EventLocation, add clean_location() method to EventSlot, call it from EventSlot.clean(), update a bit of text in eventslotunschedule template * fix EventSession.get_available_slots() so it doesnt return busy slots as available, and since this means we can no longer schedule stuff in the lunchbreak lower the number of talks in the bootstrap script a bit so we have a better chance of having a solvable problem * fix the excludefilter in EventSession.get_available_slots() for real this time, also fix an icon and add link in event schedule template in backoffice * show message when no slots are available for manual scheduling in backoffice * add event_conflicts to SpeakerUpdateView form in backoffice * fix link to speaker object in speakerproposal list in backoffice * allow blank tags * make duration validation depend on the eventtype event_duration_minutes if we have one. fix help_text and label and placeholder for all duration fields * allow music acts up to 180 mins in the bootstrap data * fix wrong eventtype name for recreational events in speakerproposalform * stretch the colspan one cell more * save event_conflicts m2m when submitting speaker and event together * form not self, and add succes message * move js function toggleclass() to bornhack.js and rename to toggle_sa_form_class(), function is used in several templates and was missing when submitting combined proposals * move the no-js removal to the top of ready() function This will allow other javascript initialization (eg. DataTable) to see the elements and initialize accordingly (eg. column width for tables) * Fixed problem with event feedback detail view * Fixed problem with event feedback list view * introduce a get_tzrange_days() function and use that to get the relevant days for the matrix instead of camp.get_days(), thereby fixing some display issues when eventsessions cross dates * show submitting user and link to proposal on backoffice event detail page, change User to Submitter in backoffice speaker list table * show warning by the buttons when a proposal cannot be approved, and show better text on approve/reject buttons * disable js schedule, save m2m, prefetch some stuff * fix broken date header in table * remove use of djangos regular slugify function, use the new utils.slugs.unique_slugify() instead Co-authored-by: Thomas Steen Rasmussen <tykling@bornhack.org> Co-authored-by: Benjamin Balder Bach <benjamin@overtag.dk> Co-authored-by: Thomas Flummer <tf@flummer.net>
2020-06-03 19:18:06 +00:00
)
# and remember the current chunk for next iteration
formerchunk = daychunk
formeravailable = available
# save the last chunk?
if formerchunk:
AvailabilityModel.objects.create(
when=formerchunk, available=available, **kwargs
)
def add_existing_availability_to_matrix(matrix, speaker_proposal):
"""
Loops over the matrix and adds an "intial" member to the daychunk dicts
with the availability info for the speaker_proposal.
This is used to populate initial form field values and to set <td> background
colours in the html table.
speaker_proposal can be either a SpeakerProposal object or a Speaker object.
"""
# loop over dates in the matrix
for date in matrix.keys():
# loop over daychunks and check if we need a checkbox
for daychunk in matrix[date].keys():
if not matrix[date][daychunk]:
# we have no event_session here, carry on
continue
# do we have any availability info for this speakerproposal?
try:
availability = speaker_proposal.availabilities.get(
when__contains=daychunk
)
matrix[date][daychunk]["initial"] = availability.available
except ObjectDoesNotExist:
matrix[date][daychunk]["initial"] = None
def get_slots(period, duration, bounds="()"):
"""
Cuts a DateTimeTZRange into slices of duration minutes length and returns a list of them
"""
slots = []
if period.upper - period.lower < timedelta(minutes=duration):
# this period is shorter than the duration, no slots
return slots
# create the first slot
slot = DateTimeTZRange(
period.lower, period.lower + timedelta(minutes=duration), bounds=bounds
)
# loop until we pass the end
while slot.upper < period.upper:
slots.append(slot)
# the next slot starts when this one ends
slot = DateTimeTZRange(
slot.upper, slot.upper + timedelta(minutes=duration), bounds=bounds
)
# append the final slot to the list unless it continues past the end
if not slot.upper > period.upper:
slots.append(slot)
return slots
def get_tzrange_days(tzranges):
"""Loop over tzranges and build a list of datetimetzranges representing all unique dates (in the local timezone)."""
days = set()
# loop over input ranges
for tzrange in tzranges:
# convert this range to local timezone
localrange = DateTimeTZRange(
timezone.localtime(tzrange.lower), timezone.localtime(tzrange.upper)
)
# find the first date in this range
day = DateTimeTZRange(
localrange.lower.replace(hour=0),
localrange.lower.replace(hour=0) + timedelta(days=1),
)
# add this day to the set
days.add(day)
# does this range spans multiple dates?
if localrange.lower.date() != localrange.upper.date():
# this range spans multiple dates, loop over them
while day.lower.date() <= localrange.upper.date():
day = DateTimeTZRange(day.upper, day.upper + timedelta(days=1))
days.add(day)
days = list(days)
days.sort()
return days