python-terminusgps 33.1.0__py3-none-any.whl → 33.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-terminusgps
3
- Version: 33.1.0
3
+ Version: 33.3.0
4
4
  Summary: Provides abstractions/utilities for working with Wialon API, Authorize.NET API, AWS API, and more.
5
5
  Project-URL: Documentation, https://docs.terminusgps.com
6
6
  Project-URL: Repository, https://github.com/terminusgps/python-terminusgps
@@ -3,7 +3,7 @@ terminusgps/authorizenet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
3
3
  terminusgps/authorizenet/auth.py,sha256=umNpOXObDrIt2K40padWBedQl7tVAmEg2alBWhGk-qQ,1238
4
4
  terminusgps/authorizenet/constants.py,sha256=xmulPmiBLhFfZoc7Il3l-DZA9Nq5HSaJmJKU-v-5X-Y,420
5
5
  terminusgps/authorizenet/controllers.py,sha256=9t62qdGpmAkP4tNyPefd7vBRutXvMWsxql6z_pg52Bg,2146
6
- terminusgps/authorizenet/utils.py,sha256=zmBVqRamCmxEO6tZWRaGPn5uvL_Lr0yO36Lgc954m8k,2930
6
+ terminusgps/authorizenet/utils.py,sha256=FrKqXWrplcgiET1rDTmUaqmN_ze7j93jWgfFeJR8rlg,7073
7
7
  terminusgps/authorizenet/profiles/__init__.py,sha256=IG4XCEEYtHKqXOatlZlob6J3ukAgF7cFR-RXxOSMS1c,161
8
8
  terminusgps/authorizenet/profiles/addresses.py,sha256=xDlnFlIjuR7NVXwfj5aJT9E_GolnYFuYASkfenvWfm8,6292
9
9
  terminusgps/authorizenet/profiles/base.py,sha256=lPc8Pugp5EcXtZmeBpeqicij7l0ROGvMYTV8J0fLLmQ,3768
@@ -12,17 +12,23 @@ terminusgps/authorizenet/profiles/payments.py,sha256=MmjvE4ZSfgyGPRUzeREB_dX_n6Q
12
12
  terminusgps/authorizenet/profiles/subscriptions.py,sha256=m_jb9GqeD1tLmeqSnaGwJrzPcQOulEkF8K1wCaqKR24,7924
13
13
  terminusgps/authorizenet/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  terminusgps/authorizenet/tests/test_auth.py,sha256=sdByYrcv8RKjcJckOHbyHH1z_M4qrATFI3qj_XDQXbk,1624
15
- terminusgps/authorizenet/tests/test_profiles.py,sha256=24lo71F5MWOUNdB2MwTAstPUkyfZ58aOiIxiRwyWcd0,7476
15
+ terminusgps/authorizenet/tests/test_profiles.py,sha256=tvLBY0iSAC33RlWiBP_NSnPskUQBjfrf0YHMpR-m22k,7566
16
16
  terminusgps/authorizenet/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  terminusgps/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  terminusgps/aws/connect.py,sha256=TWf2bVz6LqSNQfVD7FKRn3K71VuLOwBj05Gs-9BsROg,693
19
19
  terminusgps/aws/secrets.py,sha256=MxQEmmBLpMUQ2tYAsHdCYf-7RZ84LiogdKcTsACX5dw,361
20
20
  terminusgps/aws/utils.py,sha256=68GWcllu4h41qzFiKdUkWOAA4LDsTPqRY446VRMxYsY,116
21
21
  terminusgps/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- terminusgps/django/forms.py,sha256=47z0NWtA1edkHviVmmpwmG-ofxU6j36BCRzO3UW3aek,4989
23
22
  terminusgps/django/mixins.py,sha256=Q9ZJuzpk3d9lDnlVA8ZTVvnZWxB13p08EQ8yVJcztn4,1034
24
23
  terminusgps/django/settings.py,sha256=yJIcBE0xjY60AXF_gonY5hS3ofseqPW2k9uSoELgoho,639
