Esempio n. 1
0
    def test_bytes_conversion(self):

        settings.clear_cache()

        register_setting(name="BYTES_TEST_SETTING", editable=True, default=b"")
        Setting.objects.create(name="BYTES_TEST_SETTING", value="A unicode value")
        self.assertEqual(settings.BYTES_TEST_SETTING, b"A unicode value")
Esempio n. 2
0
def send_order_email(request, order):
    """
    Send order receipt email on successful order.
    """
    settings.clear_cache()
    order_context = {
        "order": order,
        "request": request,
        "order_items": order.items.all()
    }
    order_context.update(order.details_as_dict())
    try:
        get_template("shop/email/order_receipt.html")
    except TemplateDoesNotExist:
        receipt_template = "email/order_receipt"
    else:
        receipt_template = "shop/email/order_receipt"
        from warnings import warn
        warn("Shop email receipt templates have moved from "
             "templates/shop/email/ to templates/email/")
    send_mail_template(settings.SHOP_ORDER_EMAIL_SUBJECT,
                       receipt_template,
                       settings.SHOP_ORDER_FROM_EMAIL,
                       order.billing_detail_email,
                       context=order_context,
                       addr_bcc=settings.SHOP_ORDER_EMAIL_BCC or None)
Esempio n. 3
0
    def test_settings(self):
        """
        Test that an editable setting can be overridden with a DB
        value and that the data type is preserved when the value is
        returned back out of the DB. Also checks to ensure no
        unsupported types are defined for editable settings.
        """

        settings.clear_cache()

        # Find an editable setting for each supported type.
        names_by_type = {}
        for setting in registry.values():
            if setting["editable"] and setting["type"] not in names_by_type:
                names_by_type[setting["type"]] = setting["name"]
        # Create a modified value for each setting and save it.
        values_by_name = {}
        for (setting_type, setting_name) in names_by_type.items():
            setting_value = registry[setting_name]["default"]
            if setting_type in (int, float):
                setting_value += 1
            elif setting_type is bool:
                setting_value = not setting_value
            elif setting_type is str:
                setting_value += u"test"
            elif setting_type is bytes:
                setting_value += b"test"
            else:
                setting = "%s: %s" % (setting_name, setting_type)
                self.fail("Unsupported setting type for %s" % setting)
            values_by_name[setting_name] = setting_value
            Setting.objects.create(name=setting_name, value=setting_value)
        # Load the settings and make sure the DB values have persisted.
        for (name, value) in values_by_name.items():
            self.assertEqual(getattr(settings, name), value)
    def test_settings(self):
        """
        Test that an editable setting can be overridden with a DB
        value and that the data type is preserved when the value is
        returned back out of the DB. Also checks to ensure no
        unsupported types are defined for editable settings.
        """

        settings.clear_cache()

        # Find an editable setting for each supported type.
        names_by_type = {}
        for setting in registry.values():
            if setting["editable"] and setting["type"] not in names_by_type:
                names_by_type[setting["type"]] = setting["name"]
        # Create a modified value for each setting and save it.
        values_by_name = {}
        for (setting_type, setting_name) in names_by_type.items():
            setting_value = registry[setting_name]["default"]
            if setting_type in (int, float):
                setting_value += 1
            elif setting_type is bool:
                setting_value = not setting_value
            elif setting_type is str:
                setting_value += u"test"
            elif setting_type is bytes:
                setting_value += b"test"
            else:
                setting = "%s: %s" % (setting_name, setting_type)
                self.fail("Unsupported setting type for %s" % setting)
            values_by_name[setting_name] = setting_value
            Setting.objects.create(name=setting_name, value=setting_value)
        # Load the settings and make sure the DB values have persisted.
        for (name, value) in values_by_name.items():
            self.assertEqual(getattr(settings, name), value)
    def test_bytes_conversion(self):

        settings.clear_cache()

        register_setting(name="BYTES_TEST_SETTING", editable=True, default=b"")
        Setting.objects.create(name="BYTES_TEST_SETTING",
                               value="A unicode value")
        self.assertEqual(settings.BYTES_TEST_SETTING, b"A unicode value")
Esempio n. 6
0
 def test_default_handler_exists(self):
     """
     Ensure that the handler specified in default settings exists as well as
     the default setting itself.
     """
     settings.clear_cache()
     handler = lambda s: import_dotted_path(s) if s else lambda *args: None
     self.assertTrue(handler(settings.SHOP_HANDLER_TAX) is not None)
