From 208b9ba67490a2894da10775d0d7d3683bb7360d Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 28 Mar 2022 18:17:52 -0500 Subject: [PATCH 01/33] Fix new post form length filters not redirecting correctly I never ran into this error myself, but I think if we did, it's possible it could have caused some kind of infinite loop. Or it would just error because the form is mapped to POST requests only - unless the browser would take the redirect and send the POST request there - but that wouldn't make sense given it's normal purpose. --- forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forms.py b/forms.py index b53c493..b41156e 100644 --- a/forms.py +++ b/forms.py @@ -31,10 +31,10 @@ def new_post(): if len(post_text) < 15: flash('Must have at least 15 characters of text.') - return redirect(url_for('forms.new_post')) + return redirect(url_for('main.feed')) elif len(post_text) > 1000: flash('Cannot have more than 1000 characters of text.') - return redirect(url_for('forms.new_post')) + return redirect(url_for('main.feed')) post = Post(author=current_user.id, text=post_text) db.session.add(post) From 2973f3ca817df7a412586f36922fbbe9d73d07e9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 28 Mar 2022 18:29:02 -0500 Subject: [PATCH 02/33] Add profanity filter to comment and post creation - For Heroku, I worry someone might add a racial slur or something. This isn't perfect, but it's good enough. --- Pipfile | 1 + Pipfile.lock | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++- forms.py | 13 ++ 3 files changed, 383 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index a3bba7c..67baa41 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ faker = "*" humanize = "*" gunicorn = "*" psycopg2 = "*" +profanity-filter = "*" [dev-packages] diff --git a/Pipfile.lock b/Pipfile.lock index ff4a891..3015107 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "e51737b38157b4d1e2b57226a5a07dbce7558baeef5895e41cede4d2809de74b" + "sha256": "e847c58401b17c65209f6f6d77aacb1926b2f8805007f4629b67fcf95dccb209" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,57 @@ ] }, "default": { + "blis": { + "hashes": [ + "sha256:148f59a0a47a38ce82e3afc50c709494d5e5a494bef28ce1519c7a17346c645b", + "sha256:1667db8439d9ca41c0c1f0ea954d87462be01b125436c4b264f73603c9fb4e82", + "sha256:3e024f103522e72a27019cfcfe14569522a394f5d651565560a18040fdd69a6c", + "sha256:4a48eeaa506f176bcac306378f5e8063697c93e26d2418fcbe053e8912019090", + "sha256:5d4a81f9438db7a19ac8e64ad41331f65a659ea8f3bb1889a9c2088cfd9fe104", + "sha256:64bef63b1abd5b41819ea53897bdbc03c631a59c1757a9393e6ae0828692f31c", + "sha256:680480dfa16b354f2e4d584edb8d36f0505ed8df12939beee2d161aea7bb3609", + "sha256:76d13dbcd648ca33dfc83569bb219d0696e4f6e5ad00b9f538332a3bdb28ff30", + "sha256:7865e39cac4e10506afc49213938fb7e13bf73ca980c9c20ffad2de4ef858f43", + "sha256:a0183760604b14e8eb671a431d06606594def03c36aaaa2a2e7b7f88382dac76", + "sha256:b5e0acc760daf5c3b45bce44653943e3a04d81c21c5b92213ed51664525dc24e", + "sha256:bead485e5d79d3eb62a8df55618743878fb3cba606aaf926153db5803270b185", + "sha256:cfb7d730fef706f3ea4389196ce5f610f24cc83f828c498a275c12f05f0cf5c4", + "sha256:d6055ced65d6581ab4f1da0d3f6ec14c60512474c5c9b3210c9f30dd7dd1447d", + "sha256:e22145110864bcffb1d52cb57050b67b8a8ecd43c7c0a1ac0bcdb2c85c8bf416", + "sha256:f4109cce38e644e81d923836b34024905d59e88c8fb48b89b420f4d7661cd89f" + ], + "version": "==0.7.7" + }, + "cached-property": { + "hashes": [ + "sha256:9fa5755838eecbb2d234c3aa390bd80fbd3ac6b6869109bfc1b499f7bd89a130", + "sha256:df4f613cf7ad9a588cc381aaf4a512d26265ecebd5eb9e1ba12f1319eb85a6a0" + ], + "version": "==1.5.2" + }, + "catalogue": { + "hashes": [ + "sha256:584d78e7f4c3c6e2fd498eb56dfc8ef1f4ff738480237de2ccd26cbe2cf47172", + "sha256:d74d1d856c6b36a37bf14aa6dbbc27d0582667b7ab979a6108e61a575e8723f5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "markers": "python_version >= '3'", + "version": "==2.0.12" + }, "click": { "hashes": [ "sha256:19a4baa64da924c5e0cd889aba8e947f280309f1a2ce0947a3e3a7bcb7cc72d6", @@ -32,6 +83,27 @@ "markers": "platform_system == 'Windows'", "version": "==0.4.4" }, + "cymem": { + "hashes": [ + "sha256:04676d696596b0db3f3c5a3936bab12fb6f24278921a6622bb185e61765b2b4d", + "sha256:169725b5816959d34de2545b33fee6a8021a6e08818794a426c5a4f981f17e5e", + "sha256:2aa3fa467d906cd2c27fa0a2e2952dd7925f5fcc7973fab6d815ef6acb25aad8", + "sha256:4749f220e4c06ec44eb10de13794ff0508cdc4f8eff656cf49cab2cdb3122c0c", + "sha256:492084aef23ac2ff3da3729e9d36340bc91a96c2dc8c3a82a1926e384ab52412", + "sha256:4f87fe087f2ae36c3e20e2b1a29d7f76a28c035372d0a97655f26223d975235a", + "sha256:6b0d1a6b0a1296f31fa9e4b7ae5ea49394084ecc883b1ae6fec4844403c43468", + "sha256:700540b68e96a7056d0691d467df2bbaaf0934a3e6fe2383669998cbee19580a", + "sha256:971cf0a8437dfb4185c3049c086e463612fe849efadc0f5cc153fc81c501da7d", + "sha256:a93fba62fe79dbf6fc4d5b6d804a6e114b44af3ff3d40a28833ee39f21bd336b", + "sha256:af3c01e6b20f9e6c07c7d7cdb7f710e49889d3906c9a3e039546ee6636a34b9a", + "sha256:b8e1c18bb00800425576710468299153caad20c64ddb6819d40a6a34e21ee21c", + "sha256:c59293b232b53ebb47427f16cf648e937022f489cff36c11d1d8a1f0075b6609", + "sha256:d7a59cef8f2fa25d12e2c30138f8623acbd43ad2715e730a709e49c5eef8e1b0", + "sha256:dd52d8a81881804625df88453611175ab7e0099b34f52204da1f6940cf2e83c9", + "sha256:ea535f74ab6024e7416f93de564e5c81fb7c0964b96280de66f60aeb05f0cf53" + ], + "version": "==2.0.6" + }, "faker": { "hashes": [ "sha256:188961065fb5c78ea639f42176f55100f72c90c3a3179ac6c955c4bd712b0511", @@ -141,6 +213,14 @@ "index": "pypi", "version": "==4.0.0" }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, "importlib-metadata": { "hashes": [ "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", @@ -211,6 +291,120 @@ "markers": "python_version >= '3.7'", "version": "==2.1.1" }, + "more-itertools": { + "hashes": [ + "sha256:43e6dd9942dffd72661a2c4ef383ad7da1e6a3e968a927ad7a6083ab410a688b", + "sha256:7dc6ad46f05f545f900dd59e8dfb4e84a4827b97b3cfecb175ea0c7d247f6064" + ], + "markers": "python_version >= '3.5'", + "version": "==8.12.0" + }, + "murmurhash": { + "hashes": [ + "sha256:00a5252b569d3f914b5bd0bce72d2efe9c0fb91a9703556ea1b608b141c68f2d", + "sha256:1431d817e1fff1ed35f8dc54dd5b4d70165ec98076de8aca351805f8037293f3", + "sha256:2911bc3e8040dfaac536b141539b0351915f1439953f0aa9e957f082cff035a6", + "sha256:5c7b8cc4a8db1c821b80f8ca70a25c3166b14d68ecef8693a117c6a0b1d74ace", + "sha256:773411eba268bf524c012e781f4405aacb9ef4edc063d1f6b38bbf06358b988e", + "sha256:7dc5a79346afa07f14384926c335c0c455226d687d1305b9378264875b450e51", + "sha256:8de08d145c85bb7ba89cb1b591742e3ef54cede73e35f62752af687a4a1859f7", + "sha256:90a8e06872015d6f9f66a42669e003a1df8be229defef69cd98546f4cb25546d", + "sha256:92cd7196974307143ce8e9e9b6e22e0a57abf30bdd5a1effe696b4825677e616", + "sha256:9d69cc0ffc0ef6d37399b8a0484a44f9877e531ebc164e55105e89738ed52089", + "sha256:a78d53f047c3410ce4c589d9b47090f628f844ed5694418144e63cfe7f3da7e9", + "sha256:ab326b172dc470331490bda516d4d6d7578c91445ad83a2a3418ac1b9c5f9f55", + "sha256:cdd1036688341413e5adef32b3fd58e8b44f24405f394f90129f39ed879e4f24", + "sha256:de267459d040c96727ba141075d5bc983ec69c6f75b6df1b703e3b5cd7090382", + "sha256:e40790fdaf65213d70da4ed9229f16f6d6376310dc8fc23eacc98e6151c6ae7e", + "sha256:f4ef3b26229ff192032a12653d637313e1231d23e788b83a2f4a3d8e2bf2d031" + ], + "version": "==1.0.6" + }, + "numpy": { + "hashes": [ + "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676", + "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4", + "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce", + "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123", + "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1", + "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e", + "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5", + "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d", + "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a", + "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab", + "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75", + "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168", + "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4", + "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f", + "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18", + "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62", + "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe", + "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430", + "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802", + "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa" + ], + "markers": "python_version >= '3.8'", + "version": "==1.22.3" + }, + "ordered-set": { + "hashes": [ + "sha256:a7bfa858748c73b096e43db14eb23e2bc714a503f990c89fac8fab9b0ee79724" + ], + "markers": "python_version >= '2.7'", + "version": "==3.1.1" + }, + "ordered-set-stubs": { + "hashes": [ + "sha256:29274ae21ca2cfc127874042461996e748bb3c68ae7618ffd0028309554e2dff", + "sha256:4f76c7470a4e364feb51fe42c4bcd968c8ff9dab520aff9287b394e14da1d1ee" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.1.3" + }, + "plac": { + "hashes": [ + "sha256:398cb947c60c4c25e275e1f1dadf027e7096858fb260b8ece3b33bcff90d985f", + "sha256:487e553017d419f35add346c4c09707e52fa53f7e7181ce1098ca27620e9ceee" + ], + "version": "==1.1.3" + }, + "poetry-version": { + "hashes": [ + "sha256:ba259257640cd36c76375563a001b9e85c6f54d764cc56b3eed0f3b5cefb0317", + "sha256:f7d77b5c4f0761c5a5b9031f199c79c7f81d125676248053ea84e69ad79faa89" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.1.5" + }, + "preshed": { + "hashes": [ + "sha256:3af09f4cfcdaca085fd87dac8107617c4e2bb0ad1458f953841b71e9728287f5", + "sha256:58661bea8d0d63a648588511407285e43d43627e27f836e30819801fb3c75d70", + "sha256:5f99837e7353ce1fa81f0074d4b15f36e0af5af60a2a54d4d11e13cb09768a9e", + "sha256:61b2ea656cb1c38d544cc774f1c2ad1cdab23167b46b35310a7e211d4ba9c6d0", + "sha256:66a71ced487516cf81fd0431a3a843514262ae2f33e9a7688b87562258fa75d5", + "sha256:6c98f725d8478f3ade4ab1ea00f50a92d2d9406d37276bc46fd8bab1d47452c4", + "sha256:87e1add41b7f6236a3ccc34788f47ab8682bc28e8a2d369089062e274494c1a0", + "sha256:8c60a400babfc5b25ba371fda7041be227f7c625e1fb7a43329c2c08fe00a53b", + "sha256:92a8f49d17a63537a8beed48a049b62ef168ca07e0042a5b2bcdf178a1fb5d48", + "sha256:a279c138ad1d5be02547b1545254929588414b01571fe637016367f6a1aa11de", + "sha256:cfe1495fcfc7f479de840ddc4f426dbb55351e218ae5c8712c1269183a4d0060", + "sha256:e03ae3eee961106a517fcd827b5a7c51f7317236b3e665c989054ab8dc381d28", + "sha256:ea8aa9610837e907e8442e79300df0a861bfdb4dcaf026a5d9642a688ad04815", + "sha256:eaffbc71fdb8625f9aac4fe7e19e20bf318d1421ea05903bebe3e6ffef27b587", + "sha256:f92e752a868ea2690e1b38c4b775251a145e0fce36b9bdd972539e8271b7a23a", + "sha256:fb3b7588a3a0f2f2f1bf3fe403361b2b031212b73a37025aea1df7215af3772a" + ], + "version": "==3.0.6" + }, + "profanity-filter": { + "hashes": [ + "sha256:67f9baffb13cb1256dd1b717c56caea2f058ec3bfd8e40024beb7f0fff298f94", + "sha256:8f5c908ac7f14c8e3bf4f039da21dbddbc14ed2061191450b0670ad3bdc50d0b" + ], + "index": "pypi", + "version": "==1.3.3" + }, "psycopg2": { "hashes": [ "sha256:06f32425949bd5fe8f625c49f17ebb9784e1e4fe928b7cce72edc36fb68e4c0c", @@ -228,6 +422,47 @@ "index": "pypi", "version": "==2.9.3" }, + "pydantic": { + "hashes": [ + "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3", + "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398", + "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1", + "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65", + "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4", + "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16", + "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2", + "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c", + "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6", + "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce", + "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9", + "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3", + "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034", + "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c", + "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a", + "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77", + "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b", + "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6", + "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f", + "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721", + "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37", + "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032", + "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d", + "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed", + "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6", + "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054", + "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25", + "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46", + "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5", + "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c", + "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070", + "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1", + "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7", + "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d", + "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145" + ], + "markers": "python_full_version >= '3.6.1'", + "version": "==1.9.0" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -252,6 +487,45 @@ "index": "pypi", "version": "==2022.1" }, + "redis": { + "hashes": [ + "sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2", + "sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==3.5.3" + }, + "requests": { + "hashes": [ + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.27.1" + }, + "ruamel.yaml": { + "hashes": [ + "sha256:08aaaa74ff66565024ecabf9ba2db212712382a21c0458f9a91c623a1fa83b34", + "sha256:23f2efb872d2ebe3d5428b4f1a8f30cbf59f56e780c4981c155411ee65572673", + "sha256:38718e69270141c403b5fc539f774ed394568f8a5195b507991f5b690356facb", + "sha256:44da2be1153e173f90ad8775d4ac4237a3c06cfbb9660c1c1980271621833faa", + "sha256:4b1674a936cdae9735578d4fd64bcbc6cfbb77a1a8f7037a50c6e3874ba4c9d8", + "sha256:51d49c870aca850e652e2cd1c9bea9b52b77d13ad52b0556de496c1d264ea65f", + "sha256:63dc8c6147a4cf77efadf2ae0f34e89e03de79289298bb941b7ae333d5d4020b", + "sha256:6672798c6b52a976a7b24e20665055852388c83198d88029d3c76e2197ac221a", + "sha256:6b6025f9b6a557e15e9fdfda4d9af0b57cd8d59ff98e23a0097ab2d7c0540f07", + "sha256:7b750252e3d1ec5b53d03be508796c04a907060900c7d207280b7456650ebbfc", + "sha256:847177699994f9c31adf78d1ef1ff8f069ef0241e744a3ee8b30fbdaa914cc1e", + "sha256:8e42f3067a59e819935a2926e247170ed93c8f0b2ab64526f888e026854db2e4", + "sha256:922d9e483c05d9000256640026f277fcc0c2e1e9271d05acada8e6cfb4c8b721", + "sha256:92a8ca79f9173cca29ca9663b49d9c936aefc4c8a76f39318b0218c8f3626438", + "sha256:ab8eeca4de4decf0d0a42cb6949d354da9fc70a2d9201f0dd55186c599b2e3a5", + "sha256:bd4b60b649f4a81086f70cd56eff4722018ef36a28094c396f1a53bf450bd579", + "sha256:fc6471ef15b69e454cca82433ac5f84929d9f3e2d72b9e54b06850b6b7133cc0", + "sha256:ffc89770339191acbe5a15041950b5ad9daec7d659619b0ed9dad8c9c80c26f3" + ], + "version": "==0.15.100" + }, "setuptools": { "hashes": [ "sha256:8f4813dd6a4d6cc17bde85fb2e635fe19763f96efbb0ddf5575562e5ee0bc47a", @@ -268,6 +542,25 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, + "spacy": { + "hashes": [ + "sha256:0416e708c0b672e79400aeade070d2ec91052ca9942cc378ee9f833a3ae9b6ed", + "sha256:2d454e4d08c8263ab0bb4e63445e9565c5924f588dcf26bcb0add3a4d98a6042", + "sha256:34570a7571d8bf08c696003423589b750ac684c0b79f3e3672e3d941a44f2452", + "sha256:4942670ee16e153ddb6d5ae85eb03c39c33a2ef19b9ea0423d28f63536e21d72", + "sha256:65ecac35b9812f146d99d91610d38f9b8d849a8164fccf0fdcfd4cf7e2826618", + "sha256:6ebc5a7f7da70a793cc1d2569097bdf80fbce66d7e39e17a157f95c258bec78d", + "sha256:96eaa20d6074158b5686740a40ba78aac4f52759f8acec8af9db4d586bc49f1f", + "sha256:abddbf424233a842aec91119bc8c4578890c587809d33f353f334a5383d8f490", + "sha256:b4f1a02d62e861a044b8fbe8a0ce89e49d5a63c3a5bfc5849cb1d4f0247b8ab9", + "sha256:c0f2315fea23497662e28212f89af3a03667f97c867c597b599c37ab84092e22", + "sha256:c774d5cd869c0d086fd092eeacbb72296c8d470dda73bc45ed9f9f9ca5822759", + "sha256:c831bcec3f3c0b425f753248763c838b57f31edfca2dfcafce205f6f2b548d7e", + "sha256:fc59c5cf8f363d7c1dfd779bae7eafe99dc98a74063267f9f8df4154b65f7a2f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.3.7" + }, "sqlalchemy": { "hashes": [ "sha256:04164e0063feb7aedd9d073db0fd496edb244be40d46ea1f0d8990815e4b8c34", @@ -309,6 +602,81 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==1.4.32" }, + "srsly": { + "hashes": [ + "sha256:11447f8e659e1f62f29302252fb057f179031457b36c83426027182f624fe565", + "sha256:23c7205b8c1cac49a03521bee37f0afe3680d9f0ec18c75ab3ac39bd3e15272b", + "sha256:2615b8713dfe793ca57925076b0869385d56754816b1eaee5490a6827a1cb5c7", + "sha256:334f29435099e644a8047b63d60b8386a98b5f7b4739f7efc86b46ca0200aa0e", + "sha256:4c43a1f28e555891a1e65650adea2c5d0f0fe4b3d63821de65c8357f32c3a11c", + "sha256:779ebfaa3cf1d5c0f1286ac1baf06af5f2a17bb103622992c71acc6ac20b2781", + "sha256:8fc4c0641537262e15c7b5b57edc47487b15ac47b696adcb81e0a770ef78e8f5", + "sha256:a1449da4195e30a3bd1fd3122e5b1a0c57703843c590643555c412fc87132aa0", + "sha256:a2746afccfd4f51f0793cccc2b6d5e8a564c962870feec5c77408244c1dbb3c5", + "sha256:a696e9c925e91f76ec53840c55483a4fbf76cb717424410a4f249d4805439038", + "sha256:b5b887328ac6e210842560fcf32a29c2a9c1ed38c6d47479cadc03d81940da8c", + "sha256:d3dd796372367c71946d0cd6f734e49db3d99dd13a57bdac937d9eb62689fc9e", + "sha256:fd5e1e01f5fd0f532a6f3977bb74facc42f1b7155402ee3d06c07a73e83e3c47" + ], + "version": "==1.0.5" + }, + "thinc": { + "hashes": [ + "sha256:02b71ae5a0fa906a0aca968bd65589e0ab9fabd511e57be839774228b1509224", + "sha256:10bafe5ddce698180098345b9c55f762dc3456558be844d35d64175e511581b6", + "sha256:24086aa0fb72f466782115d529574a825c89afa62eb817962b9339f61ab50e0d", + "sha256:29a47ad0289dda0520b5af8538b30e8134553130200b83c34311feb71739968d", + "sha256:309ec4cae81f4de2e4e4fbd0bcb52b10bef4b1a6352c6a9143f6a53d3b1060ef", + "sha256:5743fde41706252ec6ce4737c68d3505f7e1cc3d4431174a17149838d594f8cb", + "sha256:5774007b5c52501cab5e2970cadca84923b4c420fff06172f2d0c86531973ce8", + "sha256:8b647de79fe5f98cd327983bf0e27d006b48ad9694ceabdb9a3832b614ed1618", + "sha256:c408ab24b24e6368ce4b6ddebb579118042a22d3f2f2c4e19ca67e3eadc9ed33", + "sha256:c43ed753aa70bc619e42e168be4926c8a47799af6121ff0727ba99b330afbb44", + "sha256:cce68c5ea54cd32cef661858363509afdedad047027e8cdf0dc4edec0c2cc010", + "sha256:d01ab1480d37ebefcac22d63ffe01916c9f025ae3dbdbe5824ac3ea5cce8e3fd", + "sha256:fae320de65af70786c1526ffc33b88f2da650d3106f5f9a06b37f0ac3944a44f" + ], + "version": "==7.4.5" + }, + "tomlkit": { + "hashes": [ + "sha256:4e1bd6c9197d984528f9ff0cc9db667c317d8881288db50db20eeeb0f6b0380b", + "sha256:f044eda25647882e5ef22b43a1688fb6ab12af2fc50e8456cdfc751c873101cf" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.5.11" + }, + "tqdm": { + "hashes": [ + "sha256:4230a49119a416c88cc47d0d2d32d5d90f1a282d5e497d49801950704e49863d", + "sha256:6461b009d6792008d0000e1b0c7ca50195ec78c0e808a3a6b668a56a3236c3a5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==4.63.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42", + "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2" + ], + "markers": "python_version >= '3.6'", + "version": "==4.1.1" + }, + "urllib3": { + "hashes": [ + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.9" + }, + "wasabi": { + "hashes": [ + "sha256:152245d892030a3a7b511038e9472acff6d0e237cfe4123fef0d147f2d3274fc", + "sha256:f40f317981d019903db5b69eb2bf78519c9e165c1dfdbd0452e4ca81ff9a31d2" + ], + "version": "==0.9.0" + }, "werkzeug": { "hashes": [ "sha256:1421ebfc7648a39a5c58c601b154165d05cf47a3cd0ccb70857cbdacf6c8f2b8", diff --git a/forms.py b/forms.py index b41156e..564199b 100644 --- a/forms.py +++ b/forms.py @@ -1,10 +1,12 @@ from flask import Blueprint, flash, redirect, request, url_for from flask_login import current_user, login_required +from profanity_filter import ProfanityFilter from database import db from models import User, Post, Comment blueprint = Blueprint('forms', __name__) +pf = ProfanityFilter() @blueprint.route('/user//edit', methods=['POST']) @@ -36,6 +38,10 @@ def new_post(): flash('Cannot have more than 1000 characters of text.') return redirect(url_for('main.feed')) + if not pf.is_clean(post_text): + flash('Sorry, profanity is not allowed on runnerspace.') + return redirect(url_for('main.feed')) + post = Post(author=current_user.id, text=post_text) db.session.add(post) db.session.commit() @@ -53,6 +59,13 @@ def add_comment(post_id: int): if len(comment_text) > 50: flash('Cannot have more than 50 characters of text.') return redirect(url_for('main.view_post', post_id=post_id)) + elif len(comment_text) < 5: + flash('Your comment must have at least 5 characters of text.') + return redirect(url_for('main.view_post', post_id=post_id)) + + if not pf.is_clean(comment_text): + flash('Sorry, profanity is not allowed on runnerspace.') + return redirect(url_for('main.view_post', post_id=post_id)) comment = Comment(post=post.id, author=current_user.id, text=comment_text) db.session.add(comment) From 040e505561eba2542badbac7a43d4d5d2b183f73 Mon Sep 17 00:00:00 2001 From: Xevion Date: Mon, 28 Mar 2022 18:54:17 -0500 Subject: [PATCH 03/33] Improve/add error messages to look better visually - Have not tested everywhere, but should work okayish --- static/styles.css | 15 +++++++-- static/styles.css.map | 2 +- static/styles.scss | 18 ++++++++-- templates/pages/auth/login.html | 56 +++++++++++++++---------------- templates/pages/auth/signup.html | 57 ++++++++++++++++---------------- templates/pages/feed.html | 16 ++++----- templates/pages/post.html | 8 +++++ templates/pages/user_edit.html | 4 +-- 8 files changed, 105 insertions(+), 71 deletions(-) diff --git a/static/styles.css b/static/styles.css index f7a5d3b..2f63e2b 100644 --- a/static/styles.css +++ b/static/styles.css @@ -124,12 +124,12 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after } form { - margin: 0 auto; + margin: 1em auto; width: fit-content; } form.login-form { border: 1px solid darkblue; - padding: 10px; + padding: 0.7em; } form .field { padding: 4px; @@ -262,6 +262,7 @@ form button { } .post-box .post-author { margin-top: 1em; + margin-bottom: 0.5em; font-size: 0.8em; border-bottom: 1px solid grey; padding-bottom: 0.8em; @@ -312,4 +313,14 @@ form button { margin: 1em; } +span.error-message { + margin: 0.5em; + border: 1px solid red; + padding: 0.3em; +} +span.error-message.center-message { + margin: 0 auto; + display: table; +} + /*# sourceMappingURL=styles.css.map */ diff --git a/static/styles.css.map b/static/styles.css.map index 9bcc18c..d32f6c1 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AACA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;;AACA;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAGF;EACE;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 5190556..be2ad22 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -59,6 +59,7 @@ body, html { #img-logo { height: 100%; box-sizing: border-box; + img { max-width: 100%; max-height: 80px; @@ -145,12 +146,12 @@ nav, footer { } form { - margin: 0 auto; + margin: 1em auto; width: fit-content; &.login-form { border: 1px solid darkblue; - padding: 10px + padding: 0.7em; } .field { @@ -308,6 +309,7 @@ form { .post-author { margin-top: 1em; + margin-bottom: 0.5em; font-size: 0.8em; border-bottom: 1px solid grey; padding-bottom: 0.8em; @@ -350,6 +352,7 @@ form { .new-users, .statistics { margin-top: 0 !important; + > div { border: 1px solid grey; padding: 0.2em; @@ -366,3 +369,14 @@ form { min-height: 235px; margin: 1em; } + +span.error-message { + margin: 0.5em; + border: 1px solid red; + padding: 0.3em; + + &.center-message { + margin: 0 auto; + display: table; + } +} diff --git a/templates/pages/auth/login.html b/templates/pages/auth/login.html index 2f1e799..dadc103 100644 --- a/templates/pages/auth/login.html +++ b/templates/pages/auth/login.html @@ -1,35 +1,35 @@ {% extends 'layouts/index.html' %} {% block content %} - {% with messages = get_flashed_messages() %} - {% if messages %} -
+ {% with messages = get_flashed_messages() %} + {% if messages %} + {{ messages[0] }} -
- {% endif %} - {% endwith %} - From 987e4f32566420926a95f793b69900d1144e3866 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 20:49:12 -0500 Subject: [PATCH 18/33] Remove unused Post.likes - As well as related methods - In preparation for PostLikes and CommentLikes model implementations --- models.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/models.py b/models.py index ffdbd95..379a2c9 100644 --- a/models.py +++ b/models.py @@ -47,24 +47,8 @@ class Post(db.Model): text = db.Column(db.Text) date_posted = db.Column(db.DateTime, server_default=func.now()) date_updated = db.Column(db.DateTime, nullable=True) - likes = db.Column(db.Text, default='[]') comments = db.relationship("Comment", backref='post') - def get_likes(self) -> List[int]: - """Return the IDs of the Users who have liked this post.""" - return json.loads(self.likes) - - def set_likes(self, likes: List[int]) -> None: - """Set the likes c""" - self.likes = list(dict.fromkeys(json.dumps(likes))) - self.save() - - def add_like(self, user_id: int) -> None: - likes: List[int] = self.get_likes() - if user_id not in likes: - likes.append(user_id) - self.set_likes(likes) - def get_time_ago(self) -> str: delta: datetime.timedelta = datetime.datetime.utcnow() - self.date_posted return humanize.naturaldelta(delta) From 2cb42bbc8b52f367df9e421aa98627cf32ab34db Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 20:57:13 -0500 Subject: [PATCH 19/33] Fix profile editing form not coming pre-populated with data Not sure why Form.populate_obj() wasn't working - looking at the source and the description as well as relevant StackOverflow answers, that method would apparently work just fine. Looking again, Form.process() was recommended instead. Also, I finally changed PyCharm to show me proper commit message standards - so, be happy that things will look nice now. :) --- routes.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/routes.py b/routes.py index 5cafb39..852ad88 100644 --- a/routes.py +++ b/routes.py @@ -73,17 +73,18 @@ def edit_user(username: str): user = db.session.query(User).filter_by(username=username).first_or_404() form = EditProfileForm(request.form) - if request.method == 'POST': - if form.validate(): - if current_user.is_admin or current_user == user: - user.about_me = form.about_me.data - user.name = form.name.data + # Check that a form was submitted + if form.validate_on_submit(): + # Check that the user submitting the form is allowed to do this + if current_user.is_admin or current_user == user: + user.about_me = form.about_me.data + user.name = form.name.data - db.session.commit() - return redirect(url_for('main.view_user', username=username)) + db.session.commit() + return redirect(url_for('main.view_user', username=username)) return render_template('pages/user_edit.html', form=form) - form.populate_obj(user) + form.process(obj=user) return render_template('pages/user_edit.html', form=form) From 66715ecc72663b37ec9bd372346a19feb50db9a9 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 21:14:42 -0500 Subject: [PATCH 20/33] Improve edit profile and add comment styling --- static/styles.css | 30 ++++++++++++++++++++++++++++ static/styles.css.map | 2 +- static/styles.scss | 36 ++++++++++++++++++++++++++++++++++ templates/pages/post.html | 3 +-- templates/pages/user_edit.html | 2 +- 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/static/styles.css b/static/styles.css index 5dc4646..88e0b58 100644 --- a/static/styles.css +++ b/static/styles.css @@ -229,6 +229,13 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after .form.post-form { border: none; } +.form.edit-profile-form { + white-space: normal; +} +.form.edit-profile-form label { + min-width: 5em; + font-weight: 600; +} .form button { margin: 0.3em auto; text-align: center; @@ -269,6 +276,29 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after .post-box .post-comments .post-comment.add-comment { border: 0; } +.post-box .post-comments .post-comment.add-comment form { + border: 0; + padding: 0; +} +.post-box .post-comments .post-comment.add-comment form input { + font-size: 0.8rem; + margin: 0; + padding: 3px; + border: #ccc 1px solid; + line-height: 1.3; +} +.post-box .post-comments .post-comment.add-comment form input[type=submit] { + width: auto; + border-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.post-box .post-comments .post-comment.add-comment form input[type=text] { + width: 13em; + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} .post-box .post-comments .post-comment.add-comment button { margin-left: 0.8em; width: fit-content; diff --git a/static/styles.css.map b/static/styles.css.map index 9cc537c..a673361 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 0823544..8f2ef40 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -271,6 +271,15 @@ nav, footer { border: none; } + &.edit-profile-form { + white-space: normal; + + label { + min-width: 5em; + font-weight: 600; + } + } + button { margin: 0.3em auto; text-align: center; @@ -315,6 +324,33 @@ nav, footer { &.add-comment { border: 0; + form { + border: 0; + padding: 0; + + input { + font-size: 0.8rem; + margin: 0; + padding: 3px; + border: #ccc 1px solid;; + line-height: 1.3; + + &[type=submit] { + width: auto; + border-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + &[type=text] { + width: 13em; + border-right: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + } + button { margin-left: 0.8em; width: fit-content; diff --git a/templates/pages/post.html b/templates/pages/post.html index 2867562..2b1fac6 100644 --- a/templates/pages/post.html +++ b/templates/pages/post.html @@ -12,8 +12,7 @@
{{ form.csrf_token }} - {{ form.text }} - + {{ form.text }}
{% endif %} diff --git a/templates/pages/user_edit.html b/templates/pages/user_edit.html index 14895c5..509fad9 100644 --- a/templates/pages/user_edit.html +++ b/templates/pages/user_edit.html @@ -3,7 +3,7 @@ {% block content %}

