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 7.9KB

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