You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

forms.py 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. from django import forms
  2. from django.conf import settings
  3. from django.forms.models import modelformset_factory, BaseModelFormSet
  4. from django.utils.translation import ugettext_lazy as _
  5. from oscar.core.loading import get_model
  6. from oscar.forms import widgets
  7. Line = get_model('basket', 'line')
  8. Basket = get_model('basket', 'basket')
  9. Product = get_model('catalogue', 'product')
  10. class BasketLineForm(forms.ModelForm):
  11. save_for_later = forms.BooleanField(
  12. initial=False, required=False, label=_('Save for Later'))
  13. def __init__(self, strategy, *args, **kwargs):
  14. super(BasketLineForm, self).__init__(*args, **kwargs)
  15. self.instance.strategy = strategy
  16. def clean_quantity(self):
  17. qty = self.cleaned_data['quantity']
  18. if qty > 0:
  19. self.check_max_allowed_quantity(qty)
  20. self.check_permission(qty)
  21. return qty
  22. def check_max_allowed_quantity(self, qty):
  23. is_allowed, reason = self.instance.basket.is_quantity_allowed(qty)
  24. if not is_allowed:
  25. raise forms.ValidationError(reason)
  26. def check_permission(self, qty):
  27. policy = self.instance.purchase_info.availability
  28. is_available, reason = policy.is_purchase_permitted(
  29. quantity=qty)
  30. if not is_available:
  31. raise forms.ValidationError(reason)
  32. class Meta:
  33. model = Line
  34. exclude = ('basket', 'product', 'stockrecord', 'line_reference',
  35. 'price_excl_tax', 'price_incl_tax', 'price_currency')
  36. class BaseBasketLineFormSet(BaseModelFormSet):
  37. def __init__(self, strategy, *args, **kwargs):
  38. self.strategy = strategy
  39. super(BaseBasketLineFormSet, self).__init__(*args, **kwargs)
  40. def _construct_form(self, i, **kwargs):
  41. return super(BaseBasketLineFormSet, self)._construct_form(
  42. i, strategy=self.strategy, **kwargs)
  43. BasketLineFormSet = modelformset_factory(
  44. Line, form=BasketLineForm, formset=BaseBasketLineFormSet, extra=0,
  45. can_delete=True)
  46. class SavedLineForm(forms.ModelForm):
  47. move_to_basket = forms.BooleanField(initial=False, required=False,
  48. label=_('Move to Basket'))
  49. class Meta:
  50. model = Line
  51. fields = ('id', 'move_to_basket')
  52. def __init__(self, strategy, basket, *args, **kwargs):
  53. self.strategy = strategy
  54. self.basket = basket
  55. super(SavedLineForm, self).__init__(*args, **kwargs)
  56. def clean(self):
  57. cleaned_data = super(SavedLineForm, self).clean()
  58. if not cleaned_data['move_to_basket']:
  59. # skip further validation (see issue #666)
  60. return cleaned_data
  61. try:
  62. line = self.basket.lines.get(product=self.instance.product)
  63. except Line.DoesNotExist:
  64. desired_qty = self.instance.quantity
  65. else:
  66. desired_qty = self.instance.quantity + line.quantity
  67. result = self.strategy.fetch_for_product(self.instance.product)
  68. is_available, reason = result.availability.is_purchase_permitted(
  69. quantity=desired_qty)
  70. if not is_available:
  71. raise forms.ValidationError(reason)
  72. return cleaned_data
  73. class BaseSavedLineFormSet(BaseModelFormSet):
  74. def __init__(self, strategy, basket, *args, **kwargs):
  75. self.strategy = strategy
  76. self.basket = basket
  77. super(BaseSavedLineFormSet, self).__init__(*args, **kwargs)
  78. def _construct_form(self, i, **kwargs):
  79. return super(BaseSavedLineFormSet, self)._construct_form(
  80. i, strategy=self.strategy, basket=self.basket, **kwargs)
  81. SavedLineFormSet = modelformset_factory(Line, form=SavedLineForm,
  82. formset=BaseSavedLineFormSet, extra=0,
  83. can_delete=True)
  84. class BasketVoucherForm(forms.Form):
  85. code = forms.CharField(max_length=128, label=_('Code'))
  86. def __init__(self, *args, **kwargs):
  87. super(BasketVoucherForm, self).__init__(*args, **kwargs)
  88. def clean_code(self):
  89. return self.cleaned_data['code'].strip().upper()
  90. class ProductSelectionForm(forms.Form):
  91. product_id = forms.IntegerField(min_value=1, label=_("Product ID"))
  92. def clean_product_id(self):
  93. id = self.cleaned_data['product_id']
  94. try:
  95. return Product.objects.get(pk=id)
  96. except Product.DoesNotExist:
  97. raise forms.ValidationError(
  98. _("This product is unavailable for purchase"))
  99. class AddToBasketForm(forms.Form):
  100. # We set required=False as validation happens later on
  101. product_id = forms.IntegerField(widget=forms.HiddenInput(), required=False,
  102. min_value=1, label=_("Product ID"))
  103. quantity = forms.IntegerField(initial=1, min_value=1, label=_('Quantity'))
  104. def __init__(self, request, instance, *args, **kwargs):
  105. super(AddToBasketForm, self).__init__(*args, **kwargs)
  106. self.request = request
  107. self.basket = request.basket
  108. self.instance = instance
  109. if instance:
  110. if instance.is_group:
  111. self._create_group_product_fields(instance)
  112. else:
  113. self._create_product_fields(instance)
  114. def cleaned_options(self):
  115. """
  116. Return submitted options in a clean format
  117. """
  118. options = []
  119. for option in self.instance.options:
  120. if option.code in self.cleaned_data:
  121. options.append({
  122. 'option': option,
  123. 'value': self.cleaned_data[option.code]})
  124. return options
  125. def clean(self):
  126. # Check product exists
  127. try:
  128. product = Product.objects.get(
  129. id=self.cleaned_data.get('product_id', None))
  130. except Product.DoesNotExist:
  131. raise forms.ValidationError(
  132. _("Please select a valid product"))
  133. # Check user has permission to this the desired quantity to their
  134. # basket.
  135. current_qty = self.basket.product_quantity(product)
  136. desired_qty = current_qty + self.cleaned_data.get('quantity', 1)
  137. result = self.request.strategy.fetch_for_product(product)
  138. is_permitted, reason = result.availability.is_purchase_permitted(
  139. desired_qty)
  140. if not is_permitted:
  141. raise forms.ValidationError(reason)
  142. return self.cleaned_data
  143. def clean_quantity(self):
  144. qty = self.cleaned_data['quantity']
  145. basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
  146. if basket_threshold:
  147. total_basket_quantity = self.basket.num_items
  148. max_allowed = basket_threshold - total_basket_quantity
  149. if qty > max_allowed:
  150. raise forms.ValidationError(
  151. _("Due to technical limitations we are not able to ship"
  152. " more than %(threshold)d items in one order. Your"
  153. " basket currently has %(basket)d items.")
  154. % {'threshold': basket_threshold,
  155. 'basket': total_basket_quantity})
  156. return qty
  157. def _create_group_product_fields(self, item):
  158. """
  159. Adds the fields for a "group"-type product (eg, a parent product with a
  160. list of variants.
  161. Currently requires that a stock record exists for the variant
  162. """
  163. choices = []
  164. disabled_values = []
  165. for variant in item.variants.all():
  166. attr_summary = variant.attribute_summary
  167. if attr_summary:
  168. summary = attr_summary
  169. else:
  170. summary = variant.get_title()
  171. info = self.request.strategy.fetch_for_product(variant)
  172. if not info.availability.is_available_to_buy:
  173. disabled_values.append(variant.id)
  174. choices.append((variant.id, summary))
  175. self.fields['product_id'] = forms.ChoiceField(
  176. choices=tuple(choices), label=_("Variant"),
  177. widget=widgets.AdvancedSelect(disabled_values=disabled_values))
  178. def _create_product_fields(self, item):
  179. """
  180. Add the product option fields.
  181. """
  182. for option in item.options:
  183. self._add_option_field(item, option)
  184. def _add_option_field(self, item, option):
  185. """
  186. Creates the appropriate form field for the product option.
  187. This is designed to be overridden so that specific widgets can be used
  188. for certain types of options.
  189. """
  190. kwargs = {'required': option.is_required}
  191. self.fields[option.code] = forms.CharField(**kwargs)
  192. class SimpleAddToBasketForm(AddToBasketForm):
  193. quantity = forms.IntegerField(
  194. initial=1, min_value=1, widget=forms.HiddenInput, label=_('Quantity'))