Przeglądaj źródła

Update payment forms (and tests)

Fields now more self-contained, the bankcard form is much simpler.
master
David Winterbottom 12 lat temu
rodzic
commit
65e6effd73
2 zmienionych plików z 178 dodań i 41 usunięć
  1. 77
    38
      oscar/apps/payment/forms.py
  2. 101
    3
      tests/unit/payment/form_tests.py

+ 77
- 38
oscar/apps/payment/forms.py Wyświetl plik

@@ -18,14 +18,26 @@ Bankcard = get_class('payment.utils', 'Bankcard')
18 18
 
19 19
 class BankcardNumberField(forms.CharField):
20 20
 
21
+    def __init__(self, *args, **kwargs):
22
+        _kwargs = {
23
+            'max_length': 20,
24
+            'widget': forms.TextInput(attrs={'autocomplete': 'off'}),
25
+            'label': _("Card number")
26
+        }
27
+        _kwargs.update(kwargs)
28
+        super(BankcardNumberField, self).__init__(*args, **_kwargs)
29
+
21 30
     def clean(self, value):
22
-        """Check if given CC number is valid and one of the
23
-           card types we accept"""
31
+        """
32
+        Check if given CC number is valid and one of the
33
+        card types we accept
34
+        """
24 35
         non_decimal = re.compile(r'\D+')
25 36
         value = non_decimal.sub('', value.strip())
26 37
 
27 38
         if value and not bankcards.luhn(value):
28
-            raise forms.ValidationError(_("Please enter a valid credit card number."))
39
+            raise forms.ValidationError(
40
+                _("Please enter a valid credit card number."))
29 41
         return super(BankcardNumberField, self).clean(value)
30 42
 
31 43
 
@@ -62,14 +74,16 @@ class BankcardMonthField(forms.MultiValueField):
62 74
             errors.update(kwargs['error_messages'])
63 75
 
64 76
         fields = (
65
-            forms.ChoiceField(choices=self.month_choices(),
66
-                              error_messages={'invalid': errors['invalid_month']}),
67
-            forms.ChoiceField(choices=self.year_choices(),
68
-                              error_messages={'invalid': errors['invalid_year']}),
77
+            forms.ChoiceField(
78
+                choices=self.month_choices(),
79
+                error_messages={'invalid': errors['invalid_month']}),
80
+            forms.ChoiceField(
81
+                choices=self.year_choices(),
82
+                error_messages={'invalid': errors['invalid_year']}),
69 83
         )
70
-
71 84
         super(BankcardMonthField, self).__init__(fields, *args, **kwargs)
72
-        self.widget = BankcardMonthWidget(widgets = [fields[0].widget, fields[1].widget])
85
+        self.widget = BankcardMonthWidget(
86
+            widgets=[fields[0].widget, fields[1].widget])
73 87
 
74 88
     def month_choices(self):
75 89
         return []
@@ -79,19 +93,29 @@ class BankcardMonthField(forms.MultiValueField):
79 93
 
80 94
 
81 95
 class BankcardExpiryMonthField(BankcardMonthField):
82
-    """
83
-    Expiry month
84
-    """
96
+
97
+    def __init__(self, *args, **kwargs):
98
+        today = date.today()
99
+        _kwargs = {
100
+            'required': True,
101
+            'label': _("Valid to"),
102
+            'initial': ["%.2d" % today.month, today.year]
103
+        }
104
+        _kwargs.update(kwargs)
105
+        super(BankcardExpiryMonthField, self).__init__(*args, **_kwargs)
106
+
85 107
     def month_choices(self):
