class OrganizerSettingsForm(SettingsForm): auto_fields = [ 'contact_mail', 'imprint_url', 'organizer_info_text', 'event_list_type', 'event_list_availability', 'organizer_homepage_text', 'organizer_link_back', 'organizer_logo_image_large', 'giftcard_length', 'giftcard_expiry_years', 'locales', 'region', 'event_team_provisioning', 'primary_color', 'theme_color_success', 'theme_color_danger', 'theme_color_background', 'theme_round_borders', 'primary_font' ] organizer_logo_image = ExtFileField( label=_('Header image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), max_size=10 * 1024 * 1024, required=False, help_text= _('If you provide a logo image, we will by default not show your organization name ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'can increase the size with the setting below. We recommend not using small details on the picture ' 'as it will be resized on smaller screens.')) favicon = ExtFileField( label=_('Favicon'), ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=1 * 1024 * 1024, help_text= _('If you provide a favicon, we will show it instead of the default pretix icon. ' 'We recommend a size of at least 200x200px to accommodate most devices.' ))
class DisplaySettingsForm(SettingsForm): primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )) ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, help_text= _('If you provide a logo image, we will by default not show your events name and date ' 'in the page header. We will show your logo with a maximal height of 120 pixels.' )) primary_font = forms.ChoiceField( label=_('Font'), choices=[('Open Sans', 'Open Sans')], help_text=_('Only respected by modern browsers.')) frontpage_text = I18nFormField(label=_("Frontpage text"), required=False, widget=I18nTextarea) show_variations_expanded = forms.BooleanField( label=_("Show variations of a product expanded by default"), required=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['primary_font'].choices += [(a, a) for a in get_fonts()]
class DisplaySettingsForm(SettingsForm): primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')) ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"), required=False, help_text=_('If you provide a logo image, we will by default not show your events name and date ' 'in the page header. We will show your logo with a maximal height of 120 pixels.') ) frontpage_text = I18nFormField( label=_("Frontpage text"), required=False, widget=I18nTextarea ) show_variations_expanded = forms.BooleanField( label=_("Show variations of a product expanded by default"), required=False )
class OrganizerSettingsForm(SettingsForm): locales = forms.MultipleChoiceField( choices=settings.LANGUAGES, label=_("Use languages"), widget=forms.CheckboxSelectMultiple, help_text=_('Choose all languages that your organizer homepage should be available in.') ) organizer_homepage_text = I18nFormField( label=_('Homepage text'), required=False, widget=I18nTextarea, help_text=_('This will be displayed on the organizer homepage.') ) organizer_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"), required=False, help_text=_('If you provide a logo image, we will by default not show your organization name ' 'in the page header. We will show your logo with a maximal height of 120 pixels.') ) event_list_type = forms.ChoiceField( label=_('Event overview stile'), choices=( ('list', _('List')), ('calendar', _('Calendar')) ) )
class OrganizerDisplaySettingsForm(SettingsForm): primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')) ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) organizer_homepage_text = I18nFormField( label=_('Homepage text'), required=False, widget=I18nTextarea, help_text=_('This will be displayed on the organizer homepage.') ) organizer_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, help_text=_('If you provide a logo image, we will by default not show your organization name ' 'in the page header. We will show your logo with a maximal height of 120 pixels.') ) event_list_type = forms.ChoiceField( label=_('Default overview style'), choices=( ('list', _('List')), ('calendar', _('Calendar')) ) ) locales = forms.MultipleChoiceField( choices=settings.LANGUAGES, label=_("Use languages"), widget=forms.CheckboxSelectMultiple, help_text=_('Choose all languages that your organizer homepage should be available in.') ) primary_font = forms.ChoiceField( label=_('Font'), choices=[ ('Open Sans', 'Open Sans') ], help_text=_('Only respected by modern browsers.') ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['primary_font'].choices += [ (a, a) for a in get_fonts() ]
def settings_form_fields(self) -> dict: return OrderedDict( list(super().settings_form_fields.items()) + [ ('paper_size', forms.ChoiceField( label=_('Paper size'), choices=( ('A4', 'A4'), ('A5', 'A5'), ('B4', 'B4'), ('B5', 'B5'), ('letter', 'Letter'), ('legal', 'Legal'), ), required=False )), ('orientation', forms.ChoiceField( label=_('Paper orientation'), choices=( ('portrait', _('Portrait')), ('landscape', _('Landscape')), ), required=False )), ('background', ExtFileField( label=_('Background PDF'), ext_whitelist=(".pdf", ), required=False )), ('qr_x', forms.FloatField(label=_('QR-Code x position (mm)'), required=False)), ('qr_y', forms.FloatField(label=_('QR-Code y position (mm)'), required=False)), ('qr_s', forms.FloatField(label=_('QR-Code size (mm)'), required=False)), ('code_x', forms.FloatField(label=_('Ticket code x position (mm)'), required=False)), ('code_y', forms.FloatField(label=_('Ticket code y position (mm)'), required=False)), ('code_s', forms.FloatField(label=_('Ticket code size (mm)'), required=False)), ('name_x', forms.FloatField(label=_('Product name x position (mm)'), required=False)), ('name_y', forms.FloatField(label=_('Product name y position (mm)'), required=False)), ('name_s', forms.FloatField(label=_('Product name size (mm)'), required=False)), ('price_x', forms.FloatField(label=_('Price x position (mm)'), required=False)), ('price_y', forms.FloatField(label=_('Price y position (mm)'), required=False)), ('price_s', forms.FloatField(label=_('Price size (mm)'), required=False)), ('event_x', forms.FloatField(label=_('Event name x position (mm)'), required=False)), ('event_y', forms.FloatField(label=_('Event name y position (mm)'), required=False)), ('event_s', forms.FloatField(label=_('Event name size (mm)'), required=False)), ] )
class InvoiceSettingsForm(SettingsForm): invoice_address_asked = forms.BooleanField( label=_("Ask for invoice address"), required=False) invoice_address_required = forms.BooleanField( label=_("Require invoice address"), required=False, widget=forms.CheckboxInput( attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), ) invoice_name_required = forms.BooleanField( label=_("Require customer name"), required=False, widget=forms.CheckboxInput( attrs={ 'data-checkbox-dependency': '#id_invoice_address_asked', 'data-inverse-dependency': '#id_invoice_address_required' }), ) invoice_address_vatid = forms.BooleanField( label=_("Ask for VAT ID"), help_text= _("Does only work if an invoice address is asked for. VAT ID is not required." ), widget=forms.CheckboxInput( attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), required=False) invoice_include_free = forms.BooleanField( label=_("Show free products on invoices"), help_text=_( "Note that invoices will never be generated for orders that contain only free " "products."), required=False) invoice_numbers_consecutive = forms.BooleanField( label=_("Generate invoices with consecutive numbers"), help_text=_( "If deactivated, the order code will be used in the invoice number." ), required=False) invoice_numbers_prefix = forms.CharField( label=_("Invoice number prefix"), help_text= _("This will be prepended to invoice numbers. If you leave this field empty, your event slug will " "be used followed by a dash. Attention: If multiple events within the same organization use the " "same value in this field, they will share their number range, i.e. every full number will be " "used at most once over all of your events. This setting only affects future invoices." ), required=False, ) invoice_generate = forms.ChoiceField( label=_("Generate invoices"), required=False, choices=( ('False', _('No')), ('admin', _('Manually in admin panel')), ('user', _('Automatically on user request')), ('True', _('Automatically for all created orders')), ('paid', _('Automatically on payment')), ), help_text=_( "Invoices will never be automatically generated for free orders.")) invoice_attendee_name = forms.BooleanField( label=_("Show attendee names on invoices"), required=False) invoice_email_attachment = forms.BooleanField( label=_("Attach invoices to emails"), help_text= _("If invoices are automatically generated for all orders, they will be attached to the order " "confirmation mail. If they are automatically generated on payment, they will be attached to the " "payment confirmation mail. If they are not automatically generated, they will not be attached " "to emails."), required=False) invoice_renderer = forms.ChoiceField(label=_("Invoice style"), required=True, choices=[]) invoice_address_from = forms.CharField( widget=forms.Textarea( attrs={ 'rows': 5, 'placeholder': _('Sample Event Company\n' 'Albert Einstein Road 52\n' '12345 Samplecity') }), required=False, label=_("Your address"), help_text=_( "Will be printed as the sender on invoices. Be sure to include relevant details required in " "your jurisdiction.")) invoice_introductory_text = I18nFormField( widget=I18nTextarea, widget_kwargs={ 'attrs': { 'rows': 3, 'placeholder': _('e.g. With this document, we sent you the invoice for your ticket order.' ) } }, required=False, label=_("Introductory text"), help_text=_( "Will be printed on every invoice above the invoice rows.")) invoice_additional_text = I18nFormField( widget=I18nTextarea, widget_kwargs={ 'attrs': { 'rows': 3, 'placeholder': _('e.g. Thank you for your purchase! You can find more information on the event at ...' ) } }, required=False, label=_("Additional text"), help_text=_( "Will be printed on every invoice below the invoice total.")) invoice_footer_text = I18nFormField( widget=I18nTextarea, widget_kwargs={ 'attrs': { 'rows': 5, 'placeholder': _('e.g. your bank details, legal details like your VAT ID, registration numbers, etc.' ) } }, required=False, label=_("Footer"), help_text= _("Will be printed centered and in a smaller font at the end of every invoice page." )) invoice_language = forms.ChoiceField( widget=forms.Select, required=True, label=_("Invoice language"), choices=[('__user__', _('The user\'s language'))] + settings.LANGUAGES, ) invoice_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, help_text=_( 'We will show your logo with a maximal height and width of 2.5 cm.' )) def __init__(self, *args, **kwargs): event = kwargs.get('obj') super().__init__(*args, **kwargs) self.fields['invoice_renderer'].choices = [ (r.identifier, r.verbose_name) for r in event.get_invoice_renderers().values() ] self.fields['invoice_numbers_prefix'].widget.attrs[ 'placeholder'] = event.slug.upper() + '-'
class OrganizerSettingsForm(SettingsForm): organizer_info_text = I18nFormField( label=_('Info text'), required=False, widget=I18nTextarea, help_text= _('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.' )) event_team_provisioning = forms.BooleanField( label=_('Allow creating a new team during event creation'), help_text= _('Users that do not have access to all events under this organizer, must select one of their teams ' 'to have access to the created event. This setting allows users to create an event-specified team' ' on-the-fly, even when they do not have \"Can change teams and permissions\" permission.' ), required=False, ) primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) theme_color_success = forms.CharField( label=_("Accent color for success"), help_text=_("We strongly suggest to use a shade of green."), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) theme_color_danger = forms.CharField( label=_("Accent color for errors"), help_text=_("We strongly suggest to use a shade of red."), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) theme_color_background = forms.CharField( label=_("Page background color"), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput( attrs={'class': 'colorpickerfield no-contrast'})) theme_round_borders = forms.BooleanField( label=_("Use round edges"), required=False, ) organizer_homepage_text = I18nFormField( label=_('Homepage text'), required=False, widget=I18nTextarea, help_text=_('This will be displayed on the organizer homepage.')) organizer_logo_image = ExtFileField( label=_('Header image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, help_text= _('If you provide a logo image, we will by default not show your organization name ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'can increase the size with the setting below. We recommend not using small details on the picture ' 'as it will be resized on smaller screens.')) organizer_logo_image_large = forms.BooleanField( label=_('Use header image in its full size'), help_text=_( 'We recommend to upload a picture at least 1170 pixels wide.'), required=False, ) event_list_type = forms.ChoiceField(label=_('Default overview style'), choices=( ('list', _('List')), ('week', _('Week calendar')), ('calendar', _('Month calendar')), )) event_list_availability = forms.BooleanField( label=_('Show availability in event overviews'), help_text= _('If checked, the list of events will show if events are sold out. This might ' 'make for longer page loading times if you have lots of events and the shown status might be out ' 'of date for up to two minutes.'), required=False) organizer_link_back = forms.BooleanField( label=_('Link back to organizer overview on all event pages'), required=False) locales = forms.MultipleChoiceField( choices=settings.LANGUAGES, label=_("Use languages"), widget=MultipleLanguagesWidget, help_text= _('Choose all languages that your organizer homepage should be available in.' )) primary_font = forms.ChoiceField( label=_('Font'), choices=[('Open Sans', 'Open Sans')], widget=FontSelect, help_text=_('Only respected by modern browsers.')) favicon = ExtFileField( label=_('Favicon'), ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"), required=False, help_text= _('If you provide a favicon, we will show it instead of the default pretix icon. ' 'We recommend a size of at least 200x200px to accommodate most devices.' )) giftcard_length = forms.IntegerField( label=_('Length of gift card codes'), help_text=_( 'The system generates by default {}-character long gift card codes. However, if a different length ' 'is required, it can be set here.'.format( settings.ENTROPY['giftcard_secret'])), required=False) giftcard_expiry_years = forms.IntegerField( label=_('Validity of gift card codes in years'), help_text= _('If you set a number here, gift cards will by default expire at the end of the year after this ' 'many years. If you keep it empty, gift cards do not have an explicit expiry date.' ), required=False) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['primary_font'].choices += [(a, { "title": a, "data": v }) for a, v in get_fonts().items()]
def __init__(self, *args, **kwargs): """ Takes two additional keyword arguments: :param cartpos: The cart position the form should be for :param event: The event this belongs to """ cartpos = self.cartpos = kwargs.pop('cartpos', None) orderpos = self.orderpos = kwargs.pop('orderpos', None) pos = cartpos or orderpos item = pos.item questions = pos.item.questions_to_ask event = kwargs.pop('event') self.all_optional = kwargs.pop('all_optional', False) super().__init__(*args, **kwargs) add_fields = {} if item.admission and event.settings.attendee_names_asked: add_fields['attendee_name_parts'] = NamePartsFormField( max_length=255, required=event.settings.attendee_names_required and not self.all_optional, scheme=event.settings.name_scheme, titles=event.settings.name_scheme_titles, label=_('Attendee name'), initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts), ) if item.admission and event.settings.attendee_emails_asked: add_fields['attendee_email'] = forms.EmailField( required=event.settings.attendee_emails_required and not self.all_optional, label=_('Attendee email'), initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email), widget=forms.EmailInput(attrs={'autocomplete': 'email'})) if item.admission and event.settings.attendee_company_asked: add_fields['company'] = forms.CharField( required=event.settings.attendee_company_required and not self.all_optional, label=_('Company'), max_length=255, initial=(cartpos.company if cartpos else orderpos.company), ) if item.admission and event.settings.attendee_addresses_asked: add_fields['street'] = forms.CharField( required=event.settings.attendee_addresses_required and not self.all_optional, label=_('Address'), widget=forms.Textarea( attrs={ 'rows': 2, 'placeholder': _('Street and Number'), 'autocomplete': 'street-address' }), initial=(cartpos.street if cartpos else orderpos.street), ) add_fields['zipcode'] = forms.CharField( required=event.settings.attendee_addresses_required and not self.all_optional, max_length=30, label=_('ZIP code'), initial=(cartpos.zipcode if cartpos else orderpos.zipcode), widget=forms.TextInput(attrs={ 'autocomplete': 'postal-code', }), ) add_fields['city'] = forms.CharField( required=event.settings.attendee_addresses_required and not self.all_optional, label=_('City'), max_length=255, initial=(cartpos.city if cartpos else orderpos.city), widget=forms.TextInput(attrs={ 'autocomplete': 'address-level2', }), ) country = (cartpos.country if cartpos else orderpos.country) or guess_country(event) add_fields['country'] = CountryField( countries=CachedCountries).formfield( required=event.settings.attendee_addresses_required and not self.all_optional, label=_('Country'), initial=country, widget=forms.Select(attrs={ 'autocomplete': 'country', }), ) c = [('', pgettext_lazy('address', 'Select state'))] fprefix = str( self.prefix ) + '-' if self.prefix is not None and self.prefix != '-' else '' cc = None if fprefix + 'country' in self.data: cc = str(self.data[fprefix + 'country']) elif country: cc = str(country) if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS: types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc] statelist = [ s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types ] c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1]) elif fprefix + 'state' in self.data: self.data = self.data.copy() del self.data[fprefix + 'state'] add_fields['state'] = forms.ChoiceField( label=pgettext_lazy('address', 'State'), required=False, choices=c, widget=forms.Select(attrs={ 'autocomplete': 'address-level1', }), ) add_fields['state'].widget.is_required = True field_positions = list([(n, event.settings.system_question_order.get( n if n != 'state' else 'country', 0)) for n in add_fields.keys()]) for q in questions: # Do we already have an answer? Provide it as the initial value answers = [a for a in pos.answerlist if a.question_id == q.id] if answers: initial = answers[0] else: initial = None tz = pytz.timezone(event.settings.timezone) help_text = rich_text(q.help_text) label = escape(q.question) # django-bootstrap3 calls mark_safe required = q.required and not self.all_optional if q.type == Question.TYPE_BOOLEAN: if q.required: # For some reason, django-bootstrap3 does not set the required attribute # itself. widget = forms.CheckboxInput( attrs={'required': 'required'}) else: widget = forms.CheckboxInput() if initial: initialbool = (initial.answer == "True") else: initialbool = False field = forms.BooleanField( label=label, required=required, help_text=help_text, initial=initialbool, widget=widget, ) elif q.type == Question.TYPE_NUMBER: field = forms.DecimalField( label=label, required=required, min_value=q.valid_number_min or Decimal('0.00'), max_value=q.valid_number_max, help_text=q.help_text, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_STRING: field = forms.CharField( label=label, required=required, help_text=help_text, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_TEXT: field = forms.CharField( label=label, required=required, help_text=help_text, widget=forms.Textarea, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_COUNTRYCODE: field = CountryField( countries=CachedCountries, blank=True, null=True, blank_label=' ', ).formfield( label=label, required=required, help_text=help_text, widget=forms.Select, empty_label=' ', initial=initial.answer if initial else (guess_country(event) if required else None), ) elif q.type == Question.TYPE_CHOICE: field = forms.ModelChoiceField( queryset=q.options, label=label, required=required, help_text=help_text, widget=forms.Select, to_field_name='identifier', empty_label='', initial=initial.options.first() if initial else None, ) elif q.type == Question.TYPE_CHOICE_MULTIPLE: field = forms.ModelMultipleChoiceField( queryset=q.options, label=label, required=required, help_text=help_text, to_field_name='identifier', widget=QuestionCheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = ExtFileField( label=label, required=required, help_text=help_text, initial=initial.file if initial else None, widget=UploadedFileWidget(position=pos, event=event, answer=initial), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg", ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages", ".bmp", ".tif", ".tiff"), max_size=10 * 1024 * 1024, ) elif q.type == Question.TYPE_DATE: attrs = {} if q.valid_date_min: attrs['data-min'] = q.valid_date_min.isoformat() if q.valid_date_max: attrs['data-max'] = q.valid_date_max.isoformat() field = forms.DateField( label=label, required=required, help_text=help_text, initial=dateutil.parser.parse(initial.answer).date() if initial and initial.answer else None, widget=DatePickerWidget(attrs), ) if q.valid_date_min: field.validators.append(MinDateValidator(q.valid_date_min)) if q.valid_date_max: field.validators.append(MaxDateValidator(q.valid_date_max)) elif q.type == Question.TYPE_TIME: field = forms.TimeField( label=label, required=required, help_text=help_text, initial=dateutil.parser.parse(initial.answer).time() if initial and initial.answer else None, widget=TimePickerWidget( time_format=get_format_without_seconds( 'TIME_INPUT_FORMATS')), ) elif q.type == Question.TYPE_DATETIME: field = SplitDateTimeField( label=label, required=required, help_text=help_text, initial=dateutil.parser.parse( initial.answer).astimezone(tz) if initial and initial.answer else None, widget=SplitDateTimePickerWidget( time_format=get_format_without_seconds( 'TIME_INPUT_FORMATS'), min_date=q.valid_datetime_min, max_date=q.valid_datetime_max), ) if q.valid_datetime_min: field.validators.append( MinDateTimeValidator(q.valid_datetime_min)) if q.valid_datetime_max: field.validators.append( MaxDateTimeValidator(q.valid_datetime_max)) elif q.type == Question.TYPE_PHONENUMBER: babel_locale = 'en' # Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal if localedata.exists(get_language()): babel_locale = get_language() elif localedata.exists(get_language()[:2]): babel_locale = get_language()[:2] with language(babel_locale): default_country = guess_country(event) default_prefix = None for prefix, values in _COUNTRY_CODE_TO_REGION_CODE.items(): if str(default_country) in values: default_prefix = prefix try: initial = PhoneNumber().from_string( initial.answer) if initial else "+{}.".format( default_prefix) except NumberParseException: initial = None field = PhoneNumberField( label=label, required=required, help_text=help_text, # We now exploit an implementation detail in PhoneNumberPrefixWidget to allow us to pass just # a country code but no number as an initial value. It's a bit hacky, but should be stable for # the future. initial=initial, widget=WrappedPhoneNumberPrefixWidget()) field.question = q if answers: # Cache the answer object for later use field.answer = answers[0] if q.dependency_question_id: field.widget.attrs[ 'data-question-dependency'] = q.dependency_question_id field.widget.attrs[ 'data-question-dependency-values'] = escapejson_attr( json.dumps(q.dependency_values)) if q.type != 'M': field.widget.attrs[ 'required'] = q.required and not self.all_optional field._required = q.required and not self.all_optional field.required = False add_fields['question_%s' % q.id] = field field_positions.append(('question_%s' % q.id, q.position)) field_positions.sort(key=lambda e: e[1]) for fname, p in field_positions: self.fields[fname] = add_fields[fname] responses = question_form_fields.send(sender=event, position=pos) data = pos.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): # We need to be this explicit, since OrderedDict.update does not retain ordering self.fields[key] = value value.initial = data.get('question_form_data', {}).get(key) for k, v in self.fields.items(): if v.widget.attrs.get( 'autocomplete') or k == 'attendee_name_parts': v.widget.attrs['autocomplete'] = 'section-{} '.format( self.prefix) + v.widget.attrs.get('autocomplete', '')
class InvoiceSettingsForm(SettingsForm): auto_fields = [ 'invoice_address_asked', 'invoice_address_required', 'invoice_address_vatid', 'invoice_address_company_required', 'invoice_address_beneficiary', 'invoice_address_custom_field', 'invoice_name_required', 'invoice_address_not_asked_free', 'invoice_include_free', 'invoice_show_payments', 'invoice_reissue_after_modify', 'invoice_generate', 'invoice_attendee_name', 'invoice_include_expire_date', 'invoice_numbers_consecutive', 'invoice_numbers_prefix', 'invoice_numbers_prefix_cancellations', 'invoice_address_explanation_text', 'invoice_email_attachment', 'invoice_address_from_name', 'invoice_address_from', 'invoice_address_from_zipcode', 'invoice_address_from_city', 'invoice_address_from_country', 'invoice_address_from_tax_id', 'invoice_address_from_vat_id', 'invoice_introductory_text', 'invoice_additional_text', 'invoice_footer_text', 'invoice_eu_currencies', ] invoice_generate_sales_channels = forms.MultipleChoiceField( label=_('Generate invoices for Sales channels'), choices=[], widget=forms.CheckboxSelectMultiple, help_text=_("If you have enabled invoice generation in the previous setting, you can limit it here to specific " "sales channels.") ) invoice_renderer = forms.ChoiceField( label=_("Invoice style"), required=True, choices=[] ) invoice_language = forms.ChoiceField( widget=forms.Select, required=True, label=_("Invoice language"), choices=[('__user__', _('The user\'s language'))] + settings.LANGUAGES, ) invoice_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=10 * 1024 * 1024, help_text=_('We will show your logo with a maximal height and width of 2.5 cm.') ) def __init__(self, *args, **kwargs): event = kwargs.get('obj') super().__init__(*args, **kwargs) self.fields['invoice_renderer'].choices = [ (r.identifier, r.verbose_name) for r in event.get_invoice_renderers().values() ] self.fields['invoice_numbers_prefix'].widget.attrs['placeholder'] = event.slug.upper() + '-' if event.settings.invoice_numbers_prefix: self.fields['invoice_numbers_prefix_cancellations'].widget.attrs['placeholder'] = event.settings.invoice_numbers_prefix else: self.fields['invoice_numbers_prefix_cancellations'].widget.attrs['placeholder'] = event.slug.upper() + '-' locale_names = dict(settings.LANGUAGES) self.fields['invoice_language'].choices = [('__user__', _('The user\'s language'))] + [(a, locale_names[a]) for a in event.settings.locales] self.fields['invoice_generate_sales_channels'].choices = ( (c.identifier, c.verbose_name) for c in get_all_sales_channels().values() ) def clean(self): data = super().clean() settings_dict = self.obj.settings.freeze() settings_dict.update(data) validate_settings(self.obj, data) return data
class EventSettingsForm(SettingsForm): timezone = forms.ChoiceField( choices=((a, a) for a in common_timezones), label=_("Event timezone"), ) name_scheme = forms.ChoiceField( label=_("Name format"), help_text=_("This defines how pretix will ask for human names. Changing this after you already received " "orders might lead to unexpected behavior when sorting or changing names."), required=True, ) name_scheme_titles = forms.ChoiceField( label=_("Allowed titles"), help_text=_("If the naming scheme you defined above allows users to input a title, you can use this to " "restrict the set of selectable titles."), required=False, ) logo_image = ExtFileField( label=_('Header image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=10 * 1024 * 1024, help_text=_('If you provide a logo image, we will by default not show your event name and date ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'can increase the size with the setting below. We recommend not using small details on the picture ' 'as it will be resized on smaller screens.') ) logo_image_large = forms.BooleanField( label=_('Use header image in its full size'), help_text=_('We recommend to upload a picture at least 1170 pixels wide.'), required=False, ) logo_show_title = forms.BooleanField( label=_('Show event title even if a header image is present'), help_text=_('The title will only be shown on the event front page.'), required=False, ) og_image = ExtFileField( label=_('Social media image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=10 * 1024 * 1024, help_text=_('This picture will be used as a preview if you post links to your ticket shop on social media. ' 'Facebook advises to use a picture size of 1200 x 630 pixels, however some platforms like ' 'WhatsApp and Reddit only show a square preview, so we recommend to make sure it still looks good ' 'only the center square is shown. If you do not fill this, we will use the logo given above.') ) primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) theme_color_success = forms.CharField( label=_("Accent color for success"), help_text=_("We strongly suggest to use a shade of green."), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) theme_color_danger = forms.CharField( label=_("Accent color for errors"), help_text=_("We strongly suggest to use a dark shade of red."), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'}) ) theme_color_background = forms.CharField( label=_("Page background color"), required=False, validators=[ RegexValidator(regex='^#[0-9a-fA-F]{6}$', message=_('Please enter the hexadecimal code of a color, e.g. #990000.')), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield no-contrast'}) ) theme_round_borders = forms.BooleanField( label=_("Use round edges"), required=False, ) primary_font = forms.ChoiceField( label=_('Font'), choices=[ ('Open Sans', 'Open Sans') ], widget=FontSelect, help_text=_('Only respected by modern browsers.') ) auto_fields = [ 'imprint_url', 'checkout_email_helptext', 'presale_has_ended_text', 'voucher_explanation_text', 'show_dates_on_frontpage', 'show_date_to', 'show_times', 'show_items_outside_presale_period', 'display_net_prices', 'presale_start_show_date', 'locales', 'locale', 'show_quota_left', 'waiting_list_enabled', 'waiting_list_hours', 'waiting_list_auto', 'max_items_per_order', 'reservation_time', 'contact_mail', 'show_variations_expanded', 'hide_sold_out', 'meta_noindex', 'redirect_to_checkout_directly', 'frontpage_subevent_ordering', 'event_list_type', 'frontpage_text', 'attendee_names_asked', 'attendee_names_required', 'attendee_emails_asked', 'attendee_emails_required', 'attendee_company_asked', 'attendee_company_required', 'attendee_addresses_asked', 'attendee_addresses_required', 'confirm_text', 'banner_text', 'banner_text_bottom', 'order_email_asked_twice', 'last_order_modification_date', ] def clean(self): data = super().clean() settings_dict = self.event.settings.freeze() settings_dict.update(data) validate_settings(self.event, data) return data def __init__(self, *args, **kwargs): self.event = kwargs['obj'] super().__init__(*args, **kwargs) self.fields['confirm_text'].widget.attrs['rows'] = '3' self.fields['confirm_text'].widget.attrs['placeholder'] = _( 'e.g. I hereby confirm that I have read and agree with the event organizer\'s terms of service ' 'and agree with them.' ) self.fields['name_scheme'].choices = ( (k, _('Ask for {fields}, display like {example}').format( fields=' + '.join(str(vv[1]) for vv in v['fields']), example=v['concatenation'](v['sample']) )) for k, v in PERSON_NAME_SCHEMES.items() ) self.fields['name_scheme_titles'].choices = [('', _('Free text input'))] + [ (k, '{scheme}: {samples}'.format( scheme=v[0], samples=', '.join(v[1]) )) for k, v in PERSON_NAME_TITLE_GROUPS.items() ] if not self.event.has_subevents: del self.fields['frontpage_subevent_ordering'] del self.fields['event_list_type'] self.fields['primary_font'].choices += [ (a, {"title": a, "data": v}) for a, v in get_fonts().items() ]
class OrganizerSettingsForm(SettingsForm): timezone = forms.ChoiceField( choices=((a, a) for a in common_timezones), label=_("Default timezone"), ) name_scheme = forms.ChoiceField( label=_("Name format"), help_text= _("This defines how pretix will ask for human names. Changing this after you already received " "orders might lead to unexpected behavior when sorting or changing names." ), required=True, ) name_scheme_titles = forms.ChoiceField( label=_("Allowed titles"), help_text=_( "If the naming scheme you defined above allows users to input a title, you can use this to " "restrict the set of selectable titles."), required=False, ) auto_fields = [ 'customer_accounts', 'customer_accounts_link_by_email', 'invoice_regenerate_allowed', 'contact_mail', 'imprint_url', 'organizer_info_text', 'event_list_type', 'event_list_availability', 'organizer_homepage_text', 'organizer_link_back', 'organizer_logo_image_large', 'organizer_logo_image_inherit', 'giftcard_length', 'giftcard_expiry_years', 'locales', 'region', 'meta_noindex', 'event_team_provisioning', 'primary_color', 'theme_color_success', 'theme_color_danger', 'theme_color_background', 'theme_round_borders', 'primary_font', 'privacy_url', 'cookie_consent', 'cookie_consent_dialog_title', 'cookie_consent_dialog_text', 'cookie_consent_dialog_text_secondary', 'cookie_consent_dialog_button_yes', 'cookie_consent_dialog_button_no', ] organizer_logo_image = ExtFileField( label=_('Header image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), max_size=settings.FILE_UPLOAD_MAX_SIZE_IMAGE, required=False, help_text= _('If you provide a logo image, we will by default not show your organization name ' 'in the page header. By default, we show your logo with a size of up to 1140x120 pixels. You ' 'can increase the size with the setting below. We recommend not using small details on the picture ' 'as it will be resized on smaller screens.')) favicon = ExtFileField( label=_('Favicon'), ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"), required=False, max_size=settings.FILE_UPLOAD_MAX_SIZE_FAVICON, help_text= _('If you provide a favicon, we will show it instead of the default pretix icon. ' 'We recommend a size of at least 200x200px to accommodate most devices.' )) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['name_scheme'].choices = ( (k, _('Ask for {fields}, display like {example}').format( fields=' + '.join(str(vv[1]) for vv in v['fields']), example=v['concatenation'](v['sample']))) for k, v in PERSON_NAME_SCHEMES.items()) self.fields['name_scheme_titles'].choices = [ ('', _('Free text input')) ] + [(k, '{scheme}: {samples}'.format(scheme=v[0], samples=', '.join(v[1]))) for k, v in PERSON_NAME_TITLE_GROUPS.items()]
class InvoiceSettingsForm(SettingsForm): invoice_address_asked = forms.BooleanField( label=_("Ask for invoice address"), required=False) invoice_address_required = forms.BooleanField( label=_("Require invoice address"), required=False, widget=forms.CheckboxInput( attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), ) invoice_address_vatid = forms.BooleanField( label=_("Ask for VAT ID"), help_text= _("Does only work if an invoice address is asked for. VAT ID is not required." ), widget=forms.CheckboxInput( attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}), required=False) invoice_numbers_consecutive = forms.BooleanField( label=_("Generate invoices with consecutive numbers"), help_text=_( "If deactivated, the order code will be used in the invoice number." ), required=False) invoice_generate = forms.ChoiceField( label=_("Generate invoices"), required=False, choices=( ('False', _('No')), ('admin', _('Manually in admin panel')), ('user', _('Automatically on user request')), ('True', _('Automatically for all created orders')), ('paid', _('Automatically on payment')), )) invoice_address_from = forms.CharField( widget=forms.Textarea(attrs={'rows': 5}), required=False, label=_("Your address"), help_text=_( "Will be printed as the sender on invoices. Be sure to include relevant details required in " "your jurisdiction (e.g. your VAT ID).")) invoice_introductory_text = I18nFormField( widget=I18nTextarea, required=False, label=_("Introductory text"), help_text=_( "Will be printed on every invoice above the invoice rows.")) invoice_additional_text = I18nFormField( widget=I18nTextarea, required=False, label=_("Additional text"), help_text=_( "Will be printed on every invoice below the invoice total.")) invoice_footer_text = I18nFormField( widget=I18nTextarea, required=False, label=_("Footer"), help_text= _("Will be printed centered and in a smaller font at the end of every invoice page." )) invoice_language = forms.ChoiceField( widget=forms.Select, required=True, label=_("Invoice language"), choices=[('__user__', _('The user\'s language'))] + settings.LANGUAGES, ) invoice_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".svg", ".gif", ".jpeg"), required=False, help_text=_( 'We will show your logo with a maximal height and width of 2.5 cm.' ))
class OrganizerSettingsForm(SettingsForm): organizer_info_text = I18nFormField( label=_('Info text'), required=False, widget=I18nTextarea, help_text= _('Not displayed anywhere by default, but if you want to, you can use this e.g. in ticket templates.' )) primary_color = forms.CharField( label=_("Primary color"), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) theme_color_success = forms.CharField( label=_("Accent color for success"), help_text=_("We strongly suggest to use a shade of green."), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) theme_color_danger = forms.CharField( label=_("Accent color for errors"), help_text=_("We strongly suggest to use a shade of red."), required=False, validators=[ RegexValidator( regex='^#[0-9a-fA-F]{6}$', message= _('Please enter the hexadecimal code of a color, e.g. #990000.' )), ], widget=forms.TextInput(attrs={'class': 'colorpickerfield'})) organizer_homepage_text = I18nFormField( label=_('Homepage text'), required=False, widget=I18nTextarea, help_text=_('This will be displayed on the organizer homepage.')) organizer_logo_image = ExtFileField( label=_('Logo image'), ext_whitelist=(".png", ".jpg", ".gif", ".jpeg"), required=False, help_text= _('If you provide a logo image, we will by default not show your organization name ' 'in the page header. We will show your logo with a maximal height of 120 pixels.' )) event_list_type = forms.ChoiceField(label=_('Default overview style'), choices=(('list', _('List')), ('calendar', _('Calendar')))) event_list_availability = forms.BooleanField( label=_('Show availability in event overviews'), help_text= _('If checked, the list of events will show if events are sold out. This might ' 'make for longer page loading times if you have lots of events and the shown status might be out ' 'of date for up to two minutes.'), required=False) organizer_link_back = forms.BooleanField( label=_('Link back to organizer overview on all event pages'), required=False) locales = forms.MultipleChoiceField( choices=settings.LANGUAGES, label=_("Use languages"), widget=MultipleLanguagesWidget, help_text= _('Choose all languages that your organizer homepage should be available in.' )) primary_font = forms.ChoiceField( label=_('Font'), choices=[('Open Sans', 'Open Sans')], widget=FontSelect, help_text=_('Only respected by modern browsers.')) favicon = ExtFileField( label=_('Favicon'), ext_whitelist=(".ico", ".png", ".jpg", ".gif", ".jpeg"), required=False, help_text= _('If you provide a favicon, we will show it instead of the default pretix icon. ' 'We recommend a size of at least 200x200px to accomodate most devices.' )) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['primary_font'].choices += [(a, a) for a in get_fonts()]
def settings_form_fields(self) -> dict: return OrderedDict( list(super().settings_form_fields.items()) + [('paper_size', forms.ChoiceField(label=_('Paper size'), choices=( ('A4', 'A4'), ('A5', 'A5'), ('B4', 'B4'), ('B5', 'B5'), ('letter', 'Letter'), ('legal', 'Legal'), ), required=False)), ('orientation', forms.ChoiceField(label=_('Paper orientation'), choices=( ('portrait', _('Portrait')), ('landscape', _('Landscape')), ), required=False)), ('background', ExtFileField(label=_('Background PDF'), ext_whitelist=(".pdf", ), required=False)), ('qr_x', forms.FloatField(label=_('QR-Code x position (mm)'), required=False)), ('qr_y', forms.FloatField(label=_('QR-Code y position (mm)'), required=False)), ('qr_s', forms.FloatField(label=_('QR-Code size (mm)'), required=False)), ('code_x', forms.FloatField(label=_('Ticket code x position (mm)'), required=False)), ('code_y', forms.FloatField(label=_('Ticket code y position (mm)'), required=False)), ('code_s', forms.FloatField( label=_('Ticket code size (mm)'), required=False, help_text=_( 'Visible by default, set this to 0 to hide the element.') )), ('order_x', forms.FloatField(label=_('Order x position (mm)'), required=False)), ('order_y', forms.FloatField(label=_('Order y position (mm)'), required=False)), ('order_s', forms.FloatField( label=_('Order size (mm)'), required=False, help_text=_( 'Visible by default, set this to 0 to hide the element.') )), ('name_x', forms.FloatField(label=_('Product name x position (mm)'), required=False)), ('name_y', forms.FloatField(label=_('Product name y position (mm)'), required=False)), ('name_s', forms.FloatField( label=_('Product name size (mm)'), required=False, help_text=_( 'Visible by default, set this to 0 to hide the element.') )), ('price_x', forms.FloatField(label=_('Price x position (mm)'), required=False)), ('price_y', forms.FloatField(label=_('Price y position (mm)'), required=False)), ('price_s', forms.FloatField( label=_('Price size (mm)'), required=False, help_text=_( 'Visible by default, set this to 0 to hide the element.') )), ('event_x', forms.FloatField(label=_('Event name x position (mm)'), required=False)), ('event_y', forms.FloatField(label=_('Event name y position (mm)'), required=False)), ('event_s', forms.FloatField( label=_('Event name size (mm)'), required=False, help_text=_( 'Visible by default, set this to 0 to hide the element.') )), ('attendee_x', forms.FloatField(label=_('Attendee name x position (mm)'), required=False)), ('attendee_y', forms.FloatField(label=_('Attendee name y position (mm)'), required=False)), ('attendee_s', forms.FloatField( label=_('Attendee name size (mm)'), required=False, help_text= _('Invisible by default, set this to a number greater than 0 ' 'to show.')))])
class MailForm(forms.Form): recipients = forms.ChoiceField(label=_('Send email to'), widget=forms.RadioSelect, initial='orders', choices=[]) sendto = forms.MultipleChoiceField() # overridden later subject = forms.CharField(label=_("Subject")) message = forms.CharField(label=_("Message")) attachment = ExtFileField( label=_("Attachment"), required=False, ext_whitelist=(".png", ".jpg", ".gif", ".jpeg", ".pdf", ".txt", ".docx", ".gif", ".svg", ".pptx", ".ppt", ".doc", ".xlsx", ".xls", ".jfif", ".heic", ".heif", ".pages", ".bmp", ".tif", ".tiff"), help_text=_( 'Sending an attachment increases the chance of your email not arriving or being sorted into spam folders. We recommend only using PDFs ' 'of no more than 2 MB in size.'), max_size=10 * 1024 * 1024) # TODO i18n items = forms.ModelMultipleChoiceField( widget=forms.CheckboxSelectMultiple( attrs={'class': 'scrolling-multiple-choice'}), label=_('Only send to people who bought'), required=True, queryset=Item.objects.none()) filter_checkins = forms.BooleanField(label=_('Filter check-in status'), required=False) checkin_lists = SafeModelMultipleChoiceField( queryset=CheckinList.objects.none(), required=False) # overridden later not_checked_in = forms.BooleanField( label=_("Send to customers not checked in"), required=False) subevent = forms.ModelChoiceField(SubEvent.objects.none(), label=_('Only send to customers of'), required=False, empty_label=pgettext_lazy( 'subevent', 'All dates')) def _set_field_placeholders(self, fn, base_parameters): phs = [ '{%s}' % p for p in sorted( get_available_placeholders(self.event, base_parameters).keys()) ] ht = _('Available placeholders: {list}').format(list=', '.join(phs)) if self.fields[fn].help_text: self.fields[fn].help_text += ' ' + str(ht) else: self.fields[fn].help_text = ht self.fields[fn].validators.append(PlaceholderValidator(phs)) def __init__(self, *args, **kwargs): event = self.event = kwargs.pop('event') super().__init__(*args, **kwargs) recp_choices = [('orders', _('Everyone who created a ticket order'))] if event.settings.attendee_emails_asked: recp_choices += [ ('attendees', _('Every attendee (falling back to the order contact when no attendee email address is ' 'given)')), ('both', _('Both (all order contact addresses and all attendee email addresses)' )) ] self.fields['recipients'].choices = recp_choices self.fields['subject'] = I18nFormField( label=_('Subject'), widget=I18nTextInput, required=True, locales=event.settings.get('locales'), ) self.fields['message'] = I18nFormField( label=_('Message'), widget=I18nTextarea, required=True, locales=event.settings.get('locales'), ) self._set_field_placeholders('subject', ['event', 'order', 'position_or_address']) self._set_field_placeholders('message', ['event', 'order', 'position_or_address']) choices = list(Order.STATUS_CHOICE) if not event.settings.get('payment_term_expire_automatically', as_type=bool): choices.append(('overdue', _('pending with payment overdue'))) self.fields['sendto'] = forms.MultipleChoiceField( label=_("Send to customers with order status"), widget=forms.CheckboxSelectMultiple( attrs={'class': 'scrolling-multiple-choice'}), choices=choices) if not self.initial.get('sendto'): self.initial['sendto'] = ['p', 'n'] self.fields['items'].queryset = event.items.all() if not self.initial.get('items'): self.initial['items'] = event.items.all() self.fields['checkin_lists'].queryset = event.checkin_lists.all() self.fields['checkin_lists'].widget = Select2Multiple( attrs={ 'data-model-select2': 'generic', 'data-select2-url': reverse('control:event.orders.checkinlists.select2', kwargs={ 'event': event.slug, 'organizer': event.organizer.slug, }), 'data-placeholder': _('Send to customers checked in on list'), }) self.fields['checkin_lists'].widget.choices = self.fields[ 'checkin_lists'].choices self.fields['checkin_lists'].label = _( 'Send to customers checked in on list') if event.has_subevents: self.fields['subevent'].queryset = event.subevents.all() self.fields['subevent'].widget = Select2( attrs={ 'data-model-select2': 'event', 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ 'event': event.slug, 'organizer': event.organizer.slug, }), 'data-placeholder': pgettext_lazy('subevent', 'Date') }) self.fields['subevent'].widget.choices = self.fields[ 'subevent'].choices else: del self.fields['subevent']