@ -0,0 +1,3 @@ | |||
[run] | |||
source = bluelog | |||
branch = true |
@ -0,0 +1,4 @@ | |||
[flake8] | |||
exclude = .git,*migrations* | |||
max-line-length = 119 | |||
max-complexity = 7 |
@ -0,0 +1,2 @@ | |||
FLASK_APP=bluelog | |||
FLASK_ENV=development |
@ -0,0 +1,112 @@ | |||
# Byte-compiled / optimized / DLL files | |||
__pycache__/ | |||
*.py[cod] | |||
*$py.class | |||
# C extensions | |||
*.so | |||
# Distribution / packaging | |||
.Python | |||
build/ | |||
develop-eggs/ | |||
dist/ | |||
downloads/ | |||
eggs/ | |||
.eggs/ | |||
lib/ | |||
lib64/ | |||
parts/ | |||
sdist/ | |||
var/ | |||
wheels/ | |||
*.egg-info/ | |||
.installed.cfg | |||
*.egg | |||
MANIFEST | |||
# PyInstaller | |||
# Usually these files are written by a python script from a template | |||
# before PyInstaller builds the exe, so as to inject date/other infos into it. | |||
*.manifest | |||
*.spec | |||
# Installer logs | |||
pip-log.txt | |||
pip-delete-this-directory.txt | |||
# Unit test / coverage reports | |||
htmlcov/ | |||
.tox/ | |||
.coverage | |||
.coverage.* | |||
.cache | |||
nosetests.xml | |||
coverage.xml | |||
*.cover | |||
.hypothesis/ | |||
.pytest_cache/ | |||
# Translations | |||
*.mo | |||
*.pot | |||
# Django stuff: | |||
*.log | |||
local_settings.py | |||
db.sqlite3 | |||
# Flask stuff: | |||
instance/ | |||
.webassets-cache | |||
# Scrapy stuff: | |||
.scrapy | |||
# Sphinx documentation | |||
docs/_build/ | |||
# PyBuilder | |||
target/ | |||
# Jupyter Notebook | |||
.ipynb_checkpoints | |||
# pyenv | |||
.python-version | |||
# celery beat schedule file | |||
celerybeat-schedule | |||
# SageMath parsed files | |||
*.sage.py | |||
# Environments | |||
.env | |||
.venv | |||
env/ | |||
venv/ | |||
ENV/ | |||
env.bak/ | |||
venv.bak/ | |||
# Spyder project settings | |||
.spyderproject | |||
.spyproject | |||
# Rope project settings | |||
.ropeproject | |||
# mkdocs documentation | |||
/site | |||
# mypy | |||
.mypy_cache/ | |||
# Development | |||
*.db | |||
.idea | |||
logs/* | |||
!logs/.gitkeep | |||
uploads/* | |||
!uploads/.gitkeep |
@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2017 Grey Li | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@ -0,0 +1,25 @@ | |||
[[source]] | |||
url = "https://pypi.org/simple" | |||
verify_ssl = true | |||
name = "pypi" | |||
[dev-packages] | |||
coverage = "*" | |||
watchdog = "*" | |||
"flake8" = "*" | |||
faker = "*" | |||
[packages] | |||
flask = "*" | |||
flask-ckeditor = "*" | |||
flask-mail = "*" | |||
flask-sqlalchemy = "*" | |||
flask-wtf = "*" | |||
flask-moment = "*" | |||
python-dotenv = "*" | |||
bootstrap-flask = "*" | |||
flask-login = "*" | |||
flask-debugtoolbar = "*" | |||
gunicorn = "*" | |||
"psycopg2" = "*" | |||
flask-migrate = "*" |
@ -0,0 +1,369 @@ | |||
{ | |||
"_meta": { | |||
"hash": { | |||
"sha256": "b211c0ef54a1d431af2250e563c542fa0a957b3d009bd8005c0195ce56fd55e3" | |||
}, | |||
"pipfile-spec": 6, | |||
"requires": {}, | |||
"sources": [ | |||
{ | |||
"name": "pypi", | |||
"url": "https://pypi.org/simple", | |||
"verify_ssl": true | |||
} | |||
] | |||
}, | |||
"default": { | |||
"alembic": { | |||
"hashes": [ | |||
"sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf" | |||
], | |||
"version": "==1.4.2" | |||
}, | |||
"blinker": { | |||
"hashes": [ | |||
"sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" | |||
], | |||
"version": "==1.4" | |||
}, | |||
"bootstrap-flask": { | |||
"hashes": [ | |||
"sha256:d03c738f5377dbe7ecce010e28afcc0fb8373c4c9d01f8590af7657b53342c1d", | |||
"sha256:ed817e82acadac4c8b3fc2d1a310325b6ed395d13b0f6ca9d14e1742f880e685" | |||
], | |||
"index": "pypi", | |||
"version": "==1.2.0" | |||
}, | |||
"click": { | |||
"hashes": [ | |||
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", | |||
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" | |||
], | |||
"version": "==7.1.1" | |||
}, | |||
"flask": { | |||
"hashes": [ | |||
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", | |||
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" | |||
], | |||
"index": "pypi", | |||
"version": "==1.1.2" | |||
}, | |||
"flask-ckeditor": { | |||
"hashes": [ | |||
"sha256:327ad60c3787d96cdc579ce04c284d33343802d3b7422ba998aadbbdee6756f1", | |||
"sha256:8d91ee747473dc4e6e0dc166fd71b3136b7d388de757731c019fad835d776c8e" | |||
], | |||
"index": "pypi", | |||
"version": "==0.4.3" | |||
}, | |||
"flask-debugtoolbar": { | |||
"hashes": [ | |||
"sha256:0e9a80d4c599233c68376e81cc99976200b5ac5248cfb24f18935cc5b69ac5b3", | |||
"sha256:3c4e79d354ede014e6657c545a536d4fb273cc89e3fd6b4835b02e346dd3aab4" | |||
], | |||
"index": "pypi", | |||
"version": "==0.11.0" | |||
}, | |||
"flask-login": { | |||
"hashes": [ | |||
"sha256:6d33aef15b5bcead780acc339464aae8a6e28f13c90d8b1cf9de8b549d1c0b4b", | |||
"sha256:7451b5001e17837ba58945aead261ba425fdf7b4f0448777e597ddab39f4fba0" | |||
], | |||
"index": "pypi", | |||
"version": "==0.5.0" | |||
}, | |||
"flask-mail": { | |||
"hashes": [ | |||
"sha256:22e5eb9a940bf407bcf30410ecc3708f3c56cc44b29c34e1726fe85006935f41" | |||
], | |||
"index": "pypi", | |||
"version": "==0.9.1" | |||
}, | |||
"flask-migrate": { | |||
"hashes": [ | |||
"sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", | |||
"sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" | |||
], | |||
"index": "pypi", | |||
"version": "==2.5.3" | |||
}, | |||
"flask-moment": { | |||
"hashes": [ | |||
"sha256:3c509afa25fd77459c9d799f292dcea384b1cd588ed1fd68f97f6fda6131299e", | |||
"sha256:3e8b88f99af7cf75f2f29ef9d8c158eb92ca6f3c1ba6456ad70f715efa6eb7f7" | |||
], | |||
"index": "pypi", | |||
"version": "==0.9.0" | |||
}, | |||
"flask-sqlalchemy": { | |||
"hashes": [ | |||
"sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", | |||
"sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" | |||
], | |||
"index": "pypi", | |||
"version": "==2.4.1" | |||
}, | |||
"flask-wtf": { | |||
"hashes": [ | |||
"sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", | |||
"sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" | |||
], | |||
"index": "pypi", | |||
"version": "==0.14.3" | |||
}, | |||
"gunicorn": { | |||
"hashes": [ | |||
"sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626", | |||
"sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c" | |||
], | |||
"index": "pypi", | |||
"version": "==20.0.4" | |||
}, | |||
"itsdangerous": { | |||
"hashes": [ | |||
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", | |||
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" | |||
], | |||
"version": "==1.1.0" | |||
}, | |||
"jinja2": { | |||
"hashes": [ | |||
"sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", | |||
"sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" | |||
], | |||
"version": "==2.11.1" | |||
}, | |||
"mako": { | |||
"hashes": [ | |||
"sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d", | |||
"sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9" | |||
], | |||
"version": "==1.1.2" | |||
}, | |||
"markupsafe": { | |||
"hashes": [ | |||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", | |||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", | |||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", | |||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", | |||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", | |||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", | |||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", | |||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", | |||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", | |||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", | |||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", | |||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", | |||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", | |||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", | |||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", | |||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", | |||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", | |||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", | |||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", | |||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", | |||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", | |||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", | |||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", | |||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", | |||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", | |||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", | |||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", | |||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", | |||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", | |||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", | |||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", | |||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", | |||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be" | |||
], | |||
"version": "==1.1.1" | |||
}, | |||
"psycopg2": { | |||
"hashes": [ | |||
"sha256:4212ca404c4445dc5746c0d68db27d2cbfb87b523fe233dc84ecd24062e35677", | |||
"sha256:47fc642bf6f427805daf52d6e52619fe0637648fe27017062d898f3bf891419d", | |||
"sha256:72772181d9bad1fa349792a1e7384dde56742c14af2b9986013eb94a240f005b", | |||
"sha256:8396be6e5ff844282d4d49b81631772f80dabae5658d432202faf101f5283b7c", | |||
"sha256:893c11064b347b24ecdd277a094413e1954f8a4e8cdaf7ffbe7ca3db87c103f0", | |||
"sha256:92a07dfd4d7c325dd177548c4134052d4842222833576c8391aab6f74038fc3f", | |||
"sha256:965c4c93e33e6984d8031f74e51227bd755376a9df6993774fd5b6fb3288b1f4", | |||
"sha256:9ab75e0b2820880ae24b7136c4d230383e07db014456a476d096591172569c38", | |||
"sha256:b0845e3bdd4aa18dc2f9b6fb78fbd3d9d371ad167fd6d1b7ad01c0a6cdad4fc6", | |||
"sha256:dca2d7203f0dfce8ea4b3efd668f8ea65cd2b35112638e488a4c12594015f67b", | |||
"sha256:ed686e5926929887e2c7ae0a700e32c6129abb798b4ad2b846e933de21508151", | |||
"sha256:ef6df7e14698e79c59c7ee7cf94cd62e5b869db369ed4b1b8f7b729ea825712a", | |||
"sha256:f898e5cc0a662a9e12bde6f931263a1bbd350cfb18e1d5336a12927851825bb6" | |||
], | |||
"index": "pypi", | |||
"version": "==2.8.4" | |||
}, | |||
"python-dateutil": { | |||
"hashes": [ | |||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", | |||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" | |||
], | |||
"version": "==2.8.1" | |||
}, | |||
"python-dotenv": { | |||
"hashes": [ | |||
"sha256:81822227f771e0cab235a2939f0f265954ac4763cafd806d845801c863bf372f", | |||
"sha256:92b3123fb2d58a284f76cc92bfe4ee6c502c32ded73e8b051c4f6afc8b6751ed" | |||
], | |||
"index": "pypi", | |||
"version": "==0.12.0" | |||
}, | |||
"python-editor": { | |||
"hashes": [ | |||
"sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", | |||
"sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", | |||
"sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" | |||
], | |||
"version": "==1.0.4" | |||
}, | |||
"six": { | |||
"hashes": [ | |||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", | |||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" | |||
], | |||
"version": "==1.14.0" | |||
}, | |||
"sqlalchemy": { | |||
"hashes": [ | |||
"sha256:c4cca4aed606297afbe90d4306b49ad3a4cd36feb3f87e4bfd655c57fd9ef445" | |||
], | |||
"version": "==1.3.15" | |||
}, | |||
"werkzeug": { | |||
"hashes": [ | |||
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", | |||
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" | |||
], | |||
"version": "==1.0.1" | |||
}, | |||
"wtforms": { | |||
"hashes": [ | |||
"sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61", | |||
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1" | |||
], | |||
"version": "==2.2.1" | |||
} | |||
}, | |||
"develop": { | |||
"coverage": { | |||
"hashes": [ | |||
"sha256:03f630aba2b9b0d69871c2e8d23a69b7fe94a1e2f5f10df5049c0df99db639a0", | |||
"sha256:046a1a742e66d065d16fb564a26c2a15867f17695e7f3d358d7b1ad8a61bca30", | |||
"sha256:0a907199566269e1cfa304325cc3b45c72ae341fbb3253ddde19fa820ded7a8b", | |||
"sha256:165a48268bfb5a77e2d9dbb80de7ea917332a79c7adb747bd005b3a07ff8caf0", | |||
"sha256:1b60a95fc995649464e0cd48cecc8288bac5f4198f21d04b8229dc4097d76823", | |||
"sha256:1f66cf263ec77af5b8fe14ef14c5e46e2eb4a795ac495ad7c03adc72ae43fafe", | |||
"sha256:2e08c32cbede4a29e2a701822291ae2bc9b5220a971bba9d1e7615312efd3037", | |||
"sha256:3844c3dab800ca8536f75ae89f3cf566848a3eb2af4d9f7b1103b4f4f7a5dad6", | |||
"sha256:408ce64078398b2ee2ec08199ea3fcf382828d2f8a19c5a5ba2946fe5ddc6c31", | |||
"sha256:443be7602c790960b9514567917af538cac7807a7c0c0727c4d2bbd4014920fd", | |||
"sha256:4482f69e0701139d0f2c44f3c395d1d1d37abd81bfafbf9b6efbe2542679d892", | |||
"sha256:4a8a259bf990044351baf69d3b23e575699dd60b18460c71e81dc565f5819ac1", | |||
"sha256:513e6526e0082c59a984448f4104c9bf346c2da9961779ede1fc458e8e8a1f78", | |||
"sha256:5f587dfd83cb669933186661a351ad6fc7166273bc3e3a1531ec5c783d997aac", | |||
"sha256:62061e87071497951155cbccee487980524d7abea647a1b2a6eb6b9647df9006", | |||
"sha256:641e329e7f2c01531c45c687efcec8aeca2a78a4ff26d49184dce3d53fc35014", | |||
"sha256:65a7e00c00472cd0f59ae09d2fb8a8aaae7f4a0cf54b2b74f3138d9f9ceb9cb2", | |||
"sha256:6ad6ca45e9e92c05295f638e78cd42bfaaf8ee07878c9ed73e93190b26c125f7", | |||
"sha256:73aa6e86034dad9f00f4bbf5a666a889d17d79db73bc5af04abd6c20a014d9c8", | |||
"sha256:7c9762f80a25d8d0e4ab3cb1af5d9dffbddb3ee5d21c43e3474c84bf5ff941f7", | |||
"sha256:85596aa5d9aac1bf39fe39d9fa1051b0f00823982a1de5766e35d495b4a36ca9", | |||
"sha256:86a0ea78fd851b313b2e712266f663e13b6bc78c2fb260b079e8b67d970474b1", | |||
"sha256:8a620767b8209f3446197c0e29ba895d75a1e272a36af0786ec70fe7834e4307", | |||
"sha256:922fb9ef2c67c3ab20e22948dcfd783397e4c043a5c5fa5ff5e9df5529074b0a", | |||
"sha256:9fad78c13e71546a76c2f8789623eec8e499f8d2d799f4b4547162ce0a4df435", | |||
"sha256:a37c6233b28e5bc340054cf6170e7090a4e85069513320275a4dc929144dccf0", | |||
"sha256:c3fc325ce4cbf902d05a80daa47b645d07e796a80682c1c5800d6ac5045193e5", | |||
"sha256:cda33311cb9fb9323958a69499a667bd728a39a7aa4718d7622597a44c4f1441", | |||
"sha256:db1d4e38c9b15be1521722e946ee24f6db95b189d1447fa9ff18dd16ba89f732", | |||
"sha256:eda55e6e9ea258f5e4add23bcf33dc53b2c319e70806e180aecbff8d90ea24de", | |||
"sha256:f372cdbb240e09ee855735b9d85e7f50730dcfb6296b74b95a3e5dea0615c4c1" | |||
], | |||
"index": "pypi", | |||
"version": "==5.0.4" | |||
}, | |||
"entrypoints": { | |||
"hashes": [ | |||
"sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", | |||
"sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" | |||
], | |||
"version": "==0.3" | |||
}, | |||
"faker": { | |||
"hashes": [ | |||
"sha256:2d3f866ef25e1a5af80e7b0ceeacc3c92dec5d0fdbad3e2cb6adf6e60b22188f", | |||
"sha256:b89aa33837498498e15c709eb40c31386408a901a53c7a5e12a425737a767976" | |||
], | |||
"index": "pypi", | |||
"version": "==4.0.2" | |||
}, | |||
"flake8": { | |||
"hashes": [ | |||
"sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", | |||
"sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" | |||
], | |||
"index": "pypi", | |||
"version": "==3.7.9" | |||
}, | |||
"mccabe": { | |||
"hashes": [ | |||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", | |||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" | |||
], | |||
"version": "==0.6.1" | |||
}, | |||
"pathtools": { | |||
"hashes": [ | |||
"sha256:7c35c5421a39bb82e58018febd90e3b6e5db34c5443aaaf742b3f33d4655f1c0" | |||
], | |||
"version": "==0.1.2" | |||
}, | |||
"pycodestyle": { | |||
"hashes": [ | |||
"sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", | |||
"sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" | |||
], | |||
"version": "==2.5.0" | |||
}, | |||
"pyflakes": { | |||
"hashes": [ | |||
"sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", | |||
"sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" | |||
], | |||
"version": "==2.1.1" | |||
}, | |||
"python-dateutil": { | |||
"hashes": [ | |||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", | |||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" | |||
], | |||
"version": "==2.8.1" | |||
}, | |||
"six": { | |||
"hashes": [ | |||
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", | |||
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" | |||
], | |||
"version": "==1.14.0" | |||
}, | |||
"text-unidecode": { | |||
"hashes": [ | |||
"sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", | |||
"sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" | |||
], | |||
"version": "==1.3" | |||
}, | |||
"watchdog": { | |||
"hashes": [ | |||
"sha256:c560efb643faed5ef28784b2245cf8874f939569717a4a12826a173ac644456b" | |||
], | |||
"index": "pypi", | |||
"version": "==0.10.2" | |||
} | |||
} | |||
} |
@ -0,0 +1 @@ | |||
web: gunicorn wsgi:app --log-file - |
@ -0,0 +1 @@ | |||
web: flask run |
@ -1,3 +1,83 @@ | |||
# bluelog | |||
# Bluelog 安装 | |||
博客系统 | |||
**使用镜像直接看第4步** | |||
**云数据库以删除,需要新建**(防止花费过高) | |||
## 1,安装过程 | |||
clone: | |||
``` | |||
$ git clone https://github.com/hsy77/bluelog.git | |||
$ cd bluelog | |||
``` | |||
## 2,使用已有数据库 | |||
## (使用新建数据库先跳转至3,再做2) | |||
在云主机中安装依赖: | |||
``` | |||
(这两步可不做,即不用创建虚拟环境) | |||
$ python3 -m venv env | |||
$ source env/bin/activate | |||
(需要在云主机上或者虚拟环境中运行,两个环境都可以,为了保证psycopg2的安装不报错) | |||
$ sudo yum install postgresql-libs -y | |||
$ sudo yum install postgresql-devel -y | |||
$ sudo yum install gcc -y | |||
(安装需要的包) | |||
$ pip3 install -r requirements.txt | |||
``` | |||
生成数据并且运行: | |||
``` | |||
$ flask forge #将数据导入数据表中 | |||
$ flask run -h 0.0.0.0 -p 80 #在0.0.0.0 80端口运行,为了使得使用外网ip可以访问 | |||
* Running on http://0.0.0.0:80/ | |||
``` | |||
接下来就可以在网页上输入云主机外网ip访问 | |||
Test account: | |||
* username: `admin` | |||
* password: `helloflask` | |||
## 3, 使用新建云数据库 | |||
- 将`bluelog\bluelog\settings.py` 中的以下四个参数改为自己数据库的设置 | |||
![数据库设置.jpg](http://ww1.sinaimg.cn/large/005ZSk16gy1gmqk9kv2iwj30cw03rwei.jpg) | |||
# 4,从镜像中导入: | |||
### - 先做步骤3,新建数据库 | |||
### - 然后 | |||
生成数据并且运行: | |||
``` | |||
$ flask forge #将数据导入数据表中 | |||
$ flask run -h 0.0.0.0 -p 80 #在0.0.0.0 80端口运行,为了使得使用外网ip可以访问 | |||
* Running on http://0.0.0.0:80/ | |||
``` | |||
接下来就可以在网页上输入云主机外网ip访问 | |||
Test account: | |||
* username: `admin` | |||
* password: `helloflask` | |||
## 安装成果 | |||
![安装成果.jpg](http://ww1.sinaimg.cn/large/005ZSk16gy1gmqk9ujm8xj30y30q0wi2.jpg) |
@ -0,0 +1,223 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
import logging | |||
import os | |||
from logging.handlers import SMTPHandler, RotatingFileHandler | |||
import click | |||
from flask import Flask, render_template, request | |||
from flask_login import current_user | |||
from flask_sqlalchemy import get_debug_queries | |||
from flask_wtf.csrf import CSRFError | |||
from bluelog.blueprints.admin import admin_bp | |||
from bluelog.blueprints.auth import auth_bp | |||
from bluelog.blueprints.blog import blog_bp | |||
from bluelog.extensions import bootstrap, db, login_manager, csrf, ckeditor, mail, moment, toolbar, migrate | |||
from bluelog.models import Admin, Post, Category, Comment, Link | |||
from bluelog.settings import config | |||
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) | |||
def create_app(config_name=None): | |||
if config_name is None: | |||
config_name = os.getenv('FLASK_CONFIG', 'development') | |||
app = Flask('bluelog') | |||
app.config.from_object(config[config_name]) | |||
register_logging(app) | |||
register_extensions(app) | |||
register_blueprints(app) | |||
register_commands(app) | |||
register_errors(app) | |||
register_shell_context(app) | |||
register_template_context(app) | |||
register_request_handlers(app) | |||
return app | |||
def register_logging(app): | |||
class RequestFormatter(logging.Formatter): | |||
def format(self, record): | |||
record.url = request.url | |||
record.remote_addr = request.remote_addr | |||
return super(RequestFormatter, self).format(record) | |||
request_formatter = RequestFormatter( | |||
'[%(asctime)s] %(remote_addr)s requested %(url)s\n' | |||
'%(levelname)s in %(module)s: %(message)s' | |||
) | |||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |||
file_handler = RotatingFileHandler(os.path.join(basedir, 'logs/bluelog.log'), | |||
maxBytes=10 * 1024 * 1024, backupCount=10) | |||
file_handler.setFormatter(formatter) | |||
file_handler.setLevel(logging.INFO) | |||
mail_handler = SMTPHandler( | |||
mailhost=app.config['MAIL_SERVER'], | |||
fromaddr=app.config['MAIL_USERNAME'], | |||
toaddrs=['ADMIN_EMAIL'], | |||
subject='Bluelog Application Error', | |||
credentials=(app.config['MAIL_USERNAME'], app.config['MAIL_PASSWORD'])) | |||
mail_handler.setLevel(logging.ERROR) | |||
mail_handler.setFormatter(request_formatter) | |||
if not app.debug: | |||
app.logger.addHandler(mail_handler) | |||
app.logger.addHandler(file_handler) | |||
def register_extensions(app): | |||
bootstrap.init_app(app) | |||
db.init_app(app) | |||
login_manager.init_app(app) | |||
csrf.init_app(app) | |||
ckeditor.init_app(app) | |||
mail.init_app(app) | |||
moment.init_app(app) | |||
toolbar.init_app(app) | |||
migrate.init_app(app, db) | |||
def register_blueprints(app): | |||
app.register_blueprint(blog_bp) | |||
app.register_blueprint(admin_bp, url_prefix='/admin') | |||
app.register_blueprint(auth_bp, url_prefix='/auth') | |||
def register_shell_context(app): | |||
@app.shell_context_processor | |||
def make_shell_context(): | |||
return dict(db=db, Admin=Admin, Post=Post, Category=Category, Comment=Comment) | |||
def register_template_context(app): | |||
@app.context_processor | |||
def make_template_context(): | |||
admin = Admin.query.first() | |||
categories = Category.query.order_by(Category.name).all() | |||
links = Link.query.order_by(Link.name).all() | |||
if current_user.is_authenticated: | |||
unread_comments = Comment.query.filter_by(reviewed=False).count() | |||
else: | |||
unread_comments = None | |||
return dict( | |||
admin=admin, categories=categories, | |||
links=links, unread_comments=unread_comments) | |||
def register_errors(app): | |||
@app.errorhandler(400) | |||
def bad_request(e): | |||
return render_template('errors/400.html'), 400 | |||
@app.errorhandler(404) | |||
def page_not_found(e): | |||
return render_template('errors/404.html'), 404 | |||
@app.errorhandler(500) | |||
def internal_server_error(e): | |||
return render_template('errors/500.html'), 500 | |||
@app.errorhandler(CSRFError) | |||
def handle_csrf_error(e): | |||
return render_template('errors/400.html', description=e.description), 400 | |||
def register_commands(app): | |||
@app.cli.command() | |||
@click.option('--drop', is_flag=True, help='Create after drop.') | |||
def initdb(drop): | |||
"""Initialize the database.""" | |||
if drop: | |||
click.confirm('This operation will delete the database, do you want to continue?', abort=True) | |||
db.drop_all() | |||
click.echo('Drop tables.') | |||
db.create_all() | |||
click.echo('Initialized database.') | |||
@app.cli.command() | |||
@click.option('--username', prompt=True, help='The username used to login.') | |||
@click.option('--password', prompt=True, hide_input=True, | |||
confirmation_prompt=True, help='The password used to login.') | |||
def init(username, password): | |||
"""Building Bluelog, just for you.""" | |||
click.echo('Initializing the database...') | |||
db.create_all() | |||
admin = Admin.query.first() | |||
if admin is not None: | |||
click.echo('The administrator already exists, updating...') | |||
admin.username = username | |||
admin.set_password(password) | |||
else: | |||
click.echo('Creating the temporary administrator account...') | |||
admin = Admin( | |||
username=username, | |||
blog_title='Bluelog', | |||
blog_sub_title="No, I'm the real thing.", | |||
name='Admin', | |||
about='Anything about you.' | |||
) | |||
admin.set_password(password) | |||
db.session.add(admin) | |||
category = Category.query.first() | |||
if category is None: | |||
click.echo('Creating the default category...') | |||
category = Category(name='Default') | |||
db.session.add(category) | |||
db.session.commit() | |||
click.echo('Done.') | |||
@app.cli.command() | |||
@click.option('--category', default=10, help='Quantity of categories, default is 10.') | |||
@click.option('--post', default=15, help='Quantity of posts, default is 50.') | |||
@click.option('--comment', default=500, help='Quantity of comments, default is 500.') | |||
def forge(category, post, comment): | |||
"""Generate fake data.""" | |||
from bluelog.fakes import fake_admin, fake_categories, fake_posts, fake_comments, fake_links | |||
db.drop_all() | |||
db.create_all() | |||
click.echo('Generating the administrator...') | |||
fake_admin() | |||
click.echo('Generating %d categories...' % category) | |||
fake_categories(category) | |||
click.echo('Generating %d posts...' % post) | |||
fake_posts(post) | |||
click.echo('Generating %d comments...' % comment) | |||
fake_comments(comment) | |||
click.echo('Generating links...') | |||
fake_links() | |||
click.echo('Done.') | |||
def register_request_handlers(app): | |||
@app.after_request | |||
def query_profiler(response): | |||
for q in get_debug_queries(): | |||
if q.duration >= app.config['BLUELOG_SLOW_QUERY_THRESHOLD']: | |||
app.logger.warning( | |||
'Slow query: Duration: %fs\n Context: %s\nQuery: %s\n ' | |||
% (q.duration, q.context, q.statement) | |||
) | |||
return response |
@ -0,0 +1,259 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
import os | |||
from flask import render_template, flash, redirect, url_for, request, current_app, Blueprint, send_from_directory | |||
from flask_login import login_required, current_user | |||
from flask_ckeditor import upload_success, upload_fail | |||
from bluelog.extensions import db | |||
from bluelog.forms import SettingForm, PostForm, CategoryForm, LinkForm | |||
from bluelog.models import Post, Category, Comment, Link | |||
from bluelog.utils import redirect_back, allowed_file | |||
admin_bp = Blueprint('admin', __name__) | |||
@admin_bp.route('/settings', methods=['GET', 'POST']) | |||
@login_required | |||
def settings(): | |||
form = SettingForm() | |||
if form.validate_on_submit(): | |||
current_user.name = form.name.data | |||
current_user.blog_title = form.blog_title.data | |||
current_user.blog_sub_title = form.blog_sub_title.data | |||
current_user.about = form.about.data | |||
db.session.commit() | |||
flash('Setting updated.', 'success') | |||
return redirect(url_for('blog.index')) | |||
form.name.data = current_user.name | |||
form.blog_title.data = current_user.blog_title | |||
form.blog_sub_title.data = current_user.blog_sub_title | |||
form.about.data = current_user.about | |||
return render_template('admin/settings.html', form=form) | |||
@admin_bp.route('/post/manage') | |||
@login_required | |||
def manage_post(): | |||
page = request.args.get('page', 1, type=int) | |||
pagination = Post.query.order_by(Post.timestamp.desc()).paginate( | |||
page, per_page=current_app.config['BLUELOG_MANAGE_POST_PER_PAGE']) | |||
posts = pagination.items | |||
return render_template('admin/manage_post.html', page=page, pagination=pagination, posts=posts) | |||
@admin_bp.route('/post/new', methods=['GET', 'POST']) | |||
@login_required | |||
def new_post(): | |||
form = PostForm() | |||
if form.validate_on_submit(): | |||
title = form.title.data | |||
body = form.body.data | |||
category = Category.query.get(form.category.data) | |||
post = Post(title=title, body=body, category=category) | |||
# same with: | |||
# category_id = form.category.data | |||
# post = Post(title=title, body=body, category_id=category_id) | |||
db.session.add(post) | |||
db.session.commit() | |||
flash('Post created.', 'success') | |||
return redirect(url_for('blog.show_post', post_id=post.id)) | |||
return render_template('admin/new_post.html', form=form) | |||
@admin_bp.route('/post/<int:post_id>/edit', methods=['GET', 'POST']) | |||
@login_required | |||
def edit_post(post_id): | |||
form = PostForm() | |||
post = Post.query.get_or_404(post_id) | |||
if form.validate_on_submit(): | |||
post.title = form.title.data | |||
post.body = form.body.data | |||
post.category = Category.query.get(form.category.data) | |||
db.session.commit() | |||
flash('Post updated.', 'success') | |||
return redirect(url_for('blog.show_post', post_id=post.id)) | |||
form.title.data = post.title | |||
form.body.data = post.body | |||
form.category.data = post.category_id | |||
return render_template('admin/edit_post.html', form=form) | |||
@admin_bp.route('/post/<int:post_id>/delete', methods=['POST']) | |||
@login_required | |||
def delete_post(post_id): | |||
post = Post.query.get_or_404(post_id) | |||
db.session.delete(post) | |||
db.session.commit() | |||
flash('Post deleted.', 'success') | |||
return redirect_back() | |||
@admin_bp.route('/post/<int:post_id>/set-comment', methods=['POST']) | |||
@login_required | |||
def set_comment(post_id): | |||
post = Post.query.get_or_404(post_id) | |||
if post.can_comment: | |||
post.can_comment = False | |||
flash('Comment disabled.', 'success') | |||
else: | |||
post.can_comment = True | |||
flash('Comment enabled.', 'success') | |||
db.session.commit() | |||
return redirect_back() | |||
@admin_bp.route('/comment/manage') | |||
@login_required | |||
def manage_comment(): | |||
filter_rule = request.args.get('filter', 'all') # 'all', 'unreviewed', 'admin' | |||
page = request.args.get('page', 1, type=int) | |||
per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] | |||
if filter_rule == 'unread': | |||
filtered_comments = Comment.query.filter_by(reviewed=False) | |||
elif filter_rule == 'admin': | |||
filtered_comments = Comment.query.filter_by(from_admin=True) | |||
else: | |||
filtered_comments = Comment.query | |||
pagination = filtered_comments.order_by(Comment.timestamp.desc()).paginate(page, per_page=per_page) | |||
comments = pagination.items | |||
return render_template('admin/manage_comment.html', comments=comments, pagination=pagination) | |||
@admin_bp.route('/comment/<int:comment_id>/approve', methods=['POST']) | |||
@login_required | |||
def approve_comment(comment_id): | |||
comment = Comment.query.get_or_404(comment_id) | |||
comment.reviewed = True | |||
db.session.commit() | |||
flash('Comment published.', 'success') | |||
return redirect_back() | |||
@admin_bp.route('/comment/<int:comment_id>/delete', methods=['POST']) | |||
@login_required | |||
def delete_comment(comment_id): | |||
comment = Comment.query.get_or_404(comment_id) | |||
db.session.delete(comment) | |||
db.session.commit() | |||
flash('Comment deleted.', 'success') | |||
return redirect_back() | |||
@admin_bp.route('/category/manage') | |||
@login_required | |||
def manage_category(): | |||
return render_template('admin/manage_category.html') | |||
@admin_bp.route('/category/new', methods=['GET', 'POST']) | |||
@login_required | |||
def new_category(): | |||
form = CategoryForm() | |||
if form.validate_on_submit(): | |||
name = form.name.data | |||
category = Category(name=name) | |||
db.session.add(category) | |||
db.session.commit() | |||
flash('Category created.', 'success') | |||
return redirect(url_for('.manage_category')) | |||
return render_template('admin/new_category.html', form=form) | |||
@admin_bp.route('/category/<int:category_id>/edit', methods=['GET', 'POST']) | |||
@login_required | |||
def edit_category(category_id): | |||
form = CategoryForm() | |||
category = Category.query.get_or_404(category_id) | |||
if category.id == 1: | |||
flash('You can not edit the default category.', 'warning') | |||
return redirect(url_for('blog.index')) | |||
if form.validate_on_submit(): | |||
category.name = form.name.data | |||
db.session.commit() | |||
flash('Category updated.', 'success') | |||
return redirect(url_for('.manage_category')) | |||
form.name.data = category.name | |||
return render_template('admin/edit_category.html', form=form) | |||
@admin_bp.route('/category/<int:category_id>/delete', methods=['POST']) | |||
@login_required | |||
def delete_category(category_id): | |||
category = Category.query.get_or_404(category_id) | |||
if category.id == 1: | |||
flash('You can not delete the default category.', 'warning') | |||
return redirect(url_for('blog.index')) | |||
category.delete() | |||
flash('Category deleted.', 'success') | |||
return redirect(url_for('.manage_category')) | |||
@admin_bp.route('/link/manage') | |||
@login_required | |||
def manage_link(): | |||
return render_template('admin/manage_link.html') | |||
@admin_bp.route('/link/new', methods=['GET', 'POST']) | |||
@login_required | |||
def new_link(): | |||
form = LinkForm() | |||
if form.validate_on_submit(): | |||
name = form.name.data | |||
url = form.url.data | |||
link = Link(name=name, url=url) | |||
db.session.add(link) | |||
db.session.commit() | |||
flash('Link created.', 'success') | |||
return redirect(url_for('.manage_link')) | |||
return render_template('admin/new_link.html', form=form) | |||
@admin_bp.route('/link/<int:link_id>/edit', methods=['GET', 'POST']) | |||
@login_required | |||
def edit_link(link_id): | |||
form = LinkForm() | |||
link = Link.query.get_or_404(link_id) | |||
if form.validate_on_submit(): | |||
link.name = form.name.data | |||
link.url = form.url.data | |||
db.session.commit() | |||
flash('Link updated.', 'success') | |||
return redirect(url_for('.manage_link')) | |||
form.name.data = link.name | |||
form.url.data = link.url | |||
return render_template('admin/edit_link.html', form=form) | |||
@admin_bp.route('/link/<int:link_id>/delete', methods=['POST']) | |||
@login_required | |||
def delete_link(link_id): | |||
link = Link.query.get_or_404(link_id) | |||
db.session.delete(link) | |||
db.session.commit() | |||
flash('Link deleted.', 'success') | |||
return redirect(url_for('.manage_link')) | |||
@admin_bp.route('/uploads/<path:filename>') | |||
def get_image(filename): | |||
return send_from_directory(current_app.config['BLUELOG_UPLOAD_PATH'], filename) | |||
@admin_bp.route('/upload', methods=['POST']) | |||
def upload_image(): | |||
f = request.files.get('upload') | |||
if not allowed_file(f.filename): | |||
return upload_fail('Image only!') | |||
f.save(os.path.join(current_app.config['BLUELOG_UPLOAD_PATH'], f.filename)) | |||
url = url_for('.get_image', filename=f.filename) | |||
return upload_success(url, f.filename) |
@ -0,0 +1,86 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from flask import render_template, flash, redirect, url_for, Blueprint | |||
from flask_login import login_user, logout_user, login_required, current_user | |||
from bluelog.forms import LoginForm, RegisterForm | |||
from bluelog.models import Admin | |||
from bluelog.utils import redirect_back | |||
from bluelog.extensions import db | |||
from sqlalchemy.exc import IntegrityError | |||
auth_bp = Blueprint('auth', __name__) | |||
@auth_bp.route('/login', methods=['GET', 'POST']) | |||
def login(): | |||
if current_user.is_authenticated: | |||
return redirect(url_for('blog.index')) | |||
form = LoginForm() | |||
if form.validate_on_submit(): | |||
username = form.username.data | |||
password = form.password.data | |||
remember = form.remember.data | |||
admin = Admin.query.filter_by(username=username).first() | |||
# admin = Admin.query.first() | |||
if admin: | |||
if username == admin.username and admin.validate_password(password): | |||
login_user(admin, remember) | |||
flash('Welcome back.', 'info') | |||
return redirect_back() | |||
flash('Invalid username or password.', 'warning') | |||
else: | |||
flash('No account.', 'warning') | |||
return render_template('auth/login.html', form=form) | |||
@auth_bp.route('/logout') | |||
@login_required | |||
def logout(): | |||
logout_user() | |||
flash('Logout success.', 'info') | |||
return redirect_back() | |||
def create_user(username, password): | |||
obj = Admin( | |||
username=username, | |||
blog_title='Bluelog', | |||
blog_sub_title="No, I'm the real thing.", | |||
name='Mima Kirigoe', | |||
about='Um, l, Mima Kirigoe, had a fun time as a member of CHAM...' | |||
) | |||
obj.set_password(password) | |||
db.session.add(obj) | |||
code = 200 | |||
try: | |||
db.session.commit() | |||
return code | |||
except: | |||
code = 500 | |||
db.session.rollback() | |||
return code | |||
@auth_bp.route('/register', methods=['GET', 'POST']) | |||
def register(): | |||
form = RegisterForm() | |||
if form.validate_on_submit(): | |||
username = form.username.data | |||
password = form.password.data | |||
remember = form.remember.data | |||
code = create_user(username, password) | |||
if code == 200: | |||
flash('注册成功', 'info') | |||
return redirect_back() | |||
else: | |||
flash('存在该用户名', 'warning') | |||
return render_template('auth/register.html', form=form) |
@ -0,0 +1,106 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from flask import render_template, flash, redirect, url_for, request, current_app, Blueprint, abort, make_response | |||
from flask_login import current_user | |||
from bluelog.emails import send_new_comment_email, send_new_reply_email | |||
from bluelog.extensions import db | |||
from bluelog.forms import CommentForm, AdminCommentForm | |||
from bluelog.models import Post, Category, Comment | |||
from bluelog.utils import redirect_back | |||
blog_bp = Blueprint('blog', __name__) | |||
@blog_bp.route('/') | |||
def index(): | |||
page = request.args.get('page', 1, type=int) | |||
per_page = current_app.config['BLUELOG_POST_PER_PAGE'] | |||
pagination = Post.query.order_by(Post.timestamp.desc()).paginate(page, per_page=per_page) | |||
posts = pagination.items | |||
return render_template('blog/index.html', pagination=pagination, posts=posts) | |||
@blog_bp.route('/about') | |||
def about(): | |||
return render_template('blog/about.html') | |||
@blog_bp.route('/category/<int:category_id>') | |||
def show_category(category_id): | |||
category = Category.query.get_or_404(category_id) | |||
page = request.args.get('page', 1, type=int) | |||
per_page = current_app.config['BLUELOG_POST_PER_PAGE'] | |||
pagination = Post.query.with_parent(category).order_by(Post.timestamp.desc()).paginate(page, per_page) | |||
posts = pagination.items | |||
return render_template('blog/category.html', category=category, pagination=pagination, posts=posts) | |||
@blog_bp.route('/post/<int:post_id>', methods=['GET', 'POST']) | |||
def show_post(post_id): | |||
post = Post.query.get_or_404(post_id) | |||
page = request.args.get('page', 1, type=int) | |||
per_page = current_app.config['BLUELOG_COMMENT_PER_PAGE'] | |||
pagination = Comment.query.with_parent(post).filter_by(reviewed=True).order_by(Comment.timestamp.asc()).paginate( | |||
page, per_page) | |||
comments = pagination.items | |||
if current_user.is_authenticated: | |||
form = AdminCommentForm() | |||
form.author.data = current_user.name | |||
form.email.data = current_app.config['BLUELOG_EMAIL'] | |||
form.site.data = url_for('.index') | |||
from_admin = True | |||
reviewed = True | |||
else: | |||
form = CommentForm() | |||
from_admin = False | |||
reviewed = False | |||
if form.validate_on_submit(): | |||
author = form.author.data | |||
email = form.email.data | |||
site = form.site.data | |||
body = form.body.data | |||
comment = Comment( | |||
author=author, email=email, site=site, body=body, | |||
from_admin=from_admin, post=post, reviewed=reviewed) | |||
replied_id = request.args.get('reply') | |||
if replied_id: | |||
replied_comment = Comment.query.get_or_404(replied_id) | |||
comment.replied = replied_comment | |||
send_new_reply_email(replied_comment) | |||
db.session.add(comment) | |||
db.session.commit() | |||
if current_user.is_authenticated: # send message based on authentication status | |||
flash('Comment published.', 'success') | |||
else: | |||
flash('Thanks, your comment will be published after reviewed.', 'info') | |||
send_new_comment_email(post) # send notification email to admin | |||
return redirect(url_for('.show_post', post_id=post_id)) | |||
return render_template('blog/post.html', post=post, pagination=pagination, form=form, comments=comments) | |||
@blog_bp.route('/reply/comment/<int:comment_id>') | |||
def reply_comment(comment_id): | |||
comment = Comment.query.get_or_404(comment_id) | |||
if not comment.post.can_comment: | |||
flash('Comment is disabled.', 'warning') | |||
return redirect(url_for('.show_post', post_id=comment.post.id)) | |||
return redirect( | |||
url_for('.show_post', post_id=comment.post_id, reply=comment_id, author=comment.author) + '#comment-form') | |||
@blog_bp.route('/change-theme/<theme_name>') | |||
def change_theme(theme_name): | |||
if theme_name not in current_app.config['BLUELOG_THEMES'].keys(): | |||
abort(404) | |||
response = make_response(redirect_back()) | |||
response.set_cookie('theme', theme_name, max_age=30 * 24 * 60 * 60) | |||
return response |
@ -0,0 +1,44 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from threading import Thread | |||
from flask import url_for, current_app | |||
from flask_mail import Message | |||
from bluelog.extensions import mail | |||
def _send_async_mail(app, message): | |||
with app.app_context(): | |||
mail.send(message) | |||
def send_mail(subject, to, html): | |||
app = current_app._get_current_object() | |||
message = Message(subject, recipients=[to], html=html) | |||
thr = Thread(target=_send_async_mail, args=[app, message]) | |||
thr.start() | |||
return thr | |||
def send_new_comment_email(post): | |||
post_url = url_for('blog.show_post', post_id=post.id, _external=True) + '#comments' | |||
send_mail(subject='New comment', to=current_app.config['BLUELOG_EMAIL'], | |||
html='<p>New comment in post <i>%s</i>, click the link below to check:</p>' | |||
'<p><a href="%s">%s</a></P>' | |||
'<p><small style="color: #868e96">Do not reply this email.</small></p>' | |||
% (post.title, post_url, post_url)) | |||
def send_new_reply_email(comment): | |||
post_url = url_for('blog.show_post', post_id=comment.post_id, _external=True) + '#comments' | |||
send_mail(subject='New reply', to=comment.email, | |||
html='<p>New reply for the comment you left in post <i>%s</i>, click the link below to check: </p>' | |||
'<p><a href="%s">%s</a></p>' | |||
'<p><small style="color: #868e96">Do not reply this email.</small></p>' | |||
% (comment.post.title, post_url, post_url)) |
@ -0,0 +1,38 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from flask_bootstrap import Bootstrap | |||
from flask_ckeditor import CKEditor | |||
from flask_login import LoginManager | |||
from flask_mail import Mail | |||
from flask_moment import Moment | |||
from flask_sqlalchemy import SQLAlchemy | |||
from flask_wtf import CSRFProtect | |||
from flask_debugtoolbar import DebugToolbarExtension | |||
from flask_migrate import Migrate | |||
bootstrap = Bootstrap() | |||
db = SQLAlchemy() | |||
login_manager = LoginManager() | |||
csrf = CSRFProtect() | |||
ckeditor = CKEditor() | |||
mail = Mail() | |||
moment = Moment() | |||
toolbar = DebugToolbarExtension() | |||
migrate = Migrate() | |||
@login_manager.user_loader | |||
def load_user(user_id): | |||
from bluelog.models import Admin | |||
user = Admin.query.get(int(user_id)) | |||
return user | |||
login_manager.login_view = 'auth.login' | |||
# login_manager.login_message = 'Your custom message' | |||
login_manager.login_message_category = 'warning' |
@ -0,0 +1,216 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
import random | |||
from faker import Faker | |||
from sqlalchemy.exc import IntegrityError | |||
from bluelog.extensions import db | |||
from bluelog.models import Admin, Category, Post, Comment, Link | |||
fake = Faker("zh_CN") | |||
def fake_admin(): | |||
admin = Admin( | |||
username='admin', | |||
blog_title='博客', | |||
blog_sub_title="写下你的感悟。", | |||
name='思悟', | |||
about='思世间之大,悟人生变换。' | |||
) | |||
admin.set_password('helloflask') | |||
db.session.add(admin) | |||
db.session.commit() | |||
def fake_categories(count=10): | |||
category1 = Category(name='新冠疫情') | |||
db.session.add(category1) | |||
category2 = Category(name='健康') | |||
db.session.add(category2) | |||
category3 = Category(name='体育') | |||
db.session.add(category3) | |||
category5 = Category(name='经济') | |||
db.session.add(category5) | |||
category6 = Category(name='军事') | |||
db.session.add(category6) | |||
category7 = Category(name='艺术') | |||
db.session.add(category7) | |||
try: | |||
db.session.commit() | |||
except IntegrityError: | |||
db.session.rollback() | |||
# path="D:/这学期/云计算/云计算项目/bluelog" | |||
path="" | |||
def fake_posts(count=15): | |||
f1 = open(path+"/root/content/文本/content.txt", "r", encoding="utf-8") | |||
content1 = f1.readlines() | |||
f2 = open(path+"/root/content/文本/title.txt", "r", encoding="utf-8") | |||
head1 = f2.readlines() | |||
# f3 = open(path+"/root/content/健康/content.txt", "r", encoding="utf-8") | |||
# content2 = f3.readlines() | |||
# f4 = open(path+"/root/content/健康/title.txt", "r", encoding="utf-8") | |||
# head2 = f4.readlines() | |||
f5 = open(path+"/root/content/体育/content.txt", "r", encoding="utf-8") | |||
content3 = f5.readlines() | |||
f6 = open(path+"/root/content/体育/title.txt", "r", encoding="utf-8") | |||
head3 = f6.readlines() | |||
# f7 = open("C:/Users/12293/Desktop/程序员/content.txt", "r", encoding="utf-8") | |||
# content4 = f7.readlines() | |||
# f8 = open("C:/Users/12293/Desktop/程序员/title.txt", "r", encoding="utf-8") | |||
# head4 = f8.readlines() | |||
f9 = open(path+"/root/content/经济/content.txt", "r", encoding="utf-8") | |||
content5 = f9.readlines() | |||
f10 = open(path+"/root/content/经济/title.txt", "r", encoding="utf-8") | |||
head5 = f10.readlines() | |||
f11 = open(path+"/root/content/军事/content.txt", "r", encoding="utf-8") | |||
content6 = f11.readlines() | |||
f12 = open(path+"/root/content/军事/title.txt", "r", encoding="utf-8") | |||
head6 = f12.readlines() | |||
f13 = open(path+"/root/content/艺术/content.txt", "r", encoding="utf-8") | |||
content7 = f13.readlines() | |||
f14 = open(path+"/root/content/艺术/title.txt", "r", encoding="utf-8") | |||
head7 = f14.readlines() | |||
for i in range(count): | |||
post1 = Post( | |||
title=head1[i][:-19], | |||
body=content1[i], | |||
category=Category.query.get(1), | |||
timestamp=fake.date_time_this_year() | |||
) | |||
# post2 = Post( | |||
# title=head2[i], | |||
# body=content2[i], | |||
# category=Category.query.get(2), | |||
# timestamp=fake.date_time_this_year() | |||
# ) | |||
post3 = Post( | |||
title=head3[i][:-19], | |||
body=content3[i], | |||
category=Category.query.get(3), | |||
timestamp=fake.date_time_this_year() | |||
) | |||
# post4 = Post( | |||
# title=head4[i], | |||
# body=content4[i], | |||
# category=Category.query.get(4), | |||
# timestamp=fake.date_time_this_year() | |||
# ) | |||
post5 = Post( | |||
title=head5[i][:-19], | |||
body=content5[i], | |||
category=Category.query.get(4), | |||
timestamp=fake.date_time_this_year() | |||
) | |||
post6 = Post( | |||
title=head6[i][:-19], | |||
body=content6[i], | |||
category=Category.query.get(5), | |||
timestamp=fake.date_time_this_year() | |||
) | |||
post7 = Post( | |||
title=head7[i][:-19], | |||
body=content7[i], | |||
category=Category.query.get(6), | |||
timestamp=fake.date_time_this_year() | |||
) | |||
db.session.add(post1) | |||
# db.session.add(post2) | |||
db.session.add(post3) | |||
# db.session.add(post4) | |||
db.session.add(post5) | |||
db.session.add(post6) | |||
db.session.add(post7) | |||
db.session.commit() | |||
# for i in range(count): | |||
# post = Post( | |||
# title=fake.sentence(), | |||
# body=fake.text(2000), | |||
# category=Category.query.get(random.randint(1, Category.query.count())), | |||
# timestamp=fake.date_time_this_year() | |||
# ) | |||
# print(type(fake.sentence()),type(fake.text(2000))) | |||
# print(fake.sentence(),fake.text(2000)) | |||
# | |||
# db.session.add(post) | |||
# db.session.commit() | |||
def fake_comments(count=500): | |||
for i in range(count): | |||
comment = Comment( | |||
author=fake.name(), | |||
email=fake.email(), | |||
site=fake.url(), | |||
body=fake.sentence(), | |||
timestamp=fake.date_time_this_year(), | |||
reviewed=True, | |||
post=Post.query.get(random.randint(1, Post.query.count())) | |||
) | |||
db.session.add(comment) | |||
salt = int(count * 0.1) | |||
for i in range(salt): | |||
# unreviewed comments | |||
comment = Comment( | |||
author=fake.name(), | |||
email=fake.email(), | |||
site=fake.url(), | |||
body=fake.sentence(), | |||
timestamp=fake.date_time_this_year(), | |||
reviewed=False, | |||
post=Post.query.get(random.randint(1, Post.query.count())) | |||
) | |||
db.session.add(comment) | |||
# from admin | |||
comment = Comment( | |||
author='Mima Kirigoe', | |||
email='mima@example.com', | |||
site='example.com', | |||
body=fake.sentence(), | |||
timestamp=fake.date_time_this_year(), | |||
from_admin=True, | |||
reviewed=True, | |||
post=Post.query.get(random.randint(1, Post.query.count())) | |||
) | |||
db.session.add(comment) | |||
db.session.commit() | |||
# replies | |||
for i in range(salt): | |||
comment = Comment( | |||
author=fake.name(), | |||
email=fake.email(), | |||
site=fake.url(), | |||
body=fake.sentence(), | |||
timestamp=fake.date_time_this_year(), | |||
reviewed=True, | |||
replied=Comment.query.get(random.randint(1, Comment.query.count())), | |||
post=Post.query.get(random.randint(1, Post.query.count())) | |||
) | |||
db.session.add(comment) | |||
db.session.commit() | |||
def fake_links(): | |||
twitter = Link(name='Twitter', url='#') | |||
facebook = Link(name='Facebook', url='#') | |||
linkedin = Link(name='LinkedIn', url='#') | |||
google = Link(name='Google+', url='#') | |||
db.session.add_all([twitter, facebook, linkedin, google]) | |||
db.session.commit() |
@ -0,0 +1,77 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from flask_ckeditor import CKEditorField | |||
from flask_wtf import FlaskForm | |||
from wtforms import StringField, SubmitField, SelectField, TextAreaField, ValidationError, HiddenField, \ | |||
BooleanField, PasswordField | |||
from wtforms.validators import DataRequired, Email, Length, Optional, URL | |||
from bluelog.models import Category | |||
class LoginForm(FlaskForm): | |||
username = StringField('Username', validators=[DataRequired(), Length(1, 20)]) | |||
password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)]) | |||
remember = BooleanField('Remember me') | |||
submit = SubmitField('Log in') | |||
class RegisterForm(FlaskForm): | |||
username = StringField('Username', validators=[DataRequired(), Length(1, 20)]) | |||
password = PasswordField('Password', validators=[DataRequired(), Length(1, 128)]) | |||
remember = BooleanField('Remember me') | |||
submit = SubmitField('Register') | |||
class SettingForm(FlaskForm): | |||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||
blog_title = StringField('Blog Title', validators=[DataRequired(), Length(1, 60)]) | |||
blog_sub_title = StringField('Blog Sub Title', validators=[DataRequired(), Length(1, 100)]) | |||
about = CKEditorField('About Page', validators=[DataRequired()]) | |||
submit = SubmitField() | |||
class PostForm(FlaskForm): | |||
title = StringField('Title', validators=[DataRequired(), Length(1, 60)]) | |||
category = SelectField('Category', coerce=int, default=1) | |||
body = CKEditorField('Body', validators=[DataRequired()]) | |||
submit = SubmitField() | |||
def __init__(self, *args, **kwargs): | |||
super(PostForm, self).__init__(*args, **kwargs) | |||
self.category.choices = [(category.id, category.name) | |||
for category in Category.query.order_by(Category.name).all()] | |||
class CategoryForm(FlaskForm): | |||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||
submit = SubmitField() | |||
def validate_name(self, field): | |||
if Category.query.filter_by(name=field.data).first(): | |||
raise ValidationError('Name already in use.') | |||
class CommentForm(FlaskForm): | |||
author = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||
email = StringField('Email', validators=[DataRequired(), Email(), Length(1, 254)]) | |||
site = StringField('Site', validators=[Optional(), URL(), Length(0, 255)]) | |||
body = TextAreaField('Comment', validators=[DataRequired()]) | |||
submit = SubmitField() | |||
class AdminCommentForm(CommentForm): | |||
author = HiddenField() | |||
email = HiddenField() | |||
site = HiddenField() | |||
class LinkForm(FlaskForm): | |||
name = StringField('Name', validators=[DataRequired(), Length(1, 30)]) | |||
url = StringField('URL', validators=[DataRequired(), URL(), Length(1, 255)]) | |||
submit = SubmitField() |
@ -0,0 +1,84 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
from datetime import datetime | |||
from flask_login import UserMixin | |||
from werkzeug.security import generate_password_hash, check_password_hash | |||
from bluelog.extensions import db | |||
class Admin(db.Model, UserMixin): | |||
id = db.Column(db.Integer, primary_key=True) | |||
username = db.Column(db.String(20), unique=True) | |||
password_hash = db.Column(db.String(128)) | |||
blog_title = db.Column(db.String(60)) | |||
blog_sub_title = db.Column(db.String(100)) | |||
name = db.Column(db.String(30)) | |||
about = db.Column(db.Text) | |||
def set_password(self, password): | |||
self.password_hash = generate_password_hash(password) | |||
def validate_password(self, password): | |||
return check_password_hash(self.password_hash, password) | |||
class Category(db.Model): | |||
id = db.Column(db.Integer, primary_key=True) | |||
name = db.Column(db.String(30), unique=True) | |||
posts = db.relationship('Post', back_populates='category') | |||
def delete(self): | |||
default_category = Category.query.get(1) | |||
posts = self.posts[:] | |||
for post in posts: | |||
post.category = default_category | |||
db.session.delete(self) | |||
db.session.commit() | |||
class Post(db.Model): | |||
id = db.Column(db.Integer, primary_key=True) | |||
title = db.Column(db.String(60)) | |||
body = db.Column(db.Text) | |||
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) | |||
can_comment = db.Column(db.Boolean, default=True) | |||
category_id = db.Column(db.Integer, db.ForeignKey('category.id')) | |||
category = db.relationship('Category', back_populates='posts') | |||
comments = db.relationship('Comment', back_populates='post', cascade='all, delete-orphan') | |||
class Comment(db.Model): | |||
id = db.Column(db.Integer, primary_key=True) | |||
author = db.Column(db.String(30)) | |||
email = db.Column(db.String(254)) | |||
site = db.Column(db.String(255)) | |||
body = db.Column(db.Text) | |||
from_admin = db.Column(db.Boolean, default=False) | |||
reviewed = db.Column(db.Boolean, default=False) | |||
timestamp = db.Column(db.DateTime, default=datetime.utcnow, index=True) | |||
replied_id = db.Column(db.Integer, db.ForeignKey('comment.id')) | |||
post_id = db.Column(db.Integer, db.ForeignKey('post.id')) | |||
post = db.relationship('Post', back_populates='comments') | |||
replies = db.relationship('Comment', back_populates='replied', cascade='all, delete-orphan') | |||
replied = db.relationship('Comment', back_populates='replies', remote_side=[id]) | |||
# Same with: | |||
# replies = db.relationship('Comment', backref=db.backref('replied', remote_side=[id]), | |||
# cascade='all,delete-orphan') | |||
class Link(db.Model): | |||
id = db.Column(db.Integer, primary_key=True) | |||
name = db.Column(db.String(30)) | |||
url = db.Column(db.String(255)) |
@ -0,0 +1,74 @@ | |||
# -*- coding: utf-8 -*- | |||
""" | |||
:author: Grey Li (李辉) | |||
:url: http://greyli.com | |||
:copyright: © 2018 Grey Li <withlihui@gmail.com> | |||
:license: MIT, see LICENSE for more details. | |||
""" | |||
import os | |||
import sys | |||
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) | |||
#等会记得改回去 | |||
myhost="localhost" #数据库内网地址 | |||
myroot="root" | |||
mypwd="hsy19991231" | |||
mydb="bluelog" | |||
# SQLite URI compatible | |||
WIN = sys.platform.startswith('win') | |||
if WIN: | |||
prefix = 'sqlite:///' | |||
else: | |||
prefix = 'sqlite:////' | |||
class BaseConfig(object): | |||
SECRET_KEY = os.getenv('SECRET_KEY', 'dev key') | |||
DEBUG_TB_INTERCEPT_REDIRECTS = False | |||
SQLALCHEMY_TRACK_MODIFICATIONS = False | |||
SQLALCHEMY_RECORD_QUERIES = True | |||
CKEDITOR_ENABLE_CSRF = True | |||
CKEDITOR_FILE_UPLOADER = 'admin.upload_image' | |||
MAIL_SERVER = os.getenv('MAIL_SERVER') | |||
MAIL_PORT = 465 | |||
MAIL_USE_SSL = True | |||
MAIL_USERNAME = os.getenv('MAIL_USERNAME') | |||
MAIL_PASSWORD = os.getenv('MAIL_PASSWORD') | |||
MAIL_DEFAULT_SENDER = ('Bluelog Admin', MAIL_USERNAME) | |||
BLUELOG_EMAIL = os.getenv('BLUELOG_EMAIL') | |||
BLUELOG_POST_PER_PAGE = 10 | |||
BLUELOG_MANAGE_POST_PER_PAGE = 15 | |||
BLUELOG_COMMENT_PER_PAGE = 15 | |||
# ('theme name', 'display name') | |||
BLUELOG_THEMES = {'perfect_blue': 'Perfect Blue', 'black_swan': 'Black Swan'} | |||
BLUELOG_SLOW_QUERY_THRESHOLD = 1 | |||
BLUELOG_UPLOAD_PATH = os.path.join(basedir, 'uploads') | |||
BLUELOG_ALLOWED_IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif'] | |||
class DevelopmentConfig(BaseConfig): | |||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb #连接mysql数据库 | |||
class TestingConfig(BaseConfig): | |||
TESTING = True | |||
WTF_CSRF_ENABLED = False | |||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb # in-memory database | |||
class ProductionConfig(BaseConfig): | |||
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://"+myroot+":"+mypwd+"@"+myhost+"/"+mydb | |||
config = { | |||
'development': DevelopmentConfig, | |||
'testing': TestingConfig, | |||
'production': ProductionConfig | |||
} |
@ -0,0 +1,39 @@ | |||
CKEditor 4 | |||
========== | |||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
http://ckeditor.com - See LICENSE.md for license information. | |||
CKEditor is a text editor to be used inside web pages. It's not a replacement | |||
for desktop text editors like Word or OpenOffice, but a component to be used as | |||
part of web applications and websites. | |||
## Documentation | |||
The full editor documentation is available online at the following address: | |||
http://docs.ckeditor.com | |||
## Installation | |||
Installing CKEditor is an easy task. Just follow these simple steps: | |||
1. **Download** the latest version from the CKEditor website: | |||
http://ckeditor.com. You should have already completed this step, but be | |||
sure you have the very latest version. | |||
2. **Extract** (decompress) the downloaded file into the root of your website. | |||
**Note:** CKEditor is by default installed in the `ckeditor` folder. You can | |||
place the files in whichever you want though. | |||
## Checking Your Installation | |||
The editor comes with a few sample pages that can be used to verify that | |||
installation proceeded properly. Take a look at the `samples` directory. | |||
To test your installation, just call the following page at your website: | |||
http://<your site>/<CKEditor installation path>/samples/index.html | |||
For example: | |||
http://www.example.com/ckeditor/samples/index.html |
@ -0,0 +1,10 @@ | |||
/* | |||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||
*/ | |||
(function(a){if("undefined"==typeof a)throw Error("jQuery should be loaded before CKEditor jQuery adapter.");if("undefined"==typeof CKEDITOR)throw Error("CKEditor should be loaded before CKEditor jQuery adapter.");CKEDITOR.config.jqueryOverrideVal="undefined"==typeof CKEDITOR.config.jqueryOverrideVal?!0:CKEDITOR.config.jqueryOverrideVal;a.extend(a.fn,{ckeditorGet:function(){var a=this.eq(0).data("ckeditorInstance");if(!a)throw"CKEditor is not initialized yet, use ckeditor() with a callback.";return a}, | |||
ckeditor:function(g,d){if(!CKEDITOR.env.isCompatible)throw Error("The environment is incompatible.");if(!a.isFunction(g)){var m=d;d=g;g=m}var k=[];d=d||{};this.each(function(){var b=a(this),c=b.data("ckeditorInstance"),f=b.data("_ckeditorInstanceLock"),h=this,l=new a.Deferred;k.push(l.promise());if(c&&!f)g&&g.apply(c,[this]),l.resolve();else if(f)c.once("instanceReady",function(){setTimeout(function(){c.element?(c.element.$==h&&g&&g.apply(c,[h]),l.resolve()):setTimeout(arguments.callee,100)},0)}, | |||
null,null,9999);else{if(d.autoUpdateElement||"undefined"==typeof d.autoUpdateElement&&CKEDITOR.config.autoUpdateElement)d.autoUpdateElementJquery=!0;d.autoUpdateElement=!1;b.data("_ckeditorInstanceLock",!0);c=a(this).is("textarea")?CKEDITOR.replace(h,d):CKEDITOR.inline(h,d);b.data("ckeditorInstance",c);c.on("instanceReady",function(d){var e=d.editor;setTimeout(function(){if(e.element){d.removeListener();e.on("dataReady",function(){b.trigger("dataReady.ckeditor",[e])});e.on("setData",function(a){b.trigger("setData.ckeditor", | |||
[e,a.data])});e.on("getData",function(a){b.trigger("getData.ckeditor",[e,a.data])},999);e.on("destroy",function(){b.trigger("destroy.ckeditor",[e])});e.on("save",function(){a(h.form).submit();return!1},null,null,20);if(e.config.autoUpdateElementJquery&&b.is("textarea")&&a(h.form).length){var c=function(){b.ckeditor(function(){e.updateElement()})};a(h.form).submit(c);a(h.form).bind("form-pre-serialize",c);b.bind("destroy.ckeditor",function(){a(h.form).unbind("submit",c);a(h.form).unbind("form-pre-serialize", | |||
c)})}e.on("destroy",function(){b.removeData("ckeditorInstance")});b.removeData("_ckeditorInstanceLock");b.trigger("instanceReady.ckeditor",[e]);g&&g.apply(e,[h]);l.resolve()}else setTimeout(arguments.callee,100)},0)},null,null,9999)}});var f=new a.Deferred;this.promise=f.promise();a.when.apply(this,k).then(function(){f.resolve()});this.editor=this.eq(0).data("ckeditorInstance");return this}});CKEDITOR.config.jqueryOverrideVal&&(a.fn.val=CKEDITOR.tools.override(a.fn.val,function(g){return function(d){if(arguments.length){var m= | |||
this,k=[],f=this.each(function(){var b=a(this),c=b.data("ckeditorInstance");if(b.is("textarea")&&c){var f=new a.Deferred;c.setData(d,function(){f.resolve()});k.push(f.promise());return!0}return g.call(b,d)});if(k.length){var b=new a.Deferred;a.when.apply(this,k).done(function(){b.resolveWith(m)});return b.promise()}return f}var f=a(this).eq(0),c=f.data("ckeditorInstance");return f.is("textarea")&&c?c.getData():g.call(f)}}))})(window.jQuery); |
@ -0,0 +1,165 @@ | |||
/** | |||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
* For licensing, see LICENSE.md or http://ckeditor.com/license | |||
*/ | |||
/** | |||
* This file was added automatically by CKEditor builder. | |||
* You may re-use it at any time to build CKEditor again. | |||
* | |||
* If you would like to build CKEditor online again | |||
* (for example to upgrade), visit one the following links: | |||
* | |||
* (1) http://ckeditor.com/builder | |||
* Visit online builder to build CKEditor from scratch. | |||
* | |||
* (2) http://ckeditor.com/builder/2f57fc244039d7273aeb5bb45ddb4866 | |||
* Visit online builder to build CKEditor, starting with the same setup as before. | |||
* | |||
* (3) http://ckeditor.com/builder/download/2f57fc244039d7273aeb5bb45ddb4866 | |||
* Straight download link to the latest version of CKEditor (Optimized) with the same setup as before. | |||
* | |||
* NOTE: | |||
* This file is not used by CKEditor, you may remove it. | |||
* Changing this file will not change your CKEditor configuration. | |||
*/ | |||
var CKBUILDER_CONFIG = { | |||
skin: 'moono-lisa', | |||
preset: 'standard', | |||
ignore: [ | |||
'.DS_Store', | |||
'.bender', | |||
'.editorconfig', | |||
'.gitattributes', | |||
'.gitignore', | |||
'.idea', | |||
'.jscsrc', | |||
'.jshintignore', | |||
'.jshintrc', | |||
'.mailmap', | |||
'.travis.yml', | |||
'bender-err.log', | |||
'bender-out.log', | |||
'bender.ci.js', | |||
'bender.js', | |||
'dev', | |||
'gruntfile.js', | |||
'less', | |||
'node_modules', | |||
'package.json', | |||
'tests' | |||
], | |||
plugins : { | |||
'a11yhelp' : 1, | |||
'about' : 1, | |||
'basicstyles' : 1, | |||
'blockquote' : 1, | |||
'clipboard' : 1, | |||
'contextmenu' : 1, | |||
'elementspath' : 1, | |||
'enterkey' : 1, | |||
'entities' : 1, | |||
'filebrowser' : 1, | |||
'floatingspace' : 1, | |||
'format' : 1, | |||
'horizontalrule' : 1, | |||
'htmlwriter' : 1, | |||
'image' : 1, | |||
'indentlist' : 1, | |||
'link' : 1, | |||
'list' : 1, | |||
'magicline' : 1, | |||
'maximize' : 1, | |||
'pastefromword' : 1, | |||
'pastetext' : 1, | |||
'removeformat' : 1, | |||
'resize' : 1, | |||
'scayt' : 1, | |||
'showborders' : 1, | |||
'sourcearea' : 1, | |||
'specialchar' : 1, | |||
'stylescombo' : 1, | |||
'tab' : 1, | |||
'table' : 1, | |||
'tableselection' : 1, | |||
'tabletools' : 1, | |||
'toolbar' : 1, | |||
'undo' : 1, | |||
'uploadimage' : 1, | |||
'wsc' : 1, | |||
'wysiwygarea' : 1 | |||
}, | |||
languages : { | |||
'af' : 1, | |||
'ar' : 1, | |||
'az' : 1, | |||
'bg' : 1, | |||
'bn' : 1, | |||
'bs' : 1, | |||
'ca' : 1, | |||
'cs' : 1, | |||
'cy' : 1, | |||
'da' : 1, | |||
'de' : 1, | |||
'de-ch' : 1, | |||
'el' : 1, | |||
'en' : 1, | |||
'en-au' : 1, | |||
'en-ca' : 1, | |||
'en-gb' : 1, | |||
'eo' : 1, | |||
'es' : 1, | |||
'es-mx' : 1, | |||
'et' : 1, | |||
'eu' : 1, | |||
'fa' : 1, | |||
'fi' : 1, | |||
'fo' : 1, | |||
'fr' : 1, | |||
'fr-ca' : 1, | |||
'gl' : 1, | |||
'gu' : 1, | |||
'he' : 1, | |||
'hi' : 1, | |||
'hr' : 1, | |||
'hu' : 1, | |||
'id' : 1, | |||
'is' : 1, | |||
'it' : 1, | |||
'ja' : 1, | |||
'ka' : 1, | |||
'km' : 1, | |||
'ko' : 1, | |||
'ku' : 1, | |||
'lt' : 1, | |||
'lv' : 1, | |||
'mk' : 1, | |||
'mn' : 1, | |||
'ms' : 1, | |||
'nb' : 1, | |||
'nl' : 1, | |||
'no' : 1, | |||
'oc' : 1, | |||
'pl' : 1, | |||
'pt' : 1, | |||
'pt-br' : 1, | |||
'ro' : 1, | |||
'ru' : 1, | |||
'si' : 1, | |||
'sk' : 1, | |||
'sl' : 1, | |||
'sq' : 1, | |||
'sr' : 1, | |||
'sr-latn' : 1, | |||
'sv' : 1, | |||
'th' : 1, | |||
'tr' : 1, | |||
'tt' : 1, | |||
'ug' : 1, | |||
'uk' : 1, | |||
'vi' : 1, | |||
'zh' : 1, | |||
'zh-cn' : 1 | |||
} | |||
}; |
@ -0,0 +1,38 @@ | |||
/** | |||
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
* For licensing, see https://ckeditor.com/legal/ckeditor-oss-license | |||
*/ | |||
CKEDITOR.editorConfig = function( config ) { | |||
// Define changes to default configuration here. | |||
// For complete reference see: | |||
// http://docs.ckeditor.com/#!/api/CKEDITOR.config | |||
// The toolbar groups arrangement, optimized for two toolbar rows. | |||
config.toolbarGroups = [ | |||
{ name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, | |||
{ name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] }, | |||
{ name: 'links' }, | |||
{ name: 'insert' }, | |||
{ name: 'forms' }, | |||
{ name: 'tools' }, | |||
{ name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, | |||
{ name: 'others' }, | |||
'/', | |||
{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, | |||
{ name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] }, | |||
{ name: 'styles' }, | |||
{ name: 'colors' }, | |||
{ name: 'about' } | |||
]; | |||
// Remove some buttons provided by the standard plugins, which are | |||
// not needed in the Standard(s) toolbar. | |||
config.removeButtons = 'Underline,Subscript,Superscript'; | |||
// Set the most common block elements. | |||
config.format_tags = 'p;h1;h2;h3;pre'; | |||
// Simplify the dialog windows. | |||
config.removeDialogTabs = 'image:advanced;link:advanced'; | |||
}; |
@ -0,0 +1,207 @@ | |||
/* | |||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||
*/ | |||
body | |||
{ | |||
/* Font */ | |||
font-family: sans-serif, Arial, Verdana, "Trebuchet MS"; | |||
font-size: 12px; | |||
/* Text color */ | |||
color: #333; | |||
/* Remove the background color to make it transparent */ | |||
background-color: #fff; | |||
margin: 20px; | |||
} | |||
.cke_editable | |||
{ | |||
font-size: 13px; | |||
line-height: 1.6; | |||
/* Fix for missing scrollbars with RTL texts. (#10488) */ | |||
word-wrap: break-word; | |||
} | |||
blockquote | |||
{ | |||
font-style: italic; | |||
font-family: Georgia, Times, "Times New Roman", serif; | |||
padding: 2px 0; | |||
border-style: solid; | |||
border-color: #ccc; | |||
border-width: 0; | |||
} | |||
.cke_contents_ltr blockquote | |||
{ | |||
padding-left: 20px; | |||
padding-right: 8px; | |||
border-left-width: 5px; | |||
} | |||
.cke_contents_rtl blockquote | |||
{ | |||
padding-left: 8px; | |||
padding-right: 20px; | |||
border-right-width: 5px; | |||
} | |||
a | |||
{ | |||
color: #0782C1; | |||
} | |||
ol,ul,dl | |||
{ | |||
/* IE7: reset rtl list margin. (#7334) */ | |||
*margin-right: 0px; | |||
/* preserved spaces for list items with text direction other than the list. (#6249,#8049)*/ | |||
padding: 0 40px; | |||
} | |||
h1,h2,h3,h4,h5,h6 | |||
{ | |||
font-weight: normal; | |||
line-height: 1.2; | |||
} | |||
hr | |||
{ | |||
border: 0px; | |||
border-top: 1px solid #ccc; | |||
} | |||
img.right | |||
{ | |||
border: 1px solid #ccc; | |||
float: right; | |||
margin-left: 15px; | |||
padding: 5px; | |||
} | |||
img.left | |||
{ | |||
border: 1px solid #ccc; | |||
float: left; | |||
margin-right: 15px; | |||
padding: 5px; | |||
} | |||
pre | |||
{ | |||
white-space: pre-wrap; /* CSS 2.1 */ | |||
word-wrap: break-word; /* IE7 */ | |||
-moz-tab-size: 4; | |||
tab-size: 4; | |||
} | |||
.marker | |||
{ | |||
background-color: Yellow; | |||
} | |||
span[lang] | |||
{ | |||
font-style: italic; | |||
} | |||
figure | |||
{ | |||
text-align: center; | |||
outline: solid 1px #ccc; | |||
background: rgba(0,0,0,0.05); | |||
padding: 10px; | |||
margin: 10px 20px; | |||
display: inline-block; | |||
} | |||
figure > figcaption | |||
{ | |||
text-align: center; | |||
display: block; /* For IE8 */ | |||
} | |||
a > img { | |||
padding: 1px; | |||
margin: 1px; | |||
border: none; | |||
outline: 1px solid #0782C1; | |||
} | |||
/* Widget Styles */ | |||
.code-featured | |||
{ | |||
border: 5px solid red; | |||
} | |||
.math-featured | |||
{ | |||
padding: 20px; | |||
box-shadow: 0 0 2px rgba(200, 0, 0, 1); | |||
background-color: rgba(255, 0, 0, 0.05); | |||
margin: 10px; | |||
} | |||
.image-clean | |||
{ | |||
border: 0; | |||
background: none; | |||
padding: 0; | |||
} | |||
.image-clean > figcaption | |||
{ | |||
font-size: .9em; | |||
text-align: right; | |||
} | |||
.image-grayscale | |||
{ | |||
background-color: white; | |||
color: #666; | |||
} | |||
.image-grayscale img, img.image-grayscale | |||
{ | |||
filter: grayscale(100%); | |||
} | |||
.embed-240p | |||
{ | |||
max-width: 426px; | |||
max-height: 240px; | |||
margin:0 auto; | |||
} | |||
.embed-360p | |||
{ | |||
max-width: 640px; | |||
max-height: 360px; | |||
margin:0 auto; | |||
} | |||
.embed-480p | |||
{ | |||
max-width: 854px; | |||
max-height: 480px; | |||
margin:0 auto; | |||
} | |||
.embed-720p | |||
{ | |||
max-width: 1280px; | |||
max-height: 720px; | |||
margin:0 auto; | |||
} | |||
.embed-1080p | |||
{ | |||
max-width: 1920px; | |||
max-height: 1080px; | |||
margin:0 auto; | |||
} |
@ -0,0 +1,10 @@ | |||
/* | |||
Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved. | |||
For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | |||
*/ | |||
CKEDITOR.dialog.add("a11yHelp",function(e){var a=e.lang.a11yhelp,b=e.lang.common.keyboard,q=CKEDITOR.tools.getNextId(),d={8:b[8],9:a.tab,13:b[13],16:b[16],17:b[17],18:b[18],19:a.pause,20:a.capslock,27:a.escape,33:a.pageUp,34:a.pageDown,35:b[35],36:b[36],37:a.leftArrow,38:a.upArrow,39:a.rightArrow,40:a.downArrow,45:a.insert,46:b[46],91:a.leftWindowKey,92:a.rightWindowKey,93:a.selectKey,96:a.numpad0,97:a.numpad1,98:a.numpad2,99:a.numpad3,100:a.numpad4,101:a.numpad5,102:a.numpad6,103:a.numpad7,104:a.numpad8, | |||
105:a.numpad9,106:a.multiply,107:a.add,109:a.subtract,110:a.decimalPoint,111:a.divide,112:a.f1,113:a.f2,114:a.f3,115:a.f4,116:a.f5,117:a.f6,118:a.f7,119:a.f8,120:a.f9,121:a.f10,122:a.f11,123:a.f12,144:a.numLock,145:a.scrollLock,186:a.semiColon,187:a.equalSign,188:a.comma,189:a.dash,190:a.period,191:a.forwardSlash,192:a.graveAccent,219:a.openBracket,220:a.backSlash,221:a.closeBracket,222:a.singleQuote};d[CKEDITOR.ALT]=b[18];d[CKEDITOR.SHIFT]=b[16];d[CKEDITOR.CTRL]=CKEDITOR.env.mac?b[224]:b[17];var k= | |||
[CKEDITOR.ALT,CKEDITOR.SHIFT,CKEDITOR.CTRL],r=/\$\{(.*?)\}/g,t=function(a,b){var c=e.getCommandKeystroke(b);if(c){for(var l,f,h=[],g=0;g<k.length;g++)f=k[g],l=c/k[g],1<l&&2>=l&&(c-=f,h.push(d[f]));h.push(d[c]||String.fromCharCode(c));c=h.join("+")}else c=a;return c};return{title:a.title,minWidth:600,minHeight:400,contents:[{id:"info",label:e.lang.common.generalTab,expand:!0,elements:[{type:"html",id:"legends",style:"white-space:normal;",focus:function(){this.getElement().focus()},html:function(){for(var b= | |||
'\x3cdiv class\x3d"cke_accessibility_legend" role\x3d"document" aria-labelledby\x3d"'+q+'_arialbl" tabIndex\x3d"-1"\x3e%1\x3c/div\x3e\x3cspan id\x3d"'+q+'_arialbl" class\x3d"cke_voice_label"\x3e'+a.contents+" \x3c/span\x3e",d=[],c=a.legend,l=c.length,f=0;f<l;f++){for(var h=c[f],g=[],e=h.items,k=e.length,p=0;p<k;p++){var m=e[p],n=CKEDITOR.env.edge&&m.legendEdge?m.legendEdge:m.legend,n=n.replace(r,t);n.match(r)||g.push("\x3cdt\x3e%1\x3c/dt\x3e\x3cdd\x3e%2\x3c/dd\x3e".replace("%1",m.name).replace("%2", | |||
n))}d.push("\x3ch1\x3e%1\x3c/h1\x3e\x3cdl\x3e%2\x3c/dl\x3e".replace("%1",h.name).replace("%2",g.join("")))}return b.replace("%1",d.join(""))}()+'\x3cstyle type\x3d"text/css"\x3e.cke_accessibility_legend{width:600px;height:400px;padding-right:5px;overflow-y:auto;overflow-x:hidden;}.cke_browser_quirks .cke_accessibility_legend,{height:390px}.cke_accessibility_legend *{white-space:normal;}.cke_accessibility_legend h1{font-size: 20px;border-bottom: 1px solid #AAA;margin: 5px 0px 15px;}.cke_accessibility_legend dl{margin-left: 5px;}.cke_accessibility_legend dt{font-size: 13px;font-weight: bold;}.cke_accessibility_legend dd{margin:10px}\x3c/style\x3e'}]}], | |||
buttons:[CKEDITOR.dialog.cancelButton]}}); |