Esempio n. 7
0
 def test_default_handler_exists(self):
     """
     Ensure that the handler specified in default settings exists as well as
     the default setting itself.
     """
     settings.clear_cache()
     handler = lambda s: import_dotted_path(s) if s else lambda *args: None
     self.assertTrue(handler(settings.SHOP_HANDLER_TAX) is not None)
Esempio n. 8
0
def cart(
    request,
    template="shop/cart.html",
    cart_formset_class=CartItemFormSet,
    discount_form_class=DiscountForm,
    extra_context=None,
):
    """
    Display cart and handle removing items from the cart.
    """
    cart_formset = cart_formset_class(instance=request.cart)
    discount_form = discount_form_class(request, request.POST or None)
    if request.method == "POST":
        valid = True
        if request.POST.get("update_cart"):
            valid = request.cart.has_items()
            if not valid:
                # Session timed out.
                info(request, _("Your cart has expired"))
            else:
                cart_formset = cart_formset_class(request.POST,
                                                  instance=request.cart)
                valid = cart_formset.is_valid()
                if valid:
                    cart_formset.save()
                    recalculate_cart(request)
                    info(request, _("Cart updated"))
                else:
                    # Reset the cart formset so that the cart
                    # always indicates the correct quantities.
                    # The user is shown their invalid quantity
                    # via the error message, which we need to
                    # copy over to the new formset here.
                    errors = cart_formset._errors
                    cart_formset = cart_formset_class(instance=request.cart)
                    cart_formset._errors = errors
        else:
            valid = discount_form.is_valid()
            if valid:
                discount_form.set_discount()
            # Potentially need to set shipping if a discount code
            # was previously entered with free shipping, and then
            # another was entered (replacing the old) without
            # free shipping, *and* the user has already progressed
            # to the final checkout step, which they'd go straight
            # to when returning to checkout, bypassing billing and
            # shipping details step where shipping is normally set.
            recalculate_cart(request)
        if valid:
            return redirect("shop_cart")
    context = {"cart_formset": cart_formset}
    context.update(extra_context or {})
    settings.clear_cache()
    if settings.SHOP_DISCOUNT_FIELD_IN_CART and DiscountCode.objects.active(
    ).exists():
        context["discount_form"] = discount_form
    return TemplateResponse(request, template, context)
Esempio n. 9
0
def cart(request, template="shop/cart.html",
         cart_formset_class=CartItemFormSet,
         discount_form_class=DiscountForm,
         extra_context=None):
    """
    Display cart and handle removing items from the cart.
    """
    cart_formset = cart_formset_class(instance=request.cart)
    discount_form = discount_form_class(request, request.POST or None)
    if request.method == "POST":
        valid = True
        if request.POST.get("update_cart"):
            valid = request.cart.has_items()
            if not valid:
                # Session timed out.
                info(request, _("Your cart has expired"))
            else:
                cart_formset = cart_formset_class(request.POST,
                                                  instance=request.cart)
                valid = cart_formset.is_valid()
                if valid:
                    cart_formset.save()
                    recalculate_cart(request)
                    info(request, _("Cart updated"))
                else:
                    # Reset the cart formset so that the cart
                    # always indicates the correct quantities.
                    # The user is shown their invalid quantity
                    # via the error message, which we need to
                    # copy over to the new formset here.
                    errors = cart_formset._errors
                    cart_formset = cart_formset_class(instance=request.cart)
                    cart_formset._errors = errors
        else:
            valid = discount_form.is_valid()
            if valid:
                discount_form.set_discount()
            # Potentially need to set shipping if a discount code
            # was previously entered with free shipping, and then
            # another was entered (replacing the old) without
            # free shipping, *and* the user has already progressed
            # to the final checkout step, which they'd go straight
            # to when returning to checkout, bypassing billing and
            # shipping details step where shipping is normally set.
            recalculate_cart(request)
        if valid:
            return redirect("shop_cart")
    context = {"cart_formset": cart_formset}
    context.update(extra_context or {})
    settings.clear_cache()
    if (settings.SHOP_DISCOUNT_FIELD_IN_CART and
            DiscountCode.objects.active().exists()):
        context["discount_form"] = discount_form
    return TemplateResponse(request, template, context)
Esempio n. 10
0
 def changelist_view(self, request, extra_context=None):
     if extra_context is None:
         extra_context = {}
     settings_form = SettingsForm(request.POST or None)
     if settings_form.is_valid():
         settings_form.save()
         settings.clear_cache()
         info(request, _("Settings were successfully updated."))
         return self.changelist_redirect()
     extra_context["settings_form"] = settings_form
     extra_context["title"] = "%s %s" % (_("Change"), force_text(Setting._meta.verbose_name_plural))
     return super(SettingsAdmin, self).changelist_view(request, extra_context)