86 108
         return [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
87 109
 
88 110
     def year_choices(self):
89
-        return [(x, x) for x in xrange(date.today().year, date.today().year+self.num_years)]
111
+        return [(x, x) for x in xrange(date.today().year,
112
+                                       date.today().year + self.num_years)]
90 113
 
91 114
     def clean(self, value):
92 115
         expiry_date = super(BankcardExpiryMonthField, self).clean(value)
93 116
         if date.today() > expiry_date:
94
-            raise forms.ValidationError(_("The expiration date you entered is in the past."))
117
+            raise forms.ValidationError(
118
+                _("The expiration date you entered is in the past."))
95 119
         return expiry_date
96 120
 
97 121
     def compress(self, data_list):
@@ -111,20 +135,32 @@ class BankcardExpiryMonthField(BankcardMonthField):
111 135
 
112 136
 
113 137
 class BankcardStartingMonthField(BankcardMonthField):
138
+
139
+    def __init__(self, *args, **kwargs):
140
+        _kwargs = {
141
+            'required': False,
142
+            'label': _("Valid from"),
143
+        }
144
+        _kwargs.update(kwargs)
145
+        super(BankcardStartingMonthField, self).__init__(*args, **_kwargs)
146
+
114 147
     def month_choices(self):
115 148
         months = [("%.2d" % x, "%.2d" % x) for x in xrange(1, 13)]
116 149
         months.insert(0, ("", "--"))
117 150
         return months
118 151
 
119 152
     def year_choices(self):
120
-        years = [(x, x) for x in xrange(date.today().year - self.num_years, date.today().year + 1)]
153
+        today = date.today()
154
+        years = [(x, x) for x in xrange(today.year - self.num_years,
155
+                                        today.year + 1)]
121 156
         years.insert(0, ("", "--"))
122 157
         return years
123 158
 
124 159
     def clean(self, value):
125 160
         starting_date = super(BankcardMonthField, self).clean(value)
126 161
         if starting_date and date.today() < starting_date:
127
-            raise forms.ValidationError(_("The starting date you entered is in the future."))
162
+            raise forms.ValidationError(
163
+                _("The starting date you entered is in the future."))
128 164
         return starting_date
129 165
 
130 166
     def compress(self, data_list):
@@ -141,32 +177,35 @@ class BankcardStartingMonthField(BankcardMonthField):
141 177
         return None
142 178
 
143 179
 
144
-class BankcardForm(forms.ModelForm):
145
-    number = BankcardNumberField(max_length=20, widget=forms.TextInput(attrs={'autocomplete':'off'}),
146
-                                 label=_("Card number"))
147
-    # Name is not normally needed by payment gateways so we hide it by default
148
-    name = forms.CharField(max_length=128, label=_("Name on card"),
149
-                           widget=forms.widgets.HiddenInput, required=False)
150
-    cvv_number = forms.RegexField(
151
-        required=True, label=_("CVV Number"),
152
-        regex=r'^\d{3,4}$',
153
-        widget=forms.TextInput(attrs={'size': '5'}),
154
-        help_text=_("This is the 3 or 4 digit security number on the back of your bankcard"))
155
-    start_month = BankcardStartingMonthField(label=_("Valid from"), required=False)
156
-    expiry_month = BankcardExpiryMonthField(required=True, label=_("Valid to"))
180
+class BankcardCCVField(forms.RegexField):
157 181
 
158 182
     def __init__(self, *args, **kwargs):
159
-        if 'initial' not in kwargs:
160
-            # Set the initial expiry month to be current month
161
-            today = date.today()
162
-            kwargs['initial'] = {'expiry_month': ["%.2d" % today.month,
163
-                                                  today.year]}
164
-        super(BankcardForm, self).__init__(*args, **kwargs)
183
+        _kwargs = {
184
+            'required': True,
185
+            'label': _("CCV number"),
186
+            'widget': forms.TextInput(attrs={'size': '5'}),
187
+            'error_message': _("Please enter a 3 or 4 digit number"),
188
+            'help_text': _("This is the 3 or 4 digit security number "
189
+                           "on the back of your bankcard")
190
+        }
191
+        _kwargs.update(kwargs)
192
+        super(BankcardCCVField, self).__init__(
193
+            r'^\d{3,4}$', *args, **_kwargs)
194
+
195
+    def clean(self, value):
196
+        value = value.strip()
197
+        return super(BankcardCCVField, self).clean(value)
198
+
199
+
200
+class BankcardForm(forms.ModelForm):
201
+    number = BankcardNumberField()
202
+    cvv_number = BankcardCCVField()
203
+    start_month = BankcardStartingMonthField()
204
+    expiry_month = BankcardExpiryMonthField()
165 205
 
166 206
     class Meta:
167 207
         model = BankcardModel
168
-        exclude = ('user', 'partner_reference')
169
-        fields = ('number', 'name', 'start_month', 'expiry_month', 'cvv_number')
208
+        fields = ('number', 'start_month', 'expiry_month', 'cvv_number')
170 209
 
171 210
     def get_bankcard_obj(self):
172 211
         """
@@ -186,7 +225,7 @@ class BankcardForm(forms.ModelForm):
186 225
 class BillingAddressForm(AbstractAddressForm):
187 226
 
188 227
     def __init__(self, *args, **kwargs):
189
-        super(BillingAddressForm,self ).__init__(*args, **kwargs)
228
+        super(BillingAddressForm, self).__init__(*args, **kwargs)
190 229
         self.set_country_queryset()
191 230
 
192 231
     def set_country_queryset(self):

+ 101
- 3
tests/unit/payment/form_tests.py Wyświetl plik

@@ -1,12 +1,110 @@
1
+import datetime
2
+
1 3
 from django.test import TestCase
4
+from django.forms import ValidationError
2 5
 
3 6
 from oscar.apps.payment import forms
4 7
 
5 8
 
6
-class BankcardNumberFieldTest(TestCase):
9
+class TestBankcardNumberField(TestCase):
7 10
 
8 11
     def setUp(self):
9 12
         self.field = forms.BankcardNumberField()
10 13
 
11
-    def test_spaces_are_stipped(self):
12
-        self.assertEquals('4111111111111111', self.field.clean('  4111 1111 1111 1111'))
14
+    def test_strips_non_digits(self):
15
+        self.assertEquals(
16
+            '4111111111111111', self.field.clean('  4111 1111 1111 1111'))
17
+
18
+    def test_rejects_numbers_which_dont_pass_luhn(self):
19
+        with self.assertRaises(ValidationError):
20
+            self.field.clean('1234123412341234')
21
+
22
+
23
+class TestStartingMonthField(TestCase):
24
+
25
+    def setUp(self):
26
+        self.field = forms.BankcardStartingMonthField()
27
+
28
+    def test_returns_a_date(self):
29
+        start_date = self.field.clean(['01', '2010'])
30
+        self.assertTrue(isinstance(start_date, datetime.date))
31
+
32
+    def test_rejects_invalid_months(self):
33
+        with self.assertRaises(ValidationError):
34
+            self.field.clean(['00', '2010'])
35
+
36
+    def test_rejects_invalid_years(self):
37
+        with self.assertRaises(ValidationError):
38
+            self.field.clean(['01', '201'])
39
+
40
+    def test_rejects_months_in_the_future(self):
41
+        today = datetime.date.today()
42
+        with self.assertRaises(ValidationError):
43
+            self.field.clean(['01', today.year + 1])
44
+
45
+    def test_returns_the_first_day_of_month(self):
46
+        start_date = self.field.clean(['01', '2010'])
47
+        self.assertEquals(1, start_date.day)
48
+
49
+
50
+
51
+class TestExpiryMonthField(TestCase):
52
+
53
+    def setUp(self):
54
+        self.field = forms.BankcardExpiryMonthField()
55
+
56
+    def test_returns_a_date(self):
57
+        today = datetime.date.today()
58
+        end_date = self.field.clean(['01', today.year + 1])
59
+        self.assertTrue(isinstance(end_date, datetime.date))
60
+
61
+    def test_rejects_invalid_months(self):
62
+        with self.assertRaises(ValidationError):
63
+            self.field.clean(['00', '2010'])
64
+
65
+    def test_rejects_invalid_years(self):
66
+        with self.assertRaises(ValidationError):
67
+            self.field.clean(['01', '201'])
68
+
69
+    def test_rejects_months_in_the_past(self):
70
+        today = datetime.date.today()
71
+        with self.assertRaises(ValidationError):
72
+            self.field.clean(['01', today.year - 1])
73
+
74
+    def test_returns_last_day_of_month(self):
75
+        today = datetime.date.today()
76
+        end_date = self.field.clean(['01', today.year + 1])
77
+        self.assertEquals(31, end_date.day)
78
+
79
+    def test_defaults_to_current_month(self):
80
+        today = datetime.date.today()
81
+        self.assertEquals(["%.2d" % today.month, today.year],
82
+                          self.field.initial)
83
+
84
+
85
+class TestCCVField(TestCase):
86
+    """CCV field"""
87
+
88
+    def setUp(self):
89
+        self.field = forms.BankcardCCVField()
90
+
91
+    def test_is_required_by_default(self):
92
+        with self.assertRaises(ValidationError):
93
+            self.field.clean("")
94
+
95
+    def test_only_permits_3_or_4_digit_numbers(self):
96
+        invalid = ['00', '12a', '12345']
97
+        for sample in invalid:
98
+            with self.assertRaises(ValidationError):
99
+                self.field.clean(sample)
100
+        valid = ['123', '  123   ', '1235']
101
+        for sample in valid:
102
+            self.field.clean(sample)
103
+
104
+    def test_has_meaningful_error_message(self):
105
+        try:
106
+            self.field.clean("asdf")
107
+        except ValidationError, e:
108
+            self.assertEquals("Please enter a 3 or 4 digit number",
109
+                              e.messages[0])
110
+

Ładowanie…
Anuluj
Zapisz