aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Leon Sandøy <[email protected]>2020-08-27 00:59:39 +0200
committerGravatar GitHub <[email protected]>2020-08-27 00:59:39 +0200
commit05a0575e28abfebb54a7f3996c182ae6ae091ab6 (patch)
tree61fa0951297cd0745bda4b95a3c40f04e042f4ad
parentOT: Rename variable `ext` to `other_names` (diff)
parentMerge pull request #374 from Numerlor/reminder-direct-retrieve (diff)
Merge branch 'master' into off-topic-non-random
-rw-r--r--.github/workflows/codeql-analysis.yml32
-rw-r--r--Pipfile16
-rw-r--r--Pipfile.lock624
-rw-r--r--pydis_site/apps/api/migrations/0007_tag.py2
-rw-r--r--pydis_site/apps/api/migrations/0009_snakefact.py2
-rw-r--r--pydis_site/apps/api/migrations/0010_snakeidiom.py2
-rw-r--r--pydis_site/apps/api/migrations/0012_specialsnake.py2
-rw-r--r--pydis_site/apps/api/migrations/0018_messagedeletioncontext.py2
-rw-r--r--pydis_site/apps/api/migrations/0019_deletedmessage.py2
-rw-r--r--pydis_site/apps/api/migrations/0020_infraction.py2
-rw-r--r--pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py4
-rw-r--r--pydis_site/apps/api/migrations/0030_reminder.py2
-rw-r--r--pydis_site/apps/api/migrations/0031_nomination.py2
-rw-r--r--pydis_site/apps/api/migrations/0032_botsetting.py2
-rw-r--r--pydis_site/apps/api/migrations/0035_create_table_log_entry.py2
-rw-r--r--pydis_site/apps/api/migrations/0049_offensivemessage.py4
-rw-r--r--pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py21
-rw-r--r--pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py17
-rw-r--r--pydis_site/apps/api/migrations/0053_user_roles_to_array.py24
-rw-r--r--pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py21
-rw-r--r--pydis_site/apps/api/migrations/0055_merge_20200714_2027.py14
-rw-r--r--pydis_site/apps/api/migrations/0055_reminder_mentions.py20
-rw-r--r--pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py21
-rw-r--r--pydis_site/apps/api/migrations/0057_merge_20200716_0751.py14
-rw-r--r--pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py33
-rw-r--r--pydis_site/apps/api/migrations/0059_populate_filterlists.py153
-rw-r--r--pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py85
-rw-r--r--pydis_site/apps/api/models/__init__.py2
-rw-r--r--pydis_site/apps/api/models/bot/__init__.py1
-rw-r--r--pydis_site/apps/api/models/bot/bot_setting.py2
-rw-r--r--pydis_site/apps/api/models/bot/documentation_link.py2
-rw-r--r--pydis_site/apps/api/models/bot/filter_list.py41
-rw-r--r--pydis_site/apps/api/models/bot/infraction.py2
-rw-r--r--pydis_site/apps/api/models/bot/message.py3
-rw-r--r--pydis_site/apps/api/models/bot/message_deletion_context.py2
-rw-r--r--pydis_site/apps/api/models/bot/nomination.py2
-rw-r--r--pydis_site/apps/api/models/bot/off_topic_channel_name.py2
-rw-r--r--pydis_site/apps/api/models/bot/offensive_message.py2
-rw-r--r--pydis_site/apps/api/models/bot/reminder.py16
-rw-r--r--pydis_site/apps/api/models/bot/role.py2
-rw-r--r--pydis_site/apps/api/models/bot/tag.py2
-rw-r--r--pydis_site/apps/api/models/bot/user.py38
-rw-r--r--pydis_site/apps/api/models/log_entry.py2
-rw-r--r--pydis_site/apps/api/models/mixins.py (renamed from pydis_site/apps/api/models/utils.py)14
-rw-r--r--pydis_site/apps/api/serializers.py52
-rw-r--r--pydis_site/apps/api/tests/test_deleted_messages.py2
-rw-r--r--pydis_site/apps/api/tests/test_filterlists.py122
-rw-r--r--pydis_site/apps/api/tests/test_infractions.py7
-rw-r--r--pydis_site/apps/api/tests/test_models.py57
-rw-r--r--pydis_site/apps/api/tests/test_nominations.py2
-rw-r--r--pydis_site/apps/api/tests/test_reminders.py33
-rw-r--r--pydis_site/apps/api/tests/test_users.py10
-rw-r--r--pydis_site/apps/api/urls.py24
-rw-r--r--pydis_site/apps/api/views.py3
-rw-r--r--pydis_site/apps/api/viewsets/__init__.py1
-rw-r--r--pydis_site/apps/api/viewsets/bot/__init__.py1
-rw-r--r--pydis_site/apps/api/viewsets/bot/filter_list.py97
-rw-r--r--pydis_site/apps/api/viewsets/bot/reminder.py62
-rw-r--r--pydis_site/apps/api/viewsets/bot/user.py7
-rw-r--r--pydis_site/apps/home/forms/account_deletion.py14
-rw-r--r--pydis_site/apps/home/signals.py10
-rw-r--r--pydis_site/apps/home/tests/mock_github_api_response.json2
-rw-r--r--pydis_site/apps/home/tests/test_signal_listener.py15
-rw-r--r--pydis_site/apps/home/views/home.py4
-rw-r--r--pydis_site/apps/staff/tests/test_logs_view.py3
-rw-r--r--pydis_site/settings.py16
-rw-r--r--pydis_site/static/images/events/summer_code_jam_2020.pngbin0 -> 271282 bytes
-rw-r--r--pydis_site/templates/base/navbar.html4
-rw-r--r--pydis_site/templates/home/account/delete.html9
-rw-r--r--pydis_site/templates/home/index.html4
-rw-r--r--pydis_site/templates/wiki/base.html2
-rw-r--r--pydis_site/tests/__init__.py0
-rw-r--r--pydis_site/tests/test_utils_account.py79
-rw-r--r--pydis_site/utils/account.py2
74 files changed, 1389 insertions, 515 deletions
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..8760b35e
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,32 @@
+name: "Code scanning - action"
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: '0 12 * * *'
+
+jobs:
+ CodeQL-Build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 2
+
+ - run: git checkout HEAD^2
+ if: ${{ github.event_name == 'pull_request' }}
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: python
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/Pipfile b/Pipfile
index 7b4298c9..0f794078 100644
--- a/Pipfile
+++ b/Pipfile
@@ -4,23 +4,21 @@ url = "https://pypi.org/simple"
verify_ssl = true
[packages]
-django = "~=2.2.8"
-django-crispy-forms = "~=1.7.2"
+django = "~=3.0.4"
django-environ = "~=0.4.5"
django-filter = "~=2.1.0"
-django-hosts = "~=3.0"
-djangorestframework = "~=3.9.2"
+django-hosts = "~=4.0"
+djangorestframework = "~=3.11.0"
djangorestframework-bulk = "~=0.2.1"
psycopg2-binary = "~=2.8"
-django-simple-bulma = ">=1.1.7,<2.0"
-django-crispy-bulma = ">=0.1.2,<2.0"
-whitenoise = "==4.1.2"
+django-simple-bulma = "~=1.2"
+whitenoise = "~=5.0"
requests = "~=2.21"
pygments = "~=2.3.1"
-wiki = "~=0.5.0"
+wiki = "~=0.6.0"
pyyaml = "~=5.1"
pyuwsgi = {version = "~=2.0", sys_platform = "!='win32'"}
-django-allauth = "~=0.40"
+django-allauth = "~=0.41"
sentry-sdk = "~=0.14"
[dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index 0adb35c7..02d81d76 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "28ebc86c71f033b8aceebf18c84a1c70c98c7a0c859b0d2131663b74d5f6cb2c"
+ "sha256": "edc59f1c711954bd22606d68e00f44c21c68a7b3193b20e44a86438e24c0f54b"
},
"pipfile-spec": 6,
"requires": {
@@ -16,19 +16,28 @@
]
},
"default": {
+ "asgiref": {
+ "hashes": [
+ "sha256:7e51911ee147dd685c3c8b805c0ad0cb58d360987b56953878f8c06d2d1c6f1a",
+ "sha256:9fc6fb5d39b8af147ba40765234fa822b39818b12cc80b35ad9b0cef3a476aed"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==3.2.10"
+ },
"bleach": {
"hashes": [
- "sha256:0ee95f6167129859c5dce9b1ca291ebdb5d8cd7e382ca0e237dfd0dad63f63d8",
- "sha256:24754b9a7d530bf30ce7cbc805bc6cce785660b4a10ff3a43633728438c105ab"
+ "sha256:2bce3d8fab545a6528c8fa5d9f9ae8ebc85a56da365c7f85180bfe96a35ef22f",
+ "sha256:3c4c520fdb9db59ef139915a5db79f8b51bc2a7257ea0389f30c846883430a4b"
],
- "version": "==2.1.4"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==3.1.5"
},
"certifi": {
"hashes": [
- "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
- "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
+ "sha256:5930595817496dd21bb8dc35dad090f1c2cd0adfaf21204bf6732ca5d8ee34d3",
+ "sha256:8fc0819f1f30ba15bdb34cceffb9ef04d99f420f68eb75d901e9560b8749fc41"
],
- "version": "==2019.11.28"
+ "version": "==2020.6.20"
},
"chardet": {
"hashes": [
@@ -42,22 +51,23 @@
"sha256:6687150770438374ab581bb7a1b327a847dd9c5749e396102de3fad4e8a3ef93",
"sha256:f684034d135af4c6cbb949b8a4d2ed61634515257a67299e5f940fbaa34377f5"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.6.0"
},
"django": {
"hashes": [
- "sha256:65e2387e6bde531d3bb803244a2b74e0253550a9612c64a60c8c5be267b30f50",
- "sha256:b51c9c548d5c3b3ccbb133d0bebc992e8ec3f14899bce8936e6fdda6b23a1881"
+ "sha256:31a5fbbea5fc71c99e288ec0b2f00302a0a92c44b13ede80b73a6a4d6d205582",
+ "sha256:5457fc953ec560c5521b41fad9e6734a4668b7ba205832191bbdff40ec61073c"
],
"index": "pypi",
- "version": "==2.2.11"
+ "version": "==3.0.8"
},
"django-allauth": {
"hashes": [
- "sha256:7ab91485b80d231da191d5c7999ba93170ef1bf14ab6487d886598a1ad03e1d8"
+ "sha256:f17209410b7f87da0a84639fd79d3771b596a6d3fc1a8e48ce50dabc7f441d30"
],
"index": "pypi",
- "version": "==0.41.0"
+ "version": "==0.42.0"
},
"django-classy-tags": {
"hashes": [
@@ -65,22 +75,6 @@
],
"version": "==1.0.0"
},
- "django-crispy-bulma": {
- "hashes": [
- "sha256:6ce8f6df87442040fbba39baede7aba7026d71359376e3ce7eecb7695b09c02b",
- "sha256:72a61a1ed87611c87347553f57879dd05e21b4cedffaf9c676971488a0f065b2"
- ],
- "index": "pypi",
- "version": "==0.2"
- },
- "django-crispy-forms": {
- "hashes": [
- "sha256:5952bab971110d0b86c278132dae0aa095beee8f723e625c3d3fa28888f1675f",
- "sha256:705ededc554ad8736157c666681165fe22ead2dec0d5446d65fc9dd976a5a876"
- ],
- "index": "pypi",
- "version": "==1.7.2"
- },
"django-environ": {
"hashes": [
"sha256:6c9d87660142608f63ec7d5ce5564c49b603ea8ff25da595fd6098f6dc82afde",
@@ -99,11 +93,11 @@
},
"django-hosts": {
"hashes": [
- "sha256:3599645f37b4c51df6140d659bef356e05ae7ff7748f8fef14c2c84083dd8089",
- "sha256:8e83232dbd7ff0d9de5c814f16bdf4cd1971bd00c54fa1f3e507aed4f93215a8"
+ "sha256:136ac225f34e7f2c007294441a38663ec2bba9637d870ad001def81bca87e390",
+ "sha256:59a870d453f113c889a7888bae5408888870350e83e362740f382dad569c2281"
],
"index": "pypi",
- "version": "==3.0"
+ "version": "==4.0"
},
"django-js-asset": {
"hashes": [
@@ -114,10 +108,11 @@
},
"django-mptt": {
"hashes": [
- "sha256:18a41d1b56ca7c02a5b04d246e33ee2d18f6ee5459c02ed1d945f5abdef23a2e",
- "sha256:689a04cce0981671d6061a9928c33a16b47abb0d4cd43cf7dec31ae284fdae9d"
+ "sha256:90eb236eb4f1a92124bd7c37852bbe09c0d21158477cc237556d59842a91c509",
+ "sha256:dfdb3af75ad27cdd4458b0544ec8574174f2b90f99bc2cafab6a15b4bc1895a8"
],
- "version": "==0.9.1"
+ "markers": "python_version >= '3.5'",
+ "version": "==0.11.0"
},
"django-nyt": {
"hashes": [
@@ -142,11 +137,11 @@
},
"djangorestframework": {
"hashes": [
- "sha256:376f4b50340a46c15ae15ddd0c853085f4e66058f97e4dbe7d43ed62f5e60651",
- "sha256:c12869cfd83c33d579b17b3cb28a2ae7322a53c3ce85580c2a2ebe4e3f56c4fb"
+ "sha256:05809fc66e1c997fd9a32ea5730d9f4ba28b109b9da71fccfa5ff241201fd0a4",
+ "sha256:e782087823c47a26826ee5b6fa0c542968219263fb3976ec3c31edab23a4001f"
],
"index": "pypi",
- "version": "==3.9.4"
+ "version": "==3.11.0"
},
"djangorestframework-bulk": {
"hashes": [
@@ -155,119 +150,131 @@
"index": "pypi",
"version": "==0.2.1"
},
- "html5lib": {
+ "idna": {
"hashes": [
- "sha256:20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3",
- "sha256:66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"
+ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
+ "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
],
- "version": "==1.0.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.10"
},
- "idna": {
+ "importlib-metadata": {
"hashes": [
- "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
- "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
+ "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
+ "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
],
- "version": "==2.9"
+ "markers": "python_version < '3.8'",
+ "version": "==1.7.0"
},
"libsass": {
"hashes": [
- "sha256:003a65b4facb4c5dbace53fb0f70f61c5aae056a04b4d112a198c3c9674b31f2",
- "sha256:0fd8b4337b3b101c6e6afda9112cc0dc4bacb9133b59d75d65968c7317aa3272",
- "sha256:338e9ae066bf1fde874e335324d5355c52d2081d978b4f74fc59536564b35b08",
- "sha256:4dcfd561fb100250b89496e1362b96f2cc804f689a59731eb0f94f9a9e144f4a",
- "sha256:50778d4be269a021ba2bf42b5b8f6ff3704ab96a82175a052680bddf3ba7cc9f",
- "sha256:6a51393d75f6e3c812785b0fa0b7d67c54258c28011921f204643b55f7355ec0",
- "sha256:74acd9adf506142699dfa292f0e569fdccbd9e7cf619e8226f7117de73566e32",
- "sha256:81a013a4c2a614927fd1ef7a386eddabbba695cbb02defe8f31cf495106e974c",
- "sha256:845a9573b25c141164972d498855f4ad29367c09e6d76fad12955ad0e1c83013",
- "sha256:8b5b6d1a7c4ea1d954e0982b04474cc076286493f6af2d0a13c2e950fbe0be95",
- "sha256:9b59afa0d755089c4165516400a39a289b796b5612eeef5736ab7a1ebf96a67c",
- "sha256:a7e685466448c9b1bf98243339793978f654a1151eb5c975f09b83c7a226f4c1",
- "sha256:c93df526eeef90b1ea4799c1d33b6cd5aea3e9f4633738fb95c1287c13e6b404",
- "sha256:e318f06f06847ff49b1f8d086ac9ebce1e63404f7ea329adab92f4f16ba0e00e",
- "sha256:fc5f8336750f76f1bfae82f7e9e89ae71438d26fc4597e3ab4c05ca8fcd41d8a",
- "sha256:fcb7ab4dc81889e5fc99cafbc2017bc76996f9992fc6b175f7a80edac61d71df"
- ],
- "version": "==0.19.4"
+ "sha256:107c409524c6a4ed14410fa9dafa9ee59c6bd3ecae75d73af749ab2b75685726",
+ "sha256:3bc0d68778b30b5fa83199e18795314f64b26ca5871e026343e63934f616f7f7",
+ "sha256:5c8ff562b233734fbc72b23bb862cc6a6f70b1e9bf85a58422aa75108b94783b",
+ "sha256:74f6fb8da58179b5d86586bc045c16d93d55074bc7bb48b6354a4da7ac9f9dfd",
+ "sha256:7555d9b24e79943cfafac44dbb4ca7e62105c038de7c6b999838c9ff7b88645d",
+ "sha256:794f4f4661667263e7feafe5cc866e3746c7c8a9192b2aa9afffdadcbc91c687",
+ "sha256:8cf72552b39e78a1852132e16b706406bc76029fe3001583284ece8d8752a60a",
+ "sha256:98f6dee9850b29e62977a963e3beb3cfeb98b128a267d59d2c3d675e298c8d57",
+ "sha256:a43f3830d83ad9a7f5013c05ce239ca71744d0780dad906587302ac5257bce60",
+ "sha256:b077261a04ba1c213e932943208471972c5230222acb7fa97373e55a40872cbb",
+ "sha256:b7452f1df274b166dc22ee2e9154c4adca619bcbbdf8041a7aa05f372a1dacbc",
+ "sha256:e6a547c0aa731dcb4ed71f198e814bee0400ce04d553f3f12a53bc3a17f2a481",
+ "sha256:fd19c8f73f70ffc6cbcca8139da08ea9a71fc48e7dfc4bb236ad88ab2d6558f1"
+ ],
+ "version": "==0.20.0"
},
"markdown": {
"hashes": [
- "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a",
- "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"
+ "sha256:1fafe3f1ecabfb514a5285fca634a53c1b32a81cb0feb154264d55bf2ff22c17",
+ "sha256:c467cd6233885534bf0fe96e62e3cf46cfc1605112356c4f9981512b8174de59"
],
- "version": "==3.1.1"
+ "markers": "python_version >= '3.5'",
+ "version": "==3.2.2"
},
"oauthlib": {
"hashes": [
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0"
},
+ "packaging": {
+ "hashes": [
+ "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8",
+ "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==20.4"
+ },
"pillow": {
"hashes": [
- "sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be",
- "sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946",
- "sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837",
- "sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f",
- "sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00",
- "sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d",
- "sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533",
- "sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a",
- "sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358",
- "sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda",
- "sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435",
- "sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2",
- "sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313",
- "sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff",
- "sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317",
- "sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2",
- "sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614",
- "sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0",
- "sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386",
- "sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9",
- "sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636",
- "sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865"
- ],
- "version": "==7.0.0"
+ "sha256:0295442429645fa16d05bd567ef5cff178482439c9aad0411d3f0ce9b88b3a6f",
+ "sha256:06aba4169e78c439d528fdeb34762c3b61a70813527a2c57f0540541e9f433a8",
+ "sha256:09d7f9e64289cb40c2c8d7ad674b2ed6105f55dc3b09aa8e4918e20a0311e7ad",
+ "sha256:0a80dd307a5d8440b0a08bd7b81617e04d870e40a3e46a32d9c246e54705e86f",
+ "sha256:1ca594126d3c4def54babee699c055a913efb01e106c309fa6b04405d474d5ae",
+ "sha256:25930fadde8019f374400f7986e8404c8b781ce519da27792cbe46eabec00c4d",
+ "sha256:431b15cffbf949e89df2f7b48528be18b78bfa5177cb3036284a5508159492b5",
+ "sha256:52125833b070791fcb5710fabc640fc1df07d087fc0c0f02d3661f76c23c5b8b",
+ "sha256:5e51ee2b8114def244384eda1c82b10e307ad9778dac5c83fb0943775a653cd8",
+ "sha256:612cfda94e9c8346f239bf1a4b082fdd5c8143cf82d685ba2dba76e7adeeb233",
+ "sha256:6d7741e65835716ceea0fd13a7d0192961212fd59e741a46bbed7a473c634ed6",
+ "sha256:6edb5446f44d901e8683ffb25ebdfc26988ee813da3bf91e12252b57ac163727",
+ "sha256:725aa6cfc66ce2857d585f06e9519a1cc0ef6d13f186ff3447ab6dff0a09bc7f",
+ "sha256:8dad18b69f710bf3a001d2bf3afab7c432785d94fcf819c16b5207b1cfd17d38",
+ "sha256:94cf49723928eb6070a892cb39d6c156f7b5a2db4e8971cb958f7b6b104fb4c4",
+ "sha256:97f9e7953a77d5a70f49b9a48da7776dc51e9b738151b22dacf101641594a626",
+ "sha256:9ad7f865eebde135d526bb3163d0b23ffff365cf87e767c649550964ad72785d",
+ "sha256:a060cf8aa332052df2158e5a119303965be92c3da6f2d93b6878f0ebca80b2f6",
+ "sha256:c79f9c5fb846285f943aafeafda3358992d64f0ef58566e23484132ecd8d7d63",
+ "sha256:c92302a33138409e8f1ad16731568c55c9053eee71bb05b6b744067e1b62380f",
+ "sha256:d08b23fdb388c0715990cbc06866db554e1822c4bdcf6d4166cf30ac82df8c41",
+ "sha256:d350f0f2c2421e65fbc62690f26b59b0bcda1b614beb318c81e38647e0f673a1",
+ "sha256:ec29604081f10f16a7aea809ad42e27764188fc258b02259a03a8ff7ded3808d",
+ "sha256:edf31f1150778abd4322444c393ab9c7bd2af271dd4dafb4208fb613b1f3cdc9",
+ "sha256:f7e30c27477dffc3e85c2463b3e649f751789e0f6c8456099eea7ddd53be4a8a",
+ "sha256:ffe538682dc19cc542ae7c3e504fdf54ca7f86fb8a135e59dd6bc8627eae6cce"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==7.2.0"
},
"psycopg2-binary": {
"hashes": [
- "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29",
- "sha256:086f7e89ec85a6704db51f68f0dcae432eff9300809723a6e8782c41c2f48e03",
- "sha256:18ca813fdb17bc1db73fe61b196b05dd1ca2165b884dd5ec5568877cabf9b039",
- "sha256:19dc39616850342a2a6db70559af55b22955f86667b5f652f40c0e99253d9881",
- "sha256:2166e770cb98f02ed5ee2b0b569d40db26788e0bf2ec3ae1a0d864ea6f1d8309",
- "sha256:3a2522b1d9178575acee4adf8fd9f979f9c0449b00b4164bb63c3475ea6528ed",
- "sha256:3aa773580f85a28ffdf6f862e59cb5a3cc7ef6885121f2de3fca8d6ada4dbf3b",
- "sha256:3b5deaa3ee7180585a296af33e14c9b18c218d148e735c7accf78130765a47e3",
- "sha256:407af6d7e46593415f216c7f56ba087a9a42bd6dc2ecb86028760aa45b802bd7",
- "sha256:4c3c09fb674401f630626310bcaf6cd6285daf0d5e4c26d6e55ca26a2734e39b",
- "sha256:4c6717962247445b4f9e21c962ea61d2e884fc17df5ddf5e35863b016f8a1f03",
- "sha256:50446fae5681fc99f87e505d4e77c9407e683ab60c555ec302f9ac9bffa61103",
- "sha256:5057669b6a66aa9ca118a2a860159f0ee3acf837eda937bdd2a64f3431361a2d",
- "sha256:5dd90c5438b4f935c9d01fcbad3620253da89d19c1f5fca9158646407ed7df35",
- "sha256:659c815b5b8e2a55193ede2795c1e2349b8011497310bb936da7d4745652823b",
- "sha256:69b13fdf12878b10dc6003acc8d0abf3ad93e79813fd5f3812497c1c9fb9be49",
- "sha256:7a1cb80e35e1ccea3e11a48afe65d38744a0e0bde88795cc56a4d05b6e4f9d70",
- "sha256:7e6e3c52e6732c219c07bd97fff6c088f8df4dae3b79752ee3a817e6f32e177e",
- "sha256:7f42a8490c4fe854325504ce7a6e4796b207960dabb2cbafe3c3959cb00d1d7e",
- "sha256:84156313f258eafff716b2961644a4483a9be44a5d43551d554844d15d4d224e",
- "sha256:8578d6b8192e4c805e85f187bc530d0f52ba86c39172e61cd51f68fddd648103",
- "sha256:890167d5091279a27e2505ff0e1fb273f8c48c41d35c5b92adbf4af80e6b2ed6",
- "sha256:98e10634792ac0e9e7a92a76b4991b44c2325d3e7798270a808407355e7bb0a1",
- "sha256:9aadff9032e967865f9778485571e93908d27dab21d0fdfdec0ca779bb6f8ad9",
- "sha256:9f24f383a298a0c0f9b3113b982e21751a8ecde6615494a3f1470eb4a9d70e9e",
- "sha256:a73021b44813b5c84eda4a3af5826dd72356a900bac9bd9dd1f0f81ee1c22c2f",
- "sha256:afd96845e12638d2c44d213d4810a08f4dc4a563f9a98204b7428e567014b1cd",
- "sha256:b73ddf033d8cd4cc9dfed6324b1ad2a89ba52c410ef6877998422fcb9c23e3a8",
- "sha256:b8f490f5fad1767a1331df1259763b3bad7d7af12a75b950c2843ba319b2415f",
- "sha256:dbc5cd56fff1a6152ca59445178652756f4e509f672e49ccdf3d79c1043113a4",
- "sha256:eac8a3499754790187bb00574ab980df13e754777d346f85e0ff6df929bcd964",
- "sha256:eaed1c65f461a959284649e37b5051224f4db6ebdc84e40b5e65f2986f101a08"
- ],
- "index": "pypi",
- "version": "==2.8.4"
+ "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac",
+ "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a",
+ "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5",
+ "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04",
+ "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1",
+ "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5",
+ "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce",
+ "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434",
+ "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9",
+ "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057",
+ "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98",
+ "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522",
+ "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505",
+ "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa",
+ "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3",
+ "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f",
+ "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4",
+ "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4",
+ "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266",
+ "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66",
+ "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38",
+ "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3",
+ "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389",
+ "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab",
+ "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb",
+ "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6",
+ "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d",
+ "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162",
+ "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e",
+ "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"
+ ],
+ "index": "pypi",
+ "version": "==2.8.5"
},
"pygments": {
"hashes": [
@@ -277,105 +284,102 @@
"index": "pypi",
"version": "==2.3.1"
},
+ "pyparsing": {
+ "hashes": [
+ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
+ "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.4.7"
+ },
"python3-openid": {
"hashes": [
- "sha256:0086da6b6ef3161cfe50fb1ee5cceaf2cda1700019fda03c2c5c440ca6abe4fa",
- "sha256:628d365d687e12da12d02c6691170f4451db28d6d68d050007e4a40065868502"
+ "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf",
+ "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"
],
- "version": "==3.1.0"
+ "version": "==3.2.0"
},
"pytz": {
"hashes": [
- "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d",
- "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"
+ "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed",
+ "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048"
],
- "version": "==2019.3"
+ "version": "==2020.1"
},
"pyuwsgi": {
"hashes": [
- "sha256:15a4626740753b0d0dfeeac7d367f9b2e89ab6af16c195927e60f75359fc1bbc",
- "sha256:24c40c3b889eb9f283d43feffbc0f7c7fc024e914451425156ddb68af3df1e71",
- "sha256:393737bd43a7e38f0a4a1601a37a69c4bf893635b37665ff958170fdb604fdb7",
- "sha256:5a08308f87e639573c1efaa5966a6d04410cd45a73c4586a932fe3ee4b56369d",
- "sha256:5f4b36c0dbb9931c4da8008aa423158be596e3b4a23cec95a958631603a94e45",
- "sha256:7c31794f71bbd0ccf542cab6bddf38aa69e84e31ae0f9657a2e18ebdc150c01a",
- "sha256:802ec6dad4b6707b934370926ec1866603abe31ba03c472f56149001b3533ba1",
- "sha256:814d73d4569add69a6c19bb4a27cd5adb72b196e5e080caed17dbda740402072",
- "sha256:829299cd117cf8abe837796bf587e61ce6bfe18423a3a1c510c21e9825789c2c",
- "sha256:85f2210ceae5f48b7d8fad2240d831f4b890cac85cd98ca82683ac6aa481dfc8",
- "sha256:861c94442b28cd64af033e88e0f63c66dbd5609f67952dc18694098b47a43f3a",
- "sha256:957bc6316ffc8463795d56d9953d58e7f32aa5aad1c5ac80bc45c69f3299961e",
- "sha256:9760c3f56fb5f15852d163429096600906478e9ed2c189a52f2bb21d8a2a986c",
- "sha256:9fdfb98a2992de01e8efad2aeed22c825e36db628b144b2d6b93d81fb549f811",
- "sha256:a4b24703ea818196d0be1dc64b3b57b79c67e8dee0cfa207a4216220912035a7",
- "sha256:ad7f4968c1ddbf139a306d9b075360d959cc554d994ba5e1f512af9a40e62357",
- "sha256:b1127d34b90f74faf1707718c57a4193ac028b9f4aec0238638983132297d456",
- "sha256:bcb04d6ec644b3e08d03c64851e06edd7110489261e50627a4bcadf66ff6920e",
- "sha256:bebfebb9ee83d7cf37668bf54275b677b7ae283e84a944f9f3ac6a4b66f95d4b",
- "sha256:c29892dafc65a8b6eb95823fa4bac7754ca3fd1c28ab8d2a973289531b340a27",
- "sha256:cb296b50b51ba022b0090b28d032ff1dd395a6db03672b65a39e83532edad527",
- "sha256:ce777ebdf49ce736fc04abf555b5c41ab3f130127543a689dcf8d4871cd18fe4",
- "sha256:d8b4bf930b6a19bc9ee982b9163d948c87501ad91b71516924e8ed25fe85d2ee",
- "sha256:e2a420f2c4d35f3ec0b7e752a80d7bd385e2c5a64f67c05f2d2d74230e3114b6",
- "sha256:ef5eb630f541af6b69378d58594be90a0922fa6d6a50a9248c25b9502585f6bf",
- "sha256:fed899ce96f4f2b4d1b9f338dd145a4040ee1d8a5152213af0dd8d4a4d36e9fe"
+ "sha256:1a4dd8d99b8497f109755e09484b0bd2aeaa533f7621e7c7e2a120a72111219d",
+ "sha256:206937deaebbac5c87692657c3151a5a9d40ecbc9b051b94154205c50a48e963",
+ "sha256:2cf35d9145208cc7c96464d688caa3de745bfc969e1a1ae23cb046fc10b0ac7e",
+ "sha256:3ab84a168633eeb55847d59475d86e9078d913d190c2a1aed804c562a10301a3",
+ "sha256:430406d1bcf288a87f14fde51c66877eaf5e98516838a1c6f761af5d814936fc",
+ "sha256:72be25ce7aa86c5616c59d12c2961b938e7bde47b7ff6a996ff83b89f7c5cd27",
+ "sha256:aa4d615de430e2066a1c76d9cc2a70abf2dfc703a82c21aee625b445866f2c3b",
+ "sha256:aadd231256a672cf4342ef9fb976051949e4d5b616195e696bcb7b8a9c07789e",
+ "sha256:b15ee6a7759b0465786d856334b8231d882deda5291cf243be6a343a8f3ef910",
+ "sha256:bd1d0a8d4cb87eb63417a72e6b1bac47053f9b0be550adc6d2a375f4cbaa22f0",
+ "sha256:d5787779ec24b67ac8898be9dc2b2b4e35f17d79f14361f6cf303d6283a848f2",
+ "sha256:ecfae85d6504e0ecbba100a795032a88ce8f110b62b93243f2df1bd116eca67f"
],
"index": "pypi",
"markers": "sys_platform != 'win32'",
- "version": "==2.0.18.post0"
+ "version": "==2.0.19.1"
},
"pyyaml": {
"hashes": [
- "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
- "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
- "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
- "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
- "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
- "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
- "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
- "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
- "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
- "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
- "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
+ "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
+ "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
+ "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
- "version": "==5.3"
+ "version": "==5.3.1"
},
"requests": {
"hashes": [
- "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
- "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
+ "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b",
+ "sha256:fe75cc94a9443b9246fc7049224f75604b113c36acb93f87b80ed42c44cbb898"
],
"index": "pypi",
- "version": "==2.23.0"
+ "version": "==2.24.0"
},
"requests-oauthlib": {
"hashes": [
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
- "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
+ "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
+ "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
],
"version": "==1.3.0"
},
"sentry-sdk": {
"hashes": [
- "sha256:480eee754e60bcae983787a9a13bc8f155a111aef199afaa4f289d6a76aa622a",
- "sha256:a920387dc3ee252a66679d0afecd34479fb6fc52c2bc20763793ed69e5b0dcc0"
+ "sha256:2f023ff348359ec5f0b73a840e8b08e6a8d3b2613a98c57d11c222ef43879237",
+ "sha256:380a280cfc7c4ade5912294e6d9aa71ce776b5fca60a3782e9331b0bcd2866bf"
],
"index": "pypi",
- "version": "==0.14.2"
+ "version": "==0.16.1"
},
"six": {
"hashes": [
- "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
- "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+ "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "version": "==1.14.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.15.0"
},
"sorl-thumbnail": {
"hashes": [
"sha256:66771521f3c0ed771e1ce8e1aaf1639ebff18f7f5a40cfd3083da8f0fe6c7c99",
"sha256:7162639057dff222a651bacbdb6bd6f558fc32946531d541fc71e10c0167ebdf"
],
+ "markers": "python_version >= '3.4'",
"version": "==12.6.3"
},
"sqlparse": {
@@ -383,14 +387,16 @@
"sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.3.1"
},
"urllib3": {
"hashes": [
- "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
- "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
+ "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
+ "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
],
- "version": "==1.25.8"
+ "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.25.9"
},
"webencodings": {
"hashes": [
@@ -401,34 +407,43 @@
},
"whitenoise": {
"hashes": [
- "sha256:118ab3e5f815d380171b100b05b76de2a07612f422368a201a9ffdeefb2251c1",
- "sha256:42133ddd5229eeb6a0c9899496bdbe56c292394bf8666da77deeb27454c0456a"
+ "sha256:60154b976a13901414a25b0273a841145f77eb34a141f9ae032a0ace3e4d5b27",
+ "sha256:6dd26bfda3af29177d8ab7333a0c7b7642eb615ce83764f4d15a9aecda3201c4"
],
"index": "pypi",
- "version": "==4.1.2"
+ "version": "==5.1.0"
},
"wiki": {
"hashes": [
- "sha256:51096f0139ebd8d79e6acbef2784f9792884dc16a0a14c2deb124ae20f1886b9",
- "sha256:8f7f687c2a24e74ffefac2c06b415291cb12e51659fb1946e3d5c7e783431520"
+ "sha256:aad8b8ef6f669a6f11453ea2f35722065cf6281cc558e789c49cb2800ba0b6e5",
+ "sha256:eac841fba33d317b0ce038023f14db427735c279642a22ffdce98e2575e20086"
],
"index": "pypi",
- "version": "==0.5"
+ "version": "==0.6"
+ },
+ "zipp": {
+ "hashes": [
+ "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
+ "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.1.0"
}
},
"develop": {
"appdirs": {
"hashes": [
- "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
- "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
+ "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
+ "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
],
- "version": "==1.4.3"
+ "version": "==1.4.4"
},
"attrs": {
"hashes": [
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==19.3.0"
},
"bandit": {
@@ -443,57 +458,55 @@
"sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53",
"sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"
],
+ "markers": "python_full_version >= '3.6.1'",
"version": "==3.1.0"
},
"coverage": {
"hashes": [
- "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3",
- "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c",
- "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0",
- "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477",
- "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a",
- "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf",
- "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691",
- "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73",
- "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987",
- "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894",
- "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e",
- "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef",
- "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf",
- "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68",
- "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8",
- "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954",
- "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2",
- "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40",
- "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc",
- "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc",
- "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e",
- "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d",
- "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f",
- "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc",
- "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301",
- "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea",
- "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb",
- "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af",
- "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52",
- "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37",
- "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"
- ],
- "index": "pypi",
- "version": "==5.0.3"
+ "sha256:0fc4e0d91350d6f43ef6a61f64a48e917637e1dcfcba4b4b7d543c628ef82c2d",
+ "sha256:10f2a618a6e75adf64329f828a6a5b40244c1c50f5ef4ce4109e904e69c71bd2",
+ "sha256:12eaccd86d9a373aea59869bc9cfa0ab6ba8b1477752110cb4c10d165474f703",
+ "sha256:1874bdc943654ba46d28f179c1846f5710eda3aeb265ff029e0ac2b52daae404",
+ "sha256:1dcebae667b73fd4aa69237e6afb39abc2f27520f2358590c1b13dd90e32abe7",
+ "sha256:1e58fca3d9ec1a423f1b7f2aa34af4f733cbfa9020c8fe39ca451b6071237405",
+ "sha256:214eb2110217f2636a9329bc766507ab71a3a06a8ea30cdeebb47c24dce5972d",
+ "sha256:25fe74b5b2f1b4abb11e103bb7984daca8f8292683957d0738cd692f6a7cc64c",
+ "sha256:32ecee61a43be509b91a526819717d5e5650e009a8d5eda8631a59c721d5f3b6",
+ "sha256:3740b796015b889e46c260ff18b84683fa2e30f0f75a171fb10d2bf9fb91fc70",
+ "sha256:3b2c34690f613525672697910894b60d15800ac7e779fbd0fccf532486c1ba40",
+ "sha256:41d88736c42f4a22c494c32cc48a05828236e37c991bd9760f8923415e3169e4",
+ "sha256:42fa45a29f1059eda4d3c7b509589cc0343cd6bbf083d6118216830cd1a51613",
+ "sha256:4bb385a747e6ae8a65290b3df60d6c8a692a5599dc66c9fa3520e667886f2e10",
+ "sha256:509294f3e76d3f26b35083973fbc952e01e1727656d979b11182f273f08aa80b",
+ "sha256:5c74c5b6045969b07c9fb36b665c9cac84d6c174a809fc1b21bdc06c7836d9a0",
+ "sha256:60a3d36297b65c7f78329b80120f72947140f45b5c7a017ea730f9112b40f2ec",
+ "sha256:6f91b4492c5cde83bfe462f5b2b997cdf96a138f7c58b1140f05de5751623cf1",
+ "sha256:7403675df5e27745571aba1c957c7da2dacb537c21e14007ec3a417bf31f7f3d",
+ "sha256:87bdc8135b8ee739840eee19b184804e5d57f518578ffc797f5afa2c3c297913",
+ "sha256:8a3decd12e7934d0254939e2bf434bf04a5890c5bf91a982685021786a08087e",
+ "sha256:9702e2cb1c6dec01fb8e1a64c015817c0800a6eca287552c47a5ee0ebddccf62",
+ "sha256:a4d511012beb967a39580ba7d2549edf1e6865a33e5fe51e4dce550522b3ac0e",
+ "sha256:bbb387811f7a18bdc61a2ea3d102be0c7e239b0db9c83be7bfa50f095db5b92a",
+ "sha256:bfcc811883699ed49afc58b1ed9f80428a18eb9166422bce3c31a53dba00fd1d",
+ "sha256:c32aa13cc3fe86b0f744dfe35a7f879ee33ac0a560684fef0f3e1580352b818f",
+ "sha256:ca63dae130a2e788f2b249200f01d7fa240f24da0596501d387a50e57aa7075e",
+ "sha256:d54d7ea74cc00482a2410d63bf10aa34ebe1c49ac50779652106c867f9986d6b",
+ "sha256:d67599521dff98ec8c34cd9652cbcfe16ed076a2209625fca9dc7419b6370e5c",
+ "sha256:d82db1b9a92cb5c67661ca6616bdca6ff931deceebb98eecbd328812dab52032",
+ "sha256:d9ad0a988ae20face62520785ec3595a5e64f35a21762a57d115dae0b8fb894a",
+ "sha256:ebf2431b2d457ae5217f3a1179533c456f3272ded16f8ed0b32961a6d90e38ee",
+ "sha256:ed9a21502e9223f563e071759f769c3d6a2e1ba5328c31e86830368e8d78bc9c",
+ "sha256:f50632ef2d749f541ca8e6c07c9928a37f87505ce3a9f20c8446ad310f1aa87b"
+ ],
+ "index": "pypi",
+ "version": "==5.2"
},
"distlib": {
"hashes": [
- "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"
- ],
- "version": "==0.3.0"
- },
- "entrypoints": {
- "hashes": [
- "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
- "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
+ "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb",
+ "sha256:edf6116872c863e1aa9d5bb7cb5e05a022c519a4594dc703843343a9ddd9bff1"
],
- "version": "==0.3"
+ "version": "==0.3.1"
},
"filelock": {
"hashes": [
@@ -504,19 +517,19 @@
},
"flake8": {
"hashes": [
- "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb",
- "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"
+ "sha256:15e351d19611c887e482fb960eae4d44845013cc142d42896e9862f775d8cf5c",
+ "sha256:f04b9fcbac03b0a3e58c0ab3a0ecc462e023a9faf046d57794184028123aa208"
],
"index": "pypi",
- "version": "==3.7.9"
+ "version": "==3.8.3"
},
"flake8-annotations": {
"hashes": [
- "sha256:a38b44d01abd480586a92a02a2b0a36231ec42dcc5e114de78fa5db016d8d3f9",
- "sha256:d5b0e8704e4e7728b352fa1464e23539ff2341ba11cc153b536fa2cf921ee659"
+ "sha256:7816a5d8f65ffdf37b8e21e5b17e0fd1e492aa92638573276de066e889a22b26",
+ "sha256:8d18db74a750dd97f40b483cc3ef80d07d03f687525bad8fd83365dcd3bfd414"
],
"index": "pypi",
- "version": "==2.0.1"
+ "version": "==2.3.0"
},
"flake8-bandit": {
"hashes": [
@@ -566,11 +579,11 @@
},
"flake8-tidy-imports": {
"hashes": [
- "sha256:8aa34384b45137d4cf33f5818b8e7897dc903b1d1e10a503fa7dd193a9a710ba",
- "sha256:b26461561bcc80e8012e46846630ecf0aaa59314f362a94cb7800dfdb32fa413"
+ "sha256:62059ca07d8a4926b561d392cbab7f09ee042350214a25cf12823384a45d27dd",
+ "sha256:c30b40337a2e6802ba3bb611c26611154a27e94c53fc45639e3e282169574fd3"
],
"index": "pypi",
- "version": "==4.0.0"
+ "version": "==4.1.0"
},
"flake8-todo": {
"hashes": [
@@ -581,32 +594,35 @@
},
"gitdb": {
"hashes": [
- "sha256:284a6a4554f954d6e737cddcff946404393e030b76a282c6640df8efd6b3da5e",
- "sha256:598e0096bb3175a0aab3a0b5aedaa18a9a25c6707e0eca0695ba1a0baf1b2150"
+ "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac",
+ "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"
],
- "version": "==4.0.2"
+ "markers": "python_version >= '3.4'",
+ "version": "==4.0.5"
},
"gitpython": {
"hashes": [
- "sha256:43da89427bdf18bf07f1164c6d415750693b4d50e28fc9b68de706245147b9dd",
- "sha256:e426c3b587bd58c482f0b7fe6145ff4ac7ae6c82673fc656f489719abca6f4cb"
+ "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858",
+ "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"
],
- "version": "==3.1.0"
+ "markers": "python_version >= '3.4'",
+ "version": "==3.1.7"
},
"identify": {
"hashes": [
- "sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5",
- "sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"
+ "sha256:06b4373546ae55eaaefdac54f006951dbd968fe2912846c00e565b09cfaed101",
+ "sha256:5519601b70c831011fb425ffd214101df7639ba3980f24dc283f7675b19127b3"
],
- "version": "==1.4.11"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.4.24"
},
"importlib-metadata": {
"hashes": [
- "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302",
- "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"
+ "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83",
+ "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"
],
"markers": "python_version < '3.8'",
- "version": "==1.5.0"
+ "version": "==1.7.0"
},
"mccabe": {
"hashes": [
@@ -618,84 +634,89 @@
},
"nodeenv": {
"hashes": [
- "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"
+ "sha256:4b0b77afa3ba9b54f4b6396e60b0c83f59eaeb2d63dc3cc7a70f7f4af96c82bc"
],
- "version": "==1.3.5"
+ "version": "==1.4.0"
},
"pbr": {
"hashes": [
- "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
- "sha256:61aa52a0f18b71c5cc58232d2cf8f8d09cd67fcad60b742a60124cb8d6951488"
+ "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c",
+ "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"
],
- "version": "==5.4.4"
+ "version": "==5.4.5"
},
"pep8-naming": {
"hashes": [
- "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f",
- "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf"
+ "sha256:a1dd47dd243adfe8a83616e27cf03164960b507530f155db94e10b36a6cd6724",
+ "sha256:f43bfe3eea7e0d73e8b5d07d6407ab47f2476ccaeff6937c84275cd30b016738"
],
"index": "pypi",
- "version": "==0.9.1"
+ "version": "==0.11.1"
},
"pre-commit": {
"hashes": [
- "sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6",
- "sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb"
+ "sha256:1657663fdd63a321a4a739915d7d03baedd555b25054449090f97bb0cb30a915",
+ "sha256:e8b1315c585052e729ab7e99dcca5698266bedce9067d21dc909c23e3ceed626"
],
"index": "pypi",
- "version": "==2.1.1"
+ "version": "==2.6.0"
},
"pycodestyle": {
"hashes": [
- "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56",
- "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"
+ "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
+ "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
],
- "version": "==2.5.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.6.0"
},
"pydocstyle": {
"hashes": [
"sha256:da7831660b7355307b32778c4a0dbfb137d89254ef31a2b2978f50fc0b4d7586",
"sha256:f4f5d210610c2d153fae39093d44224c17429e2ad7da12a8b419aba5c2f614b5"
],
+ "markers": "python_version >= '3.5'",
"version": "==5.0.2"
},
"pyflakes": {
"hashes": [
- "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0",
- "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"
+ "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
+ "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
],
- "version": "==2.1.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.2.0"
},
"pyyaml": {
"hashes": [
- "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
- "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
- "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
- "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
- "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
- "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
- "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
- "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
- "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
- "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
- "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
+ "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
+ "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
+ "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
+ "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
+ "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
+ "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
+ "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
+ "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
+ "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
+ "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
+ "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"index": "pypi",
- "version": "==5.3"
+ "version": "==5.3.1"
},
"six": {
"hashes": [
- "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
- "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
+ "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
+ "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
- "version": "==1.14.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.15.0"
},
"smmap": {
"hashes": [
- "sha256:171484fe62793e3626c8b05dd752eb2ca01854b0c55a1efc0dc4210fccb65446",
- "sha256:5fead614cf2de17ee0707a8c6a5f2aa5a2fc6c698c70993ba42f515485ffda78"
+ "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4",
+ "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"
],
- "version": "==3.0.1"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==3.0.4"
},
"snowballstemmer": {
"hashes": [
@@ -706,17 +727,18 @@
},
"stevedore": {
"hashes": [
- "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b",
- "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b"
+ "sha256:38791aa5bed922b0a844513c5f9ed37774b68edc609e5ab8ab8d8fe0ce4315e5",
+ "sha256:c8f4f0ebbc394e52ddf49de8bcc3cf8ad2b4425ebac494106bbc5e3661ac7633"
],
- "version": "==1.32.0"
+ "markers": "python_version >= '3.6'",
+ "version": "==3.2.0"
},
"toml": {
"hashes": [
- "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
- "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
+ "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f",
+ "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"
],
- "version": "==0.10.0"
+ "version": "==0.10.1"
},
"typed-ast": {
"hashes": [
@@ -755,16 +777,18 @@
},
"virtualenv": {
"hashes": [
- "sha256:0c04c7e8e0314470b4c2b43740ff68be1c62bb3fdef8309341ff1daea60d49d1",
- "sha256:1f0369d068d9761b5c1ed7b44dad1ec124727eb10bc7f4aaefbba0cdca3bd924"
+ "sha256:26cdd725a57fef4c7c22060dba4647ebd8ca377e30d1c1cf547b30a0b79c43b4",
+ "sha256:c51f1ba727d1614ce8fd62457748b469fbedfdab2c7e5dd480c9ae3fbe1233f1"
],
- "version": "==20.0.8"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==20.0.27"
},
"zipp": {
"hashes": [
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
+ "markers": "python_version >= '3.6'",
"version": "==3.1.0"
}
}
diff --git a/pydis_site/apps/api/migrations/0007_tag.py b/pydis_site/apps/api/migrations/0007_tag.py
index c22715f9..b6d146fe 100644
--- a/pydis_site/apps/api/migrations/0007_tag.py
+++ b/pydis_site/apps/api/migrations/0007_tag.py
@@ -18,6 +18,6 @@ class Migration(migrations.Migration):
('title', models.CharField(help_text='The title of this tag, shown in searches and providing a quick overview over what this embed contains.', max_length=100, primary_key=True, serialize=False)),
('embed', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual embed shown by this tag.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0009_snakefact.py b/pydis_site/apps/api/migrations/0009_snakefact.py
index 4fc63bc9..fd583846 100644
--- a/pydis_site/apps/api/migrations/0009_snakefact.py
+++ b/pydis_site/apps/api/migrations/0009_snakefact.py
@@ -16,6 +16,6 @@ class Migration(migrations.Migration):
fields=[
('fact', models.CharField(help_text='A fact about snakes.', max_length=200, primary_key=True, serialize=False)),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0010_snakeidiom.py b/pydis_site/apps/api/migrations/0010_snakeidiom.py
index be089cf4..7d06ce5f 100644
--- a/pydis_site/apps/api/migrations/0010_snakeidiom.py
+++ b/pydis_site/apps/api/migrations/0010_snakeidiom.py
@@ -16,6 +16,6 @@ class Migration(migrations.Migration):
fields=[
('idiom', models.CharField(help_text='A snake idiom', max_length=140, primary_key=True, serialize=False)),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0012_specialsnake.py b/pydis_site/apps/api/migrations/0012_specialsnake.py
index 77072526..ed0c1563 100644
--- a/pydis_site/apps/api/migrations/0012_specialsnake.py
+++ b/pydis_site/apps/api/migrations/0012_specialsnake.py
@@ -17,6 +17,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=140, primary_key=True, serialize=False)),
('info', models.TextField()),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
index dced1288..7e372d04 100644
--- a/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
+++ b/pydis_site/apps/api/migrations/0018_messagedeletioncontext.py
@@ -19,6 +19,6 @@ class Migration(migrations.Migration):
('creation', models.DateTimeField(help_text='When this deletion took place.')),
('actor', models.ForeignKey(help_text='The original actor causing this deletion. Could be the author of a manual clean command invocation, the bot when executing automatic actions, or nothing to indicate that the bulk deletion was not issued by us.', null=True, on_delete=django.db.models.deletion.CASCADE, to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0019_deletedmessage.py b/pydis_site/apps/api/migrations/0019_deletedmessage.py
index 4b028f0c..33746253 100644
--- a/pydis_site/apps/api/migrations/0019_deletedmessage.py
+++ b/pydis_site/apps/api/migrations/0019_deletedmessage.py
@@ -25,6 +25,6 @@ class Migration(migrations.Migration):
options={
'abstract': False,
},
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0020_infraction.py b/pydis_site/apps/api/migrations/0020_infraction.py
index 6bef6b77..96c71687 100644
--- a/pydis_site/apps/api/migrations/0020_infraction.py
+++ b/pydis_site/apps/api/migrations/0020_infraction.py
@@ -25,6 +25,6 @@ class Migration(migrations.Migration):
('actor', models.ForeignKey(help_text='The user which applied the infraction.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_given', to='api.User')),
('user', models.ForeignKey(help_text='The user to which the infraction was applied.', on_delete=django.db.models.deletion.CASCADE, related_name='infractions_received', to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
index 0c02cb91..c7fac012 100644
--- a/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
+++ b/pydis_site/apps/api/migrations/0025_allow_custom_inserted_at_infraction_field.py
@@ -1,7 +1,7 @@
# Generated by Django 2.1.4 on 2019-01-06 16:01
-import datetime
from django.db import migrations, models
+from django.utils import timezone
class Migration(migrations.Migration):
@@ -14,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='infraction',
name='inserted_at',
- field=models.DateTimeField(default=datetime.datetime.utcnow, help_text='The date and time of the creation of this infraction.'),
+ field=models.DateTimeField(default=timezone.now, help_text='The date and time of the creation of this infraction.'),
),
]
diff --git a/pydis_site/apps/api/migrations/0030_reminder.py b/pydis_site/apps/api/migrations/0030_reminder.py
index 8c42f6dc..e1f1afc3 100644
--- a/pydis_site/apps/api/migrations/0030_reminder.py
+++ b/pydis_site/apps/api/migrations/0030_reminder.py
@@ -22,6 +22,6 @@ class Migration(migrations.Migration):
('expiration', models.DateTimeField(help_text='When this reminder should be sent.')),
('author', models.ForeignKey(help_text='The creator of this reminder.', on_delete=django.db.models.deletion.CASCADE, to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0031_nomination.py b/pydis_site/apps/api/migrations/0031_nomination.py
index 75e69701..f39436c1 100644
--- a/pydis_site/apps/api/migrations/0031_nomination.py
+++ b/pydis_site/apps/api/migrations/0031_nomination.py
@@ -21,6 +21,6 @@ class Migration(migrations.Migration):
('inserted_at', models.DateTimeField(auto_now_add=True, help_text='The creation date of this nomination.')),
('author', models.ForeignKey(help_text='The staff member that nominated this user.', on_delete=django.db.models.deletion.CASCADE, related_name='nomination_set', to='api.User')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0032_botsetting.py b/pydis_site/apps/api/migrations/0032_botsetting.py
index 25186a2b..3304edef 100644
--- a/pydis_site/apps/api/migrations/0032_botsetting.py
+++ b/pydis_site/apps/api/migrations/0032_botsetting.py
@@ -18,6 +18,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=50, primary_key=True, serialize=False)),
('data', django.contrib.postgres.fields.jsonb.JSONField(help_text='The actual settings of this setting.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
index a8256a0e..c9a1ad19 100644
--- a/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
+++ b/pydis_site/apps/api/migrations/0035_create_table_log_entry.py
@@ -24,6 +24,6 @@ class Migration(migrations.Migration):
('line', models.PositiveSmallIntegerField(help_text='The line at which the log line was emitted.')),
('message', models.TextField(help_text='The textual content of the log line.')),
],
- bases=(pydis_site.apps.api.models.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0049_offensivemessage.py b/pydis_site/apps/api/migrations/0049_offensivemessage.py
index fe4a1961..f342cec3 100644
--- a/pydis_site/apps/api/migrations/0049_offensivemessage.py
+++ b/pydis_site/apps/api/migrations/0049_offensivemessage.py
@@ -3,7 +3,7 @@
import django.core.validators
from django.db import migrations, models
import pydis_site.apps.api.models.bot.offensive_message
-import pydis_site.apps.api.models.utils
+import pydis_site.apps.api.models.mixins
class Migration(migrations.Migration):
@@ -20,6 +20,6 @@ class Migration(migrations.Migration):
('channel_id', models.BigIntegerField(help_text='The channel ID that the message was sent in, taken from Discord.', validators=[django.core.validators.MinValueValidator(limit_value=0, message='Channel IDs cannot be negative.')])),
('delete_date', models.DateTimeField(help_text='The date on which the message will be auto-deleted.', validators=[pydis_site.apps.api.models.bot.offensive_message.future_date_validator])),
],
- bases=(pydis_site.apps.api.models.utils.ModelReprMixin, models.Model),
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
),
]
diff --git a/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py
new file mode 100644
index 00000000..e617e1c9
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0051_allow_blank_message_embeds.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.4 on 2020-03-21 17:05
+
+import django.contrib.postgres.fields
+import django.contrib.postgres.fields.jsonb
+from django.db import migrations
+import pydis_site.apps.api.models.bot.tag
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0050_remove_infractions_active_default_value'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='deletedmessage',
+ name='embeds',
+ field=django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.jsonb.JSONField(validators=[pydis_site.apps.api.models.bot.tag.validate_tag_embed]), blank=True, help_text='Embeds attached to this message.', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py
new file mode 100644
index 00000000..26b3b954
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0052_remove_user_avatar_hash.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.11 on 2020-05-27 07:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0051_create_news_setting'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='user',
+ name='avatar_hash',
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0053_user_roles_to_array.py b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py
new file mode 100644
index 00000000..7ff3a548
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0053_user_roles_to_array.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.2.11 on 2020-06-02 13:42
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0052_remove_user_avatar_hash'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='user',
+ name='roles',
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.')]), default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py
new file mode 100644
index 00000000..96230015
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0054_user_invalidate_unknown_role.py
@@ -0,0 +1,21 @@
+# Generated by Django 2.2.11 on 2020-06-02 20:08
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+import pydis_site.apps.api.models.bot.user
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0053_user_roles_to_array'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py
new file mode 100644
index 00000000..f2a0e638
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0055_merge_20200714_2027.py
@@ -0,0 +1,14 @@
+# Generated by Django 3.0.8 on 2020-07-14 20:27
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0051_allow_blank_message_embeds'),
+ ('api', '0054_user_invalidate_unknown_role'),
+ ]
+
+ operations = [
+ ]
diff --git a/pydis_site/apps/api/migrations/0055_reminder_mentions.py b/pydis_site/apps/api/migrations/0055_reminder_mentions.py
new file mode 100644
index 00000000..d73b450d
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0055_reminder_mentions.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.14 on 2020-07-15 07:37
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0054_user_invalidate_unknown_role'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='reminder',
+ name='mentions',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Mention IDs cannot be negative.')]), blank=True, default=list, help_text='IDs of roles or users to ping with the reminder.', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py
new file mode 100644
index 00000000..489941c7
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0056_allow_blank_user_roles.py
@@ -0,0 +1,21 @@
+# Generated by Django 3.0.8 on 2020-07-14 20:35
+
+import django.contrib.postgres.fields
+import django.core.validators
+from django.db import migrations, models
+import pydis_site.apps.api.models.bot.user
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0055_merge_20200714_2027'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='user',
+ name='roles',
+ field=django.contrib.postgres.fields.ArrayField(base_field=models.BigIntegerField(validators=[django.core.validators.MinValueValidator(limit_value=0, message='Role IDs cannot be negative.'), pydis_site.apps.api.models.bot.user._validate_existing_role]), blank=True, default=list, help_text='IDs of roles the user has on the server', size=None),
+ ),
+ ]
diff --git a/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py
new file mode 100644
index 00000000..47a6d2d4
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0057_merge_20200716_0751.py
@@ -0,0 +1,14 @@
+# Generated by Django 2.2.14 on 2020-07-16 07:51
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0055_reminder_mentions'),
+ ('api', '0056_allow_blank_user_roles'),
+ ]
+
+ operations = [
+ ]
diff --git a/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py
new file mode 100644
index 00000000..aecfdad7
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0058_create_new_filterlist_model.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.0.8 on 2020-07-15 11:23
+
+from django.db import migrations, models
+import pydis_site.apps.api.models.mixins
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('api', '0057_merge_20200716_0751'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='FilterList',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('type', models.CharField(
+ choices=[('GUILD_INVITE', 'Guild Invite'), ('FILE_FORMAT', 'File Format'),
+ ('DOMAIN_NAME', 'Domain Name'), ('FILTER_TOKEN', 'Filter Token')],
+ help_text='The type of allowlist this is on.', max_length=50)),
+ ('allowed', models.BooleanField(help_text='Whether this item is on the allowlist or the denylist.')),
+ ('content', models.TextField(help_text='The data to add to the allow or denylist.')),
+ ('comment', models.TextField(help_text="Optional comment on this entry.", null=True)),
+ ],
+ bases=(pydis_site.apps.api.models.mixins.ModelReprMixin, models.Model),
+ ),
+ migrations.AddConstraint(
+ model_name='filterlist',
+ constraint=models.UniqueConstraint(fields=('content', 'type'), name='unique_filter_list')
+ )
+ ]
diff --git a/pydis_site/apps/api/migrations/0059_populate_filterlists.py b/pydis_site/apps/api/migrations/0059_populate_filterlists.py
new file mode 100644
index 00000000..8c550191
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0059_populate_filterlists.py
@@ -0,0 +1,153 @@
+from django.db import migrations
+
+guild_invite_whitelist = [
+ ("discord.gg/python", "Python Discord", True),
+ ("discord.gg/4JJdJKb", "RLBot", True),
+ ("discord.gg/djPtTRJ", "Kivy", True),
+ ("discord.gg/QXyegWe", "Pyglet", True),
+ ("discord.gg/9XsucTT", "Panda3D", True),
+ ("discord.gg/AP3rq2k", "PyWeek", True),
+ ("discord.gg/vSPsP9t", "Microsoft Python", True),
+ ("discord.gg/bRCvFy9", "Discord.js Official", True),
+ ("discord.gg/9zT7NHP", "Programming Discussions", True),
+ ("discord.gg/ysd6M4r", "JetBrains Community", True),
+ ("discord.gg/4xJeCgy", "Raspberry Pie", True),
+ ("discord.gg/AStb3kZ", "Ren'Py", True),
+ ("discord.gg/t655QNV", "Python Discord: Emojis 1", True),
+ ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True),
+ ("discord.gg/jTtgWuy", "Django", True),
+ ("discord.gg/W9BypZF", "STEM", True),
+ ("discord.gg/dpy", "discord.py", True),
+ ("discord.gg/programming", "Programmers Hangout", True),
+ ("discord.gg/qhGUjGD", "SpeakJS", True),
+ ("discord.gg/eTbWSZj", "Functional Programming", True),
+ ("discord.gg/r8yreB6", "PyGame", True),
+ ("discord.gg/5UBnR3P", "Python Atlanta", True),
+ ("discord.gg/ccyrDKv", "C#", True),
+]
+
+domain_name_blacklist = [
+ ("pornhub.com", None, False),
+ ("liveleak.com", None, False),
+ ("grabify.link", None, False),
+ ("bmwforum.co", None, False),
+ ("leancoding.co", None, False),
+ ("spottyfly.com", None, False),
+ ("stopify.co", None, False),
+ ("yoütu.be", None, False),
+ ("discörd.com", None, False),
+ ("minecräft.com", None, False),
+ ("freegiftcards.co", None, False),
+ ("disçordapp.com", None, False),
+ ("fortnight.space", None, False),
+ ("fortnitechat.site", None, False),
+ ("joinmy.site", None, False),
+ ("curiouscat.club", None, False),
+ ("catsnthings.fun", None, False),
+ ("yourtube.site", None, False),
+ ("youtubeshort.watch", None, False),
+ ("catsnthing.com", None, False),
+ ("youtubeshort.pro", None, False),
+ ("canadianlumberjacks.online", None, False),
+ ("poweredbydialup.club", None, False),
+ ("poweredbydialup.online", None, False),
+ ("poweredbysecurity.org", None, False),
+ ("poweredbysecurity.online", None, False),
+ ("ssteam.site", None, False),
+ ("steamwalletgift.com", None, False),
+ ("discord.gift", None, False),
+ ("lmgtfy.com", None, False),
+]
+
+filter_token_blacklist = [
+ ("\bgoo+ks*\b", None, False),
+ ("\bky+s+\b", None, False),
+ ("\bki+ke+s*\b", None, False),
+ ("\bbeaner+s?\b", None, False),
+ ("\bcoo+ns*\b", None, False),
+ ("\bnig+lets*\b", None, False),
+ ("\bslant-eyes*\b", None, False),
+ ("\btowe?l-?head+s*\b", None, False),
+ ("\bchi*n+k+s*\b", None, False),
+ ("\bspick*s*\b", None, False),
+ ("\bkill* +(?:yo)?urself+\b", None, False),
+ ("\bjew+s*\b", None, False),
+ ("\bsuicide\b", None, False),
+ ("\brape\b", None, False),
+ ("\b(re+)tar+(d+|t+)(ed)?\b", None, False),
+ ("\bta+r+d+\b", None, False),
+ ("\bcunts*\b", None, False),
+ ("\btrann*y\b", None, False),
+ ("\bshemale\b", None, False),
+ ("fa+g+s*", None, False),
+ ("卐", None, False),
+ ("卍", None, False),
+ ("࿖", None, False),
+ ("࿕", None, False),
+ ("࿘", None, False),
+ ("࿗", None, False),
+ ("cuck(?!oo+)", None, False),
+ ("nigg+(?:e*r+|a+h*?|u+h+)s?", None, False),
+ ("fag+o+t+s*", None, False),
+]
+
+file_format_whitelist = [
+ (".3gp", None, True),
+ (".3g2", None, True),
+ (".avi", None, True),
+ (".bmp", None, True),
+ (".gif", None, True),
+ (".h264", None, True),
+ (".jpg", None, True),
+ (".jpeg", None, True),
+ (".m4v", None, True),
+ (".mkv", None, True),
+ (".mov", None, True),
+ (".mp4", None, True),
+ (".mpeg", None, True),
+ (".mpg", None, True),
+ (".png", None, True),
+ (".tiff", None, True),
+ (".wmv", None, True),
+ (".svg", None, True),
+ (".psd", "Photoshop", True),
+ (".ai", "Illustrator", True),
+ (".aep", "After Effects", True),
+ (".xcf", "GIMP", True),
+ (".mp3", None, True),
+ (".wav", None, True),
+ (".ogg", None, True),
+ (".webm", None, True),
+ (".webp", None, True),
+]
+
+populate_data = {
+ "FILTER_TOKEN": filter_token_blacklist,
+ "DOMAIN_NAME": domain_name_blacklist,
+ "FILE_FORMAT": file_format_whitelist,
+ "GUILD_INVITE": guild_invite_whitelist,
+}
+
+
+class Migration(migrations.Migration):
+ dependencies = [("api", "0058_create_new_filterlist_model")]
+
+ def populate_filterlists(app, _):
+ FilterList = app.get_model("api", "FilterList")
+
+ for filterlist_type, metadata in populate_data.items():
+ for content, comment, allowed in metadata:
+ FilterList.objects.create(
+ type=filterlist_type,
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ def clear_filterlists(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.all().delete()
+
+ operations = [
+ migrations.RunPython(populate_filterlists, clear_filterlists)
+ ]
diff --git a/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py
new file mode 100644
index 00000000..53846f02
--- /dev/null
+++ b/pydis_site/apps/api/migrations/0060_populate_filterlists_fix.py
@@ -0,0 +1,85 @@
+from django.db import migrations
+
+bad_guild_invite_whitelist = [
+ ("discord.gg/python", "Python Discord", True),
+ ("discord.gg/4JJdJKb", "RLBot", True),
+ ("discord.gg/djPtTRJ", "Kivy", True),
+ ("discord.gg/QXyegWe", "Pyglet", True),
+ ("discord.gg/9XsucTT", "Panda3D", True),
+ ("discord.gg/AP3rq2k", "PyWeek", True),
+ ("discord.gg/vSPsP9t", "Microsoft Python", True),
+ ("discord.gg/bRCvFy9", "Discord.js Official", True),
+ ("discord.gg/9zT7NHP", "Programming Discussions", True),
+ ("discord.gg/ysd6M4r", "JetBrains Community", True),
+ ("discord.gg/4xJeCgy", "Raspberry Pie", True),
+ ("discord.gg/AStb3kZ", "Ren'Py", True),
+ ("discord.gg/t655QNV", "Python Discord: Emojis 1", True),
+ ("discord.gg/vRZPkqC", "Python Discord: Emojis 2", True),
+ ("discord.gg/jTtgWuy", "Django", True),
+ ("discord.gg/W9BypZF", "STEM", True),
+ ("discord.gg/dpy", "discord.py", True),
+ ("discord.gg/programming", "Programmers Hangout", True),
+ ("discord.gg/qhGUjGD", "SpeakJS", True),
+ ("discord.gg/eTbWSZj", "Functional Programming", True),
+ ("discord.gg/r8yreB6", "PyGame", True),
+ ("discord.gg/5UBnR3P", "Python Atlanta", True),
+ ("discord.gg/ccyrDKv", "C#", True),
+]
+
+guild_invite_whitelist = [
+ ("267624335836053506", "Python Discord", True),
+ ("348658686962696195", "RLBot", True),
+ ("423249981340778496", "Kivy", True),
+ ("438622377094414346", "Pyglet", True),
+ ("524691714909274162", "Panda3D", True),
+ ("666560367173828639", "PyWeek", True),
+ ("702724176489873509", "Microsoft Python", True),
+ ("222078108977594368", "Discord.js Official", True),
+ ("238666723824238602", "Programming Discussions", True),
+ ("433980600391696384", "JetBrains Community", True),
+ ("204621105720328193", "Raspberry Pie", True),
+ ("286633898581164032", "Ren'Py", True),
+ ("440186186024222721", "Python Discord: Emojis 1", True),
+ ("578587418123304970", "Python Discord: Emojis 2", True),
+ ("159039020565790721", "Django", True),
+ ("273944235143593984", "STEM", True),
+ ("336642139381301249", "discord.py", True),
+ ("244230771232079873", "Programmers Hangout", True),
+ ("239433591950540801", "SpeakJS", True),
+ ("280033776820813825", "Functional Programming", True),
+ ("349505959032389632", "PyGame", True),
+ ("488751051629920277", "Python Atlanta", True),
+ ("143867839282020352", "C#", True),
+]
+
+
+class Migration(migrations.Migration):
+ dependencies = [("api", "0059_populate_filterlists")]
+
+ def fix_filterlist(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.filter(type="GUILD_INVITE").delete() # Clear out the stuff added in 0059.
+
+ for content, comment, allowed in guild_invite_whitelist:
+ FilterList.objects.create(
+ type="GUILD_INVITE",
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ def restore_bad_filterlist(app, _):
+ FilterList = app.get_model("api", "FilterList")
+ FilterList.objects.filter(type="GUILD_INVITE").delete()
+
+ for content, comment, allowed in bad_guild_invite_whitelist:
+ FilterList.objects.create(
+ type="GUILD_INVITE",
+ allowed=allowed,
+ content=content,
+ comment=comment,
+ )
+
+ operations = [
+ migrations.RunPython(fix_filterlist, restore_bad_filterlist)
+ ]
diff --git a/pydis_site/apps/api/models/__init__.py b/pydis_site/apps/api/models/__init__.py
index 450d18cd..1d0ab7ea 100644
--- a/pydis_site/apps/api/models/__init__.py
+++ b/pydis_site/apps/api/models/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa
from .bot import (
+ FilterList,
BotSetting,
DocumentationLink,
DeletedMessage,
@@ -15,4 +16,3 @@ from .bot import (
User
)
from .log_entry import LogEntry
-from .utils import ModelReprMixin
diff --git a/pydis_site/apps/api/models/bot/__init__.py b/pydis_site/apps/api/models/bot/__init__.py
index 8ae47746..efd98184 100644
--- a/pydis_site/apps/api/models/bot/__init__.py
+++ b/pydis_site/apps/api/models/bot/__init__.py
@@ -1,4 +1,5 @@
# flake8: noqa
+from .filter_list import FilterList
from .bot_setting import BotSetting
from .deleted_message import DeletedMessage
from .documentation_link import DocumentationLink
diff --git a/pydis_site/apps/api/models/bot/bot_setting.py b/pydis_site/apps/api/models/bot/bot_setting.py
index 8d48eac7..2a3944f8 100644
--- a/pydis_site/apps/api/models/bot/bot_setting.py
+++ b/pydis_site/apps/api/models/bot/bot_setting.py
@@ -2,7 +2,7 @@ from django.contrib.postgres import fields as pgfields
from django.core.exceptions import ValidationError
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def validate_bot_setting_name(name: str) -> None:
diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py
index f844ae04..5a46460b 100644
--- a/pydis_site/apps/api/models/bot/documentation_link.py
+++ b/pydis_site/apps/api/models/bot/documentation_link.py
@@ -1,6 +1,6 @@
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class DocumentationLink(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/filter_list.py b/pydis_site/apps/api/models/bot/filter_list.py
new file mode 100644
index 00000000..d279e137
--- /dev/null
+++ b/pydis_site/apps/api/models/bot/filter_list.py
@@ -0,0 +1,41 @@
+from django.db import models
+
+from pydis_site.apps.api.models.mixins import ModelReprMixin, ModelTimestampMixin
+
+
+class FilterList(ModelTimestampMixin, ModelReprMixin, models.Model):
+ """An item that is either allowed or denied."""
+
+ FilterListType = models.TextChoices(
+ 'FilterListType',
+ 'GUILD_INVITE '
+ 'FILE_FORMAT '
+ 'DOMAIN_NAME '
+ 'FILTER_TOKEN '
+ )
+ type = models.CharField(
+ max_length=50,
+ help_text="The type of allowlist this is on.",
+ choices=FilterListType.choices,
+ )
+ allowed = models.BooleanField(
+ help_text="Whether this item is on the allowlist or the denylist."
+ )
+ content = models.TextField(
+ help_text="The data to add to the allow or denylist."
+ )
+ comment = models.TextField(
+ help_text="Optional comment on this entry.",
+ null=True
+ )
+
+ class Meta:
+ """Metaconfig for this model."""
+
+ # This constraint ensures only one filterlist with the
+ # same content can exist. This means that we cannot have both an allow
+ # and a deny for the same item, and we cannot have duplicates of the
+ # same item.
+ constraints = [
+ models.UniqueConstraint(fields=['content', 'type'], name='unique_filter_list'),
+ ]
diff --git a/pydis_site/apps/api/models/bot/infraction.py b/pydis_site/apps/api/models/bot/infraction.py
index f58e89a3..7660cbba 100644
--- a/pydis_site/apps/api/models/bot/infraction.py
+++ b/pydis_site/apps/api/models/bot/infraction.py
@@ -2,7 +2,7 @@ from django.db import models
from django.utils import timezone
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Infraction(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py
index 8b18fc9f..78dcbf1d 100644
--- a/pydis_site/apps/api/models/bot/message.py
+++ b/pydis_site/apps/api/models/bot/message.py
@@ -7,7 +7,7 @@ from django.utils import timezone
from pydis_site.apps.api.models.bot.tag import validate_tag_embed
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Message(ModelReprMixin, models.Model):
@@ -49,6 +49,7 @@ class Message(ModelReprMixin, models.Model):
pgfields.JSONField(
validators=(validate_tag_embed,)
),
+ blank=True,
help_text="Embeds attached to this message."
)
attachments = pgfields.ArrayField(
diff --git a/pydis_site/apps/api/models/bot/message_deletion_context.py b/pydis_site/apps/api/models/bot/message_deletion_context.py
index 44a0c8ae..04ae8d34 100644
--- a/pydis_site/apps/api/models/bot/message_deletion_context.py
+++ b/pydis_site/apps/api/models/bot/message_deletion_context.py
@@ -1,7 +1,7 @@
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class MessageDeletionContext(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py
index cd9951aa..21e34e87 100644
--- a/pydis_site/apps/api/models/bot/nomination.py
+++ b/pydis_site/apps/api/models/bot/nomination.py
@@ -1,7 +1,7 @@
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Nomination(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/off_topic_channel_name.py b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
index 413cbfae..403c7465 100644
--- a/pydis_site/apps/api/models/bot/off_topic_channel_name.py
+++ b/pydis_site/apps/api/models/bot/off_topic_channel_name.py
@@ -1,7 +1,7 @@
from django.core.validators import RegexValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class OffTopicChannelName(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/offensive_message.py b/pydis_site/apps/api/models/bot/offensive_message.py
index b466d9c2..6c0e5ffb 100644
--- a/pydis_site/apps/api/models/bot/offensive_message.py
+++ b/pydis_site/apps/api/models/bot/offensive_message.py
@@ -4,7 +4,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def future_date_validator(date: datetime.date) -> None:
diff --git a/pydis_site/apps/api/models/bot/reminder.py b/pydis_site/apps/api/models/bot/reminder.py
index d53fedb5..7d968a0e 100644
--- a/pydis_site/apps/api/models/bot/reminder.py
+++ b/pydis_site/apps/api/models/bot/reminder.py
@@ -1,8 +1,9 @@
+from django.contrib.postgres.fields import ArrayField
from django.core.validators import MinValueValidator
from django.db import models
from pydis_site.apps.api.models.bot.user import User
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Reminder(ModelReprMixin, models.Model):
@@ -45,6 +46,19 @@ class Reminder(ModelReprMixin, models.Model):
expiration = models.DateTimeField(
help_text="When this reminder should be sent."
)
+ mentions = ArrayField(
+ models.BigIntegerField(
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Mention IDs cannot be negative."
+ ),
+ )
+ ),
+ default=list,
+ blank=True,
+ help_text="IDs of roles or users to ping with the reminder."
+ )
def __str__(self):
"""Returns some info on the current reminder, for display purposes."""
diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py
index 58bbf8b4..721e4815 100644
--- a/pydis_site/apps/api/models/bot/role.py
+++ b/pydis_site/apps/api/models/bot/role.py
@@ -3,7 +3,7 @@ from __future__ import annotations
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class Role(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/bot/tag.py b/pydis_site/apps/api/models/bot/tag.py
index 5d4cc393..5e53582f 100644
--- a/pydis_site/apps/api/models/bot/tag.py
+++ b/pydis_site/apps/api/models/bot/tag.py
@@ -6,7 +6,7 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxLengthValidator, MinLengthValidator
from django.db import models
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
def is_bool_validator(value: Any) -> None:
diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py
index 5140d2bf..cd2d58b9 100644
--- a/pydis_site/apps/api/models/bot/user.py
+++ b/pydis_site/apps/api/models/bot/user.py
@@ -1,8 +1,18 @@
+from django.contrib.postgres.fields import ArrayField
+from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from pydis_site.apps.api.models.bot.role import Role
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
+
+
+def _validate_existing_role(value: int) -> None:
+ """Validate that a role exists when given in to the user model."""
+ role = Role.objects.filter(id=value)
+
+ if not role:
+ raise ValidationError(f"Role with ID {value} does not exist")
class User(ModelReprMixin, models.Model):
@@ -31,17 +41,19 @@ class User(ModelReprMixin, models.Model):
),
help_text="The discriminator of this user, taken from Discord."
)
- avatar_hash = models.CharField(
- max_length=100,
- help_text=(
- "The user's avatar hash, taken from Discord. "
- "Null if the user does not have any custom avatar."
+ roles = ArrayField(
+ models.BigIntegerField(
+ validators=(
+ MinValueValidator(
+ limit_value=0,
+ message="Role IDs cannot be negative."
+ ),
+ _validate_existing_role
+ )
),
- null=True
- )
- roles = models.ManyToManyField(
- Role,
- help_text="Any roles this user has on our server."
+ default=list,
+ blank=True,
+ help_text="IDs of roles the user has on the server"
)
in_guild = models.BooleanField(
default=True,
@@ -59,7 +71,7 @@ class User(ModelReprMixin, models.Model):
This will fall back to the Developers role if the user does not have any roles.
"""
- roles = self.roles.all()
+ roles = Role.objects.filter(id__in=self.roles)
if not roles:
return Role.objects.get(name="Developers")
- return max(self.roles.all())
+ return max(roles)
diff --git a/pydis_site/apps/api/models/log_entry.py b/pydis_site/apps/api/models/log_entry.py
index 488af48e..752cd2ca 100644
--- a/pydis_site/apps/api/models/log_entry.py
+++ b/pydis_site/apps/api/models/log_entry.py
@@ -1,7 +1,7 @@
from django.db import models
from django.utils import timezone
-from pydis_site.apps.api.models.utils import ModelReprMixin
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class LogEntry(ModelReprMixin, models.Model):
diff --git a/pydis_site/apps/api/models/utils.py b/pydis_site/apps/api/models/mixins.py
index 0540c4de..5d75b78b 100644
--- a/pydis_site/apps/api/models/utils.py
+++ b/pydis_site/apps/api/models/mixins.py
@@ -1,5 +1,7 @@
from operator import itemgetter
+from django.db import models
+
class ModelReprMixin:
"""Mixin providing a `__repr__()` to display model class name and initialisation parameters."""
@@ -15,3 +17,15 @@ class ModelReprMixin:
if not attribute.startswith('_')
)
return f'<{self.__class__.__name__}({attributes})>'
+
+
+class ModelTimestampMixin(models.Model):
+ """Mixin providing created_at and updated_at fields."""
+
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ """Metaconfig for the mixin."""
+
+ abstract = True
diff --git a/pydis_site/apps/api/serializers.py b/pydis_site/apps/api/serializers.py
index e11c4af2..52e0d972 100644
--- a/pydis_site/apps/api/serializers.py
+++ b/pydis_site/apps/api/serializers.py
@@ -4,13 +4,20 @@ from rest_framework.validators import UniqueTogetherValidator
from rest_framework_bulk import BulkSerializerMixin
from .models import (
- BotSetting, DeletedMessage,
- DocumentationLink, Infraction,
- LogEntry, MessageDeletionContext,
- Nomination, OffTopicChannelName,
+ BotSetting,
+ DeletedMessage,
+ DocumentationLink,
+ FilterList,
+ Infraction,
+ LogEntry,
+ MessageDeletionContext,
+ Nomination,
+ OffTopicChannelName,
OffensiveMessage,
- Reminder, Role,
- Tag, User
+ Reminder,
+ Role,
+ Tag,
+ User
)
@@ -97,6 +104,31 @@ class DocumentationLinkSerializer(ModelSerializer):
fields = ('package', 'base_url', 'inventory_url')
+class FilterListSerializer(ModelSerializer):
+ """A class providing (de-)serialization of `FilterList` instances."""
+
+ class Meta:
+ """Metadata defined for the Django REST Framework."""
+
+ model = FilterList
+ fields = ('id', 'created_at', 'updated_at', 'type', 'allowed', 'content', 'comment')
+
+ # This validator ensures only one filterlist with the
+ # same content can exist. This means that we cannot have both an allow
+ # and a deny for the same item, and we cannot have duplicates of the
+ # same item.
+ validators = [
+ UniqueTogetherValidator(
+ queryset=FilterList.objects.all(),
+ fields=['content', 'type'],
+ message=(
+ "A filterlist for this item already exists. "
+ "Please note that you cannot add the same item to both allow and deny."
+ )
+ ),
+ ]
+
+
class InfractionSerializer(ModelSerializer):
"""A class providing (de-)serialization of `Infraction` instances."""
@@ -203,7 +235,9 @@ class ReminderSerializer(ModelSerializer):
"""Metadata defined for the Django REST Framework."""
model = Reminder
- fields = ('active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id')
+ fields = (
+ 'active', 'author', 'jump_url', 'channel_id', 'content', 'expiration', 'id', 'mentions'
+ )
class RoleSerializer(ModelSerializer):
@@ -229,13 +263,11 @@ class TagSerializer(ModelSerializer):
class UserSerializer(BulkSerializerMixin, ModelSerializer):
"""A class providing (de-)serialization of `User` instances."""
- roles = PrimaryKeyRelatedField(many=True, queryset=Role.objects.all(), required=False)
-
class Meta:
"""Metadata defined for the Django REST Framework."""
model = User
- fields = ('id', 'avatar_hash', 'name', 'discriminator', 'roles', 'in_guild')
+ fields = ('id', 'name', 'discriminator', 'roles', 'in_guild')
depth = 1
diff --git a/pydis_site/apps/api/tests/test_deleted_messages.py b/pydis_site/apps/api/tests/test_deleted_messages.py
index fb93cae6..f079a8dd 100644
--- a/pydis_site/apps/api/tests/test_deleted_messages.py
+++ b/pydis_site/apps/api/tests/test_deleted_messages.py
@@ -13,7 +13,6 @@ class DeletedMessagesWithoutActorTests(APISubdomainTestCase):
id=55,
name='Robbie Rotten',
discriminator=55,
- avatar_hash=None
)
cls.data = {
@@ -54,7 +53,6 @@ class DeletedMessagesWithActorTests(APISubdomainTestCase):
id=12904,
name='Joe Armstrong',
discriminator=1245,
- avatar_hash=None
)
cls.data = {
diff --git a/pydis_site/apps/api/tests/test_filterlists.py b/pydis_site/apps/api/tests/test_filterlists.py
new file mode 100644
index 00000000..188c0fff
--- /dev/null
+++ b/pydis_site/apps/api/tests/test_filterlists.py
@@ -0,0 +1,122 @@
+from django_hosts.resolvers import reverse
+
+from pydis_site.apps.api.models import FilterList
+from pydis_site.apps.api.tests.base import APISubdomainTestCase
+
+URL = reverse('bot:filterlist-list', host='api')
+JPEG_ALLOWLIST = {
+ "type": 'FILE_FORMAT',
+ "allowed": True,
+ "content": ".jpeg",
+}
+PNG_ALLOWLIST = {
+ "type": 'FILE_FORMAT',
+ "allowed": True,
+ "content": ".png",
+}
+
+
+class UnauthenticatedTests(APISubdomainTestCase):
+ def setUp(self):
+ super().setUp()
+ self.client.force_authenticate(user=None)
+
+ def test_cannot_read_allowedlist_list(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 401)
+
+
+class EmptyDatabaseTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+
+ def test_returns_empty_object(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json(), [])
+
+
+class FetchTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+ cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST)
+ cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST)
+
+ def test_returns_name_in_list(self):
+ response = self.client.get(URL)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json()[0]["content"], self.jpeg_format.content)
+ self.assertEqual(response.json()[1]["content"], self.png_format.content)
+
+ def test_returns_single_item_by_id(self):
+ response = self.client.get(f'{URL}/{self.jpeg_format.id}')
+
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.json().get("content"), self.jpeg_format.content)
+
+ def test_returns_filter_list_types(self):
+ response = self.client.get(f'{URL}/get-types')
+
+ self.assertEqual(response.status_code, 200)
+ for api_type, model_type in zip(response.json(), FilterList.FilterListType.choices):
+ self.assertEquals(api_type[0], model_type[0])
+ self.assertEquals(api_type[1], model_type[1])
+
+
+class CreationTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+
+ def test_returns_400_for_missing_params(self):
+ no_type_json = {
+ "allowed": True,
+ "content": ".jpeg"
+ }
+ no_allowed_json = {
+ "type": "FILE_FORMAT",
+ "content": ".jpeg"
+ }
+ no_content_json = {
+ "allowed": True,
+ "type": "FILE_FORMAT"
+ }
+ cases = [{}, no_type_json, no_allowed_json, no_content_json]
+
+ for case in cases:
+ with self.subTest(case=case):
+ response = self.client.post(URL, data=case)
+ self.assertEqual(response.status_code, 400)
+
+ def test_returns_201_for_successful_creation(self):
+ response = self.client.post(URL, data=JPEG_ALLOWLIST)
+ self.assertEqual(response.status_code, 201)
+
+ def test_returns_400_for_duplicate_creation(self):
+ self.client.post(URL, data=JPEG_ALLOWLIST)
+ response = self.client.post(URL, data=JPEG_ALLOWLIST)
+ self.assertEqual(response.status_code, 400)
+
+
+class DeletionTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ FilterList.objects.all().delete()
+ cls.jpeg_format = FilterList.objects.create(**JPEG_ALLOWLIST)
+ cls.png_format = FilterList.objects.create(**PNG_ALLOWLIST)
+
+ def test_deleting_unknown_id_returns_404(self):
+ response = self.client.delete(f"{URL}/200")
+ self.assertEqual(response.status_code, 404)
+
+ def test_deleting_known_id_returns_204(self):
+ response = self.client.delete(f"{URL}/{self.jpeg_format.id}")
+ self.assertEqual(response.status_code, 204)
+
+ response = self.client.get(f"{URL}/{self.jpeg_format.id}")
+ self.assertNotIn(self.png_format.content, response.json())
diff --git a/pydis_site/apps/api/tests/test_infractions.py b/pydis_site/apps/api/tests/test_infractions.py
index bc258b77..93ef8171 100644
--- a/pydis_site/apps/api/tests/test_infractions.py
+++ b/pydis_site/apps/api/tests/test_infractions.py
@@ -47,7 +47,6 @@ class InfractionTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.ban_hidden = Infraction.objects.create(
user_id=cls.user.id,
@@ -169,13 +168,11 @@ class CreationTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.second_user = User.objects.create(
id=6,
name='carl',
discriminator=2,
- avatar_hash=None
)
def test_accepts_valid_data(self):
@@ -522,7 +519,6 @@ class ExpandedTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
cls.kick = Infraction.objects.create(
user_id=cls.user.id,
@@ -540,7 +536,7 @@ class ExpandedTests(APISubdomainTestCase):
def check_expanded_fields(self, infraction):
for key in ('user', 'actor'):
obj = infraction[key]
- for field in ('id', 'name', 'discriminator', 'avatar_hash', 'roles', 'in_guild'):
+ for field in ('id', 'name', 'discriminator', 'roles', 'in_guild'):
self.assertTrue(field in obj, msg=f'field "{field}" missing from {key}')
def test_list_expanded(self):
@@ -599,7 +595,6 @@ class SerializerTests(APISubdomainTestCase):
id=5,
name='james',
discriminator=1,
- avatar_hash=None
)
def create_infraction(self, _type: str, active: bool):
diff --git a/pydis_site/apps/api/tests/test_models.py b/pydis_site/apps/api/tests/test_models.py
index a97d3251..e0e347bb 100644
--- a/pydis_site/apps/api/tests/test_models.py
+++ b/pydis_site/apps/api/tests/test_models.py
@@ -3,13 +3,12 @@ from datetime import datetime as dt
from django.test import SimpleTestCase
from django.utils import timezone
-from ..models import (
+from pydis_site.apps.api.models import (
DeletedMessage,
DocumentationLink,
Infraction,
Message,
MessageDeletionContext,
- ModelReprMixin,
Nomination,
OffTopicChannelName,
OffensiveMessage,
@@ -18,6 +17,7 @@ from ..models import (
Tag,
User
)
+from pydis_site.apps.api.models.mixins import ModelReprMixin
class SimpleClass(ModelReprMixin):
@@ -39,12 +39,14 @@ class StringDunderMethodTests(SimpleTestCase):
self.nomination = Nomination(
id=123,
actor=User(
- id=9876, name='Mr. Hemlock',
- discriminator=6666, avatar_hash=None
+ id=9876,
+ name='Mr. Hemlock',
+ discriminator=6666,
),
user=User(
- id=9876, name="Hemlock's Cat",
- discriminator=7777, avatar_hash=None
+ id=9876,
+ name="Hemlock's Cat",
+ discriminator=7777,
),
reason="He purrrrs like the best!",
)
@@ -53,15 +55,17 @@ class StringDunderMethodTests(SimpleTestCase):
DeletedMessage(
id=45,
author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
+ id=444,
+ name='bill',
+ discriminator=5,
),
channel_id=666,
content="wooey",
deletion_context=MessageDeletionContext(
actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
+ id=5555,
+ name='shawn',
+ discriminator=555,
),
creation=dt.utcnow()
),
@@ -84,8 +88,9 @@ class StringDunderMethodTests(SimpleTestCase):
Message(
id=45,
author=User(
- id=444, name='bill',
- discriminator=5, avatar_hash=None
+ id=444,
+ name='bill',
+ discriminator=5,
),
channel_id=666,
content="wooey",
@@ -93,8 +98,9 @@ class StringDunderMethodTests(SimpleTestCase):
),
MessageDeletionContext(
actor=User(
- id=5555, name='shawn',
- discriminator=555, avatar_hash=None
+ id=5555,
+ name='shawn',
+ discriminator=555,
),
creation=dt.utcnow()
),
@@ -103,22 +109,29 @@ class StringDunderMethodTests(SimpleTestCase):
embed={'content': "the builder"}
),
User(
- id=5, name='bob',
- discriminator=1, avatar_hash=None
+ id=5,
+ name='bob',
+ discriminator=1,
),
Infraction(
- user_id=5, actor_id=5,
- type='kick', reason='He terk my jerb!'
+ user_id=5,
+ actor_id=5,
+ type='kick',
+ reason='He terk my jerb!'
),
Infraction(
- user_id=5, actor_id=5, hidden=True,
- type='kick', reason='He terk my jerb!',
+ user_id=5,
+ actor_id=5,
+ hidden=True,
+ type='kick',
+ reason='He terk my jerb!',
expires_at=dt(5018, 11, 20, 15, 52, tzinfo=timezone.utc)
),
Reminder(
author=User(
- id=452, name='billy',
- discriminator=5, avatar_hash=None
+ id=452,
+ name='billy',
+ discriminator=5,
),
channel_id=555,
jump_url=(
diff --git a/pydis_site/apps/api/tests/test_nominations.py b/pydis_site/apps/api/tests/test_nominations.py
index 76cb4112..92c62c87 100644
--- a/pydis_site/apps/api/tests/test_nominations.py
+++ b/pydis_site/apps/api/tests/test_nominations.py
@@ -13,7 +13,6 @@ class CreationTests(APISubdomainTestCase):
id=1234,
name='joe dart',
discriminator=1111,
- avatar_hash=None
)
def test_accepts_valid_data(self):
@@ -190,7 +189,6 @@ class NominationTests(APISubdomainTestCase):
id=1234,
name='joe dart',
discriminator=1111,
- avatar_hash=None
)
cls.active_nomination = Nomination.objects.create(
diff --git a/pydis_site/apps/api/tests/test_reminders.py b/pydis_site/apps/api/tests/test_reminders.py
index 3441e0cc..9dffb668 100644
--- a/pydis_site/apps/api/tests/test_reminders.py
+++ b/pydis_site/apps/api/tests/test_reminders.py
@@ -53,7 +53,6 @@ class ReminderCreationTests(APISubdomainTestCase):
id=1234,
name='Mermaid Man',
discriminator=1234,
- avatar_hash=None,
)
def test_accepts_valid_data(self):
@@ -63,6 +62,7 @@ class ReminderCreationTests(APISubdomainTestCase):
'expiration': datetime.utcnow().isoformat(),
'jump_url': "https://www.google.com",
'channel_id': 123,
+ 'mentions': [8888, 9999],
}
url = reverse('bot:reminder-list', host='api')
response = self.client.post(url, data=data)
@@ -86,7 +86,6 @@ class ReminderDeletionTests(APISubdomainTestCase):
id=6789,
name='Barnacle Boy',
discriminator=6789,
- avatar_hash=None,
)
cls.reminder = Reminder.objects.create(
@@ -118,7 +117,6 @@ class ReminderListTests(APISubdomainTestCase):
id=6789,
name='Patrick Star',
discriminator=6789,
- avatar_hash=None,
)
cls.reminder_one = Reminder.objects.create(
@@ -165,6 +163,34 @@ class ReminderListTests(APISubdomainTestCase):
self.assertEqual(response.json(), [self.rem_dict_one])
+class ReminderRetrieveTests(APISubdomainTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.author = User.objects.create(
+ id=6789,
+ name='Reminder author',
+ discriminator=6789,
+ )
+
+ cls.reminder = Reminder.objects.create(
+ author=cls.author,
+ content="Reminder content",
+ expiration=datetime.utcnow().isoformat(),
+ jump_url="http://example.com/",
+ channel_id=123
+ )
+
+ def test_retrieve_unknown_returns_404(self):
+ url = reverse('bot:reminder-detail', args=("not_an_id",), host='api')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+
+ def test_retrieve_known_returns_200(self):
+ url = reverse('bot:reminder-detail', args=(self.reminder.id,), host='api')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+
+
class ReminderUpdateTests(APISubdomainTestCase):
@classmethod
def setUpTestData(cls):
@@ -172,7 +198,6 @@ class ReminderUpdateTests(APISubdomainTestCase):
id=666,
name='Man Ray',
discriminator=666,
- avatar_hash=None,
)
cls.reminder = Reminder.objects.create(
diff --git a/pydis_site/apps/api/tests/test_users.py b/pydis_site/apps/api/tests/test_users.py
index 86799f19..4c0f6e27 100644
--- a/pydis_site/apps/api/tests/test_users.py
+++ b/pydis_site/apps/api/tests/test_users.py
@@ -49,7 +49,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': 42,
- 'avatar_hash': "validavatarhashiswear",
'name': "Test",
'discriminator': 42,
'roles': [
@@ -63,7 +62,6 @@ class CreationTests(APISubdomainTestCase):
self.assertEqual(response.json(), data)
user = User.objects.get(id=42)
- self.assertEqual(user.avatar_hash, data['avatar_hash'])
self.assertEqual(user.name, data['name'])
self.assertEqual(user.discriminator, data['discriminator'])
self.assertEqual(user.in_guild, data['in_guild'])
@@ -73,7 +71,6 @@ class CreationTests(APISubdomainTestCase):
data = [
{
'id': 5,
- 'avatar_hash': "hahayes",
'name': "test man",
'discriminator': 42,
'roles': [
@@ -83,7 +80,6 @@ class CreationTests(APISubdomainTestCase):
},
{
'id': 8,
- 'avatar_hash': "maybenot",
'name': "another test man",
'discriminator': 555,
'roles': [],
@@ -99,7 +95,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': 5,
- 'avatar_hash': "hahayes",
'name': "test man",
'discriminator': 42,
'roles': [
@@ -114,7 +109,6 @@ class CreationTests(APISubdomainTestCase):
url = reverse('bot:user-list', host='api')
data = {
'id': True,
- 'avatar_hash': 1902831,
'discriminator': "totally!"
}
@@ -148,16 +142,14 @@ class UserModelTests(APISubdomainTestCase):
)
cls.user_with_roles = User.objects.create(
id=1,
- avatar_hash="coolavatarhash",
name="Test User with two roles",
discriminator=1111,
in_guild=True,
)
- cls.user_with_roles.roles.add(cls.role_bottom, cls.role_top)
+ cls.user_with_roles.roles.extend([cls.role_bottom.id, cls.role_top.id])
cls.user_without_roles = User.objects.create(
id=2,
- avatar_hash="coolavatarhash",
name="Test User without roles",
discriminator=2222,
in_guild=True,
diff --git a/pydis_site/apps/api/urls.py b/pydis_site/apps/api/urls.py
index 4a0281b4..a4fd5b2e 100644
--- a/pydis_site/apps/api/urls.py
+++ b/pydis_site/apps/api/urls.py
@@ -3,18 +3,28 @@ from rest_framework.routers import DefaultRouter
from .views import HealthcheckView, RulesView
from .viewsets import (
- BotSettingViewSet, DeletedMessageViewSet,
- DocumentationLinkViewSet, InfractionViewSet,
- LogEntryViewSet, NominationViewSet,
+ BotSettingViewSet,
+ DeletedMessageViewSet,
+ DocumentationLinkViewSet,
+ FilterListViewSet,
+ InfractionViewSet,
+ LogEntryViewSet,
+ NominationViewSet,
OffTopicChannelNameViewSet,
- OffensiveMessageViewSet, ReminderViewSet,
- RoleViewSet, TagViewSet, UserViewSet
+ OffensiveMessageViewSet,
+ ReminderViewSet,
+ RoleViewSet,
+ TagViewSet,
+ UserViewSet
)
-
# http://www.django-rest-framework.org/api-guide/routers/#defaultrouter
bot_router = DefaultRouter(trailing_slash=False)
bot_router.register(
+ 'filter-lists',
+ FilterListViewSet
+)
+bot_router.register(
'bot-settings',
BotSettingViewSet
)
@@ -41,7 +51,7 @@ bot_router.register(
bot_router.register(
'off-topic-channel-names',
OffTopicChannelNameViewSet,
- base_name='offtopicchannelname'
+ basename='offtopicchannelname'
)
bot_router.register(
'reminders',
diff --git a/pydis_site/apps/api/views.py b/pydis_site/apps/api/views.py
index a73d4718..7ac56641 100644
--- a/pydis_site/apps/api/views.py
+++ b/pydis_site/apps/api/views.py
@@ -140,7 +140,8 @@ class RulesView(APIView):
),
(
"No spamming or unapproved advertising, including requests for paid work. "
- "Open-source projects can be showcased in #show-your-projects."
+ "Open-source projects can be shared with others in #python-general and "
+ "code reviews can be asked for in a help channel."
),
(
"Keep discussions relevant to channel topics and guidelines."
diff --git a/pydis_site/apps/api/viewsets/__init__.py b/pydis_site/apps/api/viewsets/__init__.py
index 3cf9f641..8699517e 100644
--- a/pydis_site/apps/api/viewsets/__init__.py
+++ b/pydis_site/apps/api/viewsets/__init__.py
@@ -1,5 +1,6 @@
# flake8: noqa
from .bot import (
+ FilterListViewSet,
BotSettingViewSet,
DeletedMessageViewSet,
DocumentationLinkViewSet,
diff --git a/pydis_site/apps/api/viewsets/bot/__init__.py b/pydis_site/apps/api/viewsets/bot/__init__.py
index b3e0fa4d..e64e3988 100644
--- a/pydis_site/apps/api/viewsets/bot/__init__.py
+++ b/pydis_site/apps/api/viewsets/bot/__init__.py
@@ -1,4 +1,5 @@
# flake8: noqa
+from .filter_list import FilterListViewSet
from .bot_setting import BotSettingViewSet
from .deleted_message import DeletedMessageViewSet
from .documentation_link import DocumentationLinkViewSet
diff --git a/pydis_site/apps/api/viewsets/bot/filter_list.py b/pydis_site/apps/api/viewsets/bot/filter_list.py
new file mode 100644
index 00000000..2cb21ab9
--- /dev/null
+++ b/pydis_site/apps/api/viewsets/bot/filter_list.py
@@ -0,0 +1,97 @@
+from rest_framework.decorators import action
+from rest_framework.request import Request
+from rest_framework.response import Response
+from rest_framework.viewsets import ModelViewSet
+
+from pydis_site.apps.api.models.bot.filter_list import FilterList
+from pydis_site.apps.api.serializers import FilterListSerializer
+
+
+class FilterListViewSet(ModelViewSet):
+ """
+ View providing CRUD operations on items allowed or denied by our bot.
+
+ ## Routes
+ ### GET /bot/filter-lists
+ Returns all filterlist items in the database.
+
+ #### Response format
+ >>> [
+ ... {
+ ... 'id': "2309268224",
+ ... 'created_at': "01-01-2020 ...",
+ ... 'updated_at': "01-01-2020 ...",
+ ... 'type': "file_format",
+ ... 'allowed': 'true',
+ ... 'content': ".jpeg",
+ ... 'comment': "Popular image format.",
+ ... },
+ ... ...
+ ... ]
+
+ #### Status codes
+ - 200: returned on success
+ - 401: returned if unauthenticated
+
+ ### GET /bot/filter-lists/<id:int>
+ Returns a specific FilterList item from the database.
+
+ #### Response format
+ >>> {
+ ... 'id': "2309268224",
+ ... 'created_at': "01-01-2020 ...",
+ ... 'updated_at': "01-01-2020 ...",
+ ... 'type': "file_format",
+ ... 'allowed': 'true',
+ ... 'content': ".jpeg",
+ ... 'comment': "Popular image format.",
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned if the id was not found.
+
+ ### GET /bot/filter-lists/get-types
+ Returns a list of valid list types that can be used in POST requests.
+
+ #### Response format
+ >>> [
+ ... ["GUILD_INVITE","Guild Invite"],
+ ... ["FILE_FORMAT","File Format"],
+ ... ["DOMAIN_NAME","Domain Name"],
+ ... ["FILTER_TOKEN","Filter Token"]
+ ... ]
+
+ #### Status codes
+ - 200: returned on success
+
+ ### POST /bot/filter-lists
+ Adds a single FilterList item to the database.
+
+ #### Request body
+ >>> {
+ ... 'type': str,
+ ... 'allowed': bool,
+ ... 'content': str,
+ ... 'comment': Optional[str],
+ ... }
+
+ #### Status codes
+ - 201: returned on success
+ - 400: if one of the given fields is invalid
+
+ ### DELETE /bot/filter-lists/<id:int>
+ Deletes the FilterList item with the given `id`.
+
+ #### Status codes
+ - 204: returned on success
+ - 404: if a tag with the given `id` does not exist
+ """
+
+ serializer_class = FilterListSerializer
+ queryset = FilterList.objects.all()
+
+ @action(detail=False, url_path='get-types', methods=["get"])
+ def get_types(self, _: Request) -> Response:
+ """Get a list of all the types of FilterLists we support."""
+ return Response(FilterList.FilterListType.choices)
diff --git a/pydis_site/apps/api/viewsets/bot/reminder.py b/pydis_site/apps/api/viewsets/bot/reminder.py
index 147f6dbc..111660d9 100644
--- a/pydis_site/apps/api/viewsets/bot/reminder.py
+++ b/pydis_site/apps/api/viewsets/bot/reminder.py
@@ -4,6 +4,7 @@ from rest_framework.mixins import (
CreateModelMixin,
DestroyModelMixin,
ListModelMixin,
+ RetrieveModelMixin,
UpdateModelMixin
)
from rest_framework.viewsets import GenericViewSet
@@ -13,7 +14,12 @@ from pydis_site.apps.api.serializers import ReminderSerializer
class ReminderViewSet(
- CreateModelMixin, ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet
+ CreateModelMixin,
+ RetrieveModelMixin,
+ ListModelMixin,
+ DestroyModelMixin,
+ UpdateModelMixin,
+ GenericViewSet,
):
"""
View providing CRUD access to reminders.
@@ -27,9 +33,16 @@ class ReminderViewSet(
... {
... 'active': True,
... 'author': 1020103901030,
+ ... 'mentions': [
+ ... 336843820513755157,
+ ... 165023948638126080,
+ ... 267628507062992896
+ ... ],
... 'content': "Make dinner",
... 'expiration': '5018-11-20T15:52:00Z',
- ... 'id': 11
+ ... 'id': 11,
+ ... 'channel_id': 634547009956872193,
+ ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>"
... },
... ...
... ]
@@ -37,14 +50,41 @@ class ReminderViewSet(
#### Status codes
- 200: returned on success
+ ### GET /bot/reminders/<id:int>
+ Fetches the reminder with the given id.
+
+ #### Response format
+ >>>
+ ... {
+ ... 'active': True,
+ ... 'author': 1020103901030,
+ ... 'mentions': [
+ ... 336843820513755157,
+ ... 165023948638126080,
+ ... 267628507062992896
+ ... ],
+ ... 'content': "Make dinner",
+ ... 'expiration': '5018-11-20T15:52:00Z',
+ ... 'id': 11,
+ ... 'channel_id': 634547009956872193,
+ ... 'jump_url': "https://discord.com/channels/<guild_id>/<channel_id>/<message_id>"
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 404: returned when the reminder doesn't exist
+
### POST /bot/reminders
Create a new reminder.
#### Request body
>>> {
... 'author': int,
+ ... 'mentions': List[int],
... 'content': str,
- ... 'expiration': str # ISO-formatted datetime
+ ... 'expiration': str, # ISO-formatted datetime
+ ... 'channel_id': int,
+ ... 'jump_url': str
... }
#### Status codes
@@ -52,6 +92,22 @@ class ReminderViewSet(
- 400: if the body format is invalid
- 404: if no user with the given ID could be found
+ ### PATCH /bot/reminders/<id:int>
+ Update the user with the given `id`.
+ All fields in the request body are optional.
+
+ #### Request body
+ >>> {
+ ... 'mentions': List[int],
+ ... 'content': str,
+ ... 'expiration': str # ISO-formatted datetime
+ ... }
+
+ #### Status codes
+ - 200: returned on success
+ - 400: if the body format is invalid
+ - 404: if no user with the given ID could be found
+
### DELETE /bot/reminders/<id:int>
Delete the reminder with the given `id`.
diff --git a/pydis_site/apps/api/viewsets/bot/user.py b/pydis_site/apps/api/viewsets/bot/user.py
index a407787e..9571b3d7 100644
--- a/pydis_site/apps/api/viewsets/bot/user.py
+++ b/pydis_site/apps/api/viewsets/bot/user.py
@@ -17,7 +17,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
>>> [
... {
... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
... 'name': "Python",
... 'discriminator': 4329,
... 'roles': [
@@ -39,7 +38,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Response format
>>> {
... 'id': 409107086526644234,
- ... 'avatar': "3ba3c1acce584c20b1e96fc04bbe80eb",
... 'name': "Python",
... 'discriminator': 4329,
... 'roles': [
@@ -62,7 +60,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -83,7 +80,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -102,7 +98,6 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
#### Request body
>>> {
... 'id': int,
- ... 'avatar': str,
... 'name': str,
... 'discriminator': int,
... 'roles': List[int],
@@ -123,4 +118,4 @@ class UserViewSet(BulkCreateModelMixin, ModelViewSet):
"""
serializer_class = UserSerializer
- queryset = User.objects.prefetch_related('roles')
+ queryset = User.objects
diff --git a/pydis_site/apps/home/forms/account_deletion.py b/pydis_site/apps/home/forms/account_deletion.py
index 17ffe5c1..eec70bea 100644
--- a/pydis_site/apps/home/forms/account_deletion.py
+++ b/pydis_site/apps/home/forms/account_deletion.py
@@ -1,23 +1,9 @@
-from crispy_forms.helper import FormHelper
-from crispy_forms.layout import Layout
from django.forms import CharField, Form
-from django_crispy_bulma.layout import IconField, Submit
class AccountDeletionForm(Form):
"""Account deletion form, to collect username for confirmation of removal."""
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.helper = FormHelper()
-
- self.helper.form_method = "post"
- self.helper.add_input(Submit("submit", "I understand, delete my account"))
-
- self.helper.layout = Layout(
- IconField("username", icon_prepend="user")
- )
-
username = CharField(
label="Username",
required=True
diff --git a/pydis_site/apps/home/signals.py b/pydis_site/apps/home/signals.py
index 4cb4564b..8af48c15 100644
--- a/pydis_site/apps/home/signals.py
+++ b/pydis_site/apps/home/signals.py
@@ -150,7 +150,7 @@ class AllauthSignalListener:
social_account = SocialAccount.objects.get(user=user, provider=DiscordProvider.id)
discord_user = DiscordUser.objects.get(id=int(social_account.uid))
- mappings = RoleMapping.objects.filter(role__in=discord_user.roles.all()).all()
+ mappings = RoleMapping.objects.filter(role__id__in=discord_user.roles).all()
is_staff = any(m.is_staff for m in mappings)
if user.is_staff != is_staff:
@@ -185,7 +185,7 @@ class AllauthSignalListener:
self.mapping_model_deleted(RoleMapping, instance=old_instance)
accounts = SocialAccount.objects.filter(
- uid__in=(u.id for u in instance.role.user_set.all())
+ uid__in=(u.id for u in DiscordUser.objects.filter(roles__contains=[instance.role.id]))
)
for account in accounts:
@@ -198,7 +198,7 @@ class AllauthSignalListener:
discord_user = DiscordUser.objects.get(id=int(account.uid))
mappings = RoleMapping.objects.filter(
- role__in=discord_user.roles.all()
+ role__id__in=discord_user.roles
).exclude(id=instance.id).all()
is_staff = any(m.is_staff for m in mappings)
@@ -289,9 +289,9 @@ class AllauthSignalListener:
new_groups = []
is_staff = False
- for role in user.roles.all():
+ for role in user.roles:
try:
- mapping = mappings.get(role=role)
+ mapping = mappings.get(role__id=role)
except RoleMapping.DoesNotExist:
continue # No mapping exists
diff --git a/pydis_site/apps/home/tests/mock_github_api_response.json b/pydis_site/apps/home/tests/mock_github_api_response.json
index 37dc672e..35604a85 100644
--- a/pydis_site/apps/home/tests/mock_github_api_response.json
+++ b/pydis_site/apps/home/tests/mock_github_api_response.json
@@ -28,7 +28,7 @@
"forks_count": 31
},
{
- "full_name": "python-discord/django-crispy-bulma",
+ "full_name": "python-discord/flake8-annotations",
"description": "test",
"stargazers_count": 97,
"language": "Python",
diff --git a/pydis_site/apps/home/tests/test_signal_listener.py b/pydis_site/apps/home/tests/test_signal_listener.py
index 66a67252..d99d81a5 100644
--- a/pydis_site/apps/home/tests/test_signal_listener.py
+++ b/pydis_site/apps/home/tests/test_signal_listener.py
@@ -81,24 +81,21 @@ class SignalListenerTests(TestCase):
id=0,
name="user",
discriminator=0,
- avatar_hash=None
)
cls.discord_unmapped = DiscordUser.objects.create(
id=2,
name="unmapped",
discriminator=0,
- avatar_hash=None
)
- cls.discord_unmapped.roles.add(cls.unmapped_role)
+ cls.discord_unmapped.roles.append(cls.unmapped_role.id)
cls.discord_unmapped.save()
cls.discord_not_in_guild = DiscordUser.objects.create(
id=3,
name="not-in-guild",
discriminator=0,
- avatar_hash=None,
in_guild=False
)
@@ -106,20 +103,18 @@ class SignalListenerTests(TestCase):
id=1,
name="admin",
discriminator=0,
- avatar_hash=None
)
- cls.discord_admin.roles.set([cls.admin_role])
+ cls.discord_admin.roles = [cls.admin_role.id]
cls.discord_admin.save()
cls.discord_moderator = DiscordUser.objects.create(
id=4,
name="admin",
discriminator=0,
- avatar_hash=None
)
- cls.discord_moderator.roles.set([cls.moderator_role])
+ cls.discord_moderator.roles = [cls.moderator_role.id]
cls.discord_moderator.save()
cls.django_user_discordless = DjangoUser.objects.create(username="no-discord")
@@ -338,7 +333,7 @@ class SignalListenerTests(TestCase):
handler._apply_groups(self.discord_admin, self.social_admin)
self.assertEqual(self.django_user_discordless.groups.all().count(), 0)
- self.discord_admin.roles.add(self.admin_role)
+ self.discord_admin.roles.append(self.admin_role.id)
self.discord_admin.save()
def test_apply_groups_moderator(self):
@@ -365,7 +360,7 @@ class SignalListenerTests(TestCase):
handler._apply_groups(self.discord_moderator, self.social_moderator)
self.assertEqual(self.django_user_discordless.groups.all().count(), 0)
- self.discord_moderator.roles.add(self.moderator_role)
+ self.discord_moderator.roles.append(self.moderator_role.id)
self.discord_moderator.save()
def test_apply_groups_other(self):
diff --git a/pydis_site/apps/home/views/home.py b/pydis_site/apps/home/views/home.py
index 4cf22594..20e38ab0 100644
--- a/pydis_site/apps/home/views/home.py
+++ b/pydis_site/apps/home/views/home.py
@@ -23,8 +23,8 @@ class HomeView(View):
"python-discord/bot",
"python-discord/snekbox",
"python-discord/seasonalbot",
+ "python-discord/flake8-annotations",
"python-discord/django-simple-bulma",
- "python-discord/django-crispy-bulma",
]
def _get_api_data(self) -> Dict[str, Dict[str, str]]:
@@ -61,7 +61,7 @@ class HomeView(View):
# Try to get new data from the API. If it fails, return the cached data.
try:
api_repositories = self._get_api_data()
- except TypeError:
+ except (TypeError, ConnectionError):
return RepositoryMetadata.objects.all()
database_repositories = []
diff --git a/pydis_site/apps/staff/tests/test_logs_view.py b/pydis_site/apps/staff/tests/test_logs_view.py
index 1415c558..17910bb6 100644
--- a/pydis_site/apps/staff/tests/test_logs_view.py
+++ b/pydis_site/apps/staff/tests/test_logs_view.py
@@ -21,10 +21,9 @@ class TestLogsView(TestCase):
id=12345678901,
name='Alan Turing',
discriminator=1912,
- avatar_hash=None
)
- cls.author.roles.add(cls.developers_role)
+ cls.author.roles.append(cls.developers_role.id)
cls.deletion_context = MessageDeletionContext.objects.create(
actor=cls.actor,
diff --git a/pydis_site/settings.py b/pydis_site/settings.py
index 5f80a414..2c87007c 100644
--- a/pydis_site/settings.py
+++ b/pydis_site/settings.py
@@ -52,6 +52,8 @@ if DEBUG:
'api.pythondiscord.local',
'admin.pythondiscord.local',
'staff.pythondiscord.local',
+ '0.0.0.0', # noqa: S104
+ 'localhost',
'web',
'api.web',
'admin.web',
@@ -104,8 +106,6 @@ INSTALLED_APPS = [
'allauth.socialaccount.providers.discord',
'allauth.socialaccount.providers.github',
- 'crispy_forms',
- 'django_crispy_bulma',
'django_hosts',
'django_filters',
'django_nyt.apps.DjangoNytConfig',
@@ -288,7 +288,6 @@ LOGGING = {
}
# Django Messages framework config
-
MESSAGE_TAGS = {
messages.DEBUG: 'primary',
messages.INFO: 'info',
@@ -297,17 +296,6 @@ MESSAGE_TAGS = {
messages.ERROR: 'danger',
}
-# Custom settings for Crispyforms
-CRISPY_ALLOWED_TEMPLATE_PACKS = (
- "bootstrap",
- "uni_form",
- "bootstrap3",
- "bootstrap4",
- "bulma",
-)
-
-CRISPY_TEMPLATE_PACK = "bulma"
-
# Custom settings for django-simple-bulma
BULMA_SETTINGS = {
"variables": { # If you update these colours, please update the notification.css file
diff --git a/pydis_site/static/images/events/summer_code_jam_2020.png b/pydis_site/static/images/events/summer_code_jam_2020.png
new file mode 100644
index 00000000..63c311b0
--- /dev/null
+++ b/pydis_site/static/images/events/summer_code_jam_2020.png
Binary files differ
diff --git a/pydis_site/templates/base/navbar.html b/pydis_site/templates/base/navbar.html
index d2ea9589..c2915025 100644
--- a/pydis_site/templates/base/navbar.html
+++ b/pydis_site/templates/base/navbar.html
@@ -87,8 +87,8 @@
<div class="navbar-item">
<strong>Events</strong>
</div>
- <a class="navbar-item" href="{% url 'wiki:get' path="events/game-jam-2020/" %}">
- Most Recent: Game Jam 2020
+ <a class="navbar-item" href="{% url 'wiki:get' path="code-jams/code-jam-7/" %}">
+ Most Recent: Code Jam 7
</a>
<a class="navbar-item" href="{% url 'wiki:get' path="events/" %}">
All events
diff --git a/pydis_site/templates/home/account/delete.html b/pydis_site/templates/home/account/delete.html
index 1020a82b..0d44e32a 100644
--- a/pydis_site/templates/home/account/delete.html
+++ b/pydis_site/templates/home/account/delete.html
@@ -1,6 +1,4 @@
{% extends 'base/base.html' %}
-
-{% load crispy_forms_tags %}
{% load static %}
{% block title %}Delete Account{% endblock %}
@@ -36,7 +34,12 @@
<div class="columns is-centered">
<div class="column is-half-desktop is-full-tablet is-full-mobile">
- {% crispy form %}
+ <form method="post">
+ {% csrf_token %}
+ <label for="id_username" class="label requiredField">Username</label>
+ <input id="id_username" class="input" type="text" required name="username">
+ <input style="margin-top: 1em;" type="submit" value="I understand, delete my account" class="button is-primary">
+ </form>
</div>
</div>
</div>
diff --git a/pydis_site/templates/home/index.html b/pydis_site/templates/home/index.html
index c30cbee6..3e96cc91 100644
--- a/pydis_site/templates/home/index.html
+++ b/pydis_site/templates/home/index.html
@@ -39,8 +39,8 @@
{# Right column container #}
<div class="column is-half-desktop">
- <a href="https://pythondiscord.com/pages/events/game-jam-2020/">
- <img src="https://raw.githubusercontent.com/python-discord/branding/master/events/game%20jam%202020/game%20jam%202020%20-%20website%20banner.png">
+ <a href="https://pythondiscord.com/pages/code-jams/code-jam-7/">
+ <img src="{% static "images/events/summer_code_jam_2020.png" %}">
</a>
</div>
</div>
diff --git a/pydis_site/templates/wiki/base.html b/pydis_site/templates/wiki/base.html
index 9f904324..846492ab 100644
--- a/pydis_site/templates/wiki/base.html
+++ b/pydis_site/templates/wiki/base.html
@@ -7,7 +7,7 @@
{% block head %}
{{ block.super }}
- <script src="{% static "wiki/js/jquery-3.3.1.min.js" %}" type="text/javascript"></script>
+ <script src="{% static "wiki/js/jquery-3.4.1.min.js" %}" type="text/javascript"></script>
<script src="{% static "wiki/js/core.js" %}" type="text/javascript"></script>
<script src="{% static "js/wiki/simplemde.min.js" %}" type="text/javascript"></script>
diff --git a/pydis_site/tests/__init__.py b/pydis_site/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pydis_site/tests/__init__.py
diff --git a/pydis_site/tests/test_utils_account.py b/pydis_site/tests/test_utils_account.py
index d2946368..6f8338b4 100644
--- a/pydis_site/tests/test_utils_account.py
+++ b/pydis_site/tests/test_utils_account.py
@@ -5,7 +5,7 @@ from allauth.socialaccount.models import SocialAccount, SocialLogin
from django.contrib.auth.models import User
from django.contrib.messages.storage.base import BaseStorage
from django.http import HttpRequest
-from django.test import TestCase
+from django.test import RequestFactory, TestCase
from pydis_site.apps.api.models import Role, User as DiscordUser
from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter
@@ -13,28 +13,43 @@ from pydis_site.utils.account import AccountAdapter, SocialAccountAdapter
class AccountUtilsTests(TestCase):
def setUp(self):
+ # Create the user
self.django_user = User.objects.create(username="user")
+ # Create the roles
+ developers_role = Role.objects.create(
+ id=1,
+ name="Developers",
+ colour=0,
+ permissions=0,
+ position=1
+ )
+ everyone_role = Role.objects.create(
+ id=0,
+ name="@everyone",
+ colour=0,
+ permissions=0,
+ position=0
+ )
+
+ # Create the social accounts
self.discord_account = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=0
)
-
- self.discord_account_role = SocialAccount.objects.create(
+ self.discord_account_one_role = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=1
)
-
self.discord_account_two_roles = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=2
)
-
self.discord_account_not_present = SocialAccount.objects.create(
user=self.django_user, provider="discord", uid=3
)
-
self.github_account = SocialAccount.objects.create(
user=self.django_user, provider="github", uid=0
)
+ # Create DiscordUsers
self.discord_user = DiscordUser.objects.create(
id=0,
name="user",
@@ -44,33 +59,18 @@ class AccountUtilsTests(TestCase):
self.discord_user_role = DiscordUser.objects.create(
id=1,
name="user present",
- discriminator=0
+ discriminator=0,
+ roles=[everyone_role.id]
)
self.discord_user_two_roles = DiscordUser.objects.create(
id=2,
name="user with both roles",
- discriminator=0
+ discriminator=0,
+ roles=[everyone_role.id, developers_role.id]
)
- everyone_role = Role.objects.create(
- id=0,
- name="@everyone",
- colour=0,
- permissions=0,
- position=0
- )
-
- self.discord_user_role.roles.add(everyone_role)
- self.discord_user_two_roles.roles.add(everyone_role)
-
- self.discord_user_two_roles.roles.add(Role.objects.create(
- id=1,
- name="Developers",
- colour=0,
- permissions=0,
- position=1
- ))
+ self.request_factory = RequestFactory()
def test_account_adapter(self):
"""Test that our Allauth account adapter functions correctly."""
@@ -83,13 +83,13 @@ class AccountUtilsTests(TestCase):
adapter = SocialAccountAdapter()
discord_login = SocialLogin(account=self.discord_account)
- discord_login_role = SocialLogin(account=self.discord_account_role)
- discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles)
+ discord_login_role = SocialLogin(account=self.discord_account_one_role)
discord_login_not_present = SocialLogin(account=self.discord_account_not_present)
+ discord_login_two_roles = SocialLogin(account=self.discord_account_two_roles)
github_login = SocialLogin(account=self.github_account)
- messages_request = HttpRequest()
+ messages_request = self.request_factory.get("/")
messages_request._messages = BaseStorage(messages_request)
with patch("pydis_site.utils.account.reverse") as mock_reverse:
@@ -106,12 +106,14 @@ class AccountUtilsTests(TestCase):
with self.assertRaises(ImmediateHttpResponse):
adapter.is_open_for_signup(messages_request, discord_login_not_present)
+ self.assertTrue(
+ adapter.is_open_for_signup(messages_request, discord_login_two_roles)
+ )
+
self.assertEqual(len(messages_request._messages._queued_messages), 4)
self.assertEqual(mock_redirect.call_count, 4)
self.assertEqual(mock_reverse.call_count, 4)
- self.assertTrue(adapter.is_open_for_signup(HttpRequest(), discord_login_two_roles))
-
def test_social_account_adapter_populate(self):
"""Test that our Allauth social account adapter correctly handles data population."""
adapter = SocialAccountAdapter()
@@ -120,13 +122,18 @@ class AccountUtilsTests(TestCase):
account=self.discord_account,
user=self.django_user
)
-
discord_login.account.extra_data["discriminator"] = "0000"
- user = adapter.populate_user(
- HttpRequest(), discord_login,
+ discord_user = adapter.populate_user(
+ self.request_factory.get("/"), discord_login,
{"username": "user"}
)
+ self.assertEqual(discord_user.username, "user#0000")
+ self.assertEqual(discord_user.first_name, "user#0000")
- self.assertEqual(user.username, "user#0000")
- self.assertEqual(user.first_name, "user#0000")
+ discord_login.account.provider = "not_discord"
+ not_discord_user = adapter.populate_user(
+ self.request_factory.get("/"), discord_login,
+ {"username": "user"}
+ )
+ self.assertEqual(not_discord_user.username, "user")
diff --git a/pydis_site/utils/account.py b/pydis_site/utils/account.py
index 2d699c88..b4e41198 100644
--- a/pydis_site/utils/account.py
+++ b/pydis_site/utils/account.py
@@ -54,7 +54,7 @@ class SocialAccountAdapter(DefaultSocialAccountAdapter):
raise ImmediateHttpResponse(redirect(reverse("home")))
- if user.roles.count() <= 1:
+ if len(user.roles) <= 1:
add_message(request, ERROR, ERROR_JOIN_DISCORD)
raise ImmediateHttpResponse(redirect(reverse("home")))