Esempio n. 11
0
 def changelist_view(self, request, extra_context=None):
     if extra_context is None:
         extra_context = {}
     settings_form = SettingsForm(request.POST or None)
     if settings_form.is_valid():
         settings_form.save()
         settings.clear_cache()
         info(request, _("Settings were successfully updated."))
         return self.changelist_redirect()
     extra_context["settings_form"] = settings_form
     extra_context["title"] = u"%s %s" % (
         _("Change"), force_text(Setting._meta.verbose_name_plural))
     return super(SettingsAdmin,
                  self).changelist_view(request, extra_context)
Esempio n. 12
0
def default_billship_handler(request, order_form):
    """
    Default billing/shipping handler - called when the first step in
    the checkout process with billing/shipping address fields is
    submitted. Implement your own and specify the path to import it
    from via the setting ``SHOP_HANDLER_BILLING_SHIPPING``.
    This function will typically contain any shipping calculation
    where the shipping amount can then be set using the function
    ``cartridge.shop.utils.set_shipping``. The Cart object is also
    accessible via ``request.cart``
    """
    if not request.session.get("free_shipping"):
        settings.clear_cache()
        set_shipping(request, _("Flat rate shipping"),
                     settings.SHOP_DEFAULT_SHIPPING_VALUE)
    def test_editable_override(self):
        """
        Test that an editable setting is always overridden by a settings.py
        setting of the same name.
        """

        settings.clear_cache()

        Setting.objects.all().delete()
        django_settings.FOO = "Set in settings.py"
        Setting.objects.create(name="FOO", value="Set in database")
        first_value = settings.FOO
        settings.SITE_TITLE  # Triggers access?
        second_value = settings.FOO
        self.assertEqual(first_value, second_value)
Esempio n. 14
0
    def test_editable_override(self):
        """
        Test that an editable setting is always overridden by a settings.py
        setting of the same name.
        """

        settings.clear_cache()

        Setting.objects.all().delete()
        django_settings.FOO = "Set in settings.py"
        Setting.objects.create(name="FOO", value="Set in database")
        first_value = settings.FOO
        settings.SITE_TITLE  # Triggers access?
        second_value = settings.FOO
        self.assertEqual(first_value, second_value)
Esempio n. 15
0
def default_billship_handler(request, order_form):
    """
    Default billing/shipping handler - called when the first step in
    the checkout process with billing/shipping address fields is
    submitted. Implement your own and specify the path to import it
    from via the setting ``SHOP_HANDLER_BILLING_SHIPPING``.
    This function will typically contain any shipping calculation
    where the shipping amount can then be set using the function
    ``cartridge.shop.utils.set_shipping``. The Cart object is also
    accessible via ``request.cart``
    """
    if not request.session.get("free_shipping"):
        settings.clear_cache()
        set_shipping(request, _("Flat rate shipping"),
                     settings.SHOP_DEFAULT_SHIPPING_VALUE)
    def test_invalid_value_warning(self):
        """
        Test that a warning is raised when a database setting has an invalid
        value, i.e. one that can't be converted to the correct Python type.
        """

        settings.clear_cache()

        register_setting(name="INVALID_INT_SETTING", editable=True, default=0)
        Setting.objects.create(name="INVALID_INT_SETTING", value="zero")
        with warnings.catch_warnings():
            warning_re = r"The setting \w+ should be of type"
            warnings.filterwarnings("error", warning_re, UserWarning)
            with self.assertRaises(UserWarning):
                settings.INVALID_INT_SETTING
        self.assertEqual(settings.INVALID_INT_SETTING, 0)
Esempio n. 17
0
    def test_invalid_value_warning(self):
        """
        Test that a warning is raised when a database setting has an invalid
        value, i.e. one that can't be converted to the correct Python type.
        """

        settings.clear_cache()

        register_setting(name="INVALID_INT_SETTING", editable=True, default=0)
        Setting.objects.create(name="INVALID_INT_SETTING", value='zero')
        with warnings.catch_warnings():
            warning_re = r'The setting \w+ should be of type'
            warnings.filterwarnings('error', warning_re, UserWarning)
            with self.assertRaises(UserWarning):
                settings.INVALID_INT_SETTING
        self.assertEqual(settings.INVALID_INT_SETTING, 0)
