├── .bowerrc ├── .coveragerc ├── .dockerignore ├── .github └── workflows │ ├── build.yml │ ├── code-quality.yml │ ├── deploy-docker.yml │ └── deploy-pypi.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── .readthedocs.yaml ├── CONTRIBUTORS.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docker ├── .env ├── Dockerfile ├── celery ├── celeryconfig.py ├── docker-compose.common.yml ├── docker-compose.override.template.yml ├── docker-compose.yml ├── manage ├── run ├── run-server ├── scripts │ └── run-server ├── test └── user_settings.py ├── docs ├── Makefile ├── api.rst ├── api_keys.rst ├── aws.rst ├── celery.rst ├── changelog.rst ├── conf.py ├── configuration.rst ├── customizations.rst ├── dev_setup.rst ├── digitalocean.rst ├── docker_scripts.rst ├── fly.rst ├── heroku.rst ├── img │ ├── Wooey_Home.png │ ├── api_key_view.png │ ├── ignore_bad_imports.png │ ├── script_slug_example.png │ ├── venv_admin_page.png │ ├── venv_change_form.png │ ├── venv_set_script_venv.png │ ├── wooey_admin_script_parameters.png │ ├── wooey_widget_calendar.png │ └── wooey_widget_parameters.png ├── index.rst ├── install.rst ├── make.bat ├── openshift.rst ├── remote.rst ├── requirements.in ├── requirements.txt ├── running_wooey.rst ├── script_parameters_admin.rst ├── scripts.rst ├── security.rst ├── upgrade_help.rst ├── venv_setup.rst └── wooey_ui.rst ├── scripts └── wooify ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_project.py ├── wooey-banner.png └── wooey ├── __init__.py ├── admin.py ├── api ├── __init__.py ├── forms.py ├── jobs.py └── scripts.py ├── apps.py ├── backend ├── __init__.py ├── command_line.py └── utils.py ├── conf ├── __init__.py └── project_template │ ├── __init__.py │ ├── middleware.py │ ├── requirements.txt │ ├── settings │ ├── __init__.py │ ├── user_settings.py │ └── wooey_settings.py │ ├── urls │ ├── __init__.py │ ├── user_urls.py │ └── wooey_urls.py │ └── wooey_celery_app.py ├── django_compat.py ├── errors.py ├── forms ├── __init__.py ├── config.py ├── factory.py ├── fields.py ├── profile.py └── scripts.py ├── locale ├── de │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── es │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── fr │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── ja │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── ko │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── nl │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po └── zh_Hans │ └── LC_MESSAGES │ ├── django.mo │ └── django.po ├── management ├── __init__.py └── commands │ ├── __init__.py │ └── addscript.py ├── middleware.py ├── migrations ├── 0001_initial.py ├── 0002_remove_scriptparameter_output_path.py ├── 0003_populate_from_slug.py ├── 0004_choice_limits.py ├── 0005_size_bytes.py ├── 0006_script_group_defaults.py ├── 0007_script_documentation.py ├── 0008_short_param_admin.py ├── 0009_script_versioning.py ├── 0010_script_versioning_data_migration.py ├── 0011_script_versioning_cleanup.py ├── 0012_wooeyjob_uuid.py ├── 0013_wooeyjob_uuid_populate.py ├── 0014_wooeyjob_uuid_finalise.py ├── 0015_hidden_parameters.py ├── 0016_wooeyfile_checksum.py ├── 0017_wooeyfile_generate_checksums.py ├── 0018_userfile.py ├── 0019_userfile_data.py ├── 0020_userfile_finalize.py ├── 0021_fix_verbose_names.py ├── 0022_add_collapse_arguments.py ├── 0023_script_parameter_m2m.py ├── 0024_script_parameter_m2m_data.py ├── 0025_remove_script_parameter_sv.py ├── 0026_rename_script_parameter_sv.py ├── 0027_parameter_order.py ├── 0028_add_script_subparser.py ├── 0028_make-subparser-nonnullable.py ├── 0028_update_script_subparser_data.py ├── 0029_add-m2m-sv.py ├── 0030_populate-sv-m2m.py ├── 0031_remove-sv.py ├── 0032_rename-new-sv.py ├── 0033_add-script-checksum.py ├── 0034_update-checksums.py ├── 0035_populate-jsonfield.py ├── 0036_make_scriptparameter_jsonfield.py ├── 0037_repeat_migration_spg_sp_script_version.py ├── 0038_wooeyjob-choices.py ├── 0039_wooey_widgets.py ├── 0040_add-widget-class.py ├── 0041_change-script-param-to-textfield.py ├── 0042_allow_deletion_of_scripts.py ├── 0043_update_model_protection.py ├── 0044_change_script_parameter_choices_to_text.py ├── 0045_add_created_modified_by.py ├── 0046_add_native_json_field.py ├── 0047_migrate_default_field_data.py ├── 0048_remove_default_field.py ├── 0049_rename_new_default_to_default_field.py ├── 0050_add_api_keys.py ├── 0051_add_virtual_env.py ├── 0052_add_ignore_bad_imports_to_script_model.py └── __init__.py ├── models ├── __init__.py ├── core.py ├── favorite.py ├── fields.py ├── profile.py └── widgets.py ├── settings.py ├── signals.py ├── static └── wooey │ ├── css │ ├── base.css │ ├── profile.css │ └── simple-sidebar.css │ ├── images │ └── pinstripe.png │ └── js │ └── admin │ └── script.js ├── tasks.py ├── templates ├── 404.html ├── 500.html └── wooey │ ├── base.html │ ├── home.html │ ├── jobs │ ├── job_list.html │ ├── job_view.html │ └── results │ │ └── table_row.html │ ├── modals │ ├── base_modal.html │ ├── permalink_modal.html │ └── resubmit_modal.html │ ├── preview │ ├── base.html │ ├── fasta.html │ ├── image.html │ └── tabular.html │ ├── profile │ └── profile.html │ ├── registration │ ├── login.html │ ├── login_header.html │ └── register.html │ ├── scrapbook.html │ └── scripts │ ├── script_panel.html │ ├── script_view.html │ └── scriptgroup_view.html ├── templatetags ├── __init__.py └── wooey_tags.py ├── test_settings.py ├── test_urls.py ├── tests ├── .gitignore ├── __init__.py ├── config.py ├── data │ ├── delimited.tsv │ └── fasta.fasta ├── factories.py ├── mixins.py ├── scripts │ ├── __init__.py │ ├── choices.py │ ├── choices_2.py │ ├── command_order.py │ ├── crop.py │ ├── docker-script.py │ ├── fetch_cats.py │ ├── file_maker.py │ ├── gaussian.py │ ├── heatmap.py │ ├── mandlebrot.py │ ├── multi_output.py │ ├── nested_heatmap.py │ ├── subparser_script.py │ ├── translate.py │ ├── translate2.py │ ├── venv_pandas_test.py │ ├── versioned_script │ │ ├── v1.py │ │ ├── v2.py │ │ └── v3.py │ └── without_args.py ├── test_api.py ├── test_auth.py ├── test_commands.py ├── test_forms.py ├── test_models.py ├── test_scripts.py ├── test_tasks.py ├── test_template_tags.py ├── test_utils.py ├── test_views.py ├── test_virtual_envs.py └── utils.py ├── urls.py ├── utils └── __init__.py ├── version.py ├── views ├── __init__.py ├── authentication.py ├── favorite.py ├── profile.py ├── views.py └── wooey_celery.py └── wooeystorage.py /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "wooey/static/libs" 3 | } 4 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | omit = wooey/conf*,wooey/migrations*,wooey/tests* 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | #*.mo Include so users don't have to self-build 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # IDE 60 | .idea 61 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build-and-Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | max-parallel: 4 10 | matrix: 11 | # Up to date compatibility matrix 12 | # https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django 13 | python-version: [3.7, 3.8, 3.9, "3.10", "3.11"] 14 | os: [ubuntu-latest, windows-latest] 15 | django-version: ["3.2", "4.2"] 16 | exclude: 17 | # These 2 windows python versions do not ship the json sqlite3 extension we need 18 | - os: windows-latest 19 | python-version: 3.7 20 | - os: windows-latest 21 | python-version: 3.8 22 | - python-version: 3.11 23 | django-version: 3.2 24 | - python-version: 3.7 25 | django-version: 4.2 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Set up Python ${{ matrix.python-version }} 30 | uses: actions/setup-python@v4 31 | with: 32 | python-version: ${{ matrix.python-version }} 33 | - name: Install dependencies 34 | run: | 35 | echo "DV is ${{ matrix.django-version }}" 36 | python -m pip install --upgrade pip 37 | python -m pip install "Django~=${{ matrix.django-version }}" 38 | make testenv 39 | - name: Run Tests 40 | run: | 41 | make test 42 | 43 | - uses: codecov/codecov-action@v3 44 | with: 45 | token: ${{ secrets.CODECOV_TOKEN }} 46 | file: ./coverage.xml 47 | flags: unittests 48 | name: codecov-umbrella 49 | fail_ci_if_error: true 50 | -------------------------------------------------------------------------------- /.github/workflows/code-quality.yml: -------------------------------------------------------------------------------- 1 | name: Code-Quality 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | max-parallel: 4 10 | matrix: 11 | python-version: [3.9] 12 | os: [ubuntu-latest] 13 | 14 | steps: 15 | - uses: actions/checkout@v1 16 | - name: Set up Python ${{ matrix.python-version }} 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | - name: Install pre-commit 21 | run: | 22 | python -m pip install pre-commit 23 | - name: Check pre-commit 24 | run: | 25 | pre-commit run -a 26 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docker.yml: -------------------------------------------------------------------------------- 1 | name: Deploy-To-Docker 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | max-parallel: 4 13 | matrix: 14 | os: [ubuntu-latest] 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: Build image 19 | run: | 20 | docker build -f docker/Dockerfile -t wooey/wooey:latest . 21 | docker tag wooey/wooey:latest wooey/wooey:${GITHUB_REF#refs/tags/} 22 | - name: Upload image to Dockerhub 23 | env: 24 | DOCKERHUB_ACCESS_TOKEN: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} 25 | if: matrix.os == 'ubuntu-latest' 26 | run: | 27 | docker login --username wooeyservice -p ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} 28 | docker push wooey/wooey:latest 29 | docker push wooey/wooey:${GITHUB_REF#refs/tags/} 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Deploy-To-Pypi 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | max-parallel: 4 13 | matrix: 14 | python-version: [3.7] 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v1 19 | - name: Set up Python ${{ matrix.python-version }} 20 | uses: actions/setup-python@v1 21 | with: 22 | python-version: ${{ matrix.python-version }} 23 | - name: Setup Packaging Tools 24 | run: | 25 | pip install -U setuptools twine pip 26 | - name: Upload to pypi 27 | env: 28 | PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 29 | run: | 30 | python setup.py sdist 31 | twine upload -u __token__ -p $PYPI_API_TOKEN dist/* 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | #*.mo Include so users don't have to self-build 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | docs/html/ 56 | 57 | # PyBuilder 58 | target/ 59 | 60 | # IDE 61 | .idea 62 | 63 | # Template files 64 | docker/docker-compose.override.yml 65 | 66 | .vscode 67 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.3.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | exclude: setup.cfg 9 | - repo: https://github.com/psf/black 10 | rev: 22.10.0 11 | hooks: 12 | - id: black 13 | - repo: https://github.com/pre-commit/mirrors-prettier 14 | rev: "v2.7.1" 15 | hooks: 16 | - id: prettier 17 | - repo: https://github.com/rtts/djhtml 18 | rev: "3.0.6" 19 | hooks: 20 | - id: djhtml 21 | - repo: https://github.com/pycqa/flake8 22 | rev: "6.0.0" 23 | hooks: 24 | - id: flake8 25 | 26 | default_language_version: 27 | python: python3.9 28 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | # Set the version of Python and other tools you might need 4 | build: 5 | os: ubuntu-22.04 6 | tools: 7 | python: "3.9" 8 | 9 | # Build documentation in the docs/ directory with Sphinx 10 | sphinx: 11 | configuration: docs/conf.py 12 | 13 | # Optionally declare the Python requirements required to build your docs 14 | python: 15 | install: 16 | - requirements: docs/requirements.txt 17 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Wooey contributors (sorted alphabeticaly) 2 | 3 | These are the people who have provided various forms of contributions to Wooey (new code, features, translations, and help with documentation). If you wish to update your information or add details of your contributions, please submit a PR. 4 | 5 | - **[Michael Bykovski](https://github.com/bykof)** 6 | 7 | - **[Mike DePalatis](https://github.com/mivade)** 8 | 9 | - **[Padraic Harley](https://github.com/pauricthelodger)** 10 | 11 | - **[Hottwaj](https://github.com/hottwaj)** 12 | 13 | - **[Ryosuke Ito](https://github.com/manicmaniac)** 14 | 15 | - **[Jeremías Pretto](https://github.com/puckk)** 16 | 17 | - **[Yoav Ram](https://github.com/yoavram)** 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Wooey 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Wooey nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include wooey/templates * 4 | recursive-include wooey/static * 5 | recursive-include wooey/conf * 6 | recursive-include wooey/locale * 7 | recursive-exclude * *.pyc 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | testenv: 2 | pip install -e .[dev] 3 | 4 | test: 5 | pytest --cov=wooey --cov-branch tests/* 6 | coverage run --append --branch --source=wooey -m django test --settings=wooey.test_settings wooey.tests 7 | python -m coverage report --omit='*migrations*','*wooey_scripts*','*tests/scripts*','*conf/*' 8 | python -m coverage xml --omit='*migrations*','*wooey_scripts*','*tests/scripts*','*conf/*' 9 | 10 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 11 | 12 | clean-build: ## remove build artifacts 13 | rm -fr build/ 14 | rm -fr dist/ 15 | rm -fr .eggs/ 16 | find . -name '*.egg-info' -exec rm -fr {} + 17 | find . -name '*.egg' -exec rm -f {} + 18 | 19 | clean-pyc: ## remove Python file artifacts 20 | find . -name '*.pyc' -exec rm -f {} + 21 | find . -name '*.pyo' -exec rm -f {} + 22 | find . -name '*~' -exec rm -f {} + 23 | find . -name '__pycache__' -exec rm -fr {} + 24 | 25 | clean-test: ## remove test and coverage artifacts 26 | rm -f .coverage 27 | rm -fr htmlcov/ 28 | rm -fr .pytest_cache 29 | 30 | dist: clean ## builds source and wheel package 31 | python setup.py sdist 32 | python setup.py bdist_wheel 33 | ls -l dist 34 | 35 | release/major release/minor release/patch release/rc: 36 | bump2version $(@F) 37 | git push 38 | git push --tags 39 | 40 | .PHONY: docs 41 | docs: 42 | cd docs && make html 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Wooey!](wooey-banner.png) 2 | 3 | [![Build Status](https://github.com/wooey/wooey/workflows/Build-and-Test/badge.svg)](https://github.com/wooey/wooey/actions?query=workflow%3ABuild-and-Test) 4 | [![Deploy-To-Pypi](https://github.com/wooey/Wooey/workflows/Deploy-To-Pypi/badge.svg)](https://github.com/wooey/wooey/actions?query=workflow%3ADeploy-To-Pypi) 5 | [![Deploy-To-Docker](https://github.com/wooey/Wooey/workflows/Deploy-To-Docker/badge.svg)](https://github.com/wooey/wooey/actions?query=workflow%3ADeploy-To-Docker) 6 | [![codecov](https://codecov.io/gh/wooey/Wooey/branch/master/graph/badge.svg)](https://codecov.io/gh/wooey/Wooey) 7 | [![Join the chat at https://gitter.im/wooey/Wooey](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/wooey/Wooey?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 8 | 9 | Wooey is a simple web interface to run command line Python scripts. Think of it as an easy way to get your scripts up on the web for routine data analysis, file processing, or anything else. 10 | 11 | Wooey was envisioned as a system to allow data analysts to be able to easily: 12 | 13 | 1. Autodocument workflows for data analysis 14 | (simple model saving). 15 | 2. Enable fellow co-workers with no command line 16 | experience to utilize python scripts. 17 | 3. Enable the easy wrapping of any program in simple 18 | python instead of having to use language specific 19 | to existing tools such as Galaxy. 20 | 21 | For installation and other instructions, check out our [documentation](http://wooey.readthedocs.org) or give Wooey a test drive at our [demo-server](https://wooey.fly.dev)! 22 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | BUILD_DIR=/wooey_build 2 | WOOEY_PROJECT=docker_wooey 3 | COMPOSE_PROJECT_NAME=wooey 4 | HOST_USER=1000 5 | HOST_GROUP=1000 6 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | ARG HOST_USER=1000 4 | ENV HOST_USER=${HOST_USER} 5 | ARG HOST_GROUP=1000 6 | ENV HOST_GROUP=${HOST_GROUP} 7 | 8 | RUN addgroup --gid ${HOST_GROUP} wooey 9 | RUN useradd -u ${HOST_USER} -g ${HOST_GROUP} wooey 10 | 11 | ARG BUILD_DIR=/wooey_build 12 | ENV BUILD_DIR=${BUILD_DIR} 13 | 14 | WORKDIR ${BUILD_DIR} 15 | RUN chown wooey:wooey ${BUILD_DIR} 16 | 17 | RUN pip install docker psycopg2 redis 18 | 19 | COPY --chown=wooey:wooey setup.py MANIFEST.in Makefile README.md ${BUILD_DIR}/ 20 | COPY --chown=wooey:wooey scripts ${BUILD_DIR}/scripts 21 | COPY --chown=wooey:wooey wooey ${BUILD_DIR}/wooey 22 | COPY --chown=wooey:wooey tests ${BUILD_DIR}/tests 23 | 24 | 25 | RUN pip install -e .[dev] 26 | 27 | RUN chmod -R a+rwx ${BUILD_DIR} 28 | 29 | ARG WOOEY_PROJECT=docker_wooey 30 | 31 | USER wooey 32 | RUN wooify -p ${WOOEY_PROJECT} 33 | 34 | WORKDIR ${BUILD_DIR}/${WOOEY_PROJECT} 35 | ENV PYTHONPATH=${BUILD_DIR}/${WOOEY_PROJECT}:${PYTHONPATH} 36 | 37 | COPY docker/scripts/run-server run-server 38 | 39 | # To prevent volumes from being made as root, we need to make the directory 40 | # first and then create a volume. This will make docker inherit the permissions 41 | # of the folder it is replacing. Otherwise, we will be unable to write to this 42 | # folder as non-root 43 | RUN mkdir -p $BUILD_DIR/$WOOEY_PROJECT/$WOOEY_PROJECT/user_uploads 44 | 45 | CMD ["sh", "-c", "make -C ${BUILD_DIR} test"] 46 | -------------------------------------------------------------------------------- /docker/celery: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run --rm --service-ports celery "$@" 3 | -------------------------------------------------------------------------------- /docker/celeryconfig.py: -------------------------------------------------------------------------------- 1 | broker_url = "amqp://guest@rabbit:5672" 2 | track_started = True 3 | send_events = True 4 | imports = ("wooey.tasks",) 5 | task_serializer = "json" 6 | task_acks_late = True 7 | -------------------------------------------------------------------------------- /docker/docker-compose.common.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | volumes: 3 | user_uploads: 4 | wooey_db: 5 | 6 | services: 7 | common: 8 | build: 9 | context: .. 10 | dockerfile: docker/Dockerfile 11 | args: 12 | BUILD_DIR: 13 | WOOEY_PROJECT: 14 | image: wooey:dev 15 | volumes: 16 | - ./user_settings.py:$BUILD_DIR/$WOOEY_PROJECT/$WOOEY_PROJECT/settings/user_settings.py 17 | - ./celeryconfig.py:$BUILD_DIR/$WOOEY_PROJECT/$WOOEY_PROJECT/settings/celeryconfig.py 18 | - user_uploads:$BUILD_DIR/$WOOEY_PROJECT/$WOOEY_PROJECT/user_uploads 19 | environment: 20 | AMQP_URL: amqp://rabbit:5672 21 | BUILD_DIR: 22 | DATABASE_NAME: wooey 23 | DATABASE_USER: wooey 24 | DATABASE_URL: db 25 | DATABASE_PASSWORD: wooey 26 | DJANGO_SETTINGS_MODULE: $WOOEY_PROJECT.settings 27 | CELERY_CONFIG_MODULE: $WOOEY_PROJECT.settings.celeryconfig 28 | user: 1000:1000 29 | -------------------------------------------------------------------------------- /docker/docker-compose.override.template.yml: -------------------------------------------------------------------------------- 1 | # This file should be renamed to docker-compose.overrides.yml with whatever 2 | # overrides are specific to the particular setup 3 | version: "2.1" 4 | 5 | services: 6 | wooey: 7 | volumes: &package-volumes 8 | - ../wooey:$BUILD_DIR/wooey 9 | # Mount over the clinto parser install if desired. 10 | # - ../../clinto/clinto:/usr/local/lib/python3.6/site-packages/clinto 11 | # if you want to run docker commands from within the container 12 | # - /var/run/docker.sock:/var/run/docker.sock 13 | ports: 14 | - 8080:8080 15 | 16 | celery: 17 | volumes: *package-volumes 18 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2.1" 2 | volumes: 3 | user_uploads: 4 | wooey_db: 5 | 6 | services: 7 | wooey: 8 | extends: 9 | file: docker-compose.common.yml 10 | service: common 11 | ports: 12 | - 8081:8080 13 | depends_on: 14 | - rabbit 15 | - redis 16 | - db 17 | - celery 18 | command: ./run-server 19 | 20 | celery: 21 | extends: 22 | file: docker-compose.common.yml 23 | service: common 24 | depends_on: 25 | - rabbit 26 | - redis 27 | - db 28 | command: watchmedo auto-restart --directory=$BUILD_DIR/wooey --recursive --ignore-patterns="*.pyc" -- celery -A $WOOEY_PROJECT worker -c 4 -B -l debug -s schedule 29 | 30 | rabbit: 31 | image: rabbitmq:3.9.29-management-alpine 32 | ports: 33 | - 15672:15672 34 | 35 | db: 36 | image: postgres:13.10 37 | volumes: 38 | - wooey_db:/var/lib/postgresql/data 39 | environment: 40 | POSTGRES_USER: wooey 41 | POSTGRES_PASSWORD: wooey 42 | POSTGRES_DB: wooey 43 | 44 | redis: 45 | image: redis:7.2.3 46 | -------------------------------------------------------------------------------- /docker/manage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run --rm wooey django-admin "$@" 3 | -------------------------------------------------------------------------------- /docker/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run --rm wooey "$@" 3 | -------------------------------------------------------------------------------- /docker/run-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run --rm --service-ports wooey "$@" 3 | -------------------------------------------------------------------------------- /docker/scripts/run-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python manage.py migrate 3 | python manage.py collectstatic -c --noinput 4 | python manage.py runserver 0.0.0.0:8080 5 | -------------------------------------------------------------------------------- /docker/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker-compose run --rm wooey sh -c "make -C \${BUILD_DIR} test" 3 | -------------------------------------------------------------------------------- /docker/user_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .wooey_settings import * # noqa: F403 3 | 4 | # Whether to allow anonymous job submissions, set False to disallow 'guest' job submissions 5 | WOOEY_ALLOW_ANONYMOUS = True 6 | 7 | WOOEY_ENABLE_API_KEYS = True 8 | 9 | WOOEY_REALTIME_CACHE = "default" 10 | CACHES = { 11 | "default": { 12 | "BACKEND": "django.core.cache.backends.redis.RedisCache", 13 | "LOCATION": "redis://redis:6379", 14 | } 15 | } 16 | 17 | ## Celery related options 18 | WOOEY_CELERY = True 19 | 20 | # the directory for uploads (physical directory) 21 | MEDIA_ROOT = os.path.join(BASE_DIR, "user_uploads") # noqa: F405 22 | # the url mapping 23 | MEDIA_URL = "/uploads/" 24 | 25 | # the directory to store our webpage assets (images, javascript, etc.) 26 | STATIC_ROOT = os.path.join(BASE_DIR, "static") # noqa: F405 27 | # the url mapping 28 | STATIC_URL = "/static/" 29 | ## Here is a setup example for production servers 30 | 31 | ## A postgres database -- for multiple users a sqlite based database is asking for trouble 32 | 33 | DATABASES = { 34 | "default": { 35 | "ENGINE": "django.db.backends.postgresql_psycopg2", 36 | # for production environments, these should be stored as environment variables 37 | # I also recommend the django-heroku-postgresify package for a super simple setup 38 | "NAME": os.environ.get("DATABASE_NAME", "wooey"), 39 | "USER": os.environ.get("DATABASE_USER", "wooey"), 40 | "PASSWORD": os.environ.get("DATABASE_PASSWORD", "wooey"), 41 | "HOST": os.environ.get("DATABASE_URL", "localhost"), 42 | "PORT": os.environ.get("DATABASE_PORT", "5432"), 43 | } 44 | } 45 | 46 | ALLOWED_HOSTS = ( 47 | "localhost", 48 | "127.0.0.1", 49 | ) 50 | 51 | AUTHENTICATION_BACKEND = "django.contrib.auth.backends.ModelBackend" 52 | -------------------------------------------------------------------------------- /docs/api_keys.rst: -------------------------------------------------------------------------------- 1 | API Keys 2 | ======== 3 | 4 | API Keys allows authentication to Wooey for remote access, such as through an 5 | automated job runner. 6 | 7 | API Keys can be managed and accessed via the User Profile interface 8 | 9 | .. image:: img/api_key_view.png 10 | 11 | From here, a user can create a new API key, toggle the status of an API key, or delete it. 12 | During creating, a modal window will provide the API key. This is the only time this key will 13 | be shown, as it is stored in the database as a hash. 14 | 15 | Example Usage 16 | ------------- 17 | 18 | Here is a simple example of how to set the API key credentials in the request header: 19 | 20 | .. code-block:: python 21 | 22 | import requests 23 | 24 | response = requests.post('https://wooey.fly.dev/xxx', headers={'Authorization': 'Bearer your_token_here'}) 25 | -------------------------------------------------------------------------------- /docs/celery.rst: -------------------------------------------------------------------------------- 1 | .. _celery_guide: 2 | 3 | Wooey Celery Configuration 4 | ========================== 5 | 6 | Celery 7 | ------ 8 | 9 | `Celery 10 | `_ is an app designed to distribute tasks to workers. This is generally useful as a way to scale up a server and carry out work without 11 | impacting the main webserver. 12 | 13 | The Broker 14 | ---------- 15 | 16 | In celery, the broker exists to pass messages between servers. There are several brokers to use, with RabbitMQ and Redis being the common choices. 17 | Here, we detail how to use RabbitMQ (AMQP). 18 | 19 | If you are coming from a bootstrapped project, to switch to an AMQP broker, it is a matter of uncommenting the following lines in your production settings. 20 | In the code, there are 2 cloud rabbit providers we have used in the past for our demo application. 21 | 22 | :: 23 | 24 | broker_url = os.environ.get('AMQP_URL') or \ 25 | os.environ.get('RABBITMQ_BIGWIG_TX_URL') or \ 26 | os.environ.get('CLOUDAMQP_URL', 'amqp://guest:guest@localhost:5672/') 27 | broker_pool_limit = 1 28 | worker_concurrency = 1 29 | task_acks_late = True 30 | 31 | If you are coming from a project which has wooey installed as an additional app, you want to add the above to your settings. 32 | 33 | Additional Heroku Options 34 | ------------------------- 35 | 36 | For heroku, you will want to add AMQP to your app through the dashboard, which should give you a AMQP url compatible with the above options. 37 | 38 | Fly.dev Setup 39 | ------------- 40 | 41 | For fly.dev, we use the `Cloudamqp service `_, whose free tier is sufficient for our needs. 42 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | v0.14.0 5 | ------- 6 | 7 | | [`Feature `__] API Keys were added 8 | 9 | v0.12.0 10 | ------- 11 | 12 | | [`Feature `__] Spanish Translations 13 | | [`Feature `__] Improve script deletion experience 14 | | [`Feature `__] Move from Appveyor and Travis to Github Actions 15 | | [`Feature `__] Add support for Django 2.2 and Django 3.0 16 | | [`Feature `__] Add an official Docker Image 17 | | [`Feature `__] Add admin interface for Script Versions 18 | | [`Debt `__] Update to boto3 19 | 20 | v0.11.0 21 | ------------ 22 | 23 | | [`Feature `__] WooeyWidgets, which enable custom form input elements to be created and used. 24 | | [`Feature `__] Korean translations added! 25 | | [`Feature `__] Improved UI to not allow job operations on message brokers that do not allow them. 26 | | [`Feature `__] Django2 Support. 27 | | [`BugFix `__] Fix bug where all parameters from all subparsers were needed to validate in order to submit a job. 28 | | [`BugFix `__] Fix bug with escaping parameter arguments that prevented special characters from being used. 29 | | [`BugFix `__] Fix bug where multiple initial files for a cloned job were not populated. 30 | | [`BugFix `__] Fix bug in parsing multiple arguments where argparse specifies `action='append'` 31 | | [`BugFix `__] Fix bug in cleaning up empty jobs where workers cannot be contacted. 32 | | [`BugFix `__] Fix bug where scripts on remote workers were not invalidated after updates on main server. 33 | | [`BugFix `__] Fix race condition where celery tasks would start before database transaction finished. 34 | | [`BugFix `__] Handle characters in script version that need to be escaped for urls. 35 | -------------------------------------------------------------------------------- /docs/customizations.rst: -------------------------------------------------------------------------------- 1 | Wooey Customizations 2 | ==================== 3 | 4 | Wooey provides a number of ways to customize both the look and feel of Wooey, with 5 | the easiest mechanisms for customization being various :ref:`wooey-configuration` settings. 6 | Here, one can customize the site name, tag line, and other text throughout. 7 | 8 | 9 | Form Widgets 10 | ------------ 11 | 12 | Advanced users can add custom form elements, which can be used for advanced input 13 | elements such as a calendar. These can be set by creating a WooeyWidget via the 14 | Wooey admin page: 15 | 16 | .. image:: img/wooey_widget_calendar.png 17 | 18 | Widgets are made to be usable and generally useful components. As such, they need 19 | to be associated with the form input elements in a separate step, which may be 20 | accessed through the :ref:`script-parameters-admin` page within the Wooey admin page. Here, 21 | the widget to utilize for a given parameter can be set by associating a given 22 | WooeyWidget with a Script Parameter: 23 | 24 | .. image:: img/wooey_widget_parameters.png 25 | -------------------------------------------------------------------------------- /docs/dev_setup.rst: -------------------------------------------------------------------------------- 1 | Developer Setup 2 | =============== 3 | 4 | Dev Environment 5 | --------------- 6 | 7 | Docker 8 | ~~~~~~ 9 | 10 | The `docker` folder contains the Docker setup. Wooey can be built and run by simply 11 | running 12 | 13 | :: 14 | 15 | docker-compose up --build 16 | 17 | To mount the local installation directory in the docker container, create a local version 18 | of the docker-compose overrides by running 19 | 20 | :: 21 | 22 | cp docker-compose.override.template.yml docker-compose.override.yml 23 | 24 | Several convenience scripts exist for common scenarios: 25 | 26 | * manage -- run a django-admin command 27 | * run -- run a command in the wooey container. For example, a bash shell can be started by `run bash` 28 | * run-server -- run the Django server that is locally accessible. This offers some advantages over `docker-compose up` such as allowing debugging via `pdb` 29 | * test -- run all unit tests. 30 | 31 | System 32 | ~~~~~~ 33 | 34 | For developers not using Docker, dependenices can be installed via 35 | 36 | :: 37 | 38 | pip install .[dev] 39 | 40 | Style 41 | ----- 42 | 43 | `Pre-commit `_ is used to standardize the tools used for linting and code formatting. 44 | -------------------------------------------------------------------------------- /docs/docker_scripts.rst: -------------------------------------------------------------------------------- 1 | Running a Script in Docker 2 | ========================== 3 | 4 | Here is a simple example of a wrapper script to call a docker image using the `docker python library `_. 5 | 6 | .. code-block:: python 7 | 8 | import argparse 9 | 10 | import docker 11 | 12 | parser = argparse.ArgumentParser(description="Docker test script") 13 | parser.add_argument("--phrase", type=str) 14 | 15 | if __name__ == "__main__": 16 | args = parser.parse_args() 17 | client = docker.from_env() 18 | print(client.containers.run("docker/whalesay", f"cowsay {args.phrase}").decode('utf-8')) 19 | 20 | When running in this configuration, whatever environment the worker is running in must have access to docker. 21 | Additionally, if you want assets generated by a dockerfile, you should mount the current working directory to 22 | wherever the docker image outputs files to. For example, if a docker image output files to `/output`, the 23 | above invocation would be changed to: 24 | 25 | .. code-block:: python 26 | 27 | import argparse 28 | import os 29 | 30 | import docker 31 | from docker.types import Mount 32 | 33 | parser = argparse.ArgumentParser(description="Docker test script") 34 | 35 | if __name__ == "__main__": 36 | args = parser.parse_args() 37 | client = docker.from_env() 38 | # IMPORTANT # 39 | # This assumes we have a host directory for media storage (the MEDIA_ROOT setting) that has the same 40 | # path inside the container as the host (e.g. volume: /host/path:/host/path) 41 | wooey_data_dir = os.getcwd() 42 | print( 43 | client.containers.run( 44 | image="busybox", 45 | command=f"dd if=/dev/urandom of=/output/test.garbage bs=1M count=1", 46 | mounts=[Mount(target="/output", source=wooey_data_dir, type='bind')], 47 | ).decode( 48 | "utf-8" 49 | ) 50 | ) 51 | 52 | If you are running Wooey inside Docker, then this becomes a bit more complicated as the data directory may 53 | be on a mount point different on the host than inside the container. The following script shows how to handle 54 | these scenarios: 55 | 56 | .. code-block:: python 57 | 58 | import argparse 59 | import os 60 | 61 | import django 62 | django.setup() 63 | from django.conf import settings 64 | 65 | import docker 66 | from docker.types import Mount 67 | 68 | parser = argparse.ArgumentParser(description="Docker test script") 69 | 70 | if __name__ == "__main__": 71 | args = parser.parse_args() 72 | client = docker.from_env() 73 | # our volume is called `wooey_user_uploads`. If we had a NAS, then we could simplify this by ensuring the path 74 | # inside the container matches the path on the host -- in which case the above snippet is all that is needed. 75 | volume = client.volumes.get('wooey_user_uploads') 76 | volume_mount = volume.attrs['Mountpoint'] 77 | current_dir = os.getcwd() 78 | wooey_data_dir = os.path.join(volume_mount.rstrip('/'), current_dir.replace(settings.MEDIA_ROOT, '').lstrip('/')) 79 | print( 80 | client.containers.run( 81 | image="busybox", 82 | command=f"dd if=/dev/urandom of=/output/test.garbage bs=1M count=1", 83 | mounts=[Mount(target="/output", source=wooey_data_dir, type='bind')], 84 | ).decode( 85 | "utf-8" 86 | ) 87 | ) 88 | -------------------------------------------------------------------------------- /docs/fly.rst: -------------------------------------------------------------------------------- 1 | Configuration on Fly.io 2 | ======================= 3 | 4 | How to get Wooey up and running on fly.io. This assumes the `wooify` command was run with `app` as the project's name and the 5 | fly.io project is named `wooey-test`. 6 | 7 | Directory Layout 8 | ---------------- 9 | 10 | :: 11 | 12 | app 13 | ├── app 14 | │ ├── application files 15 | ├── manage.py # this is unused and can be removed if running via django-admin 16 | ├── fly.toml 17 | ├── Dockerfile 18 | ├── requirements.txt 19 | 20 | Fly TOML file 21 | ------------- 22 | 23 | Here is an example fly.toml file. One arbitary choice is the webserver used. Here, waitress is used but there are many alternatives such as 24 | gunicorn and uwsgi. 25 | 26 | :: 27 | 28 | app = "wooey-test" 29 | kill_signal = "SIGINT" 30 | kill_timeout = 5 31 | 32 | [env] 33 | DJANGO_SETTINGS_MODULE = "app.settings" 34 | 35 | [experimental] 36 | allowed_public_ports = [] 37 | auto_rollback = true 38 | 39 | [build] 40 | dockerfile = "Dockerfile" 41 | 42 | [deploy] 43 | release_command = "django-admin migrate" 44 | 45 | [processes] 46 | web = "waitress-serve --connection-limit 2000 --channel-timeout=300 --port=8080 app.wsgi:application" 47 | worker = "celery -A app worker -c 1 --beat -l info --without-gossip --without-mingle --without-heartbeat" 48 | 49 | [[services]] 50 | http_checks = [] 51 | internal_port = 8080 52 | processes = ["web"] 53 | protocol = "tcp" 54 | script_checks = [] 55 | [services.concurrency] 56 | hard_limit = 25 57 | soft_limit = 20 58 | type = "connections" 59 | 60 | [[services.ports]] 61 | force_https = true 62 | handlers = ["http"] 63 | port = 80 64 | 65 | [[services.ports]] 66 | handlers = ["tls", "http"] 67 | port = 443 68 | 69 | [[services.tcp_checks]] 70 | grace_period = "1s" 71 | interval = "15s" 72 | restart_limit = 0 73 | timeout = "2s" 74 | 75 | Dockerfile 76 | ---------- 77 | 78 | Here is an example Dockerfile 79 | 80 | :: 81 | 82 | FROM python:3.10.9-slim-buster 83 | 84 | ENV VIRTUAL_ENV=/opt/venv 85 | 86 | RUN useradd -u 1000 wooey 87 | RUN mkdir -p $VIRTUAL_ENV && chown wooey:wooey $VIRTUAL_ENV 88 | USER wooey 89 | 90 | RUN python3 -m venv --system-site-packages $VIRTUAL_ENV 91 | ENV PATH="$VIRTUAL_ENV/bin:$PATH" 92 | 93 | COPY --chown=wooey:wooey requirements.txt requirements.txt 94 | RUN pip install -r requirements.txt 95 | 96 | COPY --chown=wooey:wooey app app 97 | ENV PYTHONPATH="/app:$PYTHONPATH" 98 | 99 | 100 | Requirements 101 | ------------ 102 | 103 | Here is an example requirements.txt 104 | 105 | :: 106 | 107 | Django~=3.2.14 108 | wooey==0.13.2 109 | django-storages==1.13.2 110 | django-autoslug==1.9.8 111 | dj-database-url==1.0.0 112 | boto3==1.26.27 113 | waitress==2.1.2 114 | collectfast==2.2.0 115 | psycopg2-binary==2.9.5 116 | -------------------------------------------------------------------------------- /docs/img/Wooey_Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/Wooey_Home.png -------------------------------------------------------------------------------- /docs/img/api_key_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/api_key_view.png -------------------------------------------------------------------------------- /docs/img/ignore_bad_imports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/ignore_bad_imports.png -------------------------------------------------------------------------------- /docs/img/script_slug_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/script_slug_example.png -------------------------------------------------------------------------------- /docs/img/venv_admin_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/venv_admin_page.png -------------------------------------------------------------------------------- /docs/img/venv_change_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/venv_change_form.png -------------------------------------------------------------------------------- /docs/img/venv_set_script_venv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/venv_set_script_venv.png -------------------------------------------------------------------------------- /docs/img/wooey_admin_script_parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/wooey_admin_script_parameters.png -------------------------------------------------------------------------------- /docs/img/wooey_widget_calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/wooey_widget_calendar.png -------------------------------------------------------------------------------- /docs/img/wooey_widget_parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/docs/img/wooey_widget_parameters.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. figure:: ../wooey-banner.png 2 | :alt: Wooey! 3 | 4 | Welcome to Wooey's documentation 5 | ================================ 6 | 7 | Wooey is a simple web interface to run command line Python scripts. Think of it as an easy way to get your scripts 8 | up on the web for routine data analysis, file processing, or anything else. 9 | 10 | The project was inspired by how simply and powerfully 11 | `sandman `__ could expose users to 12 | a database and by how `Gooey `__ 13 | turns ArgumentParser-based command-line scripts into WxWidgets GUIs. 14 | Originally two separate projects (Django-based djangui by `Chris 15 | Mitchell `__ and Flask-based Wooey by `Martin 16 | Fitzpatrick `__) it has been merged to combine 17 | our efforts. 18 | 19 | Both of our tools were based on our needs as data scientists to have a 20 | system that could: 21 | 22 | 1. Autodocument workflows for data analysis (simple model saving). 23 | 2. Enable fellow lab members with no command line experience to utilize python scripts. 24 | 3. Enable the easy wrapping of any program in simple python instead of having to use language specific 25 | to existing tools such as Galaxy. 26 | 27 | 28 | 29 | Getting Started 30 | =============== 31 | 32 | .. toctree:: 33 | :maxdepth: 3 34 | 35 | install 36 | configuration 37 | running_wooey 38 | scripts 39 | venv_setup 40 | wooey_ui 41 | api 42 | api_keys 43 | customizations 44 | remote 45 | upgrade_help 46 | changelog 47 | security 48 | 49 | 50 | Contributing 51 | ============ 52 | 53 | .. toctree:: 54 | :maxdepth: 2 55 | 56 | dev_setup 57 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | :: 5 | 6 | pip install wooey 7 | 8 | Currently, Wooey supports Django versions 1.8+. To use Wooey in a project which is still running Django 1.6 or 1.7, you must install 9 | version 0.9.8. 10 | 11 | A Wooey only project 12 | -------------------- 13 | 14 | There is a bootstrapper included with wooey, which will create a Django 15 | project and setup most of the needed settings automagically for you. 16 | 17 | :: 18 | 19 | 1. wooify -p ProjectName 20 | 2. Follow the instructions at the end of the bootstrapper 21 | to create the admin user and access the admin page 22 | 3. Login to the admin page wherever the project is 23 | being hosted (locally this would be localhost:8000/admin) 24 | 25 | Installation with existing Django Projects 26 | ------------------------------------------ 27 | 28 | :: 29 | 30 | 1. Add 'wooey' to INSTALLED_APPS in settings.py (and optionally, djcelery unless you wish to tie into an existing celery instance) 31 | 2. Add the following to your urls.py: 32 | path(r'', include('wooey.urls')), 33 | (Note: it does not need to be rooted at your site base, 34 | you can have r'wooey/'... as your router): 35 | 36 | 3. Migrate your database: 37 | # Django 1.8 and above 38 | ./manage.py makemigrations 39 | ./manage.py migrate 40 | 41 | 4. Ensure the following are in your TEMPLATE_CONTEXT_PROCSSORS variable: 42 | TEMPLATE_CONTEXT_PROCESSORS = [ 43 | ... 44 | 'django.contrib.auth.context_processors.auth', 45 | 'django.core.context_processors.request' 46 | ...] 47 | 48 | 5. If necessary, setup static file serving. For non-production servers, Django 49 | can be setup to do this for you by adding the following to your urls.py: 50 | 51 | from django.conf import settings 52 | from django.conf.urls.static import static 53 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 54 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 55 | 56 | 6. You may also need to define a MEDIA_ROOT, MEDIA_URL, STATIC_ROOT, and STATIC_URL 57 | if these are not setup already. 58 | 59 | Installation on remote servers 60 | ------------------------------ 61 | 62 | We have provided guides for several available services that host sites, as well as guides for steps common to multiple services (such as using Amazon Web Services). 63 | For examples not listed here, you are free to open up an `issue `_. (or document it and send a pull request!). 64 | 65 | 66 | .. toctree:: 67 | :maxdepth: 1 68 | 69 | heroku 70 | digitalocean 71 | openshift 72 | fly 73 | aws 74 | celery 75 | -------------------------------------------------------------------------------- /docs/remote.rst: -------------------------------------------------------------------------------- 1 | Remote File Systems 2 | =================== 3 | 4 | Wooey has been tested on heroku with S3 as a file storage system. 5 | Settings for this can be seen in the user\_settings.py, which give you a 6 | starting point for a non-local server. In short, you need to change your 7 | storage settings like such: 8 | 9 | STATICFILES\_STORAGE = DEFAULT\_FILE\_STORAGE = 10 | 'wooey.wooeystorage.CachedS3BotoStorage' WOOEY\_EPHEMERAL\_FILES = True 11 | -------------------------------------------------------------------------------- /docs/requirements.in: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-rtd-theme 3 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.9 3 | # by the following command: 4 | # 5 | # pip-compile --resolver=backtracking requirements.in 6 | # 7 | alabaster==0.7.13 8 | # via sphinx 9 | babel==2.12.1 10 | # via sphinx 11 | certifi==2022.12.7 12 | # via requests 13 | charset-normalizer==3.1.0 14 | # via requests 15 | docutils==0.18.1 16 | # via 17 | # sphinx 18 | # sphinx-rtd-theme 19 | idna==3.4 20 | # via requests 21 | imagesize==1.4.1 22 | # via sphinx 23 | importlib-metadata==6.6.0 24 | # via sphinx 25 | jinja2==3.1.2 26 | # via sphinx 27 | markupsafe==2.1.2 28 | # via jinja2 29 | packaging==23.1 30 | # via sphinx 31 | pygments==2.15.1 32 | # via sphinx 33 | requests==2.29.0 34 | # via sphinx 35 | snowballstemmer==2.2.0 36 | # via sphinx 37 | sphinx==6.2.1 38 | # via 39 | # -r requirements.in 40 | # sphinx-rtd-theme 41 | # sphinxcontrib-jquery 42 | sphinx-rtd-theme==1.2.0 43 | # via -r requirements.in 44 | sphinxcontrib-applehelp==1.0.4 45 | # via sphinx 46 | sphinxcontrib-devhelp==1.0.2 47 | # via sphinx 48 | sphinxcontrib-htmlhelp==2.0.1 49 | # via sphinx 50 | sphinxcontrib-jquery==4.1 51 | # via sphinx-rtd-theme 52 | sphinxcontrib-jsmath==1.0.1 53 | # via sphinx 54 | sphinxcontrib-qthelp==1.0.3 55 | # via sphinx 56 | sphinxcontrib-serializinghtml==1.1.5 57 | # via sphinx 58 | urllib3==1.26.15 59 | # via requests 60 | zipp==3.15.0 61 | # via importlib-metadata 62 | -------------------------------------------------------------------------------- /docs/running_wooey.rst: -------------------------------------------------------------------------------- 1 | Running Wooey 2 | ============= 3 | 4 | Wooey depends on a distributed worker to handle tasks, you can disable 5 | this by setting **WOOEY\_CELERY** to False in your settings, which will 6 | allow you to run Wooey through the simple command: 7 | 8 | :: 9 | 10 | python manage.py runserver 11 | 12 | However, this will cause the server to execute tasks, which will block 13 | the site. 14 | 15 | The recommended ways to run Wooey are: 16 | 17 | Through two separate processes 18 | ------------------------------ 19 | 20 | You can run Wooey by calling two commands (you will need a 21 | separate process for each): 22 | 23 | :: 24 | 25 | celery -A your_project_name worker -c 1 --beat -l info 26 | python manage.py runserver 27 | 28 | On Windows, the ``--beat`` option may not be supported and the `eventlet` 29 | pool will need to be specified. This looks like: 30 | 31 | :: 32 | 33 | celery -A your_project_name worker --pool=eventlet -l info 34 | 35 | Through a Procfile 36 | ------------------ 37 | 38 | A simple way to run Wooey on a server such as Heroku is through a Procfile 39 | using `honcho `__, which can be 40 | installed via pip. Make a file, called Procfile in the root of your 41 | project (the same place as manage.py) with the following contents: 42 | 43 | :: 44 | 45 | web: python manage.py runserver 46 | worker: celery -A your_project_name worker -c 1 --beat -l info 47 | EOM 48 | 49 | Your server can then be run by the simple command: 50 | 51 | :: 52 | 53 | honcho start 54 | 55 | On Windows, the ``--beat`` option may not be supported. 56 | 57 | 58 | With Docker 59 | ----------- 60 | 61 | `Docker `__ is a great way to get Wooey up and running quickly, especially 62 | for development. To get Wooey up and running with Docker and `docker-compose `__, 63 | follow these commands: 64 | 65 | :: 66 | 67 | git clone git@github.com:wooey/Wooey.git 68 | cd Wooey/docker 69 | ./wooey-compose build wooey 70 | ./wooey-compose run wooey python manage.py createsuperuser 71 | ... fill in info ... 72 | ./wooey-compose up wooey celery 73 | 74 | Now, a local Wooey server will be available at http://localhost:8081/ (or change the port in 75 | docker-compose.override.yml). 76 | -------------------------------------------------------------------------------- /docs/script_parameters_admin.rst: -------------------------------------------------------------------------------- 1 | .. _script-parameters-admin: 2 | 3 | Script Parameters Admin 4 | ======================= 5 | 6 | The Script Parameters admin page contains the models that correspond to 7 | the available parameters in a command line tool. Here, users may customize 8 | aspects of how a parameter is exposed in the Web UI and utilized. 9 | 10 | .. image:: img/wooey_admin_script_parameters.png 11 | 12 | Script Versions 13 | --------------- 14 | 15 | This column corresponds to the different versions of a script the parameter is 16 | associated with. If a script is updated, multiple script versions will be 17 | listed that this parameter is associated with. 18 | 19 | Parameter Group 20 | --------------- 21 | 22 | This corresponds to the group a parameter belongs to. This is exposed in the UI 23 | as the tab a parameter appears under. 24 | 25 | Short Param 26 | ----------- 27 | 28 | This corresponds to the command line argument a parameter is associated with. 29 | -------------------------------------------------------------------------------- /docs/scripts.rst: -------------------------------------------------------------------------------- 1 | Adding & Managing Scripts 2 | ========================= 3 | 4 | Scripts may be added in two ways, through the Django admin interface as 5 | well as through the *addscript* command in manage.py. 6 | 7 | Script Guidelines 8 | ----------------- 9 | 10 | The easiest way to make your scripts compatible with Wooey is to define 11 | your ArgParse class in the global scope. For instance: 12 | 13 | :: 14 | 15 | 16 | import argparse 17 | import sys 18 | 19 | parser = argparse.ArgumentParser(description="Find the sum of all the numbers below a certain number.") 20 | parser.add_argument('--below', help='The number to find the sum of numbers below.', type=int, default=1000) 21 | 22 | def main(): 23 | args = parser.parse_args() 24 | s = sum((i for i in range(args.below))) 25 | print("Sum =", s) 26 | return 0 27 | 28 | if __name__ == "__main__": 29 | sys.exit(main()) 30 | 31 | 32 | If you have failing scripts, please open an issue with their contents so 33 | we can handle cases as they appear and try to make this as 34 | all-encompasing as possible. One known area which fails currently is 35 | defining your argparse instance inside the 36 | ``if __name__ == "__main__"`` block 37 | 38 | 39 | The admin Interface 40 | ------------------- 41 | 42 | Within the django admin interface, scripts may be added to through the 43 | 'scripts' model. Here, the user permissions may be set, as well as 44 | cosmetic features such as the script's display name, description (if 45 | provided, otherwise the script name and description will be 46 | automatically populated by the description from argparse if available). 47 | 48 | The command line 49 | ---------------- 50 | 51 | ``./manage.py addscript`` 52 | 53 | This will add a script or a folder of scripts to Wooey (if a folder is 54 | passed instead of a file). By default, scripts will be created in the 55 | 'Wooey Scripts' group. 56 | 57 | Script Organization 58 | ------------------- 59 | 60 | Scripts can be viewed at the root url of Wooey. The ordering of scripts, 61 | and groupings of scripts can be altered by changing the 'Script order' 62 | or 'Group order' options within the admin. 63 | 64 | Script Permissions 65 | ------------------ 66 | 67 | Scripts and script groups can be relegated to certain groups of users. 68 | The 'user groups' option, if set, will restrict script usage to users 69 | within selected groups. 70 | 71 | Scripts and groups may also be shutoff to all users by unchecked the 72 | 'script/group active' option. 73 | 74 | Deleting Scripts 75 | ---------------- 76 | 77 | Scripts may be deleted from the admin interface. When deleting a script, 78 | all related objects, such as previously run jobs, will also be deleted. 79 | 80 | Other Script Runners 81 | -------------------- 82 | 83 | There have been several requests for more advanced script setups, such as executing R code or docker. 84 | For now, there is no official integrations with these languages, but it is possible to create a simple 85 | wrapper script that calls docker or another programming language. 86 | 87 | .. toctree:: 88 | :maxdepth: 1 89 | 90 | docker_scripts 91 | -------------------------------------------------------------------------------- /docs/security.rst: -------------------------------------------------------------------------------- 1 | Security 2 | ======== 3 | 4 | Wooey is run with Django, which has a great record with respect to security, and the 5 | production deployment settings utilizes many of the best practices espoused in 6 | the `12-factor-app `__. 7 | 8 | Scripts run by Wooey must be uploaded by users with administrator privileges. Thus, 9 | only scripts you are comfortable users running should be available (and scripts 10 | can be isolated to a given set of users). 11 | -------------------------------------------------------------------------------- /docs/venv_setup.rst: -------------------------------------------------------------------------------- 1 | Virtual Environment Setup 2 | ========================= 3 | 4 | Virtual environments allow you to specify a python interpreter and a set of requirements to run a script in. 5 | 6 | Like with script addition, virtual environment setup is done via the Django admin. 7 | 8 | Adding a virtual environment 9 | ---------------------------- 10 | 11 | Virtual environments may be setup from the admin under the `Virtual Environments` section: 12 | 13 | .. image:: img/venv_admin_page.png 14 | 15 | To add a virtual environment, click the Add button and a form will be rendered for setting up a new virtual environment. 16 | 17 | .. image:: img/venv_change_form.png 18 | 19 | The fields are: 20 | 21 | * **name** What to call the virtual environment. Virtual environments can be reused across scripts if desired. 22 | 23 | * **python binary** The path to a python executable to create and use for running the virtual environment 24 | 25 | * **requirements** This is equivalent to the requirements.txt file for defining packages to install 26 | 27 | * **Venv directory** Where to store the virtual environment. The default location for this can be defined via the `WOOEY_VIRTUAL_ENVIRONMENT_DIRECTORY` setting. If not defined, this defaults to the system temporary directory folder. 28 | 29 | Lastly, to add the virtual environment to a script, from the script admin view, choose the virtual environment to run 30 | the script in: 31 | 32 | .. image:: img/venv_set_script_venv.png 33 | 34 | Adding scripts with invalid imports 35 | ----------------------------------- 36 | 37 | Virtual environments are meant to have requirements that may not be present on the main Wooey server. Thus, some 38 | scripts may fail to import because of dependency conflicts. To resolve this, a new option is available on scripts, 39 | `ignore_bad_imports`, that may be set via the admin. 40 | 41 | .. image:: img/ignore_bad_imports.png 42 | -------------------------------------------------------------------------------- /docs/wooey_ui.rst: -------------------------------------------------------------------------------- 1 | Wooey UI 2 | ============= 3 | 4 | Wooey's homepage provides a list of all scripts available to a user. 5 | 6 | .. image:: img/Wooey_Home.png 7 | 8 | From here, a user can choose a script to configure for execution. On all pages 9 | a header menu is available that provides the number of running scripts, the 10 | number of queued scripts, and the number of scripts that has finished executing. 11 | Users can select these header items for further inspection of pending or completed 12 | jobs. Additionally, there is a *scrapbook* where a user can save results from previous 13 | jobs for easy access and a language menu item for translation of Wooey's interface 14 | to various languages (If your language is not currently supported, we would love to 15 | add it!). 16 | 17 | Running scripts 18 | --------------- 19 | 20 | Scripts may be accessed via the homepage or by searching for scripts in the 21 | script search. Searching for scripts is accessible via the left menu sidebar 22 | that is viewable by clicking the menu button on the left side of the header. 23 | From the script panel, scripts can be parameterized and executed by Wooey. 24 | If a script has subparsers, they are accessible via a dropdown menu on 25 | the upper left of the script parameter panel (in a script with subparsers, 26 | this simply has the text *Settings*). Because most subparsers have a "main" 27 | parser, such as Django's `manage.py`, these settings can be specified via 28 | the *Main Parser Parameter* button. To select and parameterize a given 29 | subparser, the subparser command and its parameters are available by 30 | selecting it via the dropdown menu. 31 | 32 | Running previous versions of a script 33 | ------------------------------------- 34 | 35 | Previously uploaded scripts are kept in Wooey, providing a mechanism for 36 | to evaluate script changes and give end-users an opportunity to provide 37 | feedback. In the admin interface, there is an option to set a script version 38 | as the *default* version to use, but previous versions are accessible from 39 | the main UI via the black down array next to the script name. There are 2 40 | deliniations specified here -- the *Script Version*, and the *Script Iteration*. 41 | If a command line generating tool supports versioning (and Wooey is able 42 | to parse this information), updates to the script version will result in a new 43 | version being created. If a command line library doesn't support versioning 44 | or the version has not been updated in a script, the Script Iteration counter 45 | will be incremented. 46 | 47 | Using URL parameters to pre-populate script parameters 48 | ------------------------------------------------------ 49 | 50 | Wooey supports pre-populating script parameters via URL parameters. This is 51 | useful for providing a link to a script with some parameters already set. The 52 | following rules apply. 53 | 54 | * URL parameters are specified as :code:`?parameter=value` 55 | * Multiple parameters may be specified by separating them with :code:`&`. 56 | For example, :code:`?parameter1=value1¶meter2=value2`. 57 | * Parameters are specified by the name used in the script, **but in snake case**. 58 | For example, a parameter named :code:`--my-parameter` would be specified as 59 | :code:`?my_parameter=value`. 60 | * If a parameter is a flag, passing any value will set the flag. For example, 61 | :code:`?my_flag=1` or :code:`?my_flag=true` will set the flag. 62 | * If a parameter accepts multiple values (:code:`nargs`), the values are specified 63 | by passing the parameter multiple times. For example, :code:`?my_parameter=1&my_parameter=2` 64 | will set the parameter to have two values filled out: :code:`1` and :code:`2`. 65 | If a parameter does not support multiple values and multiple values are passed, 66 | the last value will be used. 67 | * If the script has subparsers, the :code:`__subparser` parameter is used to specify 68 | the subparser to use. For example, :code:`?__subparser=mysubparser` will select the 69 | subparser named :code:`mysubparser`. 70 | -------------------------------------------------------------------------------- /scripts/wooify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __author__ = 'chris' 3 | import os 4 | os.environ['WOOEY_BOOTSTRAP'] = 'True' 5 | 6 | from wooey.backend import command_line 7 | 8 | if __name__ == "__main__": 9 | command_line.bootstrap() 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.13.3 3 | commit = True 4 | tag = True 5 | parse = (?P\d+)\.(?P\d+)\.(?P\d+)(rc(?P\d+))? 6 | serialize = 7 | {major}.{minor}.{patch}rc{rc} 8 | {major}.{minor}.{patch} 9 | 10 | [bumpversion:file:setup.py] 11 | search = version="{current_version}" 12 | replace = version="{new_version}" 13 | 14 | [flake8] 15 | ignore = E111,E114,E121,E122,E124,E125,E126,E127,E128,E129,E131,E203,E266,E231,E265,E302,E501,E731,F841,W503,W504 16 | exclude = 17 | .git, 18 | __pycache__, 19 | build, 20 | dist, 21 | wooey/migrations/*, 22 | wooey/tests/scripts/*, 23 | statistics = True 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | with open(os.path.join(os.path.dirname(__file__), "README.md")) as readme: 5 | README = readme.read() 6 | 7 | # allow setup.py to be run from any path 8 | os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) 9 | 10 | setup( 11 | name="wooey", 12 | version="0.13.3", 13 | packages=find_packages(), 14 | scripts=["scripts/wooify"], 15 | entry_points={ 16 | "console_scripts": [ 17 | "wooify = wooey.backend.command_line:bootstrap", 18 | ] 19 | }, 20 | python_requires=">3.5.0", 21 | install_requires=[ 22 | "celery>=4,<6", 23 | "clinto>=0.5.1", 24 | "Django>=3,<5", 25 | "django-autoslug", 26 | "django-storages", 27 | 'eventlet>=0.22.1 ;platform_system=="Windows"', 28 | 'importlib-metadata<5.0 ;python_version<="3.7"', 29 | 'pypiwin32 ;(platform_system=="Windows" and python_version>"3.4")', 30 | ], 31 | extras_require={ 32 | "dev": [ 33 | "boto3", 34 | "coverage", 35 | "factory-boy < 3.0.0", 36 | "mock", 37 | "pytest", 38 | "pytest-cov", 39 | "sphinx", 40 | "watchdog[watchmedo]", 41 | ] 42 | }, 43 | include_package_data=True, 44 | description="A Django app which creates a web GUI and task interface for argparse scripts", 45 | url="http://www.github.com/wooey/wooey", 46 | author="Chris Mitchell , Martin Fitzpatrick ", 47 | classifiers=[ 48 | "Environment :: Web Environment", 49 | "Framework :: Django", 50 | "Intended Audience :: Developers", 51 | "License :: OSI Approved :: BSD License", 52 | "Operating System :: OS Independent", 53 | "Programming Language :: Python", 54 | "Programming Language :: Python :: 3", 55 | "Programming Language :: Python :: 3.7", 56 | "Programming Language :: Python :: 3.8", 57 | "Programming Language :: Python :: 3.9", 58 | "Programming Language :: Python :: 3.10", 59 | "Topic :: Internet :: WWW/HTTP", 60 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 61 | ], 62 | ) 63 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /tests/test_project.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | from unittest import TestCase 3 | import subprocess 4 | import os 5 | import shutil 6 | import sys 7 | 8 | BASE_DIR = os.path.split(__file__)[0] 9 | WOOEY_SCRIPT_PATH = os.path.join(BASE_DIR, "..", "scripts", "wooify") 10 | WOOEY_TEST_PROJECT_NAME = "wooey_project" 11 | WOOEY_TEST_PROJECT_PATH = os.path.join(BASE_DIR, WOOEY_TEST_PROJECT_NAME) 12 | WOOEY_TEST_PROJECT_MANAGE = os.path.join(WOOEY_TEST_PROJECT_PATH, "manage.py") 13 | PYTHON_INTERPRETTER = sys.executable if sys.executable else "python" 14 | 15 | env = os.environ 16 | env["DJANGO_SETTINGS_MODULE"] = "{}.settings".format(WOOEY_TEST_PROJECT_NAME) 17 | env["TESTING"] = "True" 18 | 19 | 20 | class TestProject(TestCase): 21 | def setUp(self): 22 | os.chdir(BASE_DIR) 23 | # if old stuff exists, remove it 24 | if os.path.exists(WOOEY_TEST_PROJECT_PATH): 25 | shutil.rmtree(WOOEY_TEST_PROJECT_PATH) 26 | 27 | def tearDown(self): 28 | os.chdir(BASE_DIR) 29 | if os.path.exists(WOOEY_TEST_PROJECT_PATH): 30 | shutil.rmtree(WOOEY_TEST_PROJECT_PATH) 31 | 32 | def test_bootstrap(self): 33 | from wooey.backend import command_line 34 | 35 | sys.argv = [WOOEY_SCRIPT_PATH, "-p", WOOEY_TEST_PROJECT_NAME] 36 | ret = command_line.bootstrap(env=env, cwd=BASE_DIR) 37 | self.assertIsNone(ret) 38 | # test our script is executable from the command line, it will fail with return code of 1 since 39 | # the project already exists 40 | proc = subprocess.Popen( 41 | [PYTHON_INTERPRETTER, WOOEY_SCRIPT_PATH, "-p", WOOEY_TEST_PROJECT_NAME] 42 | ) 43 | stdout, stderr = proc.communicate() 44 | self.assertEqual(proc.returncode, 1, stderr) 45 | -------------------------------------------------------------------------------- /wooey-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey-banner.png -------------------------------------------------------------------------------- /wooey/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = "wooey.apps.WooeyConfig" 2 | -------------------------------------------------------------------------------- /wooey/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .jobs import ( # noqa: F401 2 | job_details, 3 | job_status, 4 | ) 5 | from .scripts import ( # noqa: F401 6 | add_or_update_script, 7 | submit_script, 8 | ) 9 | -------------------------------------------------------------------------------- /wooey/api/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class SubmitForm(forms.Form): 6 | job_name = forms.CharField() 7 | job_description = forms.CharField(required=False) 8 | version = forms.CharField(required=False) 9 | iteration = forms.IntegerField(required=False) 10 | command = forms.CharField(required=False) 11 | 12 | 13 | class AddScriptForm(forms.Form): 14 | group = forms.CharField(required=False) 15 | default = forms.NullBooleanField(required=False) 16 | ignore_bad_imports = forms.BooleanField( 17 | required=False, 18 | help_text=_( 19 | "Ignore bad imports when adding scripts. This is useful if a script is under a virtual environment." 20 | ), 21 | ) 22 | 23 | def clean_default(self): 24 | if self.cleaned_data["default"] is None: 25 | return True 26 | return self.cleaned_data["default"] 27 | 28 | def clean_ignore_bad_imports(self): 29 | if self.cleaned_data["ignore_bad_imports"] is None: 30 | return False 31 | return self.cleaned_data["ignore_bad_imports"] 32 | -------------------------------------------------------------------------------- /wooey/api/jobs.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from django.utils.encoding import force_str 3 | from django.utils.translation import gettext_lazy as _ 4 | from django.views.decorators.csrf import csrf_exempt 5 | from django.views.decorators.http import require_http_methods 6 | 7 | from .. import models 8 | from ..utils import requires_login 9 | 10 | 11 | @csrf_exempt 12 | @require_http_methods(["GET"]) 13 | @requires_login 14 | def job_status(request, job_id): 15 | job = models.WooeyJob.objects.get(id=job_id) 16 | if job.can_user_view(request.user): 17 | return JsonResponse( 18 | { 19 | "status": job.status, 20 | "is_complete": job.status in models.WooeyJob.TERMINAL_STATES, 21 | } 22 | ) 23 | else: 24 | return JsonResponse( 25 | { 26 | "valid": False, 27 | "errors": { 28 | "__all__": [ 29 | force_str(_("You are not permitted to access this job.")) 30 | ] 31 | }, 32 | }, 33 | status=403, 34 | ) 35 | 36 | 37 | @csrf_exempt 38 | @require_http_methods(["GET"]) 39 | @requires_login 40 | def job_details(request, job_id): 41 | job = models.WooeyJob.objects.get(id=job_id) 42 | if job.can_user_view(request.user): 43 | assets = [] 44 | is_terminal = job.status in models.WooeyJob.TERMINAL_STATES 45 | if is_terminal: 46 | for asset in job.userfile_set.all(): 47 | assets.append( 48 | { 49 | "name": asset.filename, 50 | "url": request.build_absolute_uri( 51 | asset.system_file.filepath.url 52 | ), 53 | } 54 | ) 55 | return JsonResponse( 56 | { 57 | "status": job.status, 58 | "is_complete": is_terminal, 59 | "uuid": job.uuid, 60 | "job_name": job.job_name, 61 | "job_description": job.job_description, 62 | "stdout": job.stdout, 63 | "stderr": job.stderr, 64 | "assets": assets, 65 | } 66 | ) 67 | else: 68 | return JsonResponse( 69 | { 70 | "valid": False, 71 | "errors": { 72 | "__all__": [ 73 | force_str(_("You are not permitted to access this job.")) 74 | ] 75 | }, 76 | }, 77 | status=403, 78 | ) 79 | -------------------------------------------------------------------------------- /wooey/apps.py: -------------------------------------------------------------------------------- 1 | try: 2 | from django.apps import AppConfig 3 | except ImportError: 4 | AppConfig = object 5 | from django.conf import settings 6 | from . import settings as wooey_settings 7 | 8 | 9 | class WooeyConfig(AppConfig): 10 | name = "wooey" 11 | verbose_name = "Wooey" 12 | 13 | def ready(self): 14 | from . import signals # noqa: F401 15 | 16 | if wooey_settings.WOOEY_ENABLE_API_KEYS: 17 | new_middleware = [] 18 | for value in settings.MIDDLEWARE: 19 | new_middleware.append(value) 20 | if value == "django.contrib.auth.middleware.AuthenticationMiddleware": 21 | new_middleware.append("wooey.middleware.api_key_login") 22 | settings.MIDDLEWARE = new_middleware 23 | -------------------------------------------------------------------------------- /wooey/backend/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | -------------------------------------------------------------------------------- /wooey/conf/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /wooey/conf/project_template/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | # This will make sure the app is always imported when 4 | # Django starts so that shared_task will use this app. 5 | from .wooey_celery_app import app as celery_app # noqa: F401 6 | -------------------------------------------------------------------------------- /wooey/conf/project_template/middleware.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | import traceback 3 | import sys 4 | 5 | 6 | class ProcessExceptionMiddleware: 7 | def __init__(self, get_response): 8 | self.get_response = get_response 9 | 10 | def __call__(self, request): 11 | response = self.get_response(request) 12 | if response.status_code != 200: 13 | try: 14 | sys.stderr.write("{}".format("".join(traceback.format_exc()))) 15 | except AttributeError: 16 | pass 17 | return response 18 | -------------------------------------------------------------------------------- /wooey/conf/project_template/requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | wooey 3 | django-sslify 4 | django-storages 5 | django-celery 6 | django-autoslug 7 | boto 8 | waitress 9 | psycopg2 10 | collectfast 11 | honcho 12 | -------------------------------------------------------------------------------- /wooey/conf/project_template/settings/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_settings import * # noqa: F401, F403 2 | -------------------------------------------------------------------------------- /wooey/conf/project_template/settings/wooey_settings.py: -------------------------------------------------------------------------------- 1 | from .django_settings import * # noqa: F403 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | INSTALLED_APPS += ( # noqa: F405 5 | # 'corsheaders', 6 | "wooey", 7 | ) 8 | 9 | 10 | LANGUAGES = [ 11 | ("de", _("German")), 12 | ("en", _("English")), 13 | ("fr", _("French")), 14 | ("ja", _("Japanese")), 15 | ("nl", _("Dutch")), 16 | ("zh-hans", _("Simplified Chinese")), 17 | ("ko", _("Korean")), 18 | ("es", _("Spanish")), 19 | ] 20 | 21 | NEW_MIDDLEWARE = [] 22 | for i in MIDDLEWARE: # noqa: F405 23 | NEW_MIDDLEWARE.append(i) 24 | if i == "django.contrib.sessions.middleware.SessionMiddleware": 25 | NEW_MIDDLEWARE.append("django.middleware.locale.LocaleMiddleware") 26 | 27 | NEW_MIDDLEWARE.append("{{ project_name }}.middleware.ProcessExceptionMiddleware") 28 | MIDDLEWARE = NEW_MIDDLEWARE 29 | 30 | PROJECT_NAME = "{{ project_name }}" 31 | WOOEY_CELERY_APP_NAME = "wooey.celery" 32 | WOOEY_CELERY_TASKS = "wooey.tasks" 33 | -------------------------------------------------------------------------------- /wooey/conf/project_template/urls/__init__.py: -------------------------------------------------------------------------------- 1 | from .user_urls import * # noqa: F401, F403 2 | -------------------------------------------------------------------------------- /wooey/conf/project_template/urls/user_urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | 4 | from .wooey_urls import * # noqa: F403 5 | 6 | if settings.DEBUG: 7 | urlpatterns += static( # noqa: F405 8 | settings.MEDIA_URL, document_root=settings.MEDIA_ROOT 9 | ) 10 | urlpatterns += static( # noqa: F405 11 | settings.STATIC_URL, document_root=settings.STATIC_ROOT 12 | ) 13 | -------------------------------------------------------------------------------- /wooey/conf/project_template/urls/wooey_urls.py: -------------------------------------------------------------------------------- 1 | from .django_urls import * # noqa: F403 2 | from django.urls import include, path 3 | 4 | urlpatterns += [ # noqa: F405 5 | # path('admin/', include(admin.site.urls)), 6 | path("", include("wooey.urls")), 7 | path("", include("django.contrib.auth.urls")), 8 | ] 9 | -------------------------------------------------------------------------------- /wooey/conf/project_template/wooey_celery_app.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | import os 3 | 4 | from celery import Celery 5 | 6 | 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings") 8 | 9 | app = Celery("{{ project_name }}") 10 | 11 | # Using a string here means the worker will not have to 12 | # pickle the object when using Windows. 13 | if "CELERY_CONFIG_MODULE" in os.environ: 14 | app.config_from_envvar("CELERY_CONFIG_MODULE") 15 | else: 16 | app.config_from_object("django.conf:settings") 17 | app.autodiscover_tasks() 18 | 19 | 20 | @app.task(bind=True) 21 | def debug_task(self): 22 | print("Request: {0!r}".format(self.request)) 23 | -------------------------------------------------------------------------------- /wooey/django_compat.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | try: 4 | settings.configure() 5 | except RuntimeError: 6 | pass 7 | 8 | from django.template import Engine 9 | 10 | get_template_from_string = Engine.get_default().from_string 11 | -------------------------------------------------------------------------------- /wooey/errors.py: -------------------------------------------------------------------------------- 1 | class ParserError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /wooey/forms/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | __author__ = "chris" 4 | from .factory import * # noqa: F401, F403 5 | from .profile import * # noqa: F401, F403 6 | from .scripts import * # noqa: F401, F403 7 | -------------------------------------------------------------------------------- /wooey/forms/config.py: -------------------------------------------------------------------------------- 1 | WOOEY_MULTI_WIDGET_ATTR = "data-wooey-multiple" 2 | WOOEY_MULTI_WIDGET_ANCHOR = "wooey-multi-input" 3 | -------------------------------------------------------------------------------- /wooey/forms/fields.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | __author__ = "chris" 4 | from django.forms import FileField 5 | from django.forms import widgets 6 | 7 | 8 | class WooeyOutputFileField(FileField): 9 | widget = widgets.TextInput 10 | 11 | def __init__(self, *args, **kwargs): 12 | kwargs["allow_empty_file"] = True 13 | super(WooeyOutputFileField, self).__init__(*args, **kwargs) 14 | 15 | 16 | # TODO: Make a complex widget of filepathfield/filefield 17 | class WooeyUploadFileField(FileField): 18 | pass 19 | -------------------------------------------------------------------------------- /wooey/forms/profile.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | class APIKeyForm(forms.Form): 5 | name = forms.CharField() 6 | -------------------------------------------------------------------------------- /wooey/forms/scripts.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django import forms 3 | 4 | 5 | class WooeyForm(forms.Form): 6 | wooey_type = forms.IntegerField(widget=forms.HiddenInput) 7 | wooey_parser = forms.IntegerField(widget=forms.HiddenInput, required=False) 8 | 9 | def add_wooey_fields(self): 10 | # This adds fields such as job name, description that we like to validate on but don't want to include in 11 | # form rendering 12 | self.fields["job_name"] = forms.CharField() 13 | self.fields["job_description"] = forms.CharField(required=False) 14 | -------------------------------------------------------------------------------- /wooey/locale/de/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/de/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/ja/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/ja/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/ko/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/ko/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/locale/zh_Hans/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/locale/zh_Hans/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /wooey/management/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /wooey/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /wooey/middleware.py: -------------------------------------------------------------------------------- 1 | from .models import APIKey 2 | from . import settings as wooey_settings 3 | 4 | 5 | def api_key_login(get_response): 6 | def middleware(request): 7 | if not request.user.is_authenticated and wooey_settings.WOOEY_ENABLE_API_KEYS: 8 | auth_value = request.META.get("HTTP_AUTHORIZATION", "") 9 | if auth_value: 10 | try: 11 | prefix, token = auth_value.split(" ") 12 | except ValueError: 13 | pass 14 | else: 15 | if prefix == "Bearer": 16 | user = APIKey.get_user_by_key(token) 17 | if user: 18 | request.user = user 19 | 20 | return get_response(request) 21 | 22 | return middleware 23 | -------------------------------------------------------------------------------- /wooey/migrations/0002_remove_scriptparameter_output_path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="scriptparameter", 16 | name="output_path", 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wooey/migrations/0003_populate_from_slug.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import autoslug.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0002_remove_scriptparameter_output_path"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="script", 17 | name="slug", 18 | field=autoslug.fields.AutoSlugField( 19 | populate_from="script_name", unique=True, editable=False 20 | ), 21 | ), 22 | migrations.AlterField( 23 | model_name="scriptgroup", 24 | name="slug", 25 | field=autoslug.fields.AutoSlugField( 26 | populate_from="group_name", unique=True, editable=False 27 | ), 28 | ), 29 | migrations.AlterField( 30 | model_name="scriptparameter", 31 | name="slug", 32 | field=autoslug.fields.AutoSlugField( 33 | populate_from="script_param", unique=True, editable=False 34 | ), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /wooey/migrations/0004_choice_limits.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0003_populate_from_slug"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="scriptparameter", 16 | name="choice_limit", 17 | field=models.CharField(max_length=10, null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wooey/migrations/0005_size_bytes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("wooey", "0004_choice_limits"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Favorite", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | verbose_name="ID", 23 | serialize=False, 24 | auto_created=True, 25 | primary_key=True, 26 | ), 27 | ), 28 | ("object_id", models.PositiveIntegerField()), 29 | ("created_date", models.DateTimeField(auto_now_add=True)), 30 | ( 31 | "content_type", 32 | models.ForeignKey( 33 | to="contenttypes.ContentType", on_delete=models.CASCADE 34 | ), 35 | ), 36 | ( 37 | "user", 38 | models.ForeignKey( 39 | related_name="favorites", 40 | blank=True, 41 | to=settings.AUTH_USER_MODEL, 42 | null=True, 43 | on_delete=models.CASCADE, 44 | ), 45 | ), 46 | ], 47 | ), 48 | migrations.AlterUniqueTogether( 49 | name="favorite", 50 | unique_together=set([("user", "content_type", "object_id")]), 51 | ), 52 | migrations.AddField( 53 | model_name="wooeyfile", 54 | name="size_bytes", 55 | field=models.IntegerField(null=True), 56 | ), 57 | ] 58 | -------------------------------------------------------------------------------- /wooey/migrations/0006_script_group_defaults.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0005_size_bytes"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="script", 16 | name="script_group", 17 | field=models.ForeignKey( 18 | blank=True, to="wooey.ScriptGroup", null=True, on_delete=models.PROTECT 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wooey/migrations/0007_script_documentation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0006_script_group_defaults"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="script", 16 | name="documentation", 17 | field=models.TextField(null=True, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wooey/migrations/0008_short_param_admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0007_script_documentation"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="scriptparameter", 16 | name="short_param", 17 | field=models.CharField(max_length=255, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wooey/migrations/0009_script_versioning.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0008_short_param_admin"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="ScriptVersion", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | verbose_name="ID", 21 | serialize=False, 22 | auto_created=True, 23 | primary_key=True, 24 | ), 25 | ), 26 | ( 27 | "script_version", 28 | models.CharField( 29 | default="1", 30 | help_text="The script version.", 31 | max_length=50, 32 | blank=True, 33 | ), 34 | ), 35 | ("script_iteration", models.PositiveSmallIntegerField(default=1)), 36 | ("script_path", models.FileField(upload_to=b"")), 37 | ("default_version", models.BooleanField(default=False)), 38 | ("created_date", models.DateTimeField(auto_now_add=True)), 39 | ("modified_date", models.DateTimeField(auto_now=True)), 40 | ( 41 | "script", 42 | models.ForeignKey( 43 | related_name="script_version_new", 44 | to="wooey.Script", 45 | on_delete=models.PROTECT, 46 | ), 47 | ), 48 | ], 49 | ), 50 | migrations.AddField( 51 | model_name="scriptparameter", 52 | name="script_version", 53 | field=models.ForeignKey( 54 | null=True, to="wooey.ScriptVersion", on_delete=models.PROTECT 55 | ), 56 | preserve_default=False, 57 | ), 58 | migrations.AddField( 59 | model_name="scriptparametergroup", 60 | name="script_version", 61 | field=models.ForeignKey( 62 | null=True, to="wooey.ScriptVersion", on_delete=models.PROTECT 63 | ), 64 | preserve_default=False, 65 | ), 66 | migrations.AddField( 67 | model_name="wooeyjob", 68 | name="script_version", 69 | field=models.ForeignKey( 70 | null=True, to="wooey.ScriptVersion", on_delete=models.PROTECT 71 | ), 72 | preserve_default=False, 73 | ), 74 | ] 75 | -------------------------------------------------------------------------------- /wooey/migrations/0010_script_versioning_data_migration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | def make_script_versions(apps, schema_editor): 8 | Script = apps.get_model("wooey", "Script") 9 | ScriptVersion = apps.get_model("wooey", "ScriptVersion") 10 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 11 | ScriptParameterGroup = apps.get_model("wooey", "ScriptParameterGroup") 12 | WooeyJob = apps.get_model("wooey", "WooeyJob") 13 | # first group all scripts by name 14 | from itertools import groupby 15 | from operator import itemgetter 16 | 17 | scripts = sorted( 18 | [(i.script_name, i) for i in Script.objects.all()], key=itemgetter(0) 19 | ) 20 | for script_name, scripts in groupby(scripts, key=lambda x: x[0]): 21 | # create ScriptVersion from the information in scripts 22 | ordered_scripts = sorted( 23 | [(int(script.script_version), script) for i, script in scripts], 24 | key=itemgetter(0), 25 | ) 26 | last_script = ordered_scripts[-1][1] 27 | for i, v in enumerate(ordered_scripts): 28 | script = v[1] 29 | version_kwargs = { 30 | "script_version": "1", 31 | "script_iteration": script.script_version, 32 | "script_path": script.script_path, 33 | "script": last_script, 34 | } 35 | if v[1] == last_script: 36 | version_kwargs.update({"default_version": True}) 37 | script_version = ScriptVersion(**version_kwargs) 38 | script_version.save() 39 | ScriptParameter.objects.filter(script=script).update( 40 | script_version=script_version 41 | ) 42 | ScriptParameterGroup.objects.filter(script=script).update( 43 | script_version=script_version 44 | ) 45 | WooeyJob.objects.filter(script=script).update(script_version=script_version) 46 | 47 | 48 | class Migration(migrations.Migration): 49 | 50 | dependencies = [ 51 | ("wooey", "0009_script_versioning"), 52 | ] 53 | 54 | operations = [ 55 | migrations.RunPython(make_script_versions), 56 | ] 57 | -------------------------------------------------------------------------------- /wooey/migrations/0011_script_versioning_cleanup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0010_script_versioning_data_migration"), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="scriptparameter", 16 | name="script", 17 | ), 18 | migrations.RemoveField( 19 | model_name="scriptparametergroup", 20 | name="script", 21 | ), 22 | migrations.RemoveField( 23 | model_name="wooeyjob", 24 | name="script", 25 | ), 26 | migrations.RemoveField( 27 | model_name="script", 28 | name="script_path", 29 | ), 30 | migrations.RemoveField( 31 | model_name="script", 32 | name="script_version", 33 | ), 34 | migrations.AlterField( 35 | model_name="scriptversion", 36 | name="script", 37 | field=models.ForeignKey( 38 | related_name="script_version", 39 | to="wooey.Script", 40 | on_delete=models.PROTECT, 41 | ), 42 | ), 43 | migrations.AlterField( 44 | model_name="scriptparameter", 45 | name="script_version", 46 | field=models.ForeignKey(to="wooey.ScriptVersion", on_delete=models.PROTECT), 47 | ), 48 | migrations.AlterField( 49 | model_name="scriptparametergroup", 50 | name="script_version", 51 | field=models.ForeignKey(to="wooey.ScriptVersion", on_delete=models.PROTECT), 52 | ), 53 | migrations.AlterField( 54 | model_name="wooeyjob", 55 | name="script_version", 56 | field=models.ForeignKey(to="wooey.ScriptVersion", on_delete=models.PROTECT), 57 | ), 58 | ] 59 | -------------------------------------------------------------------------------- /wooey/migrations/0012_wooeyjob_uuid.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0011_script_versioning_cleanup"), 12 | ] 13 | 14 | operations = [ 15 | # Add the uuid field with unique=False for existing entries 16 | # due to a bug in migrations this will set all to the same uuid 17 | migrations.AddField( 18 | model_name="wooeyjob", 19 | name="uuid", 20 | field=models.CharField(default=uuid.uuid4, unique=False, max_length=255), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wooey/migrations/0013_wooeyjob_uuid_populate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import uuid 6 | 7 | 8 | def gen_uuid(apps, schema_editor): 9 | WooeyJob = apps.get_model("wooey", "WooeyJob") 10 | for obj in WooeyJob.objects.all(): 11 | obj.uuid = uuid.uuid4() 12 | obj.save() 13 | 14 | 15 | class Migration(migrations.Migration): 16 | 17 | dependencies = [ 18 | ("wooey", "0012_wooeyjob_uuid"), 19 | ] 20 | 21 | operations = [ 22 | # Set the uuids for existing records 23 | migrations.RunPython(gen_uuid), 24 | ] 25 | -------------------------------------------------------------------------------- /wooey/migrations/0014_wooeyjob_uuid_finalise.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import uuid 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0013_wooeyjob_uuid_populate"), 12 | ] 13 | 14 | operations = [ 15 | # Set to unique=True 16 | migrations.AlterField( 17 | model_name="wooeyjob", 18 | name="uuid", 19 | field=models.CharField(default=uuid.uuid4, unique=True, max_length=255), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wooey/migrations/0015_hidden_parameters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0014_wooeyjob_uuid_finalise"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="scriptparameter", 16 | name="hidden", 17 | field=models.BooleanField(default=False), 18 | ), 19 | migrations.AddField( 20 | model_name="scriptparametergroup", 21 | name="hidden", 22 | field=models.BooleanField(default=False), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /wooey/migrations/0016_wooeyfile_checksum.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0015_hidden_parameters"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="wooeyfile", 16 | name="checksum", 17 | field=models.CharField(max_length=40, blank=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wooey/migrations/0017_wooeyfile_generate_checksums.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations 5 | 6 | 7 | def gen_checksums(apps, schema_editor): 8 | WooeyFile = apps.get_model("wooey", "WooeyFile") 9 | from ..backend.utils import get_checksum 10 | 11 | for obj in WooeyFile.objects.all(): 12 | try: 13 | obj.checksum = get_checksum(path=obj.filepath.path) 14 | obj.save() 15 | except IOError: 16 | print(obj.filepath, "not found") 17 | 18 | 19 | class Migration(migrations.Migration): 20 | 21 | dependencies = [ 22 | ("wooey", "0016_wooeyfile_checksum"), 23 | ] 24 | 25 | operations = [ 26 | migrations.RunPython(gen_checksums), 27 | ] 28 | -------------------------------------------------------------------------------- /wooey/migrations/0018_userfile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0017_wooeyfile_generate_checksums"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="UserFile", 16 | fields=[ 17 | ( 18 | "id", 19 | models.AutoField( 20 | verbose_name="ID", 21 | serialize=False, 22 | auto_created=True, 23 | primary_key=True, 24 | ), 25 | ), 26 | ("filename", models.TextField()), 27 | ( 28 | "job", 29 | models.ForeignKey(to="wooey.WooeyJob", on_delete=models.CASCADE), 30 | ), 31 | ( 32 | "parameter", 33 | models.ForeignKey( 34 | blank=True, 35 | to="wooey.ScriptParameters", 36 | null=True, 37 | on_delete=models.CASCADE, 38 | ), 39 | ), 40 | ], 41 | ), 42 | migrations.AddField( 43 | model_name="userfile", 44 | name="system_file", 45 | field=models.ForeignKey(to="wooey.WooeyFile", on_delete=models.CASCADE), 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /wooey/migrations/0020_userfile_finalize.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0019_userfile_data"), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="wooeyfile", 16 | name="job", 17 | ), 18 | migrations.RemoveField( 19 | model_name="wooeyfile", 20 | name="parameter", 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /wooey/migrations/0021_fix_verbose_names.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-03-25 02:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0020_userfile_finalize"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name="favorite", 17 | options={"verbose_name": "favorite", "verbose_name_plural": "favorites"}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name="script", 21 | options={"verbose_name": "script", "verbose_name_plural": "scripts"}, 22 | ), 23 | migrations.AlterModelOptions( 24 | name="scriptgroup", 25 | options={ 26 | "verbose_name": "script group", 27 | "verbose_name_plural": "script groups", 28 | }, 29 | ), 30 | migrations.AlterModelOptions( 31 | name="scriptparameter", 32 | options={ 33 | "verbose_name": "script parameter", 34 | "verbose_name_plural": "script parameters", 35 | }, 36 | ), 37 | migrations.AlterModelOptions( 38 | name="scriptparametergroup", 39 | options={ 40 | "verbose_name": "script parameter group", 41 | "verbose_name_plural": "script parameter groups", 42 | }, 43 | ), 44 | migrations.AlterModelOptions( 45 | name="scriptparameters", 46 | options={"verbose_name": "script parameters"}, 47 | ), 48 | migrations.AlterModelOptions( 49 | name="scriptversion", 50 | options={ 51 | "verbose_name": "script version", 52 | "verbose_name_plural": "script versions", 53 | }, 54 | ), 55 | migrations.AlterModelOptions( 56 | name="wooeyfile", 57 | options={ 58 | "verbose_name": "wooey file", 59 | "verbose_name_plural": "wooey files", 60 | }, 61 | ), 62 | migrations.AlterModelOptions( 63 | name="wooeyjob", 64 | options={"verbose_name": "wooey job", "verbose_name_plural": "wooey jobs"}, 65 | ), 66 | ] 67 | -------------------------------------------------------------------------------- /wooey/migrations/0022_add_collapse_arguments.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2016-05-08 12:23 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0021_fix_verbose_names"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="scriptparameter", 17 | name="collapse_arguments", 18 | field=models.BooleanField( 19 | default=True, 20 | help_text="Collapse separate inputs to a given argument to a single input (ie: --arg 1 --arg 2 becomes --arg 1 2)", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /wooey/migrations/0023_script_parameter_m2m.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-02 14:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wooey", "0022_add_collapse_arguments"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="scriptparameter", 18 | name="script_versions", 19 | field=models.ManyToManyField( 20 | related_name="scriptparameters", to="wooey.ScriptVersion" 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="scriptparameter", 25 | name="script_version", 26 | field=models.ForeignKey( 27 | null=True, 28 | on_delete=django.db.models.deletion.CASCADE, 29 | related_name="scriptparameter", 30 | to="wooey.ScriptVersion", 31 | ), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /wooey/migrations/0024_script_parameter_m2m_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-02 14:26 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def update_script_versions(apps, schema_editor): 9 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 10 | 11 | for script_parameter in ScriptParameter.objects.all(): 12 | script_parameter.script_versions.add(script_parameter.script_version) 13 | 14 | 15 | class Migration(migrations.Migration): 16 | 17 | dependencies = [ 18 | ("wooey", "0023_script_parameter_m2m"), 19 | ] 20 | 21 | operations = [ 22 | migrations.RunPython(update_script_versions), 23 | ] 24 | -------------------------------------------------------------------------------- /wooey/migrations/0025_remove_script_parameter_sv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-02 14:27 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0024_script_parameter_m2m_data"), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name="scriptparameter", 17 | name="script_version", 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /wooey/migrations/0026_rename_script_parameter_sv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-02 14:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0025_remove_script_parameter_sv"), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name="scriptparameter", 17 | old_name="script_versions", 18 | new_name="script_version", 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wooey/migrations/0027_parameter_order.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.7 on 2016-07-03 09:48 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0026_rename_script_parameter_sv"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="scriptparameter", 17 | name="param_order", 18 | field=models.SmallIntegerField( 19 | default=0, verbose_name="The order the parameter appears to the user." 20 | ), 21 | ), 22 | migrations.AlterField( 23 | model_name="scriptparameter", 24 | name="script_version", 25 | field=models.ManyToManyField(to="wooey.ScriptVersion"), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /wooey/migrations/0028_add_script_subparser.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2017-04-25 09:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wooey", "0027_parameter_order"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="ScriptParser", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("name", models.CharField(blank=True, max_length=255, default="")), 29 | ( 30 | "script_version", 31 | models.ForeignKey( 32 | on_delete=django.db.models.deletion.CASCADE, 33 | to="wooey.ScriptVersion", 34 | ), 35 | ), 36 | ], 37 | ), 38 | migrations.AddField( 39 | model_name="scriptparameter", 40 | name="parser", 41 | field=models.ForeignKey( 42 | null=True, 43 | on_delete=django.db.models.deletion.CASCADE, 44 | to="wooey.ScriptParser", 45 | ), 46 | preserve_default=False, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /wooey/migrations/0028_make-subparser-nonnullable.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-15 13:56 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wooey", "0028_update_script_subparser_data"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="scriptparameter", 18 | name="parser", 19 | field=models.ForeignKey( 20 | on_delete=django.db.models.deletion.CASCADE, to="wooey.ScriptParser" 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /wooey/migrations/0028_update_script_subparser_data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.4 on 2017-04-25 09:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def createParsers(apps, schema_editor): 9 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 10 | ScriptParser = apps.get_model("wooey", "ScriptParser") 11 | 12 | for param in ScriptParameter.objects.all(): 13 | script_version = param.script_version.last() 14 | parser = list( 15 | ScriptParser.objects.filter(script_version__script=script_version.script) 16 | ) 17 | if parser: 18 | parser = parser[-1] 19 | else: 20 | parser = ScriptParser( 21 | script_version=script_version, 22 | name="", 23 | ) 24 | parser.save() 25 | param.parser = parser 26 | param.save() 27 | 28 | 29 | class Migration(migrations.Migration): 30 | 31 | dependencies = [ 32 | ("wooey", "0028_add_script_subparser"), 33 | ] 34 | 35 | operations = [ 36 | migrations.RunPython(createParsers), 37 | ] 38 | -------------------------------------------------------------------------------- /wooey/migrations/0029_add-m2m-sv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-01-15 15:43 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0028_make-subparser-nonnullable"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="scriptparametergroup", 17 | name="new_script_version", 18 | field=models.ManyToManyField( 19 | related_name="new_script_version_group", to="wooey.ScriptVersion" 20 | ), 21 | ), 22 | migrations.AddField( 23 | model_name="scriptparser", 24 | name="new_script_version", 25 | field=models.ManyToManyField( 26 | related_name="new_script_version", to="wooey.ScriptVersion" 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /wooey/migrations/0030_populate-sv-m2m.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-01-15 15:43 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def populate_m2m(apps, schema_editor): 9 | ScriptParser = apps.get_model("wooey", "ScriptParser") 10 | ScriptParameterGroup = apps.get_model("wooey", "ScriptParameterGroup") 11 | for obj in ScriptParser.objects.all(): 12 | obj.new_script_version.add(obj.script_version) 13 | 14 | for obj in ScriptParameterGroup.objects.all(): 15 | obj.new_script_version.add(obj.script_version) 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ("wooey", "0029_add-m2m-sv"), 22 | ] 23 | 24 | operations = [migrations.RunPython(populate_m2m)] 25 | -------------------------------------------------------------------------------- /wooey/migrations/0031_remove-sv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-01-15 15:49 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0030_populate-sv-m2m"), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name="scriptparametergroup", 17 | name="script_version", 18 | ), 19 | migrations.RemoveField( 20 | model_name="scriptparser", 21 | name="script_version", 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /wooey/migrations/0032_rename-new-sv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-01-15 15:49 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0031_remove-sv"), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name="scriptparametergroup", 17 | old_name="new_script_version", 18 | new_name="script_version", 19 | ), 20 | migrations.RenameField( 21 | model_name="scriptparser", 22 | old_name="new_script_version", 23 | new_name="script_version", 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /wooey/migrations/0033_add-script-checksum.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-02-18 09:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0032_rename-new-sv"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="scriptversion", 17 | name="checksum", 18 | field=models.CharField(blank=True, max_length=40), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /wooey/migrations/0034_update-checksums.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-02-18 16:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | def update_checksums(apps, schema_editor): 9 | ScriptVersion = apps.get_model("wooey", "ScriptVersion") 10 | from wooey.backend import utils 11 | 12 | for obj in ScriptVersion.objects.all(): 13 | checksum = utils.get_checksum(buff=obj.script_path.read()) 14 | obj.checksum = checksum 15 | obj.save() 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ("wooey", "0033_add-script-checksum"), 22 | ] 23 | 24 | operations = [migrations.RunPython(update_checksums)] 25 | -------------------------------------------------------------------------------- /wooey/migrations/0035_populate-jsonfield.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.13 on 2018-03-04 23:14 3 | from __future__ import unicode_literals 4 | import json 5 | 6 | from django.db import migrations 7 | 8 | 9 | def populate_default(apps, schema_editor): 10 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 11 | for obj in ScriptParameter.objects.all(): 12 | try: 13 | obj.default = json.loads(obj.default) 14 | except Exception: 15 | obj.default = obj.default 16 | obj.save() 17 | 18 | 19 | def reverse_populate_default(apps, schema_editor): 20 | pass 21 | 22 | 23 | class Migration(migrations.Migration): 24 | 25 | dependencies = [ 26 | ("wooey", "0034_update-checksums"), 27 | ] 28 | 29 | operations = [migrations.RunPython(populate_default, reverse_populate_default)] 30 | -------------------------------------------------------------------------------- /wooey/migrations/0036_make_scriptparameter_jsonfield.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.11 on 2018-03-28 09:48 3 | from __future__ import unicode_literals 4 | from functools import partial 5 | 6 | from django.db import migrations, models 7 | 8 | try: 9 | from jsonfield.fields import JSONCharField as JSONField 10 | 11 | JSONField = partial(JSONField, max_length=255) 12 | except ImportError: 13 | JSONField = models.JSONField 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ("wooey", "0035_populate-jsonfield"), 20 | ] 21 | 22 | operations = [ 23 | migrations.AlterField( 24 | model_name="scriptparameter", 25 | name="default", 26 | field=JSONField(blank=True, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /wooey/migrations/0037_repeat_migration_spg_sp_script_version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.11 on 2018-03-28 09:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0036_make_scriptparameter_jsonfield"), 12 | ] 13 | 14 | operations = [ 15 | # These migrations were covered by 0029->0032 but for some reason Django wants to 16 | # repeat them. This should be a pointless change. 17 | migrations.AlterField( 18 | model_name="scriptparametergroup", 19 | name="script_version", 20 | field=models.ManyToManyField(to="wooey.ScriptVersion"), 21 | ), 22 | migrations.AlterField( 23 | model_name="scriptparser", 24 | name="script_version", 25 | field=models.ManyToManyField(to="wooey.ScriptVersion"), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /wooey/migrations/0038_wooeyjob-choices.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.12 on 2018-04-08 20:12 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0037_repeat_migration_spg_sp_script_version"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="wooeyjob", 17 | name="status", 18 | field=models.CharField( 19 | choices=[ 20 | ("completed", "Completed"), 21 | ("deleted", "Deleted"), 22 | ("FAILURE", "Failed"), 23 | ("running", "Running"), 24 | ("submitted", "Submitted"), 25 | ], 26 | default="submitted", 27 | max_length=255, 28 | ), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /wooey/migrations/0039_wooey_widgets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.12 on 2017-03-05 16:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wooey", "0038_wooeyjob-choices"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="WooeyWidget", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("name", models.CharField(max_length=50, verbose_name="Widget Name")), 29 | ( 30 | "input_attributes", 31 | models.TextField( 32 | blank=True, 33 | help_text='Extra attributes to the input field. The extra attributes MUST be specified like key="value".', 34 | null=True, 35 | verbose_name="Input Widget Extra Attributes", 36 | ), 37 | ), 38 | ( 39 | "input_class", 40 | models.CharField( 41 | blank=True, 42 | help_text="The class name(s) for the input field.", 43 | max_length=255, 44 | null=True, 45 | verbose_name="Input Widget Class name(s)", 46 | ), 47 | ), 48 | ( 49 | "input_properties", 50 | models.CharField( 51 | blank=True, 52 | help_text="Additional properties to append to the input field.", 53 | max_length=255, 54 | null=True, 55 | verbose_name="Input Widget Extra Properties", 56 | ), 57 | ), 58 | ], 59 | ), 60 | migrations.AlterField( 61 | model_name="scriptparameter", 62 | name="param_order", 63 | field=models.SmallIntegerField( 64 | default=0, help_text="The order the parameter appears to the user." 65 | ), 66 | ), 67 | migrations.AddField( 68 | model_name="scriptparameter", 69 | name="custom_widget", 70 | field=models.ForeignKey( 71 | blank=True, 72 | null=True, 73 | on_delete=django.db.models.deletion.SET_NULL, 74 | to="wooey.WooeyWidget", 75 | ), 76 | ), 77 | ] 78 | -------------------------------------------------------------------------------- /wooey/migrations/0040_add-widget-class.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2018-06-23 20:41 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("wooey", "0039_wooey_widgets"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="wooeywidget", 17 | name="widget_class", 18 | field=models.CharField( 19 | blank=True, 20 | help_text="Widget class to use (e.g. django.forms.TextInput, defaults to Form Field on Script Parameter model if blank).", 21 | max_length=50, 22 | null=True, 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="scriptparameter", 27 | name="input_type", 28 | field=models.CharField( 29 | help_text="The python type expected by the script (e.g. boolean, integer, file).", 30 | max_length=255, 31 | ), 32 | ), 33 | migrations.AlterField( 34 | model_name="wooeywidget", 35 | name="input_attributes", 36 | field=models.TextField( 37 | blank=True, 38 | help_text='Extra attributes to the input field. The extra attributes MUST be specified like key="value" (e.g. type="date").', 39 | null=True, 40 | verbose_name="Input Widget Extra Attributes", 41 | ), 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /wooey/migrations/0041_change-script-param-to-textfield.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2019-06-09 08:15 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("wooey", "0040_add-widget-class"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="scriptparameter", 18 | name="parser", 19 | field=models.ForeignKey( 20 | on_delete=django.db.models.deletion.PROTECT, to="wooey.ScriptParser" 21 | ), 22 | ), 23 | migrations.AlterField( 24 | model_name="scriptparameter", 25 | name="script_param", 26 | field=models.TextField(), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /wooey/migrations/0042_allow_deletion_of_scripts.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.15 on 2020-04-12 13:50 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0041_change-script-param-to-textfield"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="scriptversion", 16 | name="script", 17 | field=models.ForeignKey( 18 | on_delete=django.db.models.deletion.CASCADE, 19 | related_name="script_version", 20 | to="wooey.Script", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /wooey/migrations/0043_update_model_protection.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-12 19:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0042_allow_deletion_of_scripts"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="script", 16 | name="script_group", 17 | field=models.ForeignKey( 18 | blank=True, 19 | null=True, 20 | on_delete=django.db.models.deletion.CASCADE, 21 | to="wooey.ScriptGroup", 22 | ), 23 | ), 24 | migrations.AlterField( 25 | model_name="scriptparameter", 26 | name="parameter_group", 27 | field=models.ForeignKey( 28 | on_delete=django.db.models.deletion.CASCADE, 29 | to="wooey.ScriptParameterGroup", 30 | ), 31 | ), 32 | migrations.AlterField( 33 | model_name="scriptparameter", 34 | name="parser", 35 | field=models.ForeignKey( 36 | on_delete=django.db.models.deletion.CASCADE, to="wooey.ScriptParser" 37 | ), 38 | ), 39 | migrations.AlterField( 40 | model_name="scriptparameters", 41 | name="parameter", 42 | field=models.ForeignKey( 43 | on_delete=django.db.models.deletion.CASCADE, to="wooey.ScriptParameter" 44 | ), 45 | ), 46 | migrations.AlterField( 47 | model_name="wooeyjob", 48 | name="script_version", 49 | field=models.ForeignKey( 50 | on_delete=django.db.models.deletion.CASCADE, to="wooey.ScriptVersion" 51 | ), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /wooey/migrations/0044_change_script_parameter_choices_to_text.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.6 on 2022-07-15 09:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("wooey", "0043_update_model_protection"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="scriptparameter", 15 | name="choices", 16 | field=models.TextField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wooey/migrations/0045_add_created_modified_by.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.15 on 2022-08-03 22:24 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 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("wooey", "0044_change_script_parameter_choices_to_text"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="scriptversion", 18 | name="created_by", 19 | field=models.ForeignKey( 20 | blank=True, 21 | null=True, 22 | on_delete=django.db.models.deletion.SET_NULL, 23 | related_name="created_script_version_set", 24 | to=settings.AUTH_USER_MODEL, 25 | ), 26 | ), 27 | migrations.AddField( 28 | model_name="scriptversion", 29 | name="modified_by", 30 | field=models.ForeignKey( 31 | blank=True, 32 | null=True, 33 | on_delete=django.db.models.deletion.SET_NULL, 34 | related_name="modified_script_version_set", 35 | to=settings.AUTH_USER_MODEL, 36 | ), 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /wooey/migrations/0046_add_native_json_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2023-06-02 12:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("wooey", "0045_add_created_modified_by"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="scriptparameter", 15 | name="new_default", 16 | field=models.JSONField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wooey/migrations/0047_migrate_default_field_data.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2023-06-02 12:48 2 | 3 | from django.db import migrations 4 | 5 | 6 | def forwards_func(apps, schema_editor): 7 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 8 | for parameter in ScriptParameter.objects.all(): 9 | parameter.new_default = parameter.default 10 | 11 | 12 | def reverse_func(apps, schema_editor): 13 | ScriptParameter = apps.get_model("wooey", "ScriptParameter") 14 | for parameter in ScriptParameter.objects.all(): 15 | parameter.default = parameter.new_default 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ("wooey", "0046_add_native_json_field"), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunPython(forwards_func, reverse_func), 26 | ] 27 | -------------------------------------------------------------------------------- /wooey/migrations/0048_remove_default_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2023-06-02 12:51 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("wooey", "0047_migrate_default_field_data"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="scriptparameter", 15 | name="default", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /wooey/migrations/0049_rename_new_default_to_default_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.14 on 2023-06-02 12:51 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("wooey", "0048_remove_default_field"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name="scriptparameter", 15 | old_name="new_default", 16 | new_name="default", 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /wooey/migrations/0050_add_api_keys.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.19 on 2023-06-05 12:17 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 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("wooey", "0049_rename_new_default_to_default_field"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="WooeyProfile", 18 | fields=[ 19 | ( 20 | "id", 21 | models.BigAutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ( 29 | "user", 30 | models.OneToOneField( 31 | on_delete=django.db.models.deletion.CASCADE, 32 | to=settings.AUTH_USER_MODEL, 33 | ), 34 | ), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name="APIKey", 39 | fields=[ 40 | ( 41 | "id", 42 | models.BigAutoField( 43 | auto_created=True, 44 | primary_key=True, 45 | serialize=False, 46 | verbose_name="ID", 47 | ), 48 | ), 49 | ("name", models.TextField()), 50 | ("active", models.BooleanField(default=True)), 51 | ("key", models.TextField()), 52 | ("created_date", models.DateTimeField(auto_now_add=True)), 53 | ("last_used", models.DateTimeField(blank=True, null=True)), 54 | ( 55 | "profile", 56 | models.ForeignKey( 57 | on_delete=django.db.models.deletion.CASCADE, 58 | to="wooey.wooeyprofile", 59 | ), 60 | ), 61 | ], 62 | ), 63 | ] 64 | -------------------------------------------------------------------------------- /wooey/migrations/0051_add_virtual_env.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2023-11-22 02:05 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("wooey", "0050_add_api_keys"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="VirtualEnvironment", 16 | fields=[ 17 | ( 18 | "id", 19 | models.BigAutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("name", models.CharField(max_length=25)), 27 | ("python_binary", models.CharField(max_length=1024)), 28 | ("requirements", models.TextField(null=True, blank=True)), 29 | ("venv_directory", models.CharField(max_length=1024)), 30 | ], 31 | options={ 32 | "verbose_name": "virtual environment", 33 | "verbose_name_plural": "virtual environments", 34 | }, 35 | ), 36 | migrations.AddField( 37 | model_name="script", 38 | name="virtual_environment", 39 | field=models.ForeignKey( 40 | null=True, 41 | on_delete=django.db.models.deletion.SET_NULL, 42 | to="wooey.virtualenvironment", 43 | ), 44 | ), 45 | ] 46 | -------------------------------------------------------------------------------- /wooey/migrations/0052_add_ignore_bad_imports_to_script_model.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.23 on 2023-11-22 22:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("wooey", "0051_add_virtual_env"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="script", 15 | name="ignore_bad_imports", 16 | field=models.BooleanField( 17 | default=False, 18 | help_text="Ignore bad imports when adding scripts. This is useful if a script is under a virtual environment.", 19 | ), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /wooey/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/migrations/__init__.py -------------------------------------------------------------------------------- /wooey/models/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | from .core import * # noqa: F401, F403 3 | from .favorite import * # noqa: F401, F403 4 | from .profile import * # noqa: F401, F403 5 | from .widgets import * # noqa: F401, F403 6 | -------------------------------------------------------------------------------- /wooey/models/favorite.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.utils.translation import gettext_lazy as _ 6 | 7 | 8 | class Favorite(models.Model): 9 | 10 | user = models.ForeignKey( 11 | settings.AUTH_USER_MODEL, 12 | related_name="favorites", 13 | blank=True, 14 | null=True, 15 | on_delete=models.CASCADE, 16 | ) 17 | 18 | content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) 19 | object_id = models.PositiveIntegerField() 20 | content_object = GenericForeignKey("content_type", "object_id") 21 | 22 | created_date = models.DateTimeField(auto_now_add=True) 23 | 24 | class Meta: 25 | unique_together = ( 26 | "user", 27 | "content_type", 28 | "object_id", 29 | ) 30 | app_label = "wooey" 31 | verbose_name = _("favorite") 32 | verbose_name_plural = _("favorites") 33 | -------------------------------------------------------------------------------- /wooey/models/fields.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | __author__ = "chris" 4 | from django.db import models 5 | from ..forms import fields as wooey_form_fields 6 | 7 | 8 | class WooeyOutputFileField(models.FileField): 9 | def formfield(self, **kwargs): 10 | # TODO: Make this from an app that is plugged in 11 | defaults = {"form_class": wooey_form_fields.WooeyOutputFileField} 12 | defaults.update(kwargs) 13 | return super(WooeyOutputFileField, self).formfield(**defaults) 14 | 15 | 16 | class WooeyUploadFileField(models.FileField): 17 | def formfield(self, **kwargs): 18 | # TODO: Make this from an app that is plugged in 19 | defaults = {"form_class": wooey_form_fields.WooeyUploadFileField} 20 | defaults.update(kwargs) 21 | return super(WooeyUploadFileField, self).formfield(**defaults) 22 | -------------------------------------------------------------------------------- /wooey/models/profile.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | from django.utils import timezone 4 | 5 | from ..utils import generate_hash, get_api_key 6 | 7 | 8 | __all__ = ["WooeyProfile", "APIKey"] 9 | 10 | 11 | class WooeyProfile(models.Model): 12 | user = models.OneToOneField( 13 | settings.AUTH_USER_MODEL, 14 | on_delete=models.CASCADE, 15 | ) 16 | 17 | 18 | class APIKey(models.Model): 19 | profile = models.ForeignKey("WooeyProfile", on_delete=models.CASCADE, db_index=True) 20 | name = models.TextField() 21 | active = models.BooleanField(default=True) 22 | key = models.TextField() 23 | created_date = models.DateTimeField(auto_now_add=True) 24 | last_used = models.DateTimeField(null=True, blank=True) 25 | 26 | @staticmethod 27 | def get_user_by_key(key): 28 | hashed_key = generate_hash(key) 29 | try: 30 | api_key = APIKey.objects.get( 31 | active=True, key=hashed_key, profile__user__is_active=True 32 | ) 33 | except APIKey.DoesNotExist: 34 | return None 35 | 36 | api_key.last_used = timezone.now() 37 | api_key.save() 38 | 39 | return api_key.profile.user 40 | 41 | def save(self, *args, **kwargs): 42 | if not self.key: 43 | api_key, hashed_key = get_api_key() 44 | self.key = hashed_key 45 | self._api_key = api_key 46 | return super().save(*args, **kwargs) 47 | -------------------------------------------------------------------------------- /wooey/models/widgets.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django.db import models 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | from ..backend import utils 6 | 7 | 8 | class WooeyWidget(models.Model): 9 | name = models.CharField(_("Widget Name"), max_length=50) 10 | widget_class = models.CharField( 11 | max_length=50, 12 | help_text=_( 13 | "Widget class to use (e.g. django.forms.TextInput, defaults to Form Field on Script Parameter model if blank)." 14 | ), 15 | null=True, 16 | blank=True, 17 | ) 18 | input_attributes = models.TextField( 19 | verbose_name=_("Input Widget Extra Attributes"), 20 | help_text=_( 21 | 'Extra attributes to the input field. The extra attributes MUST be specified like key="value" (e.g. type="date").' 22 | ), 23 | null=True, 24 | blank=True, 25 | ) 26 | input_class = models.CharField( 27 | max_length=255, 28 | verbose_name=_("Input Widget Class name(s)"), 29 | help_text=_("The class name(s) for the input field."), 30 | null=True, 31 | blank=True, 32 | ) 33 | input_properties = models.CharField( 34 | max_length=255, 35 | verbose_name=_("Input Widget Extra Properties"), 36 | help_text=_("Additional properties to append to the input field."), 37 | null=True, 38 | blank=True, 39 | ) 40 | 41 | @property 42 | def widget_attributes(self): 43 | attrs = {} 44 | 45 | properties = self.input_properties 46 | if properties is not None: 47 | for attr in properties.split(" "): 48 | attrs[attr] = True 49 | 50 | attributes = self.input_attributes 51 | if attributes is not None: 52 | for key, value in utils.tokenize_html_attributes(attributes): 53 | attrs[key] = value 54 | 55 | if self.input_class: 56 | attrs["class"] = self.input_class 57 | 58 | return attrs 59 | 60 | def __str__(self): 61 | return self.name 62 | -------------------------------------------------------------------------------- /wooey/settings.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | 3 | import os 4 | import tempfile 5 | 6 | from django.conf import settings 7 | from django.utils.translation import gettext_lazy as _ 8 | 9 | from celery import app 10 | 11 | celery_app = app.app_or_default() 12 | 13 | 14 | def get(key, default): 15 | return getattr(settings, key, default) 16 | 17 | 18 | IS_WINDOWS = os.name == "nt" 19 | 20 | # AUTH based settings 21 | WOOEY_ALLOW_ANONYMOUS = get("WOOEY_ALLOW_ANONYMOUS", True) 22 | WOOEY_AUTH = get("WOOEY_AUTH", True) 23 | WOOEY_LOGIN_URL = get("WOOEY_LOGIN_URL", settings.LOGIN_URL) 24 | WOOEY_REGISTER_URL = get("WOOEY_REGISTER_URL", "/accounts/register/") 25 | WOOEY_ENABLE_API_KEYS = get("WOOEY_ENABLE_API_KEYS", False) 26 | 27 | # Celery and job queue settings 28 | WOOEY_CELERY = get("WOOEY_CELERY", True) 29 | WOOEY_CELERY_TASKS = get("WOOEY_CELERY_TASKS", "wooey.tasks") 30 | WOOEY_CELERY_STOPPABLE_JOBS = "amqp" in str( 31 | celery_app.conf.get("CELERY_BROKER_URL", celery_app.conf.get("broker_url") or "") 32 | ) 33 | 34 | # Site setup settings 35 | WOOEY_DEFAULT_SCRIPT_GROUP = get("WOOEY_DEFAULT_SCRIPT_GROUP", _("Scripts")) 36 | WOOEY_EPHEMERAL_FILES = get("WOOEY_EPHEMERAL_FILES", False) 37 | WOOEY_FILE_DIR = get("WOOEY_FILE_DIR", "wooey_files") 38 | WOOEY_JOB_EXPIRATION = get("WOOEY_JOB_EXPIRATION", {"anonymous": None, "users": None}) 39 | WOOEY_REALTIME_CACHE = get("WOOEY_REALTIME_CACHE", None) 40 | WOOEY_SCRIPT_DIR = get("WOOEY_SCRIPT_DIR", "wooey_scripts") 41 | 42 | # User interface settings 43 | WOOEY_SHOW_LOCKED_SCRIPTS = get("WOOEY_SHOW_LOCKED_SCRIPTS", True) 44 | WOOEY_SITE_NAME = get("WOOEY_SITE_NAME", _("Wooey!")) 45 | WOOEY_SITE_TAG = get("WOOEY_SITE_TAG", _("A web UI for Python scripts")) 46 | 47 | # Virtual Environment Settings 48 | WOOEY_VIRTUAL_ENVIRONMENT_DIRECTORY = get( 49 | "WOOEY_VIRTUAL_ENVIRONMENT_DIRECTORY", tempfile.gettempdir() 50 | ) 51 | -------------------------------------------------------------------------------- /wooey/static/wooey/css/profile.css: -------------------------------------------------------------------------------- 1 | .profile-contents, 2 | .profile-contents > * { 3 | background: white; 4 | } 5 | 6 | #api-key-table tr.deleted { 7 | background: grey; 8 | text-decoration: line-through; 9 | } 10 | -------------------------------------------------------------------------------- /wooey/static/wooey/css/simple-sidebar.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Start Bootstrap - Simple Sidebar HTML Template (http://startbootstrap.com) 3 | * Code licensed under the Apache License v2.0. 4 | * For details, see http://www.apache.org/licenses/LICENSE-2.0. 5 | */ 6 | 7 | #wrapper { 8 | padding-left: 0; 9 | -webkit-transition: all 0.5s ease; 10 | -moz-transition: all 0.5s ease; 11 | -o-transition: all 0.5s ease; 12 | transition: all 0.5s ease; 13 | } 14 | 15 | #wrapper.toggled { 16 | padding-left: 250px; 17 | } 18 | 19 | #sidebar-wrapper { 20 | z-index: 1000; 21 | position: fixed; 22 | left: 250px; 23 | width: 0; 24 | height: 100%; 25 | margin-left: -250px; 26 | overflow-y: auto; 27 | background: #000; 28 | -webkit-transition: all 0.5s ease; 29 | -moz-transition: all 0.5s ease; 30 | -o-transition: all 0.5s ease; 31 | transition: all 0.5s ease; 32 | } 33 | 34 | #wrapper.toggled #sidebar-wrapper { 35 | width: 250px; 36 | } 37 | 38 | #page-content-wrapper { 39 | width: 100%; 40 | position: absolute; 41 | } 42 | 43 | #wrapper.toggled #page-content-wrapper { 44 | position: absolute; 45 | margin-right: -250px; 46 | } 47 | 48 | /* Sidebar Styles */ 49 | 50 | .sidebar-nav { 51 | position: absolute; 52 | top: 0; 53 | width: 250px; 54 | margin: 0; 55 | padding: 0; 56 | list-style: none; 57 | } 58 | 59 | .sidebar-nav li { 60 | text-indent: 20px; 61 | line-height: 40px; 62 | } 63 | 64 | .sidebar-nav li a { 65 | display: block; 66 | text-decoration: none; 67 | color: #999999; 68 | } 69 | 70 | .sidebar-nav li a:hover { 71 | text-decoration: none; 72 | color: #fff; 73 | background: rgba(255, 255, 255, 0.2); 74 | } 75 | 76 | .sidebar-nav li a:active, 77 | .sidebar-nav li a:focus { 78 | text-decoration: none; 79 | } 80 | 81 | @media (min-width: 768px) { 82 | #wrapper { 83 | padding-left: 0; 84 | } 85 | 86 | #sidebar-wrapper { 87 | width: 0; 88 | } 89 | 90 | #wrapper.toggled { 91 | padding-left: 250px; 92 | } 93 | 94 | #wrapper.toggled #sidebar-wrapper { 95 | width: 250px; 96 | } 97 | 98 | #page-content-wrapper { 99 | position: relative; 100 | margin-right: 0; 101 | } 102 | 103 | #wrapper.toggled #page-content-wrapper { 104 | position: relative; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /wooey/static/wooey/images/pinstripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooey/Wooey/dc457291cf3d976f5cfbc15fce71c95a733d3699/wooey/static/wooey/images/pinstripe.png -------------------------------------------------------------------------------- /wooey/static/wooey/js/admin/script.js: -------------------------------------------------------------------------------- 1 | var $ = django.jQuery; 2 | $(document).ready(function () { 3 | var default_version_selector = ".field-default_version > input"; 4 | var $default_script_versions = $(default_version_selector); 5 | $default_script_versions.click(function (event) { 6 | var $this = $(this); 7 | var checked = $this.is(":checked"); 8 | if (checked === true) { 9 | // remove all other checks 10 | $(default_version_selector).each(function (index, value) { 11 | if (value.id != $this.attr("id")) { 12 | $(value).prop("checked", false); 13 | } 14 | }); 15 | } else { 16 | // if we are the sole check, do not remove it 17 | if (!$(default_version_selector).is(":checked")) { 18 | $this.prop("checked", true); 19 | } 20 | } 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /wooey/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block left_sidebar %}{% endblock left_sidebar %} 6 | {% block center_content_class %}col-md-12 col-xs-12{% endblock center_content_class %} 7 | {% block center_content %} 8 |
404
9 | {% endblock center_content %} 10 | -------------------------------------------------------------------------------- /wooey/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block left_sidebar %}{% endblock left_sidebar %} 6 | {% block center_content_class %}col-md-12 col-xs-12{% endblock center_content_class %} 7 | {% block center_content %} 8 |
500
9 | {% endblock center_content %} 10 | -------------------------------------------------------------------------------- /wooey/templates/wooey/home.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block extra_style %} 6 | {{ block.super }} 7 | .wooey-script-dropdown { 8 | display: inline-block; 9 | } 10 | 11 | {% endblock extra_style %} 12 | 13 | {% block outer_content %} 14 | {{ block.super }} 15 | {% if not request.user.is_authenticated %} 16 |
17 |

