diff --git a/.gitignore b/.gitignore index f81937d..fa6de09 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,5 @@ media /.env /.mypy_cache /.pytest_cache -/*.egg-info/ /static node_modules diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86a47d3..fc4eb69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,41 +1,30 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v2.1.0 hooks: - - id: check-ast - - id: check-builtin-literals + - id: check-byte-order-marker - id: check-case-conflict - - id: check-docstring-first - id: check-executables-have-shebangs - id: check-json - id: check-merge-conflict - - id: check-symlinks - - id: check-toml - - id: check-vcs-permalinks - id: check-yaml - - id: destroyed-symlinks - id: end-of-file-fixer - - id: fix-byte-order-marker + - id: flake8 - id: mixed-line-ending args: - --fix=lf - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 23.7.0 - hooks: - - id: black - language_version: python3.11 - repo: local hooks: - id: pytest name: Check pytest unit tests pass - entry: poetry run pytest + entry: pipenv run pytest pass_filenames: false language: system types: [python] - id: mypy name: Check mypy static types match - entry: poetry run mypy . --ignore-missing-imports + entry: pipenv run mypy . --ignore-missing-imports pass_filenames: false language: system types: [python] diff --git a/pyproject.toml b/Pipfile similarity index 64% rename from pyproject.toml rename to Pipfile index 4fc7074..8a070d4 100644 --- a/pyproject.toml +++ b/Pipfile @@ -1,66 +1,63 @@ -[tool.poetry] -name = "lemoncurry" -version = "1.11.0" -description = "Indieweb-compatible personal website" -authors = ["Danielle McLean "] -license = "MIT" +[[source]] +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" -[tool.poetry.dependencies] -python = "^3.9" -accept-types = "*" -ago = "*" -argon2-cffi = "*" -bleach = "*" -cachecontrol = "*" -django = "<4,>=3" -django-activeurl = "*" -django-analytical = "*" -django-annoying = "*" +[requires] +python_version = '3.9' + +[packages] +django = ">=3,<4" django-compressor = "*" -django-computed-property = "*" -django-cors-headers = "*" -django-debug-toolbar = "*" -django-extensions = "*" -django-meta = "*" -django-model-utils = "*" -django-otp = "*" -django-otp-agents = "*" -django-push = "*" -django-randomslugfield = "*" -django-redis = "*" -django-rq = "*" -docutils = "*" -gevent = "*" -gunicorn = {extras = ["gevent"], version = "*"} -hiredis = "*" -jinja2 = "*" -markdown = "*" -mf2py = "*" -mf2util = "*" -msgpack = "*" +gunicorn = {extras = ["gevent"]} +"psycopg2-binary" = "*" pillow = "*" -psycopg2-binary = "*" -python-baseconv = "*" -python-magic = "*" -python-slugify = "*" -pyup-django = "*" -pyyaml = "*" +django-meta = "*" +django-activeurl = "*" +django-otp = "*" qrcode = "*" -ronkyuu = "*" +django-otp-agents = "*" +python-slugify = "*" +"mf2py" = "*" +markdown = "*" +bleach = "*" +django-debug-toolbar = "*" xrd = "*" +django-push = "*" +pyyaml = "*" +django-annoying = "*" +accept-types = "*" +django-analytical = "*" +django-model-utils = "*" +django-rq = "*" +ronkyuu = "*" +cachecontrol = "*" +hiredis = "*" +"mf2util" = "*" +django-cors-headers = "*" +"argon2-cffi" = "*" +python-baseconv = "*" +django-computed-property = "*" +docutils = "*" +django-super-favicon = "*" +django-redis = "*" +gevent = "*" +django-extensions = "*" +python-magic = "*" +pyup-django = "*" +jinja2 = "*" +msgpack = "*" +django-randomslugfield = "*" +ago = "*" -[tool.poetry.dev-dependencies] -mypy = "*" +[dev-packages] ptpython = "*" pytest-django = "*" -types-bleach = "*" -types-markdown = "*" -types-python-slugify = "*" +werkzeug = "*" +watchdog = "*" +mypy = "*" types-pyyaml = "*" types-requests = "*" -watchdog = "*" -werkzeug = "*" - -[build-system] -requires = ["poetry>=0.12"] -build-backend = "poetry.masonry.api" +types-python-slugify = "*" +types-bleach = "*" +types-markdown = "*" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..824365d --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1496 @@ +{ + "_meta": { + "hash": { + "sha256": "48e812f8f32ffe8196d1af1596adfcd6acd6a9110d9cd033ff6c776a4cbd5188" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "accept-types": { + "hashes": [ + "sha256:c87feccdffb66b02f9343ff387d7fd5c451ccb2e1221fbd37ea0cedef5cf290f", + "sha256:fb27099716d8f0360408c8ca86d69dbfed44455834b70d1506250abe521b535a" + ], + "index": "pypi", + "version": "==0.4.1" + }, + "ago": { + "hashes": [ + "sha256:6f060596a90c6ec33c2e806bcc3146e7e7bfda7e90ca5c11b579c5bf03baf6fa", + "sha256:9d1956edd8103c266d968ae2a7eaf2f23470b6384e655aaaf54d1158408178ad" + ], + "index": "pypi", + "version": "==0.0.93" + }, + "argon2-cffi": { + "hashes": [ + "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80", + "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b" + ], + "index": "pypi", + "version": "==21.3.0" + }, + "argon2-cffi-bindings": { + "hashes": [ + "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670", + "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f", + "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583", + "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194", + "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", + "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a", + "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", + "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5", + "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", + "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7", + "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", + "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", + "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", + "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", + "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", + "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", + "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d", + "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", + "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb", + "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", + "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351" + ], + "markers": "python_version >= '3.6'", + "version": "==21.2.0" + }, + "asgiref": { + "hashes": [ + "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", + "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.5.0" + }, + "async-timeout": { + "hashes": [ + "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", + "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" + ], + "markers": "python_version >= '3.6'", + "version": "==4.0.2" + }, + "beautifulsoup4": { + "hashes": [ + "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", + "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" + ], + "markers": "python_version >= '3.6'", + "version": "==4.11.1" + }, + "bleach": { + "hashes": [ + "sha256:08a1fe86d253b5c88c92cc3d810fd8048a16d15762e1e5b74d502256e5926aa1", + "sha256:c6d6cc054bdc9c83b48b8083e236e5f00f238428666d2ce2e083eaa5fd568565" + ], + "index": "pypi", + "version": "==5.0.0" + }, + "cachecontrol": { + "hashes": [ + "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b", + "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144" + ], + "index": "pypi", + "version": "==0.12.11" + }, + "certifi": { + "hashes": [ + "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", + "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569" + ], + "version": "==2021.10.8" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "version": "==1.15.0" + }, + "charset-normalizer": { + "hashes": [ + "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", + "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" + ], + "markers": "python_version >= '3'", + "version": "==2.0.12" + }, + "click": { + "hashes": [ + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.3" + }, + "deprecated": { + "hashes": [ + "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", + "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.2.13" + }, + "django": { + "hashes": [ + "sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6", + "sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf" + ], + "index": "pypi", + "version": "==3.2.13" + }, + "django-activeurl": { + "hashes": [ + "sha256:d2902cfd74e30e5c10c302b8280c849dd23b48ab0b04bab152d2270b904f90ea" + ], + "index": "pypi", + "version": "==0.2.0" + }, + "django-agent-trust": { + "hashes": [ + "sha256:2832fcd6e1e68274fc19dc09fefa1878758715c0a8f5d313f0d5a606e2e66a39", + "sha256:926304f1ae3b915f42c519b9b993c4275dcfe5c3ec85d6a44659f6a59558343a" + ], + "version": "==1.0.4" + }, + "django-analytical": { + "hashes": [ + "sha256:43de3d8ef7734732f58eba4e5e7df0dea37512dbd89727efdfb30c27a96d4ea9", + "sha256:6127c9196c8de3bcb4626f420d2ae670a7703152b1841b1b3e852b31a9a9d44b" + ], + "index": "pypi", + "version": "==3.1.0" + }, + "django-annoying": { + "hashes": [ + "sha256:083b5e26f140f86178fcb47931f69b7ff75181ccd1e318d9c218ae9babc1805e", + "sha256:e230f28fec0559b4fdb621ff202068a78170ada2ac1ec7a73ba822cc1f737791" + ], + "index": "pypi", + "version": "==0.10.6" + }, + "django-appconf": { + "hashes": [ + "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d", + "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.5" + }, + "django-classy-tags": { + "hashes": [ + "sha256:0e4a03462b29bd94c8da8160d514cb93e4b4c858752437c082a3efeca94b9f51", + "sha256:d222b45502ac99e550a566667839efaef954b9c7366a6493f143862aabeac878" + ], + "markers": "python_version >= '3.7'", + "version": "==3.0.1" + }, + "django-compressor": { + "hashes": [ + "sha256:1db91b6d04293636a68bd1328dc7bb90d636b0295f67b1cc6d4fa102b9fd25f6", + "sha256:b4fe15cc23bf39420b37cb0030572bd0971104ca1ec3764f502c0f179e576dff" + ], + "index": "pypi", + "version": "==4.0" + }, + "django-computed-property": { + "hashes": [ + "sha256:086289d8d21e3fb09e232ff1f61d747d78445cd90b3420899bcec1709c916fff", + "sha256:3dfd93d13c258ced06ec356b276963cc22d87d5c0944a3ee0e1431eabcae16c8" + ], + "index": "pypi", + "version": "==0.3.0" + }, + "django-cors-headers": { + "hashes": [ + "sha256:a22be2befd4069c4fc174f11cf067351df5c061a3a5f94a01650b4e928b0372b", + "sha256:eb98389bf7a2afc5d374806af4a9149697e3a6955b5a2dc2bf049f7d33647456" + ], + "index": "pypi", + "version": "==3.11.0" + }, + "django-debug-toolbar": { + "hashes": [ + "sha256:644bbd5c428d3283aa9115722471769cac1bec189edf3a0c855fd8ff870375a9", + "sha256:6b633b6cfee24f232d73569870f19aa86c819d750e7f3e833f2344a9eb4b4409" + ], + "index": "pypi", + "version": "==3.2.4" + }, + "django-extensions": { + "hashes": [ + "sha256:28e1e1bf49f0e00307ba574d645b0af3564c981a6dfc87209d48cb98f77d0b1a", + "sha256:9238b9e016bb0009d621e05cf56ea8ce5cce9b32e91ad2026996a7377ca28069" + ], + "index": "pypi", + "version": "==3.1.5" + }, + "django-meta": { + "hashes": [ + "sha256:25010157000fa2f1d1ed46e8739f43cfe00e40af199d913b230e52133b650da5", + "sha256:3d69068b1c9e8369427fe0843f4cb6974c24c27dc87750cd915db487ee475fce" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "django-model-utils": { + "hashes": [ + "sha256:a768a25c80514e0ad4e4a6f9c02c44498985f36c5dfdea47b5b1e8cf994beba6", + "sha256:e7a95e102f9c9653427eadab980d5d59e1dea972913b9c9e01ac37f86bba0ddf" + ], + "index": "pypi", + "version": "==4.2.0" + }, + "django-otp": { + "hashes": [ + "sha256:8637be826c0465d0fd1710e4472efe9fc83883853a2141fefdbace9358d20003", + "sha256:f002c71d4ea7f514590be00492980d3c87397b73dc20542e1c4fc00b66f2dda1" + ], + "index": "pypi", + "version": "==1.1.3" + }, + "django-otp-agents": { + "hashes": [ + "sha256:23ae483c1ad0f04e26df66a726ee402e9d72a07c7e804708f2c6fbc775b00d4d", + "sha256:e99addf12fe342628e623aad554c766c0c50bcad79da553197ecbfb49a2929cf" + ], + "index": "pypi", + "version": "==1.0.1" + }, + "django-push": { + "hashes": [ + "sha256:9d73a27f147ea46f5e92d6ab36c19640b11214b43b378693c8961aaf8bea5b60", + "sha256:d5442fcb6d8254a7e837383ce766a72e8fb921f3bcfc2355440c2da8fbcf07b4" + ], + "index": "pypi", + "version": "==1.1" + }, + "django-randomslugfield": { + "hashes": [ + "sha256:8f5866d9383f020fb7f270a218ddc65b6a33d3833633a0c995c68f28cac59efb" + ], + "index": "pypi", + "version": "==0.3.0" + }, + "django-redis": { + "hashes": [ + "sha256:1d037dc02b11ad7aa11f655d26dac3fb1af32630f61ef4428860a2e29ff92026", + "sha256:8a99e5582c79f894168f5865c52bd921213253b7fd64d16733ae4591564465de" + ], + "index": "pypi", + "version": "==5.2.0" + }, + "django-rq": { + "hashes": [ + "sha256:7be1e10e7091555f9f36edf100b0dbb205ea2b98683d74443d2bdf3c6649a03f", + "sha256:f08486602664d73a6e335872c868d79663e380247e6307496d01b8fa770fefd8" + ], + "index": "pypi", + "version": "==2.5.1" + }, + "django-super-favicon": { + "hashes": [ + "sha256:56cb5268ea73ef3cbde5cb01fef02fea2ec00739cdae0566d3102009f052f683" + ], + "index": "pypi", + "version": "==0.6.1" + }, + "docutils": { + "hashes": [ + "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", + "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + ], + "index": "pypi", + "version": "==0.18.1" + }, + "gevent": { + "hashes": [ + "sha256:0082d8a5d23c35812ce0e716a91ede597f6dd2c5ff508a02a998f73598c59397", + "sha256:01928770972181ad8866ee37ea3504f1824587b188fcab782ef1619ce7538766", + "sha256:05c5e8a50cd6868dd36536c92fb4468d18090e801bd63611593c0717bab63692", + "sha256:08b4c17064e28f4eb85604486abc89f442c7407d2aed249cf54544ce5c9baee6", + "sha256:177f93a3a90f46a5009e0841fef561601e5c637ba4332ab8572edd96af650101", + "sha256:22ce1f38fdfe2149ffe8ec2131ca45281791c1e464db34b3b4321ae9d8d2efbb", + "sha256:24d3550fbaeef5fddd794819c2853bca45a86c3d64a056a2c268d981518220d1", + "sha256:2afa3f3ad528155433f6ac8bd64fa5cc303855b97004416ec719a6b1ca179481", + "sha256:2bcec9f80196c751fdcf389ca9f7141e7b0db960d8465ed79be5e685bfcad682", + "sha256:2cfff82f05f14b7f5d9ed53ccb7a609ae8604df522bb05c971bca78ec9d8b2b9", + "sha256:3baeeccc4791ba3f8db27179dff11855a8f9210ddd754f6c9b48e0d2561c2aea", + "sha256:3c012c73e6c61f13c75e3a4869dbe6a2ffa025f103421a6de9c85e627e7477b1", + "sha256:3dad62f55fad839d498c801e139481348991cee6e1c7706041b5fe096cb6a279", + "sha256:542ae891e2aa217d2cf6d8446538fcd2f3263a40eec123b970b899bac391c47a", + "sha256:6a02a88723ed3f0fd92cbf1df3c4cd2fbd87d82b0a4bac3e36a8875923115214", + "sha256:74fc1ef16b86616cfddcc74f7292642b0f72dde4dd95aebf4c45bb236744be54", + "sha256:7909780f0cf18a1fc32aafd8c8e130cdd93c6e285b11263f7f2d1a0f3678bc50", + "sha256:7ccffcf708094564e442ac6fde46f0ae9e40015cb69d995f4b39cc29a7643881", + "sha256:8c21cb5c9f4e14d75b3fe0b143ec875d7dbd1495fad6d49704b00e57e781ee0f", + "sha256:973749bacb7bc4f4181a8fb2a7e0e2ff44038de56d08e856dd54a5ac1d7331b4", + "sha256:9d86438ede1cbe0fde6ef4cc3f72bf2f1ecc9630d8b633ff344a3aeeca272cdd", + "sha256:9f9652d1e4062d4b5b5a0a49ff679fa890430b5f76969d35dccb2df114c55e0f", + "sha256:a5ad4ed8afa0a71e1927623589f06a9b5e8b5e77810be3125cb4d93050d3fd1f", + "sha256:b7709c64afa8bb3000c28bb91ec42c79594a7cb0f322e20427d57f9762366a5b", + "sha256:bb5cb8db753469c7a9a0b8a972d2660fe851aa06eee699a1ca42988afb0aaa02", + "sha256:c43f081cbca41d27fd8fef9c6a32cf83cb979345b20abc07bf68df165cdadb24", + "sha256:cc2fef0f98ee180704cf95ec84f2bc2d86c6c3711bb6b6740d74e0afe708b62c", + "sha256:da8d2d51a49b2a5beb02ad619ca9ddbef806ef4870ba04e5ac7b8b41a5b61db3", + "sha256:e1899b921219fc8959ff9afb94dae36be82e0769ed13d330a393594d478a0b3a", + "sha256:eae3c46f9484eaacd67ffcdf4eaf6ca830f587edd543613b0f5c4eb3c11d052d", + "sha256:ec21f9eaaa6a7b1e62da786132d6788675b314f25f98d9541f1bf00584ed4749", + "sha256:f289fae643a3f1c3b909d6b033e6921b05234a4907e9c9c8c3f1fe403e6ac452", + "sha256:f48b64578c367b91fa793bf8eaaaf4995cb93c8bc45860e473bf868070ad094e" + ], + "index": "pypi", + "version": "==21.12.0" + }, + "greenlet": { + "hashes": [ + "sha256:0051c6f1f27cb756ffc0ffbac7d2cd48cb0362ac1736871399a739b2885134d3", + "sha256:00e44c8afdbe5467e4f7b5851be223be68adb4272f44696ee71fe46b7036a711", + "sha256:013d61294b6cd8fe3242932c1c5e36e5d1db2c8afb58606c5a67efce62c1f5fd", + "sha256:049fe7579230e44daef03a259faa24511d10ebfa44f69411d99e6a184fe68073", + "sha256:14d4f3cd4e8b524ae9b8aa567858beed70c392fdec26dbdb0a8a418392e71708", + "sha256:166eac03e48784a6a6e0e5f041cfebb1ab400b394db188c48b3a84737f505b67", + "sha256:17ff94e7a83aa8671a25bf5b59326ec26da379ace2ebc4411d690d80a7fbcf23", + "sha256:1e12bdc622676ce47ae9abbf455c189e442afdde8818d9da983085df6312e7a1", + "sha256:21915eb821a6b3d9d8eefdaf57d6c345b970ad722f856cd71739493ce003ad08", + "sha256:288c6a76705dc54fba69fbcb59904ae4ad768b4c768839b8ca5fdadec6dd8cfd", + "sha256:2bde6792f313f4e918caabc46532aa64aa27a0db05d75b20edfc5c6f46479de2", + "sha256:32ca72bbc673adbcfecb935bb3fb1b74e663d10a4b241aaa2f5a75fe1d1f90aa", + "sha256:356b3576ad078c89a6107caa9c50cc14e98e3a6c4874a37c3e0273e4baf33de8", + "sha256:40b951f601af999a8bf2ce8c71e8aaa4e8c6f78ff8afae7b808aae2dc50d4c40", + "sha256:572e1787d1460da79590bf44304abbc0a2da944ea64ec549188fa84d89bba7ab", + "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6", + "sha256:64e6175c2e53195278d7388c454e0b30997573f3f4bd63697f88d855f7a6a1fc", + "sha256:7227b47e73dedaa513cdebb98469705ef0d66eb5a1250144468e9c3097d6b59b", + "sha256:7418b6bfc7fe3331541b84bb2141c9baf1ec7132a7ecd9f375912eca810e714e", + "sha256:7cbd7574ce8e138bda9df4efc6bf2ab8572c9aff640d8ecfece1b006b68da963", + "sha256:7ff61ff178250f9bb3cd89752df0f1dd0e27316a8bd1465351652b1b4a4cdfd3", + "sha256:833e1551925ed51e6b44c800e71e77dacd7e49181fdc9ac9a0bf3714d515785d", + "sha256:8639cadfda96737427330a094476d4c7a56ac03de7265622fcf4cfe57c8ae18d", + "sha256:8c5d5b35f789a030ebb95bff352f1d27a93d81069f2adb3182d99882e095cefe", + "sha256:8c790abda465726cfb8bb08bd4ca9a5d0a7bd77c7ac1ca1b839ad823b948ea28", + "sha256:8d2f1fb53a421b410751887eb4ff21386d119ef9cde3797bf5e7ed49fb51a3b3", + "sha256:903bbd302a2378f984aef528f76d4c9b1748f318fe1294961c072bdc7f2ffa3e", + "sha256:93f81b134a165cc17123626ab8da2e30c0455441d4ab5576eed73a64c025b25c", + "sha256:95e69877983ea39b7303570fa6760f81a3eec23d0e3ab2021b7144b94d06202d", + "sha256:9633b3034d3d901f0a46b7939f8c4d64427dfba6bbc5a36b1a67364cf148a1b0", + "sha256:97e5306482182170ade15c4b0d8386ded995a07d7cc2ca8f27958d34d6736497", + "sha256:9f3cba480d3deb69f6ee2c1825060177a22c7826431458c697df88e6aeb3caee", + "sha256:aa5b467f15e78b82257319aebc78dd2915e4c1436c3c0d1ad6f53e47ba6e2713", + "sha256:abb7a75ed8b968f3061327c433a0fbd17b729947b400747c334a9c29a9af6c58", + "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a", + "sha256:b11548073a2213d950c3f671aa88e6f83cda6e2fb97a8b6317b1b5b33d850e06", + "sha256:b1692f7d6bc45e3200844be0dba153612103db241691088626a33ff1f24a0d88", + "sha256:b336501a05e13b616ef81ce329c0e09ac5ed8c732d9ba7e3e983fcc1a9e86965", + "sha256:b8c008de9d0daba7b6666aa5bbfdc23dcd78cafc33997c9b7741ff6353bafb7f", + "sha256:b92e29e58bef6d9cfd340c72b04d74c4b4e9f70c9fa7c78b674d1fec18896dc4", + "sha256:be5f425ff1f5f4b3c1e33ad64ab994eed12fc284a6ea71c5243fd564502ecbe5", + "sha256:dd0b1e9e891f69e7675ba5c92e28b90eaa045f6ab134ffe70b52e948aa175b3c", + "sha256:e30f5ea4ae2346e62cedde8794a56858a67b878dd79f7df76a0767e356b1744a", + "sha256:e6a36bb9474218c7a5b27ae476035497a6990e21d04c279884eb10d9b290f1b1", + "sha256:e859fcb4cbe93504ea18008d1df98dee4f7766db66c435e4882ab35cf70cac43", + "sha256:eb6ea6da4c787111adf40f697b4e58732ee0942b5d3bd8f435277643329ba627", + "sha256:ec8c433b3ab0419100bd45b47c9c8551248a5aee30ca5e9d399a0b57ac04651b", + "sha256:eff9d20417ff9dcb0d25e2defc2574d10b491bf2e693b4e491914738b7908168", + "sha256:f0214eb2a23b85528310dad848ad2ac58e735612929c8072f6093f3585fd342d", + "sha256:f276df9830dba7a333544bd41070e8175762a7ac20350786b322b714b0e654f5", + "sha256:f3acda1924472472ddd60c29e5b9db0cec629fbe3c5c5accb74d6d6d14773478", + "sha256:f70a9e237bb792c7cc7e44c531fd48f5897961701cdaa06cf22fc14965c496cf", + "sha256:f9d29ca8a77117315101425ec7ec2a47a22ccf59f5593378fc4077ac5b754fce", + "sha256:fa877ca7f6b48054f847b61d6fa7bed5cebb663ebc55e018fda12db09dcc664c", + "sha256:fdcec0b8399108577ec290f55551d926d9a1fa6cad45882093a7a07ac5ec147b" + ], + "markers": "platform_python_implementation == 'CPython'", + "version": "==1.1.2" + }, + "gunicorn": { + "extras": [ + "gevent" + ], + "hashes": [ + "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", + "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" + ], + "index": "pypi", + "version": "==20.1.0" + }, + "hiredis": { + "hashes": [ + "sha256:04026461eae67fdefa1949b7332e488224eac9e8f2b5c58c98b54d29af22093e", + "sha256:04927a4c651a0e9ec11c68e4427d917e44ff101f761cd3b5bc76f86aaa431d27", + "sha256:07bbf9bdcb82239f319b1f09e8ef4bdfaec50ed7d7ea51a56438f39193271163", + "sha256:09004096e953d7ebd508cded79f6b21e05dff5d7361771f59269425108e703bc", + "sha256:0adea425b764a08270820531ec2218d0508f8ae15a448568109ffcae050fee26", + "sha256:0b39ec237459922c6544d071cdcf92cbb5bc6685a30e7c6d985d8a3e3a75326e", + "sha256:0d5109337e1db373a892fdcf78eb145ffb6bbd66bb51989ec36117b9f7f9b579", + "sha256:0f41827028901814c709e744060843c77e78a3aca1e0d6875d2562372fcb405a", + "sha256:11d119507bb54e81f375e638225a2c057dda748f2b1deef05c2b1a5d42686048", + "sha256:1233e303645f468e399ec906b6b48ab7cd8391aae2d08daadbb5cad6ace4bd87", + "sha256:139705ce59d94eef2ceae9fd2ad58710b02aee91e7fa0ccb485665ca0ecbec63", + "sha256:1f03d4dadd595f7a69a75709bc81902673fa31964c75f93af74feac2f134cc54", + "sha256:240ce6dc19835971f38caf94b5738092cb1e641f8150a9ef9251b7825506cb05", + "sha256:294a6697dfa41a8cba4c365dd3715abc54d29a86a40ec6405d677ca853307cfb", + "sha256:3d55e36715ff06cdc0ab62f9591607c4324297b6b6ce5b58cb9928b3defe30ea", + "sha256:3dddf681284fe16d047d3ad37415b2e9ccdc6c8986c8062dbe51ab9a358b50a5", + "sha256:3f5f7e3a4ab824e3de1e1700f05ad76ee465f5f11f5db61c4b297ec29e692b2e", + "sha256:508999bec4422e646b05c95c598b64bdbef1edf0d2b715450a078ba21b385bcc", + "sha256:5d2a48c80cf5a338d58aae3c16872f4d452345e18350143b3bf7216d33ba7b99", + "sha256:5dc7a94bb11096bc4bffd41a3c4f2b958257085c01522aa81140c68b8bf1630a", + "sha256:65d653df249a2f95673976e4e9dd7ce10de61cfc6e64fa7eeaa6891a9559c581", + "sha256:7492af15f71f75ee93d2a618ca53fea8be85e7b625e323315169977fae752426", + "sha256:7f0055f1809b911ab347a25d786deff5e10e9cf083c3c3fd2dd04e8612e8d9db", + "sha256:807b3096205c7cec861c8803a6738e33ed86c9aae76cac0e19454245a6bbbc0a", + "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a", + "sha256:87c7c10d186f1743a8fd6a971ab6525d60abd5d5d200f31e073cd5e94d7e7a9d", + "sha256:8b42c0dc927b8d7c0eb59f97e6e34408e53bc489f9f90e66e568f329bff3e443", + "sha256:a00514362df15af041cc06e97aebabf2895e0a7c42c83c21894be12b84402d79", + "sha256:a39efc3ade8c1fb27c097fd112baf09d7fd70b8cb10ef1de4da6efbe066d381d", + "sha256:a4ee8000454ad4486fb9f28b0cab7fa1cd796fc36d639882d0b34109b5b3aec9", + "sha256:a7928283143a401e72a4fad43ecc85b35c27ae699cf5d54d39e1e72d97460e1d", + "sha256:adf4dd19d8875ac147bf926c727215a0faf21490b22c053db464e0bf0deb0485", + "sha256:ae8427a5e9062ba66fc2c62fb19a72276cf12c780e8db2b0956ea909c48acff5", + "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048", + "sha256:b84f29971f0ad4adaee391c6364e6f780d5aae7e9226d41964b26b49376071d0", + "sha256:c39c46d9e44447181cd502a35aad2bb178dbf1b1f86cf4db639d7b9614f837c6", + "sha256:cb2126603091902767d96bcb74093bd8b14982f41809f85c9b96e519c7e1dc41", + "sha256:dcef843f8de4e2ff5e35e96ec2a4abbdf403bd0f732ead127bd27e51f38ac298", + "sha256:e3447d9e074abf0e3cd85aef8131e01ab93f9f0e86654db7ac8a3f73c63706ce", + "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", + "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "html5lib": { + "hashes": [ + "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d", + "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.1" + }, + "idna": { + "hashes": [ + "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", + "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" + ], + "markers": "python_version >= '3'", + "version": "==3.3" + }, + "importlib-metadata": { + "hashes": [ + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" + ], + "markers": "python_version < '3.10'", + "version": "==4.11.3" + }, + "isodate": { + "hashes": [ + "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", + "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9" + ], + "version": "==0.6.1" + }, + "jinja2": { + "hashes": [ + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + ], + "index": "pypi", + "version": "==3.1.2" + }, + "lxml": { + "hashes": [ + "sha256:078306d19a33920004addeb5f4630781aaeabb6a8d01398045fcde085091a169", + "sha256:0c1978ff1fd81ed9dcbba4f91cf09faf1f8082c9d72eb122e92294716c605428", + "sha256:1010042bfcac2b2dc6098260a2ed022968dbdfaf285fc65a3acf8e4eb1ffd1bc", + "sha256:1d650812b52d98679ed6c6b3b55cbb8fe5a5460a0aef29aeb08dc0b44577df85", + "sha256:20b8a746a026017acf07da39fdb10aa80ad9877046c9182442bf80c84a1c4696", + "sha256:2403a6d6fb61c285969b71f4a3527873fe93fd0abe0832d858a17fe68c8fa507", + "sha256:24f5c5ae618395ed871b3d8ebfcbb36e3f1091fd847bf54c4de623f9107942f3", + "sha256:28d1af847786f68bec57961f31221125c29d6f52d9187c01cd34dc14e2b29430", + "sha256:31499847fc5f73ee17dbe1b8e24c6dafc4e8d5b48803d17d22988976b0171f03", + "sha256:31ba2cbc64516dcdd6c24418daa7abff989ddf3ba6d3ea6f6ce6f2ed6e754ec9", + "sha256:330bff92c26d4aee79c5bc4d9967858bdbe73fdbdbacb5daf623a03a914fe05b", + "sha256:5045ee1ccd45a89c4daec1160217d363fcd23811e26734688007c26f28c9e9e7", + "sha256:52cbf2ff155b19dc4d4100f7442f6a697938bf4493f8d3b0c51d45568d5666b5", + "sha256:530f278849031b0eb12f46cca0e5db01cfe5177ab13bd6878c6e739319bae654", + "sha256:545bd39c9481f2e3f2727c78c169425efbfb3fbba6e7db4f46a80ebb249819ca", + "sha256:5804e04feb4e61babf3911c2a974a5b86f66ee227cc5006230b00ac6d285b3a9", + "sha256:5a58d0b12f5053e270510bf12f753a76aaf3d74c453c00942ed7d2c804ca845c", + "sha256:5f148b0c6133fb928503cfcdfdba395010f997aa44bcf6474fcdd0c5398d9b63", + "sha256:5f7d7d9afc7b293147e2d506a4596641d60181a35279ef3aa5778d0d9d9123fe", + "sha256:60d2f60bd5a2a979df28ab309352cdcf8181bda0cca4529769a945f09aba06f9", + "sha256:6259b511b0f2527e6d55ad87acc1c07b3cbffc3d5e050d7e7bcfa151b8202df9", + "sha256:6268e27873a3d191849204d00d03f65c0e343b3bcb518a6eaae05677c95621d1", + "sha256:627e79894770783c129cc5e89b947e52aa26e8e0557c7e205368a809da4b7939", + "sha256:62f93eac69ec0f4be98d1b96f4d6b964855b8255c345c17ff12c20b93f247b68", + "sha256:6d6483b1229470e1d8835e52e0ff3c6973b9b97b24cd1c116dca90b57a2cc613", + "sha256:6f7b82934c08e28a2d537d870293236b1000d94d0b4583825ab9649aef7ddf63", + "sha256:6fe4ef4402df0250b75ba876c3795510d782def5c1e63890bde02d622570d39e", + "sha256:719544565c2937c21a6f76d520e6e52b726d132815adb3447ccffbe9f44203c4", + "sha256:730766072fd5dcb219dd2b95c4c49752a54f00157f322bc6d71f7d2a31fecd79", + "sha256:74eb65ec61e3c7c019d7169387d1b6ffcfea1b9ec5894d116a9a903636e4a0b1", + "sha256:7993232bd4044392c47779a3c7e8889fea6883be46281d45a81451acfd704d7e", + "sha256:80bbaddf2baab7e6de4bc47405e34948e694a9efe0861c61cdc23aa774fcb141", + "sha256:86545e351e879d0b72b620db6a3b96346921fa87b3d366d6c074e5a9a0b8dadb", + "sha256:891dc8f522d7059ff0024cd3ae79fd224752676447f9c678f2a5c14b84d9a939", + "sha256:8a31f24e2a0b6317f33aafbb2f0895c0bce772980ae60c2c640d82caac49628a", + "sha256:8b99ec73073b37f9ebe8caf399001848fced9c08064effdbfc4da2b5a8d07b93", + "sha256:986b7a96228c9b4942ec420eff37556c5777bfba6758edcb95421e4a614b57f9", + "sha256:a1547ff4b8a833511eeaceacbcd17b043214fcdb385148f9c1bc5556ca9623e2", + "sha256:a2bfc7e2a0601b475477c954bf167dee6d0f55cb167e3f3e7cefad906e7759f6", + "sha256:a3c5f1a719aa11866ffc530d54ad965063a8cbbecae6515acbd5f0fae8f48eaa", + "sha256:a9f1c3489736ff8e1c7652e9dc39f80cff820f23624f23d9eab6e122ac99b150", + "sha256:aa0cf4922da7a3c905d000b35065df6184c0dc1d866dd3b86fd961905bbad2ea", + "sha256:ad4332a532e2d5acb231a2e5d33f943750091ee435daffca3fec0a53224e7e33", + "sha256:b2582b238e1658c4061ebe1b4df53c435190d22457642377fd0cb30685cdfb76", + "sha256:b6fc2e2fb6f532cf48b5fed57567ef286addcef38c28874458a41b7837a57807", + "sha256:b92d40121dcbd74831b690a75533da703750f7041b4bf951befc657c37e5695a", + "sha256:bbab6faf6568484707acc052f4dfc3802bdb0cafe079383fbaa23f1cdae9ecd4", + "sha256:c0b88ed1ae66777a798dc54f627e32d3b81c8009967c63993c450ee4cbcbec15", + "sha256:ce13d6291a5f47c1c8dbd375baa78551053bc6b5e5c0e9bb8e39c0a8359fd52f", + "sha256:db3535733f59e5605a88a706824dfcb9bd06725e709ecb017e165fc1d6e7d429", + "sha256:dd10383f1d6b7edf247d0960a3db274c07e96cf3a3fc7c41c8448f93eac3fb1c", + "sha256:e01f9531ba5420838c801c21c1b0f45dbc9607cb22ea2cf132844453bec863a5", + "sha256:e11527dc23d5ef44d76fef11213215c34f36af1608074561fcc561d983aeb870", + "sha256:e1ab2fac607842ac36864e358c42feb0960ae62c34aa4caaf12ada0a1fb5d99b", + "sha256:e1fd7d2fe11f1cb63d3336d147c852f6d07de0d0020d704c6031b46a30b02ca8", + "sha256:e9f84ed9f4d50b74fbc77298ee5c870f67cb7e91dcdc1a6915cb1ff6a317476c", + "sha256:ec4b4e75fc68da9dc0ed73dcdb431c25c57775383fec325d23a770a64e7ebc87", + "sha256:f10ce66fcdeb3543df51d423ede7e238be98412232fca5daec3e54bcd16b8da0", + "sha256:f63f62fc60e6228a4ca9abae28228f35e1bd3ce675013d1dfb828688d50c6e23", + "sha256:fa56bb08b3dd8eac3a8c5b7d075c94e74f755fd9d8a04543ae8d37b1612dd170", + "sha256:fa9b7c450be85bfc6cd39f6df8c5b8cbd76b5d6fc1f69efec80203f9894b885f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.8.0" + }, + "markdown": { + "hashes": [ + "sha256:76df8ae32294ec39dcf89340382882dfa12975f87f45c3ed1ecdb1e8cefc7006", + "sha256:9923332318f843411e9932237530df53162e29dc7a4e2b91e35764583c46c9a3" + ], + "index": "pypi", + "version": "==3.3.6" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "mf2py": { + "hashes": [ + "sha256:84f1f8f2ff3f1deb1c30be497e7ccd805452996a662fd4a77f09e0105bede2c9" + ], + "index": "pypi", + "version": "==1.1.2" + }, + "mf2util": { + "hashes": [ + "sha256:70b5e08dd553e19b1cb46cd6060ff6a41e57859588364efece6e52d14267c859" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "msgpack": { + "hashes": [ + "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc", + "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147", + "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3", + "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba", + "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39", + "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85", + "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9", + "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a", + "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec", + "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88", + "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e", + "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a", + "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b", + "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1", + "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3", + "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef", + "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079", + "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52", + "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a", + "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a", + "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4", + "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996", + "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73", + "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a", + "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920", + "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7", + "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d", + "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770", + "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50", + "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2", + "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2", + "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d", + "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea", + "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611" + ], + "index": "pypi", + "version": "==1.0.3" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pillow": { + "hashes": [ + "sha256:01ce45deec9df310cbbee11104bae1a2a43308dd9c317f99235b6d3080ddd66e", + "sha256:0c51cb9edac8a5abd069fd0758ac0a8bfe52c261ee0e330f363548aca6893595", + "sha256:17869489de2fce6c36690a0c721bd3db176194af5f39249c1ac56d0bb0fcc512", + "sha256:21dee8466b42912335151d24c1665fcf44dc2ee47e021d233a40c3ca5adae59c", + "sha256:25023a6209a4d7c42154073144608c9a71d3512b648a2f5d4465182cb93d3477", + "sha256:255c9d69754a4c90b0ee484967fc8818c7ff8311c6dddcc43a4340e10cd1636a", + "sha256:35be4a9f65441d9982240e6966c1eaa1c654c4e5e931eaf580130409e31804d4", + "sha256:3f42364485bfdab19c1373b5cd62f7c5ab7cc052e19644862ec8f15bb8af289e", + "sha256:3fddcdb619ba04491e8f771636583a7cc5a5051cd193ff1aa1ee8616d2a692c5", + "sha256:463acf531f5d0925ca55904fa668bb3461c3ef6bc779e1d6d8a488092bdee378", + "sha256:4fe29a070de394e449fd88ebe1624d1e2d7ddeed4c12e0b31624561b58948d9a", + "sha256:55dd1cf09a1fd7c7b78425967aacae9b0d70125f7d3ab973fadc7b5abc3de652", + "sha256:5a3ecc026ea0e14d0ad7cd990ea7f48bfcb3eb4271034657dc9d06933c6629a7", + "sha256:5cfca31ab4c13552a0f354c87fbd7f162a4fafd25e6b521bba93a57fe6a3700a", + "sha256:66822d01e82506a19407d1afc104c3fcea3b81d5eb11485e593ad6b8492f995a", + "sha256:69e5ddc609230d4408277af135c5b5c8fe7a54b2bdb8ad7c5100b86b3aab04c6", + "sha256:6b6d4050b208c8ff886fd3db6690bf04f9a48749d78b41b7a5bf24c236ab0165", + "sha256:7a053bd4d65a3294b153bdd7724dce864a1d548416a5ef61f6d03bf149205160", + "sha256:82283af99c1c3a5ba1da44c67296d5aad19f11c535b551a5ae55328a317ce331", + "sha256:8782189c796eff29dbb37dd87afa4ad4d40fc90b2742704f94812851b725964b", + "sha256:8d79c6f468215d1a8415aa53d9868a6b40c4682165b8cb62a221b1baa47db458", + "sha256:97bda660702a856c2c9e12ec26fc6d187631ddfd896ff685814ab21ef0597033", + "sha256:a325ac71914c5c043fa50441b36606e64a10cd262de12f7a179620f579752ff8", + "sha256:a336a4f74baf67e26f3acc4d61c913e378e931817cd1e2ef4dfb79d3e051b481", + "sha256:a598d8830f6ef5501002ae85c7dbfcd9c27cc4efc02a1989369303ba85573e58", + "sha256:a5eaf3b42df2bcda61c53a742ee2c6e63f777d0e085bbc6b2ab7ed57deb13db7", + "sha256:aea7ce61328e15943d7b9eaca87e81f7c62ff90f669116f857262e9da4057ba3", + "sha256:af79d3fde1fc2e33561166d62e3b63f0cc3e47b5a3a2e5fea40d4917754734ea", + "sha256:c24f718f9dd73bb2b31a6201e6db5ea4a61fdd1d1c200f43ee585fc6dcd21b34", + "sha256:c5b0ff59785d93b3437c3703e3c64c178aabada51dea2a7f2c5eccf1bcf565a3", + "sha256:c7110ec1701b0bf8df569a7592a196c9d07c764a0a74f65471ea56816f10e2c8", + "sha256:c870193cce4b76713a2b29be5d8327c8ccbe0d4a49bc22968aa1e680930f5581", + "sha256:c9efef876c21788366ea1f50ecb39d5d6f65febe25ad1d4c0b8dff98843ac244", + "sha256:de344bcf6e2463bb25179d74d6e7989e375f906bcec8cb86edb8b12acbc7dfef", + "sha256:eb1b89b11256b5b6cad5e7593f9061ac4624f7651f7a8eb4dfa37caa1dfaa4d0", + "sha256:ed742214068efa95e9844c2d9129e209ed63f61baa4d54dbf4cf8b5e2d30ccf2", + "sha256:f401ed2bbb155e1ade150ccc63db1a4f6c1909d3d378f7d1235a44e90d75fb97", + "sha256:fb89397013cf302f282f0fc998bb7abf11d49dcff72c8ecb320f76ea6e2c5717" + ], + "index": "pypi", + "version": "==9.1.0" + }, + "psycopg2-binary": { + "hashes": [ + "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7", + "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76", + "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa", + "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9", + "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004", + "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1", + "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094", + "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57", + "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af", + "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554", + "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232", + "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c", + "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b", + "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834", + "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2", + "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71", + "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460", + "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e", + "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4", + "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d", + "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d", + "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9", + "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f", + "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063", + "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478", + "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092", + "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c", + "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce", + "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1", + "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65", + "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e", + "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4", + "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029", + "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33", + "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39", + "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53", + "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307", + "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42", + "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35", + "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8", + "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb", + "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae", + "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e", + "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f", + "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba", + "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24", + "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca", + "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb", + "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef", + "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42", + "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1", + "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667", + "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272", + "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281", + "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e", + "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd" + ], + "index": "pypi", + "version": "==2.9.3" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.21" + }, + "pyparsing": { + "hashes": [ + "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", + "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.8" + }, + "python-baseconv": { + "hashes": [ + "sha256:0539f8bd0464013b05ad62e0a1673f0ac9086c76b43ebf9f833053527cd9931b" + ], + "index": "pypi", + "version": "==1.2.2" + }, + "python-magic": { + "hashes": [ + "sha256:1a2c81e8f395c744536369790bd75094665e9644110a6623bcc3bbea30f03973", + "sha256:21f5f542aa0330f5c8a64442528542f6215c8e18d2466b399b0d9d39356d83fc" + ], + "index": "pypi", + "version": "==0.4.25" + }, + "python-slugify": { + "hashes": [ + "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1", + "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927" + ], + "index": "pypi", + "version": "==6.1.2" + }, + "pytz": { + "hashes": [ + "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7", + "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c" + ], + "version": "==2022.1" + }, + "pyup-django": { + "hashes": [ + "sha256:f02242b4c7a8926bf9118054429dcaf84e5708a050548abcd3b2b9de4a7570b9", + "sha256:fe84cef39c41d5feb24e307d6c8a55454db50df4c6955fa6a890a42b6e58650e" + ], + "index": "pypi", + "version": "==0.4.0" + }, + "pyyaml": { + "hashes": [ + "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", + "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", + "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", + "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", + "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", + "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", + "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", + "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", + "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", + "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", + "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", + "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", + "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", + "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", + "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", + "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", + "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", + "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", + "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", + "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", + "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", + "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", + "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", + "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", + "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", + "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", + "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" + ], + "index": "pypi", + "version": "==6.0" + }, + "qrcode": { + "hashes": [ + "sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578" + ], + "index": "pypi", + "version": "==7.3.1" + }, + "rcssmin": { + "hashes": [ + "sha256:0a6aae7e119509445bf7aa6da6ca0f285cc198273c20f470ad999ff83bbadcf9", + "sha256:1512223b6a687bb747e4e531187bd49a56ed71287e7ead9529cbaa1ca4718a0a", + "sha256:1d7c2719d014e4e4df4e33b75ae8067c7e246cf470eaec8585e06e2efac7586c", + "sha256:2211a5c91ea14a5937b57904c9121f8bfef20987825e55368143da7d25446e3b", + "sha256:27fc400627fd3d328b7fe95af2a01f5d0af6b5af39731af5d071826a1f08e362", + "sha256:30f5522285065cae0164d20068377d84b5d10b414156115f8729b034d0ea5e8b", + "sha256:32ccaebbbd4d56eab08cf26aed36f5d33389b9d1d3ca1fecf53eb6ab77760ddf", + "sha256:352dd3a78eb914bb1cb269ac2b66b3154f2490a52ab605558c681de3fb5194d2", + "sha256:37f1242e34ca273ed2c26cf778854e18dd11b31c6bfca60e23fce146c84667c1", + "sha256:49807735f26f59404194f1e6f93254b6d5b6f7748c2a954f4470a86a40ff4c13", + "sha256:506e33ab4c47051f7deae35b6d8dbb4a5c025f016e90a830929a1ecc7daa1682", + "sha256:6158d0d86cd611c5304d738dc3d6cfeb23864dd78ad0d83a633f443696ac5d77", + "sha256:7085d1b51dd2556f3aae03947380f6e9e1da29fb1eeadfa6766b7f105c54c9ff", + "sha256:7c44002b79f3656348196005b9522ec5e04f182b466f66d72b16be0bd03c13d8", + "sha256:7da63fee37edf204bbd86785edb4d7491642adbfd1d36fd230b7ccbbd8db1a6f", + "sha256:8b659a88850e772c84cfac4520ec223de6807875e173d8ef3248ab7f90876066", + "sha256:c28b9eb20982b45ebe6adef8bd2547e5ed314dafddfff4eba806b0f8c166cfd1", + "sha256:ddff3a41611664c7f1d9e3d8a9c1669e0e155ac0458e586ffa834dc5953e7d9f", + "sha256:f1a37bbd36b050813673e62ae6464467548628690bf4d48a938170e121e8616e", + "sha256:f31c82d06ba2dbf33c20db9550157e80bb0c4cbd24575c098f0831d1d2e3c5df" + ], + "version": "==1.1.0" + }, + "redis": { + "hashes": [ + "sha256:0107dc8e98a4f1d1d4aa00100e044287f77121a1e6d2085545c4b7fa94a7a27f", + "sha256:4e95f4ec5f49e636efcf20061a5a9110c20852f607cfca6865c07aaa8a739ee2" + ], + "markers": "python_version >= '3.6'", + "version": "==4.2.2" + }, + "requests": { + "hashes": [ + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.27.1" + }, + "rjsmin": { + "hashes": [ + "sha256:05efa485dfddb6418e3b86d8862463aa15641a61f6ae05e7e6de8f116ee77c69", + "sha256:1622fbb6c6a8daaf77da13cc83356539bfe79c1440f9664b02c7f7b150b9a18e", + "sha256:1c93b29fd725e61718299ffe57de93ff32d71b313eaabbfcc7bd32ddb82831d5", + "sha256:2ed83aca637186bafdc894b4b7fc3657e2d74014ccca7d3d69122c1e82675216", + "sha256:38a4474ed52e1575fb9da983ec8657faecd8ab3738508d36e04f87769411fd3d", + "sha256:3b14f4c2933ec194eb816b71a0854ce461b6419a3d852bf360344731ab28c0a6", + "sha256:40e7211a25d9a11ac9ff50446e41268c978555676828af86fa1866615823bfff", + "sha256:41c7c3910f7b8816e37366b293e576ddecf696c5f2197d53cf2c1526ac336646", + "sha256:4387a00777faddf853eebdece9f2e56ebaf243c3f24676a9de6a20c5d4f3d731", + "sha256:54fc30519365841b27556ccc1cb94c5b4413c384ff6d467442fddba66e2e325a", + "sha256:6c395ffc130332cca744f081ed5efd5699038dcb7a5d30c3ff4bc6adb5b30a62", + "sha256:6c529feb6c400984452494c52dd9fdf59185afeacca2afc5174a28ab37751a1b", + "sha256:86c4da7285ddafe6888cb262da563570f28e4a31146b5164a7a6947b1222196b", + "sha256:8944a8a55ac825b8e5ec29f341ecb7574697691ef416506885898d2f780fb4ca", + "sha256:993935654c1311280e69665367d7e6ff694ac9e1609168cf51cae8c0307df0db", + "sha256:99e5597a812b60058baa1457387dc79cca7d273b2a700dc98bfd20d43d60711d", + "sha256:b6a7c8c8d19e154334f640954e43e57283e87bb4a2f6e23295db14eea8e9fc1d", + "sha256:c81229ffe5b0a0d5b3b5d5e6d0431f182572de9e9a077e85dbae5757db0ab75c", + "sha256:d63e193a2f932a786ae82068aa76d1d126fcdff8582094caff9e5e66c4dcc124", + "sha256:e18fe1a610fb105273bb369f61c2b0bd9e66a3f0792e27e4cac44e42ace1968b" + ], + "version": "==1.2.0" + }, + "ronkyuu": { + "hashes": [ + "sha256:06bc8bb80e31b7a1f050d61f7206fe9326249d0aed8860c49cebb73758541eea", + "sha256:bb7d9f69d76b2e1c57a665ebc2baf7002ceaa311782b54652a1e6f97b61f0ed9" + ], + "index": "pypi", + "version": "==0.9" + }, + "rq": { + "hashes": [ + "sha256:62d06b44c3acfa5d1933c5a4ec3fbc2484144a8af60e318d0b8447c5236271e2", + "sha256:92f4cf38b2364c1697b541e77c0fe62b7e5242fa864324f262be126ee2a07e3a" + ], + "markers": "python_version >= '3.5'", + "version": "==1.10.1" + }, + "setuptools": { + "hashes": [ + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + ], + "markers": "python_version >= '3.7'", + "version": "==62.1.0" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "soupsieve": { + "hashes": [ + "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", + "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" + ], + "markers": "python_version >= '3.6'", + "version": "==2.3.2.post1" + }, + "sqlparse": { + "hashes": [ + "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", + "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" + ], + "markers": "python_version >= '3.5'", + "version": "==0.4.2" + }, + "text-unidecode": { + "hashes": [ + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + ], + "version": "==1.3" + }, + "urllib3": { + "hashes": [ + "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", + "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.9" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, + "wrapt": { + "hashes": [ + "sha256:00108411e0f34c52ce16f81f1d308a571df7784932cc7491d1e94be2ee93374b", + "sha256:01f799def9b96a8ec1ef6b9c1bbaf2bbc859b87545efbecc4a78faea13d0e3a0", + "sha256:09d16ae7a13cff43660155383a2372b4aa09109c7127aa3f24c3cf99b891c330", + "sha256:14e7e2c5f5fca67e9a6d5f753d21f138398cad2b1159913ec9e9a67745f09ba3", + "sha256:167e4793dc987f77fd476862d32fa404d42b71f6a85d3b38cbce711dba5e6b68", + "sha256:1807054aa7b61ad8d8103b3b30c9764de2e9d0c0978e9d3fc337e4e74bf25faa", + "sha256:1f83e9c21cd5275991076b2ba1cd35418af3504667affb4745b48937e214bafe", + "sha256:21b1106bff6ece8cb203ef45b4f5778d7226c941c83aaaa1e1f0f4f32cc148cd", + "sha256:22626dca56fd7f55a0733e604f1027277eb0f4f3d95ff28f15d27ac25a45f71b", + "sha256:23f96134a3aa24cc50614920cc087e22f87439053d886e474638c68c8d15dc80", + "sha256:2498762814dd7dd2a1d0248eda2afbc3dd9c11537bc8200a4b21789b6df6cd38", + "sha256:28c659878f684365d53cf59dc9a1929ea2eecd7ac65da762be8b1ba193f7e84f", + "sha256:2eca15d6b947cfff51ed76b2d60fd172c6ecd418ddab1c5126032d27f74bc350", + "sha256:354d9fc6b1e44750e2a67b4b108841f5f5ea08853453ecbf44c81fdc2e0d50bd", + "sha256:36a76a7527df8583112b24adc01748cd51a2d14e905b337a6fefa8b96fc708fb", + "sha256:3a0a4ca02752ced5f37498827e49c414d694ad7cf451ee850e3ff160f2bee9d3", + "sha256:3a71dbd792cc7a3d772ef8cd08d3048593f13d6f40a11f3427c000cf0a5b36a0", + "sha256:3a88254881e8a8c4784ecc9cb2249ff757fd94b911d5df9a5984961b96113fff", + "sha256:47045ed35481e857918ae78b54891fac0c1d197f22c95778e66302668309336c", + "sha256:4775a574e9d84e0212f5b18886cace049a42e13e12009bb0491562a48bb2b758", + "sha256:493da1f8b1bb8a623c16552fb4a1e164c0200447eb83d3f68b44315ead3f9036", + "sha256:4b847029e2d5e11fd536c9ac3136ddc3f54bc9488a75ef7d040a3900406a91eb", + "sha256:59d7d92cee84a547d91267f0fea381c363121d70fe90b12cd88241bd9b0e1763", + "sha256:5a0898a640559dec00f3614ffb11d97a2666ee9a2a6bad1259c9facd01a1d4d9", + "sha256:5a9a1889cc01ed2ed5f34574c90745fab1dd06ec2eee663e8ebeefe363e8efd7", + "sha256:5b835b86bd5a1bdbe257d610eecab07bf685b1af2a7563093e0e69180c1d4af1", + "sha256:5f24ca7953f2643d59a9c87d6e272d8adddd4a53bb62b9208f36db408d7aafc7", + "sha256:61e1a064906ccba038aa3c4a5a82f6199749efbbb3cef0804ae5c37f550eded0", + "sha256:65bf3eb34721bf18b5a021a1ad7aa05947a1767d1aa272b725728014475ea7d5", + "sha256:6807bcee549a8cb2f38f73f469703a1d8d5d990815c3004f21ddb68a567385ce", + "sha256:68aeefac31c1f73949662ba8affaf9950b9938b712fb9d428fa2a07e40ee57f8", + "sha256:6915682f9a9bc4cf2908e83caf5895a685da1fbd20b6d485dafb8e218a338279", + "sha256:6d9810d4f697d58fd66039ab959e6d37e63ab377008ef1d63904df25956c7db0", + "sha256:729d5e96566f44fccac6c4447ec2332636b4fe273f03da128fff8d5559782b06", + "sha256:748df39ed634851350efa87690c2237a678ed794fe9ede3f0d79f071ee042561", + "sha256:763a73ab377390e2af26042f685a26787c402390f682443727b847e9496e4a2a", + "sha256:8323a43bd9c91f62bb7d4be74cc9ff10090e7ef820e27bfe8815c57e68261311", + "sha256:8529b07b49b2d89d6917cfa157d3ea1dfb4d319d51e23030664a827fe5fd2131", + "sha256:87fa943e8bbe40c8c1ba4086971a6fefbf75e9991217c55ed1bcb2f1985bd3d4", + "sha256:88236b90dda77f0394f878324cfbae05ae6fde8a84d548cfe73a75278d760291", + "sha256:891c353e95bb11abb548ca95c8b98050f3620a7378332eb90d6acdef35b401d4", + "sha256:89ba3d548ee1e6291a20f3c7380c92f71e358ce8b9e48161401e087e0bc740f8", + "sha256:8c6be72eac3c14baa473620e04f74186c5d8f45d80f8f2b4eda6e1d18af808e8", + "sha256:9a242871b3d8eecc56d350e5e03ea1854de47b17f040446da0e47dc3e0b9ad4d", + "sha256:9a3ff5fb015f6feb78340143584d9f8a0b91b6293d6b5cf4295b3e95d179b88c", + "sha256:9a5a544861b21e0e7575b6023adebe7a8c6321127bb1d238eb40d99803a0e8bd", + "sha256:9d57677238a0c5411c76097b8b93bdebb02eb845814c90f0b01727527a179e4d", + "sha256:9d8c68c4145041b4eeae96239802cfdfd9ef927754a5be3f50505f09f309d8c6", + "sha256:9d9fcd06c952efa4b6b95f3d788a819b7f33d11bea377be6b8980c95e7d10775", + "sha256:a0057b5435a65b933cbf5d859cd4956624df37b8bf0917c71756e4b3d9958b9e", + "sha256:a65bffd24409454b889af33b6c49d0d9bcd1a219b972fba975ac935f17bdf627", + "sha256:b0ed6ad6c9640671689c2dbe6244680fe8b897c08fd1fab2228429b66c518e5e", + "sha256:b21650fa6907e523869e0396c5bd591cc326e5c1dd594dcdccac089561cacfb8", + "sha256:b3f7e671fb19734c872566e57ce7fc235fa953d7c181bb4ef138e17d607dc8a1", + "sha256:b77159d9862374da213f741af0c361720200ab7ad21b9f12556e0eb95912cd48", + "sha256:bb36fbb48b22985d13a6b496ea5fb9bb2a076fea943831643836c9f6febbcfdc", + "sha256:d066ffc5ed0be00cd0352c95800a519cf9e4b5dd34a028d301bdc7177c72daf3", + "sha256:d332eecf307fca852d02b63f35a7872de32d5ba8b4ec32da82f45df986b39ff6", + "sha256:d808a5a5411982a09fef6b49aac62986274ab050e9d3e9817ad65b2791ed1425", + "sha256:d9bdfa74d369256e4218000a629978590fd7cb6cf6893251dad13d051090436d", + "sha256:db6a0ddc1282ceb9032e41853e659c9b638789be38e5b8ad7498caac00231c23", + "sha256:debaf04f813ada978d7d16c7dfa16f3c9c2ec9adf4656efdc4defdf841fc2f0c", + "sha256:f0408e2dbad9e82b4c960274214af533f856a199c9274bd4aff55d4634dedc33", + "sha256:f2f3bc7cd9c9fcd39143f11342eb5963317bd54ecc98e3650ca22704b69d9653" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.14.0" + }, + "xrd": { + "hashes": [ + "sha256:51d01f732b5b5b7983c5179ffaed864408d95a667b3a6630fe27aa7528274089" + ], + "index": "pypi", + "version": "==0.1" + }, + "zipp": { + "hashes": [ + "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", + "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" + ], + "markers": "python_version >= '3.7'", + "version": "==3.8.0" + }, + "zope.event": { + "hashes": [ + "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42", + "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330" + ], + "version": "==4.5.0" + }, + "zope.interface": { + "hashes": [ + "sha256:08f9636e99a9d5410181ba0729e0408d3d8748026ea938f3b970a0249daa8192", + "sha256:0b465ae0962d49c68aa9733ba92a001b2a0933c317780435f00be7ecb959c702", + "sha256:0cba8477e300d64a11a9789ed40ee8932b59f9ee05f85276dbb4b59acee5dd09", + "sha256:0cee5187b60ed26d56eb2960136288ce91bcf61e2a9405660d271d1f122a69a4", + "sha256:0ea1d73b7c9dcbc5080bb8aaffb776f1c68e807767069b9ccdd06f27a161914a", + "sha256:0f91b5b948686659a8e28b728ff5e74b1be6bf40cb04704453617e5f1e945ef3", + "sha256:15e7d1f7a6ee16572e21e3576d2012b2778cbacf75eb4b7400be37455f5ca8bf", + "sha256:17776ecd3a1fdd2b2cd5373e5ef8b307162f581c693575ec62e7c5399d80794c", + "sha256:194d0bcb1374ac3e1e023961610dc8f2c78a0f5f634d0c737691e215569e640d", + "sha256:1c0e316c9add0db48a5b703833881351444398b04111188069a26a61cfb4df78", + "sha256:205e40ccde0f37496904572035deea747390a8b7dc65146d30b96e2dd1359a83", + "sha256:273f158fabc5ea33cbc936da0ab3d4ba80ede5351babc4f577d768e057651531", + "sha256:2876246527c91e101184f63ccd1d716ec9c46519cc5f3d5375a3351c46467c46", + "sha256:2c98384b254b37ce50eddd55db8d381a5c53b4c10ee66e1e7fe749824f894021", + "sha256:2e5a26f16503be6c826abca904e45f1a44ff275fdb7e9d1b75c10671c26f8b94", + "sha256:334701327f37c47fa628fc8b8d28c7d7730ce7daaf4bda1efb741679c2b087fc", + "sha256:3748fac0d0f6a304e674955ab1365d515993b3a0a865e16a11ec9d86fb307f63", + "sha256:3c02411a3b62668200910090a0dff17c0b25aaa36145082a5a6adf08fa281e54", + "sha256:3dd4952748521205697bc2802e4afac5ed4b02909bb799ba1fe239f77fd4e117", + "sha256:3f24df7124c323fceb53ff6168da70dbfbae1442b4f3da439cd441681f54fe25", + "sha256:469e2407e0fe9880ac690a3666f03eb4c3c444411a5a5fddfdabc5d184a79f05", + "sha256:4de4bc9b6d35c5af65b454d3e9bc98c50eb3960d5a3762c9438df57427134b8e", + "sha256:5208ebd5152e040640518a77827bdfcc73773a15a33d6644015b763b9c9febc1", + "sha256:52de7fc6c21b419078008f697fd4103dbc763288b1406b4562554bd47514c004", + "sha256:5bb3489b4558e49ad2c5118137cfeaf59434f9737fa9c5deefc72d22c23822e2", + "sha256:5dba5f530fec3f0988d83b78cc591b58c0b6eb8431a85edd1569a0539a8a5a0e", + "sha256:5dd9ca406499444f4c8299f803d4a14edf7890ecc595c8b1c7115c2342cadc5f", + "sha256:5f931a1c21dfa7a9c573ec1f50a31135ccce84e32507c54e1ea404894c5eb96f", + "sha256:63b82bb63de7c821428d513607e84c6d97d58afd1fe2eb645030bdc185440120", + "sha256:66c0061c91b3b9cf542131148ef7ecbecb2690d48d1612ec386de9d36766058f", + "sha256:6f0c02cbb9691b7c91d5009108f975f8ffeab5dff8f26d62e21c493060eff2a1", + "sha256:71aace0c42d53abe6fc7f726c5d3b60d90f3c5c055a447950ad6ea9cec2e37d9", + "sha256:7d97a4306898b05404a0dcdc32d9709b7d8832c0c542b861d9a826301719794e", + "sha256:7df1e1c05304f26faa49fa752a8c690126cf98b40b91d54e6e9cc3b7d6ffe8b7", + "sha256:8270252effc60b9642b423189a2fe90eb6b59e87cbee54549db3f5562ff8d1b8", + "sha256:867a5ad16892bf20e6c4ea2aab1971f45645ff3102ad29bd84c86027fa99997b", + "sha256:877473e675fdcc113c138813a5dd440da0769a2d81f4d86614e5d62b69497155", + "sha256:8892f89999ffd992208754851e5a052f6b5db70a1e3f7d54b17c5211e37a98c7", + "sha256:9a9845c4c6bb56e508651f005c4aeb0404e518c6f000d5a1123ab077ab769f5c", + "sha256:a1e6e96217a0f72e2b8629e271e1b280c6fa3fe6e59fa8f6701bec14e3354325", + "sha256:a8156e6a7f5e2a0ff0c5b21d6bcb45145efece1909efcbbbf48c56f8da68221d", + "sha256:a9506a7e80bcf6eacfff7f804c0ad5350c8c95b9010e4356a4b36f5322f09abb", + "sha256:af310ec8335016b5e52cae60cda4a4f2a60a788cbb949a4fbea13d441aa5a09e", + "sha256:b0297b1e05fd128d26cc2460c810d42e205d16d76799526dfa8c8ccd50e74959", + "sha256:bf68f4b2b6683e52bec69273562df15af352e5ed25d1b6641e7efddc5951d1a7", + "sha256:d0c1bc2fa9a7285719e5678584f6b92572a5b639d0e471bb8d4b650a1a910920", + "sha256:d4d9d6c1a455d4babd320203b918ccc7fcbefe308615c521062bc2ba1aa4d26e", + "sha256:db1fa631737dab9fa0b37f3979d8d2631e348c3b4e8325d6873c2541d0ae5a48", + "sha256:dd93ea5c0c7f3e25335ab7d22a507b1dc43976e1345508f845efc573d3d779d8", + "sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4", + "sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==5.4.0" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "jedi": { + "hashes": [ + "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d", + "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab" + ], + "markers": "python_version >= '3.6'", + "version": "==0.18.1" + }, + "mypy": { + "hashes": [ + "sha256:0112752a6ff07230f9ec2f71b0d3d4e088a910fdce454fdb6553e83ed0eced7d", + "sha256:0384d9f3af49837baa92f559d3fa673e6d2652a16550a9ee07fc08c736f5e6f8", + "sha256:1b333cfbca1762ff15808a0ef4f71b5d3eed8528b23ea1c3fb50543c867d68de", + "sha256:1fdeb0a0f64f2a874a4c1f5271f06e40e1e9779bf55f9567f149466fc7a55038", + "sha256:4c653e4846f287051599ed8f4b3c044b80e540e88feec76b11044ddc5612ffed", + "sha256:563514c7dc504698fb66bb1cf897657a173a496406f1866afae73ab5b3cdb334", + "sha256:5b231afd6a6e951381b9ef09a1223b1feabe13625388db48a8690f8daa9b71ff", + "sha256:5ce6a09042b6da16d773d2110e44f169683d8cc8687e79ec6d1181a72cb028d2", + "sha256:5e7647df0f8fc947388e6251d728189cfadb3b1e558407f93254e35abc026e22", + "sha256:6003de687c13196e8a1243a5e4bcce617d79b88f83ee6625437e335d89dfebe2", + "sha256:61504b9a5ae166ba5ecfed9e93357fd51aa693d3d434b582a925338a2ff57fd2", + "sha256:77423570c04aca807508a492037abbd72b12a1fb25a385847d191cd50b2c9605", + "sha256:a4d9898f46446bfb6405383b57b96737dcfd0a7f25b748e78ef3e8c576bba3cb", + "sha256:a952b8bc0ae278fc6316e6384f67bb9a396eb30aced6ad034d3a76120ebcc519", + "sha256:b5b5bd0ffb11b4aba2bb6d31b8643902c48f990cc92fda4e21afac658044f0c0", + "sha256:ca75ecf2783395ca3016a5e455cb322ba26b6d33b4b413fcdedfc632e67941dc", + "sha256:cf9c261958a769a3bd38c3e133801ebcd284ffb734ea12d01457cb09eacf7d7b", + "sha256:dd4d670eee9610bf61c25c940e9ade2d0ed05eb44227275cce88701fee014b1f", + "sha256:e19736af56947addedce4674c0971e5dceef1b5ec7d667fe86bcd2b07f8f9075", + "sha256:eaea21d150fb26d7b4856766e7addcf929119dd19fc832b22e71d942835201ef", + "sha256:eaff8156016487c1af5ffa5304c3e3fd183edcb412f3e9c72db349faf3f6e0eb", + "sha256:ee0a36edd332ed2c5208565ae6e3a7afc0eabb53f5327e281f2ef03a6bc7687a", + "sha256:ef7beb2a3582eb7a9f37beaf38a28acfd801988cde688760aea9e6cc4832b10b" + ], + "index": "pypi", + "version": "==0.950" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "parso": { + "hashes": [ + "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", + "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + ], + "markers": "python_version >= '3.6'", + "version": "==0.8.3" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752", + "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7" + ], + "markers": "python_full_version >= '3.6.2'", + "version": "==3.0.29" + }, + "ptpython": { + "hashes": [ + "sha256:99636899ab0e4d026e2ecc9368269114f387b4bb5411e57f072b0bde724d9f99", + "sha256:eafd4ced27ca5dc370881d4358d1ab5041b32d88d31af8e3c24167fe4af64ed6" + ], + "index": "pypi", + "version": "==3.0.20" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pygments": { + "hashes": [ + "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", + "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" + ], + "markers": "python_version >= '3.6'", + "version": "==2.12.0" + }, + "pyparsing": { + "hashes": [ + "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", + "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.8" + }, + "pytest": { + "hashes": [ + "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", + "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" + ], + "markers": "python_version >= '3.7'", + "version": "==7.1.2" + }, + "pytest-django": { + "hashes": [ + "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e", + "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2" + ], + "index": "pypi", + "version": "==4.5.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "types-bleach": { + "hashes": [ + "sha256:6fcb75ee4b69190fe60340147b66442cecddaefe3c0629433a4240da1ec2dcf6", + "sha256:e1498c512a62117496cf82be3d129972bb89fd1d6482b001cdeb2759ab3c82f5" + ], + "index": "pypi", + "version": "==5.0.2" + }, + "types-markdown": { + "hashes": [ + "sha256:10841332581bd79efdb153fb8f856818ef5cdb40a6e71e5d18505b5db6eba01c", + "sha256:49a406c12be2181346a756086f326d42d5bb3abbfaede8942ccdc9cef367db1f" + ], + "index": "pypi", + "version": "==3.3.14" + }, + "types-python-slugify": { + "hashes": [ + "sha256:a9d44edf034d5ceab8442ce602f976b7880ee935baeff7443964aed93f7e21d4", + "sha256:d0564feb7e21bd9ab646e692ae93eb9bf1b36e9b9bf7bf38d52eab43d29de2f6" + ], + "index": "pypi", + "version": "==5.0.4" + }, + "types-pyyaml": { + "hashes": [ + "sha256:59480cf44595d836aaae050f35e3c39f197f3a833679ef3978d97aa9f2fb7def", + "sha256:7b273a34f32af9910cf9405728c9d2ad3afc4be63e4048091a1a73d76681fe67" + ], + "index": "pypi", + "version": "==6.0.7" + }, + "types-requests": { + "hashes": [ + "sha256:5501ec6bcc164c54a6598e7ee6581827ea0ac0472e9d33b9456d202892f8d94c", + "sha256:e1cde99e92d5fb7afa0ee53924b211f4c47639516434d86dc84d53ec84fcfa8a" + ], + "index": "pypi", + "version": "==2.27.24" + }, + "types-urllib3": { + "hashes": [ + "sha256:40f8fb5e8cd7d57e8aefdee3fdd5e930aa1a1bb4179cdadd55226cea588af790", + "sha256:ff7500641824f881b2c7bde4cc57e6c3abf03d1e005bae83aca752e77213a5da" + ], + "version": "==1.26.13" + }, + "typing-extensions": { + "hashes": [ + "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708", + "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376" + ], + "markers": "python_version >= '3.7'", + "version": "==4.2.0" + }, + "watchdog": { + "hashes": [ + "sha256:03b43d583df0f18782a0431b6e9e9965c5b3f7cf8ec36a00b930def67942c385", + "sha256:0908bb50f6f7de54d5d31ec3da1654cb7287c6b87bce371954561e6de379d690", + "sha256:0b4a1fe6201c6e5a1926f5767b8664b45f0fcb429b62564a41f490ff1ce1dc7a", + "sha256:177bae28ca723bc00846466016d34f8c1d6a621383b6caca86745918d55c7383", + "sha256:19b36d436578eb437e029c6b838e732ed08054956366f6dd11875434a62d2b99", + "sha256:1d1cf7dfd747dec519486a98ef16097e6c480934ef115b16f18adb341df747a4", + "sha256:1e877c70245424b06c41ac258023ea4bd0c8e4ff15d7c1368f17cd0ae6e351dd", + "sha256:340b875aecf4b0e6672076a6f05cfce6686935559bb6d34cebedee04126a9566", + "sha256:351e09b6d9374d5bcb947e6ac47a608ec25b9d70583e9db00b2fcdb97b00b572", + "sha256:3fd47815353be9c44eebc94cc28fe26b2b0c5bd889dafc4a5a7cbdf924143480", + "sha256:49639865e3db4be032a96695c98ac09eed39bbb43fe876bb217da8f8101689a6", + "sha256:4d0e98ac2e8dd803a56f4e10438b33a2d40390a72750cff4939b4b274e7906fa", + "sha256:6e6ae29b72977f2e1ee3d0b760d7ee47896cb53e831cbeede3e64485e5633cc8", + "sha256:7f14ce6adea2af1bba495acdde0e510aecaeb13b33f7bd2f6324e551b26688ca", + "sha256:81982c7884aac75017a6ecc72f1a4fedbae04181a8665a34afce9539fc1b3fab", + "sha256:81a5861d0158a7e55fe149335fb2bbfa6f48cbcbd149b52dbe2cd9a544034bbd", + "sha256:ae934e34c11aa8296c18f70bf66ed60e9870fcdb4cc19129a04ca83ab23e7055", + "sha256:b26e13e8008dcaea6a909e91d39b629a39635d1a8a7239dd35327c74f4388601", + "sha256:b3750ee5399e6e9c69eae8b125092b871ee9e2fcbd657a92747aea28f9056a5c", + "sha256:b61acffaf5cd5d664af555c0850f9747cc5f2baf71e54bbac164c58398d6ca7b", + "sha256:b9777664848160449e5b4260e0b7bc1ae0f6f4992a8b285db4ec1ef119ffa0e2", + "sha256:bdcbf75580bf4b960fb659bbccd00123d83119619195f42d721e002c1621602f", + "sha256:d802d65262a560278cf1a65ef7cae4e2bc7ecfe19e5451349e4c67e23c9dc420", + "sha256:ed6d9aad09a2a948572224663ab00f8975fae242aa540509737bb4507133fa2d" + ], + "index": "pypi", + "version": "==2.1.7" + }, + "wcwidth": { + "hashes": [ + "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784", + "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83" + ], + "version": "==0.2.5" + }, + "werkzeug": { + "hashes": [ + "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6", + "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255" + ], + "index": "pypi", + "version": "==2.1.2" + } + } +} diff --git a/entries/admin.py b/entries/admin.py index e42b7f0..dac64fb 100644 --- a/entries/admin.py +++ b/entries/admin.py @@ -8,10 +8,12 @@ class SyndicationInline(admin.TabularInline): class EntryAdmin(admin.ModelAdmin): - date_hierarchy = "created" - list_display = ("title", "id", "kind", "created") - list_filter = ("kind",) - inlines = (SyndicationInline,) + date_hierarchy = 'created' + list_display = ('title', 'id', 'kind', 'created') + list_filter = ('kind',) + inlines = ( + SyndicationInline, + ) admin.site.register(Cat) diff --git a/entries/apps.py b/entries/apps.py index f34a177..554d2a6 100644 --- a/entries/apps.py +++ b/entries/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class EntriesConfig(AppConfig): - name = "entries" + name = 'entries' diff --git a/entries/from_url.py b/entries/from_url.py index 64cf56f..d842b83 100644 --- a/entries/from_url.py +++ b/entries/from_url.py @@ -11,24 +11,24 @@ from .models import Entry def from_url(url: str) -> Entry: domain = Site.objects.get_current().domain if not url: - raise error.bad_req("url parameter required") - if "//" not in url: - url = "//" + url - parts = urlparse(url, scheme="https") - if parts.scheme not in ("http", "https") or parts.netloc != domain: - raise error.bad_req("url does not point to this site") + raise error.bad_req('url parameter required') + if '//' not in url: + url = '//' + url + parts = urlparse(url, scheme='https') + if parts.scheme not in ('http', 'https') or parts.netloc != domain: + raise error.bad_req('url does not point to this site') try: match = resolve(parts.path) except Resolver404: - raise error.bad_req("url does not point to a valid page on this site") + raise error.bad_req('url does not point to a valid page on this site') - if match.view_name != "entries:entry": - raise error.bad_req("url does not point to an entry on this site") + if match.view_name != 'entries:entry': + raise error.bad_req('url does not point to an entry on this site') try: - entry = Entry.objects.get(pk=match.kwargs["id"]) + entry = Entry.objects.get(pk=match.kwargs['id']) except Entry.DoesNotExist: - raise error.bad_req("url does not point to an existing entry") + raise error.bad_req('url does not point to an existing entry') return entry diff --git a/entries/jobs.py b/entries/jobs.py index eb4db30..68a873c 100644 --- a/entries/jobs.py +++ b/entries/jobs.py @@ -7,19 +7,16 @@ from ronkyuu import webmention @job def ping_hub(*urls): for url in urls: - requests.post( - settings.PUSH_HUB, - data={ - "hub.mode": "publish", - "hub.url": url, - }, - ) + requests.post(settings.PUSH_HUB, data={ + 'hub.mode': 'publish', + 'hub.url': url, + }) @job def send_mentions(source, targets=None): if targets is None: - targets = webmention.findMentions(source)["refs"] + targets = webmention.findMentions(source)['refs'] for target in targets: status, endpoint = webmention.discoverEndpoint(target) if endpoint is not None and status == 200: diff --git a/entries/kinds.py b/entries/kinds.py index 6378eeb..4e25466 100644 --- a/entries/kinds.py +++ b/entries/kinds.py @@ -14,62 +14,62 @@ class Entry: return self.index_page() def index_page(self, page=0): - kwargs = {"kind": self} + kwargs = {'kind': self} if page > 1: - kwargs["page"] = page - return reverse("entries:index", kwargs=kwargs) + kwargs['page'] = page + return reverse('entries:index', kwargs=kwargs) @property def entry(self): - return self.plural + "_entry" + return self.plural + '_entry' @property def atom(self): - return reverse("entries:atom_by_kind", kwargs={"kind": self}) + return reverse('entries:atom_by_kind', kwargs={'kind': self}) @property def rss(self): - return reverse("entries:rss_by_kind", kwargs={"kind": self}) + return reverse('entries:rss_by_kind', kwargs={'kind': self}) Note = Entry( - id="note", - icon="fas fa-paper-plane", - plural="notes", + id='note', + icon='fas fa-paper-plane', + plural='notes', ) Article = Entry( - id="article", - icon="fas fa-file-alt", - plural="articles", + id='article', + icon='fas fa-file-alt', + plural='articles', slug=True, ) Photo = Entry( - id="photo", - icon="fas fa-camera", - plural="photos", + id='photo', + icon='fas fa-camera', + plural='photos', ) Reply = Entry( - id="reply", - icon="fas fa-comment", - plural="replies", + id='reply', + icon='fas fa-comment', + plural='replies', on_home=False, ) Like = Entry( - id="like", - icon="fas fa-heart", - plural="likes", + id='like', + icon='fas fa-heart', + plural='likes', on_home=False, ) Repost = Entry( - id="repost", - icon="fas fa-retweet", - plural="reposts", + id='repost', + icon='fas fa-retweet', + plural='reposts', ) all = (Note, Article, Photo) @@ -79,7 +79,7 @@ from_plural = {k.plural: k for k in all} class EntryKindConverter: - regex = "|".join(k.plural for k in all) + regex = '|'.join(k.plural for k in all) def to_python(self, plural): return from_plural[plural] diff --git a/entries/migrations/0001_initial.py b/entries/migrations/0001_initial.py index 0a230a4..1228631 100644 --- a/entries/migrations/0001_initial.py +++ b/entries/migrations/0001_initial.py @@ -8,6 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): + initial = True dependencies = [ @@ -16,41 +17,20 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="Entry", + name='Entry', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "kind", - models.CharField( - choices=[("note", "Note"), ("article", "Article")], - default="note", - max_length=30, - ), - ), - ("name", models.CharField(blank=True, max_length=100)), - ("summary", models.TextField(blank=True)), - ("content", models.TextField()), - ("published", models.DateTimeField()), - ("updated", models.DateTimeField()), - ( - "author", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('kind', models.CharField(choices=[('note', 'Note'), ('article', 'Article')], default='note', max_length=30)), + ('name', models.CharField(blank=True, max_length=100)), + ('summary', models.TextField(blank=True)), + ('content', models.TextField()), + ('published', models.DateTimeField()), + ('updated', models.DateTimeField()), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - "verbose_name_plural": "entries", - "ordering": ["-published"], + 'verbose_name_plural': 'entries', + 'ordering': ['-published'], }, ), ] diff --git a/entries/migrations/0002_syndication.py b/entries/migrations/0002_syndication.py index a91a9fd..415ceee 100644 --- a/entries/migrations/0002_syndication.py +++ b/entries/migrations/0002_syndication.py @@ -7,42 +7,23 @@ import django.db.models.deletion class Migration(migrations.Migration): + dependencies = [ - ("users", "0005_auto_20171023_0158"), - ("entries", "0001_initial"), + ('users', '0005_auto_20171023_0158'), + ('entries', '0001_initial'), ] operations = [ migrations.CreateModel( - name="Syndication", + name='Syndication', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("url", models.CharField(max_length=255)), - ( - "entry", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="syndications", - to="entries.Entry", - ), - ), - ( - "profile", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="users.Profile" - ), - ), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('url', models.CharField(max_length=255)), + ('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='syndications', to='entries.Entry')), + ('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Profile')), ], options={ - "ordering": ["profile"], + 'ordering': ['profile'], }, ), ] diff --git a/entries/migrations/0003_remove_entry_summary.py b/entries/migrations/0003_remove_entry_summary.py index d405062..aac7f18 100644 --- a/entries/migrations/0003_remove_entry_summary.py +++ b/entries/migrations/0003_remove_entry_summary.py @@ -6,13 +6,14 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("entries", "0002_syndication"), + ('entries', '0002_syndication'), ] operations = [ migrations.RemoveField( - model_name="entry", - name="summary", + model_name='entry', + name='summary', ), ] diff --git a/entries/migrations/0004_auto_20171027_0846.py b/entries/migrations/0004_auto_20171027_0846.py index a267757..f276c63 100644 --- a/entries/migrations/0004_auto_20171027_0846.py +++ b/entries/migrations/0004_auto_20171027_0846.py @@ -8,28 +8,20 @@ import django.db.models.deletion class Migration(migrations.Migration): + dependencies = [ - ("entries", "0003_remove_entry_summary"), + ('entries', '0003_remove_entry_summary'), ] operations = [ migrations.AlterField( - model_name="entry", - name="author", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="entries", - to=settings.AUTH_USER_MODEL, - ), + model_name='entry', + name='author', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='entries', to=settings.AUTH_USER_MODEL), ), migrations.AlterField( - model_name="entry", - name="kind", - field=models.CharField( - choices=[("note", "note"), ("article", "article")], - db_index=True, - default="note", - max_length=30, - ), + model_name='entry', + name='kind', + field=models.CharField(choices=[('note', 'note'), ('article', 'article')], db_index=True, default='note', max_length=30), ), ] diff --git a/entries/migrations/0005_auto_20171027_1557.py b/entries/migrations/0005_auto_20171027_1557.py index 2b90132..85ec86b 100644 --- a/entries/migrations/0005_auto_20171027_1557.py +++ b/entries/migrations/0005_auto_20171027_1557.py @@ -6,24 +6,20 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0004_auto_20171027_0846"), + ('entries', '0004_auto_20171027_0846'), ] operations = [ migrations.AddField( - model_name="entry", - name="photo", - field=models.ImageField(blank=True, upload_to=""), + model_name='entry', + name='photo', + field=models.ImageField(blank=True, upload_to=''), ), migrations.AlterField( - model_name="entry", - name="kind", - field=models.CharField( - choices=[("note", "note"), ("article", "article"), ("photo", "photo")], - db_index=True, - default="note", - max_length=30, - ), + model_name='entry', + name='kind', + field=models.CharField(choices=[('note', 'note'), ('article', 'article'), ('photo', 'photo')], db_index=True, default='note', max_length=30), ), ] diff --git a/entries/migrations/0006_auto_20171102_1200.py b/entries/migrations/0006_auto_20171102_1200.py index c3f735e..af62ba0 100644 --- a/entries/migrations/0006_auto_20171102_1200.py +++ b/entries/migrations/0006_auto_20171102_1200.py @@ -8,41 +8,34 @@ import model_utils.fields class Migration(migrations.Migration): + dependencies = [ - ("entries", "0005_auto_20171027_1557"), + ('entries', '0005_auto_20171027_1557'), ] operations = [ migrations.AlterModelOptions( - name="entry", - options={"ordering": ["-created"], "verbose_name_plural": "entries"}, + name='entry', + options={'ordering': ['-created'], 'verbose_name_plural': 'entries'}, ), migrations.RenameField( - model_name="entry", - old_name="published", - new_name="created", + model_name='entry', + old_name='published', + new_name='created', ), migrations.RenameField( - model_name="entry", - old_name="updated", - new_name="modified", + model_name='entry', + old_name='updated', + new_name='modified', ), migrations.AlterField( - model_name="entry", - name="created", - field=model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), + model_name='entry', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), ), migrations.AlterField( - model_name="entry", - name="modified", - field=model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="modified", - ), + model_name='entry', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), ), ] diff --git a/entries/migrations/0007_auto_20171113_0841.py b/entries/migrations/0007_auto_20171113_0841.py index d3e8a39..40fc4f4 100644 --- a/entries/migrations/0007_auto_20171113_0841.py +++ b/entries/migrations/0007_auto_20171113_0841.py @@ -6,31 +6,20 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0006_auto_20171102_1200"), + ('entries', '0006_auto_20171102_1200'), ] operations = [ migrations.AddField( - model_name="entry", - name="cite", + model_name='entry', + name='cite', field=models.CharField(blank=True, max_length=255), ), migrations.AlterField( - model_name="entry", - name="kind", - field=models.CharField( - choices=[ - ("note", "note"), - ("article", "article"), - ("photo", "photo"), - ("reply", "reply"), - ("like", "like"), - ("repost", "repost"), - ], - db_index=True, - default="note", - max_length=30, - ), + model_name='entry', + name='kind', + field=models.CharField(choices=[('note', 'note'), ('article', 'article'), ('photo', 'photo'), ('reply', 'reply'), ('like', 'like'), ('repost', 'repost')], db_index=True, default='note', max_length=30), ), ] diff --git a/entries/migrations/0008_auto_20171116_2116.py b/entries/migrations/0008_auto_20171116_2116.py index c7aee1b..c062844 100644 --- a/entries/migrations/0008_auto_20171116_2116.py +++ b/entries/migrations/0008_auto_20171116_2116.py @@ -6,24 +6,25 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0007_auto_20171113_0841"), + ('entries', '0007_auto_20171113_0841'), ] operations = [ migrations.RenameField( - model_name="entry", - old_name="cite", - new_name="in_reply_to", + model_name='entry', + old_name='cite', + new_name='in_reply_to', ), migrations.AddField( - model_name="entry", - name="like_of", + model_name='entry', + name='like_of', field=models.CharField(blank=True, max_length=255), ), migrations.AddField( - model_name="entry", - name="repost_of", + model_name='entry', + name='repost_of', field=models.CharField(blank=True, max_length=255), ), ] diff --git a/entries/migrations/0009_tag.py b/entries/migrations/0009_tag.py index 48ddc01..aa61053 100644 --- a/entries/migrations/0009_tag.py +++ b/entries/migrations/0009_tag.py @@ -6,28 +6,21 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0008_auto_20171116_2116"), + ('entries', '0008_auto_20171116_2116'), ] operations = [ migrations.CreateModel( - name="Tag", + name='Tag', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=255, unique=True)), - ("slug", models.CharField(max_length=255, unique=True)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True)), + ('slug', models.CharField(max_length=255, unique=True)), ], options={ - "ordering": ("name",), + 'ordering': ('name',), }, ), ] diff --git a/entries/migrations/0010_entry_tags.py b/entries/migrations/0010_entry_tags.py index f657ca3..bd36f61 100644 --- a/entries/migrations/0010_entry_tags.py +++ b/entries/migrations/0010_entry_tags.py @@ -6,14 +6,15 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0009_tag"), + ('entries', '0009_tag'), ] operations = [ migrations.AddField( - model_name="entry", - name="tags", - field=models.ManyToManyField(related_name="entries", to="entries.Tag"), + model_name='entry', + name='tags', + field=models.ManyToManyField(related_name='entries', to='entries.Tag'), ), ] diff --git a/entries/migrations/0011_auto_20171120_1108.py b/entries/migrations/0011_auto_20171120_1108.py index 36acd85..296cacb 100644 --- a/entries/migrations/0011_auto_20171120_1108.py +++ b/entries/migrations/0011_auto_20171120_1108.py @@ -9,17 +9,17 @@ class Migration(migrations.Migration): atomic = False dependencies = [ - ("entries", "0010_entry_tags"), + ('entries', '0010_entry_tags'), ] operations = [ migrations.RenameModel( - old_name="Tag", - new_name="Cat", + old_name='Tag', + new_name='Cat', ), migrations.RenameField( - model_name="entry", - old_name="tags", - new_name="cats", + model_name='entry', + old_name='tags', + new_name='cats', ), ] diff --git a/entries/migrations/0012_auto_20180628_2044.py b/entries/migrations/0012_auto_20180628_2044.py index dabc60b..8769528 100644 --- a/entries/migrations/0012_auto_20180628_2044.py +++ b/entries/migrations/0012_auto_20180628_2044.py @@ -5,25 +5,25 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("entries", "0011_auto_20171120_1108"), + ('entries', '0011_auto_20171120_1108'), ] operations = [ migrations.AlterModelOptions( - name="syndication", - options={"ordering": ["domain"]}, + name='syndication', + options={'ordering': ['domain']}, ), migrations.RemoveField( - model_name="syndication", - name="profile", + model_name='syndication', + name='profile', ), migrations.AddField( - model_name="syndication", - name="domain", + model_name='syndication', + name='domain', field=computed_property.fields.ComputedCharField( - compute_from="calc_domain", default="", editable=False, max_length=255 - ), + compute_from='calc_domain', default='', editable=False, max_length=255), preserve_default=False, ), ] diff --git a/entries/migrations/0013_alter_entry_kind.py b/entries/migrations/0013_alter_entry_kind.py index a50cf00..6d254e1 100644 --- a/entries/migrations/0013_alter_entry_kind.py +++ b/entries/migrations/0013_alter_entry_kind.py @@ -4,19 +4,24 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("entries", "0012_auto_20180628_2044"), + ('entries', '0012_auto_20180628_2044'), ] operations = [ migrations.AlterField( - model_name="entry", - name="kind", + model_name='entry', + name='kind', field=models.CharField( - choices=[("note", "note"), ("article", "article"), ("photo", "photo")], + choices=[ + ('note', 'note'), + ('article', 'article'), + ('photo', 'photo') + ], db_index=True, - default="note", - max_length=30, + default='note', + max_length=30 ), ), ] diff --git a/entries/models.py b/entries/models.py index e52469b..e7419c9 100644 --- a/entries/models.py +++ b/entries/models.py @@ -17,7 +17,6 @@ from users.models import Site from . import kinds from lemoncurry import requests, utils - ENTRY_KINDS = [(k.id, k.id) for k in kinds.all] @@ -33,33 +32,38 @@ class Cat(models.Model): slug = models.CharField(max_length=255, unique=True) def __str__(self): - return "#" + self.name + return '#' + self.name @property def url(self): - return reverse("entries:cat", args=(self.slug,)) + return reverse('entries:cat', args=(self.slug,)) class Meta: - ordering = ("name",) + ordering = ('name',) class EntryManager(models.Manager): def get_queryset(self): qs = super(EntryManager, self).get_queryset() - return qs.select_related("author").prefetch_related("cats", "syndications") + return (qs + .select_related('author') + .prefetch_related('cats', 'syndications')) class Entry(ModelMeta, TimeStampedModel): objects = EntryManager() kind = models.CharField( - max_length=30, choices=ENTRY_KINDS, db_index=True, default=ENTRY_KINDS[0][0] + max_length=30, + choices=ENTRY_KINDS, + db_index=True, + default=ENTRY_KINDS[0][0] ) name = models.CharField(max_length=100, blank=True) photo = models.ImageField(blank=True) content = models.TextField() - cats = models.ManyToManyField(Cat, related_name="entries") + cats = models.ManyToManyField(Cat, related_name='entries') in_reply_to = models.CharField(max_length=255, blank=True) like_of = models.CharField(max_length=255, blank=True) @@ -67,7 +71,7 @@ class Entry(ModelMeta, TimeStampedModel): author = models.ForeignKey( get_user_model(), - related_name="entries", + related_name='entries', on_delete=models.CASCADE, ) @@ -75,7 +79,10 @@ class Entry(ModelMeta, TimeStampedModel): def reply_context(self): if not self.in_reply_to: return None - return interpret(requests.mf2(self.in_reply_to).to_dict(), self.in_reply_to) + return interpret( + requests.mf2(self.in_reply_to).to_dict(), + self.in_reply_to + ) @property def published(self): @@ -86,29 +93,35 @@ class Entry(ModelMeta, TimeStampedModel): return self.modified _metadata = { - "description": "excerpt", - "image": "image_url", - "twitter_creator": "twitter_creator", - "og_profile_id": "og_profile_id", + 'description': 'excerpt', + 'image': 'image_url', + 'twitter_creator': 'twitter_creator', + 'og_profile_id': 'og_profile_id', } @property def title(self): if self.name: return self.name - return shorten(utils.to_plain(self.paragraphs[0]), width=100, placeholder="…") + return shorten( + utils.to_plain(self.paragraphs[0]), + width=100, + placeholder='…' + ) @property def excerpt(self): try: return utils.to_plain(self.paragraphs[0 if self.name else 1]) except IndexError: - return " " + return ' ' @property def paragraphs(self): lines = self.content.splitlines() - return ["\n".join(para) for k, para in groupby(lines, key=bool) if k] + return [ + "\n".join(para) for k, para in groupby(lines, key=bool) if k + ] @property def twitter_creator(self): @@ -123,31 +136,31 @@ class Entry(ModelMeta, TimeStampedModel): return self.photo.url if self.photo else self.author.avatar_url def __str__(self): - return "{0} {1}: {2}".format(self.kind, self.id, self.title) + return '{0} {1}: {2}'.format(self.kind, self.id, self.title) def get_absolute_url(self): return self.absolute_url @property def absolute_url(self): - base = "https://" + DjangoSite.objects.get_current().domain + base = 'https://' + DjangoSite.objects.get_current().domain return urljoin(base, self.url) @property def affected_urls(self): - base = "https://" + DjangoSite.objects.get_current().domain + base = 'https://' + DjangoSite.objects.get_current().domain kind = kinds.from_id[self.kind] urls = { self.url, - reverse("entries:index", kwargs={"kind": kind}), - reverse("entries:atom_by_kind", kwargs={"kind": kind}), - reverse("entries:rss_by_kind", kwargs={"kind": kind}), + reverse('entries:index', kwargs={'kind': kind}), + reverse('entries:atom_by_kind', kwargs={'kind': kind}), + reverse('entries:rss_by_kind', kwargs={'kind': kind}), } | {cat.url for cat in self.cats.all()} if kind.on_home: urls |= { - reverse("home:index"), - reverse("entries:atom"), - reverse("entries:rss"), + reverse('home:index'), + reverse('entries:atom'), + reverse('entries:rss') } return {urljoin(base, u) for u in urls} @@ -157,7 +170,7 @@ class Entry(ModelMeta, TimeStampedModel): args = [kind, self.id] if kind.slug: args.append(self.slug) - return reverse("entries:entry", args=args) + return reverse('entries:entry', args=args) @property def short_url(self): @@ -169,48 +182,49 @@ class Entry(ModelMeta, TimeStampedModel): @property def json_ld(self): - base = "https://" + DjangoSite.objects.get_current().domain + base = 'https://' + DjangoSite.objects.get_current().domain url = urljoin(base, self.url) posting = { - "@context": "http://schema.org", - "@type": "BlogPosting", - "@id": url, - "url": url, - "mainEntityOfPage": url, - "author": { - "@type": "Person", - "url": urljoin(base, self.author.url), - "name": self.author.name, + '@context': 'http://schema.org', + '@type': 'BlogPosting', + '@id': url, + 'url': url, + 'mainEntityOfPage': url, + 'author': { + '@type': 'Person', + 'url': urljoin(base, self.author.url), + 'name': self.author.name, }, - "headline": self.title, - "description": self.excerpt, - "datePublished": self.created.isoformat(), - "dateModified": self.modified.isoformat(), + 'headline': self.title, + 'description': self.excerpt, + 'datePublished': self.created.isoformat(), + 'dateModified': self.modified.isoformat(), } if self.photo: - posting["image"] = (urljoin(base, self.photo.url),) + posting['image'] = (urljoin(base, self.photo.url), ) return posting class Meta: - verbose_name_plural = "entries" - ordering = ["-created"] + verbose_name_plural = 'entries' + ordering = ['-created'] class Syndication(models.Model): entry = models.ForeignKey( - Entry, related_name="syndications", on_delete=models.CASCADE + Entry, + related_name='syndications', + on_delete=models.CASCADE ) url = models.CharField(max_length=255) domain = ComputedCharField( - compute_from="calc_domain", - max_length=255, + compute_from='calc_domain', max_length=255, ) def calc_domain(self): domain = urlparse(self.url).netloc - if domain.startswith("www."): + if domain.startswith('www.'): domain = domain[4:] return domain @@ -220,7 +234,7 @@ class Syndication(models.Model): try: return Site.objects.get(domain=d) except Site.DoesNotExist: - return Site(name=d, domain=d, icon="fas fa-newspaper") + return Site(name=d, domain=d, icon='fas fa-newspaper') class Meta: - ordering = ["domain"] + ordering = ['domain'] diff --git a/entries/tests/views/feeds.py b/entries/tests/views/feeds.py index 6dd80e3..21f6cab 100644 --- a/entries/tests/views/feeds.py +++ b/entries/tests/views/feeds.py @@ -3,27 +3,27 @@ import pytest @pytest.mark.django_db def test_atom(client): - res = client.get("/atom") + res = client.get('/atom') assert res.status_code == 200 - assert res["Content-Type"] == "application/atom+xml; charset=utf-8" + assert res['Content-Type'] == 'application/atom+xml; charset=utf-8' @pytest.mark.django_db def test_rss(client): - res = client.get("/rss") + res = client.get('/rss') assert res.status_code == 200 - assert res["Content-Type"] == "application/rss+xml; charset=utf-8" + assert res['Content-Type'] == 'application/rss+xml; charset=utf-8' @pytest.mark.django_db def test_atom_by_kind(client): - res = client.get("/notes/atom") + res = client.get('/notes/atom') assert res.status_code == 200 - assert res["Content-Type"] == "application/atom+xml; charset=utf-8" + assert res['Content-Type'] == 'application/atom+xml; charset=utf-8' @pytest.mark.django_db def test_rss_by_kind(client): - res = client.get("/notes/rss") + res = client.get('/notes/rss') assert res.status_code == 200 - assert res["Content-Type"] == "application/rss+xml; charset=utf-8" + assert res['Content-Type'] == 'application/rss+xml; charset=utf-8' diff --git a/entries/urls.py b/entries/urls.py index d015c29..f40bceb 100644 --- a/entries/urls.py +++ b/entries/urls.py @@ -3,46 +3,47 @@ from . import kinds from .views import feeds, lists, perma from lemoncurry import breadcrumbs as crumbs -register_converter(kinds.EntryKindConverter, "kind") +register_converter(kinds.EntryKindConverter, 'kind') def to_pat(*args): - return "^{0}$".format("".join(args)) + return '^{0}$'.format(''.join(args)) def prefix(route): - return app_name + ":" + route + return app_name + ':' + route -id = r"/(?P\d+)" -kind = r"(?P{0})".format("|".join(k.plural for k in kinds.all)) -page = r"(?:/page/(?P\d+))?" -slug = r"/(?P[^/]+)" +id = r'/(?P\d+)' +kind = r'(?P{0})'.format('|'.join(k.plural for k in kinds.all)) +page = r'(?:/page/(?P\d+))?' +slug = r'/(?P[^/]+)' -slug_opt = "(?:" + slug + ")?" +slug_opt = '(?:' + slug + ')?' -app_name = "entries" +app_name = 'entries' urlpatterns = ( - path("atom", feeds.AtomHomeEntries(), name="atom"), - path("rss", feeds.RssHomeEntries(), name="rss"), - path("cats/", lists.by_cat, name="cat"), - path("cats//page/", lists.by_cat, name="cat"), - path("", lists.by_kind, name="index"), - path("/page/", lists.by_kind, name="index"), - path("/atom", feeds.AtomByKind(), name="atom_by_kind"), - path("/rss", feeds.RssByKind(), name="rss_by_kind"), - path("/", perma.entry, name="entry"), - path("//", perma.entry, name="entry"), + path('atom', feeds.AtomHomeEntries(), name='atom'), + path('rss', feeds.RssHomeEntries(), name='rss'), + path('cats/', lists.by_cat, name='cat'), + path('cats//page/', lists.by_cat, name='cat'), + path('', lists.by_kind, name='index'), + path('/page/', lists.by_kind, name='index'), + path('/atom', feeds.AtomByKind(), name='atom_by_kind'), + path('/rss', feeds.RssByKind(), name='rss_by_kind'), + + path('/', perma.entry, name='entry'), + path('//', perma.entry, name='entry'), ) class IndexCrumb(crumbs.Crumb): def __init__(self): - super().__init__(prefix("index"), parent="home:index") + super().__init__(prefix('index'), parent='home:index') @property def kind(self): - return self.match.kwargs["kind"] + return self.match.kwargs['kind'] @property def label(self): @@ -50,9 +51,9 @@ class IndexCrumb(crumbs.Crumb): @property def url(self): - return reverse(prefix("index"), kwargs={"kind": self.kind}) + return reverse(prefix('index'), kwargs={'kind': self.kind}) -crumbs.add(prefix("cat"), parent="home:index") +crumbs.add(prefix('cat'), parent='home:index') crumbs.add(IndexCrumb()) -crumbs.add(prefix("entry"), parent=prefix("index")) +crumbs.add(prefix('entry'), parent=prefix('index')) diff --git a/entries/views/feeds.py b/entries/views/feeds.py index 837239e..2a0fac9 100644 --- a/entries/views/feeds.py +++ b/entries/views/feeds.py @@ -11,8 +11,8 @@ from ..models import Entry class Atom1FeedWithHub(Atom1Feed): def add_root_elements(self, handler): super().add_root_elements(handler) - handler.startElement("link", {"rel": "hub", "href": settings.PUSH_HUB}) - handler.endElement("link") + handler.startElement('link', {'rel': 'hub', 'href': settings.PUSH_HUB}) + handler.endElement('link') class EntriesFeed(Feed): @@ -79,7 +79,7 @@ class RssHomeEntries(EntriesFeed): return Site.objects.get_current().name def link(self): - return reverse("home:index") + return reverse('home:index') def description(self): return "content from {0}".format( diff --git a/entries/views/lists.py b/entries/views/lists.py index 24a8c7e..c177670 100644 --- a/entries/views/lists.py +++ b/entries/views/lists.py @@ -5,32 +5,32 @@ from ..models import Entry, Cat from ..pagination import paginate -@render_to("entries/index.html") +@render_to('entries/index.html') def by_kind(request, kind, page=None): entries = Entry.objects.filter(kind=kind.id) entries = paginate(queryset=entries, reverse=kind.index_page, page=page) return { - "entries": entries, - "atom": kind.atom, - "rss": kind.rss, - "title": kind.plural, + 'entries': entries, + 'atom': kind.atom, + 'rss': kind.rss, + 'title': kind.plural, } -@render_to("entries/index.html") +@render_to('entries/index.html') def by_cat(request, slug, page=None): def url(page): - kwargs = {"slug": slug} + kwargs = {'slug': slug} if page > 1: - kwargs["page"] = page - return reverse("entries:cat", kwargs=kwargs) + kwargs['page'] = page + return reverse('entries:cat', kwargs=kwargs) cat = get_object_or_404(Cat, slug=slug) entries = cat.entries.all() entries = paginate(queryset=entries, reverse=url, page=page) return { - "entries": entries, - "title": "#" + cat.name, + 'entries': entries, + 'title': '#' + cat.name, } diff --git a/entries/views/perma.py b/entries/views/perma.py index 10c5183..bb4fbc9 100644 --- a/entries/views/perma.py +++ b/entries/views/perma.py @@ -3,12 +3,12 @@ from django.shortcuts import redirect, get_object_or_404 from ..models import Entry -@render_to("entries/entry.html") +@render_to('entries/entry.html') def entry(request, kind, id, slug=None): entry = get_object_or_404(Entry, pk=id) if request.path != entry.url: return redirect(entry.url, permanent=True) return { - "entry": entry, - "title": entry.title, + 'entry': entry, + 'title': entry.title, } diff --git a/gunicorn.py b/gunicorn.py index 82d6732..e34ad90 100644 --- a/gunicorn.py +++ b/gunicorn.py @@ -1,5 +1,5 @@ import multiprocessing -proc_name = "lemoncurry" -worker_class = "gevent" +proc_name = 'lemoncurry' +worker_class = 'gevent' workers = multiprocessing.cpu_count() * 2 + 1 diff --git a/home/sitemaps.py b/home/sitemaps.py index 23609be..a3c33b0 100644 --- a/home/sitemaps.py +++ b/home/sitemaps.py @@ -3,10 +3,10 @@ from django.urls import reverse class HomeSitemap(sitemaps.Sitemap): - changefreq = "daily" + changefreq = 'daily' def items(self): - return ("home:index",) + return ('home:index',) def location(self, item): return reverse(item) diff --git a/home/urls.py b/home/urls.py index f7e7e72..1f14b68 100644 --- a/home/urls.py +++ b/home/urls.py @@ -2,9 +2,9 @@ from django.urls import path from . import views -app_name = "home" +app_name = 'home' urlpatterns = [ - path("", views.index, name="index"), - path("page/", views.index, name="index"), - path("robots.txt", views.robots, name="robots.txt"), + path('', views.index, name='index'), + path('page/', views.index, name='index'), + path('robots.txt', views.robots, name='robots.txt'), ] diff --git a/home/views.py b/home/views.py index b57dc22..056f6ef 100644 --- a/home/views.py +++ b/home/views.py @@ -8,31 +8,34 @@ from urllib.parse import urljoin from entries import kinds, pagination from lemoncurry import breadcrumbs, utils -breadcrumbs.add("home:index", "home") +breadcrumbs.add('home:index', 'home') -@render_to("home/index.html") +@render_to('home/index.html') def index(request, page=None): def url(page): - kwargs = {"page": page} if page != 1 else {} - return reverse("home:index", kwargs=kwargs) + kwargs = {'page': page} if page != 1 else {} + return reverse('home:index', kwargs=kwargs) user = request.user - if not hasattr(user, "entries"): + if not hasattr(user, 'entries'): user = get_object_or_404(User, pk=1) entries = user.entries.filter(kind__in=kinds.on_home) entries = pagination.paginate(queryset=entries, reverse=url, page=page) return { - "user": user, - "entries": entries, - "atom": reverse("entries:atom"), - "rss": reverse("entries:rss"), + 'user': user, + 'entries': entries, + 'atom': reverse('entries:atom'), + 'rss': reverse('entries:rss'), } def robots(request): base = utils.origin(request) - lines = ("User-agent: *", "Sitemap: {0}".format(urljoin(base, reverse("sitemap")))) - return HttpResponse("\n".join(lines) + "\n", content_type="text/plain") + lines = ( + 'User-agent: *', + 'Sitemap: {0}'.format(urljoin(base, reverse('sitemap'))) + ) + return HttpResponse("\n".join(lines) + "\n", content_type='text/plain') diff --git a/lemonauth/migrations/0001_initial.py b/lemonauth/migrations/0001_initial.py index e8c1aa1..aa17a22 100644 --- a/lemonauth/migrations/0001_initial.py +++ b/lemonauth/migrations/0001_initial.py @@ -7,36 +7,25 @@ from django.db import migrations, models class Migration(migrations.Migration): + initial = True - dependencies = [] # type: List[Tuple[str, str]] + dependencies = [ + ] # type: List[Tuple[str, str]] operations = [ migrations.CreateModel( - name="IndieAuthCode", + name='IndieAuthCode', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("code", models.CharField(max_length=64, unique=True)), - ("me", models.CharField(max_length=255)), - ("client_id", models.CharField(max_length=255)), - ("redirect_uri", models.CharField(max_length=255)), - ( - "response_type", - models.CharField( - choices=[("id", "id"), ("code", "code")], - default="id", - max_length=4, - ), - ), - ("scope", models.CharField(blank=True, max_length=200)), + ('id', models.AutoField(auto_created=True, + primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=64, unique=True)), + ('me', models.CharField(max_length=255)), + ('client_id', models.CharField(max_length=255)), + ('redirect_uri', models.CharField(max_length=255)), + ('response_type', models.CharField(choices=[ + ('id', 'id'), ('code', 'code')], default='id', max_length=4)), + ('scope', models.CharField(blank=True, max_length=200)), ], ), ] diff --git a/lemonauth/migrations/0002_delete_indieauthcode.py b/lemonauth/migrations/0002_delete_indieauthcode.py index 4bf2893..69ad679 100644 --- a/lemonauth/migrations/0002_delete_indieauthcode.py +++ b/lemonauth/migrations/0002_delete_indieauthcode.py @@ -6,12 +6,13 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("lemonauth", "0001_initial"), + ('lemonauth', '0001_initial'), ] operations = [ migrations.DeleteModel( - name="IndieAuthCode", + name='IndieAuthCode', ), ] diff --git a/lemonauth/migrations/0003_indieauthcode_token.py b/lemonauth/migrations/0003_indieauthcode_token.py index a3e1748..d09d74c 100644 --- a/lemonauth/migrations/0003_indieauthcode_token.py +++ b/lemonauth/migrations/0003_indieauthcode_token.py @@ -9,112 +9,43 @@ import randomslugfield.fields class Migration(migrations.Migration): + initial = True dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("lemonauth", "0002_delete_indieauthcode"), + ('lemonauth', '0002_delete_indieauthcode'), ] operations = [ migrations.CreateModel( - name="IndieAuthCode", + name='IndieAuthCode', fields=[ - ( - "created", - model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ( - "modified", - model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="modified", - ), - ), - ( - "id", - randomslugfield.fields.RandomSlugField( - blank=True, - editable=False, - length=30, - max_length=30, - primary_key=True, - serialize=False, - unique=True, - ), - ), - ("client_id", models.URLField()), - ("scope", models.TextField(blank=True)), - ("redirect_uri", models.URLField()), - ( - "response_type", - model_utils.fields.StatusField( - choices=[("id", "id"), ("code", "code")], - default="id", - max_length=100, - no_check_for_status=True, - ), - ), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('id', randomslugfield.fields.RandomSlugField(blank=True, editable=False, length=30, max_length=30, primary_key=True, serialize=False, unique=True)), + ('client_id', models.URLField()), + ('scope', models.TextField(blank=True)), + ('redirect_uri', models.URLField()), + ('response_type', model_utils.fields.StatusField(choices=[('id', 'id'), ('code', 'code')], default='id', max_length=100, no_check_for_status=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - "abstract": False, + 'abstract': False, }, ), migrations.CreateModel( - name="Token", + name='Token', fields=[ - ( - "created", - model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ( - "modified", - model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="modified", - ), - ), - ( - "id", - randomslugfield.fields.RandomSlugField( - blank=True, - editable=False, - length=30, - max_length=30, - primary_key=True, - serialize=False, - unique=True, - ), - ), - ("client_id", models.URLField()), - ("scope", models.TextField(blank=True)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('id', randomslugfield.fields.RandomSlugField(blank=True, editable=False, length=30, max_length=30, primary_key=True, serialize=False, unique=True)), + ('client_id', models.URLField()), + ('scope', models.TextField(blank=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - "abstract": False, + 'abstract': False, }, ), ] diff --git a/lemonauth/models.py b/lemonauth/models.py index 7a23244..607d3cb 100644 --- a/lemonauth/models.py +++ b/lemonauth/models.py @@ -17,7 +17,6 @@ class AuthSecret(TimeStampedModel): authorisation codes and tokens in IndieAuth - the two contain many identical fields, but just a few differences. """ - id = RandomSlugField(primary_key=True, length=30) client_id = models.URLField() user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE) @@ -28,7 +27,7 @@ class AuthSecret(TimeStampedModel): return self.user.full_url def __contains__(self, scope): - return scope in self.scope.split(" ") + return scope in self.scope.split(' ') class Meta: abstract = True @@ -42,11 +41,10 @@ class IndieAuthCode(AuthSecret): Codes are single-use, and if unused will be expired automatically after thirty seconds. """ - redirect_uri = models.URLField() - RESPONSE_TYPE = Choices("id", "code") - response_type = StatusField(choices_name="RESPONSE_TYPE") + RESPONSE_TYPE = Choices('id', 'code') + response_type = StatusField(choices_name='RESPONSE_TYPE') @property def expired(self): @@ -58,5 +56,4 @@ class Token(AuthSecret): A Token grants a client long-term authorisation - it will not expire unless explicitly revoked by the user. """ - pass diff --git a/lemonauth/tokens.py b/lemonauth/tokens.py index 17d0e5b..5e6640e 100644 --- a/lemonauth/tokens.py +++ b/lemonauth/tokens.py @@ -3,17 +3,17 @@ from .models import IndieAuthCode, Token def auth(request) -> Token: - if "HTTP_AUTHORIZATION" in request.META: - auth = request.META.get("HTTP_AUTHORIZATION").split(" ") - if auth[0] != "Bearer": - raise error.bad_req("auth type {0} not supported".format(auth[0])) + if 'HTTP_AUTHORIZATION' in request.META: + auth = request.META.get('HTTP_AUTHORIZATION').split(' ') + if auth[0] != 'Bearer': + raise error.bad_req('auth type {0} not supported'.format(auth[0])) if len(auth) != 2: - raise error.bad_req("invalid Bearer auth format, must be Bearer ") + raise error.bad_req('invalid Bearer auth format, must be Bearer ') token = auth[1] - elif "access_token" in request.POST: - token = request.POST.get("access_token") - elif "access_token" in request.GET: - token = request.GET.get("access_token") + elif 'access_token' in request.POST: + token = request.POST.get('access_token') + elif 'access_token' in request.GET: + token = request.GET.get('access_token') else: raise error.unauthorized() @@ -28,11 +28,11 @@ def auth(request) -> Token: def gen_auth_code(req): code = IndieAuthCode() code.user = req.user - code.client_id = req.POST["client_id"] - code.redirect_uri = req.POST["redirect_uri"] - code.response_type = req.POST.get("response_type", "id") - if "scope" in req.POST: - code.scope = " ".join(req.POST.getlist("scope")) + code.client_id = req.POST['client_id'] + code.redirect_uri = req.POST['redirect_uri'] + code.response_type = req.POST.get('response_type', 'id') + if 'scope' in req.POST: + code.scope = ' '.join(req.POST.getlist('scope')) code.save() return code.id diff --git a/lemonauth/urls.py b/lemonauth/urls.py index a2126b9..e98672a 100644 --- a/lemonauth/urls.py +++ b/lemonauth/urls.py @@ -1,17 +1,13 @@ from django.urls import path from . import views -app_name = "lemonauth" +app_name = 'lemonauth' urlpatterns = [ - path("login", views.login, name="login"), - path("logout", views.logout, name="logout"), - path("indie", views.IndieView.as_view(), name="indie"), - path("indie/approve", views.indie_approve, name="indie_approve"), - path("token", views.TokenView.as_view(), name="token"), - path("tokens", views.TokensListView.as_view(), name="tokens"), - path( - "tokens/", - views.TokensRevokeView.as_view(), - name="tokens_revoke", - ), + path('login', views.login, name='login'), + path('logout', views.logout, name='logout'), + path('indie', views.IndieView.as_view(), name='indie'), + path('indie/approve', views.indie_approve, name='indie_approve'), + path('token', views.TokenView.as_view(), name='token'), + path('tokens', views.TokensListView.as_view(), name='tokens'), + path('tokens/', views.TokensRevokeView.as_view(), name='tokens_revoke'), ] diff --git a/lemonauth/views/indie.py b/lemonauth/views/indie.py index ac2ddc3..dbab249 100644 --- a/lemonauth/views/indie.py +++ b/lemonauth/views/indie.py @@ -12,114 +12,120 @@ from urllib.parse import urlencode, urljoin, urlunparse, urlparse from .. import tokens from ..models import IndieAuthCode -breadcrumbs.add("lemonauth:indie", parent="home:index") +breadcrumbs.add('lemonauth:indie', parent='home:index') def canonical(url): - if "//" not in url: - url = "//" + url + if '//' not in url: + url = '//' + url (scheme, netloc, path, params, query, fragment) = urlparse(url) - if not scheme or scheme == "http": - scheme = "https" + if not scheme or scheme == 'http': + scheme = 'https' if not path: - path = "/" + path = '/' return urlunparse((scheme, netloc, path, params, query, fragment)) -@method_decorator(csrf_exempt, name="dispatch") +@method_decorator(csrf_exempt, name='dispatch') class IndieView(TemplateView): - template_name = "lemonauth/indie.html" - required_params = ("client_id", "redirect_uri") + template_name = 'lemonauth/indie.html' + required_params = ('client_id', 'redirect_uri') @method_decorator(login_required) @method_decorator(render_to(template_name)) def get(self, request): params = request.GET.dict() - params.setdefault("response_type", "id") + params.setdefault('response_type', 'id') for param in self.required_params: if param not in params: - return utils.bad_req("parameter {0} is required".format(param)) - - me = request.user.full_url - if "me" in params: - param_me = canonical(params["me"]) - if me != param_me: - return utils.forbid( - "you are logged in as {}, not as {}".format(me, param_me) + return utils.bad_req( + 'parameter {0} is required'.format(param) ) - redirect_uri = urljoin(params["client_id"], params["redirect_uri"]) + me = request.user.full_url + if 'me' in params: + param_me = canonical(params['me']) + if me != param_me: + return utils.forbid( + 'you are logged in as {}, not as {}'.format(me, param_me) + ) - type = params["response_type"] - if type not in ("id", "code"): - return utils.bad_req("unknown response_type: {0}".format(type)) + redirect_uri = urljoin(params['client_id'], params['redirect_uri']) + + type = params['response_type'] + if type not in ('id', 'code'): + return utils.bad_req( + 'unknown response_type: {0}'.format(type) + ) scopes = () - if type == "code": - if "scope" not in params: - return utils.bad_req("scopes required for code type") - scopes = params["scope"].split(" ") + if type == 'code': + if 'scope' not in params: + return utils.bad_req( + 'scopes required for code type' + ) + scopes = params['scope'].split(' ') - client = requests.mf2(params["client_id"]) - rels = client.to_dict()["rel-urls"].get(redirect_uri, {}).get("rels", ()) - verified = "redirect_uri" in rels + client = requests.mf2(params['client_id']) + rels = (client.to_dict()['rel-urls'] + .get(redirect_uri, {}) + .get('rels', ())) + verified = 'redirect_uri' in rels try: - app = client.to_dict(filter_by_type="h-x-app")[0]["properties"] + app = client.to_dict(filter_by_type='h-x-app')[0]['properties'] except IndexError: app = None return { - "app": app, - "me": me, - "redirect_uri": redirect_uri, - "verified": verified, - "params": params, - "scopes": scopes, - "title": "indieauth from {client_id}".format(**params), + 'app': app, + 'me': me, + 'redirect_uri': redirect_uri, + 'verified': verified, + 'params': params, + 'scopes': scopes, + 'title': 'indieauth from {client_id}'.format(**params), } def post(self, request): post = request.POST.dict() try: - code = IndieAuthCode.objects.get(pk=post.get("code")) + code = IndieAuthCode.objects.get(pk=post.get('code')) except IndieAuthCode.DoesNotExist: # if anything at all goes wrong when decoding the auth code, bail # out immediately. - return utils.forbid("invalid auth code") + return utils.forbid('invalid auth code') code.delete() if code.expired: - return utils.forbid("invalid auth code") + return utils.forbid('invalid auth code') - if code.response_type != "id": - return utils.bad_req("this endpoint only supports response_type=id") - if code.client_id != post.get("client_id"): - return utils.forbid("client id did not match") - if code.redirect_uri != post.get("redirect_uri"): - return utils.forbid("redirect uri did not match") + if code.response_type != 'id': + return utils.bad_req( + 'this endpoint only supports response_type=id' + ) + if code.client_id != post.get('client_id'): + return utils.forbid('client id did not match') + if code.redirect_uri != post.get('redirect_uri'): + return utils.forbid('redirect uri did not match') # If we got here, it's valid! Yay! - return utils.choose_type( - request, - {"me": code.me}, - { - "application/x-www-form-urlencoded": utils.form_encoded_response, - "application/json": JsonResponse, - }, - ) + return utils.choose_type(request, {'me': code.me}, { + 'application/x-www-form-urlencoded': utils.form_encoded_response, + 'application/json': JsonResponse, + }) @login_required @require_POST def approve(request): params = { - "me": urljoin(utils.origin(request), request.user.url), - "code": tokens.gen_auth_code(request), + 'me': urljoin(utils.origin(request), request.user.url), + 'code': tokens.gen_auth_code(request), } - if "state" in request.POST: - params["state"] = request.POST["state"] + if 'state' in request.POST: + params['state'] = request.POST['state'] - uri = request.POST["redirect_uri"] - sep = "&" if "?" in uri else "?" + uri = request.POST['redirect_uri'] + sep = '&' if '?' in uri else '?' return redirect(uri + sep + urlencode(params)) diff --git a/lemonauth/views/login.py b/lemonauth/views/login.py index cffc18b..5e4b192 100644 --- a/lemonauth/views/login.py +++ b/lemonauth/views/login.py @@ -2,11 +2,11 @@ import django.contrib.auth.views from otp_agents.forms import OTPAuthenticationForm from lemoncurry import breadcrumbs -breadcrumbs.add(route="lemonauth:login", label="log in", parent="home:index") +breadcrumbs.add(route='lemonauth:login', label='log in', parent='home:index') login = django.contrib.auth.views.LoginView.as_view( authentication_form=OTPAuthenticationForm, - extra_context={"title": "log in"}, - template_name="lemonauth/login.html", + extra_context={'title': 'log in'}, + template_name='lemonauth/login.html', redirect_authenticated_user=True, ) diff --git a/lemonauth/views/token.py b/lemonauth/views/token.py index 3884fa1..251c016 100644 --- a/lemonauth/views/token.py +++ b/lemonauth/views/token.py @@ -7,42 +7,41 @@ from ..models import IndieAuthCode from lemoncurry import utils -@method_decorator(csrf_exempt, name="dispatch") +@method_decorator(csrf_exempt, name='dispatch') class TokenView(View): def get(self, req): token = tokens.auth(req) res = { - "me": token.me, - "client_id": token.client_id, - "scope": token.scope, + 'me': token.me, + 'client_id': token.client_id, + 'scope': token.scope, } return utils.choose_type(req, res) def post(self, req): post = req.POST try: - code = IndieAuthCode.objects.get(pk=post.get("code")) + code = IndieAuthCode.objects.get(pk=post.get('code')) except IndieAuthCode.DoesNotExist: - return utils.forbid("invalid auth code") + return utils.forbid('invalid auth code') code.delete() if code.expired: - return utils.forbid("invalid auth code") + return utils.forbid('invalid auth code') - if code.response_type != "code": - return utils.bad_req("this endpoint only supports response_type=code") - if "client_id" in post and code.client_id != post["client_id"]: - return utils.forbid("client id did not match") - if code.redirect_uri != post.get("redirect_uri"): - return utils.forbid("redirect uri did not match") + if code.response_type != 'code': + return utils.bad_req( + 'this endpoint only supports response_type=code' + ) + if 'client_id' in post and code.client_id != post['client_id']: + return utils.forbid('client id did not match') + if code.redirect_uri != post.get('redirect_uri'): + return utils.forbid('redirect uri did not match') - if "me" in post and code.me != post["me"]: - return utils.forbid("me did not match") + if 'me' in post and code.me != post['me']: + return utils.forbid('me did not match') - return utils.choose_type( - req, - { - "access_token": tokens.gen_token(code), - "me": code.me, - "scope": code.scope, - }, - ) + return utils.choose_type(req, { + 'access_token': tokens.gen_token(code), + 'me': code.me, + 'scope': code.scope, + }) diff --git a/lemonauth/views/tokens/list.py b/lemonauth/views/tokens/list.py index 52e0760..dba4eb3 100644 --- a/lemonauth/views/tokens/list.py +++ b/lemonauth/views/tokens/list.py @@ -20,15 +20,15 @@ class Client: self.id = client_id self.count = 0 self.scopes = set() - apps = mf2(self.id).to_dict(filter_by_type="h-x-app") + apps = mf2(self.id).to_dict(filter_by_type='h-x-app') try: - self.app = apps[0]["properties"] + self.app = apps[0]['properties'] except IndexError: self.app = None class TokensListView(LoginRequiredMixin, TemplateView): - template_name = "lemonauth/tokens.html" + template_name = 'lemonauth/tokens.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -36,6 +36,6 @@ class TokensListView(LoginRequiredMixin, TemplateView): for token in self.request.user.token_set.all(): client = clients[token.client_id] client.count += 1 - client.scopes |= set(token.scope.split(" ")) - context.update({"clients": clients, "title": "tokens"}) + client.scopes |= set(token.scope.split(' ')) + context.update({'clients': clients, 'title': 'tokens'}) return context diff --git a/lemoncurry/breadcrumbs.py b/lemoncurry/breadcrumbs.py index cba1a00..bd88b35 100644 --- a/lemoncurry/breadcrumbs.py +++ b/lemoncurry/breadcrumbs.py @@ -14,7 +14,7 @@ class Crumb: return self._label def __eq__(self, other): - if hasattr(other, "route"): + if hasattr(other, 'route'): return self.route == other.route return self.route == other diff --git a/lemoncurry/debug.py b/lemoncurry/debug.py index 7e3420f..d518f52 100644 --- a/lemoncurry/debug.py +++ b/lemoncurry/debug.py @@ -2,6 +2,6 @@ from debug_toolbar.middleware import show_toolbar as core_show_toolbar def show_toolbar(request): - if request.path.endswith("/amp"): + if request.path.endswith('/amp'): return False return core_show_toolbar(request) diff --git a/lemoncurry/jinja2/__init__.py b/lemoncurry/jinja2/__init__.py index 2379b89..0182a8f 100644 --- a/lemoncurry/jinja2/__init__.py +++ b/lemoncurry/jinja2/__init__.py @@ -22,22 +22,18 @@ def environment(**options): lstrip_blocks=True, **options ) - env.filters.update( - { - "ago": ago, - "friendly_url": friendly_url, - "markdown": markdown, - } - ) - env.globals.update( - { - "entry_kinds": entry_kinds, - "favicons": favicons, - "package": load_package_json(), - "settings": settings, - "static": staticfiles_storage.url, - "theme_color": theme_color, - "url": reverse, - } - ) + env.filters.update({ + 'ago': ago, + 'friendly_url': friendly_url, + 'markdown': markdown, + }) + env.globals.update({ + 'entry_kinds': entry_kinds, + 'favicons': favicons, + 'package': load_package_json(), + 'settings': settings, + 'static': staticfiles_storage.url, + 'theme_color': theme_color, + 'url': reverse, + }) return env diff --git a/lemoncurry/jinja2/ago.py b/lemoncurry/jinja2/ago.py index c061619..4794b0e 100644 --- a/lemoncurry/jinja2/ago.py +++ b/lemoncurry/jinja2/ago.py @@ -6,4 +6,4 @@ def ago(dt: datetime) -> str: # We have to convert the datetime we get to local time first, because ago # just strips the timezone from a timezone-aware datetime. dt = dt.astimezone() - return human(dt, precision=1, past_tense="{}", abbreviate=True) + return human(dt, precision=1, past_tense='{}', abbreviate=True) diff --git a/lemoncurry/jinja2/bleach.py b/lemoncurry/jinja2/bleach.py index 401e9ed..628f3f2 100644 --- a/lemoncurry/jinja2/bleach.py +++ b/lemoncurry/jinja2/bleach.py @@ -3,13 +3,13 @@ from bleach.linkifier import LinkifyFilter from jinja2 import pass_eval_context from markupsafe import Markup -TAGS = ["cite", "code", "details", "p", "pre", "img", "span", "summary"] +TAGS = ['cite', 'code', 'details', 'p', 'pre', 'img', 'span', 'summary'] TAGS.extend(ALLOWED_TAGS) ATTRIBUTES = { - "a": ["href", "title", "class"], - "details": ["open"], - "img": ["alt", "src", "title"], - "span": ["class"], + 'a': ['href', 'title', 'class'], + 'details': ['open'], + 'img': ['alt', 'src', 'title'], + 'span': ['class'], } cleaner = Cleaner(tags=TAGS, attributes=ATTRIBUTES, filters=(LinkifyFilter,)) diff --git a/lemoncurry/jinja2/markdown.py b/lemoncurry/jinja2/markdown.py index 37cbb68..02709c4 100644 --- a/lemoncurry/jinja2/markdown.py +++ b/lemoncurry/jinja2/markdown.py @@ -3,14 +3,12 @@ from markdown import Markdown from .bleach import bleach -md = Markdown( - extensions=( - "extra", - "sane_lists", - "smarty", - "toc", - ) -) +md = Markdown(extensions=( + 'extra', + 'sane_lists', + 'smarty', + 'toc', +)) @pass_eval_context diff --git a/lemoncurry/middleware.py b/lemoncurry/middleware.py index d6d3f3e..02221bc 100644 --- a/lemoncurry/middleware.py +++ b/lemoncurry/middleware.py @@ -8,9 +8,7 @@ class ResponseException(Exception): class ResponseExceptionMiddleware(MiddlewareMixin): - def process_exception( - self, request: HttpRequest, exception: Exception - ) -> HttpResponse: + def process_exception(self, request: HttpRequest, exception: Exception) -> HttpResponse: if isinstance(exception, ResponseException): return exception.response raise exception diff --git a/lemoncurry/requests.py b/lemoncurry/requests.py index a365f88..f4fc5eb 100644 --- a/lemoncurry/requests.py +++ b/lemoncurry/requests.py @@ -11,7 +11,7 @@ from mf2py import Parser class DjangoCache(BaseCache): @classmethod def key(cls, url): - return "req:" + sha256(url.encode("utf-8")).hexdigest() + return 'req:' + sha256(url.encode('utf-8')).hexdigest() def get(self, url): key = self.key(url) @@ -45,4 +45,4 @@ def get(url): def mf2(url): r = get(url) - return Parser(doc=r.text, url=url, html_parser="html5lib") + return Parser(doc=r.text, url=url, html_parser='html5lib') diff --git a/lemoncurry/settings/base.py b/lemoncurry/settings/base.py index b2c5ba5..cd3ea1a 100644 --- a/lemoncurry/settings/base.py +++ b/lemoncurry/settings/base.py @@ -16,7 +16,7 @@ from typing import List APPEND_SLASH = False ADMINS = [ - ("dani", "dani@00dani.me"), + ('dani', 'dani@00dani.me'), ] BASE_DIR = path.dirname(path.dirname(path.dirname(path.abspath(__file__)))) @@ -26,13 +26,13 @@ BASE_DIR = path.dirname(path.dirname(path.dirname(path.abspath(__file__)))) # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "6riil57g@r^wprf7mdy((+bs&(6l*phcn9&fd$l0@t-kzj+xww" +SECRET_KEY = '6riil57g@r^wprf7mdy((+bs&(6l*phcn9&fd$l0@t-kzj+xww' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS: List[str] = [] -INTERNAL_IPS = ["127.0.0.1", "::1"] +ALLOWED_HOSTS = [] # type: List[str] +INTERNAL_IPS = ['127.0.0.1', '::1'] # Settings to tighten up security - these can safely be on in dev mode too, # since I dev using a local HTTPS server. @@ -50,7 +50,7 @@ CSRF_COOKIE_SECURE = True # Miscellanous headers to protect against attacks. SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True -X_FRAME_OPTIONS = "DENY" +X_FRAME_OPTIONS = 'DENY' # This technically isn't needed, since nginx doesn't let the app be accessed # over insecure HTTP anyway. Just for completeness! @@ -58,106 +58,110 @@ SECURE_SSL_REDIRECT = True # We run behind nginx, so we need nginx to tell us whether we're using HTTPS or # not. -SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # Application definition INSTALLED_APPS = [ - "lemoncurry", - "pyup_django", - "django.contrib.admin", - "django.contrib.admindocs", - "django.contrib.auth", - "django.contrib.contenttypes", - "django.contrib.humanize", - "django.contrib.sessions", - "django.contrib.sites", - "django.contrib.sitemaps", - "django.contrib.messages", - "django.contrib.staticfiles", - "analytical", - "annoying", - "compressor", - "computed_property", - "corsheaders", - "debug_toolbar", - "django_activeurl", - "django_agent_trust", - "django_extensions", - "django_otp", - "django_otp.plugins.otp_static", - "django_otp.plugins.otp_totp", - "django_rq", - "meta", - "entries", - "home", - "lemonauth", - "lemonshort", - "micropub", - "users", - "webmention", - "wellknowns", + 'lemoncurry', + 'pyup_django', + + 'django.contrib.admin', + 'django.contrib.admindocs', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.humanize', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.sitemaps', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'analytical', + 'annoying', + 'compressor', + 'computed_property', + 'corsheaders', + 'debug_toolbar', + 'django_activeurl', + 'django_agent_trust', + 'django_extensions', + 'django_otp', + 'django_otp.plugins.otp_static', + 'django_otp.plugins.otp_totp', + 'django_rq', + 'favicon', + 'meta', + + 'entries', + 'home', + 'lemonauth', + 'lemonshort', + 'micropub', + 'users', + 'webmention', + 'wellknowns', ] MIDDLEWARE = [ - "debug_toolbar.middleware.DebugToolbarMiddleware", - "django.middleware.http.ConditionalGetMiddleware", - "django.middleware.security.SecurityMiddleware", - "django.contrib.admindocs.middleware.XViewMiddleware", - "django.contrib.sessions.middleware.SessionMiddleware", - "corsheaders.middleware.CorsMiddleware", - "django.middleware.common.CommonMiddleware", - "django.middleware.csrf.CsrfViewMiddleware", - "django.contrib.auth.middleware.AuthenticationMiddleware", - "django_otp.middleware.OTPMiddleware", - "django_agent_trust.middleware.AgentMiddleware", - "django.contrib.messages.middleware.MessageMiddleware", - "django.contrib.sites.middleware.CurrentSiteMiddleware", - "django.middleware.clickjacking.XFrameOptionsMiddleware", - "lemoncurry.middleware.ResponseExceptionMiddleware", + 'debug_toolbar.middleware.DebugToolbarMiddleware', + 'django.middleware.http.ConditionalGetMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.admindocs.middleware.XViewMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', + 'django_agent_trust.middleware.AgentMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.sites.middleware.CurrentSiteMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'lemoncurry.middleware.ResponseExceptionMiddleware', ] -ROOT_URLCONF = "lemoncurry.urls" +ROOT_URLCONF = 'lemoncurry.urls' -SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" +SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' TEMPLATES = [ { - "BACKEND": "django.template.backends.jinja2.Jinja2", - "APP_DIRS": True, - "OPTIONS": { - "environment": "lemoncurry.jinja2.environment", + 'BACKEND': 'django.template.backends.jinja2.Jinja2', + 'APP_DIRS': True, + 'OPTIONS': { + 'environment': 'lemoncurry.jinja2.environment', }, }, { - "BACKEND": "django.template.backends.django.DjangoTemplates", - "APP_DIRS": True, - "OPTIONS": { - "context_processors": [ - "django.template.context_processors.debug", - "django.template.context_processors.request", - "django.contrib.auth.context_processors.auth", - "django.contrib.messages.context_processors.messages", + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', ], }, }, ] -WSGI_APPLICATION = "lemoncurry.wsgi.application" +WSGI_APPLICATION = 'lemoncurry.wsgi.application' # Cache # https://docs.djangoproject.com/en/1.11/ref/settings/#std:setting-CACHES CACHES = { - "default": { - "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "redis://127.0.0.1:6380/0", - "KEY_PREFIX": "lemoncurry", - "OPTIONS": { - "PARSER_CLASS": "redis.connection.HiredisParser", - "SERIALIZER": "lemoncurry.msgpack.MSGPackModernSerializer", + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6380/0', + 'KEY_PREFIX': 'lemoncurry', + 'OPTIONS': { + 'PARSER_CLASS': 'redis.connection.HiredisParser', + 'SERIALIZER': 'lemoncurry.msgpack.MSGPackModernSerializer', }, - "VERSION": 2, + 'VERSION': 2, } } @@ -165,51 +169,51 @@ CACHES = { # https://docs.djangoproject.com/en/1.11/ref/settings/#databases DATABASES = { - "default": { - "ENGINE": "django.db.backends.postgresql", - "NAME": environ.get("POSTGRES_DB", "lemoncurry"), - "USER": environ.get("POSTGRES_USER"), - "PASSWORD": environ.get("POSTGRES_PASSWORD"), - "HOST": environ.get("POSTGRES_HOST", "localhost"), + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': environ.get('POSTGRES_DB', 'lemoncurry'), + 'USER': environ.get('POSTGRES_USER'), + 'PASSWORD': environ.get('POSTGRES_PASSWORD'), + 'HOST': environ.get('POSTGRES_HOST', 'localhost'), } } -DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' -AUTH_USER_MODEL = "users.User" +AUTH_USER_MODEL = 'users.User' # Password hashers # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators PASSWORD_HASHERS = [ - "django.contrib.auth.hashers.Argon2PasswordHasher", - "django.contrib.auth.hashers.PBKDF2PasswordHasher", - "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", - "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", - "django.contrib.auth.hashers.BCryptPasswordHasher", + 'django.contrib.auth.hashers.Argon2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', + 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', + 'django.contrib.auth.hashers.BCryptPasswordHasher', ] # Password validation # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators -PW_VALIDATOR_MODULE = "django.contrib.auth.password_validation" +PW_VALIDATOR_MODULE = 'django.contrib.auth.password_validation' AUTH_PASSWORD_VALIDATORS = [ - {"NAME": PW_VALIDATOR_MODULE + ".UserAttributeSimilarityValidator"}, - {"NAME": PW_VALIDATOR_MODULE + ".MinimumLengthValidator"}, - {"NAME": PW_VALIDATOR_MODULE + ".CommonPasswordValidator"}, - {"NAME": PW_VALIDATOR_MODULE + ".NumericPasswordValidator"}, + {'NAME': PW_VALIDATOR_MODULE + '.UserAttributeSimilarityValidator'}, + {'NAME': PW_VALIDATOR_MODULE + '.MinimumLengthValidator'}, + {'NAME': PW_VALIDATOR_MODULE + '.CommonPasswordValidator'}, + {'NAME': PW_VALIDATOR_MODULE + '.NumericPasswordValidator'}, ] -LOGIN_URL = "lemonauth:login" -LOGIN_REDIRECT_URL = "home:index" +LOGIN_URL = 'lemonauth:login' +LOGIN_REDIRECT_URL = 'home:index' LOGOUT_REDIRECT_URL = LOGIN_REDIRECT_URL # Internationalization # https://docs.djangoproject.com/en/1.11/topics/i18n/ -LANGUAGE_CODE = "en-au" +LANGUAGE_CODE = 'en-au' -TIME_ZONE = "Australia/Sydney" +TIME_ZONE = 'Australia/Sydney' USE_I18N = True @@ -221,21 +225,21 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.11/howto/static-files/ -STATIC_URL = "/static/" -STATIC_ROOT = path.join(BASE_DIR, "static") +STATIC_URL = '/static/' +STATIC_ROOT = path.join(BASE_DIR, 'static') STATICFILES_FINDERS = ( - "django.contrib.staticfiles.finders.FileSystemFinder", - "django.contrib.staticfiles.finders.AppDirectoriesFinder", - "compressor.finders.CompressorFinder", + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'compressor.finders.CompressorFinder', ) -STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' COMPRESS_PRECOMPILERS = ( - ("text/stylus", "npx stylus -u ./lemoncurry/static/lemoncurry/css/theme"), + ('text/stylus', 'npx stylus -u ./lemoncurry/static/lemoncurry/css/theme'), ) -MEDIA_URL = STATIC_URL + "media/" -MEDIA_ROOT = path.join(STATIC_ROOT, "media") +MEDIA_URL = STATIC_URL + 'media/' +MEDIA_ROOT = path.join(STATIC_ROOT, 'media') # django-contrib-sites # https://docs.djangoproject.com/en/dev/ref/contrib/sites/ @@ -247,25 +251,28 @@ AGENT_COOKIE_SECURE = True # django-cors-headers CORS_ORIGIN_ALLOW_ALL = True -CORS_URLS_REGEX = r"^/(?!admin|auth/(?:login|logout|indie)).*$" +CORS_URLS_REGEX = r'^/(?!admin|auth/(?:login|logout|indie)).*$' # lemonshort -SHORT_BASE_URL = "/s/" +SHORT_BASE_URL = '/s/' SHORTEN_MODELS = { - "e": "entries.entry", + 'e': 'entries.entry', } # django-meta # https://django-meta.readthedocs.io/en/latest/settings.html -META_SITE_PROTOCOL = "https" +META_SITE_PROTOCOL = 'https' META_USE_SITES = True META_USE_OG_PROPERTIES = True META_USE_TWITTER_PROPERTIES = True # django-push # https://django-push.readthedocs.io/en/latest/publisher.html -PUSH_HUB = "https://00dani.superfeedr.com/" +PUSH_HUB = 'https://00dani.superfeedr.com/' # django-rq # https://github.com/ui/django-rq -RQ_QUEUES = {"default": {"USE_REDIS_CACHE": "default"}} +RQ_QUEUES = {'default': {'USE_REDIS_CACHE': 'default'}} + +# django-super-favicon +FAVICON_STORAGE = 'django.core.files.storage.DefaultStorage' diff --git a/lemoncurry/settings/dev.py b/lemoncurry/settings/dev.py index 1c4edc1..999387d 100644 --- a/lemoncurry/settings/dev.py +++ b/lemoncurry/settings/dev.py @@ -1,7 +1,7 @@ from .base import * -ALLOWED_HOSTS = ["*"] -META_SITE_DOMAIN = "00dani.lo" -META_FB_APPID = "142105433189339" -STATIC_URL = "https://static.00dani.lo/" -MEDIA_URL = "https://media.00dani.lo/" +ALLOWED_HOSTS = ['*'] +META_SITE_DOMAIN = '00dani.lo' +META_FB_APPID = '142105433189339' +STATIC_URL = 'https://static.00dani.lo/' +MEDIA_URL = 'https://media.00dani.lo/' diff --git a/lemoncurry/settings/prod.py b/lemoncurry/settings/prod.py index a4407ca..31f7c84 100644 --- a/lemoncurry/settings/prod.py +++ b/lemoncurry/settings/prod.py @@ -4,19 +4,19 @@ from os.path import join from .base import * from .base import BASE_DIR, DATABASES -ALLOWED_HOSTS = ["00dani.me"] +ALLOWED_HOSTS = ['00dani.me'] DEBUG = False -SECRET_KEY = environ["DJANGO_SECRET_KEY"] -SERVER_EMAIL = "lemoncurry@00dani.me" +SECRET_KEY = environ['DJANGO_SECRET_KEY'] +SERVER_EMAIL = 'lemoncurry@00dani.me' # Authenticate as an app-specific Postgres user in production. -DATABASES["default"]["USER"] = "lemoncurry" +DATABASES['default']['USER'] = 'lemoncurry' -SHORT_BASE_URL = "https://nya.as/" +SHORT_BASE_URL = 'https://nya.as/' -STATIC_ROOT = join(BASE_DIR, "..", "static") -MEDIA_ROOT = join(BASE_DIR, "..", "media") -STATIC_URL = "https://cdn.00dani.me/" -MEDIA_URL = STATIC_URL + "m/" -META_SITE_DOMAIN = "00dani.me" -META_FB_APPID = "145311792869199" +STATIC_ROOT = join(BASE_DIR, '..', 'static') +MEDIA_ROOT = join(BASE_DIR, '..', 'media') +STATIC_URL = 'https://cdn.00dani.me/' +MEDIA_URL = STATIC_URL + 'm/' +META_SITE_DOMAIN = '00dani.me' +META_FB_APPID = '145311792869199' diff --git a/lemoncurry/settings/test.py b/lemoncurry/settings/test.py index c8dc698..c3eb440 100644 --- a/lemoncurry/settings/test.py +++ b/lemoncurry/settings/test.py @@ -1,8 +1,8 @@ from .base import * -ALLOWED_HOSTS = ["*"] +ALLOWED_HOSTS = ['*'] SECURE_SSL_REDIRECT = False -STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' -MEDIA_URL = "/media/" -STATIC_ROOT = path.join(BASE_DIR, "media") +MEDIA_URL = '/media/' +STATIC_ROOT = path.join(BASE_DIR, 'media') diff --git a/lemoncurry/templatetags/absolute_url.py b/lemoncurry/templatetags/absolute_url.py index 9724e3c..592079b 100644 --- a/lemoncurry/templatetags/absolute_url.py +++ b/lemoncurry/templatetags/absolute_url.py @@ -8,5 +8,5 @@ register = template.Library() @register.simple_tag @register.filter(is_safe=True) def absolute_url(url): - base = "https://" + Site.objects.get_current().domain + base = 'https://' + Site.objects.get_current().domain return urljoin(base, url) diff --git a/lemoncurry/templatetags/bleach.py b/lemoncurry/templatetags/bleach.py index cc410b4..7b9ffc3 100644 --- a/lemoncurry/templatetags/bleach.py +++ b/lemoncurry/templatetags/bleach.py @@ -5,13 +5,13 @@ from django.utils.safestring import mark_safe from bleach.sanitizer import Cleaner, ALLOWED_TAGS from bleach.linkifier import LinkifyFilter -tags = ["cite", "code", "details", "p", "pre", "img", "span", "summary"] +tags = ['cite', 'code', 'details', 'p', 'pre', 'img', 'span', 'summary'] tags.extend(ALLOWED_TAGS) attributes = { - "a": ["href", "title", "class"], - "details": ["open"], - "img": ["alt", "src", "title"], - "span": ["class"], + 'a': ['href', 'title', 'class'], + 'details': ['open'], + 'img': ['alt', 'src', 'title'], + 'span': ['class'], } register = template.Library() diff --git a/lemoncurry/templatetags/jsonify.py b/lemoncurry/templatetags/jsonify.py index 50864a1..377ebb7 100644 --- a/lemoncurry/templatetags/jsonify.py +++ b/lemoncurry/templatetags/jsonify.py @@ -11,5 +11,5 @@ register = template.Library() @register.filter def jsonify(value): if isinstance(value, QuerySet): - return mark_safe(serialize("json", value)) + return mark_safe(serialize('json', value)) return mark_safe(json.dumps(value, cls=DjangoJSONEncoder)) diff --git a/lemoncurry/templatetags/lemoncurry_tags.py b/lemoncurry/templatetags/lemoncurry_tags.py index bd865f0..0968f43 100644 --- a/lemoncurry/templatetags/lemoncurry_tags.py +++ b/lemoncurry/templatetags/lemoncurry_tags.py @@ -1,3 +1,4 @@ + from django import template from django.conf import settings from django.contrib.sites.models import Site @@ -39,71 +40,67 @@ def site_name(): return Site.objects.get_current().name -@register.inclusion_tag("lemoncurry/tags/nav.html") +@register.inclusion_tag('lemoncurry/tags/nav.html') def nav_left(request): - items = ( - MenuItem(label=k.plural, icon=k.icon, url=("entries:index", (k,))) - for k in kinds.all - ) - return {"items": items, "request": request} + items = (MenuItem( + label=k.plural, + icon=k.icon, + url=('entries:index', (k,)) + ) for k in kinds.all) + return {'items': items, 'request': request} -@register.inclusion_tag("lemoncurry/tags/nav.html") +@register.inclusion_tag('lemoncurry/tags/nav.html') def nav_right(request): if request.user.is_authenticated: items = ( - MenuItem(label="admin", icon="fas fa-cog", url="admin:index"), - MenuItem( - label="log out", icon="fas fa-sign-out-alt", url="lemonauth:logout" - ), + MenuItem(label='admin', icon='fas fa-cog', url='admin:index'), + MenuItem(label='log out', icon='fas fa-sign-out-alt', + url='lemonauth:logout'), ) else: items = ( - MenuItem(label="log in", icon="fas fa-sign-in-alt", url="lemonauth:login"), + MenuItem(label='log in', icon='fas fa-sign-in-alt', + url='lemonauth:login'), ) - return {"items": items, "request": request} + return {'items': items, 'request': request} -@register.inclusion_tag("lemoncurry/tags/breadcrumbs.html", takes_context=True) +@register.inclusion_tag('lemoncurry/tags/breadcrumbs.html', takes_context=True) def nav_crumbs(context, route): crumbs = breadcrumbs.find(route) current = crumbs.pop() - item_list_element = [ - { - "@type": "ListItem", - "position": i + 1, - "item": { - "@id": context["origin"] + crumb.url, - "@type": "WebPage", - "name": crumb.label, - }, + item_list_element = [{ + '@type': 'ListItem', + 'position': i + 1, + 'item': { + '@id': context['origin'] + crumb.url, + '@type': 'WebPage', + 'name': crumb.label } - for i, crumb in enumerate(crumbs) - ] - item_list_element.append( - { - "@type": "ListItem", - "position": len(item_list_element) + 1, - "item": { - "id": context["uri"], - "@type": "WebPage", - "name": current.label or context.get("title"), - }, + } for i, crumb in enumerate(crumbs)] + item_list_element.append({ + '@type': 'ListItem', + 'position': len(item_list_element) + 1, + 'item': { + 'id': context['uri'], + '@type': 'WebPage', + 'name': current.label or context.get('title'), } - ) + }) breadcrumb_list = { - "@context": "http://schema.org", - "@type": "BreadcrumbList", - "itemListElement": item_list_element, + '@context': 'http://schema.org', + '@type': 'BreadcrumbList', + 'itemListElement': item_list_element } return { - "breadcrumb_list": breadcrumb_list, - "crumbs": crumbs, - "current": current, - "title": context.get("title"), + 'breadcrumb_list': breadcrumb_list, + 'crumbs': crumbs, + 'current': current, + 'title': context.get('title'), } diff --git a/lemoncurry/templatetags/markdown.py b/lemoncurry/templatetags/markdown.py index 3452a1f..1505987 100644 --- a/lemoncurry/templatetags/markdown.py +++ b/lemoncurry/templatetags/markdown.py @@ -3,14 +3,12 @@ from django import template from markdown import Markdown from .bleach import bleach -md = Markdown( - extensions=( - "extra", - "sane_lists", - "smarty", - "toc", - ) -) +md = Markdown(extensions=( + 'extra', + 'sane_lists', + 'smarty', + 'toc', +)) register = template.Library() diff --git a/lemoncurry/tests/breadcrumbs.py b/lemoncurry/tests/breadcrumbs.py index 8372ba9..28d8e14 100644 --- a/lemoncurry/tests/breadcrumbs.py +++ b/lemoncurry/tests/breadcrumbs.py @@ -6,43 +6,43 @@ from .. import breadcrumbs as b @pytest.fixture def nested_crumbs(): - x = b.Crumb("nc.x", label="x") - y = b.Crumb("nc.y", label="y", parent="nc.x") - z = b.Crumb("nc.z", label="z", parent="nc.y") + x = b.Crumb('nc.x', label='x') + y = b.Crumb('nc.y', label='y', parent='nc.x') + z = b.Crumb('nc.z', label='z', parent='nc.y') crumbs = (x, y, z) for crumb in crumbs: b.breadcrumbs[crumb.route] = crumb - yield namedtuple("NestedCrumbs", "x y z")(*crumbs) + yield namedtuple('NestedCrumbs', 'x y z')(*crumbs) for crumb in crumbs: del b.breadcrumbs[crumb.route] @pytest.fixture def crumb_match(nested_crumbs): - return namedtuple("Match", "view_name")(nested_crumbs.z.route) + return namedtuple('Match', 'view_name')(nested_crumbs.z.route) class TestAdd: def test_inserts_a_breadcrumb_without_parent(self): - route = "tests.add.insert" + route = 'tests.add.insert' assert route not in b.breadcrumbs - b.add(route, "some label") + b.add(route, 'some label') assert route in b.breadcrumbs assert b.breadcrumbs[route] == route route = b.breadcrumbs[route] - assert route.label == "some label" + assert route.label == 'some label' assert route.parent is None def test_inserts_a_breadcrumb_with_parent(self): - route = "tests.add.with_parent" - parent = "tests.add.insert" + route = 'tests.add.with_parent' + parent = 'tests.add.insert' assert route not in b.breadcrumbs - b.add(route, "child label", parent) + b.add(route, 'child label', parent) assert route in b.breadcrumbs assert b.breadcrumbs[route] == route route = b.breadcrumbs[route] - assert route.label == "child label" + assert route.label == 'child label' assert route.parent == parent diff --git a/lemoncurry/tests/utils.py b/lemoncurry/tests/utils.py index 550a49a..5745087 100644 --- a/lemoncurry/tests/utils.py +++ b/lemoncurry/tests/utils.py @@ -5,22 +5,22 @@ from .. import utils class TestOrigin: def test_simple_http(self): """should return the correct origin for a vanilla HTTP site""" - req = Mock(scheme="http", site=Mock(domain="lemoncurry.test")) - assert utils.origin(req) == "http://lemoncurry.test" + req = Mock(scheme='http', site=Mock(domain='lemoncurry.test')) + assert utils.origin(req) == 'http://lemoncurry.test' def test_simple_https(self): """should return the correct origin for a vanilla HTTPS site""" - req = Mock(scheme="https", site=Mock(domain="secure.lemoncurry.test")) - assert utils.origin(req) == "https://secure.lemoncurry.test" + req = Mock(scheme='https', site=Mock(domain='secure.lemoncurry.test')) + assert utils.origin(req) == 'https://secure.lemoncurry.test' class TestUri: def test_siteroot(self): """should return correct full URI for requests to the site root""" - req = Mock(scheme="https", path="/", site=Mock(domain="l.test")) - assert utils.uri(req) == "https://l.test/" + req = Mock(scheme='https', path='/', site=Mock(domain='l.test')) + assert utils.uri(req) == 'https://l.test/' def test_path(self): """should return correct full URI for requests with a path""" - req = Mock(scheme="https", path="/notes/23", site=Mock(domain="l.tst")) - assert utils.uri(req) == "https://l.tst/notes/23" + req = Mock(scheme='https', path='/notes/23', site=Mock(domain='l.tst')) + assert utils.uri(req) == 'https://l.tst/notes/23' diff --git a/lemoncurry/theme.py b/lemoncurry/theme.py index 437dc6e..b282e13 100644 --- a/lemoncurry/theme.py +++ b/lemoncurry/theme.py @@ -4,14 +4,12 @@ from yaml import safe_load path = join( settings.BASE_DIR, - "lemoncurry", - "static", - "base16-materialtheme-scheme", - "material-darker.yaml", + 'lemoncurry', 'static', + 'base16-materialtheme-scheme', 'material-darker.yaml', ) -with open(path, "r") as f: +with open(path, 'r') as f: theme = safe_load(f) def color(i): - return "#" + theme["base0" + format(i, "1X")] + return '#' + theme['base0' + format(i, '1X')] diff --git a/lemoncurry/urls.py b/lemoncurry/urls.py index e2d72bb..0548105 100644 --- a/lemoncurry/urls.py +++ b/lemoncurry/urls.py @@ -27,37 +27,33 @@ from entries.sitemaps import EntriesSitemap from home.sitemaps import HomeSitemap sections = { - "entries": EntriesSitemap, - "home": HomeSitemap, + 'entries': EntriesSitemap, + 'home': HomeSitemap, } -maps = {"sitemaps": sections} +maps = {'sitemaps': sections} urlpatterns = ( - path("", include("home.urls")), - path("", include("entries.urls")), - path("", include("users.urls")), - path(".well-known/", include("wellknowns.urls")), - path("admin/doc/", include("django.contrib.admindocs.urls")), - path("admin/", admin.site.urls), - path("auth/", include("lemonauth.urls")), - path( - "favicon.ico", - RedirectView.as_view(url=settings.MEDIA_URL + "favicon/favicon.ico"), - ), - path("micropub", include("micropub.urls")), - path("s/", include("lemonshort.urls")), - path("webmention", include("webmention.urls")), - path("django-rq/", include("django_rq.urls")), - path("sitemap.xml", sitemap.index, maps, name="sitemap"), - path( - "sitemaps/
.xml", - sitemap.sitemap, - maps, - name="django.contrib.sitemaps.views.sitemap", - ), + path('', include('home.urls')), + path('', include('entries.urls')), + path('', include('users.urls')), + path('.well-known/', include('wellknowns.urls')), + path('admin/doc/', include('django.contrib.admindocs.urls')), + path('admin/', admin.site.urls), + path('auth/', include('lemonauth.urls')), + path('favicon.ico', RedirectView.as_view( + url=settings.MEDIA_URL + 'favicon/favicon.ico')), + path('micropub', include('micropub.urls')), + path('s/', include('lemonshort.urls')), + path('webmention', include('webmention.urls')), + + path('django-rq/', include('django_rq.urls')), + path('sitemap.xml', sitemap.index, maps, name='sitemap'), + path('sitemaps/
.xml', sitemap.sitemap, maps, + name='django.contrib.sitemaps.views.sitemap'), ) # type: Tuple[URLPattern, ...] if settings.DEBUG: import debug_toolbar - - urlpatterns += (path("__debug__/", include(debug_toolbar.urls)),) + urlpatterns += ( + path('__debug__/', include(debug_toolbar.urls)), + ) diff --git a/lemoncurry/utils.py b/lemoncurry/utils.py index b4b2434..1ac70d2 100644 --- a/lemoncurry/utils.py +++ b/lemoncurry/utils.py @@ -20,7 +20,7 @@ class PackageJson: def load(self) -> Dict[str, Any]: if self.data is None: - with open(join(settings.BASE_DIR, "package.json")) as f: + with open(join(settings.BASE_DIR, 'package.json')) as f: self.data = json.load(f) assert self.data is not None return self.data @@ -30,10 +30,10 @@ PACKAGE = PackageJson() def friendly_url(url): - if "//" not in url: - url = "//" + url + if '//' not in url: + url = '//' + url (scheme, netloc, path, params, q, fragment) = urlparse(url) - if path == "/": + if path == '/': return netloc return "{}\u200B{}".format(netloc, path) @@ -43,7 +43,7 @@ def load_package_json() -> Dict[str, Any]: def origin(request): - return "{0}://{1}".format(request.scheme, request.site.domain) + return '{0}://{1}'.format(request.scheme, request.site.domain) def absolute_url(request, url): @@ -56,18 +56,19 @@ def uri(request): def form_encoded_response(content): return HttpResponse( - urlencode(content), content_type="application/x-www-form-urlencoded" + urlencode(content), + content_type='application/x-www-form-urlencoded' ) REPS = { - "application/x-www-form-urlencoded": form_encoded_response, - "application/json": JsonResponse, + 'application/x-www-form-urlencoded': form_encoded_response, + 'application/json': JsonResponse, } def choose_type(request, content, reps=REPS): - accept = request.META.get("HTTP_ACCEPT", "*/*") + accept = request.META.get('HTTP_ACCEPT', '*/*') type = get_best_match(accept, reps.keys()) if type: return reps[type](content) @@ -75,11 +76,11 @@ def choose_type(request, content, reps=REPS): def bad_req(message): - return HttpResponseBadRequest(message, content_type="text/plain") + return HttpResponseBadRequest(message, content_type='text/plain') def forbid(message): - return HttpResponseForbidden(message, content_type="text/plain") + return HttpResponseForbidden(message, content_type='text/plain') def to_plain(md): diff --git a/lemonshort/apps.py b/lemonshort/apps.py index 259f20a..4edb115 100644 --- a/lemonshort/apps.py +++ b/lemonshort/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class LemonshortConfig(AppConfig): - name = "lemonshort" + name = 'lemonshort' diff --git a/lemonshort/convert.py b/lemonshort/convert.py index d408610..936df44 100644 --- a/lemonshort/convert.py +++ b/lemonshort/convert.py @@ -6,9 +6,8 @@ from string import ascii_lowercase, ascii_uppercase chars = ascii_uppercase + ascii_lowercase conv = BaseConverter(chars) - class AbcIdConverter: - regex = "[a-zA-Z]+" + regex = '[a-zA-Z]+' def to_python(self, value: str) -> int: return int(conv.decode(value)) diff --git a/lemonshort/short_url.py b/lemonshort/short_url.py index d66461b..76d7c62 100644 --- a/lemonshort/short_url.py +++ b/lemonshort/short_url.py @@ -11,7 +11,7 @@ def short_url(entity): if not prefixes: for k, m in settings.SHORTEN_MODELS.items(): prefixes[apps.get_model(m)] = k - base = "/" - if hasattr(settings, "SHORT_BASE_URL"): + base = '/' + if hasattr(settings, 'SHORT_BASE_URL'): base = settings.SHORT_BASE_URL return base + prefixes[type(entity)] + AbcIdConverter().to_url(entity.id) diff --git a/lemonshort/tests/convert.py b/lemonshort/tests/convert.py index 033aa15..80d6299 100644 --- a/lemonshort/tests/convert.py +++ b/lemonshort/tests/convert.py @@ -3,14 +3,14 @@ from .. import convert def test_to_python(): samples = { - "A": 0, - "B": 1, - "Y": 24, - "a": 26, - "b": 27, - "y": 50, - "BA": 52, - "BAB": 2705, + 'A': 0, + 'B': 1, + 'Y': 24, + 'a': 26, + 'b': 27, + 'y': 50, + 'BA': 52, + 'BAB': 2705, } converter = convert.AbcIdConverter() for abc, id in samples.items(): @@ -19,13 +19,13 @@ def test_to_python(): def test_id_to_abc(): samples = { - 1: "B", - 24: "Y", - 26: "a", - 52: "BA", - 78: "Ba", - 104: "CA", - 130: "Ca", + 1: 'B', + 24: 'Y', + 26: 'a', + 52: 'BA', + 78: 'Ba', + 104: 'CA', + 130: 'Ca', } converter = convert.AbcIdConverter() for id, abc in samples.items(): diff --git a/lemonshort/urls.py b/lemonshort/urls.py index 385a256..7fcf49c 100644 --- a/lemonshort/urls.py +++ b/lemonshort/urls.py @@ -4,10 +4,10 @@ from django.urls import path, register_converter from .convert import AbcIdConverter from .views import unshort -register_converter(AbcIdConverter, "abc_id") +register_converter(AbcIdConverter, 'abc_id') -app_name = "lemonshort" +app_name = 'lemonshort' urlpatterns = tuple( - path("{0!s}".format(k), unshort, name=m, kwargs={"model": m}) + path('{0!s}'.format(k), unshort, name=m, kwargs={'model': m}) for k, m in settings.SHORTEN_MODELS.items() ) diff --git a/micropub/apps.py b/micropub/apps.py index 8732a39..d5259ee 100644 --- a/micropub/apps.py +++ b/micropub/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class MicropubConfig(AppConfig): - name = "micropub" + name = 'micropub' diff --git a/micropub/error.py b/micropub/error.py index 20a745d..a5e9373 100644 --- a/micropub/error.py +++ b/micropub/error.py @@ -4,35 +4,33 @@ from typing import Optional def forbidden() -> ResponseException: - return res("forbidden", 403) + return res('forbidden', 403) def unauthorized() -> ResponseException: - return res("unauthorized", 401) + return res('unauthorized', 401) def bad_req(msg: str) -> ResponseException: - return res("invalid_request", msg=msg) + return res('invalid_request', msg=msg) def bad_type(type: str) -> ResponseException: - msg = "unsupported request type {0}".format(type) - return res("invalid_request", 415, msg) + msg = 'unsupported request type {0}'.format(type) + return res('invalid_request', 415, msg) def bad_scope(scope: str) -> ResponseException: - return res("insufficient_scope", 401, scope=scope) + return res('insufficient_scope', 401, scope=scope) -def res( - error: str, - status: Optional[int] = 400, - msg: Optional[str] = None, - scope: Optional[str] = None, -): - content = {"error": error} +def res(error: str, + status: Optional[int]=400, + msg: Optional[str]=None, + scope: Optional[str]=None): + content = {'error': error} if msg is not None: - content["error_description"] = msg + content['error_description'] = msg if scope: - content["scope"] = scope + content['scope'] = scope return ResponseException(JsonResponse(content, status=status)) diff --git a/micropub/urls.py b/micropub/urls.py index 06acd70..546d82f 100644 --- a/micropub/urls.py +++ b/micropub/urls.py @@ -2,8 +2,8 @@ from django.urls import path from .views import micropub from .views.media import media -app_name = "micropub" +app_name = 'micropub' urlpatterns = ( - path("", micropub, name="micropub"), - path("/media", media, name="media"), + path('', micropub, name='micropub'), + path('/media', media, name='media'), ) diff --git a/micropub/views/__init__.py b/micropub/views/__init__.py index 96d99e9..7ff7878 100644 --- a/micropub/views/__init__.py +++ b/micropub/views/__init__.py @@ -10,22 +10,22 @@ from .delete import delete from .query import query actions = { - "create": create, - "delete": delete, + 'create': create, + 'delete': delete, } @csrf_exempt -@require_http_methods(["GET", "HEAD", "POST"]) +@require_http_methods(['GET', 'HEAD', 'POST']) def micropub(request): request.token = tokens.auth(request) - if request.method in ("GET", "HEAD"): + if request.method in ('GET', 'HEAD'): return query(request) - action = request.POST.get("action", "create") - if request.content_type == "application/json": + action = request.POST.get('action', 'create') + if request.content_type == 'application/json': request.json = json.load(request) - action = request.json.get("action", "create") + action = request.json.get('action', 'create') if action not in actions: - raise error.bad_req("unknown action: {}".format(action)) + raise error.bad_req('unknown action: {}'.format(action)) return actions[action](request) diff --git a/micropub/views/create.py b/micropub/views/create.py index 35e1357..23bd21e 100644 --- a/micropub/views/create.py +++ b/micropub/views/create.py @@ -14,62 +14,63 @@ def form_to_mf2(request): properties = {} post = request.POST for key in post.keys(): - if key.endswith("[]"): + if key.endswith('[]'): key = key[:-2] - if key == "access_token": + if key == 'access_token': continue - properties[key] = post.getlist(key) + post.getlist(key + "[]") + properties[key] = post.getlist(key) + post.getlist(key + '[]') type = [] - if "h" in properties: - type = ["h-" + p for p in properties["h"]] - del properties["h"] - return {"type": type, "properties": properties} + if 'h' in properties: + type = ['h-' + p for p in properties['h']] + del properties['h'] + return {'type': type, 'properties': properties} def create(request): normalise = { - "application/json": lambda r: r.json, - "application/x-www-form-urlencoded": form_to_mf2, + 'application/json': lambda r: r.json, + 'application/x-www-form-urlencoded': form_to_mf2, } - if "create" not in request.token: - raise error.bad_scope("create") + if 'create' not in request.token: + raise error.bad_scope('create') if request.content_type not in normalise: raise error.unsupported_type(request.content_type) body = normalise[request.content_type](request) - if "type" not in body: - raise error.bad_req("mf2 object type required") - if body["type"] != ["h-entry"]: - raise error.bad_req("only h-entry supported") + if 'type' not in body: + raise error.bad_req('mf2 object type required') + if body['type'] != ['h-entry']: + raise error.bad_req('only h-entry supported') entry = Entry(author=request.token.user) - props = body.get("properties", {}) + props = body.get('properties', {}) kind = Note - if "name" in props: - entry.name = "\n".join(props["name"]) + if 'name' in props: + entry.name = '\n'.join(props['name']) kind = Article - if "content" in props: - entry.content = "\n".join( - c if isinstance(c, str) else c["html"] for c in props["content"] + if 'content' in props: + entry.content = '\n'.join( + c if isinstance(c, str) else c['html'] + for c in props['content'] ) - if "in-reply-to" in props: - entry.in_reply_to = props["in-reply-to"] + if 'in-reply-to' in props: + entry.in_reply_to = props['in-reply-to'] kind = Reply - if "like-of" in props: - entry.like_of = props["like-of"] + if 'like-of' in props: + entry.like_of = props['like-of'] kind = Like - if "repost-of" in props: - entry.repost_of = props["repost-of"] + if 'repost-of' in props: + entry.repost_of = props['repost-of'] kind = Repost - cats = [Cat.objects.from_name(c) for c in props.get("category", [])] + cats = [Cat.objects.from_name(c) for c in props.get('category', [])] entry.kind = kind.id entry.save() entry.cats.set(cats) entry.save() - for url in props.get("syndication", []): + for url in props.get('syndication', []): entry.syndications.create(url=url) base = utils.origin(request) @@ -79,6 +80,6 @@ def create(request): send_mentions.delay(perma) res = HttpResponse(status=201) - res["Location"] = perma - res["Link"] = '<{}>; rel="shortlink"'.format(short) + res['Location'] = perma + res['Link'] = '<{}>; rel="shortlink"'.format(short) return res diff --git a/micropub/views/delete.py b/micropub/views/delete.py index db6e13e..035da7e 100644 --- a/micropub/views/delete.py +++ b/micropub/views/delete.py @@ -6,25 +6,24 @@ from entries.jobs import ping_hub, send_mentions from .. import error - def delete(request): normalise = { - "application/json": lambda r: r.json.get("url"), - "application/x-www-form-urlencoded": lambda r: r.POST.get("url"), + 'application/json': lambda r: r.json.get('url'), + 'application/x-www-form-urlencoded': lambda r: r.POST.get('url'), } - if "delete" not in request.token: - raise error.bad_scope("delete") + if 'delete' not in request.token: + raise error.bad_scope('delete') if request.content_type not in normalise: raise error.unsupported_type(request.content_type) url = normalise[request.content_type](request) entry = from_url(url) if entry.author != request.token.user: - raise error.forbid("entry belongs to another user") + raise error.forbid('entry belongs to another user') perma = entry.absolute_url pings = entry.affected_urls - mentions = webmention.findMentions(perma)["refs"] + mentions = webmention.findMentions(perma)['refs'] entry.delete() diff --git a/micropub/views/media.py b/micropub/views/media.py index 6e77b93..db59972 100644 --- a/micropub/views/media.py +++ b/micropub/views/media.py @@ -11,9 +11,9 @@ from lemoncurry.utils import absolute_url from .. import error ACCEPTED_MEDIA_TYPES = ( - "image/gif", - "image/jpeg", - "image/png", + 'image/gif', + 'image/jpeg', + 'image/png', ) @@ -21,13 +21,15 @@ ACCEPTED_MEDIA_TYPES = ( @require_POST def media(request): token = tokens.auth(request) - if "file" not in request.FILES: + if 'file' not in request.FILES: raise error.bad_req( "a file named 'file' must be provided to the media endpoint" ) - file = request.FILES["file"] + file = request.FILES['file'] if file.content_type not in ACCEPTED_MEDIA_TYPES: - raise error.bad_req("unacceptable file type {0}".format(file.content_type)) + raise error.bad_req( + 'unacceptable file type {0}'.format(file.content_type) + ) mime = None sha = hashlib.sha256() @@ -38,15 +40,14 @@ def media(request): if mime != file.content_type: raise error.bad_req( - "detected file type {0} did not match specified file type {1}".format( - mime, file.content_type - ) + 'detected file type {0} did not match specified file type {1}' + .format(mime, file.content_type) ) - path = "mp/{0[0]}/{2}.{1}".format(*mime.split("/"), sha.hexdigest()) + path = 'mp/{0[0]}/{2}.{1}'.format(*mime.split('/'), sha.hexdigest()) path = store.save(path, file) url = absolute_url(request, store.url(path)) res = HttpResponse(status=201) - res["Location"] = url + res['Location'] = url return res diff --git a/micropub/views/query.py b/micropub/views/query.py index 4fc72a2..6b5d2d8 100644 --- a/micropub/views/query.py +++ b/micropub/views/query.py @@ -7,47 +7,48 @@ from lemoncurry.utils import absolute_url from .. import error + def config(request): config = syndicate_to(request) - config["media-endpoint"] = absolute_url(request, reverse("micropub:media")) + config['media-endpoint'] = absolute_url(request, reverse('micropub:media')) return config def source(request): - if "url" not in request.GET: - raise error.bad_req("must specify url parameter for source query") - entry = from_url(request.GET["url"]) + if 'url' not in request.GET: + raise error.bad_req('must specify url parameter for source query') + entry = from_url(request.GET['url']) props = {} - keys = set(request.GET.getlist("properties") + request.GET.getlist("properties[]")) - if not keys or "content" in keys: - props["content"] = [entry.content] - if (not keys or "category" in keys) and entry.cats.exists(): - props["category"] = [cat.name for cat in entry.cats.all()] - if (not keys or "name" in keys) and entry.name: - props["name"] = [entry.name] - if (not keys or "syndication" in keys) and entry.syndications.exists(): - props["syndication"] = [synd.url for synd in entry.syndications.all()] + keys = set(request.GET.getlist('properties') + request.GET.getlist('properties[]')) + if not keys or 'content' in keys: + props['content'] = [entry.content] + if (not keys or 'category' in keys) and entry.cats.exists(): + props['category'] = [cat.name for cat in entry.cats.all()] + if (not keys or 'name' in keys) and entry.name: + props['name'] = [entry.name] + if (not keys or 'syndication' in keys) and entry.syndications.exists(): + props['syndication'] = [synd.url for synd in entry.syndications.all()] - return {"type": ["h-entry"], "properties": props} + return {'type': ['h-entry'], 'properties': props} def syndicate_to(request): - return {"syndicate-to": []} + return {'syndicate-to': []} queries = { - "config": config, - "source": source, - "syndicate-to": syndicate_to, + 'config': config, + 'source': source, + 'syndicate-to': syndicate_to, } def query(request): - if "q" not in request.GET: - raise error.bad_req("must specify q parameter") - q = request.GET["q"] + if 'q' not in request.GET: + raise error.bad_req('must specify q parameter') + q = request.GET['q'] if q not in queries: - raise error.bad_req("unsupported query {0}".format(q)) + raise error.bad_req('unsupported query {0}'.format(q)) res = queries[q](request) return JsonResponse(res) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 1d07da0..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1317 +0,0 @@ -[[package]] -category = "main" -description = "Determine the best content to send in an HTTP response" -name = "accept-types" -optional = false -python-versions = "*" -version = "0.4.1" - -[[package]] -category = "main" -description = "ago: Human readable timedeltas" -name = "ago" -optional = false -python-versions = "*" -version = "0.0.95" - -[[package]] -category = "dev" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -name = "appdirs" -optional = false -python-versions = "*" -version = "1.4.4" - -[[package]] -category = "main" -description = "The secure Argon2 password hashing algorithm." -name = "argon2-cffi" -optional = false -python-versions = ">=3.6" -version = "21.3.0" - -[package.dependencies] -argon2-cffi-bindings = "*" - -[package.extras] -dev = ["pre-commit", "cogapp", "tomli", "coverage (>=5.0.2)", "hypothesis", "pytest", "sphinx", "sphinx-notfound-page", "furo"] -docs = ["sphinx", "sphinx-notfound-page", "furo"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pytest"] - -[[package]] -category = "main" -description = "Low-level CFFI bindings for Argon2" -name = "argon2-cffi-bindings" -optional = false -python-versions = ">=3.6" -version = "21.2.0" - -[package.dependencies] -cffi = ">=1.0.1" - -[package.extras] -dev = ["pytest", "cogapp", "pre-commit", "wheel"] -tests = ["pytest"] - -[[package]] -category = "main" -description = "ASGI specs, helper code, and adapters" -name = "asgiref" -optional = false -python-versions = ">=3.7" -version = "3.7.2" - -[package.dependencies] -[package.dependencies.typing-extensions] -python = "<3.11" -version = ">=4" - -[package.extras] -tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] - -[[package]] -category = "main" -description = "Timeout context manager for asyncio programs" -marker = "python_full_version <= \"3.11.2\"" -name = "async-timeout" -optional = false -python-versions = ">=3.6" -version = "4.0.2" - -[[package]] -category = "main" -description = "Screen-scraping library" -name = "beautifulsoup4" -optional = false -python-versions = ">=3.6.0" -version = "4.12.2" - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -category = "main" -description = "An easy safelist-based HTML-sanitizing tool." -name = "bleach" -optional = false -python-versions = ">=3.7" -version = "6.0.0" - -[package.dependencies] -six = ">=1.9.0" -webencodings = "*" - -[package.extras] -css = ["tinycss2 (>=1.1.0,<1.2)"] - -[[package]] -category = "main" -description = "httplib2 caching for requests" -name = "cachecontrol" -optional = false -python-versions = ">=3.7" -version = "0.13.1" - -[package.dependencies] -msgpack = ">=0.5.2" -requests = ">=2.16.0" - -[package.extras] -dev = ["cachecontrol", "build", "mypy", "tox", "pytest-cov", "pytest", "cherrypy", "sphinx", "black", "types-redis", "types-requests"] -filecache = ["filelock (>=3.8.0)"] -redis = ["redis (>=2.10.5)"] - -[[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." -name = "certifi" -optional = false -python-versions = ">=3.6" -version = "2023.7.22" - -[[package]] -category = "main" -description = "Foreign Function Interface for Python calling C code." -name = "cffi" -optional = false -python-versions = "*" -version = "1.15.1" - -[package.dependencies] -pycparser = "*" - -[[package]] -category = "main" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -name = "charset-normalizer" -optional = false -python-versions = ">=3.7.0" -version = "3.2.0" - -[[package]] -category = "main" -description = "Composable command line interface toolkit" -name = "click" -optional = false -python-versions = ">=3.7" -version = "8.1.6" - -[package.dependencies] -colorama = "*" - -[[package]] -category = "main" -description = "Cross-platform colored terminal text." -marker = "platform_system == \"Windows\" or sys_platform == \"win32\"" -name = "colorama" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -version = "0.4.6" - -[[package]] -category = "main" -description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." -name = "django" -optional = false -python-versions = ">=3.6" -version = "3.2.20" - -[package.dependencies] -asgiref = ">=3.3.2,<4" -pytz = "*" -sqlparse = ">=0.2.2" - -[package.extras] -argon2 = ["argon2-cffi (>=19.1.0)"] -bcrypt = ["bcrypt"] - -[[package]] -category = "main" -description = "Easy-to-use active URL highlighting for Django" -name = "django-activeurl" -optional = false -python-versions = "*" -version = "0.2.0" - -[package.dependencies] -django-classy-tags = "*" -django_appconf = "*" -lxml = "*" - -[package.dependencies.django] -python = ">=3" -version = "*" - -[[package]] -category = "main" -description = "A framework for managing agent trust, such as public vs. private computers." -name = "django-agent-trust" -optional = false -python-versions = "*" -version = "1.0.4" - -[package.dependencies] -django = ">=2.2" - -[[package]] -category = "main" -description = "Analytics service integration for Django projects" -name = "django-analytical" -optional = false -python-versions = ">=3.6" -version = "3.1.0" - -[[package]] -category = "main" -description = "This is a django application that tries to eliminate annoying things in the Django framework." -name = "django-annoying" -optional = false -python-versions = "*" -version = "0.10.6" - -[package.dependencies] -Django = ">=1.11" -six = "*" - -[[package]] -category = "main" -description = "A helper class for handling configuration defaults of packaged apps gracefully." -name = "django-appconf" -optional = false -python-versions = ">=3.6" -version = "1.0.5" - -[package.dependencies] -django = "*" - -[[package]] -category = "main" -description = "Class based template tags for Django" -name = "django-classy-tags" -optional = false -python-versions = ">=3.8" -version = "4.1.0" - -[package.dependencies] -django = ">=3.2" - -[[package]] -category = "main" -description = "('Compresses linked and inline JavaScript or CSS into single cached files.',)" -name = "django-compressor" -optional = false -python-versions = "*" -version = "4.4" - -[package.dependencies] -django-appconf = ">=1.0.3" -rcssmin = "1.1.1" -rjsmin = "1.2.1" - -[[package]] -category = "main" -description = "Computed property model fields for Django" -name = "django-computed-property" -optional = false -python-versions = "*" -version = "0.3.0" - -[package.dependencies] -Django = ">=1.8.2" -six = ">=1.11.0" - -[[package]] -category = "main" -description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)." -name = "django-cors-headers" -optional = false -python-versions = ">=3.8" -version = "4.2.0" - -[package.dependencies] -Django = ">=3.2" - -[[package]] -category = "main" -description = "A configurable set of panels that display various debug information about the current request/response." -name = "django-debug-toolbar" -optional = false -python-versions = ">=3.8" -version = "4.1.0" - -[package.dependencies] -django = ">=3.2.4" -sqlparse = ">=0.2" - -[[package]] -category = "main" -description = "Extensions for Django" -name = "django-extensions" -optional = false -python-versions = ">=3.6" -version = "3.2.3" - -[package.dependencies] -Django = ">=3.2" - -[[package]] -category = "main" -description = "Pluggable app for handling webpage meta tags and OpenGraph properties" -name = "django-meta" -optional = false -python-versions = ">=3.7" -version = "2.2.0" - -[package.extras] -docs = ["django (<5.0)"] - -[[package]] -category = "main" -description = "Django model mixins and utilities" -name = "django-model-utils" -optional = false -python-versions = ">=3.7" -version = "4.3.1" - -[package.dependencies] -Django = ">=3.2" - -[[package]] -category = "main" -description = "A pluggable framework for adding two-factor authentication to Django using one-time passwords." -name = "django-otp" -optional = false -python-versions = ">=3.7" -version = "1.2.2" - -[package.dependencies] -django = ">=3.2" - -[package.extras] -qrcode = ["qrcode"] - -[[package]] -category = "main" -description = "Integration of django-otp and django-agent-trust." -name = "django-otp-agents" -optional = false -python-versions = "*" -version = "1.0.1" - -[package.dependencies] -django-agent-trust = ">=1.0.1" -django-otp = ">=1.0.1" - -[[package]] -category = "main" -description = "PubSubHubbub (PuSH) support for Django" -name = "django-push" -optional = false -python-versions = "*" -version = "1.1" - -[package.dependencies] -Django = "*" -requests = "*" - -[[package]] -category = "main" -description = "A Django field that automatically generates random slugs." -name = "django-randomslugfield" -optional = false -python-versions = "*" -version = "0.3.0" - -[[package]] -category = "main" -description = "Full featured redis cache backend for Django." -name = "django-redis" -optional = false -python-versions = ">=3.6" -version = "5.3.0" - -[package.dependencies] -Django = ">=3.2" -redis = ">=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1" - -[package.extras] -hiredis = ["redis (>=3,<4.0.0 || >4.0.0,<4.0.1 || >4.0.1)"] - -[[package]] -category = "main" -description = "An app that provides django integration for RQ (Redis Queue)" -name = "django-rq" -optional = false -python-versions = "*" -version = "2.8.1" - -[package.dependencies] -django = ">=2.0" -redis = ">=3" -rq = ">=1.14" - -[package.extras] -sentry = ["raven (>=6.1.0)"] -testing = ["mock (>=2.0.0)"] - -[[package]] -category = "main" -description = "Docutils -- Python Documentation Utilities" -name = "docutils" -optional = false -python-versions = ">=3.7" -version = "0.20.1" - -[[package]] -category = "dev" -description = "Backport of PEP 654 (exception groups)" -marker = "python_version < \"3.11\"" -name = "exceptiongroup" -optional = false -python-versions = ">=3.7" -version = "1.1.2" - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -category = "main" -description = "Coroutine-based network library" -name = "gevent" -optional = false -python-versions = ">=3.8" -version = "23.7.0" - -[package.dependencies] -cffi = ">=1.12.2" -"zope.event" = "*" -"zope.interface" = "*" - -[[package.dependencies.greenlet]] -python = "<3.12" -version = ">=2.0.0" - -[[package.dependencies.greenlet]] -python = ">=3.12" -version = ">=3.0a1" - -[package.extras] -dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] -docs = ["sphinx", "furo", "repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] -monitor = ["psutil (>=5.7.0)"] -recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"] -test = ["requests", "objgraph", "setuptools", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "coverage (>=5.0)", "psutil (>=5.7.0)"] - -[[package]] -category = "main" -description = "Lightweight in-process concurrent programming" -marker = "platform_python_implementation == \"CPython\" and python_version < \"3.12\"" -name = "greenlet" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -version = "2.0.2" - -[package.extras] -docs = ["sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] - -[[package]] -category = "main" -description = "Lightweight in-process concurrent programming" -marker = "platform_python_implementation == \"CPython\" and python_version >= \"3.12\"" -name = "greenlet" -optional = false -python-versions = ">=3.7" -version = "3.0.0a1" - -[package.extras] -docs = ["sphinx"] -test = ["objgraph", "psutil"] - -[[package]] -category = "main" -description = "WSGI HTTP Server for UNIX" -name = "gunicorn" -optional = false -python-versions = ">=3.5" -version = "21.2.0" - -[package.dependencies] -packaging = "*" - -[package.dependencies.gevent] -optional = true -version = ">=1.4.0" - -[package.extras] -eventlet = ["eventlet (>=0.24.1)"] -gevent = ["gevent (>=1.4.0)"] -setproctitle = ["setproctitle"] -tornado = ["tornado (>=0.2)"] - -[[package]] -category = "main" -description = "Python wrapper for hiredis" -name = "hiredis" -optional = false -python-versions = ">=3.7" -version = "2.2.3" - -[[package]] -category = "main" -description = "HTML parser based on the WHATWG HTML specification" -name = "html5lib" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "1.1" - -[package.dependencies] -six = ">=1.9" -webencodings = "*" - -[package.extras] -all = ["genshi", "chardet (>=2.2)", "lxml"] -chardet = ["chardet (>=2.2)"] -genshi = ["genshi"] -lxml = ["lxml"] - -[[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" -name = "idna" -optional = false -python-versions = ">=3.5" -version = "3.4" - -[[package]] -category = "main" -description = "Read metadata from Python packages" -marker = "python_version < \"3.10\"" -name = "importlib-metadata" -optional = false -python-versions = ">=3.8" -version = "6.8.0" - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] -perf = ["ipython"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ruff", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] - -[[package]] -category = "dev" -description = "brain-dead simple config-ini parsing" -name = "iniconfig" -optional = false -python-versions = ">=3.7" -version = "2.0.0" - -[[package]] -category = "dev" -description = "An autocompletion tool for Python that can be used for text editors." -name = "jedi" -optional = false -python-versions = ">=3.6" -version = "0.19.0" - -[package.dependencies] -parso = ">=0.8.3,<0.9.0" - -[package.extras] -docs = ["Jinja2 (2.11.3)", "MarkupSafe (1.1.1)", "Pygments (2.8.1)", "alabaster (0.7.12)", "babel (2.9.1)", "chardet (4.0.0)", "commonmark (0.8.1)", "docutils (0.17.1)", "future (0.18.2)", "idna (2.10)", "imagesize (1.2.0)", "mock (1.0.1)", "packaging (20.9)", "pyparsing (2.4.7)", "pytz (2021.1)", "readthedocs-sphinx-ext (2.1.4)", "recommonmark (0.5.0)", "requests (2.25.1)", "six (1.15.0)", "snowballstemmer (2.1.0)", "sphinx-rtd-theme (0.4.3)", "sphinx (1.8.5)", "sphinxcontrib-serializinghtml (1.1.4)", "sphinxcontrib-websupport (1.2.4)", "urllib3 (1.26.4)"] -qa = ["flake8 (5.0.4)", "mypy (0.971)", "types-setuptools (67.2.0.1)"] -testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] - -[[package]] -category = "main" -description = "A very fast and expressive template engine." -name = "jinja2" -optional = false -python-versions = ">=3.7" -version = "3.1.2" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -category = "main" -description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -name = "lxml" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" -version = "4.9.3" - -[package.extras] -cssselect = ["cssselect (>=0.7)"] -html5 = ["html5lib"] -htmlsoup = ["beautifulsoup4"] -source = ["Cython (>=0.29.35)"] - -[[package]] -category = "main" -description = "Python implementation of John Gruber's Markdown." -name = "markdown" -optional = false -python-versions = ">=3.7" -version = "3.4.4" - -[package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.10" -version = ">=4.4" - -[package.extras] -docs = ["mkdocs (>=1.0)", "mkdocs-nature (>=0.4)", "mdx-gh-links (>=0.2)"] -testing = ["coverage", "pyyaml"] - -[[package]] -category = "main" -description = "Safely add untrusted strings to HTML/XML markup." -name = "markupsafe" -optional = false -python-versions = ">=3.7" -version = "2.1.3" - -[[package]] -category = "main" -description = "Python Microformats2 parser" -name = "mf2py" -optional = false -python-versions = ">=2.7" -version = "1.1.3" - -[package.dependencies] -BeautifulSoup4 = ">=4.6.0" -html5lib = ">=1.0.1" -requests = ">=2.18.4" - -[[package]] -category = "main" -description = "Python Microformats2 utilities, a companion to mf2py" -name = "mf2util" -optional = false -python-versions = "*" -version = "0.5.2" - -[[package]] -category = "main" -description = "MessagePack serializer" -name = "msgpack" -optional = false -python-versions = "*" -version = "1.0.5" - -[[package]] -category = "dev" -description = "Optional static typing for Python" -name = "mypy" -optional = false -python-versions = ">=3.7" -version = "1.4.1" - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.dependencies.tomli] -python = "<3.11" -version = ">=1.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] -reports = ["lxml"] - -[[package]] -category = "dev" -description = "Type system extensions for programs checked with the mypy type checker." -name = "mypy-extensions" -optional = false -python-versions = ">=3.5" -version = "1.0.0" - -[[package]] -category = "main" -description = "Core utilities for Python packages" -name = "packaging" -optional = false -python-versions = ">=3.7" -version = "23.1" - -[[package]] -category = "dev" -description = "A Python Parser" -name = "parso" -optional = false -python-versions = ">=3.6" -version = "0.8.3" - -[package.extras] -qa = ["flake8 (3.8.3)", "mypy (0.782)"] -testing = ["docopt", "pytest (<6.0.0)"] - -[[package]] -category = "main" -description = "Python Imaging Library (Fork)" -name = "pillow" -optional = false -python-versions = ">=3.7" -version = "9.5.0" - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] - -[[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" -name = "pluggy" -optional = false -python-versions = ">=3.7" -version = "1.2.0" - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -category = "dev" -description = "Library for building powerful interactive command lines in Python" -name = "prompt-toolkit" -optional = false -python-versions = ">=3.7.0" -version = "3.0.39" - -[package.dependencies] -wcwidth = "*" - -[[package]] -category = "main" -description = "psycopg2 - Python-PostgreSQL Database Adapter" -name = "psycopg2-binary" -optional = false -python-versions = ">=3.6" -version = "2.9.7" - -[[package]] -category = "dev" -description = "Python REPL build on top of prompt_toolkit" -name = "ptpython" -optional = false -python-versions = ">=3.7" -version = "3.0.23" - -[package.dependencies] -appdirs = "*" -jedi = ">=0.16.0" -prompt-toolkit = ">=3.0.28,<3.1.0" -pygments = "*" - -[package.extras] -all = ["black"] -ptipython = ["ipython"] - -[[package]] -category = "main" -description = "C parser in Python" -name = "pycparser" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.21" - -[[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." -name = "pygments" -optional = false -python-versions = ">=3.7" -version = "2.16.1" - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -category = "main" -description = "Pure Python library for saving and loading PNG images" -name = "pypng" -optional = false -python-versions = "*" -version = "0.20220715.0" - -[[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" -name = "pytest" -optional = false -python-versions = ">=3.7" -version = "7.4.0" - -[package.dependencies] -colorama = "*" -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" - -[package.dependencies.exceptiongroup] -python = "<3.11" -version = ">=1.0.0rc8" - -[package.dependencies.tomli] -python = "<3.11" -version = ">=1.0.0" - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -category = "dev" -description = "A Django plugin for pytest." -name = "pytest-django" -optional = false -python-versions = ">=3.5" -version = "4.5.2" - -[package.dependencies] -pytest = ">=5.4.0" - -[package.extras] -docs = ["sphinx", "sphinx-rtd-theme"] -testing = ["django", "django-configurations (>=2.0)"] - -[[package]] -category = "main" -description = "Convert numbers from base 10 integers to base X strings and back again." -name = "python-baseconv" -optional = false -python-versions = "*" -version = "1.2.2" - -[[package]] -category = "main" -description = "File type identification using libmagic" -name = "python-magic" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.27" - -[[package]] -category = "main" -description = "A Python slugify application that also handles Unicode" -name = "python-slugify" -optional = false -python-versions = ">=3.7" -version = "8.0.1" - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - -[[package]] -category = "main" -description = "World timezone definitions, modern and historical" -name = "pytz" -optional = false -python-versions = "*" -version = "2023.3" - -[[package]] -category = "main" -description = "pyup-django checks your installed dependencies for known security vulnerabilities and displays them in the admin area." -name = "pyup-django" -optional = false -python-versions = "*" -version = "0.4.0" - -[package.dependencies] -packaging = "*" -requests = "*" - -[[package]] -category = "main" -description = "YAML parser and emitter for Python" -name = "pyyaml" -optional = false -python-versions = ">=3.6" -version = "6.0.1" - -[[package]] -category = "main" -description = "QR Code image generator" -name = "qrcode" -optional = false -python-versions = ">=3.7" -version = "7.4.2" - -[package.dependencies] -colorama = "*" -pypng = "*" -typing-extensions = "*" - -[package.extras] -all = ["zest.releaser", "tox", "pytest", "pytest-cov", "pillow (>=9.1.0)"] -dev = ["tox", "pytest", "pytest-cov"] -maintainer = ["zest.releaser"] -pil = ["pillow (>=9.1.0)"] -test = ["coverage", "pytest"] - -[[package]] -category = "main" -description = "CSS Minifier" -name = "rcssmin" -optional = false -python-versions = "*" -version = "1.1.1" - -[[package]] -category = "main" -description = "Python client for Redis database and key-value store" -name = "redis" -optional = false -python-versions = ">=3.7" -version = "4.6.0" - -[package.dependencies] -[package.dependencies.async-timeout] -python = "<=3.11.2" -version = ">=4.0.2" - -[package.extras] -hiredis = ["hiredis (>=1.0.0)"] -ocsp = ["cryptography (>=36.0.1)", "pyopenssl (20.0.1)", "requests (>=2.26.0)"] - -[[package]] -category = "main" -description = "Python HTTP for Humans." -name = "requests" -optional = false -python-versions = ">=3.7" -version = "2.31.0" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -category = "main" -description = "Javascript Minifier" -name = "rjsmin" -optional = false -python-versions = "*" -version = "1.2.1" - -[[package]] -category = "main" -description = "\"Webmention Manager\"" -name = "ronkyuu" -optional = false -python-versions = ">=3.9" -version = "0.9" - -[[package]] -category = "main" -description = "RQ is a simple, lightweight, library for creating background jobs, and processing them." -name = "rq" -optional = false -python-versions = ">=3.6" -version = "1.15.1" - -[package.dependencies] -click = ">=5.0.0" -redis = ">=4.0.0" - -[[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" -name = "six" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.16.0" - -[[package]] -category = "main" -description = "A modern CSS selector implementation for Beautiful Soup." -name = "soupsieve" -optional = false -python-versions = ">=3.7" -version = "2.4.1" - -[[package]] -category = "main" -description = "A non-validating SQL parser." -name = "sqlparse" -optional = false -python-versions = ">=3.5" -version = "0.4.4" - -[package.extras] -dev = ["flake8", "build"] -doc = ["sphinx"] -test = ["pytest", "pytest-cov"] - -[[package]] -category = "main" -description = "The most basic Text::Unidecode port" -name = "text-unidecode" -optional = false -python-versions = "*" -version = "1.3" - -[[package]] -category = "dev" -description = "A lil' TOML parser" -marker = "python_version < \"3.11\"" -name = "tomli" -optional = false -python-versions = ">=3.7" -version = "2.0.1" - -[[package]] -category = "dev" -description = "Typing stubs for bleach" -name = "types-bleach" -optional = false -python-versions = "*" -version = "6.0.0.4" - -[[package]] -category = "dev" -description = "Typing stubs for Markdown" -name = "types-markdown" -optional = false -python-versions = "*" -version = "3.4.2.10" - -[[package]] -category = "dev" -description = "Typing stubs for python-slugify" -name = "types-python-slugify" -optional = false -python-versions = "*" -version = "8.0.0.3" - -[[package]] -category = "dev" -description = "Typing stubs for PyYAML" -name = "types-pyyaml" -optional = false -python-versions = "*" -version = "6.0.12.11" - -[[package]] -category = "dev" -description = "Typing stubs for requests" -name = "types-requests" -optional = false -python-versions = "*" -version = "2.31.0.2" - -[package.dependencies] -types-urllib3 = "*" - -[[package]] -category = "dev" -description = "Typing stubs for urllib3" -name = "types-urllib3" -optional = false -python-versions = "*" -version = "1.26.25.14" - -[[package]] -category = "main" -description = "Backported and Experimental Type Hints for Python 3.7+" -name = "typing-extensions" -optional = false -python-versions = ">=3.7" -version = "4.7.1" - -[[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." -name = "urllib3" -optional = false -python-versions = ">=3.7" -version = "2.0.4" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] -socks = ["pysocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -category = "dev" -description = "Filesystem events monitoring" -name = "watchdog" -optional = false -python-versions = ">=3.7" -version = "3.0.0" - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" -name = "wcwidth" -optional = false -python-versions = "*" -version = "0.2.6" - -[[package]] -category = "main" -description = "Character encoding aliases for legacy web content" -name = "webencodings" -optional = false -python-versions = "*" -version = "0.5.1" - -[[package]] -category = "dev" -description = "The comprehensive WSGI web application library." -name = "werkzeug" -optional = false -python-versions = ">=3.8" -version = "2.3.6" - -[package.dependencies] -MarkupSafe = ">=2.1.1" - -[package.extras] -watchdog = ["watchdog (>=2.3)"] - -[[package]] -category = "main" -description = "Package for serializing and deserializing of XRD documents" -name = "xrd" -optional = false -python-versions = "*" -version = "0.1" - -[[package]] -category = "main" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.10\"" -name = "zipp" -optional = false -python-versions = ">=3.8" -version = "3.16.2" - -[package.extras] -docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9.3)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ruff", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-ignore-flaky", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] - -[[package]] -category = "main" -description = "Very basic event publishing system" -name = "zope.event" -optional = false -python-versions = ">=3.7" -version = "5.0" - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["sphinx"] -test = ["zope.testrunner"] - -[[package]] -category = "main" -description = "Interfaces for Python" -name = "zope.interface" -optional = false -python-versions = ">=3.7" -version = "6.0" - -[package.dependencies] -setuptools = "*" - -[package.extras] -docs = ["sphinx", "repoze.sphinx.autointerface"] -test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] -testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] - -[metadata] -content-hash = "b55f2c5ce8c55037eb76c7a9a9b3fc428b23a79fd614a3606bd8161d4451fe4f" -python-versions = "^3.9" - -[metadata.files] -accept-types = [] -ago = [] -appdirs = [] -argon2-cffi = [] -argon2-cffi-bindings = [] -asgiref = [] -async-timeout = [] -beautifulsoup4 = [] -bleach = [] -cachecontrol = [] -certifi = [] -cffi = [] -charset-normalizer = [] -click = [] -colorama = [] -django = [] -django-activeurl = [] -django-agent-trust = [] -django-analytical = [] -django-annoying = [] -django-appconf = [] -django-classy-tags = [] -django-compressor = [] -django-computed-property = [] -django-cors-headers = [] -django-debug-toolbar = [] -django-extensions = [] -django-meta = [] -django-model-utils = [] -django-otp = [] -django-otp-agents = [] -django-push = [] -django-randomslugfield = [] -django-redis = [] -django-rq = [] -docutils = [] -exceptiongroup = [] -gevent = [] -greenlet = [] -gunicorn = [] -hiredis = [] -html5lib = [] -idna = [] -importlib-metadata = [] -iniconfig = [] -jedi = [] -jinja2 = [] -lxml = [] -markdown = [] -markupsafe = [] -mf2py = [] -mf2util = [] -msgpack = [] -mypy = [] -mypy-extensions = [] -packaging = [] -parso = [] -pillow = [] -pluggy = [] -prompt-toolkit = [] -psycopg2-binary = [] -ptpython = [] -pycparser = [] -pygments = [] -pypng = [] -pytest = [] -pytest-django = [] -python-baseconv = [] -python-magic = [] -python-slugify = [] -pytz = [] -pyup-django = [] -pyyaml = [] -qrcode = [] -rcssmin = [] -redis = [] -requests = [] -rjsmin = [] -ronkyuu = [] -rq = [] -six = [] -soupsieve = [] -sqlparse = [] -text-unidecode = [] -tomli = [] -types-bleach = [] -types-markdown = [] -types-python-slugify = [] -types-pyyaml = [] -types-requests = [] -types-urllib3 = [] -typing-extensions = [] -urllib3 = [] -watchdog = [] -wcwidth = [] -webencodings = [] -werkzeug = [] -xrd = [] -zipp = [] -"zope.event" = [] -"zope.interface" = [] diff --git a/users/admin.py b/users/admin.py index 6cd53dd..c5f11c4 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,14 +1,14 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin -from .models import PgpKey, Profile, Site, User +from .models import Key, Profile, Site, User class SiteAdmin(admin.ModelAdmin): - list_display = ("name", "icon", "domain", "url_template") + list_display = ('name', 'icon', 'domain', 'url_template') -class PgpKeyInline(admin.TabularInline): - model = PgpKey +class KeyInline(admin.TabularInline): + model = Key extra = 1 @@ -19,10 +19,10 @@ class ProfileInline(admin.TabularInline): class UserAdmin(BaseUserAdmin): fieldsets = BaseUserAdmin.fieldsets + ( - ("Profile", {"fields": ("avatar", "xmpp", "note")}), + ('Profile', {'fields': ('avatar', 'xmpp', 'note')}), ) inlines = ( - PgpKeyInline, + KeyInline, ProfileInline, ) diff --git a/users/apps.py b/users/apps.py index 2ffced1..54e430e 100644 --- a/users/apps.py +++ b/users/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class UsersConfig(AppConfig): - name = "users" - verbose_name = "Users and Profiles" + name = 'users' + verbose_name = 'Users and Profiles' diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py index ba895d4..ffec4b2 100644 --- a/users/migrations/0001_initial.py +++ b/users/migrations/0001_initial.py @@ -9,127 +9,40 @@ import django.utils.timezone class Migration(migrations.Migration): + initial = True dependencies = [ - ("auth", "0008_alter_user_username_max_length"), + ('auth', '0008_alter_user_username_max_length'), ] operations = [ migrations.CreateModel( - name="User", + name='User', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("password", models.CharField(max_length=128, verbose_name="password")), - ( - "last_login", - models.DateTimeField( - blank=True, null=True, verbose_name="last login" - ), - ), - ( - "is_superuser", - models.BooleanField( - default=False, - help_text="Designates that this user has all permissions without explicitly assigning them.", - verbose_name="superuser status", - ), - ), - ( - "username", - models.CharField( - error_messages={ - "unique": "A user with that username already exists." - }, - help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", - max_length=150, - unique=True, - validators=[ - django.contrib.auth.validators.UnicodeUsernameValidator() - ], - verbose_name="username", - ), - ), - ( - "first_name", - models.CharField( - blank=True, max_length=30, verbose_name="first name" - ), - ), - ( - "last_name", - models.CharField( - blank=True, max_length=30, verbose_name="last name" - ), - ), - ( - "email", - models.EmailField( - blank=True, max_length=254, verbose_name="email address" - ), - ), - ( - "is_staff", - models.BooleanField( - default=False, - help_text="Designates whether the user can log into this admin site.", - verbose_name="staff status", - ), - ), - ( - "is_active", - models.BooleanField( - default=True, - help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", - verbose_name="active", - ), - ), - ( - "date_joined", - models.DateTimeField( - default=django.utils.timezone.now, verbose_name="date joined" - ), - ), - ("avatar", models.ImageField(upload_to="")), - ("note", models.TextField(blank=True)), - ( - "groups", - models.ManyToManyField( - blank=True, - help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", - related_name="user_set", - related_query_name="user", - to="auth.Group", - verbose_name="groups", - ), - ), - ( - "user_permissions", - models.ManyToManyField( - blank=True, - help_text="Specific permissions for this user.", - related_name="user_set", - related_query_name="user", - to="auth.Permission", - verbose_name="user permissions", - ), - ), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), + ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), + ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')), + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('avatar', models.ImageField(upload_to='')), + ('note', models.TextField(blank=True)), + ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), + ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ], options={ - "verbose_name": "user", - "verbose_name_plural": "users", - "abstract": False, + 'verbose_name': 'user', + 'verbose_name_plural': 'users', + 'abstract': False, }, managers=[ - ("objects", django.contrib.auth.models.UserManager()), + ('objects', django.contrib.auth.models.UserManager()), ], ), ] diff --git a/users/migrations/0002_auto_20171023_0109.py b/users/migrations/0002_auto_20171023_0109.py index 27fbb8f..8f521a7 100644 --- a/users/migrations/0002_auto_20171023_0109.py +++ b/users/migrations/0002_auto_20171023_0109.py @@ -7,14 +7,15 @@ import users.models class Migration(migrations.Migration): + dependencies = [ - ("users", "0001_initial"), + ('users', '0001_initial'), ] operations = [ migrations.AlterField( - model_name="user", - name="avatar", + model_name='user', + name='avatar', field=models.ImageField(upload_to=users.models.avatar_path), ), ] diff --git a/users/migrations/0003_key.py b/users/migrations/0003_key.py index 168676e..c8e564a 100644 --- a/users/migrations/0003_key.py +++ b/users/migrations/0003_key.py @@ -8,33 +8,19 @@ import django.db.models.deletion class Migration(migrations.Migration): + dependencies = [ - ("users", "0002_auto_20171023_0109"), + ('users', '0002_auto_20171023_0109'), ] operations = [ migrations.CreateModel( - name="Key", + name='Key', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("fingerprint", models.CharField(max_length=40)), - ("file", models.FileField(upload_to="keys")), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="keys", - to=settings.AUTH_USER_MODEL, - ), - ), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('fingerprint', models.CharField(max_length=40)), + ('file', models.FileField(upload_to='keys')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='keys', to=settings.AUTH_USER_MODEL)), ], ), ] diff --git a/users/migrations/0004_auto_20171023_0143.py b/users/migrations/0004_auto_20171023_0143.py index f895b37..6290295 100644 --- a/users/migrations/0004_auto_20171023_0143.py +++ b/users/migrations/0004_auto_20171023_0143.py @@ -8,67 +8,48 @@ import django.db.models.deletion class Migration(migrations.Migration): + dependencies = [ - ("users", "0003_key"), + ('users', '0003_key'), ] operations = [ migrations.CreateModel( - name="Profile", + name='Profile', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("username", models.CharField(max_length=100)), - ("display_name", models.CharField(blank=True, max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('username', models.CharField(max_length=100)), + ('display_name', models.CharField(blank=True, max_length=100)), ], options={ - "ordering": ("site", "username"), + 'ordering': ('site', 'username'), }, ), migrations.CreateModel( - name="Site", + name='Site', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=100, unique=True)), - ("icon", models.CharField(max_length=100)), - ("url", models.CharField(max_length=100)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ('icon', models.CharField(max_length=100)), + ('url', models.CharField(max_length=100)), ], options={ - "ordering": ("name",), + 'ordering': ('name',), }, ), migrations.AddField( - model_name="profile", - name="site", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to="users.Site" - ), + model_name='profile', + name='site', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Site'), ), migrations.AddField( - model_name="profile", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL - ), + model_name='profile', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), ), migrations.AddField( - model_name="user", - name="profiles", - field=models.ManyToManyField(through="users.Profile", to="users.Site"), + model_name='user', + name='profiles', + field=models.ManyToManyField(through='users.Profile', to='users.Site'), ), ] diff --git a/users/migrations/0005_auto_20171023_0158.py b/users/migrations/0005_auto_20171023_0158.py index edf267e..4f95d75 100644 --- a/users/migrations/0005_auto_20171023_0158.py +++ b/users/migrations/0005_auto_20171023_0158.py @@ -8,22 +8,19 @@ import django.db.models.deletion class Migration(migrations.Migration): + dependencies = [ - ("users", "0004_auto_20171023_0143"), + ('users', '0004_auto_20171023_0143'), ] operations = [ migrations.RemoveField( - model_name="user", - name="profiles", + model_name='user', + name='profiles', ), migrations.AlterField( - model_name="profile", - name="user", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="profiles", - to=settings.AUTH_USER_MODEL, - ), + model_name='profile', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='profiles', to=settings.AUTH_USER_MODEL), ), ] diff --git a/users/migrations/0006_auto_20171031_1336.py b/users/migrations/0006_auto_20171031_1336.py index ef9e1cb..1cf8516 100644 --- a/users/migrations/0006_auto_20171031_1336.py +++ b/users/migrations/0006_auto_20171031_1336.py @@ -6,20 +6,21 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("users", "0005_auto_20171023_0158"), + ('users', '0005_auto_20171023_0158'), ] operations = [ migrations.RenameField( - model_name="site", - old_name="url", - new_name="url_template", + model_name='site', + old_name='url', + new_name='url_template', ), migrations.AddField( - model_name="site", - name="domain", - field=models.CharField(default="", max_length=100), + model_name='site', + name='domain', + field=models.CharField(default='', max_length=100), preserve_default=False, ), ] diff --git a/users/migrations/0007_auto_20171031_1347.py b/users/migrations/0007_auto_20171031_1347.py index 8c132e8..a28bb94 100644 --- a/users/migrations/0007_auto_20171031_1347.py +++ b/users/migrations/0007_auto_20171031_1347.py @@ -6,14 +6,15 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("users", "0006_auto_20171031_1336"), + ('users', '0006_auto_20171031_1336'), ] operations = [ migrations.AlterField( - model_name="site", - name="domain", + model_name='site', + name='domain', field=models.CharField(blank=True, max_length=100), ), ] diff --git a/users/migrations/0008_auto_20171031_1357.py b/users/migrations/0008_auto_20171031_1357.py index 392d09f..eadb686 100644 --- a/users/migrations/0008_auto_20171031_1357.py +++ b/users/migrations/0008_auto_20171031_1357.py @@ -6,13 +6,14 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("users", "0007_auto_20171031_1347"), + ('users', '0007_auto_20171031_1347'), ] operations = [ migrations.AlterModelOptions( - name="site", - options={"ordering": ("domain",)}, + name='site', + options={'ordering': ('domain',)}, ), ] diff --git a/users/migrations/0009_user_xmpp.py b/users/migrations/0009_user_xmpp.py index 7c707f4..b554f08 100644 --- a/users/migrations/0009_user_xmpp.py +++ b/users/migrations/0009_user_xmpp.py @@ -6,14 +6,15 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("users", "0008_auto_20171031_1357"), + ('users', '0008_auto_20171031_1357'), ] operations = [ migrations.AddField( - model_name="user", - name="xmpp", + model_name='user', + name='xmpp', field=models.EmailField(blank=True, max_length=254), ), ] diff --git a/users/migrations/0010_auto_20171206_2211.py b/users/migrations/0010_auto_20171206_2211.py index 6d982b9..04a0819 100644 --- a/users/migrations/0010_auto_20171206_2211.py +++ b/users/migrations/0010_auto_20171206_2211.py @@ -6,13 +6,14 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("users", "0009_user_xmpp"), + ('users', '0009_user_xmpp'), ] operations = [ migrations.AlterModelOptions( - name="site", - options={"ordering": ("name",)}, + name='site', + options={'ordering': ('name',)}, ), ] diff --git a/users/migrations/0011_auto_20180124_1311.py b/users/migrations/0011_auto_20180124_1311.py index 65a1661..20d91e3 100644 --- a/users/migrations/0011_auto_20180124_1311.py +++ b/users/migrations/0011_auto_20180124_1311.py @@ -6,13 +6,15 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("users", "0010_auto_20171206_2211"), + ('users', '0010_auto_20171206_2211'), ] operations = [ migrations.AlterModelManagers( - name="user", - managers=[], + name='user', + managers=[ + ], ), ] diff --git a/users/migrations/0012_auto_20180129_1614.py b/users/migrations/0012_auto_20180129_1614.py index 364642f..f0f4a91 100644 --- a/users/migrations/0012_auto_20180129_1614.py +++ b/users/migrations/0012_auto_20180129_1614.py @@ -7,15 +7,16 @@ import users.models class Migration(migrations.Migration): + dependencies = [ - ("users", "0011_auto_20180124_1311"), + ('users', '0011_auto_20180124_1311'), ] operations = [ migrations.AlterModelManagers( - name="user", + name='user', managers=[ - ("objects", users.models.UserManager()), + ('objects', users.models.UserManager()), ], ), ] diff --git a/users/migrations/0013_auto_20180323_1200.py b/users/migrations/0013_auto_20180323_1200.py index e0f1576..953b271 100644 --- a/users/migrations/0013_auto_20180323_1200.py +++ b/users/migrations/0013_auto_20180323_1200.py @@ -5,31 +5,20 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("users", "0012_auto_20180129_1614"), + ('users', '0012_auto_20180129_1614'), ] operations = [ migrations.AddField( - model_name="user", - name="email_md5", - field=computed_property.fields.ComputedCharField( - compute_from="calc_email_md5", - default="", - editable=False, - max_length=32, - unique=True, - ), + model_name='user', + name='email_md5', + field=computed_property.fields.ComputedCharField(compute_from='calc_email_md5', default='', editable=False, max_length=32, unique=True), ), migrations.AddField( - model_name="user", - name="email_sha256", - field=computed_property.fields.ComputedCharField( - compute_from="calc_email_sha256", - default="", - editable=False, - max_length=64, - unique=True, - ), + model_name='user', + name='email_sha256', + field=computed_property.fields.ComputedCharField(compute_from='calc_email_sha256', default='', editable=False, max_length=64, unique=True), ), ] diff --git a/users/migrations/0014_auto_20180711_1248.py b/users/migrations/0014_auto_20180711_1248.py index 4d846e5..2975e47 100644 --- a/users/migrations/0014_auto_20180711_1248.py +++ b/users/migrations/0014_auto_20180711_1248.py @@ -6,79 +6,58 @@ import users.models class Migration(migrations.Migration): + dependencies = [ - ("users", "0013_auto_20180323_1200"), + ('users', '0013_auto_20180323_1200'), ] operations = [ migrations.AlterField( - model_name="profile", - name="display_name", + model_name='profile', + name='display_name', field=models.CharField( - blank=True, - help_text="overrides the username for display - useful for sites that use ugly IDs", - max_length=100, - ), + blank=True, help_text='overrides the username for display - useful for sites that use ugly IDs', max_length=100), ), migrations.AlterField( - model_name="profile", - name="username", + model_name='profile', + name='username', field=models.CharField( - help_text="the user's actual handle or ID on the remote site", - max_length=100, - ), + help_text="the user's actual handle or ID on the remote site", max_length=100), ), migrations.AlterField( - model_name="user", - name="avatar", + model_name='user', + name='avatar', field=models.ImageField( - help_text="an avatar or photo that represents this user", - upload_to=users.models.avatar_path, - ), + help_text='an avatar or photo that represents this user', upload_to=users.models.avatar_path), ), migrations.AlterField( - model_name="user", - name="email_md5", + model_name='user', + name='email_md5', field=computed_property.fields.ComputedCharField( - compute_from="calc_email_md5", - editable=False, - help_text="MD5 hash of the user's email, used for Libravatar", - max_length=32, - unique=True, - ), + compute_from='calc_email_md5', editable=False, help_text="MD5 hash of the user's email, used for Libravatar", max_length=32, unique=True), ), migrations.AlterField( - model_name="user", - name="email_sha256", + model_name='user', + name='email_sha256', field=computed_property.fields.ComputedCharField( - compute_from="calc_email_sha256", - editable=False, - help_text="SHA-256 hash of the user's email, used for Libravatar", - max_length=64, - unique=True, - ), + compute_from='calc_email_sha256', editable=False, help_text="SHA-256 hash of the user's email, used for Libravatar", max_length=64, unique=True), ), migrations.AlterField( - model_name="user", - name="last_name", + model_name='user', + name='last_name', field=models.CharField( - blank=True, max_length=150, verbose_name="last name" - ), + blank=True, max_length=150, verbose_name='last name'), ), migrations.AlterField( - model_name="user", - name="note", + model_name='user', + name='note', field=models.TextField( - blank=True, help_text="a bio or short description provided by the user" - ), + blank=True, help_text='a bio or short description provided by the user'), ), migrations.AlterField( - model_name="user", - name="xmpp", + model_name='user', + name='xmpp', field=models.EmailField( - blank=True, - help_text="an XMPP address through which the user may be reached", - max_length=254, - ), + blank=True, help_text='an XMPP address through which the user may be reached', max_length=254), ), ] diff --git a/users/migrations/0015_user_openid_sha256.py b/users/migrations/0015_user_openid_sha256.py index 2cd48a0..3070f11 100644 --- a/users/migrations/0015_user_openid_sha256.py +++ b/users/migrations/0015_user_openid_sha256.py @@ -5,22 +5,17 @@ from django.db import migrations class Migration(migrations.Migration): + dependencies = [ - ("users", "0014_auto_20180711_1248"), + ('users', '0014_auto_20180711_1248'), ] operations = [ migrations.AddField( - model_name="user", - name="openid_sha256", - field=computed_property.fields.ComputedCharField( - compute_from="calc_openid_sha256", - default="", - editable=False, - help_text="SHA-256 hash of the user's OpenID URL, used for Libravatar", - max_length=64, - unique=True, - ), + model_name='user', + name='openid_sha256', + field=computed_property.fields.ComputedCharField(compute_from='calc_openid_sha256', default='', editable=False, + help_text="SHA-256 hash of the user's OpenID URL, used for Libravatar", max_length=64, unique=True), preserve_default=False, ), ] diff --git a/users/migrations/0016_alter_user_first_name.py b/users/migrations/0016_alter_user_first_name.py index f3eccfa..f5375ae 100644 --- a/users/migrations/0016_alter_user_first_name.py +++ b/users/migrations/0016_alter_user_first_name.py @@ -4,16 +4,19 @@ from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ - ("users", "0015_user_openid_sha256"), + ('users', '0015_user_openid_sha256'), ] operations = [ migrations.AlterField( - model_name="user", - name="first_name", + model_name='user', + name='first_name', field=models.CharField( - blank=True, max_length=150, verbose_name="first name" + blank=True, + max_length=150, + verbose_name='first name' ), ), ] diff --git a/users/migrations/0017_rename_key_pgpkey.py b/users/migrations/0017_rename_key_pgpkey.py deleted file mode 100644 index 2effa47..0000000 --- a/users/migrations/0017_rename_key_pgpkey.py +++ /dev/null @@ -1,16 +0,0 @@ -# Generated by Django 3.2.20 on 2023-08-10 06:37 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("users", "0016_alter_user_first_name"), - ] - - operations = [ - migrations.RenameModel( - old_name="Key", - new_name="PgpKey", - ), - ] diff --git a/users/models.py b/users/models.py index 9ddac65..f8bfdee 100644 --- a/users/models.py +++ b/users/models.py @@ -10,7 +10,7 @@ from lemoncurry import utils def avatar_path(instance, name): - return "avatars/{id}/{name}".format(id=instance.id, name=name) + return 'avatars/{id}/{name}'.format(id=instance.id, name=name) class Site(models.Model): @@ -19,7 +19,7 @@ class Site(models.Model): domain = models.CharField(max_length=100, blank=True) url_template = models.CharField(max_length=100) - def format(self, username=""): + def format(self, username=''): return self.url_template.format(domain=self.domain, username=username) @property @@ -30,14 +30,12 @@ class Site(models.Model): return self.name class Meta: - ordering = ("name",) + ordering = ('name',) class UserManager(DjangoUserManager): def get_queryset(self): - return ( - super(UserManager, self).get_queryset().prefetch_related("keys", "profiles") - ) + return super(UserManager, self).get_queryset().prefetch_related('keys', 'profiles') class User(ModelMeta, AbstractUser): @@ -46,56 +44,52 @@ class User(ModelMeta, AbstractUser): generated based on all their associated information and may author as many h-entries (:model:`entries.Entry`) as they wish. """ - objects = UserManager() avatar = models.ImageField( - upload_to=avatar_path, help_text="an avatar or photo that represents this user" + upload_to=avatar_path, + help_text='an avatar or photo that represents this user' ) note = models.TextField( - blank=True, help_text="a bio or short description provided by the user" + blank=True, + help_text='a bio or short description provided by the user' ) xmpp = models.EmailField( - blank=True, help_text="an XMPP address through which the user may be reached" + blank=True, + help_text='an XMPP address through which the user may be reached' ) # This is gonna need to change if I ever decide to add multiple-user support ;) - url = "/" + url = '/' email_md5 = ComputedCharField( - compute_from="calc_email_md5", - max_length=32, - unique=True, - help_text="MD5 hash of the user's email, used for Libravatar", + compute_from='calc_email_md5', max_length=32, unique=True, + help_text="MD5 hash of the user's email, used for Libravatar" ) email_sha256 = ComputedCharField( - compute_from="calc_email_sha256", - max_length=64, - unique=True, - help_text="SHA-256 hash of the user's email, used for Libravatar", + compute_from='calc_email_sha256', max_length=64, unique=True, + help_text="SHA-256 hash of the user's email, used for Libravatar" ) openid_sha256 = ComputedCharField( - compute_from="calc_openid_sha256", - max_length=64, - unique=True, - help_text="SHA-256 hash of the user's OpenID URL, used for Libravatar", + compute_from='calc_openid_sha256', max_length=64, unique=True, + help_text="SHA-256 hash of the user's OpenID URL, used for Libravatar" ) @property def calc_email_md5(self): - return md5(self.email.lower().encode("utf-8")).hexdigest() + return md5(self.email.lower().encode('utf-8')).hexdigest() @property def calc_email_sha256(self): - return sha256(self.email.lower().encode("utf-8")).hexdigest() + return sha256(self.email.lower().encode('utf-8')).hexdigest() @property def calc_openid_sha256(self): - return sha256(self.full_url.encode("utf-8")).hexdigest() + return sha256(self.full_url.encode('utf-8')).hexdigest() @property def name(self): - return "{0} {1}".format(self.first_name, self.last_name) + return '{0} {1}'.format(self.first_name, self.last_name) def get_absolute_url(self): return self.absolute_url @@ -106,7 +100,7 @@ class User(ModelMeta, AbstractUser): @property def full_url(self): - base = "https://" + DjangoSite.objects.get_current().domain + base = 'https://' + DjangoSite.objects.get_current().domain return urljoin(base, self.url) @property @@ -120,45 +114,45 @@ class User(ModelMeta, AbstractUser): @cached_property def facebook_id(self): for p in self.profiles.all(): - if p.site.name == "Facebook": + if p.site.name == 'Facebook': return p.username return None @cached_property def twitter_username(self): for p in self.profiles.all(): - if p.site.name == "Twitter": - return "@" + p.username + if p.site.name == 'Twitter': + return '@' + p.username return None @property def json_ld(self): - base = "https://" + DjangoSite.objects.get_current().domain + base = 'https://' + DjangoSite.objects.get_current().domain return { - "@context": "http://schema.org", - "@type": "Person", - "@id": self.full_url, - "url": self.full_url, - "name": self.name, - "email": self.email, - "image": urljoin(base, self.avatar.url), - "givenName": self.first_name, - "familyName": self.last_name, - "sameAs": [profile.url for profile in self.profiles.all()], + '@context': 'http://schema.org', + '@type': 'Person', + '@id': self.full_url, + 'url': self.full_url, + 'name': self.name, + 'email': self.email, + 'image': urljoin(base, self.avatar.url), + 'givenName': self.first_name, + 'familyName': self.last_name, + 'sameAs': [profile.url for profile in self.profiles.all()] } _metadata = { - "image": "avatar_url", - "description": "description", - "og_type": "profile", - "og_profile_id": "facebook_id", - "twitter_creator": "twitter_username", + 'image': 'avatar_url', + 'description': 'description', + 'og_type': 'profile', + 'og_profile_id': 'facebook_id', + 'twitter_creator': 'twitter_username', } class ProfileManager(models.Manager): def get_queryset(self): - return super(ProfileManager, self).get_queryset().select_related("site") + return super(ProfileManager, self).get_queryset().select_related('site') class Profile(models.Model): @@ -169,22 +163,26 @@ class Profile(models.Model): representative h-card. Additionally, :model:`entries.Syndication` is tracked by linking each syndication to a particular profile. """ - objects = ProfileManager() - user = models.ForeignKey(User, related_name="profiles", on_delete=models.CASCADE) + user = models.ForeignKey( + User, + related_name='profiles', + on_delete=models.CASCADE + ) site = models.ForeignKey(Site, on_delete=models.CASCADE) username = models.CharField( - max_length=100, help_text="the user's actual handle or ID on the remote site" + max_length=100, + help_text="the user's actual handle or ID on the remote site" ) display_name = models.CharField( max_length=100, blank=True, - help_text="overrides the username for display - useful for sites that use ugly IDs", + help_text="overrides the username for display - useful for sites that use ugly IDs" ) def __str__(self): if self.site.domain: - return self.name + "@" + self.site.domain + return self.name + '@' + self.site.domain return self.name @property @@ -196,19 +194,22 @@ class Profile(models.Model): return self.site.format(username=self.username) class Meta: - ordering = ("site", "username") + ordering = ('site', 'username') -class PgpKey(models.Model): +class Key(models.Model): """ Represents a PGP key that belongs to a particular :model:`users.User`. Each key will be added to the user's h-card with rel="pgpkey", a format compatible with IndieAuth.com. """ - - user = models.ForeignKey(User, related_name="keys", on_delete=models.CASCADE) + user = models.ForeignKey( + User, + related_name='keys', + on_delete=models.CASCADE + ) fingerprint = models.CharField(max_length=40) - file = models.FileField(upload_to="keys") + file = models.FileField(upload_to='keys') @property def key_id(self): @@ -230,4 +231,4 @@ class PgpKey(models.Model): same way GnuPG does. This can make reading the fingerprint a little friendlier. """ - return " ".join(self.fingerprint[i : i + 4] for i in range(0, 40, 4)) + return " ".join(self.fingerprint[i:i+4] for i in range(0, 40, 4)) diff --git a/users/urls.py b/users/urls.py index ae4ec6b..b790e3a 100644 --- a/users/urls.py +++ b/users/urls.py @@ -2,5 +2,7 @@ from django.urls import re_path from .views import libravatar -app_name = "users" -urlpatterns = (re_path("^avatar/(?P[a-z0-9]+)$", libravatar, name="libravatar"),) +app_name = 'users' +urlpatterns = ( + re_path('^avatar/(?P[a-z0-9]+)$', libravatar, name='libravatar'), +) diff --git a/users/views.py b/users/views.py index cc4dffb..2e700ee 100644 --- a/users/views.py +++ b/users/views.py @@ -8,16 +8,16 @@ from .models import User def try_libravatar_org(hash, get): - url = "https://seccdn.libravatar.org/avatar/" + hash + url = 'https://seccdn.libravatar.org/avatar/' + hash if get: - url += "?" + get.urlencode() + url += '?' + get.urlencode() return HttpResponseRedirect(url) @cache_page(60 * 15) def libravatar(request, hash): g = request.GET - size = g.get("s", g.get("size", 80)) + size = g.get('s', g.get('size', 80)) try: size = int(size) except ValueError: @@ -30,7 +30,7 @@ def libravatar(request, hash): elif len(hash) == 64: where = Q(email_sha256=hash) | Q(openid_sha256=hash) else: - return utils.bad_req("hash must be either md5 or sha256") + return utils.bad_req('hash must be either md5 or sha256') # If the user doesn't exist or lacks an avatar, see if libravatar.org has # one for them - libravatar.org falls back to Gravatar when possible (only @@ -51,6 +51,6 @@ def libravatar(request, hash): im = im.crop((0, 0, natural_size, natural_size)) im = im.resize((size, size), resample=Image.HAMMING) - response = HttpResponse(content_type="image/" + image_type.lower()) + response = HttpResponse(content_type='image/'+image_type.lower()) im.save(response, image_type) return response diff --git a/webmention/apps.py b/webmention/apps.py index a2fee31..4d88f81 100644 --- a/webmention/apps.py +++ b/webmention/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class WebmentionConfig(AppConfig): - name = "webmention" + name = 'webmention' diff --git a/webmention/migrations/0001_initial.py b/webmention/migrations/0001_initial.py index 0c3d9f6..ce76889 100644 --- a/webmention/migrations/0001_initial.py +++ b/webmention/migrations/0001_initial.py @@ -9,71 +9,31 @@ import model_utils.fields class Migration(migrations.Migration): + initial = True dependencies = [ - ("entries", "0011_auto_20171120_1108"), + ('entries', '0011_auto_20171120_1108'), ] operations = [ migrations.CreateModel( - name="Webmention", + name='Webmention', fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created", - model_utils.fields.AutoCreatedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="created", - ), - ), - ( - "modified", - model_utils.fields.AutoLastModifiedField( - default=django.utils.timezone.now, - editable=False, - verbose_name="modified", - ), - ), - ("source", models.CharField(max_length=255)), - ("target", models.CharField(max_length=255)), - ( - "state", - models.CharField( - choices=[ - ("p", "pending"), - ("v", "valid"), - ("i", "invalid"), - ("d", "deleted"), - ], - default="p", - max_length=1, - ), - ), - ( - "entry", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="mentions", - to="entries.Entry", - ), - ), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('source', models.CharField(max_length=255)), + ('target', models.CharField(max_length=255)), + ('state', models.CharField(choices=[('p', 'pending'), ('v', 'valid'), ('i', 'invalid'), ('d', 'deleted')], default='p', max_length=1)), + ('entry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mentions', to='entries.Entry')), ], options={ - "default_related_name": "mentions", + 'default_related_name': 'mentions', }, ), migrations.AlterUniqueTogether( - name="webmention", - unique_together=set([("source", "target")]), + name='webmention', + unique_together=set([('source', 'target')]), ), ] diff --git a/webmention/models.py b/webmention/models.py index beb65b0..47690ab 100644 --- a/webmention/models.py +++ b/webmention/models.py @@ -4,15 +4,15 @@ from model_utils.models import TimeStampedModel class State: - PENDING = "p" - VALID = "v" - INVALID = "i" - DELETED = "d" + PENDING = 'p' + VALID = 'v' + INVALID = 'i' + DELETED = 'd' CHOICES = ( - (PENDING, "pending"), - (VALID, "valid"), - (INVALID, "invalid"), - (DELETED, "deleted"), + (PENDING, 'pending'), + (VALID, 'valid'), + (INVALID, 'invalid'), + (DELETED, 'deleted'), ) @@ -20,8 +20,12 @@ class Webmention(TimeStampedModel): entry = models.ForeignKey(Entry, on_delete=models.CASCADE) source = models.CharField(max_length=255) target = models.CharField(max_length=255) - state = models.CharField(choices=State.CHOICES, default=State.PENDING, max_length=1) + state = models.CharField( + choices=State.CHOICES, + default=State.PENDING, + max_length=1 + ) class Meta: - default_related_name = "mentions" - unique_together = ("source", "target") + default_related_name = 'mentions' + unique_together = ('source', 'target') diff --git a/webmention/urls.py b/webmention/urls.py index 27c01a5..ac307ca 100644 --- a/webmention/urls.py +++ b/webmention/urls.py @@ -1,8 +1,8 @@ from django.urls import path from . import views -app_name = "webmention" +app_name = 'webmention' urlpatterns = ( - path("s", views.accept, name="accept"), - path("s/", views.status, name="status"), + path('s', views.accept, name='accept'), + path('s/', views.status, name='status') ) diff --git a/webmention/views.py b/webmention/views.py index 0207b4c..5abd688 100644 --- a/webmention/views.py +++ b/webmention/views.py @@ -13,36 +13,36 @@ from .models import State, Webmention @csrf_exempt @require_POST def accept(request): - if "source" not in request.POST: - return bad_req("missing source url") - source_url = request.POST["source"] + if 'source' not in request.POST: + return bad_req('missing source url') + source_url = request.POST['source'] - if "target" not in request.POST: - return bad_req("missing target url") - target_url = request.POST["target"] + if 'target' not in request.POST: + return bad_req('missing target url') + target_url = request.POST['target'] source = urlparse(source_url) target = urlparse(target_url) - if source.scheme not in ("http", "https"): - return bad_req("unsupported source scheme") - if target.scheme not in ("http", "https"): - return bad_req("unsupported target scheme") + if source.scheme not in ('http', 'https'): + return bad_req('unsupported source scheme') + if target.scheme not in ('http', 'https'): + return bad_req('unsupported target scheme') if target.netloc != request.site.domain: - return bad_req("target not on this site") - origin = "https://" + target.netloc + return bad_req('target not on this site') + origin = 'https://' + target.netloc try: match = resolve(target.path) except Resolver404: - return bad_req("target not found") + return bad_req('target not found') - if match.view_name != "entries:entry": - return bad_req("target does not accept webmentions") + if match.view_name != 'entries:entry': + return bad_req('target does not accept webmentions') try: - entry = Entry.objects.get(pk=match.kwargs["id"]) + entry = Entry.objects.get(pk=match.kwargs['id']) except Entry.DoesNotExist: - return bad_req("target not found") + return bad_req('target not found') try: mention = Webmention.objects.get(source=source_url, target=target_url) @@ -54,10 +54,10 @@ def accept(request): mention.entry = entry mention.state = State.PENDING mention.save() - status_url = reverse("webmention:status", kwargs={"id": mention.id}) + status_url = reverse('webmention:status', kwargs={'id': mention.id}) res = HttpResponse(status=201) - res["Location"] = urljoin(origin, status_url) + res['Location'] = urljoin(origin, status_url) return res diff --git a/wellknowns/apps.py b/wellknowns/apps.py index 949cc76..08ba250 100644 --- a/wellknowns/apps.py +++ b/wellknowns/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class WellKnownsConfig(AppConfig): - name = "wellknowns" + name = 'wellknowns' diff --git a/wellknowns/favicons.py b/wellknowns/favicons.py index aa0a840..27f7a5f 100644 --- a/wellknowns/favicons.py +++ b/wellknowns/favicons.py @@ -3,7 +3,7 @@ from django.core.files.storage import default_storage class Favicon: - def __init__(self, size, rel="icon", mime="image/png"): + def __init__(self, size, rel='icon', mime='image/png'): self.rel = rel self.mime = mime if not isinstance(size, tuple): @@ -12,18 +12,18 @@ class Favicon: @property def url(self): - return default_storage.url("favicon/" + self.filename) + return default_storage.url('favicon/' + self.filename) @property def filename(self): - return "favicon-{0}.png".format(*self.size) + return 'favicon-{0}.png'.format(*self.size) @property def sizes(self): - return "x".join(str(s) for s in self.size) + return 'x'.join(str(s) for s in self.size) -tile_sizes = {"small": 128, "medium": 270, "wide": (558, 270), "large": 558} +tile_sizes = {'small': 128, 'medium': 270, 'wide': (558, 270), 'large': 558} class Tile(Favicon): @@ -33,17 +33,13 @@ class Tile(Favicon): @property def filename(self): - return "{0}tile.png".format(self.size_name) + return '{0}tile.png'.format(self.size_name) sizes = (32, 57, 76, 96, 120, 128, 144, 180, 195, 228) -icons = tuple( - chain( - (Favicon(s) for s in sizes), - (Tile(s) for s in tile_sizes.keys()), - ( - Favicon(152, rel="apple-touch-icon-precomposed"), - Favicon(196, rel="shortcut icon"), - ), - ) -) +icons = tuple(chain( + (Favicon(s) for s in sizes), + (Tile(s) for s in tile_sizes.keys()), + (Favicon(152, rel='apple-touch-icon-precomposed'), + Favicon(196, rel='shortcut icon')) +)) diff --git a/wellknowns/tests/views/host_meta.py b/wellknowns/tests/views/host_meta.py index 2a38522..e8e332f 100644 --- a/wellknowns/tests/views/host_meta.py +++ b/wellknowns/tests/views/host_meta.py @@ -5,22 +5,22 @@ import pytest @pytest.mark.django_db def test_host_meta_json(client): - res = client.get("/.well-known/host-meta.json") + res = client.get('/.well-known/host-meta.json') assert res.status_code == 200 - assert res["Content-Type"] == "application/json" + assert res['Content-Type'] == 'application/json' meta = json.loads(res.content) - assert meta.keys() == {"links", "subject"} - assert meta["subject"] == "https://example.com" - assert len(meta["links"]) == 13 + assert meta.keys() == {'links', 'subject'} + assert meta['subject'] == 'https://example.com' + assert len(meta['links']) == 13 @pytest.mark.django_db def test_host_meta_xml(client): - res = client.get("/.well-known/host-meta") + res = client.get('/.well-known/host-meta') assert res.status_code == 200 - assert res["Content-Type"] == "application/xrd+xml" + assert res['Content-Type'] == 'application/xrd+xml' root = etree.XML(res.content) - ns = "{http://docs.oasis-open.org/ns/xri/xrd-1.0}" - assert root.tag == (ns + "XRD") - assert root.findtext(ns + "Subject") == "https://example.com" - assert len(root.findall(ns + "Link")) == 13 + ns = '{http://docs.oasis-open.org/ns/xri/xrd-1.0}' + assert root.tag == (ns + 'XRD') + assert root.findtext(ns + 'Subject') == 'https://example.com' + assert len(root.findall(ns + 'Link')) == 13 diff --git a/wellknowns/tests/views/static.py b/wellknowns/tests/views/static.py index 9f9dcac..eb7beb2 100644 --- a/wellknowns/tests/views/static.py +++ b/wellknowns/tests/views/static.py @@ -2,12 +2,12 @@ from ...views import static def test_redirect_to_static(rf): - res = static.redirect_to_static("abcd")(rf.get("/")) + res = static.redirect_to_static('abcd')(rf.get('/')) assert res.status_code == 302 - assert res.url == "/static/wellknowns/abcd" + assert res.url == '/static/wellknowns/abcd' def test_keybase(rf): - res = static.keybase(rf.get("/.well-knowns/keybase.txt")) + res = static.keybase(rf.get('/.well-knowns/keybase.txt')) assert res.status_code == 302 - assert res.url == "/static/wellknowns/keybase.txt" + assert res.url == '/static/wellknowns/keybase.txt' diff --git a/wellknowns/urls.py b/wellknowns/urls.py index 35957c0..2b1b99f 100644 --- a/wellknowns/urls.py +++ b/wellknowns/urls.py @@ -2,11 +2,11 @@ from django.urls import path from . import views -app_name = "wellknowns" +app_name = 'wellknowns' urlpatterns = [ - path("keybase.txt", views.keybase, name="keybase"), - path("host-meta", views.host_meta_xml, name="host-meta"), - path("host-meta.json", views.host_meta_json, name="host-meta.json"), - path("manifest.json", views.manifest, name="manifest"), - path("webfinger", views.webfinger, name="webfinger"), + path('keybase.txt', views.keybase, name='keybase'), + path('host-meta', views.host_meta_xml, name='host-meta'), + path('host-meta.json', views.host_meta_json, name='host-meta.json'), + path('manifest.json', views.manifest, name='manifest'), + path('webfinger', views.webfinger, name='webfinger'), ] diff --git a/wellknowns/views/host_meta.py b/wellknowns/views/host_meta.py index 6d9050e..5fc29f2 100644 --- a/wellknowns/views/host_meta.py +++ b/wellknowns/views/host_meta.py @@ -9,55 +9,58 @@ from xrd import XRD, Attribute, Element, Link def add_links(request, dest): base = origin(request) pkg = load_package_json() - webfinger = reverse("wellknowns:webfinger") + "?resource={uri}" - license = "https://creativecommons.org/licenses/by-sa/4.0/" + webfinger = reverse('wellknowns:webfinger') + '?resource={uri}' + license = 'https://creativecommons.org/licenses/by-sa/4.0/' links = ( Link( - href=urljoin(base, reverse("entries:atom")), - rel="alternate", - type_="application/atom+xml", + href=urljoin(base, reverse('entries:atom')), + rel='alternate', type_='application/atom+xml', ), Link( - href=urljoin(base, reverse("entries:rss")), - rel="alternate", - type_="application/rss+xml", + href=urljoin(base, reverse('entries:rss')), + rel='alternate', type_='application/rss+xml', ), Link( - href=urljoin(base, reverse("lemonauth:indie")), rel="authorization_endpoint" + href=urljoin(base, reverse('lemonauth:indie')), + rel='authorization_endpoint' ), - Link(href=pkg["repository"], type_="text/html", rel="code-repository"), - Link(href=settings.PUSH_HUB, rel="hub"), - Link(href=license, type_="text/html", rel="license"), - Link(href=license + "rdf", type_="application/rdf+xml", rel="license"), + Link(href=pkg['repository'], type_='text/html', rel='code-repository'), + Link(href=settings.PUSH_HUB, rel='hub'), + Link(href=license, type_='text/html', rel='license'), + Link(href=license+'rdf', type_='application/rdf+xml', rel='license'), Link( template=urljoin(base, webfinger), - type_="application/json", - rel="lrdd", + type_='application/json', rel='lrdd', ), Link( - href=urljoin(base, reverse("wellknowns:manifest")), - rel="manifest", - type_="application/json", + href=urljoin(base, reverse('wellknowns:manifest')), + rel='manifest', type_='application/json', ), - Link(href=urljoin(base, reverse("micropub:micropub")), rel="micropub"), - Link(href=urljoin(base, reverse("lemonauth:token")), rel="token_endpoint"), - Link(href="https://openid.indieauth.com/openid", rel="openid.server"), - Link(href=base, rel="openid.delegate"), + Link( + href=urljoin(base, reverse('micropub:micropub')), + rel='micropub' + ), + Link( + href=urljoin(base, reverse('lemonauth:token')), + rel='token_endpoint' + ), + Link(href='https://openid.indieauth.com/openid', rel='openid.server'), + Link(href=base, rel='openid.delegate'), ) dest.extend(links) def host_meta(request): - h = XRD(subject="https://" + request.site.domain) + h = XRD(subject='https://' + request.site.domain) add_links(request, h.links) return h def host_meta_xml(request): return HttpResponse( - host_meta(request).to_xml().toprettyxml(indent=" ", encoding="utf-8"), - content_type="application/xrd+xml", + host_meta(request).to_xml().toprettyxml(indent=' ', encoding='utf-8'), + content_type='application/xrd+xml', ) @@ -68,15 +71,13 @@ def host_meta_json(request): links = [] for l in meta.links: link = { - "rel": l.rel, - "type": l.type, - "href": l.href, - "template": l.template, + 'rel': l.rel, 'type': l.type, + 'href': l.href, 'template': l.template, } for k in list(link.keys()): if not link[k]: del link[k] links.append(link) - meta = {"links": links, "subject": meta.subject} + meta = {'links': links, 'subject': meta.subject} return JsonResponse(meta) diff --git a/wellknowns/views/manifest.py b/wellknowns/views/manifest.py index ae62175..2105f56 100644 --- a/wellknowns/views/manifest.py +++ b/wellknowns/views/manifest.py @@ -8,24 +8,23 @@ from textwrap import shorten def manifest_icons(base): - return [ - {"src": i.url, "type": i.mime, "sizes": i.sizes} - for i in sorted(icons, key=lambda i: i.size) - ] + return [{'src': i.url, 'type': i.mime, 'sizes': i.sizes} for i in sorted(icons, key=lambda i: i.size)] def manifest(request): base = utils.origin(request) - start_url = reverse("home:index") + "?utm_source=homescreen" + start_url = reverse('home:index') + '?utm_source=homescreen' app = { - "name": request.site.name, - "short_name": shorten(request.site.name, width=20, placeholder=""), - "icons": manifest_icons(base), - "display": "browser", - "start_url": urljoin(base, start_url), - "background_color": color(0), - "theme_color": color(3), + 'name': request.site.name, + 'short_name': shorten(request.site.name, width=20, placeholder=''), + 'icons': manifest_icons(base), + + 'display': 'browser', + 'start_url': urljoin(base, start_url), + + 'background_color': color(0), + 'theme_color': color(3), } - return JsonResponse(app, content_type="application/manifest+json") + return JsonResponse(app, content_type='application/manifest+json') diff --git a/wellknowns/views/static.py b/wellknowns/views/static.py index 3bd8c03..e09e3c5 100644 --- a/wellknowns/views/static.py +++ b/wellknowns/views/static.py @@ -3,7 +3,7 @@ from django.templatetags.static import static def redirect_to_static(file): - return RedirectView.as_view(url=static("wellknowns/" + file)) + return RedirectView.as_view(url=static('wellknowns/' + file)) -keybase = redirect_to_static("keybase.txt") +keybase = redirect_to_static('keybase.txt') diff --git a/wellknowns/views/webfinger.py b/wellknowns/views/webfinger.py index ef42687..05684a9 100644 --- a/wellknowns/views/webfinger.py +++ b/wellknowns/views/webfinger.py @@ -3,9 +3,9 @@ from urllib.parse import urlencode, urlparse from users.models import User -AVATAR = "http://webfinger.net/rel/avatar" -PROFILE_PAGE = "http://webfinger.net/rel/profile-page" -BRIDGY_FED = "https://fed.brid.gy/.well-known/webfinger" +AVATAR = 'http://webfinger.net/rel/avatar' +PROFILE_PAGE = 'http://webfinger.net/rel/profile-page' +BRIDGY_FED = 'https://fed.brid.gy/.well-known/webfinger' def https_resource_matching(resource): @@ -15,10 +15,10 @@ def https_resource_matching(resource): resource, if a user with matching email or XMPP address exists locally. Will throw `User.DoesNotExist` if no such user exists. """ - if resource.scheme == "mailto": - query = {"email": resource.path} + if resource.scheme == 'mailto': + query = {'email': resource.path} else: - query = {"xmpp": resource.path} + query = {'xmpp': resource.path} return User.objects.get(**query).absolute_url @@ -41,19 +41,19 @@ def webfinger(request): original resource will be preserved in the redirect - and likely fail to find anything at Bridgy's end either. """ - if "resource" not in request.GET: - return HttpResponseBadRequest("resource parameter missing") - resource = request.GET["resource"] + if 'resource' not in request.GET: + return HttpResponseBadRequest('resource parameter missing') + resource = request.GET['resource'] try: res = urlparse(resource) except ValueError: - return HttpResponseBadRequest("resource parameter malformed") + return HttpResponseBadRequest('resource parameter malformed') - if res.scheme in ("mailto", "xmpp"): + if res.scheme in ('mailto', 'xmpp'): try: resource = https_resource_matching(res) except User.DoesNotExist: pass - query = urlencode({"resource": resource}) - return HttpResponseRedirect(BRIDGY_FED + "?" + query) + query = urlencode({'resource': resource}) + return HttpResponseRedirect(BRIDGY_FED + '?' + query)