-
Notifications
You must be signed in to change notification settings - Fork 132
(Fix)Immediately trigger waiting list notifications #1275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: enext
Are you sure you want to change the base?
(Fix)Immediately trigger waiting list notifications #1275
Conversation
- Added signal receivers for order_canceled and order_changed events - Trigger waiting list assignment when orders are canceled - Trigger waiting list when quota size is increased - Trigger waiting list when quota is manually reopened - Fixes field name from 'item' to 'product' for compatibility This change ensures waiting list members receive voucher notifications immediately instead of waiting up to 30 minutes for the periodic task. Fixes fossasia#1253
Reviewer's GuideThis PR implements immediate triggers for waiting list assignments by adding signal receivers for order cancellations and modifications, integrating quota reopen hooks in product views, detecting quota size increases in form handling, and refactoring the waiting list processor to use the updated “product” field. Sequence diagram for immediate waiting list trigger on order cancellationsequenceDiagram
actor User
participant "Order Service"
participant "Signal Receiver (on_order_canceled)"
participant "Waiting List Processor"
User->>"Order Service": Cancel order
"Order Service"->>"Signal Receiver (on_order_canceled)": Emit order_canceled signal
"Signal Receiver (on_order_canceled)"->>"Waiting List Processor": assign_automatically.apply_async(event, subevent)
"Waiting List Processor"-->>User: Send waiting list notification email
Sequence diagram for immediate waiting list trigger on quota increase or reopensequenceDiagram
actor Organizer
participant "Quota Management View"
participant "Waiting List Processor"
Organizer->>"Quota Management View": Increase quota or reopen quota
"Quota Management View"->>"Waiting List Processor": assign_automatically.apply_async(event, user, subevent)
"Waiting List Processor"-->>Organizer: Waiting list assignment processed
Class diagram for WaitingListEntry field refactor (item → product)classDiagram
class WaitingListEntry {
+product
+variation
+subevent
+voucher
+priority
+created
}
class Product
class Variation
class Subevent
class Voucher
WaitingListEntry --> Product : product
WaitingListEntry --> Variation : variation
WaitingListEntry --> Subevent : subevent
WaitingListEntry --> Voucher : voucher
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- The on_order_canceled and on_order_changed receivers share almost identical logic; consider extracting the common checks and subevent handling into a shared helper to reduce duplication.
- The waiting list trigger block in control/views/product.py is duplicated in both quota reopening branches; extract it into a single utility or decorator to keep the view code DRY.
- Relying on the view's form_valid to catch quota size increases may miss other update paths—consider moving the quota change detection into a model signal or service layer to centralize the logic.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The on_order_canceled and on_order_changed receivers share almost identical logic; consider extracting the common checks and subevent handling into a shared helper to reduce duplication.
- The waiting list trigger block in control/views/product.py is duplicated in both quota reopening branches; extract it into a single utility or decorator to keep the view code DRY.
- Relying on the view's form_valid to catch quota size increases may miss other update paths—consider moving the quota change detection into a model signal or service layer to centralize the logic.
## Individual Comments
### Comment 1
<location> `app/eventyay/base/services/waitinglist.py:135-136` </location>
<code_context>
+ assign_automatically.apply_async(args=(event.pk, None, subevent_id))
+
+
+@receiver(signal=order_changed, dispatch_uid='waitinglist_order_changed')
+def on_order_changed(sender, order, **kwargs):
+ """
+ When an order is modified (e.g., positions canceled), immediately trigger
</code_context>
<issue_to_address>
**suggestion:** The logic for subevent assignment is duplicated between order_canceled and order_changed.
Refactor the shared subevent extraction and assignment logic into a helper function to avoid duplication and enhance maintainability.
Suggested implementation:
```python
def trigger_waitinglist_assignment(event, subevents):
"""
Helper to trigger waiting list assignment for main event or subevents.
"""
if not subevents or not event.has_subevents:
assign_automatically.apply_async(args=(event.pk,))
else:
for subevent_id in subevents:
assign_automatically.apply_async(args=(event.pk, None, subevent_id))
```
```python
trigger_waitinglist_assignment(event, subevents)
```
You will need to update the corresponding logic in the `on_order_changed` function to use the new `trigger_waitinglist_assignment` helper.
If the subevent extraction logic is also duplicated in `on_order_changed`, refactor it to use the same approach as the other location and pass the subevents to the helper.
</issue_to_address>
### Comment 2
<location> `app/eventyay/base/services/waitinglist.py:121` </location>
<code_context>
@receiver(signal=order_canceled, dispatch_uid='waitinglist_order_canceled')
def on_order_canceled(sender, order, **kwargs):
"""
When an order is canceled, immediately trigger waiting list assignment
if automatic assignment is enabled for the event.
"""
event = sender
# Check if waiting list auto-assignment is enabled
if not event.settings.get('waiting_list_enabled', as_type=bool):
return
if not event.settings.get('waiting_list_auto', as_type=bool):
return
# Check if event is still selling tickets
if not (event.presale_is_running or event.has_subevents):
return
# Get unique subevents from canceled order positions
subevents = set()
for position in order.positions.all():
if position.subevent:
subevents.add(position.subevent.pk)
# Trigger assignment for the main event
if not subevents or not event.has_subevents:
assign_automatically.apply_async(args=(event.pk,))
else:
# Trigger assignment for each affected subevent
for subevent_id in subevents:
assign_automatically.apply_async(args=(event.pk, None, subevent_id))
</code_context>
<issue_to_address>
**issue (code-quality):** Convert for loop into set comprehension ([`set-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/set-comprehension/))
</issue_to_address>
### Comment 3
<location> `app/eventyay/base/services/waitinglist.py:155` </location>
<code_context>
@receiver(signal=order_changed, dispatch_uid='waitinglist_order_changed')
def on_order_changed(sender, order, **kwargs):
"""
When an order is modified (e.g., positions canceled), immediately trigger
waiting list assignment if automatic assignment is enabled for the event.
"""
event = sender
# Check if waiting list auto-assignment is enabled
if not event.settings.get('waiting_list_enabled', as_type=bool):
return
if not event.settings.get('waiting_list_auto', as_type=bool):
return
# Check if event is still selling tickets
if not (event.presale_is_running or event.has_subevents):
return
# Get unique subevents from order positions
subevents = set()
for position in order.positions.all():
if position.subevent:
subevents.add(position.subevent.pk)
# Trigger assignment for the main event
if not subevents or not event.has_subevents:
assign_automatically.apply_async(args=(event.pk,))
else:
# Trigger assignment for each affected subevent
for subevent_id in subevents:
assign_automatically.apply_async(args=(event.pk, None, subevent_id))
</code_context>
<issue_to_address>
**issue (code-quality):** Convert for loop into set comprehension ([`set-comprehension`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/set-comprehension/))
</issue_to_address>
### Comment 4
<location> `app/eventyay/control/views/product.py:1047` </location>
<code_context>
def post(self, request, *args, **kwargs):
if not request.user.has_event_permission(request.organizer, request.event, 'can_change_items', request):
raise PermissionDenied()
quota = self.get_object()
if 'reopen' in request.POST:
quota.closed = False
quota.save(update_fields=['closed'])
quota.log_action('pretix.event.quota.opened', user=request.user)
messages.success(request, _('The quota has been re-opened.'))
# Trigger waiting list assignment when quota is reopened
event = request.event
if event.settings.get('waiting_list_enabled', as_type=bool) and \
event.settings.get('waiting_list_auto', as_type=bool) and \
(event.presale_is_running or event.has_subevents):
from eventyay.base.services.waitinglist import assign_automatically
if quota.subevent:
assign_automatically.apply_async(args=(event.pk, request.user.pk, quota.subevent.pk))
else:
assign_automatically.apply_async(args=(event.pk, request.user.pk))
if 'disable' in request.POST:
quota.closed = False
quota.close_when_sold_out = False
quota.save(update_fields=['closed', 'close_when_sold_out'])
quota.log_action('pretix.event.quota.opened', user=request.user)
quota.log_action(
'pretix.event.quota.changed',
user=self.request.user,
data={'close_when_sold_out': False},
)
messages.success(request, _('The quota has been re-opened and will not close again.'))
# Trigger waiting list assignment when quota is reopened
event = request.event
if event.settings.get('waiting_list_enabled', as_type=bool) and \
event.settings.get('waiting_list_auto', as_type=bool) and \
(event.presale_is_running or event.has_subevents):
from eventyay.base.services.waitinglist import assign_automatically
if quota.subevent:
assign_automatically.apply_async(args=(event.pk, request.user.pk, quota.subevent.pk))
else:
assign_automatically.apply_async(args=(event.pk, request.user.pk))
return redirect(
reverse(
'control:event.products.quotas.show',
kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
'quota': quota.pk,
},
)
)
</code_context>
<issue_to_address>
**issue (code-quality):** We've found these issues:
- Extract duplicate code into method ([`extract-duplicate-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-duplicate-method/))
- Extract code out into method ([`extract-method`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/extract-method/))
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| @receiver(signal=order_changed, dispatch_uid='waitinglist_order_changed') | ||
| def on_order_changed(sender, order, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: The logic for subevent assignment is duplicated between order_canceled and order_changed.
Refactor the shared subevent extraction and assignment logic into a helper function to avoid duplication and enhance maintainability.
Suggested implementation:
def trigger_waitinglist_assignment(event, subevents):
"""
Helper to trigger waiting list assignment for main event or subevents.
"""
if not subevents or not event.has_subevents:
assign_automatically.apply_async(args=(event.pk,))
else:
for subevent_id in subevents:
assign_automatically.apply_async(args=(event.pk, None, subevent_id)) trigger_waitinglist_assignment(event, subevents)You will need to update the corresponding logic in the on_order_changed function to use the new trigger_waitinglist_assignment helper.
If the subevent extraction logic is also duplicated in on_order_changed, refactor it to use the same approach as the other location and pass the subevents to the helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds immediate triggers for waiting list processing to improve responsiveness when capacity becomes available. Previously, waiting list assignments were only processed periodically, but now they are triggered instantly in three key scenarios: order cancellations, order position modifications, and quota increases/reopening.
- Signal receivers added to trigger waiting list processing when orders are canceled or modified
- Quota reopening and size increases now immediately trigger waiting list assignment
- Field references updated from
itemtoproductfor schema compatibility
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| app/eventyay/base/services/waitinglist.py | Adds signal receivers for order_canceled and order_changed events to trigger immediate waiting list assignment; updates field references from item to product |
| app/eventyay/control/views/product.py | Adds waiting list triggers when quotas are reopened or their size is increased in QuotaView.post() and QuotaUpdate.form_valid() |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @receiver(signal=order_canceled, dispatch_uid='waitinglist_order_canceled') | ||
| def on_order_canceled(sender, order, **kwargs): | ||
| """ | ||
| When an order is canceled, immediately trigger waiting list assignment | ||
| if automatic assignment is enabled for the event. | ||
| """ | ||
| event = sender | ||
|
|
||
| # Check if waiting list auto-assignment is enabled | ||
| if not event.settings.get('waiting_list_enabled', as_type=bool): | ||
| return | ||
|
|
||
| if not event.settings.get('waiting_list_auto', as_type=bool): | ||
| return | ||
|
|
||
| # Check if event is still selling tickets | ||
| if not (event.presale_is_running or event.has_subevents): | ||
| return | ||
|
|
||
| # Get unique subevents from canceled order positions | ||
| subevents = set() | ||
| for position in order.positions.all(): | ||
| if position.subevent: | ||
| subevents.add(position.subevent.pk) | ||
|
|
||
| # Trigger assignment for the main event | ||
| if not subevents or not event.has_subevents: | ||
| assign_automatically.apply_async(args=(event.pk,)) | ||
| else: | ||
| # Trigger assignment for each affected subevent | ||
| for subevent_id in subevents: | ||
| assign_automatically.apply_async(args=(event.pk, None, subevent_id)) | ||
|
|
||
|
|
||
| @receiver(signal=order_changed, dispatch_uid='waitinglist_order_changed') | ||
| def on_order_changed(sender, order, **kwargs): | ||
| """ | ||
| When an order is modified (e.g., positions canceled), immediately trigger | ||
| waiting list assignment if automatic assignment is enabled for the event. | ||
| """ | ||
| event = sender | ||
|
|
||
| # Check if waiting list auto-assignment is enabled | ||
| if not event.settings.get('waiting_list_enabled', as_type=bool): | ||
| return | ||
|
|
||
| if not event.settings.get('waiting_list_auto', as_type=bool): | ||
| return | ||
|
|
||
| # Check if event is still selling tickets | ||
| if not (event.presale_is_running or event.has_subevents): | ||
| return | ||
|
|
||
| # Get unique subevents from order positions | ||
| subevents = set() | ||
| for position in order.positions.all(): | ||
| if position.subevent: | ||
| subevents.add(position.subevent.pk) | ||
|
|
||
| # Trigger assignment for the main event | ||
| if not subevents or not event.has_subevents: | ||
| assign_automatically.apply_async(args=(event.pk,)) | ||
| else: | ||
| # Trigger assignment for each affected subevent | ||
| for subevent_id in subevents: | ||
| assign_automatically.apply_async(args=(event.pk, None, subevent_id)) |
Copilot
AI
Nov 14, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The on_order_canceled and on_order_changed functions contain nearly identical code (lines 101-132 and 135-166). This code duplication makes the codebase harder to maintain. Consider extracting the common logic into a shared helper function that both signal receivers can call.
Example refactoring:
def _trigger_waitinglist_for_order(event, order):
"""Helper to trigger waiting list assignment for an order's affected subevents."""
if not event.settings.get('waiting_list_enabled', as_type=bool):
return
if not event.settings.get('waiting_list_auto', as_type=bool):
return
if not (event.presale_is_running or event.has_subevents):
return
subevents = set()
for position in order.positions.all():
if position.subevent:
subevents.add(position.subevent.pk)
if not subevents or not event.has_subevents:
assign_automatically.apply_async(args=(event.pk,))
else:
for subevent_id in subevents:
assign_automatically.apply_async(args=(event.pk, None, subevent_id))
@receiver(signal=order_canceled, dispatch_uid='waitinglist_order_canceled')
def on_order_canceled(sender, order, **kwargs):
_trigger_waitinglist_for_order(sender, order)
@receiver(signal=order_changed, dispatch_uid='waitinglist_order_changed')
def on_order_changed(sender, order, **kwargs):
_trigger_waitinglist_for_order(sender, order)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation works perfectly though, can refactor at another time?...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.
(Fix)Immediately trigger waiting list notifications
This PR adds immediate triggers for waiting list processing in three scenarios:
1. Order Cancellations
When an order is canceled, immediately trigger waiting list assignment via signal receiver.
Files Changed:
app/eventyay/base/services/waitinglist.py- Addedon_order_canceled()receiver2. Order Position Cancellations
When individual positions are canceled (order modified), immediately trigger waiting list.
Files Changed:
app/eventyay/base/services/waitinglist.py- Addedon_order_changed()receiver3. Quota Increases
When organizers increase quota size or reopen closed quotas, immediately trigger waiting list.
Technical Details
Signal Receivers
Quota Increase Detection
Fixed field name from
wle.itemtowle.productthroughoutwaitinglist.pyfor compatibility with current model schema.Email Generated
Fixes #1253
Summary by Sourcery
Implement immediate triggering of automated waiting list assignments on order cancellations/changes and quota updates, and update model references for consistency
New Features:
Enhancements: