Events

Flow for (un)registration

Upon registration

In the RegistrationCreateAndUpdateSerializer we pull the current user from the validated data, and the event from the view, through self.context['view'].kwargs['event_pk']. The user is sent as the argument to event.register(user).

In the register-method we try to find the optimal pool for the user, if he’s able to register for the event. There are three possible outcomes:

  1. The user does not have permission to join any pools, and an exception is raised.

  2. The user is added to the waiting list, along with any pools he is able to join.

  3. The user is added to a pool, and the registration is successful.

First we iterate over all the pools in the event to build a list of pools the user is able to join. If the list is empty, the user is not allowed to register for the event, and we raise an exception. If the event only has one pool, and the user is able to join it, we can simply check if the pool is full and register the user accordingly. If it’s full a Registration is created, with no pool, and the list of possible pools as waiting_pools. If it’s not full a Registration is created, with the only pool as the pool.

If the event is merged, the situation is similar: we simply check if the event is full, and register the user accordingly. The user is put in the first possible pool, since pools don’t matter post-merge.

If the event isn’t merged we build a list of full pools by popping full pools from the list of possible pools. This is done in a helper-method. There are three possible outcomes:

  1. The list of possible pools is empty.
    • All available pools are full, and from the POV of the user the event is full.

    • The user is registered in the waiting list.

    • The list of full pools is used as the list of pools that the user is waiting for.

  2. There is only one possible pool left.
    • The user is registered for this pool.

  3. There are several pools left
    • The search continues…

The “algorithm” now builds a dictionary where key=pool and value=total users who can join the pool, because we are trying to find the most exclusive pool. If there are several pools with the same exclusivity, the one with the highest capacitiy is chosen. This is done through 3 helper-methods, and the user is now registered.

Upon unregistration

In the unregistration-method the registration belonging to the user is acquired, it’s current pool is stored in a temporary variable, and the fields pool, waiting_list and waiting_pool are cleared. The unregistration date is set, and it is saved.

Then we call check_for_bump(pool), where pool = the temporary pool from earlier. Here we check if there’s room for a bump, and if so we call the bump-method. The from_pool-argument is the pool we unregistered from earlier. If the event is merged, we ignore the pool.

In bump we pop the first person in the waiting list. If from_pool is not None, the first person who can join that pool is popped. This registration has it’s waiting_list, waiting_pool and unregistration_date cleared, and it’s pool set to either from_pool or the first pool that the user can join.

Models

class lego.apps.events.models.Event(*args, **kwargs)

Bases: lego.apps.content.models.Content, lego.utils.models.BasisModel, lego.apps.permissions.models.ObjectPermissionsModel

An event has a type (e.g. Company presentation, Party. Eventually, each type of event might have slightly different ‘requirements’ or fields. For example, a company presentation will be connected to a company from our company database.

An event has between 1 and X pools, each with their own capacity, to separate users based on groups. At merge_time all pools are combined into one.

An event has a waiting list, filled with users who register after the event is full.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

active_capacity

Calculation capacity of pools that are active.

add_to_waiting_list(user)

Adds a user to the waiting list.

Parameters

user – The user that will be registered to the waiting list.

Returns

A registration for the waiting list, with pool=null

admin_register(admin_user, user, admin_registration_reason, pool=None, feedback='')

Used to force registration for a user, even if the event is full or if the user isn’t allowed to register.

Parameters
  • user – The user who will be registered

  • pool – What pool the registration will be created for

  • feedback – Feedback to organizers

Returns

The registration

bump(to_pool=None)

Pops the appropriate registration from the waiting list, and moves the registration from the waiting list to to pool.

Parameters

to_pool – A pool with a free slot. If the event is merged, this will be null.

bump_on_pool_creation_or_expansion()

Used when a pool’s capacity is expanded or a new pool is created, so that waiting registrations are bumped before anyone else can fill the open spots. This is done on event update.

This method does the same as early_bump, but only accepts people that can be bumped now, not people that can be bumped in the future. :return:

check_for_bump_or_rebalance(open_pool)

Checks if there is an available spot in the event. If so, and the event is merged, bumps the first person in the waiting list. If the event isn’t merged, bumps the first user in the waiting list who is able to join open_pool. If no one is waiting for open_pool, check if anyone is waiting for any of the other pools and attempt to rebalance.

NOTE: Remember to lock the event using select_for_update!

Parameters

open_pool – The pool where the unregistration happened.

early_bump(opening_pool)

Used when bumping users from waiting list to a pool that is about to be activated, using an async task. This is done to make sure these existing registrations are given the spot ahead of users that register at activation time. :param opening_pool: The pool about to be activated. :return:

number_of_registrations

Registration count guaranteed not to include unregistered users.

pop_from_waiting_list(to_pool=None)

Pops the first user in the waiting list that can join to_pool. If from_pool=None, pops the first user in the waiting list overall.

Parameters

to_pool – The pool we are bumping to. If post-merge, there is no pool.

Returns

The registration that is first in line for said pool.

rebalance_pool(from_pool, to_pool)

Iterates over registrations in a full pool, and checks if they can be moved to the open pool. If possible, moves a registration and calls bump(from_pool).

Parameters
  • from_pool – A full pool with waiting registrations.

  • to_pool – A pool with one open slot.

Returns

Boolean, whether or not bump() has been called.

register(registration)

Evaluates a pending registration for the event, and automatically selects the optimal pool for the registration.

First checks if there exist any legal pools for the pending registration, raises an exception if not.

If there is only one possible pool, checks if the pool is full and registers for the waiting list or the pool accordingly.

If the event is merged, and it isn’t full, joins any pool. Otherwise, joins the waiting list.

If the event isn’t merged, checks if the pools that the pending registration can possibly join are full or not. If all are full, a registration for the waiting list is created. If there’s only one pool that isn’t full, register for it.

If there’s more than one possible pool that isn’t full, calculates the total amount of users that can join each pool, and selects the most exclusive pool. If several pools have the same exclusivity, selects the biggest pool of these.

Parameters

registration – The registration that gets evaluated

Returns

The registration (in the chosen pool)

registration_count

Prefetch friendly counting of registrations for an event.

restricted_lookup()

Restricted Mail

save(*args, **kwargs)

By re-setting the pool counters on save, we can ensure that counters are updated if an event that has been merged gets un-merged. We want to avoid having to increment counters when registering after merge_time for performance reasons

total_capacity

Prefetch friendly calculation of the total possible capacity of the event.

try_to_rebalance(open_pool)

Pull the top waiting registrations for all pools, and try to move users in the pools they are waiting for to open_pool so that someone can be bumped.

Parameters

open_pool – The pool where the unregistration happened.

unregister(registration, updated_by=None, admin_unregistration_reason='')

Pulls the registration, and clears relevant fields. Sets unregistration date. If the user was in a pool, and not in the waiting list, notifies the waiting list that there might be a bump available.

class lego.apps.events.models.Pool(*args, **kwargs)

Bases: lego.utils.models.BasisModel

Pool which keeps track of users able to register from different grades/groups.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

class lego.apps.events.models.Registration(*args, **kwargs)

Bases: lego.utils.models.BasisModel

A registration for an event. Can be connected to either a pool or a waiting list.

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

save(*args, **kwargs)

Save the current instance. Override this in a subclass if you want to control the saving process.

The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

Wrap this method in a transaction

set_presence(presence)

Wrap this method in a transaction