Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

forms.py 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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. def _should_delete_form(self, form):
  44. """
  45. Quantity of zero is treated as if the user checked the DELETE checkbox,
  46. which results in the basket line being deleted
  47. """
  48. if super(BaseBasketLineFormSet, self)._should_delete_form(form):
  49. return True
  50. if self.can_delete and 'quantity' in form.cleaned_data:
  51. return form.cleaned_data['quantity'] == 0
  52. BasketLineFormSet = modelformset_factory(
  53. Line, form=BasketLineForm, formset=BaseBasketLineFormSet, extra=0,
  54. can_delete=True)
  55. class SavedLineForm(forms.ModelForm):
  56. move_to_basket = forms.BooleanField(initial=False, required=False,
  57. label=_('Move to Basket'))
  58. class Meta:
  59. model = Line
  60. fields = ('id', 'move_to_basket')
  61. def __init__(self, strategy, basket, *args, **kwargs):
  62. self.strategy = strategy
  63. self.basket = basket
  64. super(SavedLineForm, self).__init__(*args, **kwargs)
  65. def clean(self):
  66. cleaned_data = super(SavedLineForm, self).clean()
  67. if not cleaned_data['move_to_basket']:
  68. # skip further validation (see issue #666)
  69. return cleaned_data
  70. try:
  71. line = self.basket.lines.get(product=self.instance.product)
  72. except Line.DoesNotExist:
  73. desired_qty = self.instance.quantity
  74. else:
  75. desired_qty = self.instance.quantity + line.quantity
  76. result = self.strategy.fetch_for_product(self.instance.product)
  77. is_available, reason = result.availability.is_purchase_permitted(
  78. quantity=desired_qty)
  79. if not is_available:
  80. raise forms.ValidationError(reason)
  81. return cleaned_data
  82. class BaseSavedLineFormSet(BaseModelFormSet):
  83. def __init__(self, strategy, basket, *args, **kwargs):
  84. self.strategy = strategy
  85. self.basket = basket
  86. super(BaseSavedLineFormSet, self).__init__(*args, **kwargs)
  87. def _construct_form(self, i, **kwargs):
  88. return super(BaseSavedLineFormSet, self)._construct_form(
  89. i, strategy=self.strategy, basket=self.basket, **kwargs)
  90. SavedLineFormSet = modelformset_factory(Line, form=SavedLineForm,
  91. formset=BaseSavedLineFormSet, extra=0,
  92. can_delete=True)
  93. class BasketVoucherForm(forms.Form):
  94. code = forms.CharField(max_length=128, label=_('Code'))
  95. def __init__(self, *args, **kwargs):
  96. super(BasketVoucherForm, self).__init__(*args, **kwargs)
  97. def clean_code(self):
  98. return self.cleaned_data['code'].strip().upper()
  99. class AddToBasketForm(forms.Form):
  100. quantity = forms.IntegerField(initial=1, min_value=1, label=_('Quantity'))
  101. def __init__(self, basket, product, *args, **kwargs):
  102. # Note, the product passed in here isn't necessarily the product being
  103. # added to the basket. For child products, it is the *parent* product
  104. # that gets passed to the form. An optional product_id param is passed
  105. # to indicate the ID of the child product being added to the basket.
  106. self.basket = basket
  107. self.parent_product = product
  108. super(AddToBasketForm, self).__init__(*args, **kwargs)
  109. # Dynamically build fields
  110. if product.is_parent:
  111. self._create_parent_product_fields(product)
  112. self._create_product_fields(product)
  113. # Dynamic form building methods
  114. def _create_parent_product_fields(self, product):
  115. """
  116. Adds the fields for a "group"-type product (eg, a parent product with a
  117. list of children.
  118. Currently requires that a stock record exists for the children
  119. """
  120. choices = []
  121. disabled_values = []
  122. for child in product.children.all():
  123. # Build a description of the child, including any pertinent
  124. # attributes
  125. attr_summary = child.attribute_summary
  126. if attr_summary:
  127. summary = attr_summary
  128. else:
  129. summary = child.get_title()
  130. # Check if it is available to buy
  131. info = self.basket.strategy.fetch_for_product(child)
  132. if not info.availability.is_available_to_buy:
  133. disabled_values.append(child.id)
  134. choices.append((child.id, summary))
  135. self.fields['child_id'] = forms.ChoiceField(
  136. choices=tuple(choices), label=_("Child product"),
  137. widget=widgets.AdvancedSelect(disabled_values=disabled_values))
  138. def _create_product_fields(self, product):
  139. """
  140. Add the product option fields.
  141. """
  142. for option in product.options:
  143. self._add_option_field(product, option)
  144. def _add_option_field(self, product, option):
  145. """
  146. Creates the appropriate form field for the product option.
  147. This is designed to be overridden so that specific widgets can be used
  148. for certain types of options.
  149. """
  150. kwargs = {'required': option.is_required}
  151. self.fields[option.code] = forms.CharField(**kwargs)
  152. # Cleaning
  153. def clean_child_id(self):
  154. try:
  155. child = self.parent_product.children.get(
  156. id=self.cleaned_data['child_id'])
  157. except Product.DoesNotExist:
  158. raise forms.ValidationError(
  159. _("Please select a valid product"))
  160. # To avoid duplicate SQL queries, we cache a copy of the loaded child
  161. # product as we're going to need it later.
  162. self.child_product = child
  163. return self.cleaned_data['child_id']
  164. def clean_quantity(self):
  165. # Check that the proposed new line quantity is sensible
  166. qty = self.cleaned_data['quantity']
  167. basket_threshold = settings.OSCAR_MAX_BASKET_QUANTITY_THRESHOLD
  168. if basket_threshold:
  169. total_basket_quantity = self.basket.num_items
  170. max_allowed = basket_threshold - total_basket_quantity
  171. if qty > max_allowed:
  172. raise forms.ValidationError(
  173. _("Due to technical limitations we are not able to ship"
  174. " more than %(threshold)d items in one order. Your"
  175. " basket currently has %(basket)d items.")
  176. % {'threshold': basket_threshold,
  177. 'basket': total_basket_quantity})
  178. return qty
  179. @property
  180. def product(self):
  181. """
  182. The actual product being added to the basket
  183. """
  184. # Note, the child product attribute is saved in the clean_child_id
  185. # method
  186. return getattr(self, 'child_product', self.parent_product)
  187. def clean(self):
  188. info = self.basket.strategy.fetch_for_product(self.product)
  189. # Check currencies are sensible
  190. if (self.basket.currency and
  191. info.price.currency != self.basket.currency):
  192. raise forms.ValidationError(
  193. _("This product cannot be added to the basket as its currency "
  194. "isn't the same as other products in your basket"))
  195. # Check user has permission to add the desired quantity to their
  196. # basket.
  197. current_qty = self.basket.product_quantity(self.product)
  198. desired_qty = current_qty + self.cleaned_data.get('quantity', 1)
  199. is_permitted, reason = info.availability.is_purchase_permitted(
  200. desired_qty)
  201. if not is_permitted:
  202. raise forms.ValidationError(reason)
  203. return self.cleaned_data
  204. # Helpers
  205. def cleaned_options(self):
  206. """
  207. Return submitted options in a clean format
  208. """
  209. options = []
  210. for option in self.parent_product.options:
  211. if option.code in self.cleaned_data:
  212. options.append({
  213. 'option': option,
  214. 'value': self.cleaned_data[option.code]})
  215. return options
  216. class SimpleAddToBasketForm(AddToBasketForm):
  217. """
  218. Simplified version of the add to basket form where the quantity is
  219. defaulted to 1 and rendered in a hidden widget
  220. """
  221. quantity = forms.IntegerField(
  222. initial=1, min_value=1, widget=forms.HiddenInput, label=_('Quantity'))