registration types must be picked. If the attendee does not have one
of these registration types, they may be offered an upsell to a
different registration type if that is [enabled](#regtypes).
+Note that if both a required regtype and a required
+attendee is specified, both requirements must be fulfilled.
+
+Requires attendee
+: In order to add one of these options, the attendee must be one of
+the specified ones. Note that if both a required regtype and a required
+attendee is specified, both requirements must be fulfilled.
Mutually exclusive
: This option cannot be picked at the same time as the selected other
super(ConferenceAdditionalOptionAdminForm, self).__init__(*args, **kwargs)
try:
self.fields['requires_regtype'].queryset = RegistrationType.objects.filter(conference=self.instance.conference)
+ self.fields['requires_attendee'].queryset = ConferenceRegistration.objects.filter(conference=self.instance.conference)
self.fields['mutually_exclusive'].queryset = ConferenceAdditionalOption.objects.filter(conference=self.instance.conference)
self.fields['additionaldays'].queryset = RegistrationDay.objects.filter(conference=self.instance.conference)
except Conference.DoesNotExist:
list_filter = ['conference', ]
ordering = ['conference', 'name', ]
search_fields = ['name', ]
- filter_horizontal = ('requires_regtype', 'mutually_exclusive', )
+ filter_horizontal = ('requires_regtype', 'requires_attendee', 'mutually_exclusive', )
form = ConferenceAdditionalOptionAdminForm
def get_queryset(self, request):
})
vat_fields = {'cost': 'reg'}
auto_cascade_delete_to = ['registrationtype_requires_option', 'conferenceadditionaloption_requires_regtype',
+ 'conferenceadditionaloption_requires_attendee',
'conferenceadditionaloption_mutually_exclusive', ]
+ selectize_multiple_fields = {
+ 'requires_attendee': RegisteredUsersLookup(None),
+ }
coltypes = {
'Maxcount': ['nosearch', ],
}
class Meta:
model = ConferenceAdditionalOption
fields = ['name', 'cost', 'maxcount', 'sortkey', 'public', 'upsellable', 'invoice_autocancel_hours',
- 'requires_regtype', 'mutually_exclusive', 'additionaldays']
+ 'requires_regtype', 'requires_attendee', 'mutually_exclusive', 'additionaldays']
def fix_fields(self):
self.fields['requires_regtype'].queryset = RegistrationType.objects.filter(conference=self.conference)
+ self.fields['requires_attendee'].queryset = ConferenceRegistration.objects.filter(conference=self.conference)
+ self.selectize_multiple_fields['requires_attendee'] = RegisteredUsersLookup(self.conference)
self.fields['mutually_exclusive'].queryset = ConferenceAdditionalOption.objects.filter(conference=self.conference).exclude(pk=self.instance.pk)
self.fields['additionaldays'].queryset = RegistrationDay.objects.filter(conference=self.conference)
regtype = cleaned_data['regtype']
errs = []
for ao in cleaned_data['additionaloptions']:
- if ao.requires_regtype.exists():
- if regtype not in ao.requires_regtype.all():
- errs.append('Additional option "%s" requires one of the following registration types: %s.' % (ao.name, ", ".join(x.regtype for x in ao.requires_regtype.all())))
- if len(errs):
- self._errors['additionaloptions'] = self.error_class(errs)
+ if msg := ao.verify_available_to(regtype, self.instance if self.instance.pk else None):
+ self.add_error('additionaloptions', msg)
return cleaned_data
--- /dev/null
+# Generated by Django 4.2.11 on 2025-09-15 12:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('confreg', '0118_registrationtype_checkinmessage'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='conferenceadditionaloption',
+ name='requires_attendee',
+ field=models.ManyToManyField(blank=True, help_text='Can only be picked by specific attendees', to='confreg.conferenceregistration', verbose_name='Requires specific attendee'),
+ ),
+ ]
upsellable = models.BooleanField(null=False, blank=False, default=True, help_text='Can this option be purchased after the registration is completed')
invoice_autocancel_hours = models.IntegerField(blank=True, null=True, validators=[MinValueValidator(1), ], verbose_name="Autocancel invoices", help_text="Automatically cancel invoices after this many hours")
requires_regtype = models.ManyToManyField(RegistrationType, blank=True, verbose_name="Requires registration type", help_text='Can only be picked with selected registration types')
+ requires_attendee = models.ManyToManyField('ConferenceRegistration', blank=True, verbose_name="Requires specific attendee", help_text='Can only be picked by specific attendees')
mutually_exclusive = models.ManyToManyField('self', blank=True, help_text='Mutually exlusive with these additional options', symmetrical=True)
additionaldays = models.ManyToManyField(RegistrationDay, blank=True, verbose_name="Adds access to days", help_text='Adds access to additional conference day(s), even if the registration type does not')
sortkey = models.IntegerField(default=100, null=False, blank=False, verbose_name="Sort key")
self.maxcount)
return "%s%s" % (self.name, coststr)
+ def verify_available_to(self, regtype, reg):
+ if self.requires_regtype.exists() and not self.requires_regtype.filter(pk=regtype.pk).exists():
+ return 'Option "{}" requires one of the registration types {}'.format(self.name, ", ".join(x.regtype for x in self.requires_regtype.all()))
+ if self.requires_attendee.exists():
+ if reg and not self.requires_attendee.filter(pk=reg.pk).exists():
+ return 'Option "{}" is only available to specific attendees'.format(self.name)
+ elif not reg:
+ return 'Option "{}" is only available to specific attendees'.format(self.name)
+ return None
+
class BulkPayment(models.Model):
# User that owns this bulk payment
else:
upsell_cost = 0
+ # Check that the options is available at all, given the combinations
+ for ao in options:
+ if msg := ao.verify_available_to(new_regtype if new_regtype else reg.regtype, reg):
+ messages.warning(request, msg)
+ return HttpResponseRedirect('../')
+
# Build our invoice rows
invoicerows = []
autocancel_hours = [conference.invoice_autocancel_hours, ]