{% get_wooey_setting "WOOEY_SITE_NAME" %}

18 |

{% get_wooey_setting "WOOEY_SITE_TAG" %}

19 |
20 | {% endif %} 21 | {% endblock %} 22 | 23 | {% block left_sidebar %}{% endblock left_sidebar %} 24 | {% block center_content_class %}col-md-12 col-xs-12{% endblock center_content_class %} 25 | 26 | {% block center_content %} 27 | {{ block.super }} 28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 |
39 | 40 | {% for script in scripts %} 41 | {% include 'wooey/scripts/script_panel.html' %} 42 | {% endfor %} 43 | 44 |
45 | 46 | 47 | 48 | {% endblock center_content %} 49 | 50 | {% block inline_js %} 51 | {{ block.super }} 52 | 78 | {% endblock inline_js %} 79 | -------------------------------------------------------------------------------- /wooey/templates/wooey/jobs/results/table_row.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load wooey_tags %} 3 | 4 | 5 | {% if file.filetype == 'images' %} 6 | {% elif file.filetype == 'fasta' %} 7 | {% elif file.filetype == 'tabular' %} 8 | {% else %}{% endif %} 9 | {{ file.basename }} 10 | 11 | {% if file.slug %}{{ file.slug }}{% endif %} 12 | 13 | 14 | {% if file.size_bytes %}{{ file.size_bytes|filesizeformat|numericalign }}{% endif %} 15 | 16 | -------------------------------------------------------------------------------- /wooey/templates/wooey/modals/base_modal.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 19 | -------------------------------------------------------------------------------- /wooey/templates/wooey/modals/permalink_modal.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/modals/base_modal.html" %} 2 | {% load i18n %} 3 | {% block modal_id %}modal-permalink{% endblock %} 4 | {% block modal_title %}{% trans "Permanant link to these results" %}{% endblock modal_title %} 5 | {% block modal_body %} 6 |

