Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

forms.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. from datetime import date
  2. from calendar import monthrange
  3. import re
  4. from django import forms
  5. from django.db.models import get_model
  6. from django.utils.translation import ugettext_lazy as _
  7. from oscar.apps.address.forms import AbstractAddressForm
  8. from . import bankcards
  9. from oscar.views.generic import PhoneNumberMixin
  10. Country = get_model('address', 'Country')
  11. BillingAddress = get_model('order', 'BillingAddress')
  12. Bankcard = get_model('payment', 'Bankcard')
  13. class BankcardNumberField(forms.CharField):
  14. def __init__(self, *args, **kwargs):
  15. _kwargs = {
  16. 'max_length': 20,
  17. 'widget': forms.TextInput(attrs={'autocomplete': 'off'}),
  18. 'label': _("Card number")
  19. }
  20. _kwargs.update(kwargs)
  21. super(BankcardNumberField, self).__init__(*args, **_kwargs)
  22. def clean(self, value):
  23. """
  24. Check if given CC number is valid and one of the
  25. card types we accept
  26. """
  27. non_decimal = re.compile(r'\D+')
  28. value = non_decimal.sub('', value.strip())
  29. if value and not bankcards.luhn(value):
  30. raise forms.ValidationError(
  31. _("Please enter a valid credit card number."))
  32. return super(BankcardNumberField, self).clean(value)
  33. class BankcardMonthWidget(forms.MultiWidget):
  34. """
  35. Widget containing two select boxes for selecting the month and year
  36. """
  37. def decompress(self, value):
  38. return [value.month, value.year] if value else [None, None]
  39. def format_output(self, rendered_widgets):
  40. html = u' '.join(rendered_widgets)
  41. return u'<span style="white-space: nowrap">%s</span>' % html
  42. class BankcardMonthField(forms.MultiValueField):
  43. """
  44. A modified version of the snippet: http://djangosnippets.org/snippets/907/
  45. """
  46. default_error_messages = {
  47. 'invalid_month': _('Enter a valid month.'),
  48. 'invalid_year': _('Enter a valid year.'),
  49. }
  50. num_years = 5
  51. def __init__(self, *args, **kwargs):
  52. # Allow the number of years to be specified
  53. if 'num_years' in kwargs:
  54. self.num_years = kwargs.pop('num_years')
  55. errors = self.default_error_messages.copy()
  56. if 'error_messages' in kwargs:
  57. errors.update(kwargs['error_messages'])
  58. fields = (
  59. forms.ChoiceField(
  60. choices=self.month_choices(),
  61. error_messages={'invalid': errors['invalid_month']}),
  62. forms.ChoiceField(
  63. choices=self.year_choices(),
  64. error_messages={'invalid': errors['invalid_year']}),
  65. )
  66. if 'widget' not in kwargs:
  67. kwargs['widget'] = BankcardMonthWidget(
  68. widgets=[fields[0].widget, fields[1].widget])
  69. super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
  70. def month_choices(self):
  71. return []
  72. def year_choices(self):
  73. return []
  74. class BankcardExpiryMonthField(BankcardMonthField):
  75. num_years = 10
  76. def __init__(self, *args, **kwargs):
  77. today = date.today()
  78. _kwargs = {
  79. 'required': True,
  80. 'label': _("Valid to"),
  81. 'initial': ["%.2d" % today.month, today.year]
  82. }
  83. _kwargs.update(kwargs)
  84. super(BankcardExpiryMonthField, self).__init__(*args, **_kwargs)
  85. def month_choices(self):
  86. return [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  87. def year_choices(self):
  88. return [(x, x) for x in xrange(date.today().year,
  89. date.today().year + self.num_years)]
  90. def clean(self, value):
  91. expiry_date = super(BankcardExpiryMonthField, self).clean(value)
  92. if date.today() > expiry_date:
  93. raise forms.ValidationError(
  94. _("The expiration date you entered is in the past."))
  95. return expiry_date
  96. def compress(self, data_list):
  97. if data_list:
  98. if data_list[1] in forms.fields.EMPTY_VALUES:
  99. error = self.error_messages['invalid_year']
  100. raise forms.ValidationError(error)
  101. if data_list[0] in forms.fields.EMPTY_VALUES:
  102. error = self.error_messages['invalid_month']
  103. raise forms.ValidationError(error)
  104. year = int(data_list[1])
  105. month = int(data_list[0])
  106. # find last day of the month
  107. day = monthrange(year, month)[1]
  108. return date(year, month, day)
  109. return None
  110. class BankcardStartingMonthField(BankcardMonthField):
  111. def __init__(self, *args, **kwargs):
  112. _kwargs = {
  113. 'required': False,
  114. 'label': _("Valid from"),
  115. }
  116. _kwargs.update(kwargs)
  117. super(BankcardStartingMonthField, self).__init__(*args, **_kwargs)
  118. def month_choices(self):
  119. months = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  120. months.insert(0, ("", "--"))
  121. return months
  122. def year_choices(self):
  123. today = date.today()
  124. years = [(x, x) for x in xrange(today.year - self.num_years,
  125. today.year + 1)]
  126. years.insert(0, ("", "--"))
  127. return years
  128. def clean(self, value):
  129. starting_date = super(BankcardMonthField, self).clean(value)
  130. if starting_date and date.today() < starting_date:
  131. raise forms.ValidationError(
  132. _("The starting date you entered is in the future."))
  133. return starting_date
  134. def compress(self, data_list):
  135. if data_list:
  136. if data_list[1] in forms.fields.EMPTY_VALUES:
  137. error = self.error_messages['invalid_year']
  138. raise forms.ValidationError(error)
  139. if data_list[0] in forms.fields.EMPTY_VALUES:
  140. error = self.error_messages['invalid_month']
  141. raise forms.ValidationError(error)
  142. year = int(data_list[1])
  143. month = int(data_list[0])
  144. return date(year, month, 1)
  145. return None
  146. class BankcardCCVField(forms.RegexField):
  147. def __init__(self, *args, **kwargs):
  148. _kwargs = {
  149. 'required': True,
  150. 'label': _("CCV number"),
  151. 'widget': forms.TextInput(attrs={'size': '5'}),
  152. 'error_message': _("Please enter a 3 or 4 digit number"),
  153. 'help_text': _("This is the 3 or 4 digit security number "
  154. "on the back of your bankcard")
  155. }
  156. _kwargs.update(kwargs)
  157. super(BankcardCCVField, self).__init__(
  158. r'^\d{3,4}$', *args, **_kwargs)
  159. def clean(self, value):
  160. if value is not None:
  161. value = value.strip()
  162. return super(BankcardCCVField, self).clean(value)
  163. class BankcardForm(forms.ModelForm):
  164. number = BankcardNumberField()
  165. ccv = BankcardCCVField()
  166. start_month = BankcardStartingMonthField()
  167. expiry_month = BankcardExpiryMonthField()
  168. class Meta:
  169. model = Bankcard
  170. fields = ('number', 'start_month', 'expiry_month', 'ccv')
  171. def clean(self):
  172. data = self.cleaned_data
  173. number, ccv = data.get('number'), data.get('ccv')
  174. if number and ccv:
  175. if bankcards.is_amex(number) and len(ccv) != 4:
  176. raise forms.ValidationError(_(
  177. "American Express cards use a 4 digit security code"))
  178. return data
  179. def save(self, *args, **kwargs):
  180. # It doesn't really make sense to save directly from the form as saving
  181. # will obfuscate some of the card details which you normally need to
  182. # pass to a payment gateway. Better to use the bankcard property below
  183. # to get the cleaned up data, then once you've used the sensitive
  184. # details, you can save.
  185. raise RuntimeError("Don't save bankcards directly from form")
  186. @property
  187. def bankcard(self):
  188. """
  189. Return an instance of the Bankcard model (unsaved)
  190. """
  191. return Bankcard(number=self.cleaned_data['number'],
  192. expiry_date=self.cleaned_data['expiry_month'],
  193. start_date=self.cleaned_data['start_month'],
  194. ccv=self.cleaned_data['ccv'])
  195. class BillingAddressForm(PhoneNumberMixin, AbstractAddressForm):
  196. def __init__(self, *args, **kwargs):
  197. super(BillingAddressForm, self).__init__(*args, **kwargs)
  198. self.set_country_queryset()
  199. def set_country_queryset(self):
  200. self.fields['country'].queryset = Country._default_manager.all()
  201. class Meta:
  202. model = BillingAddress
  203. exclude = ('search_text',)