diff --git a/.env.sample b/.env.sample
index 0d0d0c78..8fbb893e 100644
--- a/.env.sample
+++ b/.env.sample
@@ -24,17 +24,14 @@ AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_STORAGE_BUCKET_NAME=
-# PayPal
-PAYPAL_RECEIVER_EMAIL=
-PAYPAL_BUSINESS_ID=
-PAYPAL_TEST=False
+# Stripe
+STRIPE_PUBLIC_KEY=
+STRIPE_SECRET_KEY=
+STRIPE_LIVE_MODE=False
# Sentry
SENTRY_DSN=
-# Sqreen
-SQREEN_TOKEN=
-
# reCAPTCHA
RECAPTCHA_PUBLIC_KEY=
RECAPTCHA_PRIVATE_KEY=
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b76bd6eb..b52d5933 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -23,9 +23,9 @@
"**/migrations/**",
],
"python.linting.pylintEnabled": false,
- "python.linting.pep8Enabled": true,
- "python.linting.pep8Path": "pycodestyle",
- "python.linting.pep8Args": [
+ "python.linting.pycodestyleEnabled": true,
+ "python.linting.pycodestylePath": "pycodestyle",
+ "python.linting.pycodestyleArgs": [
"--max-line-length=120",
],
"[python]": {
diff --git a/Pipfile b/Pipfile
index 9233b918..f72ec5f4 100644
--- a/Pipfile
+++ b/Pipfile
@@ -34,7 +34,7 @@ json-logging-py = "*"
django-anymail = "*"
raven = "*"
django-meta = "*"
-sqreen = "*"
+stripe = "*"
[dev-packages]
pylint = "*"
diff --git a/Pipfile.lock b/Pipfile.lock
index e7bd02c3..a38722f4 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "f94fe889c9a50b8640a6bcb926c837596be6bda8bbc90e65303169a3e75d3d69"
+ "sha256": "9e8ecdbe05cf8eccddcadbf7608746ce9093355786948fa06a842e0ab3bf2a19"
},
"pipfile-spec": 6,
"requires": {
@@ -18,26 +18,26 @@
"default": {
"arrow": {
"hashes": [
- "sha256:10257c5daba1a88db34afa284823382f4963feca7733b9107956bed041aff24f",
- "sha256:c2325911fcd79972cf493cfd957072f9644af8ad25456201ae1ede3316576eb4"
+ "sha256:01a16d8a93eddf86a29237f32ae36b29c27f047e79312eb4df5d55fd5a2b3183",
+ "sha256:e1a318a4c0b787833ae46302c02488b6eeef413c6a13324b3261ad320f21ec1e"
],
"index": "pypi",
- "version": "==0.15.2"
+ "version": "==0.15.4"
},
"boto3": {
"hashes": [
- "sha256:7fc97cb2c9cdff905e950750c8e8b23b872a84696158a28852355dc4b712ba3a",
- "sha256:818c56a317c176142dbf1dca3f5b4366c80460c6cc3c4efe22f0bde736571283"
+ "sha256:3fe790bf548c5a6a816779cf38eb844b7126c24d0c56be6561dab177f3bb5fba",
+ "sha256:653f0a7d91783d2267017c905ae6252aa9148c6bd1bd333c727de950007860d7"
],
"index": "pypi",
- "version": "==1.10.2"
+ "version": "==1.10.26"
},
"botocore": {
"hashes": [
- "sha256:8223485841ef4731a5d4943a733295ba69d0005c4ae64c468308cc07f6960d39",
- "sha256:f8e12dc6e536ea512f0ad25b74e7eecdf5d9e09ae92b5de236b535bee7804d5b"
+ "sha256:9fefb42c6d4fa0079a52b49e5491fa0738cca63649f68be180b3ed6c253d2622",
+ "sha256:ee55ce128056c5120680d25c8e8dfa3a08dbe7ac3445dc16997daaa68ae4060e"
],
- "version": "==1.13.2"
+ "version": "==1.13.26"
},
"certifi": {
"hashes": [
@@ -75,11 +75,11 @@
},
"django": {
"hashes": [
- "sha256:4025317ca01f75fc79250ff7262a06d8ba97cd4f82e93394b2a0a6a4a925caeb",
- "sha256:a8ca1033acac9f33995eb2209a6bf18a4681c3e5269a878e9a7e0b7384ed1ca3"
+ "sha256:16040e1288c6c9f68c6da2fe75ebde83c0a158f6f5d54f4c5177b0c1478c5b86",
+ "sha256:89c2007ca4fa5b351a51a279eccff298520783b713bf28efb89dfb81c80ea49b"
],
"index": "pypi",
- "version": "==2.2.6"
+ "version": "==2.2.7"
},
"django-allauth": {
"hashes": [
@@ -135,11 +135,11 @@
},
"django-debug-toolbar": {
"hashes": [
- "sha256:17c53cd6bf4e7d69902aedf9a1d26c5d3b7369b54c5718744704f27b5a72f35d",
- "sha256:9a23ada2e43cd989195db3c18710b5d7451134a0d48127ab64c1d2ad81700342"
+ "sha256:24c157bc6c0e1648e0a6587511ecb1b007a00a354ce716950bff2de12693e7a8",
+ "sha256:77cfba1d6e91b9bc3d36dc7dc74a9bb80be351948db5f880f2562a0cbf20b6c5"
],
"index": "pypi",
- "version": "==2.0"
+ "version": "==2.1"
},
"django-heroku": {
"hashes": [
@@ -214,11 +214,11 @@
},
"django-storages": {
"hashes": [
- "sha256:87287b7ad2e789cd603373439994e1ac6f94d9dc2e5f8173d2a87aa3ed458bd9",
- "sha256:f3b3def96493d3ccde37b864cea376472baf6e8a596504b209278801c510b807"
+ "sha256:0a9b7e620e969fb0797523695329ed223bf540bbfdf6cd163b061fc11dab2d1c",
+ "sha256:9322ab74ba6371e2e0fccc350c741686ade829e43085597b26b07ae8955a0a00"
],
"index": "pypi",
- "version": "==1.7.2"
+ "version": "==1.8"
},
"docutils": {
"hashes": [
@@ -230,8 +230,8 @@
},
"et-xmlfile": {
"hashes": [
- "sha256:49ae4c7dbbaee5159ba04ed073906bb50c54460cdb05255527d614e633e64236",
- "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b"
+ "sha256:614d9722d572f6246302c4491846d2c393c199cfa4edc9af593437691683335b",
+ "sha256:7e2278770ee0955e3254d5b25aded150b474c72aff0c76d40053343f3f471661"
],
"version": "==1.0.1"
},
@@ -245,18 +245,18 @@
},
"faker": {
"hashes": [
- "sha256:5902379d8df308a204fc11c4f621590ee83975805a6c7b2228203b9defa45250",
- "sha256:5e8c755c619f332d5ec28b7586389665f136bcf528e165eb925e87c06a63eda7"
+ "sha256:48c03580720e0b46538d528b1296e4e5b24a809dcaf33a7dddec719489a9edb8",
+ "sha256:6327c665c0d8721280b3036d9c9e851c60092bc1f30c8394cc433f8723e2bda5"
],
- "version": "==2.0.3"
+ "version": "==2.0.4"
},
"gunicorn": {
"hashes": [
- "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
- "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
+ "sha256:5a3bf56a1d44c2ed15dae2f9d2a9e7c87acc3517f7225e27e4ece3098a2b5efc",
+ "sha256:fe457b7093b353cefcb2ebc40d192fc7c8a62bba49da648c9e1fa0671fa654d1"
],
"index": "pypi",
- "version": "==19.9.0"
+ "version": "==20.0.2"
},
"icalendar": {
"hashes": [
@@ -340,9 +340,9 @@
},
"openpyxl": {
"hashes": [
- "sha256:340a1ab2069764559b9d58027a43a24db18db0e25deb80f81ecb8ca7ee5253db"
+ "sha256:a3ee361d3ff04af6048d594775b3a54ffdf215d40fa5c6c78b2a41c0d0b020d3"
],
- "version": "==3.0.0"
+ "version": "==3.0.1"
},
"pillow": {
"hashes": [
@@ -389,10 +389,12 @@
},
"psycopg2": {
"hashes": [
+ "sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677",
"sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d",
"sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b",
"sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c",
"sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0",
+ "sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f",
"sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4",
"sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38",
"sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6",
@@ -427,11 +429,13 @@
"sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e",
"sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103",
"sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6",
+ "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1",
"sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9",
"sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e",
"sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f",
"sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd",
"sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8",
+ "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f",
"sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4",
"sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964",
"sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"
@@ -439,29 +443,6 @@
"index": "pypi",
"version": "==2.8.4"
},
- "py-mini-racer": {
- "hashes": [
- "sha256:0176dfd4d0c0f658bdec0314c4ef6c45652df13bcef9fb50899b224b22f0972b",
- "sha256:145d5da31f6ef0a0edcb003f2e2500e73643e4ee76cab956babc11528664b9ec",
- "sha256:2888a2390d16f1c50501a49bb2beed1335c6a434792ac9765c84555016b098b2",
- "sha256:2d56f8a76b7e62d2dbe9e29afff5ef9fe033bc868fc0c26a331f3ab5dbbdbaf4",
- "sha256:2d8c3553a1f960af32d9837656064374620cfaf92f9c238ff09f4fd6010eacfe",
- "sha256:3166b7ac1a00fc7520c7e6a1737abf10245bd225644163f4ed8039501f3c6e11",
- "sha256:3263dca16d89d1be554884d32afe9366344bd7b302928becafe0b2de0b99520a",
- "sha256:38f125b7600a2cfb333e98267c78a5d40500aac849ca51de6d090bc8af7da9c6",
- "sha256:7c942bf330aeff9268c472f9b287e352d9745745ebfd6de95cf7f6cd2b39e6b8",
- "sha256:980accd6df7c646383ba314e8515c739a0d88c31f292f013f2fb1473b41f2992",
- "sha256:ac77863bdcaf930e634d123f23e1d8db0adc04f68bac65565d2bf962f1286b3a",
- "sha256:c0f1136983c10fdadd271c6c8d4f3f866588905b94a12d8d4c97299669751b95",
- "sha256:c8b58e2cc9de46506342046abc54c32ce63f3e8274aa6f6c3c9962011b14193c",
- "sha256:e048e2aa6708a6e4cf0e426d87bcce0660850b3b70ed836bea6198156b89c8cd",
- "sha256:f37917a37f9cc5f3a05640a12de86609736576846c5a55832a71db06ac64387b",
- "sha256:f3e0c55dd997db51d692d9d659060b6c0463a15b15bd4e4db2df7f44bcd1a720",
- "sha256:fc0691f772d6451b0cdb0b2f4ca516a0467bcb936f9d6875bd7d95ea048a5531",
- "sha256:ff2593eb1dde6615dbce69011a6bff42879449498710e5ba1c6e4b96ec45e970"
- ],
- "version": "==0.1.18"
- },
"python-dateutil": {
"hashes": [
"sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb",
@@ -522,14 +503,15 @@
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
],
+ "markers": "python_version >= '3.0'",
"version": "==2.22.0"
},
"requests-oauthlib": {
"hashes": [
- "sha256:bd6533330e8748e94bf0b214775fed487d309b8b8fe823dc45641ebcd9a32f57",
- "sha256:d3ed0c8f2e3bbc6b344fa63d6f933745ab394469da38db16bdddb461c7e25140"
+ "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
+ "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
],
- "version": "==1.2.0"
+ "version": "==1.3.0"
},
"s3transfer": {
"hashes": [
@@ -540,27 +522,10 @@
},
"six": {
"hashes": [
- "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
- "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
- ],
- "version": "==1.12.0"
- },
- "sq-native": {
- "hashes": [
- "sha256:034f2939656a91c3495b4401d2785846b7a6d4561db6e869c80dc3a89c706384",
- "sha256:0c8ea02f665269e5a3ccac397dbf4964836a0df76b458c5b5c1c35279c99be47",
- "sha256:2784dd6594ca2000de46f0552e0fd27818c6a2ccdac8e7bbd0e3321da3c3cdee",
- "sha256:61fba25ec6b069a520f9ab667f5da6862405042e302dab61c1c5f4a2c1f227e5",
- "sha256:6bda4f761434181105ba1f1b8f032da975f5c52f4482c928690865d2b0f531b7",
- "sha256:7bcbc4eaa85fcf0e28271bb074fc5920632ffd295c09b359990f8cbf980a6f7c",
- "sha256:8ac730812ce8020245b2a157d3146a7f2a2d36a11e35fdae9d62aa5236f5c971",
- "sha256:b8d06e4517d0397339833e91efb1dd68697de098299aa17f7ded8c6042024b65",
- "sha256:bd29846f62cf4de921de8f74647fe810d32bd43741fd39a4ef29e0fa22692178",
- "sha256:c0a24a76fb75e5de21ca276a5a2282c3dc8ec56a3f58fc8f7039719cf04ac32d",
- "sha256:c37c3cd60a9fd01ac2fb010cfabc54a5d13a6876b32a9cb0c7ce63288c2bfe14",
- "sha256:fb9339ad7915d009d20fea6bc03338f9acbf5fd19aea455ee582a8d3ae4306e6"
+ "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
+ "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
],
- "version": "==0.4.4"
+ "version": "==1.13.0"
},
"sqlparse": {
"hashes": [
@@ -569,13 +534,13 @@
],
"version": "==0.3.0"
},
- "sqreen": {
+ "stripe": {
"hashes": [
- "sha256:a39dbcf97a5260dd1ff6f71e1fd6074612a83118a26677574488be23f86b8f3c",
- "sha256:db147d7a55b4a2d57d9151e58848315c28987c4e303025d7ccc0e4623d3f4dae"
+ "sha256:babf70550ec710e2588bdd748f021ccb0c150a1baec715c68f57c198608b3345",
+ "sha256:eeacf35f828a7795660cff0d3da6ca60ee3db15d1b9ef227aaeacfe232a52999"
],
"index": "pypi",
- "version": "==1.16.1"
+ "version": "==2.40.0"
},
"tablib": {
"hashes": [
@@ -593,11 +558,11 @@
},
"urllib3": {
"hashes": [
- "sha256:3de946ffbed6e6746608990594d08faac602528ac7015ac28d33cee6a45b7398",
- "sha256:9a107b99a5393caf59c7aa3c1249c16e6879447533d0887f4336dde834c7be86"
+ "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293",
+ "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"
],
"markers": "python_version >= '3.4'",
- "version": "==1.25.6"
+ "version": "==1.25.7"
},
"whitenoise": {
"hashes": [
@@ -624,10 +589,10 @@
"develop": {
"astroid": {
"hashes": [
- "sha256:09a3fba616519311f1af8a461f804b68f0370e100c9264a035aa7846d7852e33",
- "sha256:5a79c9b4bd6c4be777424593f957c996e20beb5f74e0bc332f47713c6f675efe"
+ "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a",
+ "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"
],
- "version": "==2.3.2"
+ "version": "==2.3.3"
},
"autopep8": {
"hashes": [
@@ -645,26 +610,29 @@
},
"lazy-object-proxy": {
"hashes": [
- "sha256:02b260c8deb80db09325b99edf62ae344ce9bc64d68b7a634410b8e9a568edbf",
- "sha256:18f9c401083a4ba6e162355873f906315332ea7035803d0fd8166051e3d402e3",
- "sha256:1f2c6209a8917c525c1e2b55a716135ca4658a3042b5122d4e3413a4030c26ce",
- "sha256:2f06d97f0ca0f414f6b707c974aaf8829c2292c1c497642f63824119d770226f",
- "sha256:616c94f8176808f4018b39f9638080ed86f96b55370b5a9463b2ee5c926f6c5f",
- "sha256:63b91e30ef47ef68a30f0c3c278fbfe9822319c15f34b7538a829515b84ca2a0",
- "sha256:77b454f03860b844f758c5d5c6e5f18d27de899a3db367f4af06bec2e6013a8e",
- "sha256:83fe27ba321e4cfac466178606147d3c0aa18e8087507caec78ed5a966a64905",
- "sha256:84742532d39f72df959d237912344d8a1764c2d03fe58beba96a87bfa11a76d8",
- "sha256:874ebf3caaf55a020aeb08acead813baf5a305927a71ce88c9377970fe7ad3c2",
- "sha256:9f5caf2c7436d44f3cec97c2fa7791f8a675170badbfa86e1992ca1b84c37009",
- "sha256:a0c8758d01fcdfe7ae8e4b4017b13552efa7f1197dd7358dc9da0576f9d0328a",
- "sha256:a4def978d9d28cda2d960c279318d46b327632686d82b4917516c36d4c274512",
- "sha256:ad4f4be843dace866af5fc142509e9b9817ca0c59342fdb176ab6ad552c927f5",
- "sha256:ae33dd198f772f714420c5ab698ff05ff900150486c648d29951e9c70694338e",
- "sha256:b4a2b782b8a8c5522ad35c93e04d60e2ba7f7dcb9271ec8e8c3e08239be6c7b4",
- "sha256:c462eb33f6abca3b34cdedbe84d761f31a60b814e173b98ede3c81bb48967c4f",
- "sha256:fd135b8d35dfdcdb984828c84d695937e58cc5f49e1c854eb311c4d6aa03f4f1"
- ],
- "version": "==1.4.2"
+ "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
+ "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
+ "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
+ "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
+ "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
+ "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
+ "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
+ "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
+ "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
+ "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
+ "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
+ "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
+ "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
+ "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
+ "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
+ "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
+ "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
+ "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
+ "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
+ "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
+ "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
+ ],
+ "version": "==1.4.3"
},
"mccabe": {
"hashes": [
@@ -682,18 +650,18 @@
},
"pylint": {
"hashes": [
- "sha256:7b76045426c650d2b0f02fc47c14d7934d17898779da95288a74c2a7ec440702",
- "sha256:856476331f3e26598017290fd65bebe81c960e806776f324093a46b76fb2d1c0"
+ "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd",
+ "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"
],
"index": "pypi",
- "version": "==2.4.3"
+ "version": "==2.4.4"
},
"six": {
"hashes": [
- "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
- "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+ "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd",
+ "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"
],
- "version": "==1.12.0"
+ "version": "==1.13.0"
},
"typed-ast": {
"hashes": [
diff --git a/README.md b/README.md
index 31bbc7ac..ad3e542d 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,13 @@ docker kill $(docker ps -q); docker-compose rm -f; docker volume rm $(docker vol
docker-compose build
```
+### Adding a new django vendor app
+1. Update the pipfile
+2. run pipenv lock
+3. run docker-compose build
+
+
+
[docker-mac]: https://www.docker.com/docker-mac
[docker-windows]: https://www.docker.com/docker-windows
[docker-toolbox]: https://www.docker.com/products/docker-toolbox
diff --git a/accounts/templates/account/_base.html b/accounts/templates/account/_base.html
index 09a952bf..0653c30f 100644
--- a/accounts/templates/account/_base.html
+++ b/accounts/templates/account/_base.html
@@ -1,20 +1,26 @@
{% extends "coderdojochi/_base.html" %}
+{% load coderdojochi_extras %}
{% comment %} Need to do title this way since allauth doesn't support django-meta {% endcomment %}
{% block title %}{{ title_placeholder }} | {{ block.super }}{% endblock %}
{% block subheader %}
{% if user.is_authenticated %}
-
+
{% endif %}
{% endblock subheader %}
{% block contained_content %}
- {% block content_placeholder %}{% endblock %}
+{% block content_placeholder %}{% endblock %}
{% endblock %}
diff --git a/accounts/templates/account/account-payments.html b/accounts/templates/account/account-payments.html
new file mode 100644
index 00000000..00bc1f17
--- /dev/null
+++ b/accounts/templates/account/account-payments.html
@@ -0,0 +1,36 @@
+{% extends "account/_base.html" %}
+{% load humanize coderdojochi_extras %}
+
+{% block title %}Donations | {{ block.super }}{% endblock %}
+{% block meta_facebook_title %}My Account | {{ block.super }}{% endblock %}
+{% block meta_twitter_title %}My Account | {{ block.super }}{% endblock %}
+
+{% block body_class %}page-dojo{% endblock %}
+
+
+{% block contained_content %}
+
+
+ Donations
+ {% if object_list %}
+
+
+
+ Date
+ Amount
+
+
+
+ {% for payment in object_list %}
+
+ {{ payment.created_at }}
+ ${{ payment.get_formatted_amount | floatformat:2 | intcomma }}
+
+ {% endfor %}
+
+
+ {% else %}
+ You have no donations yet. Donate now!
+ {% endif %}
+
+{% endblock %}
diff --git a/accounts/templates/account/home_guardian.html b/accounts/templates/account/home_guardian.html
index 49c2b7a0..f9906c4f 100644
--- a/accounts/templates/account/home_guardian.html
+++ b/accounts/templates/account/home_guardian.html
@@ -62,7 +62,7 @@ Students
{% endfor %}
- Add another student
+ Add another student
{% else %}
You have no students registered yet, register a student now .
{% endif %}
diff --git a/accounts/urls.py b/accounts/urls.py
index 52226cb7..46b31e70 100644
--- a/accounts/urls.py
+++ b/accounts/urls.py
@@ -1,10 +1,11 @@
from django.conf.urls import include
from django.urls import path
-from .views import AccountHomeView, LoginView, SignupView
+from .views import AccountHomeView, LoginView, PaymentsView, SignupView
urlpatterns = [
- path('', AccountHomeView.as_view(), name='account_home'),
+ path('', AccountHomeView.as_view(), name='account-home'),
+ path('payments/', PaymentsView.as_view(), name='account-payments',),
path('login/', LoginView.as_view(), name='account_login'),
path('signup/', SignupView.as_view(), name='account_signup'),
path('', include('allauth.urls')),
diff --git a/accounts/views.py b/accounts/views.py
index 217a8c0a..ff243095 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -1,10 +1,15 @@
from django.conf import settings
from django.contrib import messages
+from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
+from django.db.models import Q
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
+from django.views.generic.list import ListView
+
+from payments.models import Donation
from allauth.account.views import LoginView as AllAuthLoginView
from allauth.account.views import SignupView as AllAuthSignupView
@@ -13,6 +18,8 @@
from coderdojochi.forms import CDCModelForm, GuardianForm, MentorForm
from coderdojochi.models import Guardian, MeetingOrder, Mentor, MentorOrder, Order, Student
+User = get_user_model()
+
class SignupView(MetadataMixin, AllAuthSignupView):
template_name = 'account/signup.html'
@@ -214,7 +221,7 @@ def post_for_mentor(self, **kwargs):
'Profile information saved.'
)
- return redirect('account_home')
+ return redirect('account-home')
else:
messages.error(
@@ -250,7 +257,7 @@ def post_for_guardian(self, **kwargs):
'Profile information saved.'
)
- return redirect('account_home')
+ return redirect('account-home')
else:
messages.error(
@@ -262,3 +269,14 @@ def post_for_guardian(self, **kwargs):
context['user_form'] = user_form
return render(self.request, 'account/home_guardian.html', context)
+
+
+@method_decorator(login_required, name='dispatch')
+class PaymentsView(ListView):
+ model = Donation
+ template_name = "account/account-payments.html"
+
+ def get_queryset(self):
+ return Donation.objects.filter(
+ Q(customer=self.request.user) | Q(email=self.request.user.email)
+ ).order_by('-created_at')
diff --git a/app.json b/app.json
index 6a4952c4..79b1dfd4 100644
--- a/app.json
+++ b/app.json
@@ -22,10 +22,13 @@
"ENABLE_DEV_FIXTURES": {
"required": true
},
- "PAYPAL_BUSINESS_ID": {
+ "STRIPE_LIVE_PUBLIC_KEY": {
"required": true
},
- "PAYPAL_RECEIVER_EMAIL": {
+ "STRIPE_LIVE_SECRET_KEY": {
+ "required": true
+ },
+ "STRIPE_LIVE_MODE": {
"required": true
},
"SECRET_KEY": {
diff --git a/coderdojochi/models.py b/coderdojochi/models.py
index c70490a8..60a9eec3 100644
--- a/coderdojochi/models.py
+++ b/coderdojochi/models.py
@@ -37,7 +37,7 @@ def save(self, *args, **kwargs):
super(CDCUser, self).save(*args, **kwargs)
def get_absolute_url(self):
- return reverse('account_home')
+ return reverse('account-home')
def generate_filename(instance, filename):
diff --git a/coderdojochi/old_views.py b/coderdojochi/old_views.py
index 5288554a..ab720810 100644
--- a/coderdojochi/old_views.py
+++ b/coderdojochi/old_views.py
@@ -5,6 +5,16 @@
from datetime import date, timedelta
from functools import reduce
+import arrow
+import stripe
+from coderdojochi.forms import (CDCModelForm, ContactForm, DonationForm,
+ GuardianForm, MentorForm, StudentForm)
+from coderdojochi.models import (Donation, Equipment, EquipmentType, Guardian,
+ Meeting, MeetingOrder, Mentor, MentorOrder,
+ Order, PartnerPasswordAccess, Session,
+ Student)
+from coderdojochi.util import email
+from dateutil.relativedelta import relativedelta
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
@@ -20,29 +30,9 @@
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView
-
-import arrow
-from dateutil.relativedelta import relativedelta
from icalendar import Calendar, Event, vText
from paypal.standard.forms import PayPalPaymentsForm
-from coderdojochi.forms import CDCModelForm, ContactForm, DonationForm, GuardianForm, MentorForm, StudentForm
-from coderdojochi.models import (
- Donation,
- Equipment,
- EquipmentType,
- Guardian,
- Meeting,
- MeetingOrder,
- Mentor,
- MentorOrder,
- Order,
- PartnerPasswordAccess,
- Session,
- Student,
-)
-from coderdojochi.util import email
-
logger = logging.getLogger(__name__)
# this will assign User to our custom CDCUser
@@ -191,7 +181,7 @@ def faqs(request, template_name="faqs.html"):
# 'Profile information saved.'
# )
-# return redirect('account_home')
+# return redirect('account-home')
# else:
# messages.error(
@@ -270,7 +260,7 @@ def faqs(request, template_name="faqs.html"):
# 'Profile information saved.'
# )
-# return redirect('account_home')
+# return redirect('account-home')
# else:
# messages.error(
@@ -420,12 +410,12 @@ def student_detail(
try:
student = Student.objects.get(id=student_id, is_active=True)
except ObjectDoesNotExist:
- return redirect('account_home')
+ return redirect('account-home')
try:
guardian = Guardian.objects.get(user=request.user, is_active=True)
except ObjectDoesNotExist:
- return redirect('account_home')
+ return redirect('account-home')
if not student.guardian == guardian:
access = False
@@ -435,7 +425,7 @@ def student_detail(
access = False
if not access:
- return redirect('account_home')
+ return redirect('account-home')
messages.error(
request,
'You do not have permissions to edit this student.'
@@ -449,13 +439,13 @@ def student_detail(
request,
f"Student \"{student.first_name} {student.last_name}\" Deleted."
)
- return redirect('account_home')
+ return redirect('account-home')
form = StudentForm(request.POST, instance=student)
if form.is_valid():
form.save()
messages.success(request, 'Student Updated.')
- return redirect('account_home')
+ return redirect('account-home')
return render(
request,
@@ -467,67 +457,54 @@ def student_detail(
def donate(request, template_name="donate.html"):
- if request.method == 'POST':
- # if new donation form submit
- if (
- 'first_name' in request.POST and
- 'last_name' in request.POST and
- 'email' in request.POST and
- 'amount' in request.POST
- ):
- donation = Donation(
- first_name=request.POST['first_name'],
- last_name=request.POST['last_name'],
- email=request.POST['email'],
- amount=request.POST['amount'],
- )
+ return render(
+ request,
+ template_name
+ )
- if 'referral_code' in request.POST and request.POST['referral_code']:
- donation.referral_code = request.POST['referral_code']
- donation.save()
+@csrf_exempt
+def donate_charge(request):
+
+ donor_name = request.POST['donor-name']
+ donor_email = request.POST['donor-email']
+ donor_phone = request.POST['donor-phone']
+ donor_amount = request.POST['donor-amount']
+ donor_statement = f"Donation to We All Code from {donor_name}"
+ stripe_token = request.POST['stripeToken']
+ stripe.api_key = settings.STRIPE_SECRET_KEY
+
+ try:
+ charge = stripe.Charge.create(
+ amount=int(float(donor_amount) * 100),
+ currency='usd',
+ card=stripe_token,
+ description=donor_statement,
+ metadata={
+ 'name': donor_name,
+ 'email': donor_email,
+ 'receipt_email': donor_email,
+ 'statement_descriptor': donor_statement,
+ }
+ )
+ messages.success(request, f"Thank you for your donation of ${donor_amount} !")
- return HttpResponse(donation.id)
+ except stripe.error.StripeError as e:
+ messages.error(
+ request,
+ f"There was an error processing your payment: {e}. "
+ f" Please try again, or contact us if you're having trouble."
+ )
- else:
- return HttpResponse('fail')
-
- referral_heading = None
- referral_code = None
- referral_disclaimer = None
-
- if 'ref' in request.GET:
- referral_code = request.GET['ref']
-
- paypal_dict = {
- 'business': settings.PAYPAL_BUSINESS_ID,
- 'amount': '25',
- 'item_name': 'We All Code Donation',
- 'cmd': '_donations',
- 'lc': 'US',
- 'invoice': '',
- 'currency_code': 'USD',
- 'no_note': '0',
- 'cn': 'Add a message for We All Code to read:',
- 'no_shipping': '1',
- 'address_override': '1',
- 'first_name': '',
- 'last_name': '',
- 'notify_url': request.build_absolute_uri(reverse('paypal-ipn')),
- 'return_url': request.build_absolute_uri('return'),
- 'cancel_return': request.build_absolute_uri('cancel'),
- 'bn': 'PP-DonationsBF:btn_donateCC_LG.gif:NonHosted'
- }
-
- form = PayPalPaymentsForm(initial=paypal_dict)
+ else:
+ # TODO: Save to model? Send Receipt? Something...
+ messages.success(
+ request,
+ "Else command worked"
+ )
- return render(request, template_name, {
- 'form': form,
- 'referral_heading': referral_heading,
- 'referral_code': referral_code,
- 'referral_disclaimer': referral_disclaimer
- })
+ return redirect('donate')
@csrf_exempt
@@ -567,7 +544,8 @@ def contact(request, template_name="contact.html"):
email(
subject=f"{request.POST['name']} | We All Code Contact Form",
recipients=[settings.CONTACT_EMAIL],
- reply_to=[f"{request.POST['name']}<{request.POST['email']}>"],
+ reply_to=[
+ f"{request.POST['name']}<{request.POST['email']}>"],
template_name='contact-email',
merge_global_data={
'message': request.POST['message'],
@@ -788,7 +766,8 @@ def session_stats(request, pk, template_name="session-stats.html"):
if current_orders_checked_in:
student_ages = []
for order in current_orders_checked_in:
- student_ages.append(order.student.get_age(order.session.start_date))
+ student_ages.append(
+ order.student.get_age(order.session.start_date))
average_age = (
reduce(
@@ -1056,7 +1035,7 @@ def session_donations(request, pk, template_name="session-donations.html"):
request,
'You do not have permission to access this page.'
)
- return redirect('account_home')
+ return redirect('account-home')
session = get_object_or_404(Session, pk=pk)
@@ -1107,7 +1086,7 @@ def meeting_check_in(
request,
'You do not have permission to access this page.'
)
- return redirect('account_home')
+ return redirect('account-home')
if request.method == 'POST':
if 'order_id' in request.POST:
diff --git a/coderdojochi/settings.py b/coderdojochi/settings.py
index 7571c76b..a87f2331 100644
--- a/coderdojochi/settings.py
+++ b/coderdojochi/settings.py
@@ -15,6 +15,7 @@
import dj_database_url
import django_heroku
import raven
+from django.urls import reverse_lazy
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -97,6 +98,7 @@
'stdimage',
'import_export',
'django_nose',
+ 'stripe',
'meta',
'captcha',
@@ -104,6 +106,7 @@
'accounts',
'coderdojochi',
'weallcode',
+ 'payments',
]
MIDDLEWARE = [
@@ -306,6 +309,10 @@ def MediaRootS3BotoStorage(): return S3Boto3Storage(location='media', file_overw
AUTH_USER_MODEL = 'coderdojochi.CDCUser'
+# Stripe
+STRIPE_PUBLIC_KEY = os.environ.get('STRIPE_PUBLIC_KEY', '')
+STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY', '')
+STRIPE_LIVE_MODE = os.environ.get('STRIPE_LIVE_MODE')
# Django Meta
META_SITE_PROTOCOL = os.environ.get('META_SITE_PROTOCOL', 'https')
META_SITE_DOMAIN = os.environ.get('META_SITE_DOMAIN', 'www.weallcode.org')
diff --git a/coderdojochi/signals_handlers.py b/coderdojochi/signals_handlers.py
index feab2e55..06371a36 100644
--- a/coderdojochi/signals_handlers.py
+++ b/coderdojochi/signals_handlers.py
@@ -10,7 +10,7 @@
from paypal.standard.ipn.signals import valid_ipn_received
from paypal.standard.models import ST_PP_COMPLETED
-from coderdojochi.models import Donation, Mentor
+from coderdojochi.models import Mentor
from coderdojochi.util import email
diff --git a/coderdojochi/templates/_topnav.html b/coderdojochi/templates/_topnav.html
index d98c6b53..228d100b 100644
--- a/coderdojochi/templates/_topnav.html
+++ b/coderdojochi/templates/_topnav.html
@@ -18,7 +18,7 @@
{% if user.is_authenticated %}
-
+
Logout
{% else %}
diff --git a/coderdojochi/templates/donate.html b/coderdojochi/templates/donate.html
deleted file mode 100644
index c8b2adc4..00000000
--- a/coderdojochi/templates/donate.html
+++ /dev/null
@@ -1,206 +0,0 @@
-{% extends "coderdojochi/_base.html" %}
-
-{% load i18n %}
-
-{% block title %}Donate | {{ block.super }}{% endblock %}
-
-{% block body_class %}page-donate{% endblock %}
-
-{% block contained_content %}
-
-
-
-
- {% if referral_heading %}{{ referral_heading }}{% else %}Donate Today!{% endif %}
-
-
-
- $
-
-
-
-
Please tell us a little about yourself
-
-
-
-
-
-
-
-
-
-
-
-
-
We will email you a receipt (includes tax exempt information)
-
-
Do it.
-
-
-
-
-
-
You can help!
-
We All Code is a registered non-profit organization supported entirely by...you! Please help us continue our mission by donating a small amount to our cause. Thank you!
-
- {% if referral_disclaimer %}
-
{{ referral_disclaimer }}
- {% endif %}
-
- {{ form.render }}
-
-
-{% endblock %}
-
-{% block extra_script %}
-
-{% endblock %}
diff --git a/coderdojochi/templates/student-detail.html b/coderdojochi/templates/student-detail.html
index bdfb4fc6..3463e623 100644
--- a/coderdojochi/templates/student-detail.html
+++ b/coderdojochi/templates/student-detail.html
@@ -13,7 +13,7 @@ Edit Student Info
{% csrf_token %}
{% bootstrap_form form %}
Update
- Cancel
+ Cancel
{% endblock %}
-
diff --git a/coderdojochi/templatetags/coderdojochi_extras.py b/coderdojochi/templatetags/coderdojochi_extras.py
index 7d3d17f6..0d772e0b 100644
--- a/coderdojochi/templatetags/coderdojochi_extras.py
+++ b/coderdojochi/templatetags/coderdojochi_extras.py
@@ -32,7 +32,8 @@ def student_register_link(context, student, session):
is_active=True
)
- url = reverse('session-sign-up', kwargs={'pk': session.id, 'student_id': student.id, })
+ url = reverse('session-sign-up',
+ kwargs={'pk': session.id, 'student_id': student.id, })
button_tag = 'a'
button_modifier = ''
diff --git a/coderdojochi/urls.py b/coderdojochi/urls.py
index c2859b20..424bcd30 100644
--- a/coderdojochi/urls.py
+++ b/coderdojochi/urls.py
@@ -7,33 +7,19 @@
from django.urls import path
from django.views import defaults
from django.views.generic import RedirectView
-
from loginas import views as loginas_views
from . import old_views
from .views.general import AboutView, HomeView, PrivacyView, WelcomeView
-from .views.meetings import (
- MeetingCalendarRedirectView,
- MeetingCalendarView,
- MeetingDetailRedirectView,
- MeetingDetailView,
- MeetingsView,
- meeting_announce,
- meeting_sign_up,
-)
+from .views.meetings import (MeetingCalendarRedirectView, MeetingCalendarView,
+ MeetingDetailRedirectView, MeetingDetailView,
+ MeetingsView, meeting_announce, meeting_sign_up)
from .views.profile import DojoMentorView
-from .views.sessions import (
- PasswordSessionRedirectView,
- PasswordSessionView,
- SessionCalendarRedirectView,
- SessionCalendarView,
- SessionDetailRedirectView,
- SessionDetailView,
- SessionSignUpRedirectView,
- SessionSignUpView,
- SessionsRedirectView,
- SessionsView,
-)
+from .views.sessions import (PasswordSessionRedirectView, PasswordSessionView,
+ SessionCalendarRedirectView, SessionCalendarView,
+ SessionDetailRedirectView, SessionDetailView,
+ SessionSignUpRedirectView, SessionSignUpView,
+ SessionsRedirectView, SessionsView)
from .views.volunteer import VolunteerView
admin.autodiscover()
@@ -51,6 +37,9 @@
path('account/', include('accounts.urls')),
]
+# Payments
+urlpatterns += [path('donate/', include('payments.urls'))]
+
# Old General
urlpatterns += [
@@ -116,24 +105,6 @@
]
-# Donate / Donations
-urlpatterns += [
- path('donate/', include([
- # Donation
- # /donate/
- path('', old_views.donate, name='donate'),
-
- # /donate/cancel/
- path('cancel/', old_views.donate_cancel, name='donate-cancel'),
-
- # /donate/return/
- path('return/', old_views.donate_return, name='donate-return'),
-
- # /donate/paypal/
- path('paypal/', include('paypal.standard.ipn.urls')),
- ])),
-]
-
# Login As
urlpatterns += [
path('dj-admin/', include('loginas.urls')),
@@ -157,18 +128,22 @@
path('/stats/', old_views.session_stats, name='stats'),
# /admin/classes/ID/check-in/
- path('/check-in/', old_views.session_check_in, name='student-check-in'),
+ path('/check-in/', old_views.session_check_in,
+ name='student-check-in'),
# /admin/classes/ID/check-in-mentors/
- path('/check-in-mentors/', old_views.session_check_in_mentors, name='mentor-check-in'),
+ path('/check-in-mentors/',
+ old_views.session_check_in_mentors, name='mentor-check-in'),
# /admin/classes/ID/donations/
- path('/donations/', old_views.session_donations, name='donations'),
+ path('/donations/',
+ old_views.session_donations, name='donations'),
])),
path('meetings/', include([
# /admin/meeting/ID/check-in/
- path('/check-in/', old_views.meeting_check_in, name='meeting-check-in'),
+ path('/check-in/',
+ old_views.meeting_check_in, name='meeting-check-in'),
])),
# Admin Check System
@@ -191,25 +166,31 @@
# Password
# /classes/ID/password/
- path('/password/', PasswordSessionView.as_view(), name='session-password'),
+ path('/password/', PasswordSessionView.as_view(),
+ name='session-password'),
# Announce
# /classes/ID/announce/mentors/
- path('/announce/mentors/', old_views.session_announce_mentors, name='session-announce-mentors'),
+ path('/announce/mentors/', old_views.session_announce_mentors,
+ name='session-announce-mentors'),
# /classes/ID/announce/guardians/
- path('/announce/guardians/', old_views.session_announce_guardians, name='session-announce-guardians'),
+ path('/announce/guardians/', old_views.session_announce_guardians,
+ name='session-announce-guardians'),
# Calendar
# /classes/ID/calendar/
- path('/calendar/', SessionCalendarView.as_view(), name='session-calendar'),
+ path('/calendar/', SessionCalendarView.as_view(),
+ name='session-calendar'),
# Sign up
# /classes/ID/sign-up/
- path('/sign-up/', SessionSignUpView.as_view(), name='session-sign-up'),
+ path('/sign-up/', SessionSignUpView.as_view(),
+ name='session-sign-up'),
# /classes/ID/sign-up/STUDENT-ID/
- path('/sign-up//', SessionSignUpView.as_view(), name='session-sign-up'),
+ path('/sign-up//',
+ SessionSignUpView.as_view(), name='session-sign-up'),
])),
# FIXME: Old redirects. Remove by July 2018
@@ -221,16 +202,20 @@
path('/', SessionDetailRedirectView.as_view()),
# Redirect /class/YYYY/MM/DD/SLUG/ID/ -> /classes/ID/
- path('/////', SessionDetailRedirectView.as_view()),
+ path('/////',
+ SessionDetailRedirectView.as_view()),
# Redirect /class/YYYY/MM/DD/SLUG/ID/password/ -> /classes/ID/password/
- path('/////password/', PasswordSessionRedirectView.as_view()),
+ path('/////password/',
+ PasswordSessionRedirectView.as_view()),
# Redirect /class/YYYY/MM/DD/SLUG/ID/calendar/ -> /classes/ID/calendar/
- path('/////calendar/', SessionCalendarRedirectView.as_view()),
+ path('/////calendar/',
+ SessionCalendarRedirectView.as_view()),
# Redirect /class/YYYY/MM/DD/SLUG/ID/sign-up/ -> /classes/ID/sign-up/
- path('/////sign-up/', SessionSignUpRedirectView.as_view()),
+ path('/////sign-up/',
+ SessionSignUpRedirectView.as_view()),
# Redirect /class/YYYY/MM/DD/SLUG/ID/sign-up/STUDENT-ID/ -> /classes/ID/sign-up/STUDENT-ID/
path(
@@ -260,10 +245,12 @@
path('/', old_views.mentor_detail, name='mentor-detail'),
# /ID/reject-avatar/
- path('/reject-avatar/', old_views.mentor_reject_avatar, name='mentor-reject-avatar'),
+ path('/reject-avatar/', old_views.mentor_reject_avatar,
+ name='mentor-reject-avatar'),
# /ID/approve-avatar/
- path('/approve-avatar/', old_views.mentor_approve_avatar, name='mentor-approve-avatar'),
+ path('/approve-avatar/', old_views.mentor_approve_avatar,
+ name='mentor-approve-avatar'),
])),
# FIXME: Old redirects. Remove by July 2018
@@ -271,8 +258,10 @@
path('mentor/', include([
path('', RedirectView.as_view(pattern_name='mentors')),
path('/', RedirectView.as_view(pattern_name='mentor-detail')),
- path('/reject-avatar/', RedirectView.as_view(pattern_name='mentor-reject-avatar')),
- path('/approve-avatar/', RedirectView.as_view(pattern_name='mentor-approve-avatar')),
+ path('/reject-avatar/',
+ RedirectView.as_view(pattern_name='mentor-reject-avatar')),
+ path('/approve-avatar/',
+ RedirectView.as_view(pattern_name='mentor-approve-avatar')),
])),
]
@@ -280,7 +269,8 @@
urlpatterns += [
# Student
# /student/ID/
- path('students//', old_views.student_detail, name='student-detail'),
+ path('students//',
+ old_views.student_detail, name='student-detail'),
]
# Dojo
@@ -301,7 +291,8 @@
# robots.txt
urlpatterns += [
- path('robots.txt', lambda r: HttpResponse('User-agent: *\nDisallow:', content_type='text/plain'))
+ path('robots.txt', lambda r: HttpResponse(
+ 'User-agent: *\nDisallow:', content_type='text/plain'))
]
# Anymail
@@ -343,7 +334,8 @@
if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
- urlpatterns = [path('__debug__/', include(debug_toolbar.urls))] + urlpatterns
+ urlpatterns = [
+ path('__debug__/', include(debug_toolbar.urls))] + urlpatterns
else:
from django.contrib.staticfiles.storage import staticfiles_storage
@@ -370,7 +362,8 @@
urlpatterns += [
path(
favicon,
- RedirectView.as_view(url=staticfiles_storage.url(favicon), permanent=False),
+ RedirectView.as_view(
+ url=staticfiles_storage.url(favicon), permanent=False),
name=favicon
),
]
diff --git a/coderdojochi/views/general.py b/coderdojochi/views/general.py
index 80779150..f252f5ec 100644
--- a/coderdojochi/views/general.py
+++ b/coderdojochi/views/general.py
@@ -107,7 +107,7 @@ def dispatch(self, request, *args, **kwargs):
):
mentor = get_object_or_404(Mentor, user=request.user)
if mentor.user.first_name:
- return redirect(next_url if next_url else 'account_home')
+ return redirect(next_url if next_url else 'account-home')
kwargs['mentor'] = mentor
return super(WelcomeView, self).dispatch(request, *args, **kwargs)
@@ -172,7 +172,7 @@ def update_account(self, request, account, next_url):
if 'enroll' in request.GET:
next_url = f"{next_url}?enroll=True"
else:
- next_url = 'account_home' if isinstance(account, Mentor) else 'welcome'
+ next_url = 'account-home' if isinstance(account, Mentor) else 'welcome'
return redirect(next_url)
return render(request, self.template_name, {
@@ -240,7 +240,7 @@ def create_new_user(self, request, user, next_url):
f"{settings.SITE_URL}{next_meeting.get_calendar_url()}"
)
if not next_url:
- next_url = reverse('account_home')
+ next_url = reverse('account-home')
else:
# check for next upcoming class
next_class = Session.objects.filter(
diff --git a/coderdojochi/views/profile.py b/coderdojochi/views/profile.py
index ce296d79..e2dfd7e5 100644
--- a/coderdojochi/views/profile.py
+++ b/coderdojochi/views/profile.py
@@ -124,7 +124,7 @@ def post(self, request, *args, **kwargs):
'Profile information saved.'
)
- return redirect('account_home')
+ return redirect('account-home')
else:
messages.error(
diff --git a/coderdojochi/views/sessions.py b/coderdojochi/views/sessions.py
index 97642657..52edd07b 100644
--- a/coderdojochi/views/sessions.py
+++ b/coderdojochi/views/sessions.py
@@ -329,7 +329,7 @@ def dispatch(self, request, *args, **kwargs):
access_dict = self.check_access(request, *args, **kwargs)
if access_dict.get('message'):
- if access_dict.get('redirect') == 'account_home':
+ if access_dict.get('redirect') == 'account-home':
messages.warning(
request,
access_dict['message']
diff --git a/coderdojochi/wsgi.py b/coderdojochi/wsgi.py
index a0e5db34..3634c96b 100644
--- a/coderdojochi/wsgi.py
+++ b/coderdojochi/wsgi.py
@@ -10,12 +10,8 @@
import os
from django.core.wsgi import get_wsgi_application
-
-import sqreen
from raven.contrib.django.raven_compat.middleware.wsgi import Sentry
-sqreen.start()
-
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "coderdojochi.settings")
application = Sentry(get_wsgi_application())
diff --git a/fixtures/06-weallcode-payments.json b/fixtures/06-weallcode-payments.json
new file mode 100644
index 00000000..5dac0fa6
--- /dev/null
+++ b/fixtures/06-weallcode-payments.json
@@ -0,0 +1,84 @@
+[{
+ "model": "payments.donation",
+ "pk": 1,
+ "fields": {
+ "customer": 2,
+ "name": "John Doe",
+ "email": "guardian@sink.sendgrid.net",
+ "stripe_customer_id": "cus_F7B6UUdHaC3SY2",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye325",
+ "amount": 1000,
+ "created_at": "2019-05-01T13:07:25.346-06:00",
+ "updated_at": "2019-05-01T13:07:25.346-06:00"
+ }
+ },
+ {
+ "model": "payments.donation",
+ "pk": 2,
+ "fields": {
+ "customer": 2,
+ "name": "John Doe",
+ "email": "guardian@sink.sendgrid.net",
+ "stripe_customer_id": "cus_F7B6UUdHaC3SY2",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye323",
+ "amount": 100,
+ "created_at": "2019-05-02T13:07:25.346-06:00",
+ "updated_at": "2019-05-02T13:07:25.346-06:00"
+ }
+ },
+ {
+ "model": "payments.donation",
+ "pk": 3,
+ "fields": {
+ "customer": 2,
+ "name": "John Doe",
+ "email": "guardian@sink.sendgrid.net",
+ "stripe_customer_id": "cus_F7B6UUdHaC3SY2",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye444",
+ "amount": 5000,
+ "created_at": "2019-05-03T13:07:25.346-06:00",
+ "updated_at": "2019-05-03T13:07:25.346-06:00"
+ }
+ },
+ {
+ "model": "payments.donation",
+ "pk": 4,
+ "fields": {
+ "customer": 2,
+ "name": "John Doe",
+ "email": "guardian@sink.sendgrid.net",
+ "stripe_customer_id": "cus_F7B6UUdHaC3SY2",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye333",
+ "amount": 12000,
+ "created_at": "2019-05-04T13:07:25.346-06:00",
+ "updated_at": "2019-05-04T13:07:25.346-06:00"
+ }
+ },
+ {
+ "model": "payments.donation",
+ "pk": 5,
+ "fields": {
+ "customer": 2,
+ "name": "John Doe",
+ "email": "guardian@sink.sendgrid.net",
+ "stripe_customer_id": "cus_F7B6UUdHaC3SY2",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye123",
+ "amount": 350000,
+ "created_at": "2019-05-05T13:07:25.346-06:00",
+ "updated_at": "2019-05-05T13:07:25.346-06:00"
+ }
+ },
+ {
+ "model": "payments.donation",
+ "pk": 6,
+ "fields": {
+ "name": "John Doe",
+ "email": "test@test.com",
+ "stripe_customer_id": "cus_F7B6UUdHaC3S32",
+ "stripe_payment_id": "ch_1Ecy3sDC33cb7CGNz15Ye321",
+ "amount": 3500,
+ "created_at": "2019-05-05T13:07:25.346-06:00",
+ "updated_at": "2019-05-05T13:07:25.346-06:00"
+ }
+ }
+]
diff --git a/payments/__init__.py b/payments/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/payments/admin.py b/payments/admin.py
new file mode 100644
index 00000000..6cbdc475
--- /dev/null
+++ b/payments/admin.py
@@ -0,0 +1,75 @@
+from django.contrib import admin
+from import_export.admin import ImportExportActionModelAdmin, ImportExportMixin
+
+from .models import Donation, Payment
+
+
+@admin.register(Donation)
+class DonationAdmin(ImportExportMixin, ImportExportActionModelAdmin):
+
+ list_per_page = 10
+
+ list_display = [
+ 'stripe_payment_id',
+ 'email',
+ 'customer',
+ 'formatted_amount',
+ ]
+
+ ordering = [
+ 'created_at',
+ ]
+
+ search_fields = [
+ 'email'
+ ]
+
+ readonly_fields = (
+ 'name',
+ 'email',
+ 'customer',
+ 'stripe_customer_id',
+ 'stripe_payment_id',
+ 'amount',
+ 'created_at',
+ )
+
+ view_on_site = False
+
+ def formatted_amount(self, obj):
+ return "$ {:.2f}".format(obj.amount / 100)
+
+
+@admin.register(Payment)
+class PaymentAdmin(ImportExportMixin, ImportExportActionModelAdmin):
+
+ list_per_page = 10
+
+ list_display = [
+ 'stripe_payment_id',
+ 'customer',
+ 'session',
+ 'formatted_amount',
+ ]
+
+ ordering = [
+ 'created_at',
+ ]
+
+ search_fields = [
+ 'customer'
+ ]
+
+ # readonly_fields = (
+ # 'customer',
+ # 'session',
+ # 'stripe_customer_id',
+ # 'stripe_payment_id',
+ # 'amount',
+ # 'created_at',
+ # )
+
+ view_on_site = False
+
+ def formatted_amount(self, obj):
+ return "$ {:.2f}".format(obj.amount / 100)
diff --git a/payments/apps.py b/payments/apps.py
new file mode 100644
index 00000000..58d36ac3
--- /dev/null
+++ b/payments/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class PaymentsConfig(AppConfig):
+ name = 'payments'
diff --git a/payments/forms.py b/payments/forms.py
new file mode 100644
index 00000000..426418ee
--- /dev/null
+++ b/payments/forms.py
@@ -0,0 +1,21 @@
+from django import forms
+
+
+class DonateForm(forms.Form):
+ name = forms.CharField()
+ email = forms.EmailField()
+ amount = forms.DecimalField(
+ widget=forms.TextInput(
+ attrs={
+ 'class': 'input-group-field',
+ 'inputmode': 'decimal',
+ 'pattern': '[0-9.]+'
+ }
+ )
+ )
+
+
+class PaymentForm(forms.Form):
+ name = forms.CharField()
+ email = forms.EmailField()
+ amount = forms.DecimalField()
diff --git a/payments/migrations/0001_initial.py b/payments/migrations/0001_initial.py
new file mode 100644
index 00000000..8bbda3dd
--- /dev/null
+++ b/payments/migrations/0001_initial.py
@@ -0,0 +1,31 @@
+# Generated by Django 2.2.1 on 2019-05-23 19:46
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Donation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(blank=True, max_length=100, null=True)),
+ ('email', models.EmailField(max_length=254)),
+ ('stripe_customer_id', models.CharField(max_length=350)),
+ ('stripe_payment_id', models.CharField(max_length=350)),
+ ('amount', models.PositiveIntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customer', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/payments/migrations/0002_payment.py b/payments/migrations/0002_payment.py
new file mode 100644
index 00000000..d683a109
--- /dev/null
+++ b/payments/migrations/0002_payment.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.2.1 on 2019-05-23 19:47
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('coderdojochi', '0024_session_cost'),
+ ('payments', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Payment',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('stripe_customer_id', models.CharField(max_length=350)),
+ ('stripe_payment_id', models.CharField(max_length=350)),
+ ('amount', models.PositiveIntegerField()),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='customer', to='coderdojochi.Student')),
+ ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='session', to='coderdojochi.Session')),
+ ],
+ ),
+ ]
diff --git a/payments/migrations/__init__.py b/payments/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/payments/models.py b/payments/models.py
new file mode 100644
index 00000000..cbd90701
--- /dev/null
+++ b/payments/models.py
@@ -0,0 +1,75 @@
+from coderdojochi.models import Session, Student
+from django.contrib.auth import get_user_model
+from django.db import models
+
+User = get_user_model()
+
+
+class Donation(models.Model):
+ customer = models.ForeignKey(
+ User,
+ on_delete=models.SET_NULL,
+ related_name="customer",
+ blank=True,
+ null=True,
+ )
+ name = models.CharField(
+ max_length=100,
+ blank=True,
+ null=True,
+ )
+ email = models.EmailField()
+ stripe_customer_id = models.CharField(
+ max_length=350,
+ )
+ stripe_payment_id = models.CharField(
+ max_length=350,
+ )
+ amount = models.PositiveIntegerField()
+ created_at = models.DateTimeField(
+ auto_now_add=True,
+ )
+ updated_at = models.DateTimeField(
+ auto_now=True,
+ )
+
+ def __str__(self):
+ return self.email
+
+ def get_absolute_url(self):
+ return reverse("Donation_detail", kwargs={"pk": self.pk})
+
+ def get_formatted_amount(self):
+ return "{:.2f}".format(self.amount / 100)
+
+
+class Payment(models.Model):
+ customer = models.ForeignKey(
+ Student,
+ on_delete=models.CASCADE,
+ related_name="customer",
+ )
+ session = models.ForeignKey(
+ Session,
+ on_delete=models.CASCADE,
+ related_name="session",
+ )
+ stripe_customer_id = models.CharField(
+ max_length=350,
+ )
+ stripe_payment_id = models.CharField(
+ max_length=350,
+ )
+ amount = models.PositiveIntegerField()
+ created_at = models.DateTimeField(
+ auto_now_add=True,
+ )
+ updated_at = models.DateTimeField(
+ auto_now=True,
+ )
+
+ def __str__(self):
+ return self.customer.first_name
+
+ def get_formatted_amount(self):
+ return "{:.2f}".format(self.amount / 100)
diff --git a/payments/templates/contact.html b/payments/templates/contact.html
new file mode 100644
index 00000000..a0e8e4b7
--- /dev/null
+++ b/payments/templates/contact.html
@@ -0,0 +1,4 @@
+
diff --git a/payments/templates/donate.html b/payments/templates/donate.html
new file mode 100644
index 00000000..ccd00486
--- /dev/null
+++ b/payments/templates/donate.html
@@ -0,0 +1,138 @@
+{% extends "coderdojochi/_base.html" %}
+
+{% load i18n %}
+
+{% block title %}Donate | {{ block.super }}{% endblock %}
+
+{% block body_class %}page-donate{% endblock %}
+
+{% block contained_content %}
+
+
+
+
{% trans "Donate" %}
+
+
+
You can help!
+
We All Code is a registered non-profit organization supported entirely by...you! Please help us continue our mission by donating a small amount to our cause. Thank you!
+
+
+
+
+
+
+{% endblock %}
+
+{% block extra_script %}
+
+
+
+{% endblock %}
diff --git a/payments/templates/donate_block.html b/payments/templates/donate_block.html
new file mode 100644
index 00000000..51988b5e
--- /dev/null
+++ b/payments/templates/donate_block.html
@@ -0,0 +1,122 @@
+
+
+
Donate
+
You can help!
+
We All Code is a registered non-profit organization supported entirely by...you! Please help us continue our mission by donating a small amount to our cause. Thank you!
+
+
+
+
+
+
+
diff --git a/payments/tests.py b/payments/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/payments/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/payments/urls.py b/payments/urls.py
new file mode 100644
index 00000000..6775431a
--- /dev/null
+++ b/payments/urls.py
@@ -0,0 +1,40 @@
+# from django.conf import settings
+from django.conf.urls import include, url
+# from django.conf.urls.static import static
+# from django.contrib import admin
+# from django.contrib.auth import views as django_views
+# from django.http import HttpResponse
+# from django.urls import path
+# from django.views import defaults
+# from django.views.generic import RedirectView
+# from loginas import views as loginas_views
+from django.urls import path
+
+from .views import DonateView
+
+urlpatterns = [
+
+]
+
+
+urlpatterns += [
+ path('', include([
+ # Donation
+ # /donate/
+ # path('', views.donate, name='donate'),
+
+ path('', DonateView.as_view(), name='donate'),
+
+ # /donate/charge
+ # path('charge/', views.donate_charge, name='donate-charge'),
+
+ # /donate/cancel/
+ # path('cancel/', payments.donate_cancel, name='donate-cancel'),
+
+ # /donate/return/
+ # path('return/', payments.donate_return, name='donate-return'),
+
+ # /donate/paypal/
+ # path('paypal/', include('paypal.standard.ipn.urls')),
+ ])),
+]
diff --git a/payments/views.py b/payments/views.py
new file mode 100644
index 00000000..e6fe4367
--- /dev/null
+++ b/payments/views.py
@@ -0,0 +1,190 @@
+from decimal import *
+
+import stripe
+from coderdojochi.models import CDCUser
+from django.conf import settings
+from django.contrib import messages
+from django.http import HttpResponseRedirect
+from django.urls import reverse_lazy
+from django.views.generic.edit import FormView
+
+from .forms import DonateForm
+from .models import Donation
+
+stripe.api_key = settings.STRIPE_SECRET_KEY
+
+
+class Amount:
+ # A class to format the amount.
+ # Params (amount)
+ def __init__(self, amount):
+ self.pennies = int(float(amount * 100))
+
+ def __str__(self):
+ # Format to dollars
+ return "{:.2f}".format(self.pennies / 100)
+
+
+class DonateView(FormView):
+ template_name = 'donate.html'
+ form_class = DonateForm
+ success_url = reverse_lazy('donate')
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['stripe_public_key'] = settings.STRIPE_PUBLIC_KEY
+ return context
+
+ # Populate the form is user is authenticated.
+ def get_initial(self):
+ initial = self.initial.copy()
+
+ if self.request.user.is_authenticated:
+ initial['email'] = self.request.user.email
+ initial['name'] = self.request.user.get_full_name()
+
+ return initial
+
+ # An already registered customer is making a donation
+ def donation_is_registered_customer(self, email, name, amount):
+ customer = None
+ # Get the user from Donation model with email
+ user = Donation.objects.filter(email=email).first()
+
+ # Check if user exists in our Database
+ user_email_exists = CDCUser.objects.filter(email=email).exists()
+
+ # If the user exists attach it to customer
+ if user_email_exists:
+ customer = CDCUser.objects.get(email=email)
+
+ try:
+ # Charge the user on Stripe.
+ charge = stripe.Charge.create(
+ amount=amount.pennies,
+ currency='usd',
+ customer=user.stripe_customer_id,
+ description='Donation to We All Code',
+ receipt_email=user.email,
+ statement_descriptor='Donation to WeAllCode'
+ )
+
+ # Save the donation to the model.
+ donation = Donation(
+ customer=customer,
+ email=user.email,
+ name=user.name,
+ amount=amount.pennies,
+ stripe_customer_id=user.stripe_customer_id,
+ stripe_payment_id=charge.id
+ )
+ donation.save()
+
+ # On Charge success alert the user.
+ messages.success(
+ self.request,
+ f"Thank you for your donation of ${ amount } !"
+ )
+
+ # On Charge error alert the user.
+ except stripe.error.StripeError as e:
+ messages.error(
+ self.request,
+ f"There was an error processing your payment: {e}. "
+ f" Please try again, or contact us if you're having trouble."
+ )
+
+ # A new customer is making a donation.
+ def donation_is_new_customer(self, token, email, name, amount):
+ customer = None
+ try:
+ # Register him as a Stripe Customer first.
+ stripe_customer = stripe.Customer.create(
+ card=token,
+ email=email,
+ name=name,
+ description="We All Code Customer"
+ )
+
+ # Check if user is authenticated
+ if self.request.user.is_authenticated:
+ customer = self.request.user
+
+ # Check if user exists in our Database
+ user_email_exists = CDCUser.objects.filter(email=email).exists()
+
+ if user_email_exists:
+ customer = CDCUser.objects.get(email=email)
+
+ # Charge the user.
+ charge = stripe.Charge.create(
+ amount=amount.pennies,
+ currency='usd',
+ customer=stripe_customer.id,
+ description='Donation to We All Code',
+ receipt_email=email,
+ statement_descriptor='Donation to WeAllCode'
+ )
+
+ # Save the Donation to the model
+ donation = Donation(
+ customer=customer,
+ email=stripe_customer.email,
+ amount=amount.pennies,
+ name=name,
+ stripe_customer_id=stripe_customer.id,
+ stripe_payment_id=charge.id
+ )
+ donation.save()
+
+ messages.success(
+ self.request,
+ f"Thank you for your donation of ${ amount } !"
+ )
+
+ except stripe.error.StripeError as e:
+ messages.error(
+ self.request,
+ f"There was an error processing your payment: {e}. "
+ f" Please try again, or contact us if you're having trouble."
+ )
+
+ def form_valid(self, form):
+
+ # Clean up the submited data.
+ data = form.cleaned_data
+
+ # Stripe token is retrieved from Stripe.JS
+ stripe_token = self.request.POST['stripeToken']
+
+ # User email retrieved from form
+ user_email = self.request.POST['email']
+
+ # Name retrieved from form
+ name = self.request.POST['name']
+
+ # Amount class atached to the amount var
+ amount = Amount(data['amount'])
+
+ # Check if the user exists in Donation model. Returns bool
+ is_registered_customer = Donation.objects.filter(
+ email=user_email
+ ).exists()
+
+ # If true call the function for a registered customer
+ if is_registered_customer:
+ self.donation_is_registered_customer(
+ user_email,
+ name,
+ amount,
+ )
+ else:
+ # Else call the function for a new customer.
+ self.donation_is_new_customer(
+ stripe_token,
+ user_email,
+ name,
+ amount,
+ )
+
+ return HttpResponseRedirect(self.get_success_url())
diff --git a/weallcode/static/weallcode/css/app.css b/weallcode/static/weallcode/css/app.css
index 437f4943..87c51fd2 100644
--- a/weallcode/static/weallcode/css/app.css
+++ b/weallcode/static/weallcode/css/app.css
@@ -22,51 +22,179 @@ body {
font-family: 'Open Sans', sans-serif;
}
-.text-primary { color: var(--we-blue); }
-.text-secondary { color: var(--all-yellow); }
-.text-tertiary { color: var(--code-red); }
-.text-dark-blue { color: var(--dark-blue); }
-.text-black { color: var(--black); }
-.text-white { color: var(--white); }
-.text-light-gray { color: var(--light-gray); }
-.text-grey-for-white { color: var(--gray-4-white); }
-.text-grey-for-blue { color: var(--gray-4-blue); }
-
-.fill-primary { fill: var(--we-blue); }
-.fill-secondary { fill: var(--all-yellow); }
-.fill-tertiary { fill: var(--code-red); }
-.fill-dark-blue { fill: var(--dark-blue); }
-.fill-black { fill: var(--black); }
-.fill-white { fill: var(--white); }
-.fill-light-gray { fill: var(--light-gray); }
-.fill-gray-for-white { fill: var(--gray-4-white); }
-.fill-gray-for-blue { fill: var(--gray-4-blue); }
-
-.bg-primary { background-color: var(--we-blue) !important; }
-.bg-secondary { background-color: var(--all-yellow) !important; }
-.bg-tertiary { background-color: var(--code-red) !important; }
-.bg-dark-blue { background-color: var(--dark-blue) !important; }
-.bg-black { background-color: var(--black) !important; }
-.bg-white { background-color: var(--white) !important; }
-.bg-light-gray { background-color: var(--light-gray) !important; }
-.bg-gray-for-white { background-color: var(--gray-4-white) !important; }
-.bg-gray-for-blue { background-color: var(--gray-4-blue) !important; }
-
-.margin-half { margin-top: 0.5rem; margin-bottom: 0.5rem; margin-left: 0.5rem; margin-right: 0.5rem; }
-.margin-vertical-half { margin-top: 0.5rem; margin-bottom: 0.5rem; }
-.margin-horizontal-half { margin-left: 0.5rem; margin-right: 0.5rem; }
-.margin-top-half { margin-top: 0.5rem; }
-.margin-right-half { margin-right: 0.5rem; }
-.margin-bottom-half { margin-bottom: 0.5rem; }
-.margin-left-half { margin-left: 0.5rem; }
-
-.padding-half { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 0.5rem; padding-right: 0.5rem; }
-.padding-vertical-half { padding-top: 0.5rem; padding-bottom: 0.5rem; }
-.padding-horizontal-half { padding-left: 0.5rem; padding-right: 0.5rem; }
-.padding-top-half { padding-top: 0.5rem; }
-.padding-right-half { padding-right: 0.5rem; }
-.padding-bottom-half { padding-bottom: 0.5rem; }
-.padding-left-half { padding-left: 0.5rem; }
+.text-primary {
+ color: var(--we-blue);
+}
+
+.text-secondary {
+ color: var(--all-yellow);
+}
+
+.text-tertiary {
+ color: var(--code-red);
+}
+
+.text-dark-blue {
+ color: var(--dark-blue);
+}
+
+.text-black {
+ color: var(--black);
+}
+
+.text-white {
+ color: var(--white);
+}
+
+.text-light-gray {
+ color: var(--light-gray);
+}
+
+.text-grey-for-white {
+ color: var(--gray-4-white);
+}
+
+.text-grey-for-blue {
+ color: var(--gray-4-blue);
+}
+
+.fill-primary {
+ fill: var(--we-blue);
+}
+
+.fill-secondary {
+ fill: var(--all-yellow);
+}
+
+.fill-tertiary {
+ fill: var(--code-red);
+}
+
+.fill-dark-blue {
+ fill: var(--dark-blue);
+}
+
+.fill-black {
+ fill: var(--black);
+}
+
+.fill-white {
+ fill: var(--white);
+}
+
+.fill-light-gray {
+ fill: var(--light-gray);
+}
+
+.fill-gray-for-white {
+ fill: var(--gray-4-white);
+}
+
+.fill-gray-for-blue {
+ fill: var(--gray-4-blue);
+}
+
+.bg-primary {
+ background-color: var(--we-blue) !important;
+}
+
+.bg-secondary {
+ background-color: var(--all-yellow) !important;
+}
+
+.bg-tertiary {
+ background-color: var(--code-red) !important;
+}
+
+.bg-dark-blue {
+ background-color: var(--dark-blue) !important;
+}
+
+.bg-black {
+ background-color: var(--black) !important;
+}
+
+.bg-white {
+ background-color: var(--white) !important;
+}
+
+.bg-light-gray {
+ background-color: var(--light-gray) !important;
+}
+
+.bg-gray-for-white {
+ background-color: var(--gray-4-white) !important;
+}
+
+.bg-gray-for-blue {
+ background-color: var(--gray-4-blue) !important;
+}
+
+.margin-half {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+ margin-left: 0.5rem;
+ margin-right: 0.5rem;
+}
+
+.margin-vertical-half {
+ margin-top: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.margin-horizontal-half {
+ margin-left: 0.5rem;
+ margin-right: 0.5rem;
+}
+
+.margin-top-half {
+ margin-top: 0.5rem;
+}
+
+.margin-right-half {
+ margin-right: 0.5rem;
+}
+
+.margin-bottom-half {
+ margin-bottom: 0.5rem;
+}
+
+.margin-left-half {
+ margin-left: 0.5rem;
+}
+
+.padding-half {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.padding-vertical-half {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+}
+
+.padding-horizontal-half {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+}
+
+.padding-top-half {
+ padding-top: 0.5rem;
+}
+
+.padding-right-half {
+ padding-right: 0.5rem;
+}
+
+.padding-bottom-half {
+ padding-bottom: 0.5rem;
+}
+
+.padding-left-half {
+ padding-left: 0.5rem;
+}
small {
font-size: 75%;
@@ -149,9 +277,17 @@ ul.list-plus li {
font-weight: bold;
}
-.text-gigantic { font-size: 4.5em; }
-.text-huge { font-size: 2.5em; }
-.text-large { font-size: 1.25em; }
+.text-gigantic {
+ font-size: 4.5em;
+}
+
+.text-huge {
+ font-size: 2.5em;
+}
+
+.text-large {
+ font-size: 1.25em;
+}
.line-height-1 {
line-height: 1.1em;
@@ -193,7 +329,7 @@ ul.list-plus li {
z-index: 0;
}
-.bg-kid-overlay > * {
+.bg-kid-overlay>* {
z-index: 1;
position: relative;
}
@@ -205,11 +341,22 @@ ul.list-plus li {
}
@media screen and (min-width: 40em) {
- .medium-padding-horizontal-3 { padding-left: 3rem; padding-right: 3rem; }
- .medium-padding-vertical-3 { padding-top: 3rem; padding-bottom: 3rem; }
+ .medium-padding-horizontal-3 {
+ padding-left: 3rem;
+ padding-right: 3rem;
+ }
+
+ .medium-padding-vertical-3 {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ }
}
+
@media screen and (min-width: 64em) {
- .large-padding-vertical-4 { padding-top: 4rem; padding-bottom: 4rem; }
+ .large-padding-vertical-4 {
+ padding-top: 4rem;
+ padding-bottom: 4rem;
+ }
}
.aside-title {
@@ -236,6 +383,7 @@ ul.list-plus li {
/* Binary Drop (only visible on larger screens) */
@media screen and (min-width: 40em) {
+
.binary-drop-left,
.binary-drop-right {
position: relative;
@@ -319,7 +467,7 @@ ul.list-plus li {
}
.reveal-overlay {
- background-color: rgba(10,10,10,.85);
+ background-color: rgba(10, 10, 10, .85);
outline: none;
}
@@ -350,7 +498,10 @@ ul.list-plus li {
/* Kill Debug on Mobile */
@media screen and (max-width: 48em) {
- #djDebug, #djDebugToolbar, #djDebugToolbarHandle {
+
+ #djDebug,
+ #djDebugToolbar,
+ #djDebugToolbarHandle {
display: none !important;
}
}
@@ -377,6 +528,7 @@ ul.list-plus li {
@media screen and (min-width: 48em) {
+
.main-logo,
.mobile-nav-trigger {
height: 1.5em;
@@ -397,6 +549,7 @@ ul.list-plus li {
}
@media screen and (min-width: 48em) {
+
.main-menu a,
.sub-menu a {
font-size: 0.83em;
@@ -404,6 +557,7 @@ ul.list-plus li {
}
@media screen and (min-width: 64em) {
+
.main-menu a,
.sub-menu a {
font-size: 1em;
@@ -487,6 +641,10 @@ html[data-whatintent='mouse'] .main-menu a:not(.button):hover,
padding-bottom: calc(1.2rem - 5px);
border-bottom: 5px solid var(--black);
}
+
+ .sub-menu .current {
+ border-bottom: 5px solid var(--black);
+ }
}
@@ -559,6 +717,51 @@ body.home .free-programs {
padding: 2rem 1rem;
}
+/*
+*
+*
+*
+*
+*
+Stripe only CSS
+*/
+
+.StripeElement {
+ box-sizing: border-box;
+
+ height: 40px;
+
+ padding: 10px 12px;
+
+ border: 1px solid transparent;
+ border-radius: 4px;
+ background-color: white;
+
+ box-shadow: 0 1px 3px 0 #e6ebf1;
+ -webkit-transition: box-shadow 150ms ease;
+ transition: box-shadow 150ms ease;
+}
+
+.StripeElement--focus {
+ box-shadow: 0 1px 3px 0 #cfd7df;
+}
+
+.StripeElement--invalid {
+ border-color: #fa755a;
+}
+
+.StripeElement--webkit-autofill {
+ background-color: #fefde5 !important;
+}
+
+/*
+*
+*
+*
+*
+*
+*/
+
/* Media Queries */
/* Small only */
diff --git a/weallcode/templates/weallcode/_header.html b/weallcode/templates/weallcode/_header.html
index 2a479bf2..8e85abf4 100644
--- a/weallcode/templates/weallcode/_header.html
+++ b/weallcode/templates/weallcode/_header.html
@@ -19,7 +19,7 @@
Get Involved
{% if user.is_authenticated %}
- Account
+ Account
{% else %}
Log In
{% endif %}
@@ -39,7 +39,7 @@
{% if user.is_authenticated %}
-
Account
+
Account
{% else %}
Log In
{% endif %}
diff --git a/weallcode/templates/weallcode/get_involved.html b/weallcode/templates/weallcode/get_involved.html
index 383fc9de..6e39a6cc 100644
--- a/weallcode/templates/weallcode/get_involved.html
+++ b/weallcode/templates/weallcode/get_involved.html
@@ -98,6 +98,9 @@
Donate to make a difference
+
+ {% include "donate_block.html" %}
+