Esempio n. 18
0
def category_processor(request, page):
    """
    Add paging/sorting to the products for the category.
    """
    settings.clear_cache()
    products = Product.objects.published(for_user=request.user).filter(
        page.category.filters()).distinct()
    sort_options = [(slugify(option[0]), option[1])
                    for option in settings.SHOP_PRODUCT_SORT_OPTIONS]
    sort_by = request.GET.get(
        "sort", sort_options[0][1] if sort_options else '-date_added')
    products = paginate(products.order_by(sort_by), request.GET.get("page", 1),
                        settings.SHOP_PER_PAGE_CATEGORY,
                        settings.MAX_PAGING_LINKS)
    products.sort_by = sort_by
    sub_categories = page.category.children.published()
    child_categories = Category.objects.filter(id__in=sub_categories)
    return {"products": products, "child_categories": child_categories}
Esempio n. 19
0
def category_processor(request, page):
    """
    Add paging/sorting to the products for the category.
    """
    settings.clear_cache()
    products = Product.objects.published(for_user=request.user
                                ).filter(page.category.filters()).distinct()
    sort_options = [(slugify(option[0]), option[1])
                    for option in settings.SHOP_PRODUCT_SORT_OPTIONS]
    sort_by = request.GET.get(
        "sort", sort_options[0][1] if sort_options else '-date_added')
    products = paginate(products.order_by(sort_by),
                        request.GET.get("page", 1),
                        settings.SHOP_PER_PAGE_CATEGORY,
                        settings.MAX_PAGING_LINKS)
    products.sort_by = sort_by
    sub_categories = page.category.children.published()
    child_categories = Category.objects.filter(id__in=sub_categories)
    return {"products": products, "child_categories": child_categories}
Esempio n. 20
0
def send_order_email(request, order):
    """
    Send order receipt email on successful order.
    """
    settings.clear_cache()
    order_context = {
        "order": order,
        "request": request,
        "order_items": order.items.all(),
    }
    order_context.update(order.details_as_dict())
    receipt_template = "email/order_receipt"
    send_mail_template(
        settings.SHOP_ORDER_EMAIL_SUBJECT,
        receipt_template,
        settings.SHOP_ORDER_FROM_EMAIL,
        order.billing_detail_email,
        context=order_context,
        addr_bcc=settings.SHOP_ORDER_EMAIL_BCC or None,
    )
Esempio n. 21
0
def send_order_email(request, order):
    """
    Send order receipt email on successful order.
    """
    settings.clear_cache()
    order_context = {"order": order, "request": request,
                     "order_items": order.items.all()}
    order_context.update(order.details_as_dict())
    try:
        get_template("shop/email/order_receipt.html")
    except TemplateDoesNotExist:
        receipt_template = "email/order_receipt"
    else:
        receipt_template = "shop/email/order_receipt"
        from warnings import warn
        warn("Shop email receipt templates have moved from "
             "templates/shop/email/ to templates/email/")
    send_mail_template(settings.SHOP_ORDER_EMAIL_SUBJECT,
                       receipt_template, settings.SHOP_ORDER_FROM_EMAIL,
                       order.billing_detail_email, context=order_context,
                       addr_bcc=settings.SHOP_ORDER_EMAIL_BCC or None)
    def test_unregistered_setting(self):
        """
        Test that accessing any editable setting will delete all Settings
        with no corresponding registered setting from the database.
        """

        settings.clear_cache()

        register_setting(name="REGISTERED_SETTING", editable=True, default="")
        Setting.objects.create(name="UNREGISTERED_SETTING", value="")

        with self.assertRaises(AttributeError):
            settings.UNREGISTERED_SETTING

        qs = Setting.objects.filter(name="UNREGISTERED_SETTING")
        self.assertEqual(qs.count(), 1)

        # This triggers Settings._load(), which deletes unregistered Settings
        settings.REGISTERED_SETTING

        self.assertEqual(qs.count(), 0)
Esempio n. 23
0
    def test_unregistered_setting(self):
        """
        Test that accessing any editable setting will delete all Settings
        with no corresponding registered setting from the database.
        """

        settings.clear_cache()

        register_setting(name="REGISTERED_SETTING", editable=True, default="")
        Setting.objects.create(name="UNREGISTERED_SETTING", value='')

        with self.assertRaises(AttributeError):
            settings.UNREGISTERED_SETTING

        qs = Setting.objects.filter(name="UNREGISTERED_SETTING")
        self.assertEqual(qs.count(), 1)

        # This triggers Settings._load(), which deletes unregistered Settings
        settings.REGISTERED_SETTING

        self.assertEqual(qs.count(), 0)
