def is_completed(self, request, warn=False): self.request = request try: emailval = EmailValidator() if 'email' not in self.cart_session: if warn: messages.warning(request, _('Please enter a valid email address.')) return False emailval(self.cart_session.get('email')) except ValidationError: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if request.event.settings.invoice_address_required and ( not self.invoice_address or not self.invoice_address.street): messages.warning(request, _('Please enter your invoicing address.')) return False if request.event.settings.invoice_name_required and ( not self.invoice_address or not self.invoice_address.name): messages.warning(request, _('Please enter your name.')) return False for cp in self.positions: answ = {aw.question_id: aw.answer for aw in cp.answers.all()} for q in cp.item.questions.all(): if q.required and q.id not in answ: if warn: messages.warning( request, _('Please fill in answers to all required questions.' )) return False if cp.item.admission and self.request.event.settings.get('attendee_names_required', as_type=bool) \ and cp.attendee_name is None: if warn: messages.warning( request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_emails_required', as_type=bool) \ and cp.attendee_email is None: if warn: messages.warning( request, _('Please fill in answers to all required questions.')) return False responses = question_form_fields.send(sender=self.request.event, position=cp) form_data = cp.meta_info_data.get('question_form_data', {}) for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): if value.required and not form_data.get(key): return False return True
def is_completed(self, request, warn=False): self.request = request try: emailval = EmailValidator() if 'email' not in self.cart_session: if warn: messages.warning(request, _('Please enter a valid email address.')) return False emailval(self.cart_session.get('email')) except ValidationError: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if request.event.settings.invoice_address_required and (not self.invoice_address or not self.invoice_address.street): messages.warning(request, _('Please enter your invoicing address.')) return False if request.event.settings.invoice_name_required and (not self.invoice_address or not self.invoice_address.name): messages.warning(request, _('Please enter your name.')) return False for cp in self._positions_for_questions: answ = { aw.question_id: aw.answer for aw in cp.answerlist } for q in cp.item.questions_to_ask: if q.required and q.id not in answ: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_names_required', as_type=bool) \ and not cp.attendee_name_parts: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_emails_required', as_type=bool) \ and cp.attendee_email is None: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False responses = question_form_fields.send(sender=self.request.event, position=cp) form_data = cp.meta_info_data.get('question_form_data', {}) for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): if value.required and not form_data.get(key): return False return True
def get_items(self): queryset = self.object.positions.all() cartpos = queryset.order_by('item', 'variation').select_related( 'item', 'variation', 'addon_to').prefetch_related('item__questions', 'answers', 'answers__question', 'checkins').order_by('positionid') positions = [] for p in cartpos: responses = question_form_fields.send(sender=self.request.event, position=p) p.additional_fields = [] data = p.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): p.additional_fields.append({ 'answer': data.get('question_form_data', {}).get(key), 'question': value.label }) p.has_questions = ( p.additional_fields or (p.item.admission and self.request.event.settings.attendee_names_asked) or (p.item.admission and self.request.event.settings.attendee_emails_asked) or p.item.questions.all()) p.cache_answers() positions.append(p) positions.sort(key=lambda p: p.sort_key) return { 'positions': positions, 'raw': cartpos, 'total': self.object.total, 'payment_fee': self.object.payment_fee, 'net_total': self.object.net_total, 'tax_total': self.object.tax_total, }
def get_cart(self, answers=False, queryset=None, order=None, downloads=False): if queryset: prefetch = [] if answers: prefetch.append('item__questions') prefetch.append( Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related( 'options'))) cartpos = queryset.order_by( 'item__category__position', 'item__category_id', 'item__position', 'item__name', 'variation__value').select_related( 'item', 'variation', 'addon_to', 'subevent', 'subevent__event', 'subevent__event__organizer').prefetch_related(*prefetch) else: cartpos = self.positions lcp = list(cartpos) has_addons = {cp.addon_to.pk for cp in lcp if cp.addon_to} pos_additional_fields = defaultdict(list) for cp in lcp: responses = question_form_fields.send(sender=self.request.event, position=cp) data = cp.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): if response: for key, value in response.items(): pos_additional_fields[cp.pk].append({ 'answer': data.get('question_form_data', {}).get(key), 'question': value.label }) # Group items of the same variation # We do this by list manipulations instead of a GROUP BY query, as # Django is unable to join related models in a .values() query def keyfunc(pos): if isinstance(pos, OrderPosition): if pos.addon_to: i = pos.addon_to.positionid else: i = pos.positionid else: if pos.addon_to: i = pos.addon_to.pk else: i = pos.pk has_attendee_data = pos.item.admission and ( self.request.event.settings.attendee_names_asked or self.request.event.settings.attendee_emails_asked or pos_additional_fields.get(pos.pk)) addon_penalty = 1 if pos.addon_to else 0 if downloads or pos.pk in has_addons or pos.addon_to: return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) if answers and (has_attendee_data or pos.item.questions.all()): return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) return (0, addon_penalty, 0, pos.item_id, pos.variation_id, pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0)) positions = [] for k, g in groupby(sorted(lcp, key=keyfunc), key=keyfunc): g = list(g) group = g[0] group.count = len(g) group.total = group.count * group.price group.net_total = group.count * group.net_price group.has_questions = answers and k[0] != "" group.tax_rule = group.item.tax_rule if answers: group.cache_answers(all=False) group.additional_answers = pos_additional_fields.get(group.pk) positions.append(group) total = sum(p.total for p in positions) net_total = sum(p.net_total for p in positions) tax_total = sum(p.total - p.net_total for p in positions) if order: fees = order.fees.all() elif positions: fees = get_fees(self.request.event, self.request, total, self.invoice_address, self.cart_session.get('payment')) else: fees = [] total += sum([f.value for f in fees]) net_total += sum([f.net_value for f in fees]) tax_total += sum([f.tax_value for f in fees]) try: first_expiry = min(p.expires for p in positions) if positions else now() total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds() minutes_left = int(total_seconds_left // 60) seconds_left = int(total_seconds_left % 60) except AttributeError: first_expiry = None minutes_left = None seconds_left = None return { 'positions': positions, 'raw': cartpos, 'total': total, 'net_total': net_total, 'tax_total': tax_total, 'fees': fees, 'answers': answers, 'minutes_left': minutes_left, 'seconds_left': seconds_left, 'first_expiry': first_expiry, }
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', '')
def is_completed(self, request, warn=False): self.request = request try: emailval = EmailValidator() if not self.cart_session.get('email') and not self.all_optional: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if self.cart_session.get('email'): emailval(self.cart_session.get('email')) except ValidationError: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if not self.all_optional: if self.address_asked: if request.event.settings.invoice_address_required and ( not self.invoice_address or not self.invoice_address.street): messages.warning(request, _('Please enter your invoicing address.')) return False if request.event.settings.invoice_name_required and ( not self.invoice_address or not self.invoice_address.name): messages.warning(request, _('Please enter your name.')) return False for cp in self._positions_for_questions: answ = {aw.question_id: aw for aw in cp.answerlist} question_cache = {q.pk: q for q in cp.item.questions_to_ask} def question_is_visible(parentid, qvals): if parentid not in question_cache: return False parentq = question_cache[parentid] if parentq.dependency_question_id and not question_is_visible( parentq.dependency_question_id, parentq.dependency_values): return False if parentid not in answ: return False return ( ('True' in qvals and answ[parentid].answer == 'True') or ('False' in qvals and answ[parentid].answer == 'False') or (any(qval in [o.identifier for o in answ[parentid].options.all()] for qval in qvals))) def question_is_required(q): return (q.required and (not q.dependency_question_id or question_is_visible( q.dependency_question_id, q.dependency_values))) for q in cp.item.questions_to_ask: if question_is_required(q) and not answ.get(q.id): if warn: messages.warning( request, _('Please fill in answers to all required questions.' )) return False if cp.item.admission and self.request.event.settings.get('attendee_names_required', as_type=bool) \ and not cp.attendee_name_parts: if warn: messages.warning( request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_emails_required', as_type=bool) \ and cp.attendee_email is None: if warn: messages.warning( request, _('Please fill in answers to all required questions.')) return False responses = question_form_fields.send(sender=self.request.event, position=cp) form_data = cp.meta_info_data.get('question_form_data', {}) for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): if value.required and not form_data.get(key): return False return True
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) if item.admission and event.settings.attendee_names_asked: self.fields['attendee_name_parts'] = NamePartsFormField( max_length=255, required=event.settings.attendee_names_required, 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: self.fields['attendee_email'] = forms.EmailField( required=event.settings.attendee_emails_required, label=_('Attendee email'), initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email), widget=forms.EmailInput(attrs={'autocomplete': 'email'})) 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, help_text=q.help_text, initial=initial.answer if initial else None, min_value=Decimal('0.00'), ) 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().formfield( label=label, required=required, help_text=help_text, widget=forms.Select, empty_label='', initial=initial.answer if initial 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=forms.CheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = forms.FileField( label=label, required=required, help_text=help_text, initial=initial.file if initial else None, widget=UploadedFileWidget(position=pos, event=event, answer=initial), ) elif q.type == Question.TYPE_DATE: 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(), ) 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')), ) 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 self.fields['question_%s' % q.id] = field 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', '')
def get_cart(self, answers=False, queryset=None, order=None, downloads=False): if queryset: prefetch = [] if answers: prefetch.append('item__questions') prefetch.append('answers') cartpos = queryset.order_by( 'item__category__position', 'item__category_id', 'item__position', 'item__name', 'variation__value' ).select_related( 'item', 'variation', 'addon_to', 'subevent', 'subevent__event', 'subevent__event__organizer' ).prefetch_related( *prefetch ) else: cartpos = self.positions lcp = list(cartpos) has_addons = {cp.addon_to.pk for cp in lcp if cp.addon_to} pos_additional_fields = defaultdict(list) for cp in lcp: responses = question_form_fields.send(sender=self.request.event, position=cp) data = cp.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): if response: for key, value in response.items(): pos_additional_fields[cp.pk].append({ 'answer': data.get('question_form_data', {}).get(key), 'question': value.label }) # Group items of the same variation # We do this by list manipulations instead of a GROUP BY query, as # Django is unable to join related models in a .values() query def keyfunc(pos): if isinstance(pos, OrderPosition): if pos.addon_to: i = pos.addon_to.positionid else: i = pos.positionid else: if pos.addon_to: i = pos.addon_to.pk else: i = pos.pk has_attendee_data = pos.item.admission and ( self.request.event.settings.attendee_names_asked or self.request.event.settings.attendee_emails_asked or pos_additional_fields.get(pos.pk) ) addon_penalty = 1 if pos.addon_to else 0 if downloads or pos.pk in has_addons or pos.addon_to: return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) if answers and (has_attendee_data or pos.item.questions.all()): return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) return ( 0, addon_penalty, 0, pos.item_id, pos.variation_id, pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0) ) positions = [] for k, g in groupby(sorted(lcp, key=keyfunc), key=keyfunc): g = list(g) group = g[0] group.count = len(g) group.total = group.count * group.price group.net_total = group.count * group.net_price group.has_questions = answers and k[0] != "" group.tax_rule = group.item.tax_rule if answers: group.cache_answers(all=False) group.additional_answers = pos_additional_fields.get(group.pk) positions.append(group) total = sum(p.total for p in positions) net_total = sum(p.net_total for p in positions) tax_total = sum(p.total - p.net_total for p in positions) if order: fees = order.fees.all() elif positions: fees = get_fees(self.request.event, self.request, total, self.invoice_address, self.cart_session.get('payment')) else: fees = [] total += sum([f.value for f in fees]) net_total += sum([f.net_value for f in fees]) tax_total += sum([f.tax_value for f in fees]) try: first_expiry = min(p.expires for p in positions) if positions else now() total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds() minutes_left = int(total_seconds_left // 60) seconds_left = int(total_seconds_left % 60) except AttributeError: first_expiry = None minutes_left = None seconds_left = None return { 'positions': positions, 'raw': cartpos, 'total': total, 'net_total': net_total, 'tax_total': tax_total, 'fees': fees, 'answers': answers, 'minutes_left': minutes_left, 'seconds_left': seconds_left, 'first_expiry': first_expiry, }
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) if item.admission and event.settings.attendee_names_asked: self.fields['attendee_name_parts'] = NamePartsFormField( max_length=255, required=event.settings.attendee_names_required, scheme=event.settings.name_scheme, label=_('Attendee name'), initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts), ) if item.admission and event.settings.attendee_emails_asked: self.fields['attendee_email'] = forms.EmailField( required=event.settings.attendee_emails_required, label=_('Attendee email'), initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email)) 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, help_text=q.help_text, initial=initial.answer if initial else None, min_value=Decimal('0.00'), ) 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_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=forms.CheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = forms.FileField( label=label, required=required, help_text=help_text, initial=initial.file if initial else None, widget=UploadedFileWidget(position=pos, event=event, answer=initial), ) elif q.type == Question.TYPE_DATE: 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(), ) 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')), ) 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-value'] = q.dependency_value 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 self.fields['question_%s' % q.id] = field 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)
def get_cart(self, answers=False, queryset=None, order=None, downloads=False): if queryset is not None: prefetch = [] if answers: prefetch.append('item__questions') prefetch.append(Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options'))) cartpos = queryset.order_by( 'item__category__position', 'item__category_id', 'item__position', 'item__name', 'variation__value' ).select_related( 'item', 'variation', 'addon_to', 'subevent', 'subevent__event', 'subevent__event__organizer', 'seat' ).prefetch_related( *prefetch ) else: cartpos = self.positions lcp = list(cartpos) has_addons = defaultdict(list) for cp in lcp: if cp.addon_to_id: has_addons[cp.addon_to_id].append(cp) pos_additional_fields = defaultdict(list) for cp in lcp: cp.item.event = self.request.event # will save some SQL queries responses = question_form_fields.send(sender=self.request.event, position=cp) data = cp.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): if response: for key, value in response.items(): pos_additional_fields[cp.pk].append({ 'answer': data.get('question_form_data', {}).get(key), 'question': value.label }) # Group items of the same variation # We do this by list manipulations instead of a GROUP BY query, as # Django is unable to join related models in a .values() query def keyfunc(pos, for_sorting=False): if isinstance(pos, OrderPosition): if pos.addon_to_id: i = pos.addon_to.positionid else: i = pos.positionid else: if pos.addon_to_id: i = pos.addon_to_id else: i = pos.pk has_attendee_data = pos.item.admission and ( self.request.event.settings.attendee_names_asked or self.request.event.settings.attendee_emails_asked or pos_additional_fields.get(pos.pk) ) addon_penalty = 1 if pos.addon_to_id else 0 if downloads \ or pos.pk in has_addons \ or pos.item.issue_giftcard \ or (answers and (has_attendee_data or bool(pos.item.questions.all()))): # do not use .exists() to re-use prefetch cache return ( # standalone positions are grouped by main product position id, addons below them also sorted by position id i, addon_penalty, pos.positionid if isinstance(pos, OrderPosition) else pos.pk, # all other places are only used for positions that can be grouped. We just put zeros. ) + (0, ) * 10 # positions are sorted and grouped by various attributes category_key = (pos.item.category.position, pos.item.category.id) if pos.item.category is not None else (0, 0) item_key = pos.item.position, pos.item_id variation_key = (pos.variation.position, pos.variation.id) if pos.variation is not None else (0, 0) grp = category_key + item_key + variation_key + (pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0), (pos.seat_id or 0)) if pos.addon_to_id: if for_sorting: ii = pos.positionid if isinstance(pos, OrderPosition) else pos.pk else: ii = 0 return ( i, addon_penalty, ii, ) + category_key + item_key + variation_key + (pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0), (pos.seat_id or 0)) return ( # These are grouped by attributes so we don't put any position ids 0, 0, 0, ) + grp positions = [] for k, g in groupby(sorted(lcp, key=partial(keyfunc, for_sorting=True)), key=keyfunc): g = list(g) group = g[0] group.count = len(g) group.total = group.count * group.price group.net_total = group.count * group.net_price group.has_questions = answers and k[0] != "" if not hasattr(group, 'tax_rule'): group.tax_rule = group.item.tax_rule group.bundle_sum = group.price + sum(a.price for a in has_addons[group.pk]) group.bundle_sum_net = group.net_price + sum(a.net_price for a in has_addons[group.pk]) if answers: group.cache_answers(all=False) group.additional_answers = pos_additional_fields.get(group.pk) positions.append(group) total = sum(p.total for p in positions) net_total = sum(p.net_total for p in positions) tax_total = sum(p.total - p.net_total for p in positions) if order: fees = order.fees.all() elif positions: fees = get_fees( self.request.event, self.request, total, self.invoice_address, self.cart_session.get('payment'), cartpos ) else: fees = [] total += sum([f.value for f in fees]) net_total += sum([f.net_value for f in fees]) tax_total += sum([f.tax_value for f in fees]) try: first_expiry = min(p.expires for p in positions) if positions else now() total_seconds_left = max(first_expiry - now(), timedelta()).total_seconds() minutes_left = int(total_seconds_left // 60) seconds_left = int(total_seconds_left % 60) except AttributeError: first_expiry = None minutes_left = None seconds_left = None return { 'positions': positions, 'invoice_address': self.invoice_address, 'all_with_voucher': all(p.voucher_id for p in positions), 'raw': cartpos, 'total': total, 'net_total': net_total, 'tax_total': tax_total, 'fees': fees, 'answers': answers, 'minutes_left': minutes_left, 'seconds_left': seconds_left, 'first_expiry': first_expiry, 'is_ordered': bool(order), 'itemcount': sum(c.count for c in positions if not c.addon_to) }
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 = list(item.questions.all()) event = kwargs.pop('event') super().__init__(*args, **kwargs) if item.admission and event.settings.attendee_names_asked: self.fields['attendee_name'] = forms.CharField( max_length=255, required=event.settings.attendee_names_required, label=_('Attendee name'), initial=(cartpos.attendee_name if cartpos else orderpos.attendee_name), widget=forms.TextInput(attrs={'data-typocheck-source': '1'}), ) if item.admission and event.settings.attendee_emails_asked: self.fields['attendee_email'] = forms.EmailField( required=event.settings.attendee_emails_required, label=_('Attendee email'), initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email)) for q in questions: # Do we already have an answer? Provide it as the initial value answers = [ a for a in (cartpos.answers.all() if cartpos else orderpos.answers.all()) if a.question_id == q.id ] if answers: initial = answers[0] else: initial = None 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=q.question, required=q.required, help_text=q.help_text, initial=initialbool, widget=widget, ) elif q.type == Question.TYPE_NUMBER: field = forms.DecimalField( label=q.question, required=q.required, help_text=q.help_text, initial=initial.answer if initial else None, min_value=Decimal('0.00')) elif q.type == Question.TYPE_STRING: field = forms.CharField( label=q.question, required=q.required, help_text=q.help_text, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_TEXT: field = forms.CharField( label=q.question, required=q.required, help_text=q.help_text, widget=forms.Textarea, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_CHOICE: field = forms.ModelChoiceField( queryset=q.options.all(), label=q.question, required=q.required, help_text=q.help_text, widget=forms.RadioSelect, initial=initial.options.first() if initial else None, ) elif q.type == Question.TYPE_CHOICE_MULTIPLE: field = forms.ModelMultipleChoiceField( queryset=q.options.all(), label=q.question, required=q.required, help_text=q.help_text, widget=forms.CheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = forms.FileField( label=q.question, required=q.required, help_text=q.help_text, initial=initial.file if initial else None, widget=UploadedFileWidget(position=pos, event=event, answer=initial)) field.question = q if answers: # Cache the answer object for later use field.answer = answers[0] self.fields['question_%s' % q.id] = field 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)
def get_cart(self, answers=False, queryset=None, order=None, downloads=False): if queryset: prefetch = [] if answers: prefetch.append('item__questions') prefetch.append('answers') cartpos = queryset.order_by('item', 'variation').select_related( 'item', 'variation', 'addon_to', 'subevent', 'subevent__event', 'subevent__event__organizer').prefetch_related(*prefetch) else: cartpos = self.positions lcp = list(cartpos) has_addons = {cp.addon_to.pk for cp in lcp if cp.addon_to} pos_additional_fields = defaultdict(list) for cp in lcp: responses = question_form_fields.send(sender=self.request.event, position=cp) data = cp.meta_info_data for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): pos_additional_fields[cp.pk].append({ 'answer': data.get('question_form_data', {}).get(key), 'question': value.label }) # Group items of the same variation # We do this by list manipulations instead of a GROUP BY query, as # Django is unable to join related models in a .values() query def keyfunc(pos): if isinstance(pos, OrderPosition): if pos.addon_to: i = pos.addon_to.positionid else: i = pos.positionid else: if pos.addon_to: i = pos.addon_to.pk else: i = pos.pk has_attendee_data = pos.item.admission and ( self.request.event.settings.attendee_names_asked or self.request.event.settings.attendee_emails_asked or pos_additional_fields.get(pos.pk)) addon_penalty = 1 if pos.addon_to else 0 if downloads or pos.pk in has_addons or pos.addon_to: return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) if answers and (has_attendee_data or pos.item.questions.all()): return i, addon_penalty, pos.pk, 0, 0, 0, 0, (pos.subevent_id or 0) return (0, addon_penalty, 0, pos.item_id, pos.variation_id, pos.price, (pos.voucher_id or 0), (pos.subevent_id or 0)) positions = [] for k, g in groupby(sorted(lcp, key=keyfunc), key=keyfunc): g = list(g) group = g[0] group.count = len(g) group.total = group.count * group.price group.net_total = group.count * group.net_price group.has_questions = answers and k[0] != "" group.tax_rule = group.item.tax_rule if answers: group.cache_answers() group.additional_answers = pos_additional_fields.get(group.pk) positions.append(group) total = sum(p.total for p in positions) net_total = sum(p.net_total for p in positions) tax_total = sum(p.total - p.net_total for p in positions) if order: payment_fee = order.payment_fee tax_total += order.payment_fee_tax_value payment_fee_net = order.payment_fee - order.payment_fee_tax_value net_total += payment_fee_net payment_fee_tax_rule = order.payment_fee_tax_rule payment_fee_tax_rate = order.payment_fee_tax_rate else: payment_fee = self.get_payment_fee(total) payment_fee_tax_rule = self.request.event.settings.tax_rate_default or TaxRule.zero( ) iapk = self.request.session.get('invoice_address_{}'.format( self.request.event.pk)) ia = None if payment_fee_tax_rule.eu_reverse_charge and iapk: try: ia = InvoiceAddress.objects.get(pk=iapk, order__isnull=True) except InvoiceAddress.DoesNotExist: pass if payment_fee_tax_rule.tax_applicable(ia): payment_fee_tax = payment_fee_tax_rule.tax( payment_fee, base_price_is='gross') tax_total += payment_fee_tax.tax net_total += payment_fee_tax.net payment_fee_net = payment_fee_tax.net payment_fee_tax_rate = payment_fee_tax.rate else: net_total += payment_fee payment_fee_net = payment_fee payment_fee_tax_rate = Decimal('0.00') try: first_expiry = min(p.expires for p in positions) if positions else now() minutes_left = max(first_expiry - now(), timedelta()).seconds // 60 except AttributeError: first_expiry = None minutes_left = None return { 'positions': positions, 'raw': cartpos, 'total': total + payment_fee, 'net_total': net_total, 'tax_total': tax_total, 'payment_fee': payment_fee, 'payment_fee_net': payment_fee_net, 'payment_fee_tax_rate': payment_fee_tax_rate, 'payment_fee_tax_rule': payment_fee_tax_rule, 'answers': answers, 'minutes_left': minutes_left, 'first_expiry': first_expiry, }
def is_completed(self, request, warn=False): self.request = request try: emailval = EmailValidator() if not self.cart_session.get('email') and not self.all_optional: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if self.cart_session.get('email'): emailval(self.cart_session.get('email')) except ValidationError: if warn: messages.warning(request, _('Please enter a valid email address.')) return False if not self.all_optional: if self.address_asked: if request.event.settings.invoice_address_required and (not self.invoice_address or not self.invoice_address.street): messages.warning(request, _('Please enter your invoicing address.')) return False if request.event.settings.invoice_name_required and (not self.invoice_address or not self.invoice_address.name): messages.warning(request, _('Please enter your name.')) return False for cp in self._positions_for_questions: answ = { aw.question_id: aw for aw in cp.answerlist } question_cache = { q.pk: q for q in cp.item.questions_to_ask } def question_is_visible(parentid, qval): parentq = question_cache[parentid] if parentq.dependency_question_id and not question_is_visible(parentq.dependency_question_id, parentq.dependency_value): return False if parentid not in answ: return False if qval == 'True': return answ[parentid].answer == 'True' elif qval == 'False': return answ[parentid].answer == 'False' else: return qval in [o.identifier for o in answ[parentid].options.all()] def question_is_required(q): return ( q.required and (not q.dependency_question_id or question_is_visible(q.dependency_question_id, q.dependency_value)) ) for q in cp.item.questions_to_ask: if question_is_required(q) and not answ.get(q.id): if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_names_required', as_type=bool) \ and not cp.attendee_name_parts: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False if cp.item.admission and self.request.event.settings.get('attendee_emails_required', as_type=bool) \ and cp.attendee_email is None: if warn: messages.warning(request, _('Please fill in answers to all required questions.')) return False responses = question_form_fields.send(sender=self.request.event, position=cp) form_data = cp.meta_info_data.get('question_form_data', {}) for r, response in sorted(responses, key=lambda r: str(r[0])): for key, value in response.items(): if value.required and not form_data.get(key): return False return True
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') super().__init__(*args, **kwargs) if item.admission and event.settings.attendee_names_asked: self.fields['attendee_name_parts'] = NamePartsFormField( max_length=255, required=event.settings.attendee_names_required, scheme=event.settings.name_scheme, label=_('Attendee name'), initial=(cartpos.attendee_name_parts if cartpos else orderpos.attendee_name_parts), ) if item.admission and event.settings.attendee_emails_asked: self.fields['attendee_email'] = forms.EmailField( required=event.settings.attendee_emails_required, label=_('Attendee email'), initial=(cartpos.attendee_email if cartpos else orderpos.attendee_email) ) 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) 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=q.question, required=q.required, help_text=help_text, initial=initialbool, widget=widget, ) elif q.type == Question.TYPE_NUMBER: field = forms.DecimalField( label=q.question, required=q.required, help_text=q.help_text, initial=initial.answer if initial else None, min_value=Decimal('0.00'), ) elif q.type == Question.TYPE_STRING: field = forms.CharField( label=q.question, required=q.required, help_text=help_text, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_TEXT: field = forms.CharField( label=q.question, required=q.required, help_text=help_text, widget=forms.Textarea, initial=initial.answer if initial else None, ) elif q.type == Question.TYPE_CHOICE: field = forms.ModelChoiceField( queryset=q.options, label=q.question, required=q.required, help_text=help_text, widget=forms.Select, empty_label='', initial=initial.options.first() if initial else None, ) elif q.type == Question.TYPE_CHOICE_MULTIPLE: field = forms.ModelMultipleChoiceField( queryset=q.options, label=q.question, required=q.required, help_text=help_text, widget=forms.CheckboxSelectMultiple, initial=initial.options.all() if initial else None, ) elif q.type == Question.TYPE_FILE: field = forms.FileField( label=q.question, required=q.required, help_text=help_text, initial=initial.file if initial else None, widget=UploadedFileWidget(position=pos, event=event, answer=initial), ) elif q.type == Question.TYPE_DATE: field = forms.DateField( label=q.question, required=q.required, help_text=help_text, initial=dateutil.parser.parse(initial.answer).date() if initial and initial.answer else None, widget=DatePickerWidget(), ) elif q.type == Question.TYPE_TIME: field = forms.TimeField( label=q.question, required=q.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=q.question, required=q.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')), ) field.question = q if answers: # Cache the answer object for later use field.answer = answers[0] self.fields['question_%s' % q.id] = field 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)