25
24
  terminusgps/django/utils.py,sha256=SYDQyHA5tTuVwdpZGsCtf0LpTTD0ilRKoxa8StfSTpQ,156
25
+ terminusgps/django/forms/__init__.py,sha256=hh8Z9AC_u2m2bu3h6sdmPyndfQneCEVovENPHmoTDC0,21
26
+ terminusgps/django/forms/fields.py,sha256=BfuPzOZyGvWsEzONW2h4i01fhoL0IS0-gDdBKW4jHgI,2225
27
+ terminusgps/django/forms/forms.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ terminusgps/django/forms/widgets.py,sha256=Hu6jNVx0i8JOOvAV1tuSHRqNI3mV2wijuKupUhol46c,1765
29
+ terminusgps/django/tests/test_forms.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
30
+ terminusgps/django/tests/test_mixins.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ terminusgps/django/tests/test_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
32
  terminusgps/twilio/__init__.py,sha256=dYo41F2jft_eHDWSUtSpKGSRG1bewq_qClqilUJZkYM,33
27
33
  terminusgps/twilio/caller.py,sha256=l53-IxXn0wXc_oDcDxfhlrKPz-F4_KfMPV1dAzZjNo4,5060
28
34
  terminusgps/twilio/validators.py,sha256=HWCpzgDXTedofty4Zhrm2OenbgtkzrDUC08B1l15gqc,1112
@@ -47,7 +53,7 @@ terminusgps/wialon/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
47
53
  terminusgps/wialon/tests/test_items.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
54
  terminusgps/wialon/tests/test_session.py,sha256=9mBlYchMo9NeqRIZsmxYzrM1zBHorqu4ooxqSNppLpI,1336
49
55
  terminusgps/wialon/tests/test_utils.py,sha256=SK4PxJQGECFnzx_EQeRAQfsQ5_3FLaVcis2W9u_ibuI,1730
50
- python_terminusgps-33.1.0.dist-info/METADATA,sha256=K_96np1Rmj8DPiPs4pxWKQygqzSkHm7Zzlloa2y8hNc,1581
51
- python_terminusgps-33.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
- python_terminusgps-33.1.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
53
- python_terminusgps-33.1.0.dist-info/RECORD,,
56
+ python_terminusgps-33.3.0.dist-info/METADATA,sha256=CMGz4nsoWA7NjUaj9BPHN8WVudEFlyYj_UwqUH0T8R8,1581
57
+ python_terminusgps-33.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
58
+ python_terminusgps-33.3.0.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
59
+ python_terminusgps-33.3.0.dist-info/RECORD,,
@@ -149,7 +149,9 @@ class PaymentProfileTestCase(TestCase):
149
149
  def test_payment_profile_create(self) -> None:
150
150
  """Fails if a payment profile is not properly created in Authorizenet."""
151
151
  test_payment_profile = PaymentProfile(
152
- id=None, customer_profile_id=self.test_customer_profile.id, default=True
152
+ id=None,
153
+ customer_profile_id=str(self.test_customer_profile.id),
154
+ default=True,
153
155
  )
