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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. from datetime import date, datetime
  2. from calendar import monthrange
  3. import re
  4. from django import forms
  5. from oscar.core.loading import import_module
  6. address_models = import_module('address.models', ['Country'])
  7. order_models = import_module('order.models', ['BillingAddress'])
  8. payment_models = import_module('payment.models', ['Bankcard'])
  9. VISA, MASTERCARD, AMEX, MAESTRO, DISCOVER = ('Visa', 'Mastercard', 'American Express', 'Maestro', 'Discover')
  10. def bankcard_type(number):
  11. u"""
  12. Returns the type of a bankcard based on its number.
  13. """
  14. number = str(number)
  15. if len(number) == 13:
  16. if number[0] == "4":
  17. return VISA
  18. elif len(number) == 14:
  19. if number[:2] == "36":
  20. return MASTERCARD
  21. elif len(number) == 15:
  22. if number[:2] in ("34", "37"):
  23. return AMEX
  24. elif len(number) == 16:
  25. if number[:4] == "6011":
  26. return DISCOVER
  27. if number[:2] in ("51", "52", "53", "54", "55"):
  28. return MASTERCARD
  29. if number[0] == "4":
  30. return VISA
  31. return None
  32. def luhn(card_number):
  33. u"""
  34. Tests whether a bankcard number passes the Luhn algorithm.
  35. """
  36. card_number = str(card_number)
  37. sum = 0
  38. num_digits = len(card_number)
  39. odd_even = num_digits & 1
  40. for i in range(0, num_digits):
  41. digit = int(card_number[i])
  42. if not (( i & 1 ) ^ odd_even ):
  43. digit = digit * 2
  44. if digit > 9:
  45. digit = digit - 9
  46. sum = sum + digit
  47. return (sum % 10) == 0
  48. class BankcardNumberField(forms.CharField):
  49. def clean(self, value):
  50. """Check if given CC number is valid and one of the
  51. card types we accept"""
  52. non_decimal = re.compile(r'\D+')
  53. value = non_decimal.sub('', value.strip())
  54. if value and not luhn(value):
  55. raise forms.ValidationError("Please enter a valid credit card number.")
  56. return super(BankcardNumberField, self).clean(value)
  57. class BankcardMonthWidget(forms.MultiWidget):
  58. """
  59. Widget containing two select boxes for selecting the month and year
  60. """
  61. def decompress(self, value):
  62. return [value.month, value.year] if value else [None, None]
  63. def format_output(self, rendered_widgets):
  64. html = u' '.join(rendered_widgets)
  65. return u'<span style="white-space: nowrap">%s</span>' % html
  66. class BankcardMonthField(forms.MultiValueField):
  67. """
  68. A modified version of the snippet: http://djangosnippets.org/snippets/907/
  69. """
  70. default_error_messages = {
  71. 'invalid_month': u'Enter a valid month.',
  72. 'invalid_year': u'Enter a valid year.',
  73. }
  74. def __init__(self, *args, **kwargs):
  75. errors = self.default_error_messages.copy()
  76. if 'error_messages' in kwargs:
  77. errors.update(kwargs['error_messages'])
  78. fields = (
  79. forms.ChoiceField(choices=self.month_choices(),
  80. error_messages={'invalid': errors['invalid_month']}),
  81. forms.ChoiceField(choices=self.year_choices(),
  82. error_messages={'invalid': errors['invalid_year']}),
  83. )
  84. super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
  85. self.widget = BankcardMonthWidget(widgets = [fields[0].widget, fields[1].widget])
  86. def month_choices(self):
  87. return []
  88. def year_choices(self):
  89. return []
  90. class BankcardExpiryMonthField(BankcardMonthField):
  91. """
  92. Expiry month
  93. """
  94. def month_choices(self):
  95. return [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  96. def year_choices(self):
  97. return [(x, x) for x in xrange( date.today().year, date.today().year+5)]
  98. def clean(self, value):
  99. expiry_date = super(BankcardExpiryMonthField, self).clean(value)
  100. if date.today() > expiry_date:
  101. raise forms.ValidationError("The expiration date you entered is in the past.")
  102. return expiry_date
  103. def compress(self, data_list):
  104. if data_list:
  105. if data_list[1] in forms.fields.EMPTY_VALUES:
  106. error = self.error_messages['invalid_year']
  107. raise forms.ValidationError(error)
  108. if data_list[0] in forms.fields.EMPTY_VALUES:
  109. error = self.error_messages['invalid_month']
  110. raise forms.ValidationError(error)
  111. year = int(data_list[1])
  112. month = int(data_list[0])
  113. # find last day of the month
  114. day = monthrange(year, month)[1]
  115. return date(year, month, day)
  116. return None
  117. class BankcardStartingMonthField(BankcardMonthField):
  118. """
  119. Starting month
  120. """
  121. def month_choices(self):
  122. months = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
  123. months.insert(0, ("", "--"))
  124. return months
  125. def year_choices(self):
  126. years = [(x, x) for x in xrange( date.today().year - 5, date.today().year)]
  127. years.insert(0, ("", "--"))
  128. return years
  129. def clean(self, value):
  130. starting_date = super(BankcardMonthField, self).clean(value)
  131. if starting_date and date.today() < starting_date:
  132. raise forms.ValidationError("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 BankcardForm(forms.ModelForm):
  147. number = BankcardNumberField(max_length=20, widget=forms.TextInput(attrs={'autocomplete':'off'}), label="Card number")
  148. name = forms.CharField(max_length=128, label="Name on card")
  149. ccv_number = forms.IntegerField(required=True, label="CCV Number",
  150. max_value = 99999, widget=forms.TextInput(attrs={'size': '5'}))
  151. start_month = BankcardStartingMonthField(label="Valid from", required=False)
  152. expiry_month = BankcardExpiryMonthField(required=True, label = "Valid to")
  153. class Meta:
  154. model = payment_models.Bankcard
  155. exclude = ('user', 'partner_reference')
  156. fields = ('number', 'name', 'start_month', 'expiry_month', 'ccv_number')
  157. class BillingAddressForm(forms.ModelForm):
  158. def __init__(self, *args, **kwargs):
  159. super(BillingAddressForm,self ).__init__(*args, **kwargs)
  160. self.set_country_queryset()
  161. def set_country_queryset(self):
  162. self.fields['country'].queryset = address_models.Country._default_manager.all()
  163. class Meta:
  164. model = order_models.BillingAddress