diff --git a/.travis.yml b/.travis.yml index e01d437..7d6724a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: python matrix: include: - python: "3.6" - dist: trusty + dist: xenial sudo: true - python: "3.7" dist: xenial @@ -25,4 +25,4 @@ before_script: - pipenv run black . --check script: - - pipenv run python manage.py test + - pipenv run python manage.py test index diff --git a/Pipfile b/Pipfile index 987633c..3b0e958 100644 --- a/Pipfile +++ b/Pipfile @@ -10,5 +10,6 @@ django = "~=2.1" black = "==18.9b0" dj-database-url = "*" psycopg2 = "*" +django-cas-ng = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 39ac889..f912052 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0fb89abc35fbcf26ea32dc29d3d2d4c59fbd555b8ae23cdd21be57fbff8af904" + "sha256": "9d477408017f929faadf03666e227c448f54feebe093b233eabbaf42bfa8828d" }, "pipfile-spec": 6, "requires": {}, @@ -23,10 +23,10 @@ }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.2.0" + "version": "==19.1.0" }, "black": { "hashes": [ @@ -36,6 +36,20 @@ "index": "pypi", "version": "==18.9b0" }, + "certifi": { + "hashes": [ + "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", + "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" + ], + "version": "==2019.3.9" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, "click": { "hashes": [ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", @@ -53,54 +67,108 @@ }, "django": { "hashes": [ - "sha256:275bec66fd2588dd517ada59b8bfb23d4a9abc5a362349139ddda3c7ff6f5ade", - "sha256:939652e9d34d7d53d74d5d8ef82a19e5f8bb2de75618f7e5360691b6e9667963" + "sha256:7c3543e4fb070d14e10926189a7fcf42ba919263b7473dceaefce34d54e8a119", + "sha256:a2814bffd1f007805b19194eb0b9a331933b82bd5da1c3ba3d7b7ba16e06dc4b" + ], + "index": "pypi", + "version": "==2.2" + }, + "django-cas-ng": { + "hashes": [ + "sha256:108e6a8bf578b3b817dfcfda25d11b9898856f3e1b82bf69150ffe5198455ba5", + "sha256:298b8b7d809b61f31caf188c3a4a568790f259cbac6e4f49ed4cb3ac4f8db9d2" ], "index": "pypi", - "version": "==2.1.7" + "version": "==3.6.0" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "lxml": { + "hashes": [ + "sha256:03984196d00670b2ab14ae0ea83d5cc0cfa4f5a42558afa9ab5fa745995328f5", + "sha256:0815b0c9f897468de6a386dc15917a0becf48cc92425613aa8bbfc7f0f82951f", + "sha256:175f3825f075cf02d15099eb52658457cf0ff103dcf11512b5d2583e1d40f58b", + "sha256:30e14c62d88d1e01a26936ecd1c6e784d4afc9aa002bba4321c5897937112616", + "sha256:3210da6f36cf4b835ff1be853962b22cc354d506f493b67a4303c88bbb40d57b", + "sha256:40f60819fbd5bad6e191ba1329bfafa09ab7f3f174b3d034d413ef5266963294", + "sha256:43b26a865a61549919f8a42e094dfdb62847113cf776d84bd6b60e4e3fc20ea3", + "sha256:4a03dd682f8e35a10234904e0b9508d705ff98cf962c5851ed052e9340df3d90", + "sha256:62f382cddf3d2e52cf266e161aa522d54fd624b8cc567bc18f573d9d50d40e8e", + "sha256:7b98f0325be8450da70aa4a796c4f06852949fe031878b4aa1d6c417a412f314", + "sha256:846a0739e595871041385d86d12af4b6999f921359b38affb99cdd6b54219a8f", + "sha256:a3080470559938a09a5d0ec558c005282e99ac77bf8211fb7b9a5c66390acd8d", + "sha256:ad841b78a476623955da270ab8d207c3c694aa5eba71f4792f65926dc46c6ee8", + "sha256:afdd75d9735e44c639ffd6258ce04a2de3b208f148072c02478162d0944d9da3", + "sha256:b4fbf9b552faff54742bcd0791ab1da5863363fb19047e68f6592be1ac2dab33", + "sha256:b90c4e32d6ec089d3fa3518436bdf5ce4d902a0787dbd9bb09f37afe8b994317", + "sha256:b91cfe4438c741aeff662d413fd2808ac901cc6229c838236840d11de4586d63", + "sha256:bdb0593a42070b0a5f138b79b872289ee73c8e25b3f0bea6564e795b55b6bcdd", + "sha256:c4e4bca2bb68ce22320297dfa1a7bf070a5b20bcbaec4ee023f83d2f6e76496f", + "sha256:cec4ab14af9eae8501be3266ff50c3c2aecc017ba1e86c160209bb4f0423df6a", + "sha256:e83b4b2bf029f5104bc1227dbb7bf5ace6fd8fabaebffcd4f8106fafc69fc45f", + "sha256:e995b3734a46d41ae60b6097f7c51ba9958648c6d1e0935b7e0ee446ee4abe22", + "sha256:f679d93dec7f7210575c85379a31322df4c46496f184ef650d3aba1484b38a2d", + "sha256:fd213bb5166e46974f113c8228daaef1732abc47cb561ce9c4c8eaed4bd3b09b", + "sha256:fdcb57b906dbc1f80666e6290e794ab8fb959a2e17aa5aee1758a85d1da4533f", + "sha256:ff424b01d090ffe1947ec7432b07f536912e0300458f9a7f48ea217dd8362b86" + ], + "version": "==4.3.3" }, "psycopg2": { "hashes": [ - "sha256:02445ebbb3a11a3fe8202c413d5e6faf38bb75b4e336203ee144ca2c46529f94", - "sha256:0e9873e60f98f0c52339abf8f0339d1e22bfe5aae0bcf7aabd40c055175035ec", - "sha256:1148a5eb29073280bf9057c7fc45468592c1bb75a28f6df1591adb93c8cb63d0", - "sha256:259a8324e109d4922b0fcd046e223e289830e2568d6f4132a3702439e5fd532b", - "sha256:28dffa9ed4595429e61bacac41d3f9671bb613d1442ff43bcbec63d4f73ed5e8", - "sha256:314a74302d4737a3865d40ea50e430ce1543c921ba10f39d562e807cfe2edf2a", - "sha256:36b60201b6d215d7658a71493fdf6bd5e60ad9a0cffed39906627ff9f4f3afd3", - "sha256:3f9d532bce54c4234161176ff3b8688ff337575ca441ea27597e112dfcd0ee0c", - "sha256:5d222983847b40af989ad96c07fc3f07e47925e463baa5de716be8f805b41d9b", - "sha256:6757a6d2fc58f7d8f5d471ad180a0bd7b4dd3c7d681f051504fbea7ae29c8d6f", - "sha256:6a0e0f1e74edb0ab57d89680e59e7bfefad2bfbdf7c80eb38304d897d43674bb", - "sha256:6ca703ccdf734e886a1cf53eb702261110f6a8b0ed74bcad15f1399f74d3f189", - "sha256:8513b953d8f443c446aa79a4cc8a898bd415fc5e29349054f03a7d696d495542", - "sha256:9262a5ce2038570cb81b4d6413720484cb1bc52c064b2f36228d735b1f98b794", - "sha256:97441f851d862a0c844d981cbee7ee62566c322ebb3d68f86d66aa99d483985b", - "sha256:a07feade155eb8e69b54dd6774cf6acf2d936660c61d8123b8b6b1f9247b67d6", - "sha256:a9b9c02c91b1e3ec1f1886b2d0a90a0ea07cc529cb7e6e472b556bc20ce658f3", - "sha256:ae88216f94728d691b945983140bf40d51a1ff6c7fe57def93949bf9339ed54a", - "sha256:b360ffd17659491f1a6ad7c928350e229c7b7bd83a2b922b6ee541245c7a776f", - "sha256:b4221957ceccf14b2abdabef42d806e791350be10e21b260d7c9ce49012cc19e", - "sha256:b90758e49d5e6b152a460d10b92f8a6ccf318fcc0ee814dcf53f3a6fc5328789", - "sha256:c669ea986190ed05fb289d0c100cc88064351f2b85177cbfd3564c4f4847d18c", - "sha256:d1b61999d15c79cf7f4f7cc9021477aef35277fc52452cf50fd13b713c84424d", - "sha256:de7bb043d1adaaf46e38d47e7a5f703bb3dab01376111e522b07d25e1a79c1e1", - "sha256:e393568e288d884b94d263f2669215197840d097c7e5b0acd1a51c1ea7d1aba8", - "sha256:ed7e0849337bd37d89f2c2b0216a0de863399ee5d363d31b1e5330a99044737b", - "sha256:f153f71c3164665d269a5d03c7fa76ba675c7a8de9dc09a4e2c2cdc9936a7b41", - "sha256:f1fb5a8427af099beb7f65093cbdb52e021b8e6dbdfaf020402a623f4181baf5", - "sha256:f36b333e9f86a2fba960c72b90c34be6ca71819e300f7b1fc3d2b0f0b2c546cd", - "sha256:f4526d078aedd5187d0508aa5f9a01eae6a48a470ed678406da94b4cd6524b7e" + "sha256:3648afc2b4828a6e00d516d2d09a260edd2c1e3de1e0d41d99c5ab004a73d180", + "sha256:5329b4530e31f58e0eafc55e26bbef684509bcc3be41604e45c0b98c297dc722", + "sha256:7c1ae1669d11105a002f804bebd7432f8dc7473459aa405164c6b44a922decd5", + "sha256:8af13498e32a00d0a66e43b7491c15231b27ab964ee4d2277a4a2dbadfb2c482", + "sha256:9d5489867bd5f6d6c6191a4debd8de9a5c03a9608cce3f4d7133e29e6bd4ec27", + "sha256:a17bfc9faffcca0ad9360c1ad97ab61ede583aa954715e8e436ffd80046661ff", + "sha256:b4a475ce87eabc0607e068a3c704d0aa0820237ed78d493b8e2d880eb73cd7fe", + "sha256:c49d66e97affdc80d084b3b363f09f17db621418f0b8e0524b06c54959e2094d", + "sha256:d13fbc3d533656cfdf094e13c1b0f40917b72813755ba780971ba0ce04280ac4", + "sha256:e1e4fe6e8ab9f9c7d28514d007f623999d2dd6b5b81069dd4f9d30dbdd6f7069", + "sha256:e67d60cb1a32f5fd8fcea935cf9efb1d1c26f96203b0ca2ae98c4c40ef8d8eac" ], "index": "pypi", - "version": "==2.7.7" + "version": "==2.8.1" + }, + "python-cas": { + "hashes": [ + "sha256:934af22469d87cff27cd22c8d94ef9265e587e5b83758fc95a91875df9574692" + ], + "version": "==1.4.0" }, "pytz": { "hashes": [ - "sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9", - "sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c" + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "requests": { + "hashes": [ + "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", + "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" ], - "version": "==2018.9" + "version": "==2.21.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "sqlparse": { + "hashes": [ + "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", + "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" + ], + "version": "==0.3.0" }, "toml": { "hashes": [ @@ -108,6 +176,13 @@ "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" ], "version": "==0.10.0" + }, + "urllib3": { + "hashes": [ + "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", + "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + ], + "version": "==1.24.1" } }, "develop": {} diff --git a/README.md b/README.md index 8a7754b..f1db420 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,9 @@ Check the [Wiki](https://github.com/wtg/petitions-rewrite/wiki) for Django refer pipenv install --python ``` -5. Activate the virtual environment ```pipenv shell``` +5. Activate the virtual environment ```pipenv shell```. + + Then run ```pipenv sync``` to install all necessary dependencies. 6. Set a secret key environment variable ``` diff --git a/index/admin.py b/index/admin.py index 5b857aa..0040f3c 100644 --- a/index/admin.py +++ b/index/admin.py @@ -7,7 +7,7 @@ class TagAdmin(admin.ModelAdmin): class PetitionAdmin(admin.ModelAdmin): - pass + exclude = ("created_date", "signatures") admin.site.register(Tag, TagAdmin) diff --git a/index/apps.py b/index/apps.py index fac4c5c..70e738e 100644 --- a/index/apps.py +++ b/index/apps.py @@ -4,6 +4,5 @@ class IndexConfig(AppConfig): name = "index" - -class ViewAllConfig(AppConfig): - name = "view_all" + def ready(self): + import index.signals diff --git a/index/forms.py b/index/forms.py index e53f170..5353199 100644 --- a/index/forms.py +++ b/index/forms.py @@ -11,3 +11,8 @@ class CreatePetitionForm(forms.ModelForm): class Meta: model = Petition fields = ["title", "description", "tags"] + + +class SignPetitionForm(forms.Form): + sign_btn = forms.CharField() + pk = forms.IntegerField() diff --git a/index/migrations/0001_initial.py b/index/migrations/0001_initial.py index 44133c4..5046a5b 100644 --- a/index/migrations/0001_initial.py +++ b/index/migrations/0001_initial.py @@ -1,16 +1,16 @@ -# Generated by Django 2.1.1 on 2019-02-15 01:22 +# Generated by Django 2.2 on 2019-04-12 12:02 -import datetime +from django.conf import settings from django.db import migrations, models import django.db.models.deletion -from django.utils.timezone import utc +import django.utils.timezone class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)] operations = [ migrations.CreateModel( @@ -18,24 +18,26 @@ class Migration(migrations.Migration): fields=[ ("title", models.CharField(max_length=200)), ("description", models.CharField(max_length=4000)), - ( - "ID", - models.IntegerField( - primary_key=True, serialize=False, verbose_name=999999 - ), - ), + ("ID", models.IntegerField(primary_key=True, serialize=False)), ("archived", models.BooleanField(default=False)), ("hidden", models.BooleanField(default=False)), ( "created_date", models.DateTimeField( - db_index=True, - default=datetime.datetime( - 2019, 2, 15, 1, 22, 11, 359671, tzinfo=utc - ), + db_index=True, default=django.utils.timezone.now ), ), ("expected_sig", models.IntegerField(verbose_name=300)), + ( + "author", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="petition_author", + to=settings.AUTH_USER_MODEL, + ), + ), ], ), migrations.CreateModel( @@ -62,6 +64,15 @@ class Migration(migrations.Migration): ("refer_other_info", models.CharField(max_length=1000)), ], ), + migrations.CreateModel( + name="Tag", + fields=[ + ( + "label", + models.CharField(max_length=15, primary_key=True, serialize=False), + ) + ], + ), migrations.CreateModel( name="Signature", fields=[ @@ -76,61 +87,31 @@ class Migration(migrations.Migration): ), ( "signed_date", - models.DateTimeField( - default=datetime.datetime( - 2019, 2, 15, 1, 22, 11, 358683, tzinfo=utc - ) - ), + models.DateTimeField(default=django.utils.timezone.now), ), - ], - ), - migrations.CreateModel( - name="Tag", - fields=[ ( - "label", - models.CharField(max_length=15, primary_key=True, serialize=False), - ) - ], - ), - migrations.CreateModel( - name="User", - fields=[ + "petition", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="index.Petition" + ), + ), ( - "rcs_id", - models.CharField(max_length=10, primary_key=True, serialize=False), + "signer", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), ), - ("name", models.CharField(max_length=50)), - ("admin", models.BooleanField(default=False)), - ("banned", models.BooleanField(default=False)), - ("initials", models.CharField(max_length=2)), - ("union_member", models.BooleanField(default=False)), ], ), - migrations.AddField( - model_name="signature", - name="signer", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="signatures", - to="index.User", - ), - ), - migrations.AddField( - model_name="petition", - name="author", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", - to="index.User", - ), - ), migrations.AddField( model_name="petition", name="senate_response", field=models.ForeignKey( + blank=True, + null=True, on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", + related_name="petition_response", to="index.Response", ), ), @@ -138,12 +119,17 @@ class Migration(migrations.Migration): model_name="petition", name="signatures", field=models.ManyToManyField( - related_name="petitions", to="index.Signature" + blank=True, + related_name="petition_signatures", + through="index.Signature", + to=settings.AUTH_USER_MODEL, ), ), migrations.AddField( model_name="petition", name="tags", - field=models.ManyToManyField(related_name="petitions", to="index.Tag"), + field=models.ManyToManyField( + blank=True, related_name="petition_tags", to="index.Tag" + ), ), ] diff --git a/index/migrations/0002_auto_20190215_0130.py b/index/migrations/0002_auto_20190215_0130.py deleted file mode 100644 index 1252f9a..0000000 --- a/index/migrations/0002_auto_20190215_0130.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.1.1 on 2019-02-15 01:30 - -import datetime -from django.db import migrations, models -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [("index", "0001_initial")] - - operations = [ - migrations.AlterField( - model_name="petition", - name="created_date", - field=models.DateTimeField( - db_index=True, - default=datetime.datetime(2019, 2, 15, 1, 30, 44, 768987, tzinfo=utc), - ), - ), - migrations.AlterField( - model_name="signature", - name="signed_date", - field=models.DateTimeField( - default=datetime.datetime(2019, 2, 15, 1, 30, 44, 767805, tzinfo=utc) - ), - ), - ] diff --git a/index/migrations/0002_auto_20190412_1638.py b/index/migrations/0002_auto_20190412_1638.py new file mode 100644 index 0000000..6c05ae3 --- /dev/null +++ b/index/migrations/0002_auto_20190412_1638.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2 on 2019-04-12 16:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [("index", "0001_initial")] + + operations = [ + migrations.AlterField( + model_name="petition", + name="author", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="author", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="petition", + name="tags", + field=models.ManyToManyField( + blank=True, related_name="tags", to="index.Tag" + ), + ), + ] diff --git a/index/migrations/0003_auto_20190215_0136.py b/index/migrations/0003_auto_20190215_0136.py deleted file mode 100644 index c4384b8..0000000 --- a/index/migrations/0003_auto_20190215_0136.py +++ /dev/null @@ -1,68 +0,0 @@ -# Generated by Django 2.1.1 on 2019-02-15 01:36 - -import datetime -from django.db import migrations, models -import django.db.models.deletion -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [("index", "0002_auto_20190215_0130")] - - operations = [ - migrations.AlterField( - model_name="petition", - name="ID", - field=models.IntegerField(primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name="petition", - name="author", - field=models.ForeignKey( - blank=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", - to="index.User", - ), - ), - migrations.AlterField( - model_name="petition", - name="created_date", - field=models.DateTimeField( - db_index=True, - default=datetime.datetime(2019, 2, 15, 1, 36, 53, 117699, tzinfo=utc), - ), - ), - migrations.AlterField( - model_name="petition", - name="senate_response", - field=models.ForeignKey( - blank=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", - to="index.Response", - ), - ), - migrations.AlterField( - model_name="petition", - name="signatures", - field=models.ManyToManyField( - blank=True, related_name="petitions", to="index.Signature" - ), - ), - migrations.AlterField( - model_name="petition", - name="tags", - field=models.ManyToManyField( - blank=True, related_name="petitions", to="index.Tag" - ), - ), - migrations.AlterField( - model_name="signature", - name="signed_date", - field=models.DateTimeField( - default=datetime.datetime(2019, 2, 15, 1, 36, 53, 116985, tzinfo=utc) - ), - ), - ] diff --git a/index/migrations/0004_auto_20190215_0140.py b/index/migrations/0004_auto_20190215_0140.py deleted file mode 100644 index 0b04977..0000000 --- a/index/migrations/0004_auto_20190215_0140.py +++ /dev/null @@ -1,58 +0,0 @@ -# Generated by Django 2.1.1 on 2019-02-15 01:40 - -import datetime -from django.db import migrations, models -import django.db.models.deletion -from django.utils.timezone import utc - - -class Migration(migrations.Migration): - - dependencies = [("index", "0003_auto_20190215_0136")] - - operations = [ - migrations.AlterField( - model_name="petition", - name="author", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", - to="index.User", - ), - ), - migrations.AlterField( - model_name="petition", - name="created_date", - field=models.DateTimeField( - db_index=True, - default=datetime.datetime(2019, 2, 15, 1, 40, 10, 865395, tzinfo=utc), - ), - ), - migrations.AlterField( - model_name="petition", - name="senate_response", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="petitions", - to="index.Response", - ), - ), - migrations.AlterField( - model_name="petition", - name="tags", - field=models.ManyToManyField( - blank=True, null=True, related_name="petitions", to="index.Tag" - ), - ), - migrations.AlterField( - model_name="signature", - name="signed_date", - field=models.DateTimeField( - default=datetime.datetime(2019, 2, 15, 1, 40, 10, 864180, tzinfo=utc) - ), - ), - ] diff --git a/index/migrations/0005_auto_20190303_0230.py b/index/migrations/0005_auto_20190303_0230.py deleted file mode 100644 index 7789129..0000000 --- a/index/migrations/0005_auto_20190303_0230.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.1.7 on 2019-03-03 02:30 - -from django.db import migrations, models -import index.models - - -class Migration(migrations.Migration): - - dependencies = [("index", "0004_auto_20190215_0140")] - - operations = [ - migrations.AlterField( - model_name="petition", - name="created_date", - field=models.DateTimeField( - db_index=True, default=index.models.timezone.now - ), - ), - migrations.AlterField( - model_name="petition", - name="tags", - field=models.ManyToManyField( - blank=True, related_name="petitions", to="index.Tag" - ), - ), - migrations.AlterField( - model_name="signature", - name="signed_date", - field=models.DateTimeField(default=index.models.timezone.now), - ), - ] diff --git a/index/models.py b/index/models.py index f4640eb..07f0b9c 100644 --- a/index/models.py +++ b/index/models.py @@ -1,5 +1,7 @@ from django.db import models from django.utils import timezone +from django.urls import reverse +from django.contrib.auth.models import User # Creates a tag, there can be at most three in a single petition class Tag(models.Model): @@ -13,46 +15,6 @@ def __str__(self): return self.label -# Creates a user based on their name, RCS ID and admin status -class User(models.Model): - rcs_id = models.CharField(max_length=10, primary_key=True) # RCS ID - name = models.CharField(max_length=50) # The author name - admin = models.BooleanField(default=False) # Is admin? - # Is the person banned?-- not very likely - banned = models.BooleanField(default=False) - initials = models.CharField(max_length=2) # Their initials - union_member = models.BooleanField(default=False) # Is member of the union? - - # Returns their name - def __unicode__(self): - return self.rcs_id - - # Returns the initials of the User using the RCS ID, first strips - # it of the digits at the end and then - def set_initials(self): - full_name = self.rcs_id - name_modified = "".join([i for i in full_name if not i.isdigit()]) - self.initials = name_modified[-1] + name_modified[0] - - # Changes the bool value that dictates whether or not a character - # is a admin - def set_admin(self, bool_val): - self.admin = bool_val - - -# Logs a signature, there can be many in a single petition -class Signature(models.Model): - # The person trying to sign the petition - signer = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="signatures" - ) - signed_date = models.DateTimeField(default=timezone.now) # When they signed - - # Returns the signer when asked for the initials - def __unicode__(self): - return self.signer - - # Creates a response based on whether the senate is investigating the topic of the Petition class Response(models.Model): senator_investigation = models.BooleanField(default=False) @@ -126,64 +88,73 @@ class Petition(models.Model): archived = models.BooleanField(default=False) hidden = models.BooleanField(default=False) - created_date = models.DateTimeField( - db_index=True, default=timezone.now - ) # Files the date created - expected_sig = models.IntegerField( - 300 - ) # The expected signature to move to the next step - + # Files the date created + created_date = models.DateTimeField(db_index=True, default=timezone.now) + # The expected signature to move to the next step + expected_sig = models.IntegerField(300) + # The author of the petition author = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="petitions", blank=True, null=True - ) # The author of the petition - tags = models.ManyToManyField( - Tag, related_name="petitions", blank=True - ) # The tags, probably a max of 3 + User, on_delete=models.CASCADE, related_name="author", blank=True, null=True + ) + # The tags, probably a max of 3 + tags = models.ManyToManyField(Tag, related_name="tags", blank=True) + # The signature signatures = models.ManyToManyField( - Signature, related_name="petitions", blank=True - ) # The signature - + User, through="Signature", related_name="petition_signatures", blank=True + ) + # If the senate has responded , their answer senate_response = models.ForeignKey( Response, on_delete=models.CASCADE, - related_name="petitions", + related_name="petition_response", blank=True, null=True, - ) # If the senate has responded , their answer + ) # Returns title when asked for the item def __unicode__(self): return self.title - # Sets the hidden variable as true if we don't want it to - # Be displayed in the main site - + # Sets the hidden variable as true if we don't want it to + # Be displayed in the main site def set_hidden(self, bool_val): self.hidden = bool_val - # If the petition isn't active, we can set the archived variable as true - + # If the petition isn't active, we can set the archived variable as true def set_archived(self, bool_val): self.archived = bool_val - # Adds a description to the basic petitions model, with a max length - # of 4000 words - + # Adds a description to the basic petitions model, with a max length + # of 4000 words def add_description(self, descript): self.description = descript return True - # Returns true if we have enough signatures on the petition + # Returns true if we have enough signatures on the petition def check_enough_sigs(self): if self.signatures.count() >= self.expected_sig: return True return False - # Makes sure the we have less than three tags in the model + # Makes sure the we have less than three tags in the model def check_tags(self): if self.tags.count() > 3: return False return True - # Add tag to a petition, there can be at most three in a single petition - # def add_tag (self): + # Add tag to a petition, there can be at most three in a single petition + # def add_tag (self): + def get_url(self): + return reverse("petition-detail", args=[str(self.ID)]) + + +# Logs a signature, there can be many in a single petition +class Signature(models.Model): + # The person trying to sign the petition + signer = models.ForeignKey(User, on_delete=models.CASCADE) + petition = models.ForeignKey(Petition, on_delete=models.CASCADE) + signed_date = models.DateTimeField(default=timezone.now) # When they signed + + # Returns the signer when asked for the initials + def __unicode__(self): + return self.signer diff --git a/index/signals.py b/index/signals.py new file mode 100644 index 0000000..f033e0c --- /dev/null +++ b/index/signals.py @@ -0,0 +1,8 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from .models import Petition, Signature + + +@receiver(post_save, sender=Petition) +def petition_created(sender, instance, **kwargs): + instance.signatures.add(instance.author) diff --git a/index/static/petition/style.css b/index/static/petition/style.css index 40da3dc..50294ec 100644 --- a/index/static/petition/style.css +++ b/index/static/petition/style.css @@ -12,8 +12,12 @@ body { width: 100%; } -.nav-link { - color: #000000; +.nav-link, .btn-link { + color: gray; +} + +.nav-item { + padding-left: 10px; } .wtg-logo { @@ -48,10 +52,38 @@ body { max-width: 60%; } -.header { +.header, .info, .description, .signatures { margin-left: 40px; margin-bottom: 20px; + margin-top: 10px; + +} + +.detail { + margin-top: 50px; + padding-left: 20px; +} + +.parent { + display: flex; } + +.left { + margin-right: auto; + flex-grow: 1; + padding: 20px; +} + +.right { + margin-left: auto; + min-width: 30%; + padding: 20px; +} + +.info { + /* max-width: 50%; */ +} + .card-title a { color: #ed1c24; } @@ -60,7 +92,7 @@ body { color: #505050; } -.link, .link:hover { +.link, .link:hover, .btn-link:hover { color: gray; } @@ -91,3 +123,32 @@ body { .card { min-height: 175px; } + +#sign-btn { + background-color: #ed1c24; + color: white; +} + +#sign-btn:hover { + background-color: #ebebeb; + border-color: #ed1c24; + color: #ed1c24; +} + +.flex-sig-container { + display: flex; + flex-flow: row wrap; + list-style-type: none; + padding-left: 0px; +} + +.flex-sig { + padding-bottom: 10px; + padding-right: 10px; + margin-top: 10px; + text-align: center; +} + +.progress-bar { + background-color: #ed1c24 !important; +} diff --git a/index/templates/base.html b/index/templates/base.html index 31172ca..298973e 100644 --- a/index/templates/base.html +++ b/index/templates/base.html @@ -33,9 +33,15 @@ + + {% if user.is_authenticated %} + + {% else %} + + {% endif %} + + diff --git a/index/templates/detail.html b/index/templates/detail.html new file mode 100644 index 0000000..99aa90e --- /dev/null +++ b/index/templates/detail.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} +{% block title %} {{ petition.title }} {% endblock %} + +{% block content %} +
+
+

{{ petition.title }}

+
Created by {{ petition.author.get_full_name }} ({{ petition.author.username.lower }})
+
+ +
+
+
+
Description
+

{{ petition.description }}

+
+ +
+
Signed by
+
    + {% for initial in initials %} +
  • {{ initial }}
  • + {% empty %} +
  • No signatures yet
  • + {% endfor %} +
+
+
+ +
+
+
Take action
+
{% csrf_token %} + {% if user.is_authenticated %} + {% if user_signed %} + + {% else %} + + + {% endif %} + + {% else %} + + {% endif %} +
+
+ +
+
{{initials|length}} / {{petition.expected_sig}} Signatures
+
+
+
+
+ +
+
Status
+
{{status}}
+
+ +
+
Expires
+
{{ date|date:'M d, Y' }}
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/index/templates/index.html b/index/templates/index.html index b061406..dcfc2f5 100644 --- a/index/templates/index.html +++ b/index/templates/index.html @@ -18,7 +18,7 @@

Make your voice heard by the RPI Student Senate and the campus community.
-
{{ petition.title }}
+
{{ petition.title }}
{{ petition.signatures.count }} signatures
{% for tag in petition.tags.all %} {{ tag }}
diff --git a/index/tests.py b/index/tests.py index 8dbfeae..a23b384 100644 --- a/index/tests.py +++ b/index/tests.py @@ -1,8 +1,8 @@ -import unittest -from .models import Petition, Tag, User, Response, Signature +from django.test import TestCase +from index.models import Petition, Tag, Response, Signature -class PetitionModelTestCase(unittest.TestCase): +class PetitionModelTestCase(TestCase): def test_title(self): petition = Petition(title="Save Greek Life") self.assertEqual("Save Greek Life", petition.title) @@ -14,10 +14,3 @@ def test_title(self): # def test_check_enough_signatures(self): # petition = Petition(title="Save Greek Life") # self.assertFalse(petition.check_enough_sigs()) - - -class UserModelTestCase(unittest.TestCase): - def test_user_initials(self): - user = User(rcs_id="rolleg", name="Grace Roller") - user.set_initials() - self.assertEqual("gr", user.initials) diff --git a/index/urls.py b/index/urls.py index 96cc5ef..46aaea6 100644 --- a/index/urls.py +++ b/index/urls.py @@ -1,8 +1,13 @@ from django.urls import path +import django_cas_ng.views from . import views urlpatterns = [ path("", views.index, name="index"), path("create/", views.create, name="create"), path("all/", views.all, name="all"), + path("petition/", views.petition_detail, name="petition-detail"), + path("petition/sign", views.sign, name="sign"), + path("login/", django_cas_ng.views.LoginView.as_view(), name="cas_ng_login"), + path("logout/", django_cas_ng.views.LogoutView.as_view(), name="cas_ng_logout"), ] diff --git a/index/views.py b/index/views.py index 2edb6a7..b246dc9 100644 --- a/index/views.py +++ b/index/views.py @@ -1,7 +1,14 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from django.views import generic -from .forms import CreatePetitionForm -from .models import Petition, Tag +from django.utils import timezone +from django.http import ( + HttpResponse, + HttpResponseRedirect, + HttpResponseBadRequest, + HttpResponseNotAllowed, +) +from .forms import CreatePetitionForm, SignPetitionForm +from .models import Petition, Tag, User, Signature def index(request): @@ -18,3 +25,48 @@ def create(request): form = CreatePetitionForm() context = {"form": form} return render(request, "create.html", context=context) + + +def petition_detail(request, pk): + petition = Petition.objects.get(pk=pk) + + user_signed = False + if petition.signatures.filter(username=request.user.username).exists(): + user_signed = True + + signatures = petition.signatures.all() + initials = [] + for user in signatures: + initials.append(user.first_name[0] + user.last_name[0]) + + status = "Goal not met" + if petition.check_enough_sigs(): + status = "Goal met" + + expiration_date = petition.created_date + timezone.timedelta(days=365) + progress_percent = int((petition.signatures.count() / petition.expected_sig) * 100) + context = { + "petition": petition, + "user_signed": user_signed, + "initials": initials, + "status": status, + "date": expiration_date, + "progress_percent": progress_percent, + } + return render(request, "detail.html", context=context) + + +def sign(request): + if request.method != "POST": + return HttpResponseNotAllowed(["POST"]) + + form = SignPetitionForm(request.POST) + + if form.is_valid(): + pk = form.cleaned_data["pk"] + petition = Petition.objects.get(pk=pk) + petition.signatures.add(request.user) + petition.save() + return HttpResponseRedirect("/petition/" + str(pk)) + + return HttpResponseBadRequest() diff --git a/petitions/backends.py b/petitions/backends.py new file mode 100644 index 0000000..dca7e42 --- /dev/null +++ b/petitions/backends.py @@ -0,0 +1,31 @@ +from django_cas_ng.backends import CASBackend +from django.conf import settings +import requests +import os + + +class StudentCASBackend(CASBackend): + def user_can_authenticate(self, user): + url = ( + "https://webtech.union.rpi.edu/services/identity/valid/" + + str(user.username).lower() + ) + key = settings.IDENTITY_KEY + r = requests.get(url, headers={"Authorization": "Token " + key}) + r.raise_for_status() + error = r.json()["error"] + if error: + return False + student = r.json()["user_type"] == "Student" + if not student: + return False + + user.first_name = r.json()["first_name"] + user.last_name = r.json()["last_name"] + + if settings.DEBUG == True: + user.is_staff = True + user.is_superuser = True + + user.save() + return True diff --git a/petitions/settings/base.py b/petitions/settings/base.py index 4d53680..aaac102 100644 --- a/petitions/settings/base.py +++ b/petitions/settings/base.py @@ -23,6 +23,12 @@ SECRET_KEY = os.environ.get("SECRET_KEY") +if "IDENTITY_KEY" not in os.environ: + sys.stderr.write("Error: The environment variable IDENTITY_KEY must be set.\n") + exit(1) + +IDENTITY_KEY = os.environ.get("IDENTITY_KEY") + ALLOWED_HOSTS = [] @@ -35,6 +41,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "django_cas_ng", "index", ] @@ -46,8 +53,16 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", + "django_cas_ng.middleware.CASMiddleware", ] +AUTHENTICATION_BACKENDS = [ + "django.contrib.auth.backends.ModelBackend", + "petitions.backends.StudentCASBackend", +] + +CAS_SERVER_URL = "https://cas-auth.rpi.edu/cas/" + ROOT_URLCONF = "petitions.urls" TEMPLATES = [