123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- from django import forms
- from django.conf import settings
- from django.forms.models import modelformset_factory, BaseModelFormSet
- from django.utils.translation import ugettext_lazy as _
-
- from oscar.core.loading import get_model
- from oscar.forms import widgets
-
- Line = get_model('basket', 'line')
- Basket = get_model('basket', 'basket')
- Product = get_model('catalogue', 'product')
-
-
- class BasketLineForm(forms.ModelForm):
- save_for_later = forms.BooleanField(
- initial=False, required=False, label=_('Save for Later'))
-
- def __init__(self, strategy, *args, **kwargs):
- super(BasketLineForm, self).__init__(*args, **kwargs)
- self.instance.strategy = strategy
-
- def clean_quantity(self):
- qty = self.cleaned_data['quantity']
- if qty > 0:
- self.check_max_allowed_quantity(qty)
- self.check_permission(qty)
- return qty
-
- def check_max_allowed_quantity(self, qty):
- is_allowed, reason = self.instance.basket.is_quantity_allowed(qty)
- if not is_allowed:
- raise forms.ValidationError(reason)
-
- def check_permission(self, qty):
- policy = self.instance.purchase_info.availability
- is_available, reason = policy.is_purchase_permitted(
- quantity=qty)
- if not is_available:
- raise forms.ValidationError(reason)
-
- class Meta:
- model = Line
- exclude = ('basket', 'product', 'stockrecord', 'line_reference',
- 'price_excl_tax', 'price_incl_tax', 'price_currency')
-
-
- class BaseBasketLineFormSet(BaseModelFormSet):
-
- def __init__(self, strategy, *args, **kwargs):
- self.strategy = strategy
- super(BaseBasketLineFormSet, self).__init__(*args, **kwargs)
-
- def _construct_form(self, i, **kwargs):
- return super(BaseBasketLineFormSet, self)._construct_form(
- i, strategy=self.strategy, **kwargs)
-
- def _should_delete_form(self, form):
- """
- Quantity of zero is treated as if the user checked the DELETE checkbox,
- which results in the basket line being deleted
- """
- if super(BaseBasketLineFormSet, self)._should_delete_form(form):
- return True
- if self.can_delete and 'quantity' in form.cleaned_data:
- return form.cleaned_data['quantity'] == 0
-
-
- BasketLineFormSet = modelformset_factory(
- Line, form=BasketLineForm, formset=BaseBasketLineFormSet, extra=0,
- can_delete=True)
-
-
- class SavedLineForm(forms.ModelForm):
- move_to_basket = forms.BooleanField(initial=False, required=False,
- label=_('Move to Basket'))
-
- class Meta:
- model = Line
- fields = ('id', 'move_to_basket')
-
- def __init__(self, strategy, basket, *args, **kwargs):
- self.strategy = strategy
- self.basket = basket
- super(SavedLineForm, self).__init__(*args, **kwargs)
-
- def clean(self):
- cleaned_data = super(SavedLineForm, self).clean()
- if not cleaned_data['move_to_basket']:
- # skip further validation (see issue #666)
- return cleaned_data
- try:
- line = self.basket.lines.get(product=self.instance.product)
- except Line.DoesNotExist:
- desired_qty = self.instance.quantity
- else:
- desired_qty = self.instance.quantity + line.quantity
-
- result = self.strategy.fetch_for_product(self.instance.product)
- is_available, reason = result.availability.is_purchase_permitted(
- quantity=desired_qty)
- if not is_available:
- raise forms.ValidationError(reason)
- return cleaned_data
-
-
- class BaseSavedLineFormSet(BaseModelFormSet):
-
- def __init__(self, strategy, basket, *args, **kwargs):
- self.strategy = strategy
- self.basket = basket
- super(BaseSavedLineFormSet, self).__init__(*args, **kwargs)
-
- def _construct_form(self, i, **kwargs):
- return super(BaseSavedLineFormSet, self)._construct_form(
- i, strategy=self.strategy, basket=self.basket, **kwargs)
-
-
- SavedLineFormSet = modelformset_factory(Line, form=SavedLineForm,
- formset=BaseSavedLineFormSet, extra=0,
- can_delete=True)
-
-
- class BasketVoucherForm(forms.Form):
- code = forms.CharField(max_length=128, label=_('Code'))
-
- def __init__(self, *args, **kwargs):
- super(BasketVoucherForm, self).__init__(*args, **kwargs)
-
- def clean_code(self):
- return self.cleaned_data['code'].strip().upper()
-
-
- class AddToBasketForm(forms.Form):
- quantity = forms.IntegerField(initial=1, min_value=1, label=_('Quantity'))
-
- def __init__(self, basket, product, *args, **kwargs):
- # Note, the product passed in here isn't necessarily the product being
- # added to the basket. For child products, it is the *parent* product
- # that gets passed to the form. An optional product_id param is passed
- # to indicate the ID of the child product being added to the basket.
- self.basket = basket
- self.parent_product = product
-
- super(AddToBasketForm, self).__init__(*args, **kwargs)
-
- # Dynamically build fields
- if product.is_parent:
- self._create_parent_product_fields(product)
- self._create_product_fields(product)
-
- # Dynamic form building methods
-
- def _create_parent_product_fields(self, product):
- """
- Adds the fields for a "group"-type product (eg, a parent product with a
- list of children.
-
- Currently requires that a stock record exists for the children
- """
- choices = []
- disabled_values = []
- for child in product.children.all():
- # Build a description of the child, including any pertinent
- # attributes
- attr_summary = child.attribute_summary
- if attr_summary:
- summary = attr_summary
- else:
- summary = child.get_title()
-
- # Check if it is available to buy
- info = self.basket.strategy.fetch_for_product(child)
- if not info.availability.is_available_to_buy:
- disabled_values.append(child.id)
-
- choices.append((child.id, summary))
-
- self.fields['child_id'] = forms.ChoiceField(
- choices=tuple(choices), label=_("Child product"),
- widget=widgets.AdvancedSelect(disabled_values=disabled_values))
-
- def _create_product_fields(self, product):
- """
- Add the product option fields.
- """
- for option in product.options:
- self._add_option_field(product, option)
-
- def _add_option_field(self, product, option):
- """
- Creates the appropriate form field for the product option.
-
- This is designed to be overridden so that specific widgets can be used
- for certain types of options.
- """
- kwargs = {'required': option.is_required}
- self.fields[option.code] = forms.CharField(**kwargs)
-
- # Cleaning
-
- def clean_child_id(self):
- try:
- child = self.parent_product.children.get(
- id=self.cleaned_data['child_id'])
- except Product.DoesNotExist:
- raise forms.ValidationError(
- _("Please select a valid product"))
-
- # To avoid duplicate SQL queries, we cache a copy of the loaded child
- # product as we're going to need it later.
- self.child_product = child
-
- return self.cleaned_data['child_id']
-
- def clean_quantity(self):
- # Check that the proposed new line quantity is sensible
- qty = self.cleaned_data['quantity']
- basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
- if basket_threshold:
- total_basket_quantity = self.basket.num_items
- max_allowed = basket_threshold - total_basket_quantity
- if qty > max_allowed:
- raise forms.ValidationError(
- _("Due to technical limitations we are not able to ship"
- " more than %(threshold)d items in one order. Your"
- " basket currently has %(basket)d items.")
- % {'threshold': basket_threshold,
- 'basket': total_basket_quantity})
- return qty
-
- @property
- def product(self):
- """
- The actual product being added to the basket
- """
- # Note, the child product attribute is saved in the clean_child_id
- # method
- return getattr(self, 'child_product', self.parent_product)
-
- def clean(self):
- info = self.basket.strategy.fetch_for_product(self.product)
-
- # Check currencies are sensible
- if (self.basket.currency and
- info.price.currency != self.basket.currency):
- raise forms.ValidationError(
- _("This product cannot be added to the basket as its currency "
- "isn't the same as other products in your basket"))
-
- # Check user has permission to add the desired quantity to their
- # basket.
- current_qty = self.basket.product_quantity(self.product)
- desired_qty = current_qty + self.cleaned_data.get('quantity', 1)
- is_permitted, reason = info.availability.is_purchase_permitted(
- desired_qty)
- if not is_permitted:
- raise forms.ValidationError(reason)
-
- return self.cleaned_data
-
- # Helpers
-
- def cleaned_options(self):
- """
- Return submitted options in a clean format
- """
- options = []
- for option in self.parent_product.options:
- if option.code in self.cleaned_data:
- options.append({
- 'option': option,
- 'value': self.cleaned_data[option.code]})
- return options
-
-
- class SimpleAddToBasketForm(AddToBasketForm):
- """
- Simplified version of the add to basket form where the quantity is
- defaulted to 1 and rendered in a hidden widget
- """
- quantity = forms.IntegerField(
- initial=1, min_value=1, widget=forms.HiddenInput, label=_('Quantity'))
|