Edit Profile

-
+ {{ form.csrf_token }} {{ render_field(form.name) }} {{ render_field(form.about_me) }} From 58861acd3d5ef4f4b171bf305ff9831aeb41277d Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 21:16:12 -0500 Subject: [PATCH 21/33] Change minimum comment length to 1 character MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comments like "yes", "no" or 😈 wouldn't be available to post either. --- forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms.py b/forms.py index 66c3d12..d1fb1be 100644 --- a/forms.py +++ b/forms.py @@ -31,4 +31,4 @@ class NewPostForm(FlaskForm): class NewCommentForm(FlaskForm): - text = StringField('Text', [validators.Length(min=5, max=50), NoProfanity()]) + text = StringField('Text', [validators.Length(min=1, max=50), NoProfanity()]) From 1a323a8349381563ddd1a31b3c88a6e84253197a Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 22:45:59 -0500 Subject: [PATCH 22/33] Add PostLike and CommentLike models - Also adjusted some User methods and added ones I believe may be wanted or needed in time. - Adjusted online time delta back to 3 minutes. - Started looking into how to sum 'likes' efficiently on a user's posts. --- models.py | 52 ++++++++++++++++++++++++++++++++++--- templates/pages/browse.html | 2 +- templates/pages/user.html | 2 +- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/models.py b/models.py index 379a2c9..c6cbc71 100644 --- a/models.py +++ b/models.py @@ -8,7 +8,8 @@ from sqlalchemy import func from database import db -MAXIMUM_ONLINE_DELTA = datetime.timedelta(minutes=1) +# Amount of time before a user is considered 'offline' +MAXIMUM_ONLINE_DELTA = datetime.timedelta(minutes=3) class User(UserMixin, db.Model): @@ -23,19 +24,48 @@ class User(UserMixin, db.Model): is_admin = db.Column(db.Boolean, default=False) posts = db.relationship("Post", backref='author') comments = db.relationship("Comment", backref='author') + posts_liked = db.relationship("PostLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') + comments_liked = db.relationship("CommentLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') - def get_last_seen(self) -> str: + def get_last_seen_text(self) -> str: delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen if delta > MAXIMUM_ONLINE_DELTA: return f'Last seen {humanize.naturaldelta(delta)} ago' return 'Online now!' + def is_online(self) -> bool: + """Returns true if the user has used the website in the time delta specified.""" + delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen + return delta < MAXIMUM_ONLINE_DELTA + + def is_offline(self) -> bool: + """Returns true if the user has not used the website in the time delta specified.""" + return not self.is_online() + def get_registration_delta(self) -> str: + """Returns a string describing how long ago the user registered.""" delta: datetime.timedelta = datetime.datetime.utcnow() - self.time_registered return humanize.naturaldelta(delta) def get_post_count(self) -> int: - return len(self.posts) + """Returns the number of posts this user has made.""" + return Post.query.filter_by(user_id=self.id).count() + + def get_comment_count(self) -> int: + """Returns the number of comments this user has made.""" + return Comment.query.filter_by(user_id=self.id).count() + + # def get_post_likes(self) -> int: + # """Returns the number of likes this user's posts have accumulated.""" + # return PostLike.query.filter_by().count() + # + # def get_comment_likes(self) -> int: + # """Returns the number of likes this user's comment shave accumulated""" + # return CommentLike.query(func.sum()).scalar() + + # def get_all_likes(self) -> int: + # """Returns the number of likes this user's posts and comments have accumulated""" + # return self.get_post_likes() + self.get_comment_likes() def display_about(self) -> str: return self.about_me or "This user hasn't written a bio yet." @@ -48,15 +78,31 @@ class Post(db.Model): date_posted = db.Column(db.DateTime, server_default=func.now()) date_updated = db.Column(db.DateTime, nullable=True) comments = db.relationship("Comment", backref='post') + liked_by = db.relationship("PostLike", backref=db.backref('post', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') def get_time_ago(self) -> str: delta: datetime.timedelta = datetime.datetime.utcnow() - self.date_posted return humanize.naturaldelta(delta) +class PostLike(db.Model): + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, server_default=func.now()) + post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + + class Comment(db.Model): id = db.Column(db.Integer, primary_key=True) text = db.Column(db.Text, nullable=False) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False) date_posted = db.Column(db.DateTime, server_default=func.now()) + liked_by = db.relationship("CommentLike", backref=db.backref('comment', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') + + +class CommentLike(db.Model): + id = db.Column(db.Integer, primary_key=True) + timestamp = db.Column(db.DateTime, server_default=func.now()) + comment_id = db.Column(db.Integer, db.ForeignKey('comment.id'), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) diff --git a/templates/pages/browse.html b/templates/pages/browse.html index 495adf4..169b3dc 100644 --- a/templates/pages/browse.html +++ b/templates/pages/browse.html @@ -11,7 +11,7 @@ {{ user.name }} - {% with seen_text = user.get_last_seen() %} + {% with seen_text = user.get_last_seen_text() %} {% if seen_text == 'Online now!' %} {{ seen_text }} {% else %} diff --git a/templates/pages/user.html b/templates/pages/user.html index ae7fe3e..792fdad 100644 --- a/templates/pages/user.html +++ b/templates/pages/user.html @@ -6,7 +6,7 @@ {% if current_user.is_admin or current_user == user %} {% endif %} - {% with seen_text = user.get_last_seen() %} + {% with seen_text = user.get_last_seen_text() %} {% if seen_text == 'Online now!' %} {{ seen_text }} {% else %} From cf8a754caf977637b3927e175a43a41013670b94 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 23:23:48 -0500 Subject: [PATCH 23/33] Implement inefficient like summation methods --- models.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/models.py b/models.py index c6cbc71..bdc7b9d 100644 --- a/models.py +++ b/models.py @@ -55,17 +55,17 @@ class User(UserMixin, db.Model): """Returns the number of comments this user has made.""" return Comment.query.filter_by(user_id=self.id).count() - # def get_post_likes(self) -> int: - # """Returns the number of likes this user's posts have accumulated.""" - # return PostLike.query.filter_by().count() - # - # def get_comment_likes(self) -> int: - # """Returns the number of likes this user's comment shave accumulated""" - # return CommentLike.query(func.sum()).scalar() + def get_post_likes(self) -> int: + """Returns the number of likes this user's posts have accumulated.""" + return sum(PostLike.query.filter_by(post=post).count() for post in self.posts) - # def get_all_likes(self) -> int: - # """Returns the number of likes this user's posts and comments have accumulated""" - # return self.get_post_likes() + self.get_comment_likes() + def get_comment_likes(self) -> int: + """Returns the number of likes this user's comment shave accumulated""" + return sum(CommentLike.query.filter_by(comment=comment).count() for comment in self.comments) + + def get_all_likes(self) -> int: + """Returns the number of likes this user's posts and comments have accumulated""" + return self.get_post_likes() + self.get_comment_likes() def display_about(self) -> str: return self.about_me or "This user hasn't written a bio yet." From 6b4d5acfb977c87555d2a416591b7571d9f67a48 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 23:34:31 -0500 Subject: [PATCH 24/33] Move highly-static routes in separate blueprint --- app.py | 3 +++ routes.py | 19 ------------------- static_routes.py | 23 +++++++++++++++++++++++ templates/layouts/footer.html | 8 ++++---- templates/layouts/header.html | 2 +- 5 files changed, 31 insertions(+), 24 deletions(-) create mode 100644 static_routes.py diff --git a/app.py b/app.py index d3dca17..f4416ec 100644 --- a/app.py +++ b/app.py @@ -51,6 +51,9 @@ def create_app(): from route_forms import blueprint as forms_blueprint app.register_blueprint(forms_blueprint) + from static_routes import blueprint as static_blueprint + app.register_blueprint(static_blueprint) + @app.errorhandler(404) def page_not_found(e): # note that we set the 404 status explicitly diff --git a/routes.py b/routes.py index 852ad88..b66af4d 100644 --- a/routes.py +++ b/routes.py @@ -19,11 +19,6 @@ def index(): # put application's code here return render_template('layouts/index.html', new_users=users, stats=stats) -@blueprint.route('/about') -def about(): - return render_template('pages/about.html') - - @blueprint.route('/users') def browse(): users = User.query.all() @@ -87,20 +82,6 @@ def edit_user(username: str): form.process(obj=user) return render_template('pages/user_edit.html', form=form) - -@blueprint.route('/terms_of_service') -def tos(): - return render_template('static/tos.html') - - -@blueprint.route('/privacy') -def privacy(): - return render_template('static/privacy.html') - - -@blueprint.route('/license') -def license(): - return render_template('static/license.html') # @blueprint.route('/blogs') # def blogs(): # return render_template('pages/blogs.html') diff --git a/static_routes.py b/static_routes.py new file mode 100644 index 0000000..5f00b01 --- /dev/null +++ b/static_routes.py @@ -0,0 +1,23 @@ +from flask import Blueprint, redirect, render_template, url_for, request + +blueprint = Blueprint('static', __name__) + + +@blueprint.route('/about') +def about(): + return render_template('pages/about.html') + + +@blueprint.route('/terms_of_service') +def tos(): + return render_template('static/tos.html') + + +@blueprint.route('/privacy') +def privacy(): + return render_template('static/privacy.html') + + +@blueprint.route('/license') +def license(): + return render_template('static/license.html') diff --git a/templates/layouts/footer.html b/templates/layouts/footer.html index c5e100f..e7b3bf9 100644 --- a/templates/layouts/footer.html +++ b/templates/layouts/footer.html @@ -3,10 +3,10 @@ created by Ryan Walters and Zachary Seligman

  • My Messages
  • #} {#
  • Blog
  • #} {#
  • Groups
  • #} -
  • About
  • +
  • About
  • From 2a443979b9ca40c0ecbfd6d8dce233f569178b37 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 29 Mar 2022 23:40:02 -0500 Subject: [PATCH 25/33] Hide "New Users" box when no users are in database - Also improved CSS organization - Use space-around flex to center stats box - Capitalized 'runnerspace' --- static/styles.css | 13 ++++++++----- static/styles.css.map | 2 +- static/styles.scss | 30 +++++++++++++++++------------- templates/layouts/index.html | 28 +++++++++++++++------------- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/static/styles.css b/static/styles.css index 88e0b58..3a50c1c 100644 --- a/static/styles.css +++ b/static/styles.css @@ -309,20 +309,23 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after text-decoration: none; } -.new-users, .statistics { +.index-flex { + display: flex; + justify-content: space-around; +} +.index-flex .new-users, .index-flex .statistics { margin-top: 0 !important; } -.new-users > div, .statistics > div { +.index-flex .new-users > div, .index-flex .statistics > div { border: 1px solid grey; padding: 0.2em; width: fit-content; padding-right: 1.5em; } -.new-users > div a, .statistics > div a { +.index-flex .new-users > div a, .index-flex .statistics > div a { text-decoration: none; } - -.statistics > div { +.index-flex .statistics > div { min-height: 235px; margin: 1em; } diff --git a/static/styles.css.map b/static/styles.css.map index a673361..abf669c 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAKN;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 8f2ef40..4e307fb 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -367,24 +367,28 @@ nav, footer { } } -.new-users, .statistics { - margin-top: 0 !important; +.index-flex { + display: flex; + justify-content: space-around; + .new-users, .statistics { + margin-top: 0 !important; - > div { - border: 1px solid grey; - padding: 0.2em; - width: fit-content; - padding-right: 1.5em; + > div { + border: 1px solid grey; + padding: 0.2em; + width: fit-content; + padding-right: 1.5em; - a { - text-decoration: none; + a { + text-decoration: none; + } } } -} -.statistics > div { - min-height: 235px; - margin: 1em; + .statistics > div { + min-height: 235px; + margin: 1em; + } } span.error-message { diff --git a/templates/layouts/index.html b/templates/layouts/index.html index 57c1bc2..88d3602 100644 --- a/templates/layouts/index.html +++ b/templates/layouts/index.html @@ -1,21 +1,23 @@ {% extends 'layouts/base.html' %} {% block content %} -
    -
    -

    New Users

    -
    -
      - {% for new_user in new_users %} -
    • {{ new_user.username }} as - of {{ new_user.get_registration_delta() }} ago -
    • - {% endfor %} -
    +
    + {% if new_users|length > 0 %} +
    +

    New Users

    +
    +
      + {% for new_user in new_users %} +
    • {{ new_user.username }} as + of {{ new_user.get_registration_delta() }} ago +
    • + {% endfor %} +
    +
    -
    + {% endif %}
    -

    runnerspace Statistics

    +

    Runnerspace Statistics

    {% with comments = stats['total_comments'], posts = stats['total_posts'], users = stats['total_users'] %}
    From bfb69621e152e3dd4139f3e7ad579976c72868fe Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 00:21:55 -0500 Subject: [PATCH 26/33] Add like querying/checking/rendering to Feed posts - Change runnerspace.live to Runnerspace - Change post viewing URL to say /post/:id: instead of /feed/:id: --- models.py | 28 ++++++++++++++++++++++++++++ routes.py | 16 +--------------- static/styles.css | 10 ++++++++++ static/styles.css.map | 2 +- static/styles.scss | 11 +++++++++++ templates/layouts/footer.html | 2 +- templates/pages/feed.html | 4 +++- 7 files changed, 55 insertions(+), 18 deletions(-) diff --git a/models.py b/models.py index bdc7b9d..472da85 100644 --- a/models.py +++ b/models.py @@ -3,6 +3,7 @@ import json from typing import List import humanize +from flask import url_for from flask_login import UserMixin from sqlalchemy import func @@ -27,6 +28,9 @@ class User(UserMixin, db.Model): posts_liked = db.relationship("PostLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') comments_liked = db.relationship("CommentLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') + def get_url(self) -> str: + return url_for('main.view_user', username=self.username) + def get_last_seen_text(self) -> str: delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen if delta > MAXIMUM_ONLINE_DELTA: @@ -70,6 +74,14 @@ class User(UserMixin, db.Model): def display_about(self) -> str: return self.about_me or "This user hasn't written a bio yet." + def has_liked_post(self, post_id: int) -> bool: + """Check whether a user has liked a given post.""" + return db.session.query(PostLike.id).filter_by(post_id=post_id, user_id=self.id).first() is not None + + def has_liked_comment(self, comment_id: int) -> bool: + """Check whether a user has liked a given post.""" + return db.session.query(CommentLike.id).filter_by(comment_id=comment_id, user_id=user_id).first() is not None + class Post(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -84,6 +96,22 @@ class Post(db.Model): delta: datetime.timedelta = datetime.datetime.utcnow() - self.date_posted return humanize.naturaldelta(delta) + def get_like_count(self) -> bool: + return PostLike.query.filter_by(post_id=self.id).count() + + def get_like_text(self) -> str: + like_count = self.get_like_count() + top_likes = PostLike.query.filter_by(post_id=self.id).order_by(PostLike.timestamp.asc()).limit(3) + users = [like.user for like in top_likes] + names = [f'{user.name}' for user in users] + + if like_count >= 3: format_string = '{0}, {1} and {2} has liked this post.' + elif like_count == 2: format_string = '{0} and {1} has liked this post.' + elif like_count == 1: format_string = '{0} has liked this post.' + else: format_string = '0 likes' + + return format_string.format(*names) + class PostLike(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/routes.py b/routes.py index b66af4d..67f71fb 100644 --- a/routes.py +++ b/routes.py @@ -40,17 +40,12 @@ def feed(): return render_template('pages/feed.html', posts=posts, form=form) -@blueprint.route('/feed/') +@blueprint.route('/post/') def view_post(post_id: int): post = Post.query.get_or_404(post_id) return render_template('pages/post.html', form=NewCommentForm(), post=post) -# @blueprint.route('/messages') -# def messages(): -# return render_template('pages/messages.html') - - @blueprint.route('/search') def search(): return render_template('pages/search.html') @@ -81,12 +76,3 @@ def edit_user(username: str): form.process(obj=user) return render_template('pages/user_edit.html', form=form) - -# @blueprint.route('/blogs') -# def blogs(): -# return render_template('pages/blogs.html') -# -# -# @blueprint.route('/groups') -# def groups(): -# return render_template('pages/groups.html') diff --git a/static/styles.css b/static/styles.css index 3a50c1c..e141901 100644 --- a/static/styles.css +++ b/static/styles.css @@ -250,6 +250,16 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after padding: 1.5em; padding-bottom: 0.8em; margin: 0.45em; + position: relative; +} +.post-box .fa-heart { + position: absolute; + top: 1em; + right: 1em; + color: #b0c9f3; +} +.post-box .fa-heart.liked { + color: #1b53a8; } .post-box .post-author { margin-top: 1em; diff --git a/static/styles.css.map b/static/styles.css.map index abf669c..0731870 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 4e307fb..314d0b2 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -296,6 +296,17 @@ nav, footer { padding: 1.5em; padding-bottom: 0.8em; margin: 0.45em; + position: relative; + + .fa-heart { + position: absolute; + top: 1em; + right: 1em; + color: #b0c9f3; + &.liked { + color: #1b53a8; + } + } .post-author { margin-top: 1em; diff --git a/templates/layouts/footer.html b/templates/layouts/footer.html index e7b3bf9..1da8218 100644 --- a/templates/layouts/footer.html +++ b/templates/layouts/footer.html @@ -9,7 +9,7 @@
  • About
  • diff --git a/templates/pages/feed.html b/templates/pages/feed.html index e350b4b..d37bf04 100644 --- a/templates/pages/feed.html +++ b/templates/pages/feed.html @@ -10,16 +10,18 @@ {% endif %} -
    +
    {% for post in posts %}
    + {{ post.text }}
    From c5c3b01dfa3e84c969c284b15ba31f5b488df1b0 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 01:18:42 -0500 Subject: [PATCH 27/33] Use jQuery to allow users to like/unlike posts with dynamic updates - Only the pages that need jQuery and the likes.js script will load it --- routes.py | 25 +++++++++++++++++++++++-- static/likes.js | 19 +++++++++++++++++++ static/styles.css | 1 + static/styles.css.map | 2 +- static/styles.scss | 1 + templates/layouts/base.html | 19 +++++++++++++++++++ templates/pages/feed.html | 8 +++++--- 7 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 static/likes.js diff --git a/routes.py b/routes.py index 67f71fb..0d36540 100644 --- a/routes.py +++ b/routes.py @@ -1,7 +1,7 @@ -from flask import Blueprint, redirect, render_template, url_for, request +from flask import Blueprint, redirect, render_template, url_for, request, jsonify from flask_login import current_user, login_required -from models import User, Post, Comment +from models import User, Post, Comment, PostLike, CommentLike from forms import NewPostForm, NewCommentForm, EditProfileForm from database import db @@ -46,6 +46,27 @@ def view_post(post_id: int): return render_template('pages/post.html', form=NewCommentForm(), post=post) +@blueprint.route('/post//like', methods=['POST']) +@login_required +def like_post(post_id: int): + # Check that the relevant post exists + post = db.session.query(Post).get_or_404(post_id) + + # Acquire the relevant PostLike in question + post_like = db.session.query(PostLike).filter_by(post=post, user=current_user).first() + if post_like is None: + post_like = PostLike(post=post, user=current_user) + db.session.add(post_like) + else: + db.session.delete(post_like) + post_like = None + + db.session.commit() + + # post_like is only NOT None if the user had not liked it before, but has liked it now after db.commit(). + return jsonify({'liked': post_like is not None, 'status_text': post.get_like_text()}) + + @blueprint.route('/search') def search(): return render_template('pages/search.html') diff --git a/static/likes.js b/static/likes.js new file mode 100644 index 0000000..7fef968 --- /dev/null +++ b/static/likes.js @@ -0,0 +1,19 @@ +function like(id) { + $.ajax({url: `/post/${id}/like`, method: "POST", dataType: "json"}) + .done(function (data) { + let post_parent = $(`#post-${id}`) + let heart_icon = post_parent.find('.fa-heart') + let pre_liked = heart_icon.hasClass('liked') + + // Toggle if the current state no longer matches the database state. + if (pre_liked !== data.liked) { + if (pre_liked) + heart_icon.removeClass('liked') + else + heart_icon.addClass('liked') + } + + // Set new state of the like status text + post_parent.find('.post-like-status').html(data.status_text) + }) +} diff --git a/static/styles.css b/static/styles.css index e141901..60d333b 100644 --- a/static/styles.css +++ b/static/styles.css @@ -257,6 +257,7 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after top: 1em; right: 1em; color: #b0c9f3; + cursor: pointer; } .post-box .fa-heart.liked { color: #1b53a8; diff --git a/static/styles.css.map b/static/styles.css.map index 0731870..4112a86 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 314d0b2..27cdbe9 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -303,6 +303,7 @@ nav, footer { top: 1em; right: 1em; color: #b0c9f3; + cursor: pointer; &.liked { color: #1b53a8; } diff --git a/templates/layouts/base.html b/templates/layouts/base.html index c30633e..b93188b 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -9,6 +9,25 @@ + {% if use_jquery %} + + + {% if use_likes %} + + {% endif %} + {% endif %} {% endblock %} diff --git a/templates/pages/feed.html b/templates/pages/feed.html index d37bf04..41e9382 100644 --- a/templates/pages/feed.html +++ b/templates/pages/feed.html @@ -1,5 +1,7 @@ {% extends 'layouts/index.html' %} {% from 'macros.html' import render_field %} +{% set use_jquery = true %} +{% set use_likes = true %} {% block content %} {% if current_user.is_authenticated %} @@ -13,15 +15,15 @@
    {% for post in posts %} -
    - +
    + {{ post.text }}
    From fdec448e745fb7495da5daf36bcb6d549ff00564 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 01:20:01 -0500 Subject: [PATCH 28/33] Show up to 2 usernames, calculate number unshown on likes status text - Switched to username for likes display text instead of name - Reminder: Strict usernames, no spaces, a-Z + 0-9 --- models.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/models.py b/models.py index 472da85..3761ed9 100644 --- a/models.py +++ b/models.py @@ -101,18 +101,20 @@ class Post(db.Model): def get_like_text(self) -> str: like_count = self.get_like_count() - top_likes = PostLike.query.filter_by(post_id=self.id).order_by(PostLike.timestamp.asc()).limit(3) + top_likes = PostLike.query.filter_by(post_id=self.id).order_by(PostLike.timestamp.asc()).limit(2) users = [like.user for like in top_likes] - names = [f'{user.name}' for user in users] + names = [f'{user.username}' for user in users] - if like_count >= 3: format_string = '{0}, {1} and {2} has liked this post.' + if like_count >= 3: format_string = '{0}, {1} and {other_text} have liked this post.' elif like_count == 2: format_string = '{0} and {1} has liked this post.' elif like_count == 1: format_string = '{0} has liked this post.' else: format_string = '0 likes' + others: int = like_count - top_likes.count() + if others > 0: + return format_string.format(*names, other_text=f'{others} other{"s" if others != 1 else ""}') return format_string.format(*names) - class PostLike(db.Model): id = db.Column(db.Integer, primary_key=True) timestamp = db.Column(db.DateTime, server_default=func.now()) From 7da54f656d003039221cf1961def9c4ce0b68742 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 01:27:28 -0500 Subject: [PATCH 29/33] Limit possible characters in a username heavily to combat abuse --- forms.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/forms.py b/forms.py index d1fb1be..aafe74e 100644 --- a/forms.py +++ b/forms.py @@ -5,7 +5,9 @@ from validators import NoProfanity class RegistrationForm(FlaskForm): - username = StringField('Username', [validators.Length(min=4, max=25), NoProfanity()]) + username = StringField('Username', [validators.Length(min=4, max=25), + validators.Regexp(r' ^[a-zA-Z0-9]+([._]?[a-zA-Z0-9]+)*$'), + NoProfanity()]) name = StringField('Name', [validators.Length(min=2, max=35), NoProfanity()]) password = PasswordField('New Password', [ validators.DataRequired(), @@ -23,7 +25,7 @@ class LoginForm(FlaskForm): class EditProfileForm(FlaskForm): name = RegistrationForm.name - about_me = TextAreaField('About Me', [validators.Optional(), NoProfanity()], description='Tell us about yourself',) + about_me = TextAreaField('About Me', [validators.Optional(), NoProfanity()], description='Tell us about yourself', ) class NewPostForm(FlaskForm): From d16df75bf57a3eaed88c8e47c8fb7370a86b233f Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 01:33:17 -0500 Subject: [PATCH 30/33] Add better message hints to RegEx validators I realized that users wouldn't be able to understand why their username was invalid, so rather than getting rid of it, I added message hints and split it up into two different validators. If the first one fails, the second one will show as well. Not perfect, but better than before by a longshot. --- forms.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/forms.py b/forms.py index aafe74e..8a5b0a5 100644 --- a/forms.py +++ b/forms.py @@ -6,7 +6,10 @@ from validators import NoProfanity class RegistrationForm(FlaskForm): username = StringField('Username', [validators.Length(min=4, max=25), - validators.Regexp(r' ^[a-zA-Z0-9]+([._]?[a-zA-Z0-9]+)*$'), + validators.Regexp(r'^[a-zA-Z0-9_\.]+$', + message='Only letters, numbers, underscore character and dots are allowed.'), + validators.Regexp(r'^[a-zA-Z0-9]+([._]?[a-zA-Z0-9]+)*$', + message='Dots and underscores cannot be at the start of the username, repeat or touch.'), NoProfanity()]) name = StringField('Name', [validators.Length(min=2, max=35), NoProfanity()]) password = PasswordField('New Password', [ From 11394bfb7e4bcf74fcde6e7827f1d5cc11499600 Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 30 Mar 2022 01:36:32 -0500 Subject: [PATCH 31/33] Re-add red border to form errors, limit like button to logged in users --- static/styles.css | 7 +------ static/styles.css.map | 2 +- static/styles.scss | 11 ++++------- templates/pages/feed.html | 4 +++- 4 files changed, 9 insertions(+), 15 deletions(-) diff --git a/static/styles.css b/static/styles.css index 60d333b..c286caf 100644 --- a/static/styles.css +++ b/static/styles.css @@ -341,15 +341,10 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after margin: 1em; } -span.error-message { - margin: 0.5em; +.errors > li { border: 1px solid red; padding: 0.3em; } -span.error-message.center-message { - margin: 0 auto; - display: table; -} form { background: white; diff --git a/static/styles.css.map b/static/styles.css.map index 4112a86..c5aee0f 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EAEE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 27cdbe9..3f3e4f0 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -304,6 +304,7 @@ nav, footer { right: 1em; color: #b0c9f3; cursor: pointer; + &.liked { color: #1b53a8; } @@ -382,6 +383,7 @@ nav, footer { .index-flex { display: flex; justify-content: space-around; + .new-users, .statistics { margin-top: 0 !important; @@ -403,15 +405,10 @@ nav, footer { } } -span.error-message { - margin: 0.5em; +.errors > li { + //margin: 0.5em; border: 1px solid red; padding: 0.3em; - - &.center-message { - margin: 0 auto; - display: table; - } } form { diff --git a/templates/pages/feed.html b/templates/pages/feed.html index 41e9382..442ddb0 100644 --- a/templates/pages/feed.html +++ b/templates/pages/feed.html @@ -16,7 +16,9 @@ {% for post in posts %}
    - + {% if current_user.is_authenticated %} + + {% endif %} {{ post.text }}