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 %} + + + + + + + + + {% for payment in object_list %} + + + + + {% endfor %} + +
DateAmount
{{ payment.created_at }}${{ payment.get_formatted_amount | floatformat:2 | intcomma }}
+ {% 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)

    - - -
    -
    -
    - -
    -

    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 %}   - Cancel + Cancel
    @@ -23,4 +23,3 @@

    Edit Student Info

    {% 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 @@ +
    {% csrf_token %} + {{ form.as_p }} + +
    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!

    +
    +
    +
    + {% csrf_token %} +
    +
    + + {{ form.name }} +
    +
    + + {{ form.email }} +
    +
    + +
    + $ + {{ form.amount }} +
    +
    +
    +
    +
    + +
    + +
    + + + +
    +
    + +
    +
    +
    +
    + + +{% 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!

    +
    + {% csrf_token %} +
    +
    + + {{ form.name }} +
    +
    + + {{ form.email }} +
    +
    + +
    + $ + {{ form.amount }} +
    +
    +
    + +
    + +
    + + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + + 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

    +
    @@ -188,7 +191,8 @@

    Contact Us

    - + +