Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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