├── .coveragerc ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── Dockerfile ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── carrot_box ├── __init__.py ├── asgi.py ├── hr │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── backends.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20211217_0556.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── userparser.py │ └── views.py ├── param │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_param_id.py │ │ └── __init__.py │ └── models.py ├── settings │ ├── __init__.py │ ├── base.py │ └── dev.py ├── templates │ ├── base.html │ ├── base_ext.html │ ├── base_form.html │ └── base_formset.html ├── tests.py ├── urls.py ├── wfapp │ ├── __init__.py │ ├── leave │ │ ├── __init__.py │ │ ├── forms.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_alter_leave_id.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── templates │ │ │ └── leave │ │ │ │ ├── detail.html │ │ │ │ ├── form.html │ │ │ │ ├── inc_detail_info.html │ │ │ │ ├── list.html │ │ │ │ └── print.html │ │ ├── tests.py │ │ ├── views.py │ │ ├── wf_views.py │ │ └── wfdata.py │ └── purchase │ │ ├── __init__.py │ │ ├── models.py │ │ └── wfdata.py ├── wfdata.py └── wsgi.py ├── manage.py ├── package.json ├── screenshots ├── detail.png ├── flowchart.png └── main.png ├── setup.cfg ├── tox.ini ├── wfgen.py └── yarn.lock /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = carrot_box 4 | omit = 5 | */tests* 6 | */migrations/* 7 | 8 | [report] 9 | show_missing = True 10 | skip_covered = True 11 | omit = 12 | */tests* 13 | */migrations/* 14 | 15 | [html] 16 | directory = coverage_html 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite3 2 | node_modules/ 3 | vendor/ 4 | collectedstatic/ 5 | bower_components/ 6 | coverage_html/ 7 | *.swp 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | *.py[cod] 12 | *$py.class 13 | 14 | # C extensions 15 | *.so 16 | 17 | # Distribution / packaging 18 | .Python 19 | env/ 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .coverage 50 | .coverage.* 51 | .cache 52 | nosetests.xml 53 | coverage.xml 54 | *,cover 55 | .hypothesis/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # dotenv 91 | .env 92 | 93 | # virtualenv 94 | .venv 95 | venv/ 96 | ENV/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # idea 105 | .idea/ 106 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: git://github.com/pre-commit/pre-commit-hooks 3 | rev: v1.4.0 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-merge-conflict 7 | - id: check-symlinks 8 | - id: check-xml 9 | - id: check-yaml 10 | - id: detect-private-key 11 | - id: trailing-whitespace 12 | - id: debug-statements 13 | - id: flake8 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | dist: bionic 3 | 4 | cache: pip 5 | 6 | install: 7 | - pip install --upgrade pip setuptools tox virtualenv coveralls 8 | 9 | script: 10 | - tox 11 | 12 | notifications: 13 | email: false 14 | 15 | matrix: 16 | include: 17 | # Linting 18 | - python: 3.7 19 | env: TOXENV=flake8 20 | - python: 3.7 21 | env: TOXENV=isort 22 | 23 | # Tests 24 | - python: 3.6 25 | env: TOXENV=py36-django2x 26 | - python: 3.7 27 | env: TOXENV=py37-django2x 28 | 29 | - python: 3.6 30 | env: TOXENV=py36-django30 31 | - python: 3.7 32 | env: TOXENV=py37-django30 33 | - python: 3.8 34 | env: TOXENV=py38-django30 35 | 36 | # Future (Should be in `allow_failures`) 37 | - python: 3.8 38 | env: TOXENV=py38-django_trunk 39 | allow_failures: 40 | - env: TOXENV=py38-django_trunk 41 | 42 | after_success: coveralls 43 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | MAINTAINER vicalloy "https://github.com/vicalloy" 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | npm \ 6 | pkg-config \ 7 | --no-install-recommends && \ 8 | rm -rf /var/lib/apt/lists/* && \ 9 | npm install -g yarn 10 | 11 | RUN pip install --upgrade pip setuptools pipenv 12 | 13 | RUN mkdir /app 14 | WORKDIR /app 15 | 16 | COPY ./ ./ 17 | RUN pipenv install -d --skip-lock --system 18 | RUN make init 19 | 20 | EXPOSE 9000 21 | CMD ["make", "run"] 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | run: 2 | python manage.py runserver 0.0.0.0:9000 3 | 4 | test: 5 | coverage run manage.py test carrot_box 6 | coverage html 7 | 8 | init-pyenv: 9 | pip install pipenv --upgrade 10 | pipenv --python 3 11 | pipenv install -d --skip-lock 12 | pipenv shell 13 | 14 | init: 15 | yarn install 16 | python manage.py migrate 17 | python manage.py collectstatic --no-input 18 | python manage.py callfunc lbworkflow.wfdata.load_data 19 | python manage.py callfunc carrot_box.wfdata.load_data 20 | python manage.py callfunc carrot_box.wfapp.leave.wfdata.load_data 21 | python wfgen.py 22 | 23 | load_data: 24 | python manage.py callfunc lbworkflow.wfdata.load_data 25 | 26 | load_sample_data: 27 | python manage.py callfunc lbworkflow.wfdata.load_data 28 | python manage.py callfunc carrot_box.wfdata.load_data 29 | python manage.py callfunc carrot_box.wfapp.leave.wfdata.load_data 30 | 31 | isort: 32 | isort --recursive carrot_box 33 | 34 | wfgen: 35 | python wfgen.py 36 | 37 | wfgen_clean: 38 | python wfgen.py clean 39 | 40 | 41 | build_docker_image: 42 | docker build -t carrot-box:latest . 43 | 44 | create_docker_container: 45 | docker run -d -p 9000:9000 --name carrot-box carrot-box:latest 46 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | # url = "https://mirrors.aliyun.com/pypi/simple/" 5 | verify_ssl = true 6 | 7 | [packages] 8 | django-lb-workflow = {version=">=1.0.3", extras=["options"]} 9 | 10 | [dev-packages] 11 | coverage = "*" 12 | flake8 = "==3.7.9" 13 | isort = "*" 14 | pre-commit = "*" 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d921a3703fb6419aaf63ad25dec7d475179f15bede11db30c1fbd16b80cba081" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "asgiref": { 18 | "hashes": [ 19 | "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", 20 | "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" 21 | ], 22 | "markers": "python_version >= '3.7'", 23 | "version": "==3.5.0" 24 | }, 25 | "django": { 26 | "hashes": [ 27 | "sha256:9772e6935703e59e993960832d66a614cf0233a1c5123bc6224ecc6ad69e41e2", 28 | "sha256:9b06c289f9ba3a8abea16c9c9505f25107809fb933676f6c891ded270039d965" 29 | ], 30 | "index": "pypi", 31 | "version": "==3.2.12" 32 | }, 33 | "django-appconf": { 34 | "hashes": [ 35 | "sha256:ae9f864ee1958c815a965ed63b3fba4874eec13de10236ba063a788f9a17389d", 36 | "sha256:be3db0be6c81fa84742000b89a81c016d70ae66a7ccb620cdef592b1f1a6aaa4" 37 | ], 38 | "markers": "python_version >= '3.6'", 39 | "version": "==1.0.5" 40 | }, 41 | "django-bootstrap-pagination": { 42 | "hashes": [ 43 | "sha256:47e742679cf109e12f10ae09d5263433f94440818cf7b023e90a3f5252849639", 44 | "sha256:69d826d92217325611cb86e49944d8261e3c92eaa4deafea5a605d79fd363883" 45 | ], 46 | "version": "==1.7.1" 47 | }, 48 | "django-compressor": { 49 | "hashes": [ 50 | "sha256:89f7ba86777b30672c2f9c7557bf2aff87c5890903c73b1fa3ae38acd143e855", 51 | "sha256:c4a87bf65f9a534cfaf1c321a000a229c24e50c6d62ba6ab089482db42e819d9" 52 | ], 53 | "version": "==3.1" 54 | }, 55 | "django-crispy-forms": { 56 | "hashes": [ 57 | "sha256:35887b8851a931374dd697207a8f56c57a9c5cb9dbf0b9fa54314da5666cea5b", 58 | "sha256:bc4d2037f6de602d39c0bc452ac3029d1f5d65e88458872cc4dbc01c3a400604" 59 | ], 60 | "version": "==1.14.0" 61 | }, 62 | "django-impersonate": { 63 | "hashes": [ 64 | "sha256:282003957577c7143fe31e5861f8fffdf6fe0c25557aedb28fcf8b11474eaa23" 65 | ], 66 | "version": "==1.7.3" 67 | }, 68 | "django-lb-adminlte": { 69 | "hashes": [ 70 | "sha256:7de9061b275cb9c6c891980c9039bb2fecfabaa8b160acfe3292f277fe0b6f3f" 71 | ], 72 | "version": "==1.2.1" 73 | }, 74 | "django-lb-workflow": { 75 | "extras": [ 76 | "options" 77 | ], 78 | "hashes": [ 79 | "sha256:653446a6efe4241cb6e23e9c49369eed492f74d941381c67fa86bd367ffd7a6a" 80 | ], 81 | "index": "pypi", 82 | "version": "==1.0.4" 83 | }, 84 | "django-lbattachment": { 85 | "hashes": [ 86 | "sha256:ffd0f3ea5e21e4a0ee284d8c1a8fd8f24ac1ea8e43227aa65567732b7001805c" 87 | ], 88 | "version": "==1.1.0" 89 | }, 90 | "django-lbutils": { 91 | "hashes": [ 92 | "sha256:990d8d45751443bfc9228b03b4cdd27d59719c902ca4022729a21d44d55579f0" 93 | ], 94 | "version": "==1.1.0" 95 | }, 96 | "django-select2": { 97 | "hashes": [ 98 | "sha256:f3387ba43db4a137b5f17d30b3465dd328d01fb4b5d08a017ba0b76a7c7bbbbf", 99 | "sha256:fd78095b0eef02f990a56f887ada3d6bc3d0ffa858a73ec29498ec86988dc90d" 100 | ], 101 | "version": "==7.10.0" 102 | }, 103 | "django-stronghold": { 104 | "hashes": [ 105 | "sha256:4127d5f9c11f6582a1c03e7758256b1fe5c872f64f212980e5ad5c67f5eeaa3d" 106 | ], 107 | "version": "==0.4.0" 108 | }, 109 | "jinja2": { 110 | "hashes": [ 111 | "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8", 112 | "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7" 113 | ], 114 | "markers": "python_version >= '3.6'", 115 | "version": "==3.0.3" 116 | }, 117 | "jsonfield": { 118 | "hashes": [ 119 | "sha256:7e4e84597de21eeaeeaaa7cc5da08c61c48a9b64d0c446b2d71255d01812887a", 120 | "sha256:df857811587f252b97bafba42e02805e70a398a7a47870bc6358a0308dd689ed" 121 | ], 122 | "markers": "python_version >= '3.6'", 123 | "version": "==3.1.0" 124 | }, 125 | "markupsafe": { 126 | "hashes": [ 127 | "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", 128 | "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", 129 | "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", 130 | "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", 131 | "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", 132 | "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", 133 | "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", 134 | "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", 135 | "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", 136 | "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", 137 | "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", 138 | "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", 139 | "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", 140 | "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", 141 | "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", 142 | "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", 143 | "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", 144 | "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", 145 | "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", 146 | "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", 147 | "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", 148 | "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", 149 | "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", 150 | "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", 151 | "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", 152 | "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", 153 | "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", 154 | "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", 155 | "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", 156 | "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", 157 | "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", 158 | "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", 159 | "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", 160 | "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", 161 | "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", 162 | "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", 163 | "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", 164 | "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", 165 | "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", 166 | "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", 167 | "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", 168 | "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", 169 | "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", 170 | "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", 171 | "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", 172 | "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", 173 | "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", 174 | "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", 175 | "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", 176 | "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", 177 | "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", 178 | "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", 179 | "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", 180 | "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", 181 | "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", 182 | "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", 183 | "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", 184 | "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", 185 | "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", 186 | "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", 187 | "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", 188 | "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", 189 | "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", 190 | "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", 191 | "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", 192 | "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", 193 | "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", 194 | "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", 195 | "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" 196 | ], 197 | "markers": "python_version >= '3.6'", 198 | "version": "==2.0.1" 199 | }, 200 | "pytz": { 201 | "hashes": [ 202 | "sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c", 203 | "sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326" 204 | ], 205 | "version": "==2021.3" 206 | }, 207 | "rcssmin": { 208 | "hashes": [ 209 | "sha256:0a6aae7e119509445bf7aa6da6ca0f285cc198273c20f470ad999ff83bbadcf9", 210 | "sha256:1512223b6a687bb747e4e531187bd49a56ed71287e7ead9529cbaa1ca4718a0a", 211 | "sha256:1d7c2719d014e4e4df4e33b75ae8067c7e246cf470eaec8585e06e2efac7586c", 212 | "sha256:2211a5c91ea14a5937b57904c9121f8bfef20987825e55368143da7d25446e3b", 213 | "sha256:27fc400627fd3d328b7fe95af2a01f5d0af6b5af39731af5d071826a1f08e362", 214 | "sha256:30f5522285065cae0164d20068377d84b5d10b414156115f8729b034d0ea5e8b", 215 | "sha256:32ccaebbbd4d56eab08cf26aed36f5d33389b9d1d3ca1fecf53eb6ab77760ddf", 216 | "sha256:352dd3a78eb914bb1cb269ac2b66b3154f2490a52ab605558c681de3fb5194d2", 217 | "sha256:37f1242e34ca273ed2c26cf778854e18dd11b31c6bfca60e23fce146c84667c1", 218 | "sha256:49807735f26f59404194f1e6f93254b6d5b6f7748c2a954f4470a86a40ff4c13", 219 | "sha256:506e33ab4c47051f7deae35b6d8dbb4a5c025f016e90a830929a1ecc7daa1682", 220 | "sha256:6158d0d86cd611c5304d738dc3d6cfeb23864dd78ad0d83a633f443696ac5d77", 221 | "sha256:7085d1b51dd2556f3aae03947380f6e9e1da29fb1eeadfa6766b7f105c54c9ff", 222 | "sha256:7c44002b79f3656348196005b9522ec5e04f182b466f66d72b16be0bd03c13d8", 223 | "sha256:7da63fee37edf204bbd86785edb4d7491642adbfd1d36fd230b7ccbbd8db1a6f", 224 | "sha256:8b659a88850e772c84cfac4520ec223de6807875e173d8ef3248ab7f90876066", 225 | "sha256:c28b9eb20982b45ebe6adef8bd2547e5ed314dafddfff4eba806b0f8c166cfd1", 226 | "sha256:ddff3a41611664c7f1d9e3d8a9c1669e0e155ac0458e586ffa834dc5953e7d9f", 227 | "sha256:f1a37bbd36b050813673e62ae6464467548628690bf4d48a938170e121e8616e", 228 | "sha256:f31c82d06ba2dbf33c20db9550157e80bb0c4cbd24575c098f0831d1d2e3c5df" 229 | ], 230 | "version": "==1.1.0" 231 | }, 232 | "rjsmin": { 233 | "hashes": [ 234 | "sha256:05efa485dfddb6418e3b86d8862463aa15641a61f6ae05e7e6de8f116ee77c69", 235 | "sha256:1622fbb6c6a8daaf77da13cc83356539bfe79c1440f9664b02c7f7b150b9a18e", 236 | "sha256:1c93b29fd725e61718299ffe57de93ff32d71b313eaabbfcc7bd32ddb82831d5", 237 | "sha256:2ed83aca637186bafdc894b4b7fc3657e2d74014ccca7d3d69122c1e82675216", 238 | "sha256:38a4474ed52e1575fb9da983ec8657faecd8ab3738508d36e04f87769411fd3d", 239 | "sha256:3b14f4c2933ec194eb816b71a0854ce461b6419a3d852bf360344731ab28c0a6", 240 | "sha256:40e7211a25d9a11ac9ff50446e41268c978555676828af86fa1866615823bfff", 241 | "sha256:41c7c3910f7b8816e37366b293e576ddecf696c5f2197d53cf2c1526ac336646", 242 | "sha256:4387a00777faddf853eebdece9f2e56ebaf243c3f24676a9de6a20c5d4f3d731", 243 | "sha256:54fc30519365841b27556ccc1cb94c5b4413c384ff6d467442fddba66e2e325a", 244 | "sha256:6c395ffc130332cca744f081ed5efd5699038dcb7a5d30c3ff4bc6adb5b30a62", 245 | "sha256:6c529feb6c400984452494c52dd9fdf59185afeacca2afc5174a28ab37751a1b", 246 | "sha256:86c4da7285ddafe6888cb262da563570f28e4a31146b5164a7a6947b1222196b", 247 | "sha256:8944a8a55ac825b8e5ec29f341ecb7574697691ef416506885898d2f780fb4ca", 248 | "sha256:993935654c1311280e69665367d7e6ff694ac9e1609168cf51cae8c0307df0db", 249 | "sha256:99e5597a812b60058baa1457387dc79cca7d273b2a700dc98bfd20d43d60711d", 250 | "sha256:b6a7c8c8d19e154334f640954e43e57283e87bb4a2f6e23295db14eea8e9fc1d", 251 | "sha256:c81229ffe5b0a0d5b3b5d5e6d0431f182572de9e9a077e85dbae5757db0ab75c", 252 | "sha256:d63e193a2f932a786ae82068aa76d1d126fcdff8582094caff9e5e66c4dcc124", 253 | "sha256:e18fe1a610fb105273bb369f61c2b0bd9e66a3f0792e27e4cac44e42ace1968b" 254 | ], 255 | "version": "==1.2.0" 256 | }, 257 | "sqlparse": { 258 | "hashes": [ 259 | "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae", 260 | "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d" 261 | ], 262 | "markers": "python_version >= '3.5'", 263 | "version": "==0.4.2" 264 | }, 265 | "xlsxwriter": { 266 | "hashes": [ 267 | "sha256:1aa65166697c42284e82f5bf9a33c2e913341eeef2b262019c3f5b5334768765", 268 | "sha256:53005f03e8eb58f061ebf41d5767c7495ee0772c2396fe26b7e0ca22fa9c2570" 269 | ], 270 | "markers": "python_version >= '3.4'", 271 | "version": "==3.0.2" 272 | } 273 | }, 274 | "develop": { 275 | "cfgv": { 276 | "hashes": [ 277 | "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", 278 | "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" 279 | ], 280 | "markers": "python_full_version >= '3.6.1'", 281 | "version": "==3.3.1" 282 | }, 283 | "coverage": { 284 | "hashes": [ 285 | "sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0", 286 | "sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd", 287 | "sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884", 288 | "sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48", 289 | "sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76", 290 | "sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0", 291 | "sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64", 292 | "sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685", 293 | "sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47", 294 | "sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d", 295 | "sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840", 296 | "sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f", 297 | "sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971", 298 | "sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c", 299 | "sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a", 300 | "sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de", 301 | "sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17", 302 | "sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4", 303 | "sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521", 304 | "sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57", 305 | "sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b", 306 | "sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282", 307 | "sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644", 308 | "sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475", 309 | "sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d", 310 | "sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da", 311 | "sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953", 312 | "sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2", 313 | "sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e", 314 | "sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c", 315 | "sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc", 316 | "sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64", 317 | "sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74", 318 | "sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617", 319 | "sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3", 320 | "sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d", 321 | "sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa", 322 | "sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739", 323 | "sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8", 324 | "sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8", 325 | "sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781", 326 | "sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58", 327 | "sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9", 328 | "sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c", 329 | "sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd", 330 | "sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e", 331 | "sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49" 332 | ], 333 | "index": "pypi", 334 | "version": "==6.2" 335 | }, 336 | "distlib": { 337 | "hashes": [ 338 | "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", 339 | "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" 340 | ], 341 | "version": "==0.3.4" 342 | }, 343 | "entrypoints": { 344 | "hashes": [ 345 | "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", 346 | "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" 347 | ], 348 | "markers": "python_version >= '2.7'", 349 | "version": "==0.3" 350 | }, 351 | "filelock": { 352 | "hashes": [ 353 | "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80", 354 | "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146" 355 | ], 356 | "markers": "python_version >= '3.7'", 357 | "version": "==3.4.2" 358 | }, 359 | "flake8": { 360 | "hashes": [ 361 | "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", 362 | "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" 363 | ], 364 | "index": "pypi", 365 | "version": "==3.7.9" 366 | }, 367 | "identify": { 368 | "hashes": [ 369 | "sha256:bff7c4959d68510bc28b99d664b6a623e36c6eadc933f89a4e0a9ddff9b4fee4", 370 | "sha256:e926ae3b3dc142b6a7a9c65433eb14ccac751b724ee255f7c2ed3b5970d764fb" 371 | ], 372 | "markers": "python_version >= '3.7'", 373 | "version": "==2.4.9" 374 | }, 375 | "isort": { 376 | "hashes": [ 377 | "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", 378 | "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" 379 | ], 380 | "index": "pypi", 381 | "version": "==5.10.1" 382 | }, 383 | "mccabe": { 384 | "hashes": [ 385 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 386 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 387 | ], 388 | "version": "==0.6.1" 389 | }, 390 | "nodeenv": { 391 | "hashes": [ 392 | "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b", 393 | "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7" 394 | ], 395 | "version": "==1.6.0" 396 | }, 397 | "platformdirs": { 398 | "hashes": [ 399 | "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb", 400 | "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b" 401 | ], 402 | "markers": "python_version >= '3.7'", 403 | "version": "==2.5.0" 404 | }, 405 | "pre-commit": { 406 | "hashes": [ 407 | "sha256:758d1dc9b62c2ed8881585c254976d66eae0889919ab9b859064fc2fe3c7743e", 408 | "sha256:fe9897cac830aa7164dbd02a4e7b90cae49630451ce88464bca73db486ba9f65" 409 | ], 410 | "index": "pypi", 411 | "version": "==2.16.0" 412 | }, 413 | "pycodestyle": { 414 | "hashes": [ 415 | "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", 416 | "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" 417 | ], 418 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 419 | "version": "==2.5.0" 420 | }, 421 | "pyflakes": { 422 | "hashes": [ 423 | "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", 424 | "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" 425 | ], 426 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 427 | "version": "==2.1.1" 428 | }, 429 | "pyyaml": { 430 | "hashes": [ 431 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 432 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 433 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 434 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 435 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 436 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 437 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 438 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 439 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 440 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 441 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 442 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 443 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 444 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 445 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 446 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 447 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 448 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 449 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 450 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 451 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 452 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 453 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 454 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 455 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 456 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 457 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 458 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 459 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 460 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 461 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 462 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 463 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 464 | ], 465 | "markers": "python_version >= '3.6'", 466 | "version": "==6.0" 467 | }, 468 | "six": { 469 | "hashes": [ 470 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 471 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 472 | ], 473 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 474 | "version": "==1.16.0" 475 | }, 476 | "toml": { 477 | "hashes": [ 478 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 479 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 480 | ], 481 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 482 | "version": "==0.10.2" 483 | }, 484 | "virtualenv": { 485 | "hashes": [ 486 | "sha256:45e1d053cad4cd453181ae877c4ffc053546ae99e7dd049b9ff1d9be7491abf7", 487 | "sha256:e0621bcbf4160e4e1030f05065c8834b4e93f4fcc223255db2a823440aca9c14" 488 | ], 489 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 490 | "version": "==20.13.1" 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Carrot Box 2 | 3 | [![](https://secure.travis-ci.org/vicalloy/carrot-box.svg?branch=master)](http://travis-ci.org/vicalloy/carrot-box) 4 | [![](https://coveralls.io/repos/github/vicalloy/carrot-box/badge.svg?branch=master)](https://coveralls.io/github/vicalloy/carrot-box?branch=master) 5 | 6 | Carrot box is a workflow platform, it also an example of using [django-lb-workflow](https://github.com/vicalloy/django-lb-workflow/). 7 | 8 | Main 9 | 10 | Detail 11 | 12 | Flowchart 13 | 14 | Demo site 15 | --------- 16 | 17 | Demo site: http://wf.haoluobo.com/ 18 | 19 | username: ``admin`` password: ``password`` 20 | 21 | Switch to another user: http://wf.haoluobo.com/impersonate/search 22 | 23 | Stop switch: http://wf.haoluobo.com/impersonate/stop 24 | 25 | Running locally 26 | --------------- 27 | 28 | Run the following commands: 29 | 30 | make init-pyenv 31 | make init 32 | make run 33 | 34 | Creating Custom Workflows 35 | ------------------------- 36 | 37 | You should read the documentation of [django-lb-workflow](https://github.com/vicalloy/django-lb-workflow/). 38 | -------------------------------------------------------------------------------- /carrot_box/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/__init__.py -------------------------------------------------------------------------------- /carrot_box/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for carrot_box project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'carrot_box.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /carrot_box/hr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/hr/__init__.py -------------------------------------------------------------------------------- /carrot_box/hr/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import CarrotDepartment 4 | from .models import CarrotRole 5 | from .models import CarrotUser 6 | 7 | 8 | @admin.register(CarrotUser) 9 | class CarrotUserAdmin(admin.ModelAdmin): 10 | list_display = ('username', 'full_name', 'post', 'get_department', 'email', 'is_staff', 'is_superuser') 11 | list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') 12 | search_fields = ('username', 'first_name', 'last_name', 'email') 13 | ordering = ('username',) 14 | filter_horizontal = ('groups', 'user_permissions',) 15 | autocomplete_fields = ('leader', ) # 'department', 16 | 17 | 18 | @admin.register(CarrotDepartment) 19 | class CarrotDepartmentAdmin(admin.ModelAdmin): 20 | list_display = ('name', 'code', 'parent', 'leader', 'oid', 'is_active',) 21 | search_fields = ('code', 'name') 22 | ordering = ('oid',) 23 | filter_horizontal = ('permissions', ) 24 | autocomplete_fields = ('parent', 'parents', 'leader', ) 25 | 26 | 27 | @admin.register(CarrotRole) 28 | class CarrotRoleAdmin(admin.ModelAdmin): 29 | list_display = ('name', 'code', 'oid', 'is_active',) 30 | search_fields = ('code', 'name') 31 | ordering = ('oid',) 32 | filter_horizontal = ('permissions',) 33 | autocomplete_fields = ('users', ) 34 | -------------------------------------------------------------------------------- /carrot_box/hr/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HrConfig(AppConfig): 5 | name = 'carrot_box.hr' 6 | -------------------------------------------------------------------------------- /carrot_box/hr/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.backends import ModelBackend 2 | from django.contrib.auth.models import Permission 3 | 4 | 5 | class CarrotModelBackend(ModelBackend): 6 | 7 | def get_all_permissions(self, user_obj, obj=None): 8 | if not user_obj.is_active or user_obj.is_anonymous or obj is not None: 9 | return set() 10 | if not hasattr(user_obj, '_perm_cache'): 11 | user_obj._perm_cache = super().get_all_permissions(user_obj) 12 | user_obj._perm_cache.update(self.get_role_permissions(user_obj)) 13 | user_obj._perm_cache.update(self.get_department_permissions(user_obj)) 14 | return user_obj._perm_cache 15 | 16 | def get_role_permissions(self, user_obj, obj=None): 17 | return self._get_permissions(user_obj, obj, 'role') 18 | 19 | def _get_role_permissions(self, user_obj): 20 | return Permission.objects.filter(carrotrole__users=user_obj) 21 | 22 | def get_department_permissions(self, user_obj, obj=None): 23 | return self._get_permissions(user_obj, obj, 'department') 24 | 25 | def _get_department_permissions(self, user_obj): 26 | department = user_obj.get_department() 27 | if not department: 28 | return set() 29 | departments = department.parents.all() 30 | return Permission.objects.filter(carrotdepartment__in=departments) 31 | -------------------------------------------------------------------------------- /carrot_box/hr/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-07 06:12 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | import uuid 10 | 11 | 12 | class Migration(migrations.Migration): 13 | 14 | initial = True 15 | 16 | dependencies = [ 17 | ('auth', '0011_update_proxy_permissions'), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='CarrotUser', 23 | fields=[ 24 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('password', models.CharField(max_length=128, verbose_name='password')), 26 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 27 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 28 | ('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')), 29 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 30 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 31 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 32 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 33 | ('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')), 34 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 35 | ('full_name', models.CharField(blank=True, max_length=255)), 36 | ('post', models.CharField(blank=True, max_length=255)), 37 | ('department_id', models.PositiveIntegerField(blank=True, null=True)), 38 | ('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')), 39 | ('leader', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Leader')), 40 | ('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')), 41 | ], 42 | options={ 43 | 'abstract': False, 44 | }, 45 | managers=[ 46 | ('objects', django.contrib.auth.models.UserManager()), 47 | ], 48 | ), 49 | migrations.CreateModel( 50 | name='CarrotRole', 51 | fields=[ 52 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 53 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), 54 | ('code', models.CharField(blank=True, max_length=100)), 55 | ('name', models.CharField(max_length=100)), 56 | ('description', models.CharField(blank=True, max_length=50)), 57 | ('oid', models.IntegerField(default=999, verbose_name='Order')), 58 | ('is_active', models.BooleanField(default=True)), 59 | ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), 60 | ('users', models.ManyToManyField(blank=True, related_name='roles', to=settings.AUTH_USER_MODEL)), 61 | ], 62 | ), 63 | migrations.CreateModel( 64 | name='CarrotDepartment', 65 | fields=[ 66 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 67 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), 68 | ('code', models.CharField(blank=True, max_length=100)), 69 | ('name', models.CharField(max_length=100)), 70 | ('description', models.CharField(blank=True, max_length=50)), 71 | ('oid', models.IntegerField(default=999, verbose_name='Order')), 72 | ('is_active', models.BooleanField(default=True)), 73 | ('leader', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='department_master_user', to=settings.AUTH_USER_MODEL)), 74 | ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sub_departments', to='hr.CarrotDepartment', verbose_name='Parent Department')), 75 | ('parents', models.ManyToManyField(blank=True, editable=False, related_name='all_sub_departments', to='hr.CarrotDepartment', verbose_name='All parents department')), 76 | ('permissions', models.ManyToManyField(blank=True, to='auth.Permission')), 77 | ], 78 | ), 79 | ] 80 | -------------------------------------------------------------------------------- /carrot_box/hr/migrations/0002_auto_20211217_0556.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2021-12-17 05:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('hr', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='carrotdepartment', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | migrations.AlterField( 19 | model_name='carrotrole', 20 | name='id', 21 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 22 | ), 23 | migrations.AlterField( 24 | model_name='carrotuser', 25 | name='first_name', 26 | field=models.CharField(blank=True, max_length=150, verbose_name='first name'), 27 | ), 28 | migrations.AlterField( 29 | model_name='carrotuser', 30 | name='id', 31 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /carrot_box/hr/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/hr/migrations/__init__.py -------------------------------------------------------------------------------- /carrot_box/hr/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.conf import settings as django_settings 4 | from django.contrib.auth.models import AbstractUser 5 | from django.contrib.auth.models import Permission 6 | from django.db import models 7 | 8 | AUTH_USER_MODEL = getattr(django_settings, 'AUTH_USER_MODEL', 'auth.User') 9 | 10 | 11 | class CarrotDepartment(models.Model): 12 | uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 13 | code = models.CharField(max_length=100, blank=True) 14 | name = models.CharField(max_length=100) 15 | description = models.CharField(max_length=50, blank=True) 16 | parent = models.ForeignKey( 17 | 'CarrotDepartment', blank=True, null=True, 18 | on_delete=models.SET_NULL, 19 | related_name='sub_departments', 20 | verbose_name='Parent Department') 21 | # all parent node, include self 22 | parents = models.ManyToManyField( 23 | 'CarrotDepartment', verbose_name='All parents department', 24 | related_name='all_sub_departments', 25 | editable=False, blank=True) 26 | leader = models.ForeignKey( 27 | AUTH_USER_MODEL, blank=True, null=True, 28 | on_delete=models.SET_NULL, 29 | related_name="department_master_user") 30 | permissions = models.ManyToManyField( 31 | Permission, blank=True) 32 | oid = models.IntegerField('Order', default=999) 33 | is_active = models.BooleanField(default=True) 34 | 35 | def __str__(self): 36 | return self.name 37 | 38 | def natural_key(self): 39 | return ( 40 | self.uuid, 41 | ) 42 | 43 | def get_parents(self): 44 | department = self 45 | parent = department.parent 46 | parents = [department] 47 | while parent: 48 | parents.append(parent) 49 | department = parent 50 | parent = department.parent 51 | return parents 52 | 53 | def update_parents(self): 54 | old_parents = set(self.parents.all()) 55 | new_parents = set(self.get_parents()) 56 | if old_parents == new_parents: 57 | return 58 | self.parents.set(new_parents) 59 | for d in self.sub_departments.all(): 60 | d.update_parents() 61 | 62 | def save(self, *args, **kwargs): 63 | if self.parent == self: 64 | self.parent = None 65 | super().save(*args, **kwargs) 66 | self.update_parents() 67 | 68 | 69 | class CarrotRole(models.Model): 70 | uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 71 | code = models.CharField(max_length=100, blank=True) 72 | name = models.CharField(max_length=100) 73 | description = models.CharField(max_length=50, blank=True) 74 | users = models.ManyToManyField( 75 | AUTH_USER_MODEL, blank=True, 76 | related_name='roles', 77 | ) 78 | permissions = models.ManyToManyField( 79 | Permission, blank=True) 80 | oid = models.IntegerField('Order', default=999) 81 | is_active = models.BooleanField(default=True) 82 | 83 | def __str__(self): 84 | return self.name 85 | 86 | def natural_key(self): 87 | return ( 88 | self.uuid, 89 | ) 90 | 91 | 92 | class CarrotUserMixin(models.Model): 93 | full_name = models.CharField(max_length=255, blank=True) 94 | post = models.CharField(max_length=255, blank=True) 95 | department_id = models.PositiveIntegerField(null=True, blank=True) 96 | # department = models.ForeignKey( # will get error `Can't resolve dependencies` 97 | # CarrotDepartment, 98 | # on_delete=models.SET_NULL, 99 | # null=True, blank=True) 100 | leader = models.ForeignKey( 101 | AUTH_USER_MODEL, blank=True, null=True, 102 | on_delete=models.SET_NULL, 103 | verbose_name='Leader') 104 | 105 | def get_department(self): 106 | if self.department_id: 107 | return CarrotDepartment.objects.filter(pk=self.department_id).first() 108 | return None 109 | 110 | class Meta: 111 | abstract = True 112 | 113 | 114 | class CarrotUser(CarrotUserMixin, AbstractUser): 115 | 116 | def __str__(self): 117 | return self.username 118 | 119 | def save(self, *args, **kwargs): 120 | if not self.full_name: 121 | self.full_name = self.username 122 | super().save(*args, **kwargs) 123 | -------------------------------------------------------------------------------- /carrot_box/hr/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | 3 | from carrot_box.tests import BaseTests 4 | 5 | from .backends import CarrotModelBackend 6 | from .userparser import CarrotBoxUserParser 7 | 8 | User = get_user_model() 9 | 10 | 11 | class UserSimpleParserTests(BaseTests): 12 | 13 | def test_parser_role(self): 14 | role_it = self.roles['it'] 15 | users = CarrotBoxUserParser(f"r[{role_it.pk}:it]").parse() 16 | self.assertEqual(users[0], self.users['it']) 17 | 18 | users = CarrotBoxUserParser(f"r[it]").parse() 19 | self.assertEqual(users[0], self.users['it']) 20 | 21 | def test_dept_direct_leaders(self): 22 | users = CarrotBoxUserParser("dept_direct_leaders d[it]").parse() 23 | self.assertEqual(users[0], self.users['it_dept_leader']) 24 | 25 | 26 | class PermissionTests(BaseTests): 27 | def test_get_all_permissions(self): 28 | backend = CarrotModelBackend() 29 | it = self.users['it'] 30 | backend.get_all_permissions(it) 31 | -------------------------------------------------------------------------------- /carrot_box/hr/userparser.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from lbworkflow.core.userparser import SimpleUserParser 3 | 4 | from .models import CarrotDepartment 5 | from .models import CarrotRole 6 | 7 | User = get_user_model() 8 | 9 | 10 | class CarrotBoxUserParser(SimpleUserParser): 11 | def process_func(self, func_str): 12 | """ 13 | return None if not a func 14 | """ 15 | params = [e.strip() for e in func_str.split(' ') if e.strip()] 16 | func_name = params[0] 17 | params = params[1:] 18 | if not params: # at least one param 19 | return None 20 | elif func_name == 'dept_direct_leaders': 21 | departments = self.get_departments(params[0]) 22 | return [d.leader for d in departments if d.leader] 23 | return None # if not function return None 24 | 25 | def get_departments(self, atom_str): 26 | """ 27 | d[o.dept] 28 | d[o.depts] 29 | d[11:it] 30 | """ 31 | return self.get_object_list(atom_str, CarrotDepartment, 'code', 'd[') 32 | 33 | def get_roles(self, role_str): 34 | """ 35 | r[hr] 36 | r[1:hr] 37 | """ 38 | return self.get_object_list(role_str, CarrotRole, 'code', 'r[') 39 | 40 | def get_users_by_roles(self, role_str): 41 | roles = self.get_roles(role_str) 42 | return User.objects.filter(roles__in=roles) 43 | 44 | def parse_atom_rule(self, atom_rule): 45 | if atom_rule and atom_rule.startswith('r['): 46 | return self.get_users_by_roles(atom_rule) 47 | return super().parse_atom_rule(atom_rule) 48 | -------------------------------------------------------------------------------- /carrot_box/hr/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/hr/views.py -------------------------------------------------------------------------------- /carrot_box/param/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/param/__init__.py -------------------------------------------------------------------------------- /carrot_box/param/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Param 4 | from .models import ParamType 5 | 6 | 7 | class ParamInline(admin.TabularInline): 8 | raw_id_fields = ('parent', 'param_type') 9 | model = Param 10 | 11 | 12 | @admin.register(ParamType) 13 | class ParamTypeAdmin(admin.ModelAdmin): 14 | search_fields = ('code', 'name', ) 15 | list_display = ('code', 'name', 'oid', 'is_active',) 16 | inlines = [ 17 | ParamInline, 18 | ] 19 | 20 | 21 | @admin.register(Param) 22 | class ParamAdmin(admin.ModelAdmin): 23 | search_fields = ('code', 'name', 'param_type__name', 'param_type__code') 24 | list_display = ('name', 'param_type', 'code', 'oid', 'is_active',) 25 | raw_id_fields = ('parent', 'param_type') 26 | inlines = [ 27 | ParamInline, 28 | ] 29 | -------------------------------------------------------------------------------- /carrot_box/param/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-07 03:14 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='ParamType', 18 | fields=[ 19 | ('code', models.CharField(max_length=255, primary_key=True, serialize=False)), 20 | ('name', models.CharField(max_length=255)), 21 | ('oid', models.IntegerField(default=999, verbose_name='Order')), 22 | ('is_active', models.BooleanField(default=True)), 23 | ], 24 | options={ 25 | 'ordering': ['oid'], 26 | }, 27 | ), 28 | migrations.CreateModel( 29 | name='Param', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)), 33 | ('code', models.CharField(blank=True, max_length=255)), 34 | ('name', models.CharField(max_length=255)), 35 | ('oid', models.IntegerField(default=999, verbose_name='Order')), 36 | ('is_active', models.BooleanField(default=True)), 37 | ('param_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='param.ParamType')), 38 | ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='param.Param')), 39 | ], 40 | options={ 41 | 'ordering': ['oid'], 42 | }, 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /carrot_box/param/migrations/0002_alter_param_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2021-12-17 05:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('param', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='param', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /carrot_box/param/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/param/migrations/__init__.py -------------------------------------------------------------------------------- /carrot_box/param/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | from lbworkflow.core.datahelper import get_or_create 5 | 6 | 7 | class ParamType(models.Model): 8 | code = models.CharField(max_length=255, primary_key=True) 9 | name = models.CharField(max_length=255) 10 | oid = models.IntegerField('Order', default=999) 11 | is_active = models.BooleanField(default=True) 12 | 13 | @classmethod 14 | def get_all(cls, **kwargs): 15 | return cls.objects.filter(is_active=True, **kwargs).order_by('oid') 16 | 17 | class Meta: 18 | ordering = ["oid"] 19 | 20 | def __str__(self): 21 | return self.name 22 | 23 | 24 | def create_param_type(*args, **kwargs): 25 | return get_or_create(ParamType, *args, uid_field_name='code', **kwargs) 26 | 27 | 28 | class Param(models.Model): 29 | uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False) 30 | parent = models.ForeignKey( 31 | 'Param', 32 | on_delete=models.SET_NULL, 33 | blank=True, null=True) 34 | param_type = models.ForeignKey( 35 | ParamType, 36 | on_delete=models.CASCADE, 37 | blank=True, null=True) 38 | code = models.CharField(max_length=255, blank=True) 39 | name = models.CharField(max_length=255) 40 | oid = models.IntegerField('Order', default=999) 41 | is_active = models.BooleanField(default=True) 42 | 43 | @classmethod 44 | def get_all(cls, **kwargs): 45 | return cls.objects.filter(is_active=True, **kwargs).order_by('oid') 46 | 47 | def natural_key(self): 48 | return '%s' % self.uuid 49 | 50 | class Meta: 51 | ordering = ["oid"] 52 | 53 | def __str__(self): 54 | return self.name 55 | 56 | 57 | def create_param(*args, **kwargs): 58 | return get_or_create(Param, *args, **kwargs) 59 | -------------------------------------------------------------------------------- /carrot_box/settings/__init__.py: -------------------------------------------------------------------------------- 1 | try: 2 | from .local import * # NOQA 3 | except ImportError: 4 | from .dev import * # NOQA 5 | -------------------------------------------------------------------------------- /carrot_box/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for carrot_box project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | BASE_DIR = os.path.dirname(BASE_DIR) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'ay3)km!k_+3dt@#&9#%!+ipj8e90-_$w#9y941%e&$@zci$u))' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = ['*'] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'carrot_box', 36 | 'carrot_box.param', 37 | 'carrot_box.hr', 38 | 'carrot_box.wfapp.leave', 39 | 'carrot_box.wfapp.purchase', 40 | 41 | 'django.contrib.admin', 42 | 'django.contrib.auth', 43 | 'django.contrib.contenttypes', 44 | 'django.contrib.sessions', 45 | 'django.contrib.messages', 46 | 'django.contrib.staticfiles', 47 | 48 | 'stronghold', 49 | 'impersonate', 50 | 'crispy_forms', 51 | 'compressor', 52 | 'django_select2', 53 | 'bootstrap_pagination', 54 | 55 | 'lbattachment', 56 | 'lbadminlte', 57 | 'lbutils', 58 | 59 | 'lbworkflow', 60 | 'lbworkflow.simplewf', 61 | ] 62 | 63 | MIDDLEWARE = [ 64 | 'django.middleware.security.SecurityMiddleware', 65 | 'django.contrib.sessions.middleware.SessionMiddleware', 66 | 'django.middleware.common.CommonMiddleware', 67 | 'django.middleware.csrf.CsrfViewMiddleware', 68 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 69 | 'django.contrib.messages.middleware.MessageMiddleware', 70 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 71 | 72 | 'impersonate.middleware.ImpersonateMiddleware', 73 | 'stronghold.middleware.LoginRequiredMiddleware', 74 | ] 75 | 76 | ROOT_URLCONF = 'carrot_box.urls' 77 | 78 | TEMPLATES = [ 79 | { 80 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 81 | 'DIRS': [], 82 | 'APP_DIRS': True, 83 | 'OPTIONS': { 84 | 'context_processors': [ 85 | 'django.template.context_processors.debug', 86 | 'django.template.context_processors.request', 87 | 'django.contrib.auth.context_processors.auth', 88 | 'django.contrib.messages.context_processors.messages', 89 | ], 90 | }, 91 | }, 92 | ] 93 | 94 | WSGI_APPLICATION = 'carrot_box.wsgi.application' 95 | 96 | 97 | # Database 98 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 99 | 100 | DATABASES = { 101 | 'default': { 102 | 'ENGINE': 'django.db.backends.sqlite3', 103 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 104 | } 105 | } 106 | 107 | 108 | # Password validation 109 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 110 | 111 | AUTH_PASSWORD_VALIDATORS = [ 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 117 | }, 118 | { 119 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 120 | }, 121 | { 122 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 123 | }, 124 | ] 125 | 126 | 127 | # Internationalization 128 | # https://docs.djangoproject.com/en/3.0/topics/i18n/ 129 | 130 | LANGUAGE_CODE = 'en-us' 131 | 132 | TIME_ZONE = 'UTC' 133 | 134 | USE_I18N = True 135 | 136 | USE_L10N = True 137 | 138 | USE_TZ = True 139 | 140 | 141 | # Static files (CSS, JavaScript, Images) 142 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 143 | 144 | STATIC_URL = '/static/' 145 | 146 | STRONGHOLD_PUBLIC_URLS = [ 147 | r'^/admin/', 148 | ] 149 | LOGIN_URL = '/admin/login/' 150 | LOGOUT_URL = '/admin/logout/' 151 | 152 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 153 | MEDIA_URL_ = '/media/' 154 | MEDIA_URL = MEDIA_URL_ 155 | 156 | STATIC_URL = '/static/' 157 | 158 | LBWF_APPS = { 159 | 'simplewf': 'lbworkflow.simplewf', 160 | 'leave': 'carrot_box.wfapp.leave', 161 | 'purchase': 'carrot_box.wfapp.purchase', 162 | } 163 | 164 | STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') 165 | 166 | STATICFILES_FINDERS = ( 167 | 'django.contrib.staticfiles.finders.FileSystemFinder', 168 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 169 | ) 170 | 171 | STATICFILES_DIRS = ( 172 | os.path.join(BASE_DIR, 'node_modules'), 173 | ) 174 | 175 | CRISPY_TEMPLATE_PACK = 'bootstrap3' 176 | 177 | # django-compressor 178 | STATICFILES_FINDERS += (('compressor.finders.CompressorFinder'),) 179 | COMPRESS_PRECOMPILERS = ( 180 | ('text/coffeescript', 'coffee --compile --stdio'), 181 | ('text/less', 'lessc {infile} {outfile}'), 182 | ('text/x-sass', 'sass {infile} {outfile}'), 183 | ('text/x-scss', 'sass --scss {infile} {outfile}'), 184 | ) 185 | 186 | PROJECT_TITLE = 'Carrot Box' 187 | 188 | AUTH_USER_MODEL = 'hr.CarrotUser' 189 | 190 | LBWF_USER_PARSER = 'carrot_box.hr.userparser.CarrotBoxUserParser' 191 | 192 | AUTHENTICATION_BACKENDS = ( 193 | 'carrot_box.hr.backends.CarrotModelBackend', 194 | ) 195 | 196 | IMPERSONATE = { 197 | 'REDIRECT_URL': '/', 198 | 'PAGINATE_COUNT': 20, 199 | } 200 | 201 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -------------------------------------------------------------------------------- /carrot_box/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .base import * # NOQA 2 | 3 | DEBUG = True 4 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 5 | 6 | try: 7 | import debug_toolbar # NOQA 8 | INSTALLED_APPS += ('debug_toolbar',) # NOQA 9 | MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) # NOQA 10 | INTERNAL_IPS = ('127.0.0.1',) 11 | except ImportError: 12 | pass 13 | -------------------------------------------------------------------------------- /carrot_box/templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "lbworkflow/base.html" %} 2 | -------------------------------------------------------------------------------- /carrot_box/templates/base_ext.html: -------------------------------------------------------------------------------- 1 | {% extends "lbworkflow/base_ext.html" %} 2 | 3 | {% block header_nav_left %} 4 | 13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /carrot_box/templates/base_form.html: -------------------------------------------------------------------------------- 1 | {% extends "lbadminlte/base_form.html" %} 2 | -------------------------------------------------------------------------------- /carrot_box/templates/base_formset.html: -------------------------------------------------------------------------------- 1 | {% extends "lbadminlte/base_formset.html" %} 2 | -------------------------------------------------------------------------------- /carrot_box/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.core.management import call_command 3 | from django.test import TestCase 4 | from lbworkflow.core.datahelper import load_wf_data 5 | 6 | from .wfdata import load_departments 7 | from .wfdata import load_roles 8 | from .wfdata import load_simplewf 9 | from .wfdata import load_users 10 | 11 | User = get_user_model() 12 | 13 | 14 | # create migrations for purchase and do migrate for it. 15 | call_command('makemigrations', 'purchase') 16 | call_command('migrate') 17 | 18 | 19 | class BaseTests(TestCase): 20 | 21 | def setUp(self): 22 | self.init_data() 23 | 24 | def init_data(self): 25 | load_wf_data('lbworkflow') 26 | self.roles = load_roles() 27 | self.departments = load_departments() 28 | self.users = load_users(self.departments, self.roles) 29 | load_simplewf() 30 | 31 | 32 | class CarrotTests(BaseTests): 33 | 34 | def test_data(self): 35 | pass 36 | 37 | 38 | class PurchaseTests(BaseTests): 39 | def init_data(self): 40 | super().init_data() 41 | load_wf_data('carrot_box.wfapp.purchase') 42 | 43 | def test_data(self): 44 | pass 45 | -------------------------------------------------------------------------------- /carrot_box/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import include 5 | from django.urls import path 6 | from django.views.generic import RedirectView 7 | 8 | urlpatterns = [ 9 | path('', RedirectView.as_view(url='/wf/list/'), name='home'), 10 | path('admin/', admin.site.urls), 11 | path('wf/', include('lbworkflow.urls')), 12 | path('attachment/', include('lbattachment.urls')), 13 | path('select2/', include('django_select2.urls')), 14 | path('impersonate/', include('impersonate.urls')), 15 | ] 16 | 17 | if settings.DEBUG: 18 | urlpatterns += static(settings.MEDIA_URL_, document_root=settings.MEDIA_ROOT) 19 | -------------------------------------------------------------------------------- /carrot_box/wfapp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/wfapp/__init__.py -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/wfapp/leave/__init__.py -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from lbutils import BootstrapFormHelperMixin 3 | from lbworkflow.forms import WorkflowFormMixin 4 | 5 | from .models import Leave 6 | 7 | 8 | class LeaveForm(BootstrapFormHelperMixin, WorkflowFormMixin, forms.ModelForm): 9 | 10 | def __init__(self, *args, **kw): 11 | super().__init__(*args, **kw) 12 | self.init_crispy_helper() 13 | self.layout_fields([ 14 | ['start_on', 'end_on'], 15 | ['leave_type', 'leave_days'], 16 | ['reason', ], 17 | ]) 18 | 19 | def save(self, commit=True): 20 | obj = super().save(commit=False) 21 | obj.init_actual_info() 22 | if commit: 23 | self.save_m2m() 24 | obj.save() 25 | return obj 26 | 27 | class Meta: 28 | model = Leave 29 | fields = ['start_on', 'end_on', 'leave_type', 'leave_days', 'reason', ] 30 | 31 | 32 | class HRForm(BootstrapFormHelperMixin, WorkflowFormMixin, forms.ModelForm): 33 | comment = forms.CharField( 34 | label='Comment', required=False, 35 | widget=forms.Textarea()) 36 | 37 | def __init__(self, *args, **kw): 38 | super().__init__(*args, **kw) 39 | self.init_crispy_helper(label_class='col-md-2', field_class='col-md-8') 40 | self.layout_fields([ 41 | ['actual_start_on', ], 42 | ['actual_end_on', ], 43 | ['actual_leave_days', ], 44 | ['comment', ], 45 | ]) 46 | 47 | class Meta: 48 | model = Leave 49 | fields = ['actual_start_on', 'actual_end_on', 'actual_leave_days', ] 50 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-07 09:13 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('param', '0001_initial'), 15 | ('lbworkflow', '0004_processreportlink_uuid'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Leave', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('created_on', models.DateTimeField(auto_now_add=True, verbose_name='Created on')), 24 | ('start_on', models.DateTimeField(verbose_name='Start on')), 25 | ('end_on', models.DateTimeField(verbose_name='End on')), 26 | ('leave_days', models.DecimalField(decimal_places=1, max_digits=5, verbose_name='Leave days')), 27 | ('actual_start_on', models.DateTimeField(verbose_name='Actual start on')), 28 | ('actual_end_on', models.DateTimeField(verbose_name='Actual end on')), 29 | ('actual_leave_days', models.DecimalField(decimal_places=1, max_digits=5, verbose_name='Actual leave days')), 30 | ('reason', models.TextField(verbose_name='Reason')), 31 | ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created by')), 32 | ('leave_type', models.ForeignKey(limit_choices_to={'is_active': True, 'param_type__code': 'leave_type'}, null=True, on_delete=django.db.models.deletion.SET_NULL, to='param.Param', verbose_name='Leave Type')), 33 | ('pinstance', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave', to='lbworkflow.ProcessInstance', verbose_name='Process instance')), 34 | ], 35 | options={ 36 | 'verbose_name': 'Leave', 37 | 'ordering': ['-created_on'], 38 | 'permissions': (), 39 | }, 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/migrations/0002_alter_leave_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.10 on 2021-12-17 05:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('leave', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='leave', 15 | name='id', 16 | field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/wfapp/leave/migrations/__init__.py -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from lbworkflow.models import BaseWFObj 3 | 4 | from carrot_box.param.models import Param 5 | 6 | 7 | class Leave(BaseWFObj): 8 | start_on = models.DateTimeField('Start on') 9 | end_on = models.DateTimeField('End on') 10 | leave_days = models.DecimalField('Leave days', max_digits=5, decimal_places=1) 11 | 12 | actual_start_on = models.DateTimeField('Actual start on') 13 | actual_end_on = models.DateTimeField('Actual end on') 14 | actual_leave_days = models.DecimalField( 15 | 'Actual leave days', max_digits=5, decimal_places=1) 16 | 17 | leave_type = models.ForeignKey( 18 | Param, verbose_name='Leave Type', 19 | on_delete=models.SET_NULL, 20 | null=True, blank=False, 21 | limit_choices_to={'param_type__code': 'leave_type', 'is_active': True} 22 | ) 23 | reason = models.TextField('Reason') 24 | 25 | class Meta: 26 | verbose_name = 'Leave' 27 | ordering = ["-created_on"] 28 | permissions = ( 29 | ) 30 | 31 | def __str__(self): 32 | return '%s %s days' % (self.created_by, self.leave_days, ) 33 | 34 | def init_actual_info(self): 35 | self.actual_start_on = self.start_on 36 | self.actual_end_on = self.end_on 37 | self.actual_leave_days = self.leave_days 38 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/templates/leave/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "lbworkflow/wf_base_detail.html" %} 2 | 3 | {% block right_side_header_ext_btns %} 4 | Print 5 | | 6 | {% endblock %} 7 | 8 | {% block right_side_tab_base_ctx %} 9 | {% include "leave/inc_detail_info.html" %} 10 | {{ for_test }} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/templates/leave/form.html: -------------------------------------------------------------------------------- 1 | {% extends "lbworkflow/base_form.html" %} 2 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/templates/leave/inc_detail_info.html: -------------------------------------------------------------------------------- 1 | {% include "lbworkflow/inc_wf_status.html" %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 |
Start on{{ object.start_on }}End on{{ object.end_on }}
Leave Type{{ object.leave_type }}Days{{ object.leave_days }}
Actual start on{{ object.actual_start_on }}Actual end on{{ object.actual_end_on }}
Days{{ object.actual_leave_days }}
Reason 30 | {{ object.reason|linebreaks }} 31 |
34 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/templates/leave/list.html: -------------------------------------------------------------------------------- 1 | {% extends "base_ext.html" %} 2 | 3 | {% load crispy_forms_tags %} 4 | {% load bootstrap_pagination %} 5 | {% load lbworkflow_tags %} 6 | 7 | {% block nav_sel_node %}id-nav-leave{% endblock %} 8 | 9 | {% block right_side %} 10 |
11 | {% include "incs/messages.html" %} 12 |
13 |
14 |

15 | Leave 16 |

17 |
18 |
19 |
20 | {% if search_form %} 21 |
22 |
23 | {% crispy search_form %} 24 |
25 |
26 | {% endif %} 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% for o in object_list %}{% with pi=o.pinstance %} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 52 | 53 | {% endwith %}{% endfor %} 54 | 55 |
NO.Created byStart onEnd onCreated onCurrent operatorActivity
{{ pi.no }}{{ pi.created_by }}{{ o.start_on|date:"Y-m-d H:i" }}{{ o.end_on|date:"Y-m-d H:i" }}{{ pi.created_on|date:"Y-m-d H:i" }}{{ pi.get_operators_display }} 48 | 49 | {{ pi.cur_node.name }} 50 | 51 |
56 |
57 | 60 |
61 |
62 | {% endblock %} 63 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/templates/leave/print.html: -------------------------------------------------------------------------------- 1 | {% extends "lbadminlte/mbase_popup.html" %} 2 | 3 | {% block content %} 4 | {% include "leave/inc_detail_info.html" %} 5 |
6 | {% include "lbworkflow/inc_wf_history.html" %} 7 | {{ for_test }} 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.urls import reverse 3 | from django.utils import timezone 4 | from lbutils import get_or_none 5 | 6 | from carrot_box.param.models import Param 7 | from carrot_box.tests import BaseTests 8 | 9 | from .models import Leave 10 | from .wfdata import load_data 11 | 12 | User = get_user_model() 13 | 14 | 15 | class LeaveTests(BaseTests): 16 | 17 | def setUp(self): 18 | super().setUp() 19 | self.client.login(username='tom', password='password') 20 | self.leave_type_vacation = Param.objects.get(param_type__code='leave_type', name='Vacation') 21 | self.leave = self.create_leave('reason', False) 22 | 23 | def init_data(self): 24 | super().init_data() 25 | load_data() 26 | 27 | def create_leave(self, reason, submit=True): 28 | leave = Leave( 29 | start_on=timezone.now(), end_on=timezone.now(), leave_days=1, 30 | leave_type=self.leave_type_vacation, 31 | reason=reason, created_by=self.users['tom']) 32 | leave.init_actual_info() 33 | leave.save() 34 | leave.create_pinstance('leave', submit) 35 | return leave 36 | 37 | def get_leave(self, reason): 38 | return get_or_none(Leave, reason=reason) 39 | 40 | def test_list(self): 41 | resp = self.client.get(reverse('wf_list', args=('leave', ))) 42 | self.assertEqual(resp.status_code, 200) 43 | 44 | def test_export(self): 45 | resp = self.client.get(reverse('wf_list', args=('leave', )), {'export': 1}) 46 | self.assertEqual(resp.status_code, 200) 47 | 48 | def test_detail(self): 49 | resp = self.client.get(reverse('wf_detail', args=(self.leave.pinstance.pk, ))) 50 | self.assertEqual(resp.status_code, 200) 51 | 52 | def test_submit(self): 53 | self.client.login(username='tom', password='password') 54 | 55 | url = reverse('wf_new', args=('leave', )) 56 | resp = self.client.get(url) 57 | self.assertEqual(resp.status_code, 200) 58 | 59 | data = { 60 | 'start_on': '2017-04-19 09:01', 61 | 'end_on': '2017-04-20 09:01', 62 | 'leave_type': self.leave_type_vacation.pk, 63 | 'leave_days': '1', 64 | 'reason': 'test save', 65 | } 66 | resp = self.client.post(url, data) 67 | leave = Leave.objects.get(reason='test save') 68 | self.assertRedirects(resp, '/wf/%s/' % leave.pinstance.pk) 69 | self.assertEqual('Draft', leave.pinstance.cur_node.name) 70 | 71 | data['act_submit'] = 'Submit' 72 | data['reason'] = 'test submit' 73 | resp = self.client.post(url, data) 74 | leave = Leave.objects.get(reason='test submit') 75 | self.assertRedirects(resp, '/wf/%s/' % leave.pinstance.pk) 76 | self.assertEqual('Staff Leader', leave.pinstance.cur_node.name) 77 | 78 | def test_edit(self): 79 | self.client.login(username='tom', password='password') 80 | 81 | data = { 82 | 'start_on': '2017-04-19 09:01', 83 | 'end_on': '2017-04-20 09:01', 84 | 'leave_type': self.leave_type_vacation.pk, 85 | 'leave_days': '1', 86 | 'reason': 'test save', 87 | } 88 | url = reverse('wf_new', args=('leave', )) 89 | resp = self.client.post(url, data) 90 | leave = Leave.objects.get(reason='test save') 91 | self.assertRedirects(resp, '/wf/%s/' % leave.pinstance.pk) 92 | self.assertEqual('Draft', leave.pinstance.cur_node.name) 93 | 94 | url = reverse('wf_edit', args=(leave.pinstance.pk, )) 95 | resp = self.client.get(url) 96 | self.assertEqual(resp.status_code, 200) 97 | 98 | data['act_submit'] = 'Submit' 99 | data['reason'] = 'test submit' 100 | resp = self.client.post(url, data) 101 | leave = Leave.objects.get(reason='test submit') 102 | self.assertRedirects(resp, '/wf/%s/' % leave.pinstance.pk) 103 | self.assertEqual('Staff Leader', leave.pinstance.cur_node.name) 104 | 105 | def test_delete(self): 106 | self.client.login(username='admin', password='password') 107 | # POST 108 | url = reverse('wf_delete') 109 | leave = self.create_leave('to delete') 110 | data = {'pk': leave.pinstance.pk} 111 | resp = self.client.post(url, data) 112 | self.assertRedirects(resp, '/wf/list/') 113 | self.assertIsNone(self.get_leave('to delete')) 114 | 115 | # GET 116 | leave = self.create_leave('to delete') 117 | data = {'pk': leave.pinstance.pk} 118 | resp = self.client.get(url, data) 119 | self.assertRedirects(resp, '/wf/list/') 120 | self.assertIsNone(self.get_leave('to delete')) 121 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/views.py: -------------------------------------------------------------------------------- 1 | from lbworkflow.views.generics import CreateView 2 | from lbworkflow.views.generics import UpdateView 3 | from lbworkflow.views.generics import WFListView 4 | 5 | from .forms import LeaveForm 6 | from .models import Leave 7 | 8 | 9 | class LeaveCreateView(CreateView): 10 | form_classes = { 11 | 'form': LeaveForm, 12 | } 13 | 14 | 15 | new = LeaveCreateView.as_view() 16 | 17 | 18 | class LeaveUpdateView(UpdateView): 19 | form_classes = { 20 | 'form': LeaveForm, 21 | } 22 | 23 | 24 | edit = LeaveUpdateView.as_view() 25 | 26 | 27 | class LeaveListView(WFListView): 28 | wf_code = 'leave' 29 | model = Leave 30 | excel_file_name = 'leave' 31 | excel_titles = [ 32 | 'Created on', 'Created by', 33 | 'Start on', 'End on', 'Leave days', 34 | 'Actual start on', 'Actual start on', 'Actual leave days', 35 | 'Status', 36 | ] 37 | 38 | def get_excel_data(self, o): 39 | return [ 40 | o.created_by.username, o.created_on, 41 | o.start_on, o.end_on, o.leave_days, 42 | o.actual_start_on, o.actual_end_on, o.actual_leave_days, 43 | o.pinstance.cur_node.name, 44 | ] 45 | 46 | 47 | show_list = LeaveListView.as_view() 48 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/wf_views.py: -------------------------------------------------------------------------------- 1 | from lbworkflow.views.transition import ExecuteTransitionView 2 | 3 | from .forms import HRForm 4 | 5 | 6 | class CustomizedTransitionView(ExecuteTransitionView): 7 | form_classes = { 8 | 'form': HRForm 9 | } 10 | 11 | 12 | c = CustomizedTransitionView.as_view() 13 | -------------------------------------------------------------------------------- /carrot_box/wfapp/leave/wfdata.py: -------------------------------------------------------------------------------- 1 | from lbworkflow.core.datahelper import create_category 2 | from lbworkflow.core.datahelper import create_node 3 | from lbworkflow.core.datahelper import create_process 4 | from lbworkflow.core.datahelper import create_transition 5 | 6 | from carrot_box.param.models import create_param 7 | from carrot_box.param.models import create_param_type 8 | 9 | 10 | def load_data(): 11 | load_params() 12 | load_leave() 13 | 14 | 15 | def load_params(): 16 | leave_type = create_param_type('leave_type', name='Leave Type') 17 | create_param('5f31d065-00cc-0000-beea-641f0a670010', 18 | param_type=leave_type, name='Vacation') 19 | create_param('5f31d065-00cc-0000-beea-641f0a670020', 20 | param_type=leave_type, name='Sick') 21 | 22 | 23 | def load_leave(): 24 | """ load_[wf_code] """ 25 | category = create_category('5f31d065-00cc-0010-beea-641f0a670010', 'HR') 26 | process = create_process('leave', 'Leave', category=category) 27 | 28 | # Nodes 29 | create_node('5f31d065-00a0-0010-beea-641f0a670010', process, 'Draft', status='draft') 30 | create_node('5f31d065-00a0-0010-beea-641f0a670020', process, 'Given up', status='given up') 31 | create_node('5f31d065-00a0-0010-beea-641f0a670030', process, 'Rejected', status='rejected') 32 | create_node('5f31d065-00a0-0010-beea-641f0a670040', process, 'Completed', status='completed') 33 | 34 | create_node('5f31d065-00a0-0010-beea-641f0a670050', process, 35 | 'Staff Leader', operators='[o.created_by.leader]') 36 | create_node('5f31d065-00a0-0010-beea-641f0a670060', process, 37 | 'Department Leader', operators='[o.created_by.get_department().leader]') 38 | create_node('5f31d065-00a0-0010-beea-641f0a670065', process, 'HR', operators='r[hr]') 39 | create_node('5f31d065-00a0-0010-beea-641f0a670070', process, 'CEO', operators='[ceo]') 40 | 41 | # Transitions 42 | create_transition('5f31d065-00e0-0010-beea-641f0a670010', process, 43 | 'Draft', 'Staff Leader') 44 | create_transition('5f31d065-00e0-0010-beea-641f0a670020', process, 45 | 'Staff Leader', 'Department Leader') 46 | 47 | create_transition('5f31d065-00e0-0010-beea-641f0a670030', process, 48 | 'Department Leader', 'HR', 49 | condition='o.leave_days<7 # days<7') 50 | create_transition('5f31d065-00e0-0010-beea-641f0a670040', process, 51 | 'Department Leader', 'CEO', 52 | condition='o.leave_days>=7 # days>=7') 53 | create_transition('5f31d065-00e0-0010-beea-641f0a670045', process, 54 | 'CEO', 'HR') 55 | 56 | create_transition('5f31d065-00e0-0010-beea-641f0a670050', process, 57 | 'HR', 'Completed', 58 | app='Customized URL', 59 | app_param='wf_execute_transition {{wf_code}} c') 60 | -------------------------------------------------------------------------------- /carrot_box/wfapp/purchase/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/carrot_box/wfapp/purchase/__init__.py -------------------------------------------------------------------------------- /carrot_box/wfapp/purchase/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from lbworkflow.models import BaseWFObj 3 | 4 | 5 | class Purchase(BaseWFObj): 6 | title = models.CharField('Title', max_length=255) 7 | reason = models.CharField('Reason', max_length=255) 8 | 9 | def __str__(self): 10 | return self.reason 11 | 12 | 13 | class Item(models.Model): 14 | purchase = models.ForeignKey( 15 | Purchase, 16 | on_delete=models.CASCADE, 17 | ) 18 | name = models.CharField('Name', max_length=255) 19 | qty = models.IntegerField('Qty') 20 | note = models.CharField('Note', max_length=255) 21 | 22 | class Meta: 23 | verbose_name = 'Purchase Item' 24 | 25 | def __str__(self): 26 | return self.name 27 | -------------------------------------------------------------------------------- /carrot_box/wfapp/purchase/wfdata.py: -------------------------------------------------------------------------------- 1 | from lbworkflow.core.datahelper import create_category 2 | from lbworkflow.core.datahelper import create_node 3 | from lbworkflow.core.datahelper import create_process 4 | from lbworkflow.core.datahelper import create_transition 5 | 6 | 7 | def load_data(): 8 | load_issue() 9 | 10 | 11 | def load_issue(): 12 | """ load_[wf_code] """ 13 | category = create_category('5f31d065-00cc-0020-be00-641f0a670010', 'IT') 14 | process = create_process('purchase', 'Purchase', category=category) 15 | create_node('5f31d065-00a0-0030-beea-641f0a670010', process, 'Draft', status='draft') 16 | create_node('5f31d065-00a0-0030-beea-641f0a670020', process, 'Given up', status='given up') 17 | create_node('5f31d065-00a0-0030-beea-641f0a670030', process, 'Rejected', status='rejected') 18 | create_node('5f31d065-00a0-0030-beea-641f0a670040', process, 'Completed', status='completed') 19 | create_node('5f31d065-00a0-0030-beea-641f0a670050', process, 'IT', operators='[it]') 20 | create_transition('5f31d065-00e0-0030-beea-641f0a670010', process, 'Draft,', 'IT') 21 | create_transition('5f31d065-00e0-0030-beea-641f0a670020', process, 'IT,', 'Completed') 22 | -------------------------------------------------------------------------------- /carrot_box/wfdata.py: -------------------------------------------------------------------------------- 1 | from lbworkflow.core.datahelper import create_category 2 | from lbworkflow.core.datahelper import create_node 3 | from lbworkflow.core.datahelper import create_process 4 | from lbworkflow.core.datahelper import create_transition 5 | from lbworkflow.core.datahelper import create_user 6 | from lbworkflow.core.datahelper import get_or_create 7 | 8 | from carrot_box.hr.models import CarrotDepartment 9 | from carrot_box.hr.models import CarrotRole 10 | 11 | 12 | def load_data(): 13 | roles = load_roles() 14 | departments = load_departments() 15 | load_users(departments, roles) 16 | load_simplewf() 17 | 18 | 19 | def create_department(*args, **kwargs): 20 | return get_or_create(CarrotDepartment, *args, **kwargs) 21 | 22 | 23 | def load_departments(): 24 | root = create_department('f1864b0e-da03-4900-9a3b-54362f611cf5', code='root', name='root', ) 25 | hr = create_department('f1864b0e-da03-4901-9a3b-54362f611cf5', code='hr', name='hr', parent=root, ) 26 | it = create_department('f1864b0e-da03-4902-9a3b-54362f611cf5', code='it', name='it', parent=root, ) 27 | departments = { 28 | 'root': root, 29 | 'hr': hr, 30 | 'it': it, 31 | } 32 | return departments 33 | 34 | 35 | def create_role(*args, **kwargs): 36 | return get_or_create(CarrotRole, *args, **kwargs) 37 | 38 | 39 | def load_roles(): 40 | it = create_role('f1864b0e-da02-4900-9a3b-54362f611cf5', code='it', name='it', ) 41 | hr = create_role('f1864b0e-da02-4901-9a3b-54362f611cf5', code='hr', name='hr', ) 42 | roles = { 43 | 'it': it, 44 | 'hr': hr, 45 | } 46 | return roles 47 | 48 | 49 | def load_users(departments, roles): 50 | ceo = create_user('ceo', department_id=departments['root'].pk) 51 | departments['root'].leader = ceo 52 | departments['root'].save() 53 | 54 | tom_leader = create_user('tom_leader', department_id=departments['it'].pk, leader=ceo) 55 | tom = create_user('tom', department_id=departments['it'].pk, leader=tom_leader) 56 | 57 | it_dept_leader = create_user('it_dept_leader', department_id=departments['it'].pk, leader=ceo) 58 | it_leader = create_user('it_leader', department_id=departments['it'].pk, leader=it_dept_leader) 59 | it = create_user('it', department_id=departments['it'].pk, leader=it_leader) 60 | departments['it'].leader = it_dept_leader 61 | departments['it'].save() 62 | 63 | hr_dept_leader = create_user('hr_dept_leader', department_id=departments['hr'].pk, leader=ceo) 64 | hr_leader = create_user('hr_leader', department_id=departments['hr'].pk, leader=hr_dept_leader) 65 | hr = create_user('hr', department_id=departments['hr'].pk, leader=hr_leader) 66 | departments['hr'].leader = hr_dept_leader 67 | departments['hr'].save() 68 | 69 | users = { 70 | 'tom': tom, 71 | 'tom_leader': tom_leader, 72 | 'it': it, 73 | 'it_leader': it_leader, 74 | 'it_dept_leader': it_dept_leader, 75 | 'hr': hr, 76 | 'hr_leader': hr_leader, 77 | 'hr_dept_leader': hr_dept_leader, 78 | 'ceo': ceo, 79 | 'admin': create_user('admin', department_id=departments['root'].pk, 80 | leader=ceo, is_superuser=True, is_staff=True), 81 | } 82 | 83 | roles['it'].users.add(it) 84 | roles['hr'].users.add(hr, hr_leader, hr_dept_leader) 85 | return users 86 | 87 | 88 | def load_simplewf(): 89 | category = create_category('5f31d065-00cc-0020-be00-641f0a670010', 'IT') 90 | 91 | ext_data_buy_computer = { 92 | 'template': """Brand: 93 | Price: 94 | Other requirements: 95 | """ 96 | } 97 | process = create_process('simplewf__buy_computer', 'Buy computer', 98 | category=category, ext_data=ext_data_buy_computer) 99 | # Nodes 100 | create_node('5f31d065-00a0-0010-be00-641f0a670010', process, 'Draft', status='draft') 101 | create_node('5f31d065-00a0-0010-be00-641f0a670020', process, 'Given up', status='given up') 102 | create_node('5f31d065-00a0-0010-be00-641f0a670030', process, 'Rejected', status='rejected') 103 | create_node('5f31d065-00a0-0010-be00-641f0a670040', process, 'Completed', status='completed') 104 | create_node('5f31d065-00a0-0010-be00-641f0a670060', process, 105 | 'Department Leader', operators='[o.created_by.get_department().leader]') 106 | create_node('5f31d065-00a0-0010-be00-641f0a670065', process, 'IT', operators='[it]') 107 | 108 | create_transition('5f31d667-0010-0020-be00-641f0a670010', process, 'Draft,', 'Department Leader') 109 | create_transition('5f31d667-0010-0020-be00-641f0a670020', process, 'Department Leader', 'IT') 110 | create_transition('5f31d667-0010-0020-be00-641f0a670030', process, 'IT,', 'Completed') 111 | 112 | ext_data_issue = { 113 | 'template': """From: 114 | Requirements:""" 115 | } 116 | process = create_process('simplewf__issue', 'Issue', category=category, ext_data=ext_data_issue) 117 | create_node('5f31d667-00a0-0020-be00-641f0a670010', process, 'Draft', status='draft') 118 | create_node('5f31d667-00a0-0020-be00-641f0a670020', process, 'Given up', status='given up') 119 | create_node('5f31d667-00a0-0020-be00-641f0a670030', process, 'Rejected', status='rejected') 120 | create_node('5f31d667-00a0-0020-be00-641f0a670040', process, 'Completed', status='completed') 121 | create_node('5f31d065-00a0-0020-be00-641f0a670060', process, 122 | 'IT Leader', operators='dept_direct_leaders d[it]') 123 | create_node('5f31d065-00a0-0020-be00-641f0a670070', process, 'IT', operators='[it]') 124 | 125 | create_transition('5f31d667-0020-0020-be00-641f0a670010', process, 'Draft,', 'IT Leader') 126 | create_transition('5f31d667-0020-0020-be00-641f0a670020', process, 'IT Leader', 'IT') 127 | create_transition('5f31d667-0020-0020-be00-641f0a670030', process, 'IT,', 'Completed') 128 | -------------------------------------------------------------------------------- /carrot_box/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for carrot_box project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'carrot_box.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'carrot_box.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "admin-lte": "2.3.11", 4 | "blueimp-file-upload": "9.22.1", 5 | "flatpickr": "2.5.6", 6 | "font-awesome": "4.7.0", 7 | "html5shiv": "^3.7.3", 8 | "ionicons": "2.0.1", 9 | "masonry-layout": "^4.2.2", 10 | "mermaid": "^8.13.8", 11 | "modernizr": "^3.11.8", 12 | "respond": "^0.9.0", 13 | "selectivizr": "^1.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /screenshots/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/screenshots/detail.png -------------------------------------------------------------------------------- /screenshots/flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/screenshots/flowchart.png -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicalloy/carrot-box/470e308f449a06234597c9c838cced69c6945945/screenshots/main.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .tox/,*tox/*,docs/*,*/migrations/* 3 | ignore = E123,E128,E402,W503,E731,W601 4 | max-line-length = 119 5 | 6 | [isort] 7 | combine_as_imports = true 8 | default_section = THIRDPARTY 9 | include_trailing_comma = true 10 | known_first_party = carrot_box 11 | multi_line_output = 5 12 | not_skip = __init__.py 13 | skip=migrations 14 | force_single_line = true 15 | line_length = 119 16 | 17 | [bdist_wheel] 18 | universal=1 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{36,37,38}-django{30,_trunk}, 4 | flake8,isort 5 | 6 | skipsdist = True 7 | 8 | 9 | [testenv] 10 | commands = 11 | npm install 12 | coverage run {toxinidir}/manage.py test 13 | 14 | deps = 15 | django30: Django>=3.0,<3.1 16 | django_trunk: https://github.com/django/django/tarball/master 17 | -rrequirements.txt 18 | 19 | [testenv:flake8] 20 | basepython = python 21 | skip_install=true 22 | deps = flake8==3.7.9 23 | commands= flake8 {toxinidir} 24 | 25 | [testenv:isort] 26 | basepython = python 27 | deps = isort 28 | commands = isort --check-only --recursive carrot_box 29 | -------------------------------------------------------------------------------- /wfgen.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | import sys 4 | 5 | import django 6 | from django.core.management import call_command 7 | from lbworkflow.flowgen import FlowAppGenerator 8 | from lbworkflow.flowgen import clean_generated_files 9 | 10 | 11 | def gen(): 12 | from carrot_box.wfapp.purchase.models import Purchase as wf_class 13 | from carrot_box.wfapp.purchase.models import Item as wf_item_class 14 | FlowAppGenerator().gen(wf_class, [wf_item_class], replace=True) 15 | 16 | 17 | def clean(): 18 | from carrot_box.wfapp.purchase.models import Purchase 19 | clean_generated_files(Purchase) 20 | 21 | 22 | def load_data(): 23 | from lbworkflow.core.datahelper import load_wf_data 24 | load_wf_data('carrot_box.wfapp.purchase') 25 | 26 | 27 | if __name__ == "__main__": 28 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 29 | sys.path.insert(0, BASE_DIR) 30 | os.environ['DJANGO_SETTINGS_MODULE'] = "carrot_box.settings" 31 | django.setup() 32 | if (len(sys.argv)) == 2: 33 | cmd = sys.argv[1] 34 | if cmd == 'clean': 35 | clean() 36 | elif cmd == 'uuid': 37 | print(str(uuid.uuid4())) 38 | elif cmd == "parse": 39 | from carrot_box.hr.userparser import CarrotBoxUserParser 40 | print(CarrotBoxUserParser("dept_direct_leaders d[it]").parse()) 41 | sys.exit(0) 42 | gen() 43 | call_command('makemigrations', 'purchase') 44 | call_command('migrate') 45 | load_data() 46 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@braintree/sanitize-url@^3.1.0": 6 | version "3.1.0" 7 | resolved "https://registry.nlark.com/@braintree/sanitize-url/download/@braintree/sanitize-url-3.1.0.tgz#8ff71d51053cd5ee4981e5a501d80a536244f7fd" 8 | integrity sha1-j/cdUQU81e5JgeWlAdgKU2JE9/0= 9 | 10 | admin-lte@2.3.11: 11 | version "2.3.11" 12 | resolved "https://registry.nlark.com/admin-lte/download/admin-lte-2.3.11.tgz#da56dcd34d42e9ef1af362be15520a053a9d43db" 13 | integrity sha1-2lbc001C6e8a82K+FVIKBTqdQ9s= 14 | 15 | ansi-regex@^5.0.1: 16 | version "5.0.1" 17 | resolved "https://registry.nlark.com/ansi-regex/download/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 18 | integrity sha1-CCyyyJyf6GWaMRpTvWpNxTAdswQ= 19 | 20 | ansi-styles@^4.0.0: 21 | version "4.3.0" 22 | resolved "https://registry.nlark.com/ansi-styles/download/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 23 | integrity sha1-7dgDYornHATIWuegkG7a00tkiTc= 24 | dependencies: 25 | color-convert "^2.0.1" 26 | 27 | argparse@^1.0.7: 28 | version "1.0.10" 29 | resolved "https://registry.nlark.com/argparse/download/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 30 | integrity sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE= 31 | dependencies: 32 | sprintf-js "~1.0.2" 33 | 34 | blueimp-canvas-to-blob@3.5.0: 35 | version "3.5.0" 36 | resolved "https://registry.yarnpkg.com/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.5.0.tgz#5679ac32f6a2835821f0c3ad661719ff85a9236b" 37 | integrity sha1-VnmsMvaig1gh8MOtZhcZ/4WpI2s= 38 | 39 | blueimp-file-upload@9.22.1: 40 | version "9.22.1" 41 | resolved "https://registry.yarnpkg.com/blueimp-file-upload/-/blueimp-file-upload-9.22.1.tgz#08a9fccbaf1ec930acf6242c217620b057f0ecc6" 42 | integrity sha512-ezGkn/agWUWZOw8mYa5yYC9LvUlrT5bN3zk2fPlpLWgyhbBMz8BSGKO3M48BWlXWAeR+lVtEhy9xiG8FLnHEVw== 43 | optionalDependencies: 44 | blueimp-canvas-to-blob "3.5.0" 45 | blueimp-load-image "2.12.2" 46 | blueimp-tmpl "3.6.0" 47 | 48 | blueimp-load-image@2.12.2: 49 | version "2.12.2" 50 | resolved "https://registry.yarnpkg.com/blueimp-load-image/-/blueimp-load-image-2.12.2.tgz#6a17598aab858d4fbf01543e0631141b51057c87" 51 | integrity sha1-ahdZiquFjU+/AVQ+BjEUG1EFfIc= 52 | 53 | blueimp-tmpl@3.6.0: 54 | version "3.6.0" 55 | resolved "https://registry.yarnpkg.com/blueimp-tmpl/-/blueimp-tmpl-3.6.0.tgz#a4910975d042e2bc03ba77f0e62d04f1548a524c" 56 | integrity sha1-pJEJddBC4rwDunfw5i0E8VSKUkw= 57 | 58 | camelcase@^5.0.0: 59 | version "5.3.1" 60 | resolved "https://registry.npmmirror.com/camelcase/download/camelcase-5.3.1.tgz?cache=0&sync_timestamp=1636945130104&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcamelcase%2Fdownload%2Fcamelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 61 | integrity sha1-48mzFWnhBoEd8kL3FXJaH0xJQyA= 62 | 63 | cliui@^6.0.0: 64 | version "6.0.0" 65 | resolved "https://registry.nlark.com/cliui/download/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" 66 | integrity sha1-UR1wLAxOQcoVbX0OlgIfI+EyJbE= 67 | dependencies: 68 | string-width "^4.2.0" 69 | strip-ansi "^6.0.0" 70 | wrap-ansi "^6.2.0" 71 | 72 | color-convert@^2.0.1: 73 | version "2.0.1" 74 | resolved "https://registry.nlark.com/color-convert/download/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 75 | integrity sha1-ctOmjVmMm9s68q0ehPIdiWq9TeM= 76 | dependencies: 77 | color-name "~1.1.4" 78 | 79 | color-name@~1.1.4: 80 | version "1.1.4" 81 | resolved "https://registry.npm.taobao.org/color-name/download/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 82 | integrity sha1-wqCah6y95pVD3m9j+jmVyCbFNqI= 83 | 84 | commander@2: 85 | version "2.20.3" 86 | resolved "https://registry.npmmirror.com/commander/download/commander-2.20.3.tgz?cache=0&sync_timestamp=1634886357672&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcommander%2Fdownload%2Fcommander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 87 | integrity sha1-/UhehMA+tIgcIHIrpIA16FMa6zM= 88 | 89 | commander@7: 90 | version "7.2.0" 91 | resolved "https://registry.npmmirror.com/commander/download/commander-7.2.0.tgz?cache=0&sync_timestamp=1634886357672&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fcommander%2Fdownload%2Fcommander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" 92 | integrity sha1-o2y1fQtQHOEI5NIFWaFQo5HZerc= 93 | 94 | d3-array@1, d3-array@^1.1.1, d3-array@^1.2.0: 95 | version "1.2.4" 96 | resolved "https://registry.npmmirror.com/d3-array/download/d3-array-1.2.4.tgz?cache=0&sync_timestamp=1633231313339&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fd3-array%2Fdownload%2Fd3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" 97 | integrity sha1-Y1zk1e6nWfb2BYY9vPww7cc39x8= 98 | 99 | "d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: 100 | version "3.1.1" 101 | resolved "https://registry.npmmirror.com/d3-array/download/d3-array-3.1.1.tgz?cache=0&sync_timestamp=1633231313339&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fd3-array%2Fdownload%2Fd3-array-3.1.1.tgz#7797eb53ead6b9083c75a45a681e93fc41bc468c" 102 | integrity sha1-d5frU+rWuQg8daRaaB6T/EG8Row= 103 | dependencies: 104 | internmap "1 - 2" 105 | 106 | d3-axis@1: 107 | version "1.0.12" 108 | resolved "https://registry.nlark.com/d3-axis/download/d3-axis-1.0.12.tgz#cdf20ba210cfbb43795af33756886fb3638daac9" 109 | integrity sha1-zfILohDPu0N5WvM3Vohvs2ONqsk= 110 | 111 | d3-axis@3: 112 | version "3.0.0" 113 | resolved "https://registry.nlark.com/d3-axis/download/d3-axis-3.0.0.tgz#c42a4a13e8131d637b745fc2973824cfeaf93322" 114 | integrity sha1-xCpKE+gTHWN7dF/Clzgkz+r5MyI= 115 | 116 | d3-brush@1: 117 | version "1.1.6" 118 | resolved "https://registry.nlark.com/d3-brush/download/d3-brush-1.1.6.tgz#b0a22c7372cabec128bdddf9bddc058592f89e9b" 119 | integrity sha1-sKIsc3LKvsEovd35vdwFhZL4nps= 120 | dependencies: 121 | d3-dispatch "1" 122 | d3-drag "1" 123 | d3-interpolate "1" 124 | d3-selection "1" 125 | d3-transition "1" 126 | 127 | d3-brush@3: 128 | version "3.0.0" 129 | resolved "https://registry.nlark.com/d3-brush/download/d3-brush-3.0.0.tgz#6f767c4ed8dcb79de7ede3e1c0f89e63ef64d31c" 130 | integrity sha1-b3Z8Ttjct53n7ePhwPieY+9k0xw= 131 | dependencies: 132 | d3-dispatch "1 - 3" 133 | d3-drag "2 - 3" 134 | d3-interpolate "1 - 3" 135 | d3-selection "3" 136 | d3-transition "3" 137 | 138 | d3-chord@1: 139 | version "1.0.6" 140 | resolved "https://registry.nlark.com/d3-chord/download/d3-chord-1.0.6.tgz#309157e3f2db2c752f0280fedd35f2067ccbb15f" 141 | integrity sha1-MJFX4/LbLHUvAoD+3TXyBnzLsV8= 142 | dependencies: 143 | d3-array "1" 144 | d3-path "1" 145 | 146 | d3-chord@3: 147 | version "3.0.1" 148 | resolved "https://registry.nlark.com/d3-chord/download/d3-chord-3.0.1.tgz#d156d61f485fce8327e6abf339cb41d8cbba6966" 149 | integrity sha1-0VbWH0hfzoMn5qvzOctB2Mu6aWY= 150 | dependencies: 151 | d3-path "1 - 3" 152 | 153 | d3-collection@1: 154 | version "1.0.7" 155 | resolved "https://registry.npm.taobao.org/d3-collection/download/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e" 156 | integrity sha1-NJvSqpl32wcQkcExRNXk8WtbMQ4= 157 | 158 | d3-color@1: 159 | version "1.4.1" 160 | resolved "https://registry.nlark.com/d3-color/download/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a" 161 | integrity sha1-xSACv4hGraRCTVXZeYL+8m6zvIo= 162 | 163 | "d3-color@1 - 3", d3-color@3: 164 | version "3.0.1" 165 | resolved "https://registry.nlark.com/d3-color/download/d3-color-3.0.1.tgz#03316e595955d1fcd39d9f3610ad41bb90194d0a" 166 | integrity sha1-AzFuWVlV0fzTnZ82EK1Bu5AZTQo= 167 | 168 | d3-contour@1: 169 | version "1.3.2" 170 | resolved "https://registry.nlark.com/d3-contour/download/d3-contour-1.3.2.tgz#652aacd500d2264cb3423cee10db69f6f59bead3" 171 | integrity sha1-ZSqs1QDSJkyzQjzuENtp9vWb6tM= 172 | dependencies: 173 | d3-array "^1.1.1" 174 | 175 | d3-contour@3: 176 | version "3.0.1" 177 | resolved "https://registry.nlark.com/d3-contour/download/d3-contour-3.0.1.tgz#2c64255d43059599cd0dba8fe4cc3d51ccdd9bbd" 178 | integrity sha1-LGQlXUMFlZnNDbqP5Mw9Uczdm70= 179 | dependencies: 180 | d3-array "2 - 3" 181 | 182 | d3-delaunay@6: 183 | version "6.0.2" 184 | resolved "https://registry.nlark.com/d3-delaunay/download/d3-delaunay-6.0.2.tgz#7fd3717ad0eade2fc9939f4260acfb503f984e92" 185 | integrity sha1-f9NxetDq3i/Jk59CYKz7UD+YTpI= 186 | dependencies: 187 | delaunator "5" 188 | 189 | d3-dispatch@1: 190 | version "1.0.6" 191 | resolved "https://registry.nlark.com/d3-dispatch/download/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58" 192 | integrity sha1-ANN7zuTdjNl3Kd2JOgrCnKq6XVg= 193 | 194 | "d3-dispatch@1 - 3", d3-dispatch@3: 195 | version "3.0.1" 196 | resolved "https://registry.nlark.com/d3-dispatch/download/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e" 197 | integrity sha1-X8dShOnCN1w2yDlBGgz1UMv8TV4= 198 | 199 | d3-drag@1: 200 | version "1.2.5" 201 | resolved "https://registry.nlark.com/d3-drag/download/d3-drag-1.2.5.tgz?cache=0&sync_timestamp=1623254998807&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fd3-drag%2Fdownload%2Fd3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70" 202 | integrity sha1-JTf0UazTnTFAZne33HfIL32Yj3A= 203 | dependencies: 204 | d3-dispatch "1" 205 | d3-selection "1" 206 | 207 | "d3-drag@2 - 3", d3-drag@3: 208 | version "3.0.0" 209 | resolved "https://registry.nlark.com/d3-drag/download/d3-drag-3.0.0.tgz?cache=0&sync_timestamp=1623254998807&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fd3-drag%2Fdownload%2Fd3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba" 210 | integrity sha1-mUqunNI8cZ9TteEOOgphCMaWB7o= 211 | dependencies: 212 | d3-dispatch "1 - 3" 213 | d3-selection "3" 214 | 215 | d3-dsv@1: 216 | version "1.2.0" 217 | resolved "https://registry.nlark.com/d3-dsv/download/d3-dsv-1.2.0.tgz#9d5f75c3a5f8abd611f74d3f5847b0d4338b885c" 218 | integrity sha1-nV91w6X4q9YR900/WEew1DOLiFw= 219 | dependencies: 220 | commander "2" 221 | iconv-lite "0.4" 222 | rw "1" 223 | 224 | "d3-dsv@1 - 3", d3-dsv@3: 225 | version "3.0.1" 226 | resolved "https://registry.nlark.com/d3-dsv/download/d3-dsv-3.0.1.tgz#c63af978f4d6a0d084a52a673922be2160789b73" 227 | integrity sha1-xjr5ePTWoNCEpSpnOSK+IWB4m3M= 228 | dependencies: 229 | commander "7" 230 | iconv-lite "0.6" 231 | rw "1" 232 | 233 | d3-ease@1: 234 | version "1.0.7" 235 | resolved "https://registry.nlark.com/d3-ease/download/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" 236 | integrity sha1-moNIkO+LiujFWLL+Vb1X9Zk7heI= 237 | 238 | "d3-ease@1 - 3", d3-ease@3: 239 | version "3.0.1" 240 | resolved "https://registry.nlark.com/d3-ease/download/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" 241 | integrity sha1-llisOKIUDVnTRhYPH2ww/aC9EvQ= 242 | 243 | d3-fetch@1: 244 | version "1.2.0" 245 | resolved "https://registry.nlark.com/d3-fetch/download/d3-fetch-1.2.0.tgz#15ce2ecfc41b092b1db50abd2c552c2316cf7fc7" 246 | integrity sha1-Fc4uz8QbCSsdtQq9LFUsIxbPf8c= 247 | dependencies: 248 | d3-dsv "1" 249 | 250 | d3-fetch@3: 251 | version "3.0.1" 252 | resolved "https://registry.nlark.com/d3-fetch/download/d3-fetch-3.0.1.tgz#83141bff9856a0edb5e38de89cdcfe63d0a60a22" 253 | integrity sha1-gxQb/5hWoO21443onNz+Y9CmCiI= 254 | dependencies: 255 | d3-dsv "1 - 3" 256 | 257 | d3-force@1: 258 | version "1.2.1" 259 | resolved "https://registry.nlark.com/d3-force/download/d3-force-1.2.1.tgz#fd29a5d1ff181c9e7f0669e4bd72bdb0e914ec0b" 260 | integrity sha1-/Sml0f8YHJ5/BmnkvXK9sOkU7As= 261 | dependencies: 262 | d3-collection "1" 263 | d3-dispatch "1" 264 | d3-quadtree "1" 265 | d3-timer "1" 266 | 267 | d3-force@3: 268 | version "3.0.0" 269 | resolved "https://registry.nlark.com/d3-force/download/d3-force-3.0.0.tgz#3e2ba1a61e70888fe3d9194e30d6d14eece155c4" 270 | integrity sha1-Piuhph5wiI/j2RlOMNbRTuzhVcQ= 271 | dependencies: 272 | d3-dispatch "1 - 3" 273 | d3-quadtree "1 - 3" 274 | d3-timer "1 - 3" 275 | 276 | d3-format@1: 277 | version "1.4.5" 278 | resolved "https://registry.npmmirror.com/d3-format/download/d3-format-1.4.5.tgz#374f2ba1320e3717eb74a9356c67daee17a7edb4" 279 | integrity sha1-N08roTIONxfrdKk1bGfa7hen7bQ= 280 | 281 | "d3-format@1 - 3", d3-format@3: 282 | version "3.1.0" 283 | resolved "https://registry.npmmirror.com/d3-format/download/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" 284 | integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== 285 | 286 | d3-geo@1: 287 | version "1.12.1" 288 | resolved "https://registry.nlark.com/d3-geo/download/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f" 289 | integrity sha1-f8KrdBS3Lln7y9YD6A2a3AKbA18= 290 | dependencies: 291 | d3-array "1" 292 | 293 | d3-geo@3: 294 | version "3.0.1" 295 | resolved "https://registry.nlark.com/d3-geo/download/d3-geo-3.0.1.tgz#4f92362fd8685d93e3b1fae0fd97dc8980b1ed7e" 296 | integrity sha1-T5I2L9hoXZPjsfrg/ZfciYCx7X4= 297 | dependencies: 298 | d3-array "2.5.0 - 3" 299 | 300 | d3-hierarchy@1: 301 | version "1.1.9" 302 | resolved "https://registry.npmmirror.com/d3-hierarchy/download/d3-hierarchy-1.1.9.tgz#2f6bee24caaea43f8dc37545fa01628559647a83" 303 | integrity sha1-L2vuJMqupD+Nw3VF+gFihVlkeoM= 304 | 305 | d3-hierarchy@3: 306 | version "3.1.1" 307 | resolved "https://registry.npmmirror.com/d3-hierarchy/download/d3-hierarchy-3.1.1.tgz#9cbb0ffd2375137a351e6cfeed344a06d4ff4597" 308 | integrity sha512-LtAIu54UctRmhGKllleflmHalttH3zkfSi4NlKrTAoFKjC+AFBJohsCAdgCBYQwH0F8hIOGY89X1pPqAchlMkA== 309 | 310 | d3-interpolate@1: 311 | version "1.4.0" 312 | resolved "https://registry.nlark.com/d3-interpolate/download/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987" 313 | integrity sha1-Um554tgNqjg/ngwcHH3MDwWD6Yc= 314 | dependencies: 315 | d3-color "1" 316 | 317 | "d3-interpolate@1 - 3", "d3-interpolate@1.2.0 - 3", d3-interpolate@3: 318 | version "3.0.1" 319 | resolved "https://registry.nlark.com/d3-interpolate/download/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" 320 | integrity sha1-PEeqWzLFs9+1bvP9Q0IHimMrQA0= 321 | dependencies: 322 | d3-color "1 - 3" 323 | 324 | d3-path@1: 325 | version "1.0.9" 326 | resolved "https://registry.nlark.com/d3-path/download/d3-path-1.0.9.tgz#48c050bb1fe8c262493a8caf5524e3e9591701cf" 327 | integrity sha1-SMBQux/owmJJOoyvVSTj6VkXAc8= 328 | 329 | "d3-path@1 - 3", d3-path@3: 330 | version "3.0.1" 331 | resolved "https://registry.nlark.com/d3-path/download/d3-path-3.0.1.tgz#f09dec0aaffd770b7995f1a399152bf93052321e" 332 | integrity sha1-8J3sCq/9dwt5lfGjmRUr+TBSMh4= 333 | 334 | d3-polygon@1: 335 | version "1.0.6" 336 | resolved "https://registry.nlark.com/d3-polygon/download/d3-polygon-1.0.6.tgz#0bf8cb8180a6dc107f518ddf7975e12abbfbd38e" 337 | integrity sha1-C/jLgYCm3BB/UY3feXXhKrv7044= 338 | 339 | d3-polygon@3: 340 | version "3.0.1" 341 | resolved "https://registry.nlark.com/d3-polygon/download/d3-polygon-3.0.1.tgz#0b45d3dd1c48a29c8e057e6135693ec80bf16398" 342 | integrity sha1-C0XT3RxIopyOBX5hNWk+yAvxY5g= 343 | 344 | d3-quadtree@1: 345 | version "1.0.7" 346 | resolved "https://registry.nlark.com/d3-quadtree/download/d3-quadtree-1.0.7.tgz#ca8b84df7bb53763fe3c2f24bd435137f4e53135" 347 | integrity sha1-youE33u1N2P+PC8kvUNRN/TlMTU= 348 | 349 | "d3-quadtree@1 - 3", d3-quadtree@3: 350 | version "3.0.1" 351 | resolved "https://registry.nlark.com/d3-quadtree/download/d3-quadtree-3.0.1.tgz#6dca3e8be2b393c9a9d514dabbd80a92deef1a4f" 352 | integrity sha1-bco+i+Kzk8mp1RTau9gKkt7vGk8= 353 | 354 | d3-random@1: 355 | version "1.1.2" 356 | resolved "https://registry.nlark.com/d3-random/download/d3-random-1.1.2.tgz#2833be7c124360bf9e2d3fd4f33847cfe6cab291" 357 | integrity sha1-KDO+fBJDYL+eLT/U8zhHz+bKspE= 358 | 359 | d3-random@3: 360 | version "3.0.1" 361 | resolved "https://registry.nlark.com/d3-random/download/d3-random-3.0.1.tgz#d4926378d333d9c0bfd1e6fa0194d30aebaa20f4" 362 | integrity sha1-1JJjeNMz2cC/0eb6AZTTCuuqIPQ= 363 | 364 | d3-scale-chromatic@1: 365 | version "1.5.0" 366 | resolved "https://registry.nlark.com/d3-scale-chromatic/download/d3-scale-chromatic-1.5.0.tgz#54e333fc78212f439b14641fb55801dd81135a98" 367 | integrity sha1-VOMz/HghL0ObFGQftVgB3YETWpg= 368 | dependencies: 369 | d3-color "1" 370 | d3-interpolate "1" 371 | 372 | d3-scale-chromatic@3: 373 | version "3.0.0" 374 | resolved "https://registry.nlark.com/d3-scale-chromatic/download/d3-scale-chromatic-3.0.0.tgz#15b4ceb8ca2bb0dcb6d1a641ee03d59c3b62376a" 375 | integrity sha1-FbTOuMorsNy20aZB7gPVnDtiN2o= 376 | dependencies: 377 | d3-color "1 - 3" 378 | d3-interpolate "1 - 3" 379 | 380 | d3-scale@2: 381 | version "2.2.2" 382 | resolved "https://registry.npmmirror.com/d3-scale/download/d3-scale-2.2.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fd3-scale%2Fdownload%2Fd3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f" 383 | integrity sha1-TogOCydFrKrd0+3iap6Qip4XuB8= 384 | dependencies: 385 | d3-array "^1.2.0" 386 | d3-collection "1" 387 | d3-format "1" 388 | d3-interpolate "1" 389 | d3-time "1" 390 | d3-time-format "2" 391 | 392 | d3-scale@4: 393 | version "4.0.2" 394 | resolved "https://registry.npmmirror.com/d3-scale/download/d3-scale-4.0.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fd3-scale%2Fdownload%2Fd3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" 395 | integrity sha1-grOOjo/3CAdk+Nzsd71L45Nok5Y= 396 | dependencies: 397 | d3-array "2.10.0 - 3" 398 | d3-format "1 - 3" 399 | d3-interpolate "1.2.0 - 3" 400 | d3-time "2.1.1 - 3" 401 | d3-time-format "2 - 4" 402 | 403 | d3-selection@1, d3-selection@^1.1.0: 404 | version "1.4.2" 405 | resolved "https://registry.nlark.com/d3-selection/download/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c" 406 | integrity sha1-3KpJUiwNvzLWwYWK/Ca2CUVVvFw= 407 | 408 | "d3-selection@2 - 3", d3-selection@3: 409 | version "3.0.0" 410 | resolved "https://registry.nlark.com/d3-selection/download/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31" 411 | integrity sha1-wlM4IH76csxbm9FFihpBkB8eGzE= 412 | 413 | d3-shape@1: 414 | version "1.3.7" 415 | resolved "https://registry.nlark.com/d3-shape/download/d3-shape-1.3.7.tgz#df63801be07bc986bc54f63789b4fe502992b5d7" 416 | integrity sha1-32OAG+B7yYa8VPY3ibT+UCmStdc= 417 | dependencies: 418 | d3-path "1" 419 | 420 | d3-shape@3: 421 | version "3.0.1" 422 | resolved "https://registry.nlark.com/d3-shape/download/d3-shape-3.0.1.tgz#9ccdfb28fd9b0d12f2d8aec234cd5c4a9ea27931" 423 | integrity sha1-nM37KP2bDRLy2K7CNM1cSp6ieTE= 424 | dependencies: 425 | d3-path "1 - 3" 426 | 427 | d3-time-format@2: 428 | version "2.3.0" 429 | resolved "https://registry.npmmirror.com/d3-time-format/download/d3-time-format-2.3.0.tgz#107bdc028667788a8924ba040faf1fbccd5a7850" 430 | integrity sha1-EHvcAoZneIqJJLoED68fvM1aeFA= 431 | dependencies: 432 | d3-time "1" 433 | 434 | "d3-time-format@2 - 4", d3-time-format@4: 435 | version "4.1.0" 436 | resolved "https://registry.npmmirror.com/d3-time-format/download/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" 437 | integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== 438 | dependencies: 439 | d3-time "1 - 3" 440 | 441 | d3-time@1: 442 | version "1.1.0" 443 | resolved "https://registry.nlark.com/d3-time/download/d3-time-1.1.0.tgz#b1e19d307dae9c900b7e5b25ffc5dcc249a8a0f1" 444 | integrity sha1-seGdMH2unJALflsl/8XcwkmooPE= 445 | 446 | "d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@3: 447 | version "3.0.0" 448 | resolved "https://registry.nlark.com/d3-time/download/d3-time-3.0.0.tgz#65972cb98ae2d4954ef5c932e8704061335d4975" 449 | integrity sha1-ZZcsuYri1JVO9cky6HBAYTNdSXU= 450 | dependencies: 451 | d3-array "2 - 3" 452 | 453 | d3-timer@1: 454 | version "1.0.10" 455 | resolved "https://registry.nlark.com/d3-timer/download/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" 456 | integrity sha1-3+dripF0iDGxO22ceT/71QjdneU= 457 | 458 | "d3-timer@1 - 3", d3-timer@3: 459 | version "3.0.1" 460 | resolved "https://registry.nlark.com/d3-timer/download/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" 461 | integrity sha1-YoTSonCChbGrt+IB7aQ4CvNeY7A= 462 | 463 | d3-transition@1: 464 | version "1.3.2" 465 | resolved "https://registry.nlark.com/d3-transition/download/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398" 466 | integrity sha1-qY7yFRvo2GAFQ0NMHKgBQK4js5g= 467 | dependencies: 468 | d3-color "1" 469 | d3-dispatch "1" 470 | d3-ease "1" 471 | d3-interpolate "1" 472 | d3-selection "^1.1.0" 473 | d3-timer "1" 474 | 475 | "d3-transition@2 - 3", d3-transition@3: 476 | version "3.0.1" 477 | resolved "https://registry.nlark.com/d3-transition/download/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f" 478 | integrity sha1-aGn93hRIhoB3/dWYkgDLYbKhZF8= 479 | dependencies: 480 | d3-color "1 - 3" 481 | d3-dispatch "1 - 3" 482 | d3-ease "1 - 3" 483 | d3-interpolate "1 - 3" 484 | d3-timer "1 - 3" 485 | 486 | d3-voronoi@1: 487 | version "1.1.4" 488 | resolved "https://registry.npm.taobao.org/d3-voronoi/download/d3-voronoi-1.1.4.tgz#dd3c78d7653d2bb359284ae478645d95944c8297" 489 | integrity sha1-3Tx412U9K7NZKErkeGRdlZRMgpc= 490 | 491 | d3-zoom@1: 492 | version "1.8.3" 493 | resolved "https://registry.nlark.com/d3-zoom/download/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a" 494 | integrity sha1-tqPb5zjHdjEhzQW4p3lf/hf0/Ao= 495 | dependencies: 496 | d3-dispatch "1" 497 | d3-drag "1" 498 | d3-interpolate "1" 499 | d3-selection "1" 500 | d3-transition "1" 501 | 502 | d3-zoom@3: 503 | version "3.0.0" 504 | resolved "https://registry.nlark.com/d3-zoom/download/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3" 505 | integrity sha1-0T9BZccyF//qpUKVzWlps+eu6PM= 506 | dependencies: 507 | d3-dispatch "1 - 3" 508 | d3-drag "2 - 3" 509 | d3-interpolate "1 - 3" 510 | d3-selection "2 - 3" 511 | d3-transition "2 - 3" 512 | 513 | d3@^5.14: 514 | version "5.16.0" 515 | resolved "https://registry.npmmirror.com/d3/download/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877" 516 | integrity sha1-nF6NO1ZAPHnU7UL71i9hE/GZyHc= 517 | dependencies: 518 | d3-array "1" 519 | d3-axis "1" 520 | d3-brush "1" 521 | d3-chord "1" 522 | d3-collection "1" 523 | d3-color "1" 524 | d3-contour "1" 525 | d3-dispatch "1" 526 | d3-drag "1" 527 | d3-dsv "1" 528 | d3-ease "1" 529 | d3-fetch "1" 530 | d3-force "1" 531 | d3-format "1" 532 | d3-geo "1" 533 | d3-hierarchy "1" 534 | d3-interpolate "1" 535 | d3-path "1" 536 | d3-polygon "1" 537 | d3-quadtree "1" 538 | d3-random "1" 539 | d3-scale "2" 540 | d3-scale-chromatic "1" 541 | d3-selection "1" 542 | d3-shape "1" 543 | d3-time "1" 544 | d3-time-format "2" 545 | d3-timer "1" 546 | d3-transition "1" 547 | d3-voronoi "1" 548 | d3-zoom "1" 549 | 550 | d3@^7.0.0: 551 | version "7.2.1" 552 | resolved "https://registry.npmmirror.com/d3/download/d3-7.2.1.tgz#97eafaa6fc8cd7c564c3ace1e6678cbecf63f3ea" 553 | integrity sha512-E/5sP0aeK6YPXI/+4QlefvBFgmcyR2jYftId0PrYWv4Y/gW3c3thp1XG4rQzF0eUwV9tR1x05X5eWuJ6rQXvew== 554 | dependencies: 555 | d3-array "3" 556 | d3-axis "3" 557 | d3-brush "3" 558 | d3-chord "3" 559 | d3-color "3" 560 | d3-contour "3" 561 | d3-delaunay "6" 562 | d3-dispatch "3" 563 | d3-drag "3" 564 | d3-dsv "3" 565 | d3-ease "3" 566 | d3-fetch "3" 567 | d3-force "3" 568 | d3-format "3" 569 | d3-geo "3" 570 | d3-hierarchy "3" 571 | d3-interpolate "3" 572 | d3-path "3" 573 | d3-polygon "3" 574 | d3-quadtree "3" 575 | d3-random "3" 576 | d3-scale "4" 577 | d3-scale-chromatic "3" 578 | d3-selection "3" 579 | d3-shape "3" 580 | d3-time "3" 581 | d3-time-format "4" 582 | d3-timer "3" 583 | d3-transition "3" 584 | d3-zoom "3" 585 | 586 | dagre-d3@^0.6.4: 587 | version "0.6.4" 588 | resolved "https://registry.npm.taobao.org/dagre-d3/download/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29" 589 | integrity sha1-ByjVzn8XfKIzffFBzrYPvm7reyk= 590 | dependencies: 591 | d3 "^5.14" 592 | dagre "^0.8.5" 593 | graphlib "^2.1.8" 594 | lodash "^4.17.15" 595 | 596 | dagre@^0.8.5: 597 | version "0.8.5" 598 | resolved "https://registry.npm.taobao.org/dagre/download/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" 599 | integrity sha1-ujCwBV2sErbB/MJHgXRCd30Gr+4= 600 | dependencies: 601 | graphlib "^2.1.8" 602 | lodash "^4.17.15" 603 | 604 | decamelize@^1.2.0: 605 | version "1.2.0" 606 | resolved "https://registry.npmmirror.com/decamelize/download/decamelize-1.2.0.tgz?cache=0&sync_timestamp=1633055760479&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 607 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 608 | 609 | delaunator@5: 610 | version "5.0.0" 611 | resolved "https://registry.npm.taobao.org/delaunator/download/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" 612 | integrity sha1-YPBSsovZHJtFZoUOv3dW7+gh2Bs= 613 | dependencies: 614 | robust-predicates "^3.0.0" 615 | 616 | desandro-matches-selector@^2.0.0: 617 | version "2.0.2" 618 | resolved "https://registry.npm.taobao.org/desandro-matches-selector/download/desandro-matches-selector-2.0.2.tgz#717beed4dc13e7d8f3762f707a6d58a6774218e1" 619 | integrity sha1-cXvu1NwT59jzdi9wem1YpndCGOE= 620 | 621 | doctrine@^3.0.0: 622 | version "3.0.0" 623 | resolved "https://registry.nlark.com/doctrine/download/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 624 | integrity sha1-rd6+rXKmV023g2OdyHoSF3OXOWE= 625 | dependencies: 626 | esutils "^2.0.2" 627 | 628 | dompurify@2.3.4: 629 | version "2.3.4" 630 | resolved "https://registry.npmmirror.com/dompurify/download/dompurify-2.3.4.tgz#1cf5cf0105ccb4debdf6db162525bd41e6ddacc6" 631 | integrity sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ== 632 | 633 | emoji-regex@^8.0.0: 634 | version "8.0.0" 635 | resolved "https://registry.npmmirror.com/emoji-regex/download/emoji-regex-8.0.0.tgz?cache=0&sync_timestamp=1632751333727&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Femoji-regex%2Fdownload%2Femoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 636 | integrity sha1-6Bj9ac5cz8tARZT4QpY79TFkzDc= 637 | 638 | entities@~2.0.0: 639 | version "2.0.3" 640 | resolved "https://registry.nlark.com/entities/download/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f" 641 | integrity sha1-XEh+V0Krk8Fau12iJ1m4WQ7AO38= 642 | 643 | esutils@^2.0.2: 644 | version "2.0.3" 645 | resolved "https://registry.npm.taobao.org/esutils/download/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 646 | integrity sha1-dNLrTeC42hKTcRkQ1Qd1ubcQ72Q= 647 | 648 | ev-emitter@^1.0.0: 649 | version "1.1.1" 650 | resolved "https://registry.npm.taobao.org/ev-emitter/download/ev-emitter-1.1.1.tgz#8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" 651 | integrity sha1-jxiwzlx2pdGAF/ccCnlcZbkTjyo= 652 | 653 | file@^0.2.2: 654 | version "0.2.2" 655 | resolved "https://registry.npm.taobao.org/file/download/file-0.2.2.tgz#c3dfd8f8cf3535ae455c2b423c2e52635d76b4d3" 656 | integrity sha1-w9/Y+M81Na5FXCtCPC5SY112tNM= 657 | 658 | find-up@^4.1.0: 659 | version "4.1.0" 660 | resolved "https://registry.npmmirror.com/find-up/download/find-up-4.1.0.tgz?cache=0&sync_timestamp=1633620747957&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Ffind-up%2Fdownload%2Ffind-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 661 | integrity sha1-l6/n1s3AvFkoWEt8jXsW6KmqXRk= 662 | dependencies: 663 | locate-path "^5.0.0" 664 | path-exists "^4.0.0" 665 | 666 | fizzy-ui-utils@^2.0.0: 667 | version "2.0.7" 668 | resolved "https://registry.npm.taobao.org/fizzy-ui-utils/download/fizzy-ui-utils-2.0.7.tgz#7df45dcc4eb374a08b65d39bb9a4beedf7330505" 669 | integrity sha1-ffRdzE6zdKCLZdObuaS+7fczBQU= 670 | dependencies: 671 | desandro-matches-selector "^2.0.0" 672 | 673 | flatpickr@2.5.6: 674 | version "2.5.6" 675 | resolved "https://registry.nlark.com/flatpickr/download/flatpickr-2.5.6.tgz#79349de3d6685305a7b764d66c3cc0f8b05d9bb7" 676 | integrity sha1-eTSd49ZoUwWnt2TWbDzA+LBdm7c= 677 | 678 | font-awesome@4.7.0: 679 | version "4.7.0" 680 | resolved "https://registry.npm.taobao.org/font-awesome/download/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" 681 | integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= 682 | 683 | get-caller-file@^2.0.1: 684 | version "2.0.5" 685 | resolved "https://registry.nlark.com/get-caller-file/download/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 686 | integrity sha1-T5RBKoLbMvNuOwuXQfipf+sDH34= 687 | 688 | get-size@^2.0.2: 689 | version "2.0.3" 690 | resolved "https://registry.npm.taobao.org/get-size/download/get-size-2.0.3.tgz#54a1d0256b20ea7ac646516756202769941ad2ef" 691 | integrity sha1-VKHQJWsg6nrGRlFnViAnaZQa0u8= 692 | 693 | graphlib@^2.1.8: 694 | version "2.1.8" 695 | resolved "https://registry.npm.taobao.org/graphlib/download/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da" 696 | integrity sha1-V2HUFHN4cAhMkux7XbywWSydNdo= 697 | dependencies: 698 | lodash "^4.17.15" 699 | 700 | html5shiv@^3.7.3: 701 | version "3.7.3" 702 | resolved "https://registry.npm.taobao.org/html5shiv/download/html5shiv-3.7.3.tgz#d78a84a367bcb9a710100d57802c387b084631d2" 703 | integrity sha1-14qEo2e8uacQEA1XgCw4ewhGMdI= 704 | 705 | iconv-lite@0.4: 706 | version "0.4.24" 707 | resolved "https://registry.nlark.com/iconv-lite/download/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 708 | integrity sha1-ICK0sl+93CHS9SSXSkdKr+czkIs= 709 | dependencies: 710 | safer-buffer ">= 2.1.2 < 3" 711 | 712 | iconv-lite@0.6: 713 | version "0.6.3" 714 | resolved "https://registry.nlark.com/iconv-lite/download/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" 715 | integrity sha1-pS+AvzjaGVLrXGgXkHGYcaGnJQE= 716 | dependencies: 717 | safer-buffer ">= 2.1.2 < 3.0.0" 718 | 719 | "internmap@1 - 2": 720 | version "2.0.3" 721 | resolved "https://registry.nlark.com/internmap/download/internmap-2.0.3.tgz?cache=0&sync_timestamp=1632107515842&other_urls=https%3A%2F%2Fregistry.nlark.com%2Finternmap%2Fdownload%2Finternmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" 722 | integrity sha1-ZoXyN1XkPFJOJR0py8lySOMGEAk= 723 | 724 | ionicons@2.0.1: 725 | version "2.0.1" 726 | resolved "https://registry.npmmirror.com/ionicons/download/ionicons-2.0.1.tgz#ca398113293ea870244f538f0aabbd4b5b209a3e" 727 | integrity sha1-yjmBEyk+qHAkT1OPCqu9S1sgmj4= 728 | 729 | is-fullwidth-code-point@^3.0.0: 730 | version "3.0.0" 731 | resolved "https://registry.nlark.com/is-fullwidth-code-point/download/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 732 | integrity sha1-8Rb4Bk/pCz94RKOJl8C3UFEmnx0= 733 | 734 | khroma@^1.4.1: 735 | version "1.4.1" 736 | resolved "https://registry.npm.taobao.org/khroma/download/khroma-1.4.1.tgz#ad6a5b6a972befc5112ce5129887a1a83af2c003" 737 | integrity sha1-rWpbapcr78URLOUSmIehqDrywAM= 738 | 739 | linkify-it@^2.0.0: 740 | version "2.2.0" 741 | resolved "https://registry.npmmirror.com/linkify-it/download/linkify-it-2.2.0.tgz?cache=0&sync_timestamp=1633116216426&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Flinkify-it%2Fdownload%2Flinkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" 742 | integrity sha1-47VGl+eL+RXHCjis14/QngBYsc8= 743 | dependencies: 744 | uc.micro "^1.0.1" 745 | 746 | locate-path@^5.0.0: 747 | version "5.0.0" 748 | resolved "https://registry.nlark.com/locate-path/download/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" 749 | integrity sha1-Gvujlq/WdqbUJQTQpno6frn2KqA= 750 | dependencies: 751 | p-locate "^4.1.0" 752 | 753 | lodash@^4.17.15, lodash@^4.17.21: 754 | version "4.17.21" 755 | resolved "https://registry.nlark.com/lodash/download/lodash-4.17.21.tgz?cache=0&sync_timestamp=1618847150612&other_urls=https%3A%2F%2Fregistry.nlark.com%2Flodash%2Fdownload%2Flodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 756 | integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= 757 | 758 | markdown-it@^10.0.0: 759 | version "10.0.0" 760 | resolved "https://registry.npmmirror.com/markdown-it/download/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" 761 | integrity sha1-q/xk8UGxci1mNAIETkOSfx9QqNw= 762 | dependencies: 763 | argparse "^1.0.7" 764 | entities "~2.0.0" 765 | linkify-it "^2.0.0" 766 | mdurl "^1.0.1" 767 | uc.micro "^1.0.5" 768 | 769 | masonry-layout@^4.2.2: 770 | version "4.2.2" 771 | resolved "https://registry.npm.taobao.org/masonry-layout/download/masonry-layout-4.2.2.tgz#d57b44af13e601bfcdc423f1dd8348b5524de348" 772 | integrity sha1-1XtErxPmAb/NxCPx3YNItVJN40g= 773 | dependencies: 774 | get-size "^2.0.2" 775 | outlayer "^2.1.0" 776 | 777 | mdurl@^1.0.1: 778 | version "1.0.1" 779 | resolved "https://registry.npm.taobao.org/mdurl/download/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" 780 | integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= 781 | 782 | mermaid@^8.13.8: 783 | version "8.13.8" 784 | resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.13.8.tgz#fc137e2a59df34a3e053712033833ffbbc8d84a9" 785 | integrity sha512-Z5v31rvo8P7BPTiGicdJl9BbzyUe9s5sXILK8sM1g7ijkagpfFjPtXZVsq5P1WlN8m/fUp2PPNXVF9SqeTM91w== 786 | dependencies: 787 | "@braintree/sanitize-url" "^3.1.0" 788 | d3 "^7.0.0" 789 | dagre "^0.8.5" 790 | dagre-d3 "^0.6.4" 791 | dompurify "2.3.4" 792 | graphlib "^2.1.8" 793 | khroma "^1.4.1" 794 | moment-mini "^2.24.0" 795 | stylis "^4.0.10" 796 | 797 | minimist@^1.2.5: 798 | version "1.2.5" 799 | resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 800 | integrity sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI= 801 | 802 | mkdirp@0.5.5: 803 | version "0.5.5" 804 | resolved "https://registry.npmmirror.com/mkdirp/download/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" 805 | integrity sha1-2Rzv1i0UNsoPQWIOJRKI1CAJne8= 806 | dependencies: 807 | minimist "^1.2.5" 808 | 809 | modernizr@^3.11.8: 810 | version "3.11.8" 811 | resolved "https://registry.nlark.com/modernizr/download/modernizr-3.11.8.tgz#128fe6b66364673ae6f8d30fbf4a00f60288ec3e" 812 | integrity sha1-Eo/mtmNkZzrm+NMPv0oA9gKI7D4= 813 | dependencies: 814 | doctrine "^3.0.0" 815 | file "^0.2.2" 816 | lodash "^4.17.21" 817 | markdown-it "^10.0.0" 818 | mkdirp "0.5.5" 819 | requirejs "^2.3.6" 820 | yargs "^15.4.1" 821 | 822 | moment-mini@^2.24.0: 823 | version "2.24.0" 824 | resolved "https://registry.npm.taobao.org/moment-mini/download/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18" 825 | integrity sha1-+mjZj3/pOuZb8SYvartftpg9jRg= 826 | 827 | outlayer@^2.1.0: 828 | version "2.1.1" 829 | resolved "https://registry.npm.taobao.org/outlayer/download/outlayer-2.1.1.tgz#29863b6de10ea5dadfffcadfa0d728907387e9a2" 830 | integrity sha1-KYY7beEOpdrf/8rfoNcokHOH6aI= 831 | dependencies: 832 | ev-emitter "^1.0.0" 833 | fizzy-ui-utils "^2.0.0" 834 | get-size "^2.0.2" 835 | 836 | p-limit@^2.2.0: 837 | version "2.3.0" 838 | resolved "https://registry.nlark.com/p-limit/download/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" 839 | integrity sha1-PdM8ZHohT9//2DWTPrCG2g3CHbE= 840 | dependencies: 841 | p-try "^2.0.0" 842 | 843 | p-locate@^4.1.0: 844 | version "4.1.0" 845 | resolved "https://registry.nlark.com/p-locate/download/p-locate-4.1.0.tgz?cache=0&sync_timestamp=1629892721671&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fp-locate%2Fdownload%2Fp-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" 846 | integrity sha1-o0KLtwiLOmApL2aRkni3wpetTwc= 847 | dependencies: 848 | p-limit "^2.2.0" 849 | 850 | p-try@^2.0.0: 851 | version "2.2.0" 852 | resolved "https://registry.npmmirror.com/p-try/download/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 853 | integrity sha1-yyhoVA4xPWHeWPr741zpAE1VQOY= 854 | 855 | path-exists@^4.0.0: 856 | version "4.0.0" 857 | resolved "https://registry.nlark.com/path-exists/download/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 858 | integrity sha1-UTvb4tO5XXdi6METfvoZXGxhtbM= 859 | 860 | require-directory@^2.1.1: 861 | version "2.1.1" 862 | resolved "https://registry.nlark.com/require-directory/download/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 863 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 864 | 865 | require-main-filename@^2.0.0: 866 | version "2.0.0" 867 | resolved "https://registry.nlark.com/require-main-filename/download/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 868 | integrity sha1-0LMp7MfMD2Fkn2IhW+aa9UqomJs= 869 | 870 | requirejs@^2.3.6: 871 | version "2.3.6" 872 | resolved "https://registry.npm.taobao.org/requirejs/download/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" 873 | integrity sha1-5Qk9lgHCgpJRJYwLlEXU0Z+p58k= 874 | 875 | respond@^0.9.0: 876 | version "0.9.0" 877 | resolved "https://registry.npmmirror.com/respond/download/respond-0.9.0.tgz#fd8c298efe03b3bb7f4fc4cb58ddd7f34b51cd68" 878 | integrity sha1-/Ywpjv4Ds7t/T8TLWN3X80tRzWg= 879 | 880 | robust-predicates@^3.0.0: 881 | version "3.0.1" 882 | resolved "https://registry.npm.taobao.org/robust-predicates/download/robust-predicates-3.0.1.tgz#ecde075044f7f30118682bd9fb3f123109577f9a" 883 | integrity sha1-7N4HUET38wEYaCvZ+z8SMQlXf5o= 884 | 885 | rw@1: 886 | version "1.3.3" 887 | resolved "https://registry.npmmirror.com/rw/download/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" 888 | integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= 889 | 890 | "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": 891 | version "2.1.2" 892 | resolved "https://registry.nlark.com/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 893 | integrity sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 894 | 895 | selectivizr@^1.0.3: 896 | version "1.0.3" 897 | resolved "https://registry.npmmirror.com/selectivizr/download/selectivizr-1.0.3.tgz#a38e86089f0bfa09f2ae1d4ed29cddc7be7c4f6e" 898 | integrity sha1-o46GCJ8L+gnyrh1O0pzdx758T24= 899 | 900 | set-blocking@^2.0.0: 901 | version "2.0.0" 902 | resolved "https://registry.nlark.com/set-blocking/download/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 903 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 904 | 905 | sprintf-js@~1.0.2: 906 | version "1.0.3" 907 | resolved "https://registry.nlark.com/sprintf-js/download/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 908 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 909 | 910 | string-width@^4.1.0, string-width@^4.2.0: 911 | version "4.2.3" 912 | resolved "https://registry.npmmirror.com/string-width/download/string-width-4.2.3.tgz?cache=0&sync_timestamp=1632421309919&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fstring-width%2Fdownload%2Fstring-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 913 | integrity sha1-JpxxF9J7Ba0uU2gwqOyJXvnG0BA= 914 | dependencies: 915 | emoji-regex "^8.0.0" 916 | is-fullwidth-code-point "^3.0.0" 917 | strip-ansi "^6.0.1" 918 | 919 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 920 | version "6.0.1" 921 | resolved "https://registry.npmmirror.com/strip-ansi/download/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 922 | integrity sha1-nibGPTD1NEPpSJSVshBdN7Z6hdk= 923 | dependencies: 924 | ansi-regex "^5.0.1" 925 | 926 | stylis@^4.0.10: 927 | version "4.0.13" 928 | resolved "https://registry.npmmirror.com/stylis/download/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" 929 | integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== 930 | 931 | uc.micro@^1.0.1, uc.micro@^1.0.5: 932 | version "1.0.6" 933 | resolved "https://registry.npm.taobao.org/uc.micro/download/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" 934 | integrity sha1-nEEagCpAmpH8bPdAgbq6NLJEmaw= 935 | 936 | which-module@^2.0.0: 937 | version "2.0.0" 938 | resolved "https://registry.nlark.com/which-module/download/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 939 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 940 | 941 | wrap-ansi@^6.2.0: 942 | version "6.2.0" 943 | resolved "https://registry.nlark.com/wrap-ansi/download/wrap-ansi-6.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrap-ansi%2Fdownload%2Fwrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" 944 | integrity sha1-6Tk7oHEC5skaOyIUePAlfNKFblM= 945 | dependencies: 946 | ansi-styles "^4.0.0" 947 | string-width "^4.1.0" 948 | strip-ansi "^6.0.0" 949 | 950 | y18n@^4.0.0: 951 | version "4.0.3" 952 | resolved "https://registry.nlark.com/y18n/download/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" 953 | integrity sha1-tfJZyCzW4zaSHv17/Yv1YN6e7t8= 954 | 955 | yargs-parser@^18.1.2: 956 | version "18.1.3" 957 | resolved "https://registry.npmmirror.com/yargs-parser/download/yargs-parser-18.1.3.tgz?cache=0&sync_timestamp=1637031045984&other_urls=https%3A%2F%2Fregistry.npmmirror.com%2Fyargs-parser%2Fdownload%2Fyargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" 958 | integrity sha1-vmjEl1xrKr9GkjawyHA2L6sJp7A= 959 | dependencies: 960 | camelcase "^5.0.0" 961 | decamelize "^1.2.0" 962 | 963 | yargs@^15.4.1: 964 | version "15.4.1" 965 | resolved "https://registry.npmmirror.com/yargs/download/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" 966 | integrity sha1-DYehbeAa7p2L7Cv7909nhRcw9Pg= 967 | dependencies: 968 | cliui "^6.0.0" 969 | decamelize "^1.2.0" 970 | find-up "^4.1.0" 971 | get-caller-file "^2.0.1" 972 | require-directory "^2.1.1" 973 | require-main-filename "^2.0.0" 974 | set-blocking "^2.0.0" 975 | string-width "^4.2.0" 976 | which-module "^2.0.0" 977 | y18n "^4.0.0" 978 | yargs-parser "^18.1.2" 979 | --------------------------------------------------------------------------------