{% trans "You can share the following link to give open access to your results:" %}

7 |

{{ permalink }}

8 |

{% trans "Anyone with this link will be able to view your data — only share with people you trust!" %}

10 | {% endblock modal_body %} 11 | -------------------------------------------------------------------------------- /wooey/templates/wooey/modals/resubmit_modal.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/modals/base_modal.html" %} 2 | {% load i18n %} 3 | {% block modal_id %}modal-resubmit{% endblock %} 4 | {% block modal_title %}{% trans "Resubmission Complete" %}{% endblock modal_title %} 5 | {% block modal_body %}{% trans "Your job has been successfully resubmitted." %}{% endblock modal_body %} 6 | {% block modal_buttons %} 7 | 8 | {% endblock modal_buttons %} 9 | -------------------------------------------------------------------------------- /wooey/templates/wooey/preview/base.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load wooey_tags %} 3 |
4 |
5 |
{{ output_file_content.basename }} 6 | 7 | 8 | 9 |
10 | {% block preview_content %}{% endblock %} 11 | {% if show_preview_metadata %} 12 | {% with output_file_content.object as file %} 13 | {% url 'wooey:celery_results' job_id=file.job.id as result_url %} 14 | {% url 'wooey:celery_results' job_id=file.job.id as celery_results_url %} 15 | 22 | 23 | {% endwith %} 24 | {% endif %} 25 |
26 |
27 | -------------------------------------------------------------------------------- /wooey/templates/wooey/preview/fasta.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/preview/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block preview_content %} 6 |
7 | {% for entry in output_file_content.preview %} 8 | {{ entry }}
9 | {% endfor %} 10 |
11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /wooey/templates/wooey/preview/image.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/preview/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block preview_content %} 6 |
7 | {{ output_file_content.name }} 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /wooey/templates/wooey/preview/tabular.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/preview/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block preview_content %} 6 |
7 | {% with output_file_content.preview as table %} 8 | 9 | {% with table.0|length as colspan %} 10 | {% for row in table %} 11 | {% if forloop.first %} 12 | 13 | {% for item in row %} 14 | 15 | {% endfor %} 16 | 17 | 18 | {% else %} 19 | 20 | {% if row == None %} 21 | 22 | {% else %} 23 | 24 | {% for item in row %} 25 | 26 | {% endfor %} 27 | 28 | {% endif %} 29 | 30 | {% endif %} 31 | {% if forloop.last %} 32 | 33 | {% endif %} 34 | {% endfor %} 35 | {% endwith %} 36 |
{{ item }}
{{ item }}
37 | {% endwith %} 38 |
39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /wooey/templates/wooey/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% block center_content %} 4 | {% if form.non_field_errors %} 5 | 6 | {% endif %} 7 |
8 | {% csrf_token %} 9 |
10 | 11 |
12 | 13 | {% if 'username' in form.errors %} 14 | 15 | {% endif %} 16 |
17 |
18 |
19 | 20 |
21 | 22 | {% if 'password' in form.errors %} 23 | 24 | {% endif %} 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 | {% endblock center_content %} 34 | -------------------------------------------------------------------------------- /wooey/templates/wooey/registration/login_header.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load wooey_tags %} 3 | 19 | -------------------------------------------------------------------------------- /wooey/templates/wooey/registration/register.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% block center_content %} 4 | {% if form.non_field_errors %} 5 | 6 | {% endif %} 7 |
8 | {% csrf_token %} 9 |
10 | 11 |
12 | 13 | {% if 'username' in form.errors %} 14 | 15 | {% endif %} 16 |
17 |
18 |
19 | 20 |
21 | 22 | {% if 'email' in form.errors %} 23 | 24 | {% endif %} 25 |
26 |
27 |
28 | 29 |
30 | 31 | {% if 'password' in form.errors %} 32 | 33 | {% endif %} 34 |
35 |
36 |
37 | 38 |
39 | 40 | {% if 'password2' in form.errors %} 41 | 42 | {% endif %} 43 |
44 |
45 | 46 |
47 |
48 | 49 |
50 |
51 |
52 | {% endblock center_content %} 53 | -------------------------------------------------------------------------------- /wooey/templates/wooey/scrapbook.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block extra_js %} 6 | {{ block.super }} 7 | 8 | {% endblock extra_js %} 9 | {% block extra_css %} 10 | {{ block.super }} 11 | 12 | {% endblock extra_css %} 13 | {% block extra_style %} 14 | {{ block.super }} 15 | #wooey-task-submit { 16 | float: left; 17 | margin-left: 3px; 18 | } 19 | {% endblock extra_style %} 20 | {% block left_sidebar %}{% endblock left_sidebar %} 21 | {% block center_content_class %}col-md-12 col-xs-12{% endblock center_content_class %} 22 | {% block center_content %} 23 | 24 |
25 | {% with True as show_preview_metadata %} 26 | 27 | {% for output_group, output_files in file_groups.items %} 28 | {% for output_file_content in output_files %} 29 | {% include 'wooey/preview/'|concat:output_group|concat:'.html' %} 30 | {% endfor %} 31 | {% endfor %} 32 | 33 | {% endwith %} 34 | 35 |
36 | 37 | {% endblock center_content %} 38 | -------------------------------------------------------------------------------- /wooey/templates/wooey/scripts/script_panel.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load wooey_tags %} 3 | {% get_wooey_setting "WOOEY_SHOW_LOCKED_SCRIPTS" as wooey_show_locked_scripts %} 4 | {% if script.is_active or wooey_show_locked_scripts %} 5 |
6 | 7 | {% with group_show=script.script_group|valid_user:request.user %} 8 | {% if group_show != 'hide' %} 9 | 10 |
11 |
12 | 13 | {% if script.is_active and group_show == True %} 14 | {{ script.script_name }} 15 | {% with previous_versions=script.get_previous_versions %} 16 | {% if previous_versions %} 17 | 33 | {% endif %} 34 | {% endwith %} 35 | {% else %} 36 | {{ script.script_name }} 37 | 38 | {% endif %} 39 | 40 |
41 |
{{ script.script_description }}
42 |
{{ script.script_group.group_name }}
43 | 44 |
45 | {% endif %} 46 | {% endwith %} 47 |
48 | {% endif %} 49 | -------------------------------------------------------------------------------- /wooey/templates/wooey/scripts/scriptgroup_view.html: -------------------------------------------------------------------------------- 1 | {% extends "wooey/base.html" %} 2 | {% load i18n %} 3 | {% load wooey_tags %} 4 | 5 | {% block center_content %} 6 | 7 | 8 | {% endblock center_content %} 9 | -------------------------------------------------------------------------------- /wooey/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /wooey/test_urls.py: -------------------------------------------------------------------------------- 1 | from . import test_settings 2 | from django.conf import settings 3 | from django.urls import include, path 4 | from django.conf.urls.static import static 5 | from django.contrib import admin 6 | 7 | # the DEBUG setting in test_settings is not respected 8 | settings.DEBUG = True 9 | 10 | 11 | urlpatterns = [ 12 | path("admin/", admin.site.urls), 13 | path("", include("wooey.urls")), 14 | path("", include("django.contrib.auth.urls")), 15 | ] 16 | 17 | urlpatterns += static(test_settings.MEDIA_URL, document_root=test_settings.MEDIA_ROOT) 18 | urlpatterns += static(test_settings.STATIC_URL, document_root=test_settings.STATIC_ROOT) 19 | -------------------------------------------------------------------------------- /wooey/tests/.gitignore: -------------------------------------------------------------------------------- 1 | media 2 | -------------------------------------------------------------------------------- /wooey/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | import os 3 | 4 | os.environ["TESTING"] = "True" 5 | -------------------------------------------------------------------------------- /wooey/tests/config.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | import os 3 | 4 | from django.core.files import File 5 | 6 | BASE_DIR = os.path.split(__file__)[0] 7 | WOOEY_TEST_SCRIPTS = os.path.join(BASE_DIR, "scripts") 8 | WOOEY_TEST_DATA = os.path.join(BASE_DIR, "data") 9 | WOOEY_TEST_REMOTE_STORAGE_DIR = "remote_storage" 10 | WOOEY_TEST_REMOTE_STORAGE_PATH = os.path.join(BASE_DIR, "media", "remote_storage") 11 | 12 | # Because forms are input as lists by Django, all attributes here need to be 13 | # list like as well. The MultiValueDict/QueryDict get method assumes a list and 14 | # will take the first element in validation (so for a string like 'ATAT' it will 15 | # provide 'A'). 16 | SCRIPT_DATA = { 17 | "translate": { 18 | "data": { 19 | "wooey_type": ["1"], 20 | "job_name": ["abc"], 21 | "sequence": ["ATATATATATA"], 22 | "frame": ["+3"], 23 | "out": ["abc"], 24 | } 25 | }, 26 | "choices": { 27 | "data": { 28 | "wooey_type": ["1"], 29 | "job_name": ["abc"], 30 | "two_choices": [1, 2], 31 | }, 32 | "files": { 33 | "multiple_file_choices": [ 34 | File(open(os.path.join(WOOEY_TEST_SCRIPTS, "choices.py"), "rb")), 35 | File(open(os.path.join(WOOEY_TEST_SCRIPTS, "crop.py"), "rb")), 36 | ] 37 | }, 38 | }, 39 | "without_args": { 40 | "data": { 41 | "job_name": ["abc"], 42 | } 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /wooey/tests/data/delimited.tsv: -------------------------------------------------------------------------------- 1 | Header1 Header2 Header3 2 | data1 data2 data3 3 | data4 data5 data6 4 | data7 data8 data9 5 | -------------------------------------------------------------------------------- /wooey/tests/data/fasta.fasta: -------------------------------------------------------------------------------- 1 | >abc 2 | ABC 3 | >abc123 4 | ABCCCC 5 | -------------------------------------------------------------------------------- /wooey/tests/factories.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tempfile 3 | 4 | import factory 5 | from django.contrib.auth import get_user_model 6 | 7 | from ..models import ( 8 | APIKey, 9 | Script, 10 | ScriptGroup, 11 | VirtualEnvironment, 12 | WooeyJob, 13 | WooeyProfile, 14 | WooeyWidget, 15 | ) 16 | from . import utils as test_utils 17 | 18 | 19 | class ScriptGroupFactory(factory.DjangoModelFactory): 20 | class Meta: 21 | model = ScriptGroup 22 | 23 | group_name = "test group" 24 | group_description = "test desc" 25 | 26 | 27 | class ScriptParameterGroupFactory(factory.DjangoModelFactory): 28 | class Meta: 29 | model = "wooey.ScriptParameterGroup" 30 | 31 | 32 | class ScriptParameterFactory(factory.DjangoModelFactory): 33 | class Meta: 34 | model = "wooey.ScriptParameter" 35 | 36 | script_param = "script-param" 37 | is_output = False 38 | parameter_group = factory.SubFactory( 39 | "wooey.tests.factories.ScriptParameterGroupFactory" 40 | ) 41 | parser = factory.SubFactory("wooey.tests.factories.ScriptParserFactory") 42 | 43 | 44 | class ScriptParserFactory(factory.DjangoModelFactory): 45 | class Meta: 46 | model = "wooey.ScriptParser" 47 | 48 | 49 | class ScriptFactory(factory.DjangoModelFactory): 50 | class Meta: 51 | model = Script 52 | 53 | script_name = "test script" 54 | script_group = factory.SubFactory(ScriptGroupFactory) 55 | script_description = "test script desc" 56 | 57 | 58 | class UserFactory(factory.DjangoModelFactory): 59 | class Meta: 60 | model = get_user_model() 61 | django_get_or_create = ("username",) 62 | 63 | username = "user" 64 | email = "a@a.com" 65 | password = "testuser" 66 | 67 | 68 | class ProfileFactory(factory.DjangoModelFactory): 69 | class Meta: 70 | model = WooeyProfile 71 | django_get_or_create = ("user",) 72 | 73 | user = factory.SubFactory("wooey.tests.factories.UserFactory") 74 | 75 | 76 | class APIKeyFactory(factory.DjangoModelFactory): 77 | class Meta: 78 | model = APIKey 79 | 80 | name = factory.Sequence(lambda n: "api key %d" % n) 81 | profile = factory.SubFactory("wooey.tests.factories.ProfileFactory") 82 | 83 | 84 | class BaseJobFactory(factory.DjangoModelFactory): 85 | class Meta: 86 | model = WooeyJob 87 | 88 | job_name = "\xd0\xb9\xd1\x86\xd1\x83" 89 | job_description = "\xd0\xb9\xd1\x86\xd1\x83\xd0\xb5\xd0\xba\xd0\xb5" 90 | 91 | 92 | class WooeyWidgetFactory(factory.DjangoModelFactory): 93 | class Meta: 94 | model = WooeyWidget 95 | 96 | name = "test widget" 97 | 98 | 99 | class VirtualEnvFactory(factory.DjangoModelFactory): 100 | class Meta: 101 | model = VirtualEnvironment 102 | 103 | name = factory.Sequence(lambda n: "venv_%d" % n) 104 | python_binary = sys.executable 105 | venv_directory = tempfile.gettempdir() 106 | 107 | 108 | def generate_script(script_path, script_name=None, ignore_bad_imports=False): 109 | new_file = test_utils.save_script_path(script_path) 110 | from ..backend import utils 111 | 112 | res = utils.add_wooey_script( 113 | script_name=script_name, 114 | script_path=new_file, 115 | group=None, 116 | ignore_bad_imports=ignore_bad_imports, 117 | ) 118 | return res["script"] 119 | 120 | 121 | def generate_job(script_version): 122 | return BaseJobFactory(script_version=script_version) 123 | -------------------------------------------------------------------------------- /wooey/tests/scripts/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "chris" 2 | -------------------------------------------------------------------------------- /wooey/tests/scripts/choices.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | parser = argparse.ArgumentParser(description="Something") 5 | parser.add_argument("--one-choice", type=int, choices=[0, 1, 2, 3], nargs=1) 6 | parser.add_argument("--two-choices", type=int, choices=[0, 1, 2, 3], nargs=2) 7 | parser.add_argument("--at-least-one-choice", type=int, choices=[0, 1, 2, 3], nargs="+") 8 | parser.add_argument("--all-choices", type=int, choices=[0, 1, 2, 3], nargs="*") 9 | parser.add_argument( 10 | "--need-at-least-one-numbers", type=int, nargs="+", required=True, action="append" 11 | ) 12 | parser.add_argument("--choices-str", nargs="+", type=str) 13 | parser.add_argument("--multiple-file-choices", type=argparse.FileType("r"), nargs="*") 14 | parser.add_argument( 15 | "--more-multiple-file-choices", type=argparse.FileType("r"), nargs="*" 16 | ) 17 | 18 | if __name__ == "__main__": 19 | args = parser.parse_args() 20 | sys.stdout.write("{}".format(args)) 21 | -------------------------------------------------------------------------------- /wooey/tests/scripts/choices_2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | parser = argparse.ArgumentParser(description="Something") 5 | parser.add_argument("--one-choice-added", type=int, choices=[0, 1, 2, 3], nargs=1) 6 | parser.add_argument("--two-choices", type=int, choices=[0, 1, 2, 3], nargs=2) 7 | parser.add_argument("--at-least-one-choice", type=int, choices=[0, 1, 2, 3], nargs="+") 8 | parser.add_argument("--all-choices", type=int, choices=[0, 1, 2, 3], nargs="*") 9 | parser.add_argument( 10 | "--need-at-least-one-numbers", type=int, nargs="+", required=True, action="append" 11 | ) 12 | parser.add_argument("--choices-str", nargs="+", type=str) 13 | parser.add_argument("--multiple-file-choices", type=argparse.FileType("r"), nargs="*") 14 | parser.add_argument( 15 | "--more-multiple-file-choices", type=argparse.FileType("r"), nargs="*" 16 | ) 17 | 18 | if __name__ == "__main__": 19 | args = parser.parse_args() 20 | sys.stdout.write("{}".format(args)) 21 | -------------------------------------------------------------------------------- /wooey/tests/scripts/command_order.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | parser = argparse.ArgumentParser(description="Something") 5 | parser.add_argument("link", help="the url containing the metadata") 6 | parser.add_argument("name", help="the name of the file") 7 | 8 | if __name__ == "__main__": 9 | args = parser.parse_args() 10 | sys.stderr.write("{} {}".format(args.link, args.name)) 11 | -------------------------------------------------------------------------------- /wooey/tests/scripts/crop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "chris" 4 | import argparse 5 | import sys 6 | from PIL import Image 7 | 8 | parser = argparse.ArgumentParser(description="Crop images") 9 | parser.add_argument( 10 | "--image", help="The image to crop", type=argparse.FileType("r"), required=True 11 | ) 12 | parser.add_argument( 13 | "--left", help="The number of pixels to crop from the left", type=int, default=0 14 | ) 15 | parser.add_argument( 16 | "--right", help="The number of pixels to crop from the right", type=int, default=0 17 | ) 18 | parser.add_argument( 19 | "--top", help="The number of pixels to crop from the top", type=int, default=0 20 | ) 21 | parser.add_argument( 22 | "--bottom", help="The number of pixels to crop from the bottom", type=int, default=0 23 | ) 24 | parser.add_argument( 25 | "--save", 26 | help="Where to save the new image", 27 | type=argparse.FileType("w"), 28 | required=True, 29 | ) 30 | 31 | 32 | def main(): 33 | args = parser.parse_args() 34 | im = Image.open(args.image.name) 35 | width, height = im.size 36 | right = width - args.right 37 | bottom = height - args.bottom 38 | new = im.crop((args.left, args.top, right, bottom)) 39 | new.save(args.save.name) 40 | 41 | 42 | if __name__ == "__main__": 43 | sys.exit(main()) 44 | -------------------------------------------------------------------------------- /wooey/tests/scripts/docker-script.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | import django 5 | 6 | django.setup() 7 | from django.conf import settings 8 | 9 | import docker 10 | from docker.types import Mount 11 | 12 | parser = argparse.ArgumentParser(description="Docker test script") 13 | parser.add_argument("--phrase", type=str) 14 | 15 | if __name__ == "__main__": 16 | args = parser.parse_args() 17 | client = docker.from_env() 18 | volume = client.volumes.get("wooey_user_uploads") 19 | volume_mount = volume.attrs["Mountpoint"] 20 | current_dir = os.getcwd() 21 | wooey_data_dir = os.path.join( 22 | volume_mount.rstrip("/"), 23 | current_dir.replace(settings.MEDIA_ROOT, "").lstrip("/"), 24 | ) 25 | print( 26 | client.containers.run( 27 | image="busybox", 28 | command=f"dd if=/dev/urandom of=/output/test.garbage bs=1M count=1", 29 | mounts=[Mount(target="/output", source=wooey_data_dir, type="bind")], 30 | ).decode("utf-8") 31 | ) 32 | -------------------------------------------------------------------------------- /wooey/tests/scripts/fetch_cats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "Chris Mitchell" 4 | 5 | import argparse 6 | import os 7 | import socket 8 | import sys 9 | from urllib import FancyURLopener 10 | from apiclient import discovery 11 | 12 | description = """ 13 | This will find you cats, and optionally, kitties. 14 | """ 15 | 16 | socket.setdefaulttimeout(10) 17 | 18 | parser = argparse.ArgumentParser(description=description) 19 | parser.add_argument( 20 | "--count", help="The number of cats to find (max: 10)", type=int, default=1 21 | ) 22 | parser.add_argument("--kittens", help="Search for kittens.", action="store_true") 23 | parser.add_argument( 24 | "--breed", 25 | help="The breed of cat to find", 26 | type=str, 27 | choices=("lol", "tabby", "bengal", "scottish", "grumpy"), 28 | ) 29 | 30 | # Start FancyURLopener with defined version 31 | class MyOpener(FancyURLopener): 32 | version = "Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11)Gecko/20071127 Firefox/2.0.0.11" 33 | 34 | 35 | myopener = MyOpener() 36 | 37 | 38 | def main(): 39 | args = parser.parse_args() 40 | searchTerm = "kittens" if args.kittens else "cats" 41 | cat_count = args.count if args.count < 10 else 10 42 | if args.breed: 43 | searchTerm += " {0}".format(args.breed) 44 | 45 | # Notice that the start changes for each iteration in order to request a new set of images for each loop 46 | service = discovery.build( 47 | "customsearch", "v1", developerKey=os.environ.get("GOOGLE_DEV_KEY") 48 | ) 49 | cse = service.cse() 50 | search_kwrds = { 51 | "q": searchTerm, 52 | "cx": os.environ.get("GOOGLE_CX"), 53 | "fileType": "jpg", 54 | "imgType": "photo", 55 | "num": cat_count, 56 | "searchType": "image", 57 | } 58 | request = cse.list(**search_kwrds) 59 | response = request.execute() 60 | for item in response.get("items", []): 61 | url = item.get("link") 62 | filename = url.split("/")[-1] 63 | try: 64 | myopener.retrieve(url, filename) 65 | except IOError: 66 | continue 67 | 68 | 69 | if __name__ == "__main__": 70 | sys.exit(main()) 71 | -------------------------------------------------------------------------------- /wooey/tests/scripts/file_maker.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | parser = argparse.ArgumentParser(description="This writes a test file") 5 | parser.add_argument( 6 | "--output", help="Where to write to", type=argparse.FileType("w"), required=True 7 | ) 8 | 9 | if __name__ == "__main__": 10 | args = parser.parse_args() 11 | with args.output as o: 12 | o.write("TEST FILE!") 13 | # Create an identical file nested in a folder 14 | os.mkdir("test_dir") 15 | with open("test_dir/test_file", "w") as o: 16 | o.write("TEST FILE 2") 17 | -------------------------------------------------------------------------------- /wooey/tests/scripts/gaussian.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __author__ = "chris" 3 | import argparse 4 | import sys 5 | import math 6 | from matplotlib import pyplot as plt 7 | import numpy as np 8 | 9 | parser = argparse.ArgumentParser( 10 | description="This will plot a gaussian distribution with the given parameters." 11 | ) 12 | parser.add_argument( 13 | "--mean", help="The mean of the gaussian.", type=float, required=True 14 | ) 15 | parser.add_argument( 16 | "--std", 17 | help="The standard deviation (width) of the gaussian.", 18 | type=float, 19 | required=True, 20 | ) 21 | 22 | 23 | def main(): 24 | args = parser.parse_args() 25 | u = args.mean 26 | s = abs(args.std) 27 | variance = s**2 28 | amplitude = 1 / (s * math.sqrt(2 * math.pi)) 29 | fit = lambda x: [ 30 | amplitude * math.exp((-1 * (xi - u) ** 2) / (2 * variance)) for xi in x 31 | ] 32 | # plot +- 4 standard deviations 33 | X = np.linspace(u - 4 * s, u + 4 * s, 100) 34 | Y = fit(X) 35 | plt.plot(X, Y) 36 | plt.title("Gaussian distribution with mu={0:.2f}, sigma={1:.2f}".format(u, s)) 37 | plt.savefig("gaussian.png") 38 | 39 | 40 | if __name__ == "__main__": 41 | sys.exit(main()) 42 | -------------------------------------------------------------------------------- /wooey/tests/scripts/heatmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | __author__ = "chris" 4 | import argparse 5 | import os 6 | import sys 7 | import pandas as pd 8 | import seaborn as sns 9 | import numpy as np 10 | 11 | parser = argparse.ArgumentParser(description="Create a heatmap from a delimited file.") 12 | parser.add_argument( 13 | "--tsv", 14 | help="The delimited file to plot.", 15 | type=argparse.FileType("r"), 16 | required=True, 17 | ) 18 | parser.add_argument( 19 | "--delimiter", help="The delimiter for fields. Default: tab", type=str, default="\t" 20 | ) 21 | parser.add_argument( 22 | "--row", 23 | help="The column containing row to create a heatmap from. Default to first row.", 24 | type=str, 25 | ) 26 | parser.add_argument( 27 | "--cols", 28 | help="The columns to choose values from (separate by a comma for multiple). Default: All non-rows", 29 | type=str, 30 | ) 31 | parser.add_argument( 32 | "--log-normalize", help="Whether to log normalize data.", action="store_true" 33 | ) 34 | 35 | 36 | def main(): 37 | args = parser.parse_args() 38 | data = pd.read_table( 39 | args.tsv, 40 | index_col=args.row if args.row else 0, 41 | sep=args.delimiter, 42 | encoding="utf-8", 43 | ) 44 | if args.cols: 45 | try: 46 | data = data.loc[:, args.cols.split(",")] 47 | except KeyError: 48 | data = data.iloc[:, [int(i) - 1 for i in args.cols.split(",")]] 49 | if len(data.columns) > 50: 50 | raise BaseException("Too many columns") 51 | data = np.log2(data) if args.log_normalize else data 52 | data[data == -1 * np.inf] = data[data != -1 * np.inf].min().min() 53 | width = 5 + 0 if len(data.columns) < 50 else (len(data.columns) - 50) / 100 54 | row_cutoff = 1000 55 | height = 15 + 0 if len(data) < row_cutoff else (len(data) - row_cutoff) / 75.0 56 | seaborn_map = sns.clustermap(data, figsize=(width, height)) 57 | seaborn_map.savefig("{}_heatmap.png".format(os.path.split(args.tsv.name)[1])) 58 | seaborn_map.data2d.to_csv( 59 | "{}_heatmap.tsv".format(os.path.split(args.tsv.name)[1]), sep="\t" 60 | ) 61 | 62 | 63 | if __name__ == "__main__": 64 | sys.exit(main()) 65 | -------------------------------------------------------------------------------- /wooey/tests/scripts/mandlebrot.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | from numpy import mgrid, linspace, zeros, copy, multiply, add 4 | from matplotlib import pyplot as plt 5 | 6 | parser = argparse.ArgumentParser(description="Something") 7 | parser.add_argument("height", help="The height of the image.", type=int, default=400) 8 | parser.add_argument("width", help="the width of the image.", type=int, default=400) 9 | parser.add_argument( 10 | "xmin", help="The minimum region to compute.", type=float, default=-2 11 | ) 12 | parser.add_argument( 13 | "xmax", help="The maximum region to compute.", type=float, default=0.5 14 | ) 15 | parser.add_argument( 16 | "ymin", help="The minimum region to compute.", type=float, default=-1.25 17 | ) 18 | parser.add_argument( 19 | "ymax", help="The maximum region to compute.", type=float, default=1.25 20 | ) 21 | 22 | 23 | def mandel(n, m, itermax, xmin, xmax, ymin, ymax): 24 | # from https://thesamovar.wordpress.com/2009/03/22/fast-fractals-with-python-and-numpy/ 25 | ix, iy = mgrid[0:n, 0:m] 26 | x = linspace(xmin, xmax, n)[ix] 27 | y = linspace(ymin, ymax, m)[iy] 28 | c = x + complex(0, 1) * y 29 | del x, y 30 | img = zeros(c.shape, dtype=int) 31 | ix.shape = n * m 32 | iy.shape = n * m 33 | c.shape = n * m 34 | z = copy(c) 35 | for i in range(itermax): 36 | if not len(z): 37 | break 38 | multiply(z, z, z) 39 | add(z, c, z) 40 | rem = abs(z) > 2.0 41 | img[ix[rem], iy[rem]] = i + 1 42 | rem = ~rem 43 | z = z[rem] 44 | ix, iy = ix[rem], iy[rem] 45 | c = c[rem] 46 | 47 | plt.imshow(img) 48 | plt.savefig("fractal.png") 49 | 50 | 51 | def limit(value, min, max, default): 52 | if value < min or value > max: 53 | return default 54 | return value 55 | 56 | 57 | if __name__ == "__main__": 58 | args = parser.parse_args() 59 | height = limit(args.height, 1, 2000, 400) 60 | width = limit(args.width, 1, 2000, 400) 61 | mandel(args.height, args.width, 100, args.xmin, args.xmax, args.ymin, args.ymax) 62 | -------------------------------------------------------------------------------- /wooey/tests/scripts/multi_output.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | parser = argparse.ArgumentParser(description="Something") 5 | parser.add_argument( 6 | "--output_filename", 7 | type=argparse.FileType("w"), 8 | required=True, 9 | help="Name of Excel output file.", 10 | nargs="+", 11 | ) 12 | parser.add_argument( 13 | "--due_date_field", 14 | type=str, 15 | help="Column name for due date field. 'Due Date' by default.", 16 | ) 17 | 18 | if __name__ == "__main__": 19 | args = parser.parse_args() 20 | -------------------------------------------------------------------------------- /wooey/tests/scripts/subparser_script.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | parser = argparse.ArgumentParser(description="A useless script with subparsers") 4 | parser.add_argument( 5 | "--test-arg", type=float, default=2.3, help="a test arg for the main parser" 6 | ) 7 | 8 | subparsers = parser.add_subparsers(help="commands") 9 | 10 | subparser1 = subparsers.add_parser("subparser1", help="Subparser 1") 11 | subparser1.add_argument("--sp1", type=int, default=1, help="sp1", required=True) 12 | 13 | subparser2 = subparsers.add_parser("subparser2", help="Subparser 2") 14 | subparser2.add_argument("--sp2", type=int, default=1, help="sp2", required=True) 15 | 16 | if __name__ == "__main__": 17 | args = parser.parse_args() 18 | -------------------------------------------------------------------------------- /wooey/tests/scripts/venv_pandas_test.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | import pandas as pd 5 | 6 | parser = argparse.ArgumentParser(description="Something") 7 | 8 | 9 | def main(): 10 | df = pd.DataFrame() 11 | print(df) 12 | 13 | 14 | if __name__ == "__main__": 15 | args = parser.parse_args() 16 | sys.stdout.write("{}".format(args)) 17 | sys.exit(main()) 18 | -------------------------------------------------------------------------------- /wooey/tests/scripts/versioned_script/v1.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | parser = argparse.ArgumentParser(description="This is version 1") 5 | parser.add_argument("--one") 6 | 7 | if __name__ == "__main__": 8 | args = parser.parse_args() 9 | print(args) 10 | -------------------------------------------------------------------------------- /wooey/tests/scripts/versioned_script/v2.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | parser = argparse.ArgumentParser(description="This is version 2") 5 | parser.add_argument("--one") 6 | parser.add_argument("--two") 7 | 8 | if __name__ == "__main__": 9 | args = parser.parse_args() 10 | print(args) 11 | -------------------------------------------------------------------------------- /wooey/tests/scripts/versioned_script/v3.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | parser = argparse.ArgumentParser(description="This is version 3") 5 | parser.add_argument("--one") 6 | 7 | if __name__ == "__main__": 8 | args = parser.parse_args() 9 | print(args) 10 | -------------------------------------------------------------------------------- /wooey/tests/scripts/without_args.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | parser = argparse.ArgumentParser(description="Just do it without arguments!") 5 | 6 | 7 | def main(): 8 | for i in range(10): 9 | print(i) 10 | 11 | return 0 12 | 13 | 14 | if __name__ == "__main__": 15 | parser.parse_args() 16 | sys.exit(main()) 17 | -------------------------------------------------------------------------------- /wooey/tests/test_auth.py: -------------------------------------------------------------------------------- 1 | from django.test import Client, TestCase 2 | from django.urls import reverse 3 | 4 | from .. import settings as wooey_settings 5 | 6 | from . import factories 7 | 8 | 9 | class TestApiKeyLogin(TestCase): 10 | def test_logs_in_with_api_key(self): 11 | api_key = factories.APIKeyFactory() 12 | c = Client(HTTP_AUTHORIZATION="Bearer {}".format(api_key._api_key)) 13 | response = c.get(reverse("wooey:wooey_home")) 14 | self.assertEqual(response.wsgi_request.user, api_key.profile.user) 15 | 16 | def test_records_last_time_api_key_used(self): 17 | api_key = factories.APIKeyFactory() 18 | c = Client(HTTP_AUTHORIZATION="Bearer {}".format(api_key._api_key)) 19 | self.assertIsNone(api_key.last_used) 20 | response = c.get(reverse("wooey:wooey_home")) 21 | api_key.refresh_from_db() 22 | self.assertIsNotNone(api_key.last_used) 23 | 24 | def test_fails_if_api_key_inactive(self): 25 | api_key = factories.APIKeyFactory(active=False) 26 | c = Client(HTTP_AUTHORIZATION="Bearer {}".format(api_key._api_key)) 27 | response = c.get(reverse("wooey:wooey_home")) 28 | self.assertNotEqual(response.wsgi_request.user, api_key.profile.user) 29 | 30 | def test_only_works_if_setting_is_enabled(self): 31 | wooey_settings.WOOEY_ENABLE_API_KEYS = False 32 | api_key = factories.APIKeyFactory() 33 | c = Client(HTTP_AUTHORIZATION="Bearer {}".format(api_key._api_key)) 34 | response = c.get(reverse("wooey:wooey_home")) 35 | self.assertNotEqual(response.wsgi_request.user, api_key.profile.user) 36 | wooey_settings.WOOEY_ENABLE_API_KEYS = True 37 | -------------------------------------------------------------------------------- /wooey/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.management import call_command 4 | from django.test import TestCase 5 | 6 | from ..models import ScriptVersion 7 | 8 | from . import config 9 | from . import mixins 10 | 11 | 12 | class CommandTests(mixins.ScriptFactoryMixin, TestCase): 13 | def setUp(self): 14 | # don't setup scripts, but we want to still tear down after each test 15 | pass 16 | 17 | def test_addscript(self): 18 | call_command( 19 | "addscript", os.path.join(config.WOOEY_TEST_SCRIPTS, "translate.py") 20 | ) 21 | # Test we can update the script 22 | script_version = ScriptVersion.objects.latest("created_date") 23 | old_parameters = list(script_version.get_parameters()) 24 | call_command( 25 | "addscript", 26 | "--name", 27 | "translate", 28 | os.path.join(config.WOOEY_TEST_SCRIPTS, "translate2.py"), 29 | ) 30 | new_version = ScriptVersion.objects.latest("created_date") 31 | 32 | # make sure we updated 33 | self.assertEqual( 34 | new_version.script_iteration, script_version.script_iteration + 1 35 | ) 36 | 37 | # Make sure the parameters have not changed 38 | self.assertListEqual(old_parameters, list(new_version.get_parameters())) 39 | 40 | # Make sure we don't duplicate 41 | call_command( 42 | "addscript", 43 | "--name", 44 | "translate", 45 | os.path.join(config.WOOEY_TEST_SCRIPTS, "translate2.py"), 46 | ) 47 | newest_version = ScriptVersion.objects.latest("created_date") 48 | self.assertEqual(new_version.pk, newest_version.pk) 49 | -------------------------------------------------------------------------------- /wooey/tests/test_template_tags.py: -------------------------------------------------------------------------------- 1 | from django.template import Context 2 | from django.test import TestCase 3 | 4 | from ..django_compat import get_template_from_string 5 | from ..templatetags import wooey_tags 6 | from .. import settings as wooey_settings 7 | from .factories import UserFactory 8 | 9 | 10 | class TemplateTagsTestCase(TestCase): 11 | def test_get_wooey_setting(self): 12 | # test that get_wooey_setting works as expected 13 | self.assertEqual( 14 | wooey_tags.get_wooey_setting("WOOEY_SITE_NAME"), 15 | wooey_settings.WOOEY_SITE_NAME, 16 | ) 17 | # test that get_wooey_setting works following a change 18 | wooey_settings.WOOEY_SITE_NAME = "TEST_SITE" 19 | self.assertEqual( 20 | wooey_tags.get_wooey_setting("WOOEY_SITE_NAME"), 21 | wooey_settings.WOOEY_SITE_NAME, 22 | ) 23 | 24 | def test_gravatar(self): 25 | t = get_template_from_string( 26 | "{% load wooey_tags %}{% gravatar user.email 64 %}" 27 | ) 28 | user = UserFactory() 29 | self.assertEqual( 30 | t.render(Context({"user": user})), 31 | "http://www.gravatar.com/avatar/d10ca8d11301c2f4993ac2279ce4b930?s=64", 32 | ) 33 | -------------------------------------------------------------------------------- /wooey/tests/test_virtual_envs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | from unittest import mock 5 | 6 | from django.test import TransactionTestCase 7 | 8 | from wooey import settings as wooey_settings 9 | from wooey.backend.utils import create_wooey_job 10 | from wooey.models import WooeyJob 11 | from wooey.tasks import setup_venv 12 | 13 | from . import config 14 | from .factories import VirtualEnvFactory, generate_script 15 | 16 | 17 | class TestVirtualEnvironments(TransactionTestCase): 18 | def setUp(self): 19 | super().setUp() 20 | self.venv = VirtualEnvFactory() 21 | install_path = self.venv.get_install_path() 22 | if os.path.exists(install_path): 23 | shutil.rmtree(install_path) 24 | 25 | def test_sets_up_virtual_env(self): 26 | venv = self.venv 27 | (venv_executable, stdout, stderr, return_code) = setup_venv(venv) 28 | self.assertTrue(os.path.exists(venv_executable)) 29 | 30 | def test_reuses_virtual_env(self): 31 | venv = self.venv 32 | (venv_executable, stdout, stderr, return_code) = setup_venv(venv) 33 | self.assertTrue(os.path.exists(venv_executable)) 34 | with mock.patch("wooey.tasks.run_and_stream_command") as command_runner: 35 | command_runner.return_value = ("stdout", "stderr", 0) 36 | setup_venv(venv) 37 | self.assertFalse(command_runner.called) 38 | 39 | def test_installs_pip(self): 40 | venv = self.venv 41 | setup_venv(venv) 42 | if wooey_settings.IS_WINDOWS: 43 | self.assertTrue( 44 | os.path.exists( 45 | os.path.join(venv.get_install_path(), "Scripts", "pip.exe") 46 | ) 47 | ) 48 | else: 49 | self.assertTrue( 50 | os.path.exists(os.path.join(venv.get_install_path(), "bin", "pip")) 51 | ) 52 | 53 | def test_installs_requirements(self): 54 | venv = self.venv 55 | venv.requirements = "flask" 56 | venv.save() 57 | setup_venv(venv) 58 | binary = venv.get_venv_python_binary() 59 | results = subprocess.run( 60 | [binary, "-m" "pip", "freeze", "--local"], capture_output=True 61 | ) 62 | packages = results.stdout.decode().lower() 63 | self.assertIn("flask", packages) 64 | 65 | def test_job_can_run_in_venv(self): 66 | # For this, we install a package that is only in the venv (pandas) and make sure it runs 67 | pandas_script_path = os.path.join( 68 | config.WOOEY_TEST_SCRIPTS, "venv_pandas_test.py" 69 | ) 70 | pandas_script_version = generate_script( 71 | pandas_script_path, 72 | script_name="pandas-test", 73 | ignore_bad_imports=True, 74 | ) 75 | pandas_script = pandas_script_version.script 76 | venv = self.venv 77 | venv.requirements = "pandas" 78 | venv.save() 79 | pandas_script.virtual_environment = venv 80 | pandas_script.save() 81 | job = create_wooey_job( 82 | script_version_pk=pandas_script_version.pk, 83 | data={ 84 | "job_name": "abc", 85 | }, 86 | ) 87 | self.assertEqual(job.status, WooeyJob.SUBMITTED) 88 | job = job.submit_to_celery() 89 | job.refresh_from_db() 90 | self.assertEqual(job.status, WooeyJob.COMPLETED) 91 | -------------------------------------------------------------------------------- /wooey/tests/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.core.files.storage import default_storage 4 | 5 | from .. import settings as wooey_settings 6 | 7 | 8 | def get_subparser_form_slug(script_version, param): 9 | return script_version.scriptparameter_set.get(script_param=param).form_slug 10 | 11 | 12 | def save_script_path(script_path): 13 | filename = os.path.split(script_path)[1] 14 | filename = os.path.join(wooey_settings.WOOEY_SCRIPT_DIR, filename) 15 | with open(script_path) as script_handle: 16 | path = default_storage.save(filename, script_handle) 17 | return path 18 | -------------------------------------------------------------------------------- /wooey/utils/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | from django.http import HttpResponse 4 | from django.utils.crypto import get_random_string 5 | 6 | 7 | def get_api_key(): 8 | api_key = get_random_string(32) 9 | return api_key, generate_hash(api_key) 10 | 11 | 12 | def generate_hash(value): 13 | hasher = hashlib.sha256() 14 | hasher.update(value.encode("utf-8")) 15 | return hasher.hexdigest() 16 | 17 | 18 | def requires_login(func): 19 | def inner(request, *args, **kwargs): 20 | user = request.user 21 | if not user.is_authenticated: 22 | return HttpResponse("Must be authenticated to use this method.", status=403) 23 | return func(request, *args, **kwargs) 24 | 25 | return inner 26 | -------------------------------------------------------------------------------- /wooey/version.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from django import get_version 4 | from distutils.version import StrictVersion 5 | 6 | DJANGO_VERSION = StrictVersion(get_version()) 7 | DJ21 = StrictVersion("2.1") 8 | DJ20 = StrictVersion("2.0") 9 | DJ111 = StrictVersion("1.11") 10 | DJ110 = StrictVersion("1.10") 11 | DJ19 = StrictVersion("1.9") 12 | DJ18 = StrictVersion("1.8") 13 | DJ17 = StrictVersion("1.7") 14 | DJ16 = StrictVersion("1.6") 15 | 16 | PY_FULL_VERSION = StrictVersion( 17 | "{}.{}.{}".format( 18 | sys.version_info.major, sys.version_info.minor, sys.version_info.micro 19 | ) 20 | ) 21 | PY_MINOR_VERSION = StrictVersion( 22 | "{}.{}".format(sys.version_info.major, sys.version_info.minor) 23 | ) 24 | PY34 = StrictVersion("3.4") 25 | PY343 = StrictVersion("3.4.3") 26 | PY33 = StrictVersion("3.3") 27 | PY336 = StrictVersion("3.3.6") 28 | -------------------------------------------------------------------------------- /wooey/views/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from .views import * # noqa: F401, F403 3 | from .wooey_celery import * # noqa: F401, F403 4 | from .authentication import * # noqa: F401, F403 5 | from .favorite import * # noqa: F401, F403 6 | from .profile import * # noqa: F401, F403 7 | -------------------------------------------------------------------------------- /wooey/views/authentication.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from django.contrib.auth import login, authenticate, get_user_model 3 | from django.forms.models import modelform_factory 4 | from django.http import HttpResponseRedirect 5 | from django.http import JsonResponse 6 | from django.urls import reverse 7 | from django.utils.translation import gettext_lazy as _ 8 | from django.utils.encoding import force_str 9 | from django.views.generic import CreateView 10 | 11 | from .. import settings as wooey_settings 12 | 13 | 14 | class WooeyRegister(CreateView): 15 | template_name = "wooey/registration/register.html" 16 | model = get_user_model() 17 | fields = ("username", "email", "password") 18 | 19 | def dispatch(self, request, *args, **kwargs): 20 | if not wooey_settings.WOOEY_AUTH: 21 | return HttpResponseRedirect(wooey_settings.WOOEY_REGISTER_URL) 22 | return super(WooeyRegister, self).dispatch(request, *args, **kwargs) 23 | 24 | def post(self, request, *args, **kwargs): 25 | self.object = None 26 | form = self.get_form_class() 27 | post = request.POST.copy() 28 | post["username"] = post["username"].lower() 29 | form = form(post) 30 | if request.POST["password"] != request.POST["password2"]: 31 | form.add_error("password", _("Passwords do not match.")) 32 | if request.POST["username"].lower() == "admin": 33 | form.add_error("username", _("Reserved username.")) 34 | if not request.POST["email"]: 35 | form.add_error("email", _("Please enter your email address.")) 36 | if form.is_valid(): 37 | return self.form_valid(form) 38 | else: 39 | return self.form_invalid(form) 40 | 41 | def get_success_url(self): 42 | next_url = self.request.POST.get("next") 43 | # for some bizarre reason the password isn't setting by the modelform 44 | self.object.set_password(self.request.POST["password"]) 45 | self.object.save() 46 | auser = authenticate( 47 | username=self.object.username, password=self.request.POST["password"] 48 | ) 49 | login(self.request, auser) 50 | return reverse(next_url) if next_url else reverse("wooey:wooey_home") 51 | 52 | 53 | def wooey_login(request): 54 | if not wooey_settings.WOOEY_AUTH: 55 | return HttpResponseRedirect(wooey_settings.WOOEY_LOGIN_URL) 56 | User = get_user_model() 57 | form = modelform_factory(User, fields=("username", "password")) 58 | user = User.objects.filter(username=request.POST.get("username")) 59 | if user: 60 | user = user[0] 61 | else: 62 | user = None 63 | form = form(request.POST, instance=user) 64 | if form.is_valid(): 65 | data = form.cleaned_data 66 | user = authenticate(username=data["username"], password=data["password"]) 67 | if user is None: 68 | return JsonResponse( 69 | { 70 | "valid": False, 71 | "errors": { 72 | "__all__": [ 73 | force_str( 74 | _("You have entered an invalid username or password.") 75 | ) 76 | ] 77 | }, 78 | } 79 | ) 80 | login(request, user) 81 | return JsonResponse({"valid": True, "redirect": request.POST["next"]}) 82 | else: 83 | return JsonResponse({"valid": False, "errors": form.errors}) 84 | -------------------------------------------------------------------------------- /wooey/views/favorite.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.http import ( 4 | HttpResponseForbidden, 5 | HttpResponseNotFound, 6 | HttpResponseBadRequest, 7 | JsonResponse, 8 | ) 9 | from django.views.decorators.csrf import ensure_csrf_cookie 10 | 11 | from ..models import Favorite 12 | 13 | 14 | @login_required 15 | @ensure_csrf_cookie 16 | def toggle_favorite(request): 17 | """ 18 | Add/remove an object to the user's favorites. Checks for existence and adds if not, else removes. 19 | This is the underlying mechanism for adding items to the user 'scrapbook' and favorite scripts. 20 | 21 | :param request: 22 | :param file_id: 23 | :return: 24 | """ 25 | if not request.is_ajax(): 26 | return HttpResponseForbidden() 27 | 28 | try: 29 | app, model, pk = ( 30 | request.POST["app"], 31 | request.POST["model"], 32 | int(request.POST["pk"]), 33 | ) 34 | 35 | except ValueError: 36 | return HttpResponseBadRequest() 37 | 38 | try: 39 | ctype = ContentType.objects.get(app_label=app, model=model) 40 | obj = ctype.get_object_for_this_type(id=pk) 41 | 42 | except Favorite.DoesNotExist: 43 | return HttpResponseNotFound() 44 | 45 | try: 46 | fave = Favorite.objects.get( 47 | content_type=ctype, object_id=obj.id, user=request.user 48 | ) 49 | 50 | except Favorite.DoesNotExist: 51 | # Does not exist, so create it 52 | fave = Favorite(content_object=obj, user=request.user) 53 | fave.save() 54 | is_favorite = True 55 | 56 | else: 57 | # Exists, so delete it 58 | fave.delete() 59 | is_favorite = False 60 | 61 | # Return the current total number for UI updates 62 | favorites_count = Favorite.objects.filter( 63 | content_type=ctype, user=request.user 64 | ).count() 65 | 66 | return JsonResponse( 67 | { 68 | "app": app, 69 | "model": model, 70 | "pk": pk, 71 | "is_favorite": is_favorite, 72 | "count": favorites_count, 73 | } 74 | ) 75 | -------------------------------------------------------------------------------- /wooey/views/profile.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, JsonResponse 2 | from django.views.decorators.http import require_http_methods 3 | 4 | from ..forms import APIKeyForm 5 | from ..models import APIKey, WooeyProfile 6 | from ..utils import requires_login 7 | 8 | 9 | @require_http_methods(["POST"]) 10 | @requires_login 11 | def toggle_api_key(request, id): 12 | user = request.user 13 | profile, _ = WooeyProfile.objects.get_or_create(user=user) 14 | try: 15 | api_key = APIKey.objects.get(pk=id, profile=profile) 16 | except APIKey.DoesNotExist: 17 | return HttpResponse("API Key not found", status=404) 18 | api_key.active = not api_key.active 19 | api_key.save() 20 | return JsonResponse( 21 | { 22 | "id": api_key.id, 23 | "active": api_key.active, 24 | } 25 | ) 26 | 27 | 28 | @require_http_methods(["POST"]) 29 | @requires_login 30 | def create_api_key(request): 31 | user = request.user 32 | profile, _ = WooeyProfile.objects.get_or_create(user=user) 33 | form = APIKeyForm(request.POST) 34 | if form.is_valid(): 35 | api_key = APIKey( 36 | name=form.cleaned_data["name"], 37 | profile=profile, 38 | ) 39 | api_key.save() 40 | 41 | return JsonResponse( 42 | { 43 | "id": api_key.id, 44 | "name": api_key.name, 45 | "created_date": api_key.created_date, 46 | "api_key": api_key._api_key, 47 | } 48 | ) 49 | else: 50 | return JsonResponse({"valid": False, "errors": form.errors}) 51 | 52 | 53 | @require_http_methods(["DELETE"]) 54 | @requires_login 55 | def delete_api_key(request, id): 56 | user = request.user 57 | profile, _ = WooeyProfile.objects.get_or_create(user=user) 58 | try: 59 | api_key = APIKey.objects.get(pk=id, profile=profile) 60 | except APIKey.DoesNotExist: 61 | return HttpResponse("API Key not found", status=404) 62 | api_key.delete() 63 | return JsonResponse( 64 | { 65 | "id": api_key.id, 66 | } 67 | ) 68 | -------------------------------------------------------------------------------- /wooey/wooeystorage.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os 4 | 5 | from django.core.files.storage import get_storage_class, FileSystemStorage 6 | from storages.backends.s3boto3 import S3Boto3Storage 7 | 8 | 9 | class CachedS3Boto3Storage(S3Boto3Storage): 10 | def __init__(self, *args, **kwargs): 11 | if os.environ.get("TESTING", False): 12 | from .tests import config 13 | 14 | kwargs["location"] = config.WOOEY_TEST_REMOTE_STORAGE_DIR 15 | super(CachedS3Boto3Storage, self).__init__(*args, **kwargs) 16 | self.local_storage = get_storage_class( 17 | "django.core.files.storage.FileSystemStorage" 18 | )() 19 | 20 | def path(self, name): 21 | return self.local_storage.path(name) 22 | 23 | 24 | class FakeRemoteStorage(FileSystemStorage): 25 | def __init__(self, *args, **kwargs): 26 | from .tests import config 27 | 28 | kwargs["location"] = config.WOOEY_TEST_REMOTE_STORAGE_PATH 29 | super(FakeRemoteStorage, self).__init__(*args, **kwargs) 30 | self.local_storage = get_storage_class( 31 | "django.core.files.storage.FileSystemStorage" 32 | )() 33 | --------------------------------------------------------------------------------