154
156
  test_payment_profile.id = test_payment_profile.create(
155
157
  address=self.test_address_obj, payment=self.test_payment_obj
@@ -159,7 +161,9 @@ class PaymentProfileTestCase(TestCase):
159
161
  def test_payment_profile_delete(self) -> None:
160
162
  """Fails if a payment profile is not properly deleted in Authorizenet."""
161
163
  test_payment_profile = PaymentProfile(
162
- id=None, customer_profile_id=self.test_customer_profile.id, default=True
164
+ id=None,
165
+ customer_profile_id=str(self.test_customer_profile.id),
166
+ default=True,
163
167
  )
164
168
  test_payment_profile.id = test_payment_profile.create(
165
169
  address=self.test_address_obj, payment=self.test_payment_obj
@@ -170,7 +174,9 @@ class PaymentProfileTestCase(TestCase):
170
174
  def test_payment_profile_last_4(self) -> None:
171
175
  """Fails if a payment profile's last 4 credit card digits are not properly retrieved."""
172
176
  test_payment_profile = PaymentProfile(
173
- id=None, customer_profile_id=self.test_customer_profile.id, default=True
177
+ id=None,
178
+ customer_profile_id=str(self.test_customer_profile.id),
179
+ default=True,
174
180
  )
175
181
  test_payment_profile.id = test_payment_profile.create(
176
182
  address=self.test_address_obj, payment=self.test_payment_obj
@@ -5,11 +5,107 @@ from .auth import get_merchant_auth
5
5
  from .controllers import AuthorizenetControllerExecutor
6
6
 
7
7
 
8
+ def get_payment_profile_transactions(
9
+ customer_profile_id: int | str,
10
+ payment_profile_id: int | str,
11
+ descending: bool = False,
12
+ limit: int = 100,
13
+ offset: int = 0,
14
+ ) -> dict | None:
15
+ """
16
+ Returns a transaction list for a customer payment profile.
17
+
18
+ `getTransactionListForCustomerRequest <https://developer.authorize.net/api/reference/index.html#transaction-reporting-get-customer-profile-transaction-list>`_
19
+
20
+ :param customer_profile_id: A customer profile id.
21
+ :type customer_profile_id: :py:obj:`int` | :py:obj:`str`
22
+ :param payment_profile_id: A customer payment profile id.
23
+ :type payment_profile_id: :py:obj:`int` | :py:obj:`str`
24
+ :param descending: Whether or not to sort the response in descending order. Default is :py:obj:`False`.
25
+ :type descending: :py:obj:`bool`
26
+ :param limit: Maximum amount of transactions to return in the response. Default is :py:obj:`100` .
27
+ :type limit: :py:obj:`int`
28
+ :param offset: Offsets the response for pagination. Default is :py:obj:`0` for page 0.
29
+ :type offset: :py:obj:`int`
30
+ :raises ValueError: If ``customer_profile_id`` was provided as a string containing non-digits.
31
+ :raises ValueError: If ``payment_profile_id`` was provided as a string containing non-digits.
32
+ :raises AuthorizenetControllerExecutionError: If the API call fails.
33
+ :returns: A merchant details object, if found.
34
+ :rtype: :py:obj:`dict` | :py:obj:`None`
35
+
36
+ """
37
+ raise NotImplementedError
38
+
39
+ if isinstance(customer_profile_id, str) and not customer_profile_id.isdigit():
40
+ raise ValueError(
41
+ f"'customer_profile_id' can only contain digits, got '{customer_profile_id}'."
42
+ )
43
+ if isinstance(payment_profile_id, str) and not payment_profile_id.isdigit():
44
+ raise ValueError(
45
+ f"'payment_profile_id' can only contain digits, got '{payment_profile_id}'."
46
+ )
47
+
48
+ request = apicontractsv1.getTransactionListForCustomerRequest(
49
+ merchantAuthentication=get_merchant_auth(),
50
+ customerProfileId=str(customer_profile_id),
51
+ customerPaymentProfileId=str(payment_profile_id),
52
+ sorting={
53
+ "orderBy": "submitTimeUTC",
54
+ "orderDescending": str(descending).lower(),
55
+ },
56
+ paging={"limit": limit, "offset": offset},
57
+ )
58
+ controller = apicontrollers.getTransactionListForCustomerController(request)
59
+ return AuthorizenetControllerExecutor.execute_controller(controller)
60
+
61
+
62
+ def get_merchant_details() -> dict | None:
63
+ """
64
+ Returns Authorizenet merchant details.
65
+
66
+ `getMerchantDetailsRequest <http://developer.authorize.net/api/reference/index.html#transaction-reporting-get-merchant-details>`_
67
+
68
+ :raises AuthorizenetControllerExecutionError: If the API call fails.
69
+ :returns: A merchant details object, if found.
70
+ :rtype: :py:obj:`dict` | :py:obj:`None`
71
+
72
+ """
73
+ request = apicontractsv1.getMerchantDetailsRequest(
74
+ merchantAuthentication=get_merchant_auth()
75
+ )
76
+ controller = apicontrollers.getMerchantDetailsController(request)
77
+ return AuthorizenetControllerExecutor.execute_controller(controller)
78
+
79
+
80
+ def get_transaction(id: int | str) -> dict | None:
81
+ """
82
+ Returns Authorizenet transaction details by id.
83
+
84
+ `getTransactionDetailsRequest <https://developer.authorize.net/api/reference/index.html#transaction-reporting-get-transaction-details>`_
85
+
86
+ :param id: An Authorizenet transaction id.
87
+ :type id: :py:obj:`int` | :py:obj:`str`
88
+ :raises ValueError: If ``id`` was provided as a string containing non-digits.
89
+ :raises AuthorizenetControllerExecutionError: If the API call fails.
90
+ :returns: A transaction object, if found.
91
+ :rtype: :py:obj:`dict` | :py:obj:`None`
92
+
93
+ """
94
+ if isinstance(id, str) and not id.isdigit():
95
+ raise ValueError(f"'id' can only contain digits, got '{id}'.")
96
+
97
+ request = apicontractsv1.getTransactionDetailsRequest(
98
+ merchantAuthentication=get_merchant_auth(), transId=str(id)
99
+ )
100
+ controller = apicontrollers.getTransactionDetailsController(request)
101
+ return AuthorizenetControllerExecutor.execute_controller(controller)
102
+
103
+
8
104
  def get_customer_profile_ids() -> list[int]:
9
105
  """
10
106
  Returns a list of all customer profile ids in Authorizenet.
11
107
 
12
- :raises ControllerExecutionError: If the API call fails.
108
+ :raises AuthorizenetControllerExecutionError: If the API call fails.
13
109
  :returns: A list of all customer profile ids in Authorizenet.
14
110
  :rtype: :py:obj:`list`
15
111
 
@@ -0,0 +1 @@
1
+ from .forms import *
@@ -0,0 +1,57 @@
1
+ from collections.abc import Sequence
2
+
3
+ from authorizenet import apicontractsv1
4
+ from django import forms
5
+
6
+ from .widgets import AddressWidget, CreditCardWidget
7
+
8
+
9
+ class CreditCardField(forms.MultiValueField):
10
+ require_all_fields = True
11
+
12
+ def __init__(self, fields=(), widget=CreditCardWidget(), *args, **kwargs) -> None:
13
+ if not fields:
14
+ fields = (
15
+ forms.CharField(label="Card #"),
16
+ forms.IntegerField(label="Expiry Month", min_value=1, max_value=12),
17
+ forms.IntegerField(label="Expiry Year"),
18
+ forms.CharField(label="CCV #"),
19
+ )
20
+ super().__init__(fields=fields, widget=widget, *args, **kwargs)
21
+
22
+ def compress(self, data_list: Sequence[str]) -> apicontractsv1.creditCardType:
23
+ """Compresses ``data_list`` into a :py:obj:`~authorizenet.apicontractsv1.creditCardType`."""
24
+ return apicontractsv1.creditCardType(
25
+ **{
26
+ "cardNumber": data_list[0],
27
+ "expirationDate": f"{data_list[1]}-{data_list[2]}",
28
+ "cardCode": data_list[3],
29
+ }
30
+ )
31
+
32
+
33
+ class AddressField(forms.MultiValueField):
34
+ require_all_fields = True
35
+
36
+ def __init__(self, fields=(), widget=AddressWidget(), *args, **kwargs) -> None:
37
+ if not fields:
38
+ fields = (
39
+ forms.CharField(label="Street", max_length=128),
40
+ forms.CharField(label="City", max_length=128),
41
+ forms.CharField(label="State", max_length=64),
42
+ forms.CharField(label="Zip #", min_length=5, max_length=10),
43
+ forms.CharField(label="Country", max_length=2),
44
+ )
45
+ super().__init__(fields=fields, widget=widget, *args, **kwargs)
46
+
47
+ def compress(self, data_list: Sequence[str]) -> apicontractsv1.customerAddressType:
48
+ """Compresses ``data_list`` into a :py:obj:`~authorizenet.apicontractsv1.customerAddressType`."""
49
+ return apicontractsv1.customerAddressType(
50
+ **{
51
+ "address": data_list[0],
52
+ "city": data_list[1],
53
+ "state": data_list[2],
54
+ "zip": data_list[3],
55
+ "country": data_list[4],
56
+ }
57
+ )
File without changes
@@ -0,0 +1,46 @@
1
+ from authorizenet import apicontractsv1
2
+ from django import forms
3
+
4
+
5
+ class CreditCardWidget(forms.widgets.MultiWidget):
6
+ def __init__(self, widgets=(), attrs: dict | None = None) -> None:
7
+ if not widgets:
8
+ widgets = [
9
+ forms.TextInput(attrs=attrs),
10
+ forms.TextInput(attrs=attrs),
11
+ forms.TextInput(attrs=attrs),
12
+ forms.TextInput(attrs=attrs),
13
+ ]
14
+ super().__init__(widgets=widgets, attrs=attrs)
15
+
16
+ def decompress(
17
+ self, value: apicontractsv1.creditCardType | None
18
+ ) -> list[str | None]:
19
+ """Decompresses a :py:attr:`~authorizenet.apicontractsv1.creditCardType` into a list of strings."""
20
+ if value is None:
21
+ return [None, None, None, None]
22
+
23
+ expiry_parts = value.expirationDate.split("-")
24
+ return [value.cardNumber, expiry_parts[0], expiry_parts[1], value.cardCode]
25
+
26
+
27
+ class AddressWidget(forms.widgets.MultiWidget):
28
+ def __init__(self, widgets=(), attrs: dict | None = None) -> None:
29
+ if not widgets:
30
+ widgets = [
31
+ forms.TextInput(attrs=attrs),
32
+ forms.TextInput(attrs=attrs),
33
+ forms.TextInput(attrs=attrs),
34
+ forms.TextInput(attrs=attrs),
35
+ forms.TextInput(attrs=attrs),
36
+ ]
37
+ super().__init__(widgets=widgets, attrs=attrs)
38
+
39
+ def decompress(
40
+ self, value: apicontractsv1.customerAddressType | None
41
+ ) -> list[str | None]:
42
+ """Decompresses a :py:attr:`~authorizenet.apicontractsv1.customerAddressType` into a list of strings."""
43
+ if value is None:
44
+ return [None, None, None, None, None]
45
+
46
+ return [value.address, value.city, value.state, value.country, value.zip]
@@ -0,0 +1 @@
1
+
File without changes
File without changes
@@ -1,139 +0,0 @@
1
- from collections.abc import Sequence
2
-
3
- from authorizenet import apicontractsv1
4
- from django import forms
5
-
6
-
7
- class CreditCardWidget(forms.widgets.MultiWidget):
8
- def __init__(self, widgets=(), attrs: dict | None = None) -> None:
9
- if not widgets:
10
- widgets = [
11
- forms.TextInput(attrs=attrs),
12
- forms.TextInput(attrs=attrs),
13
- forms.TextInput(attrs=attrs),
14
- forms.TextInput(attrs=attrs),
15
- ]
16
- super().__init__(widgets=widgets, attrs=attrs)
17
-
18
- def decompress(
19
- self, value: apicontractsv1.creditCardType | None
20
- ) -> list[str | None]:
21
- """Decompresses a :py:attr:`~authorizenet.apicontractsv1.creditCardType` into a list of strings."""
22
- if value is None:
23
- return [None, None, None, None]
24
-
25
- expiry_parts = value.expirationDate.split("-")
26
- return [value.cardNumber, expiry_parts[0], expiry_parts[1], value.cardCode]
27
-
28
-
29
- class AddressWidget(forms.widgets.MultiWidget):
30
- def __init__(self, widgets=(), attrs: dict | None = None) -> None:
31
- if not widgets:
32
- widgets = [
33
- forms.TextInput(attrs=attrs),
34
- forms.TextInput(attrs=attrs),
35
- forms.TextInput(attrs=attrs),
36
- forms.TextInput(attrs=attrs),
37
- forms.TextInput(attrs=attrs),
38
- ]
39
- super().__init__(widgets=widgets, attrs=attrs)
40
-
41
- def decompress(
42
- self, value: apicontractsv1.customerAddressType | None
43
- ) -> list[str | None]:
44
- """Decompresses a :py:attr:`~authorizenet.apicontractsv1.customerAddressType` into a list of strings."""
45
- if value is None:
46
- return [None, None, None, None, None]
47
-
48
- return [value.address, value.city, value.state, value.country, value.zip]
49
-
50
-
51
- class CreditCardField(forms.MultiValueField):
52
- require_all_fields = True
53
-
54
- def __init__(self, fields=(), widget=CreditCardWidget(), *args, **kwargs) -> None:
55
- if not fields:
56
- fields = (
57
- forms.CharField(label="Card #"),
58
- forms.IntegerField(label="Expiry Month", min_value=1, max_value=12),
59
- forms.IntegerField(label="Expiry Year"),
60
- forms.CharField(label="CCV #"),
61
- )
62
- super().__init__(fields=fields, widget=widget, *args, **kwargs)
63
-
64
- def compress(self, data_list: Sequence[str]) -> apicontractsv1.creditCardType:
65
- """Compresses ``data_list`` into a :py:obj:`~authorizenet.apicontractsv1.creditCardType`."""
66
- return apicontractsv1.creditCardType(
67
- **{
68
- "cardNumber": data_list[0],
69
- "expirationDate": f"{data_list[1]}-{data_list[2]}",
70
- "cardCode": data_list[3],
71
- }
72
- )
73
-
74
-
75
- class AddressField(forms.MultiValueField):
76
- require_all_fields = True
77
-
78
- def __init__(self, fields=(), widget=AddressWidget(), *args, **kwargs) -> None:
79
- if not fields:
80
- fields = (
81
- forms.CharField(label="Street", max_length=128),
82
- forms.CharField(label="City", max_length=128),
83
- forms.CharField(label="State", max_length=64),
84
- forms.CharField(label="Zip #", min_length=5, max_length=10),
85
- forms.CharField(label="Country", max_length=2),
86
- )
87
- super().__init__(fields=fields, widget=widget, *args, **kwargs)
88
-
89
- def compress(self, data_list: Sequence[str]) -> apicontractsv1.customerAddressType:
90
- """Compresses ``data_list`` into a :py:obj:`~authorizenet.apicontractsv1.customerAddressType`."""
91
- return apicontractsv1.customerAddressType(
92
- **{
93
- "address": data_list[0],
94
- "city": data_list[1],
95
- "state": data_list[2],
96
- "zip": data_list[3],
97
- "country": data_list[4],
98
- }
99
- )
100
-
101
-
102
- def main() -> None:
103
- import os
104
-
105
- import django
106
-
107
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "terminusgps.django.settings")
108
- django.setup()
109
-
110
- class TestForm(forms.Form):
111
- first_name = forms.CharField(label="First Name")
112
- last_name = forms.CharField(label="Last Name")
113
- phone = forms.CharField(label="Phone #")
114
- address = AddressField(label="Address")
115
- credit_card = CreditCardField(label="Credit Card")
116
-
117
- form_data = {
118
- "first_name": "TestFirst",
119
- "last_name": "TestLast",
120
- "phone": "555-555-5555",
121
- "address_0": "123 Main St.",
122
- "address_1": "Houston",
123
- "address_2": "Texas",
124
- "address_3": "77065",
125
- "address_4": "US",
126
- "credit_card_0": "4111111111111111",
127
- "credit_card_1": "1",
128
- "credit_card_2": "41",
129
- "credit_card_3": "444",
130
- }
131
- form = TestForm(form_data)
132
- print(f"{form.is_valid() = }")
133
- print(f"{form.errors = }")
134
- print(f"{type(form.clean()["address"]) = }")
135
- print(f"{type(form.clean()["credit_card"]) = }")
136
-
137
-
138
- if __name__ == "__main__":
139
- main()