Esempio n. 24
0
    def test_conflicting_setting(self):
        """
        Test that conflicting settings raise a warning and use the settings.py
        value instead of the value from the database.
        """

        settings.clear_cache()

        register_setting(name="CONFLICTING_SETTING", editable=True, default=1)
        Setting.objects.create(name="CONFLICTING_SETTING", value=2)
        settings.CONFLICTING_SETTING = 3

        with warnings.catch_warnings():
            warning_re = "These settings are defined in both " "settings\.py and the database"
            warnings.filterwarnings("error", warning_re, UserWarning)

            with self.assertRaises(UserWarning):
                settings.CONFLICTING_SETTING

        self.assertEqual(settings.CONFLICTING_SETTING, 3)

        del settings.CONFLICTING_SETTING
Esempio n. 25
0
def update_cart(request, template="shop/includes/offcanvas_cart.html", cart_formset_class=OffCartItemFormSet):
    """
    Display cart and handle removing items from the cart.
    """
    cart_formset = cart_formset_class(instance=request.cart)

    if request.is_ajax():
        if request.POST and request.POST.get("update_cart"):
            valid = request.cart.has_items()
            if not valid:
                # Session timed out.
                info(request, _("Your cart has expired"))
            else:
                cart_formset = cart_formset_class(request.POST, instance=request.cart)
                valid = cart_formset.is_valid()
                if valid:
                    cart_formset.save()
                    info(request, _("Cart updated"))
                    recalculate_cart(request)
                    cart_formset = cart_formset_class(instance=request.cart)
                else:
                    # Reset the cart formset so that the cart
                    # always indicates the correct quantities.
                    # The user is shown their invalid quantity
                    # via the error message, which we need to
                    # copy over to the new formset here.
                    errors = cart_formset._errors
                    cart_formset = cart_formset_class(instance=request.cart)
                    cart_formset._errors = errors
        elif request.POST and request.POST.get("add_cart"):
            cart_formset = cart_formset_class(instance=request.cart)

        context = {"off_cart_formset": cart_formset}
        settings.clear_cache()
        return TemplateResponse(request, template, context)

    else:
        raise Http404
    def test_conflicting_setting(self):
        """
        Test that conflicting settings raise a warning and use the settings.py
        value instead of the value from the database.
        """

        settings.clear_cache()

        register_setting(name="CONFLICTING_SETTING", editable=True, default=1)
        Setting.objects.create(name="CONFLICTING_SETTING", value=2)
        settings.CONFLICTING_SETTING = 3

        with warnings.catch_warnings():
            warning_re = ("These settings are defined in both "
                          r"settings\.py and the database")
            warnings.filterwarnings("error", warning_re, UserWarning)

            with self.assertRaises(UserWarning):
                settings.CONFLICTING_SETTING

        self.assertEqual(settings.CONFLICTING_SETTING, 3)

        del settings.CONFLICTING_SETTING
Esempio n. 27
0
def default_tax_handler(request, order_form):
    """
    Default tax handler - called immediately after the handler defined
    by ``SHOP_HANDLER_BILLING_SHIPPING``. Implement your own and
    specify the path to import it from via the setting
    ``SHOP_HANDLER_TAX``. This function will typically contain any tax
    calculation where the tax amount can then be set using the function
    ``cartridge.shop.utils.set_tax``. The Cart object is also
    accessible via ``request.cart``
    """
    settings.clear_cache()
    if settings.SHOP_DEFAULT_TAX_RATE:
        locale.setlocale(locale.LC_NUMERIC, str(settings.SHOP_CURRENCY_LOCALE))
        tax_rate = settings.SHOP_DEFAULT_TAX_RATE
        if settings.SHOP_TAX_INCLUDED:
            tax = request.cart.total_price() - (
                request.cart.total_price() /
                decimal.Decimal(1 + tax_rate / 100))
            tax_type = _("Incl.") + " " + f"{tax_rate:n}" + "% " + _("VAT")
        else:
            tax = request.cart.total_price() * decimal.Decimal(tax_rate / 100)
            tax_type = _("VAT") + " (" + f"{tax_rate:n}" + "%)"
        set_tax(request, tax_type, f"{tax:.2f}")
Esempio n. 28
0
    def __init__(self,
                 request,
                 step,
                 data=None,
                 initial=None,
                 errors=None,
                 **kwargs):
        """
        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,
                                        **kwargs)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.clear_cache()
        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
Esempio n. 29
0
    def __init__(
            self, request, step, data=None, initial=None, errors=None,
            **kwargs):
        """
        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, **kwargs)
        self._checkout_errors = errors

        # Hide discount code field if it shouldn't appear in checkout,
        # or if no discount codes are active.
        settings.clear_cache()
        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