Exemplo n.º 1
0
    def __init__(self, request, step, data=None, initial=None, 
        checkout_errors=None):
        """
        Handle setting shipping field values to the same as billing field 
        values in case Javascript is disabled, hiding fields for current step 
        and hiding discount_code field if there are currently no active 
        discount codes.
        """

        # Copy billing fields to shipping fields if "same" checked.
        if (step == CHECKOUT_STEP_FIRST and data is not None and 
            "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when moving
            # backwards in steps.
            data["step"] = step 
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is required to
        # allow moving backwards in steps. 
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step
            
        super(OrderForm, self).__init__(data=data, initial=initial)
        self._request = request
        self._checkout_errors = checkout_errors

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if step == CHECKOUT_STEP_FIRST:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: f.startswith("card_")
            elif step == CHECKOUT_STEP_PAYMENT:
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and \
            step == CHECKOUT_STEP_LAST:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False
            
        # Hide Discount Code field if no codes are active.
        if DiscountCode.objects.active().count() == 0:
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Set the choices for the cc expiry year relative to the current year.
        year = datetime.now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 2
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        OrderForm.__init__(self, request, step, data, initial, errors)

        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT

        # Get list of country names
        countries = make_choices(get_country_names_list())
    
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Change country widgets to a Select widget
                self.fields["billing_detail_country"].widget = forms.Select(choices=countries)
                self.fields["billing_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY
                self.fields["shipping_detail_country"].widget = forms.Select(choices=countries)
                self.fields["shipping_detail_country"].initial= settings.SHOP_DEFAULT_COUNTRY
            if is_payment_step:
                # Make card number and cvv fields use the data encrypted widget
                self.fields["card_number"].widget = DataEncryptedTextInput()
                self.fields["card_ccv"].widget = DataEncryptedPasswordInput()
        else:
            # Change country widgets to a Select widget
            self.fields["billing_detail_country"].widget = forms.Select(choices=countries)
            self.fields["billing_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY            
            self.fields["shipping_detail_country"].widget = forms.Select(choices=countries)
            self.fields["shipping_detail_country"].initial= settings.SHOP_DEFAULT_COUNTRY
            if settings.SHOP_PAYMENT_STEP_ENABLED:
                # Make card number and cvv fields use the data encrypted widget
                self.fields["card_number"].widget = DataEncryptedTextInput()
                self.fields["card_ccv"].widget = DataEncryptedPasswordInput()
Exemplo n.º 3
0
 def __init__(self, *args, **kwargs):
     """
     Set the choices for each of the fields for product options, hiding them 
     if no choices exist.
     """
     super(ProductAdminForm, self).__init__(*args, **kwargs)
     for field, options in ProductOption.objects.as_fields().items():
         self.fields[field].choices = make_choices(options)
Exemplo n.º 4
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Handle setting shipping field values to the same as billing
        field values in case JavaScript is disabled, hiding fields for
        the current step.
        """

        # Copy billing fields to shipping fields if "same" checked.
        first = step == checkout.CHECKOUT_STEP_FIRST
        last = step == checkout.CHECKOUT_STEP_LAST
        if (first and data is not None and "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when
            # moving backwards in steps.
            data["step"] = step
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is
        # required to allow moving backwards in steps.
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors
        settings.use_editable()
        # Hide Discount Code field if no codes are active.
        if (DiscountCode.objects.active().count() == 0
                or not settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if first:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: f.startswith("card_")
            elif step == checkout.CHECKOUT_STEP_PAYMENT:
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and last:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set the choices for the cc expiry year relative to the current year.
        year = datetime.now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 5
0
class StripeOrderForm(OrderForm):
    """
    Derived order form that implements nameless cc and stripeToken inputs.

    Derived form class for Order model.  
    """
    # "required" is a core Field argument, and is used by the Field.clean method.
    # if required=False, then clean does not raise a ValdationError on empty values.
    # clean raises this error or returns a cleaned value
    card_number = forms.CharField(
        label=_("Card number"),
        required=False,
        widget=NamelessTextInput(attrs={"data-stripe": "number"}))

    card_expiry_month = forms.ChoiceField(
        label=_("Card expiry month"),
        initial="%02d" % date.today().month,
        choices=make_choices(["%02d" % i for i in range(1, 13)]),
        required=False,
        widget=NamelessSelect(attrs={"data-stripe": "exp-month"}))

    card_expiry_year = forms.ChoiceField(
        label=_("Card expiry year"),
        required=False,
        widget=NamelessSelect(attrs={'data-stripe': 'exp-year'}))

    card_ccv = forms.CharField(
        label=_("CCV"),
        help_text=_(
            "A security code, "
            "usually the last 3 digits found on the back of your card."),
        required=False,
        widget=NamelessTextInput(attrs={"data-stripe": "cvc"}))

    stripeToken = forms.CharField(widget=forms.HiddenInput())

    @classmethod
    def preprocess(cls, data):
        """
        A preprocessor for the order form data, insets stripeToken. 

        The default preprocessor that is called from super handles
        copying billing fields to shipping fields if "same" checked. StripeToken defaults to None.
        """
        data = super(StripeOrderForm, cls).preprocess(data)

        # The stripeToken form field is required, hence fill it with something.
        if ("stripeToken" in data) and (len(data["stripeToken"]) == 0):
            data["stripeToken"] = "None"

        return data
Exemplo n.º 6
0
    def __init__(self, *args, **kwargs):
        """
        Handles adding a variation to the cart or wishlist.

        When adding from the product page, the product is provided
        from the view and a set of choice fields for all the
        product options for this product's variations are added to
        the form. When the form is validated, the selected options
        are used to determine the chosen variation.

        A ``to_cart`` boolean keyword arg is also given specifying
        whether the product is being added to a cart or wishlist.
        If a product is being added to the cart, then its stock
        level is also validated.

        When adding to the cart from the wishlist page, a sku is
        given for the variation, so the creation of choice fields
        is skipped.
        """
        self._product = kwargs.pop("product", None)
        self._to_cart = kwargs.pop("to_cart")
        super(AddProductForm, self).__init__(*args, **kwargs)
        # Adding from the wishlist with a sku, bail out.
        if args[0] is not None and args[0].get("sku", None):
            return
        # Adding from the product page, remove the sku field
        # and build the choice fields for the variations.
        del self.fields["sku"]
        option_fields = ProductVariation.option_fields()
        if not option_fields:
            return
        option_names, option_labels = list(
            zip(*[(f.name, f.verbose_name) for f in option_fields]))
        option_values = list(
            zip(*self._product.variations.filter(
                unit_price__isnull=False).values_list(*option_names)))
        if option_values:
            for i, name in enumerate(option_names):
                values = [_f for _f in set(option_values[i]) if _f]
                if values:
                    field = forms.ChoiceField(label=option_labels[i],
                                              choices=make_choices(values))
                    self.fields[name] = field
        if self._product.content_model == 'reservableproduct':
            # ReservableProduct needs from/to dates and does not need quantity
            self.fields["from_date"] = forms.DateField(
                input_formats=["%d.%m.%Y"], widget=forms.HiddenInput())
            self.fields["to_date"] = forms.DateField(
                input_formats=["%d.%m.%Y"], widget=forms.HiddenInput())
            self.fields["quantity"] = forms.IntegerField(
                min_value=1, initial=1, widget=forms.HiddenInput())
Exemplo n.º 7
0
 def __init__(self, *args, **kwargs):
     """
     Set the choices for each of the fields for product options.
     Also remove the current instance from choices for related and
     upsell products.
     """
     super(ProductAdminForm, self).__init__(*args, **kwargs)
     for field, options in ProductOption.objects.as_fields().items():
         self.fields[field].choices = make_choices(options)
     instance = kwargs.get("instance")
     if instance:
         queryset = Product.objects.exclude(id=instance.id)
         self.fields["related_products"].queryset = queryset
         self.fields["upsell_products"].queryset = queryset
Exemplo n.º 8
0
 def __init__(self, *args, **kwargs):
     """
     Set the choices for each of the fields for product options.
     Also remove the current instance from choices for related and
     upsell products.
     """
     super(ProductAdminForm, self).__init__(*args, **kwargs)
     for field, options in ProductOption.objects.as_fields().items():
         self.fields[field].choices = make_choices(options)
     instance = kwargs.get("instance")
     if instance:
         queryset = Product.objects.exclude(id=instance.id)
         self.fields["related_products"].queryset = queryset
         self.fields["upsell_products"].queryset = queryset
Exemplo n.º 9
0
 def __init__(self, *args, **kwargs):
     """
     Set the choices for each of the fields for product options.
     Also remove the current instance from choices for related and
     upsell products (if enabled).
     """
     super(ProductAdminForm, self).__init__(*args, **kwargs)
     for field, options in list(ProductOption.objects.as_fields().items()):
         self.fields[field].choices = make_choices(options)
     instance = kwargs.get("instance")
     if instance:
         queryset = Product.objects.exclude(id=instance.id)
         if settings.SHOP_USE_RELATED_PRODUCTS:
             self.fields["related_products"].queryset = queryset
         if settings.SHOP_USE_UPSELL_PRODUCTS:
             self.fields["upsell_products"].queryset = queryset
Exemplo n.º 10
0
 def __init__(self, *args, **kwargs):
     """
     Set the choices for each of the fields for product options.
     Also remove the current instance from choices for related and
     upsell products (if enabled).
     """
     super(ProductAdminForm, self).__init__(*args, **kwargs)
     for field, options in list(ProductOption.objects.as_fields().items()):
         self.fields[field].choices = make_choices(options)
     instance = kwargs.get("instance")
     if instance:
         queryset = Product.objects.exclude(id=instance.id)
         if settings.SHOP_USE_RELATED_PRODUCTS:
             self.fields["related_products"].queryset = queryset
         if settings.SHOP_USE_UPSELL_PRODUCTS:
             self.fields["upsell_products"].queryset = queryset
Exemplo n.º 11
0
    def __init__(self, *args, **kwargs):
        """
        Handles adding a variation to the cart or wishlist.

        When adding from the product page, the product is provided
        from the view and a set of choice fields for all the
        product options for this product's variations are added to
        the form. When the form is validated, the selected options
        are used to determine the chosen variation.

        A ``to_cart`` boolean keyword arg is also given specifying
        whether the product is being added to a cart or wishlist.
        If a product is being added to the cart, then its stock
        level is also validated.

        When adding to the cart from the wishlist page, a sku is
        given for the variation, so the creation of choice fields
        is skipped.
        """
        self._product = kwargs.pop("product", None)
        self._to_cart = kwargs.pop("to_cart")
        super(AddProductForm, self).__init__(*args, **kwargs)
        # Adding from the wishlist with a sku, bail out.
        if args[0] is not None and args[0].get("sku", None):
            return
        # Adding from the product page, remove the sku field
        # and build the choice fields for the variations.
        del self.fields["sku"]
        option_fields = ProductVariation.option_fields()
        if not option_fields:
            return
        option_names, option_labels = list(zip(*[(f.name, f.verbose_name)
            for f in option_fields]))
        option_values = list(zip(*self._product.variations.filter(
            unit_price__isnull=False).values_list(*option_names)))
        if option_values:
            for i, name in enumerate(option_names):
                values = [_f for _f in set(option_values[i]) if _f]
                if values:
                    field = forms.ChoiceField(label=option_labels[i],
                                              choices=make_choices(values))
                    self.fields[name] = field
        if self._product.content_model == 'reservableproduct':
            # ReservableProduct needs from/to dates and does not need quantity
            self.fields["from_date"] = forms.DateField(input_formats=["%d.%m.%Y"], widget=forms.HiddenInput())
            self.fields["to_date"] = forms.DateField(input_formats=["%d.%m.%Y"], widget=forms.HiddenInput())
            self.fields["quantity"] = forms.IntegerField(min_value=1, initial=1, widget=forms.HiddenInput())
Exemplo n.º 12
0
    def __init__(self, *args, **kwargs):
        """
        Handles adding a variation to the cart or wishlist.

        When adding from the product page, the product is provided
        from the view and a set of choice fields for all the
        product options for this product's variations are added to
        the form. When the form is validated, the selected options
        are used to determine the chosen variation.

        A ``to_cart`` boolean keyword arg is also given specifying
        whether the product is being added to a cart or wishlist.
        If a product is being added to the cart, then its stock
        level is also validated.

        When adding to the cart from the wishlist page, a sku is
        given for the variation, so the creation of choice fields
        is skipped.
        """
        self._product = kwargs.pop("product", None)
        self._to_cart = kwargs.pop("to_cart")
        super(AddProductForm, self).__init__(*args, **kwargs)
        # Adding from the wishlist with a sku, bail out.
        if args[0] is not None and args[0].get("sku", None):
            return
        # Adding from the product page, remove the sku field
        # and build the choice fields for the variations.
        del self.fields["sku"]
        option_fields = ProductVariation.option_fields()
        if not option_fields:
            return
        option_names, option_labels = zip(*[(f.name, f.verbose_name)
                                            for f in option_fields])
        option_values = zip(*self._product.variations.filter(
            unit_price__isnull=False).values_list(*option_names))
        if option_values:
            for i, name in enumerate(option_names):
                values = list(option_values[i])
                try:
                    values.remove(None)
                except ValueError:
                    pass
                if values:
                    field = forms.ChoiceField(label=option_labels[i],
                                              choices=make_choices(values))
                    self.fields[name] = field
Exemplo n.º 13
0
    def __init__(self, *args, **kwargs):
        """
        Handles adding a variation to the cart or wishlist.

        When adding from the product page, the product is provided
        from the view and a set of choice fields for all the
        product options for this product's variations are added to
        the form. When the form is validated, the selected options
        are used to determine the chosen variation.

        A ``to_cart`` boolean keyword arg is also given specifying
        whether the product is being added to a cart or wishlist.
        If a product is being added to the cart, then its stock
        level is also validated.

        When adding to the cart from the wishlist page, a sku is
        given for the variation, so the creation of choice fields
        is skipped.
        """
        self._product = kwargs.pop("product", None)
        self._to_cart = kwargs.pop("to_cart")
        super(AddProductForm, self).__init__(*args, **kwargs)
        # Adding from the wishlist with a sku, bail out.
        if args[0] is not None and args[0].get("sku", None):
            return
        # Adding from the product page, remove the sku field
        # and build the choice fields for the variations.
        del self.fields["sku"]
        option_fields = ProductVariation.option_fields()
        if not option_fields:
            return
        option_names, option_labels = zip(*[(f.name, f.verbose_name)
            for f in option_fields])
        option_values = zip(*self._product.variations.filter(
            unit_price__isnull=False).values_list(*option_names))
        if option_values:
            for i, name in enumerate(option_names):
                values = list(option_values[i])
                try:
                    values.remove(None)
                except ValueError:
                    pass
                if values:
                    field = forms.ChoiceField(label=option_labels[i],
                                              choices=make_choices(values))
                    self.fields[name] = field
Exemplo n.º 14
0
 def __init__(self, *args, **kwargs):
     """
     Create each ChoiceField for selecting from the product's variations.
     """
     self._to_cart = kwargs.pop("to_cart", True)
     super(AddProductForm, self).__init__(*args, **kwargs)
     option_names, option_labels = zip(*[(f.name, f.verbose_name) 
         for f in ProductVariation.option_fields()])
     option_values = zip(*product.variations.filter(
         unit_price__isnull=False).values_list(*option_names))
     if option_values:
         for i, name in enumerate(option_names):
             values = filter(None, set(option_values[i]))
             if values:
                 field = forms.ChoiceField(label=option_labels[i], 
                     choices=make_choices(values))
                 self.fields[name] = field
Exemplo n.º 15
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        OrderForm.__init__(self, request, step, data, initial, errors)

        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT

        # Get list of country names
        countries = make_choices(get_country_names_list())

        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Change country widgets to a Select widget
                self.fields["billing_detail_country"].widget = forms.Select(
                    choices=countries)
                self.fields[
                    "billing_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY
                self.fields["shipping_detail_country"].widget = forms.Select(
                    choices=countries)
                self.fields[
                    "shipping_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY
            if is_payment_step:
                # Make card number and cvv fields use the data encrypted widget
                self.fields["card_number"].widget = DataEncryptedTextInput()
                self.fields["card_ccv"].widget = DataEncryptedPasswordInput()
        else:
            # Change country widgets to a Select widget
            self.fields["billing_detail_country"].widget = forms.Select(
                choices=countries)
            self.fields[
                "billing_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY
            self.fields["shipping_detail_country"].widget = forms.Select(
                choices=countries)
            self.fields[
                "shipping_detail_country"].initial = settings.SHOP_DEFAULT_COUNTRY
            if settings.SHOP_PAYMENT_STEP_ENABLED:
                # Make card number and cvv fields use the data encrypted widget
                self.fields["card_number"].widget = DataEncryptedTextInput()
                self.fields["card_ccv"].widget = DataEncryptedPasswordInput()
Exemplo n.º 16
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Setup for each order form step which does a few things:

        - Calls OrderForm.preprocess on posted data
        - Sets up any custom checkout errors
        - Hides the discount code field if applicable
        - Hides sets of fields based on the checkout step
        - Sets year choices for cc expiry field based on current date
        """

        # ``data`` is usually the POST attribute of a Request object,
        # which is an immutable QueryDict. We want to modify it, so we
        # need to make a copy.
        data = copy(data)

        # Force the specified step in the posted data, which is
        # required to allow moving backwards in steps. Also handle any
        # data pre-processing, which subclasses may override.
        if data is not None:
            data["step"] = step
            data = self.preprocess(data)
        if initial is not None:
            initial["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.use_editable()
        if not (settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT
                and DiscountCode.objects.active().exists()):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        # A ``hidden_filter`` function is defined that's used for
        # filtering out the fields to hide.
        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT
        hidden_filter = lambda f: False
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Hide cc fields for billing/shipping if steps are split.
                hidden_filter = lambda f: f.startswith("card_")
            elif is_payment_step:
                # Hide non-cc fields for payment if steps are split.
                hidden_filter = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all cc fields if payment step is not enabled.
            hidden_filter = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and is_last_step:
            # Hide all fields for the confirmation step.
            hidden_filter = lambda f: True
        for field in filter(hidden_filter, self.fields):
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].required = False
        if settings.SHOP_ALWAYS_SAME_BILLING_SHIPPING:
            for field in self.fields:
                if field == 'same_billing_shipping' or field.startswith(
                        'shipping_'):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set year choices for cc expiry, relative to the current year.
        year = now().year
        choices = make_choices(list(range(year, year + 21)))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 17
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Handle setting shipping field values to the same as billing
        field values in case JavaScript is disabled, hiding fields for
        the current step.
        """
        get_saved_card = True

        # Copy billing fields to shipping fields if "same" checked.
        first = step == checkout.CHECKOUT_STEP_FIRST
        last = step == checkout.CHECKOUT_STEP_LAST
        if (first and data is not None and "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when
            # moving backwards in steps.
            data["step"] = step
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is
        # required to allow moving backwards in steps.
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors
        settings.use_editable()
        # Hide Discount Code field if no codes are active.
        if (DiscountCode.objects.active().count() == 0 or
            not settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if first:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: (f.startswith("card_")
                                    or f == "stripe_token"
                                    or f == "last_4_digits"
                                    or f == "use_saved_card")
                get_saved_card = False
            elif step == checkout.CHECKOUT_STEP_PAYMENT:
                # TODO: If this user has a stripe customer object, get it and the saved card
                # Allow user to use saved card.
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not (f.startswith("card_")
                                        or f == "stripe_token"
                                        or f == "last_4_digits")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all the cc fields if payment step is not enabled.
            hidden = lambda f: (f.startswith("card_")
                                or f == "stripe_token"
                                or f == "last_4_digits"
                                or f == "use_saved_card")
            get_saved_card = False
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and last:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        
        hide_saved_card = True
        hide_card_save_options = True
        if not settings.ALLOW_SAVED_CREDIT_CARDS:
            get_saved_card = False

        if get_saved_card:
            #Get stripe customer and their saved card
            if request.user.is_authenticated():
                User = request.user
                customer = getStripeCustomerFromUser(User)
                hide_card_save_options = False
                if customer.cards.count > 0:
                    brand,last4 = getBrandAndLast4OfDefaultCard(customer)
                    if brand is not None and last4 is not None:
                        self.fields['use_saved_card'].label = "Use {} ending in {}".format(brand, last4)
                        hide_saved_card = False

        if hide_saved_card:
            self.fields['use_saved_card'].widget = forms.HiddenInput()
        if hide_card_save_options:
            self.fields['card_save_card'].widget = forms.HiddenInput()


        # Set the choices for the cc expiry year relative to the current year.
        year = now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 18
0
class OrderForm(FormsetForm, DiscountForm):
    """
    Main Form for the checkout process - ModelForm for the Order Model
    with extra fields for credit card. Used across each step of the
    checkout process with fields being hidden where applicable.
    """

    step = forms.IntegerField(widget=forms.HiddenInput())
    same_billing_shipping = forms.BooleanField(
        required=False,
        initial=True,
        label=_("My delivery details are the same as my billing details"))
    remember = forms.BooleanField(required=False,
                                  initial=True,
                                  label=_("Remember my address for next time"))
    card_name = forms.CharField(label=_("Cardholder name"))
    card_type = forms.ChoiceField(label=_("Card type"),
                                  widget=forms.RadioSelect,
                                  choices=make_choices(
                                      settings.SHOP_CARD_TYPES))
    card_number = forms.CharField(label=_("Card number"))
    card_expiry_month = forms.ChoiceField(
        label=_("Card expiry month"),
        initial="%02d" % date.today().month,
        choices=make_choices(["%02d" % i for i in range(1, 13)]))
    card_expiry_year = forms.ChoiceField(label=_("Card expiry year"))
    card_ccv = forms.CharField(
        label=_("CCV"),
        help_text=_(
            "A security code, "
            "usually the last 3 digits found on the back of your card."))
    #import captcha
    #wni_captcha = captcha.fields.CaptchaField()
    prefer_time = WniChoiceField(label="配送时间",
                                 widget=forms.RadioSelect,
                                 choices=((u"立即送餐", u"立即送餐(1小时之内)"),
                                          (u"预约", u"预约送达时间")),
                                 initial=u"立即送餐")
    #pay_type=WniChoiceField(label="支付方式",widget=forms.RadioSelect,choices=((u"货到付款",u"货到付款"),),initial=u"货到付款")
    pay_type = WniChoiceField(label="支付方式",
                              widget=forms.RadioSelect,
                              choices=((u"货到付款", u"货到付款"), (u"微信支付", u"微信支付")),
                              initial=u"货到付款")
    delivery_type = WniComplexChoiceField(
        label="配送方式",
        wnichoices=settings.CENTER_POTS['Nanjing'])  #,initial=u"efoodin冷链配送")

    class Meta:
        model = Order
        """
        fields = ([f.name for f in Order._meta.fields if
                   f.name.startswith("billing_detail") or
                   f.name.startswith("shipping_detail")] +
                   ["additional_instructions", "discount_code"])
        
        """

        fields = ([
            f.name for f in Order._meta.fields if f.name in [
                "billing_detail_first_name", "billing_detail_phone",
                "billing_detail_email", "billing_detail_street", "prefer_time",
                "delivery_type", "pay_type", "shipping_detail_first_name",
                "shipping_detail_phone", "shipping_detail_street"
            ]
        ] + ["additional_instructions", "discount_code"])
        """
        fields = ([f.name for f in Order._meta.fields if
                   f.name in ["billing_detail_first_name","billing_detail_phone","billing_detail_email"] ] +
                   ["additional_instructions", "discount_code"])
        """

    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Setup for each order form step which does a few things:

        - Calls OrderForm.preprocess on posted data
        - Sets up any custom checkout errors
        - Hides the discount code field if applicable
        - Hides sets of fields based on the checkout step
        - Sets year choices for cc expiry field based on current date
        """

        # ``data`` is usually the POST attribute of a Request object,
        # which is an immutable QueryDict. We want to modify it, so we
        # need to make a copy.
        data = copy(data)

        # Force the specified step in the posted data, which is
        # required to allow moving backwards in steps. Also handle any
        # data pre-processing, which subclasses may override.
        if data is not None:
            data["step"] = step
            data = self.preprocess(data)
        if initial is not None:
            initial["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.use_editable()
        if not (settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT
                and DiscountCode.objects.active().exists()):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        # A ``hidden_filter`` function is defined that's used for
        # filtering out the fields to hide.
        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT
        hidden_filter = lambda f: False
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Hide cc fields for billing/shipping if steps are split.
                hidden_filter = lambda f: f.startswith("card_")
            elif is_payment_step:
                # Hide non-cc fields for payment if steps are split.
                hidden_filter = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all cc fields if payment step is not enabled.
            hidden_filter = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and is_last_step:
            # Hide all fields for the confirmation step.
            hidden_filter = lambda f: True
        for field in filter(hidden_filter, self.fields):
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].required = False

        # Set year choices for cc expiry, relative to the current year.
        year = now().year
        choices = make_choices(list(range(year, year + 21)))
        self.fields["card_expiry_year"].choices = choices
        #self.fields['prefer_time'].widget = forms.DateTimeInput()
        #self.fields['prefer_time']=WniChoiceField(label="配送时间",widget=forms.RadioSelect,choices=((u"立即送餐",u"立即送餐"),(u"预约",u"预约")))
        #self.fields['prefer_time'].widget = forms.RadioSelect(choices=((u"立即送餐",u"立即送餐"),(u"预约",u"预约")))
        #self.fields['prefer_time'].widget = widgets.AdminSplitDateTime()

        if settings.WITH_WEIXIN_PAY and 'micromessenger' in request.META.get(
                'HTTP_USER_AGENT').lower():
            self.fields["pay_type"].choices = ((u"货到付款", u"货到付款"), (u"微信支付",
                                                                    u"微信支付"))
        else:
            self.fields["pay_type"].choices = ((u"货到付款", u"货到付款"), )

    @classmethod
    def preprocess(cls, data):
        """
        A preprocessor for the order form data that can be overridden
        by custom form classes. The default preprocessor here handles
        copying billing fields to shipping fields if "same" checked.
        """
        if data.get("same_billing_shipping",
                    "") == "on":  #wni: change on to shit
            for field in data:
                bill_field = field.replace("shipping_detail", "billing_detail")
                if field.startswith("shipping_detail") and bill_field in data:
                    data[field] = data[bill_field]
        return data

    def clean_card_expiry_year(self):
        """
        Ensure the card expiry doesn't occur in the past.
        """
        try:
            month = int(self.cleaned_data["card_expiry_month"])
            year = int(self.cleaned_data["card_expiry_year"])
        except ValueError:
            # Haven't reached payment step yet.
            return
        n = now()
        if year == n.year and month < n.month:
            raise forms.ValidationError(_("A valid expiry date is required."))
        return str(year)

    def clean(self):
        """
        Raise ``ValidationError`` if any errors have been assigned
        externally, via one of the custom checkout step handlers.
        """
        if self._checkout_errors:
            raise forms.ValidationError(self._checkout_errors)
        return super(OrderForm, self).clean()
Exemplo n.º 19
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Setup for each order form step which does a few things:

        - Calls OrderForm.preprocess on posted data
        - Sets up any custom checkout errors
        - Hides the discount code field if applicable
        - Hides sets of fields based on the checkout step
        - Sets year choices for cc expiry field based on current date
        """

        # ``data`` is usually the POST attribute of a Request object,
        # which is an immutable QueryDict. We want to modify it, so we
        # need to make a copy.
        data = copy(data)

        # Force the specified step in the posted data, which is
        # required to allow moving backwards in steps. Also handle any
        # data pre-processing, which subclasses may override.
        if data is not None:
            data["step"] = step
            data = self.preprocess(data)
        if initial is not None:
            initial["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.use_editable()
        if not (settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT and
                DiscountCode.objects.active().exists()):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        # A ``hidden_filter`` function is defined that's used for
        # filtering out the fields to hide.
        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT
        hidden_filter = lambda f: False
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Hide cc fields for billing/shipping if steps are split.
                hidden_filter = lambda f: f.startswith("card_")
            elif is_payment_step:
                # Hide non-cc fields for payment if steps are split.
                hidden_filter = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all cc fields if payment step is not enabled.
            hidden_filter = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and is_last_step:
            # Hide all fields for the confirmation step.
            hidden_filter = lambda f: True
        for field in filter(hidden_filter, self.fields):
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].required = False

        # Set year choices for cc expiry, relative to the current year.
        year = now().year
        choices = make_choices(list(range(year, year + 21)))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 20
0
class OrderForm(FormsetForm, DiscountForm):
    """
    Main Form for the checkout process - ModelForm for the Order Model
    with extra fields for credit card. Used across each step of the
    checkout process with fields being hidden where applicable.
    """

    step = forms.IntegerField(widget=forms.HiddenInput())
    same_billing_shipping = forms.BooleanField(required=False, initial=True,
        label=_("My delivery details are the same as my billing details"))
    remember = forms.BooleanField(required=False, initial=True,
        label=_("Remember my address for next time"))
    card_name = forms.CharField(label=_("Cardholder name"))
    card_type = forms.ChoiceField(widget=forms.RadioSelect,
        choices=make_choices(settings.SHOP_CARD_TYPES))
    card_number = forms.CharField()
    card_expiry_month = forms.ChoiceField(
        initial="%02d" % date.today().month,
        choices=make_choices(["%02d" % i for i in range(1, 13)]))
    card_expiry_year = forms.ChoiceField()
    card_ccv = forms.CharField(label="CCV", help_text=_("A security code, "
        "usually the last 3 digits found on the back of your card."))

    class Meta:
        model = Order
        fields = ([f.name for f in Order._meta.fields if
                   f.name.startswith("billing_detail") or
                   f.name.startswith("shipping_detail")] +
                   ["additional_instructions", "discount_code"])

    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Handle setting shipping field values to the same as billing
        field values in case JavaScript is disabled, hiding fields for
        the current step.
        """

        # Copy billing fields to shipping fields if "same" checked.
        first = step == checkout.CHECKOUT_STEP_FIRST
        last = step == checkout.CHECKOUT_STEP_LAST
        if (first and data is not None and "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when
            # moving backwards in steps.
            data["step"] = step
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is
        # required to allow moving backwards in steps.
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors
        settings.use_editable()
        # Hide Discount Code field if no codes are active.
        if (DiscountCode.objects.active().count() == 0 or
            not settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if first:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: f.startswith("card_")
            elif step == checkout.CHECKOUT_STEP_PAYMENT:
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all the cc fields if payment step is not enabled.
            hidden = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and last:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set the choices for the cc expiry year relative to the current year.
        year = now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices

    def clean_card_expiry_year(self):
        """
        Ensure the card expiry doesn't occur in the past.
        """
        try:
            month = int(self.cleaned_data["card_expiry_month"])
            year = int(self.cleaned_data["card_expiry_year"])
        except ValueError:
            # Haven't reached payment step yet.
            return
        n = now()
        if year == n.year and month < n.month:
            raise forms.ValidationError(_("A valid expiry date is required."))
        return str(year)

    def clean(self):
        """
        Raise ``ValidationError`` if any errors have been assigned
        externally, via one of the custom checkout step handlers.
        """
        if self._checkout_errors:
            raise forms.ValidationError(self._checkout_errors)
        return self.cleaned_data
Exemplo n.º 21
0
    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Handle setting shipping field values to the same as billing
        field values in case JavaScript is disabled, hiding fields for
        the current step.
        """

        # Copy billing fields to shipping fields if "same" checked.
        first = step == checkout.CHECKOUT_STEP_FIRST
        last = step == checkout.CHECKOUT_STEP_LAST
        if (first and data is not None and "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when
            # moving backwards in steps.
            data["step"] = step
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is
        # required to allow moving backwards in steps.
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors
        settings.use_editable()
        # Hide Discount Code field if no codes are active.
        if (DiscountCode.objects.active().count() == 0 or
            not settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if first:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: (f.startswith("card_")
                                    or f == "stripe_token"
                                    or f == "last_4_digits")
            elif step == checkout.CHECKOUT_STEP_PAYMENT:
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not (f.startswith("card_")
                                        or f == "stripe_token"
                                        or f == "last_4_digits")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all the cc fields if payment step is not enabled.
            hidden = lambda f: (f.startswith("card_")
                                or f == "stripe_token"
                                or f == "last_4_digits")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and last:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set the choices for the cc expiry year relative to the current year.
        year = now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices
Exemplo n.º 22
0
class OrderForm(FormsetForm, DiscountForm):
    """
    Main Form for the checkout process - ModelForm for the Order Model
    with extra fields for credit card. Used across each step of the
    checkout process with fields being hidden where applicable.
    """

    step = forms.IntegerField(widget=forms.HiddenInput())
    same_billing_shipping = forms.BooleanField(
        required=False,
        initial=True,
        label=_("My delivery details are the same as my billing details"))
    remember = forms.BooleanField(required=False,
                                  initial=True,
                                  label=_("Remember my address for next time"))
    card_name = forms.CharField(label=_("Cardholder name"))
    card_type = forms.ChoiceField(widget=forms.RadioSelect,
                                  choices=make_choices(
                                      settings.SHOP_CARD_TYPES))
    card_number = forms.CharField(required=False,
                                  max_length=20,
                                  widget=NoNameTextInput())
    card_cvv = forms.CharField(required=False,
                               max_length=4,
                               widget=NoNameTextInput())
    card_expiry_month = forms.ChoiceField(required=False,
                                          widget=NoNameSelect(),
                                          choices=MONTHS.iteritems())
    card_expiry_year = forms.ChoiceField(
        required=False,
        widget=NoNameSelect(),
        choices=options.ZEBRA_CARD_YEARS_CHOICES)
    last_4_digits = forms.CharField(required=True,
                                    min_length=4,
                                    max_length=4,
                                    widget=forms.HiddenInput())
    stripe_token = forms.CharField(required=True, widget=forms.HiddenInput())

    def addError(self, message):
        self._errors[NON_FIELD_ERRORS] = self.error_class([message])

    class Meta:
        model = Order
        fields = ([
            f.name
            for f in Order._meta.fields if f.name.startswith("billing_detail")
            or f.name.startswith("shipping_detail")
        ] + ["additional_instructions", "discount_code"])

    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Handle setting shipping field values to the same as billing
        field values in case JavaScript is disabled, hiding fields for
        the current step.
        """

        # Copy billing fields to shipping fields if "same" checked.
        first = step == checkout.CHECKOUT_STEP_FIRST
        last = step == checkout.CHECKOUT_STEP_LAST
        if (first and data is not None and "same_billing_shipping" in data):
            data = copy(data)
            # Prevent second copy occuring for forcing step below when
            # moving backwards in steps.
            data["step"] = step
            for field in data:
                billing = field.replace("shipping_detail", "billing_detail")
                if "shipping_detail" in field and billing in data:
                    data[field] = data[billing]

        if initial is not None:
            initial["step"] = step
        # Force the specified step in the posted data - this is
        # required to allow moving backwards in steps.
        if data is not None and int(data["step"]) != step:
            data = copy(data)
            data["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors
        settings.use_editable()
        # Hide Discount Code field if no codes are active.
        if (DiscountCode.objects.active().count() == 0
                or not settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        hidden = None
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if first:
                # Hide the cc fields for billing/shipping if steps are split.
                hidden = lambda f: (f.startswith("card_") or f ==
                                    "stripe_token" or f == "last_4_digits")
            elif step == checkout.CHECKOUT_STEP_PAYMENT:
                # Hide the non-cc fields for payment if steps are split.
                hidden = lambda f: not (f.startswith("card_") or f ==
                                        "stripe_token" or f == "last_4_digits")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all the cc fields if payment step is not enabled.
            hidden = lambda f: (f.startswith("card_") or f == "stripe_token" or
                                f == "last_4_digits")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and last:
            # Hide all fields for the confirmation step.
            hidden = lambda f: True
        if hidden is not None:
            for field in self.fields:
                if hidden(field):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set the choices for the cc expiry year relative to the current year.
        year = now().year
        choices = make_choices(range(year, year + 21))
        self.fields["card_expiry_year"].choices = choices

    def clean(self):
        """
        Raise ``ValidationError`` if any errors have been assigned
        externally, via one of the custom checkout step handlers.
        """
        if self._checkout_errors:
            raise forms.ValidationError(self._checkout_errors)
        return self.cleaned_data
Exemplo n.º 23
0
class OrderForm(FormsetForm, DiscountForm):
    """
    Main Form for the checkout process - ModelForm for the Order Model
    with extra fields for credit card. Used across each step of the
    checkout process with fields being hidden where applicable.
    """

    step = forms.IntegerField(widget=forms.HiddenInput())
    same_billing_shipping = forms.BooleanField(
        required=False,
        initial=True,
        label=_("My delivery details are the same as my billing details"))
    remember = forms.BooleanField(required=False,
                                  initial=True,
                                  label=_("Remember my address for next time"))
    card_name = forms.CharField(label=_("Cardholder name"))
    card_type = forms.ChoiceField(label=_("Card type"),
                                  widget=forms.RadioSelect,
                                  choices=make_choices(
                                      settings.SHOP_CARD_TYPES))
    card_number = forms.CharField(label=_("Card number"))
    card_expiry_month = forms.ChoiceField(
        label=_("Card expiry month"),
        initial="%02d" % date.today().month,
        choices=make_choices(["%02d" % i for i in range(1, 13)]))
    card_expiry_year = forms.ChoiceField(label=_("Card expiry year"))
    card_ccv = forms.CharField(
        label=_("CCV"),
        help_text=_(
            "A security code, "
            "usually the last 3 digits found on the back of your card."))

    class Meta:
        model = Order
        fields = ([
            f.name for f in Order._meta.fields
            if (f.name.startswith("billing_detail")
                or f.name.startswith("shipping_detail")) and f.name.replace(
                    "billing_detail_", "").replace("shipping_detail_", "")
            not in settings.SHOP_HIDE_BILLING_SHIPPING_FIELDS
        ] + [
            "additional_instructions", "discount_code", "persons_adults",
            "persons_childs"
        ])

    def __init__(self, request, step, data=None, initial=None, errors=None):
        """
        Setup for each order form step which does a few things:

        - Calls OrderForm.preprocess on posted data
        - Sets up any custom checkout errors
        - Hides the discount code field if applicable
        - Hides sets of fields based on the checkout step
        - Sets year choices for cc expiry field based on current date
        """

        # ``data`` is usually the POST attribute of a Request object,
        # which is an immutable QueryDict. We want to modify it, so we
        # need to make a copy.
        data = copy(data)

        # Force the specified step in the posted data, which is
        # required to allow moving backwards in steps. Also handle any
        # data pre-processing, which subclasses may override.
        if data is not None:
            data["step"] = step
            data = self.preprocess(data)
        if initial is not None:
            initial["step"] = step

        super(OrderForm, self).__init__(request, data=data, initial=initial)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.use_editable()
        if not (settings.SHOP_DISCOUNT_FIELD_IN_CHECKOUT
                and DiscountCode.objects.active().exists()):
            self.fields["discount_code"].widget = forms.HiddenInput()

        # Determine which sets of fields to hide for each checkout step.
        # A ``hidden_filter`` function is defined that's used for
        # filtering out the fields to hide.
        is_first_step = step == checkout.CHECKOUT_STEP_FIRST
        is_last_step = step == checkout.CHECKOUT_STEP_LAST
        is_payment_step = step == checkout.CHECKOUT_STEP_PAYMENT
        hidden_filter = lambda f: False
        if settings.SHOP_CHECKOUT_STEPS_SPLIT:
            if is_first_step:
                # Hide cc fields for billing/shipping if steps are split.
                hidden_filter = lambda f: f.startswith("card_")
            elif is_payment_step:
                # Hide non-cc fields for payment if steps are split.
                hidden_filter = lambda f: not f.startswith("card_")
        elif not settings.SHOP_PAYMENT_STEP_ENABLED:
            # Hide all cc fields if payment step is not enabled.
            hidden_filter = lambda f: f.startswith("card_")
        if settings.SHOP_CHECKOUT_STEPS_CONFIRMATION and is_last_step:
            # Hide all fields for the confirmation step.
            hidden_filter = lambda f: True
        for field in filter(hidden_filter, self.fields):
            self.fields[field].widget = forms.HiddenInput()
            self.fields[field].required = False
        if settings.SHOP_ALWAYS_SAME_BILLING_SHIPPING:
            for field in self.fields:
                if field == 'same_billing_shipping' or field.startswith(
                        'shipping_'):
                    self.fields[field].widget = forms.HiddenInput()
                    self.fields[field].required = False

        # Set year choices for cc expiry, relative to the current year.
        year = now().year
        choices = make_choices(list(range(year, year + 21)))
        self.fields["card_expiry_year"].choices = choices

    @classmethod
    def preprocess(cls, data):
        """
        A preprocessor for the order form data that can be overridden
        by custom form classes. The default preprocessor here handles
        copying billing fields to shipping fields if "same" checked.
        """
        if data.get("same_billing_shipping", "") == "on":
            for field in data:
                bill_field = field.replace("shipping_detail", "billing_detail")
                if field.startswith("shipping_detail") and bill_field in data:
                    data[field] = data[bill_field]
        return data

    def clean_card_expiry_year(self):
        """
        Ensure the card expiry doesn't occur in the past.
        """
        try:
            month = int(self.cleaned_data["card_expiry_month"])
            year = int(self.cleaned_data["card_expiry_year"])
        except ValueError:
            # Haven't reached payment step yet.
            return
        n = now()
        if year == n.year and month < n.month:
            raise forms.ValidationError(_("A valid expiry date is required."))
        return str(year)

    def clean(self):
        """
        Raise ``ValidationError`` if any errors have been assigned
        externally, via one of the custom checkout step handlers.
        """
        if self._checkout_errors:
            raise forms.ValidationError(self._checkout_errors)
        # Validate necessary fields are filled since hideable fields are
        # blank=True in the Order model
        for field in self.fields:
            if (field.startswith("billing_detail") or
                (field.startswith("shipping_detail")
                 and not settings.SHOP_ALWAYS_SAME_BILLING_SHIPPING)
                ) and len(self.data[field]) == 0 and field.replace(
                    "billing_detail_", "").replace(
                        "shipping_detail_",
                        "") not in settings.SHOP_HIDE_BILLING_SHIPPING_FIELDS:
                self.errors[field] = [_("This field is required.")]
                raise forms.ValidationError(_("Please fill out all fields."))
        return super(OrderForm, self).clean()