├── .eslintrc.json ├── .flake8 ├── .github └── workflows │ ├── publish_package.yml │ ├── run_end_to_end_tests.yml │ ├── run_linters.yml │ ├── run_python_unit_tests.yml │ └── run_style_checks.yml ├── .gitignore ├── CHANGES.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── convenient_formsets ├── __init__.py ├── apps.py ├── formsets.py ├── py.typed └── static │ ├── .eslintrc.json │ └── convenient_formsets │ └── convenient_formsets.js ├── package.json ├── py_tests ├── __init__.py ├── django_test_project │ ├── settings.py │ ├── templates │ │ ├── 404.html │ │ ├── base.html │ │ ├── initialization │ │ │ ├── hiding_add_form_button_1.html │ │ │ ├── hiding_add_form_button_2.html │ │ │ ├── hiding_add_form_button_base.html │ │ │ ├── hiding_deleted_forms_1.html │ │ │ ├── hiding_deleted_forms_2.html │ │ │ ├── hiding_deleted_forms_base.html │ │ │ ├── malformed_empty_form_template.html │ │ │ ├── missing_formset_elements_1.html │ │ │ ├── missing_formset_elements_2.html │ │ │ ├── missing_formset_elements_3.html │ │ │ ├── missing_management_form.html │ │ │ ├── missing_options_1.html │ │ │ └── missing_options_2.html │ │ └── interaction │ │ │ ├── adding_forms_1.html │ │ │ ├── adding_forms_2.html │ │ │ ├── adding_forms_3.html │ │ │ ├── combined_form_actions.html │ │ │ ├── deleting_forms.html │ │ │ ├── form_added_event.html │ │ │ ├── form_deleted_event.html │ │ │ ├── form_moved_events.html │ │ │ └── ordering_forms_all.html │ ├── urls.py │ └── views.py ├── end_to_end_tests │ ├── __init__.py │ ├── conftest.py │ ├── test_initialization.py │ └── test_interaction.py └── unit_tests │ ├── __init__.py │ └── test_formsets.py ├── pyproject.toml ├── pytest.ini ├── setup.py └── tox.ini /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "eslint:recommended", 5 | "rules": { 6 | "arrow-spacing": ["error", {"before": true, "after": true}], 7 | "camelcase": ["error"], 8 | "comma-spacing": ["error", {"before": false, "after": true}], 9 | "curly": ["error"], 10 | "dot-notation": ["error"], 11 | "eqeqeq": ["error"], 12 | "indent": ["error", 4, {"CallExpression": {"arguments": "off"}}], 13 | "key-spacing": ["error", {"beforeColon": false, "afterColon": true}], 14 | "keyword-spacing": ["error", {"before": false, "after": true}], 15 | "linebreak-style": ["error", "unix"], 16 | "new-cap": ["error"], 17 | "no-console": ["error", {"allow": ["warn", "error"]}], 18 | "no-eval": ["error"], 19 | "no-extend-native": ["error"], 20 | "no-multi-spaces": ["error", {"ignoreEOLComments": true}], 21 | "no-octal-escape": ["error"], 22 | "no-trailing-spaces": ["error"], 23 | "no-unused-vars": ["error", {"vars": "local"}], 24 | "no-var": ["error"], 25 | "object-curly-spacing": ["error", "never"], 26 | "prefer-const": ["warn"], 27 | "quotes": ["error", "single"], 28 | "semi": ["error"], 29 | "semi-spacing": ["error", {"before": false, "after": true }], 30 | "space-before-blocks": ["error"], 31 | "space-before-function-paren": ["error", {"anonymous": "never", "named": "never"}], 32 | "space-in-parens": ["error"], 33 | "space-infix-ops": ["error"], 34 | "spaced-comment": ["error", "always", {"block": {"balanced": true, "exceptions": ["!"]}}], 35 | "strict": ["error", "global"] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | -------------------------------------------------------------------------------- /.github/workflows/publish_package.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Publish PyPI package 3 | 4 | on: 5 | push: 6 | branches-ignore: 7 | - "**" 8 | tags: 9 | - "**" 10 | 11 | concurrency: pypi_publishing_environment 12 | 13 | jobs: 14 | PyPI: 15 | environment: pypi_publishing_environment 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check-out repository 20 | uses: actions/checkout@v3.3.0 21 | 22 | - name: Set up Node.js 23 | uses: actions/setup-node@v3.6.0 24 | with: 25 | node-version: "18" 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v4.5.0 29 | with: 30 | python-version: "3.9" 31 | 32 | - name: Install Node.js dependencies 33 | run: npm install 34 | 35 | - name: Install Python dependencies 36 | run: python3 -m pip install build twine 37 | 38 | - name: Minify JavaScript code 39 | run: npm run minify 40 | 41 | - name: Build package 42 | run: python3 -m build --sdist --wheel ./ 43 | 44 | - name: Publish package 45 | run: python3 -m twine upload dist/* 46 | env: 47 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 48 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 49 | -------------------------------------------------------------------------------- /.github/workflows/run_end_to_end_tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: End-to-end tests 3 | 4 | on: workflow_dispatch 5 | 6 | concurrency: end_to_end_testing_environment 7 | 8 | jobs: 9 | Test: 10 | continue-on-error: true 11 | environment: end_to_end_testing_environment 12 | runs-on: ubuntu-latest 13 | timeout-minutes: 60 14 | 15 | env: 16 | PY_COLORS: "1" 17 | 18 | strategy: 19 | max-parallel: 2 20 | matrix: 21 | capabilities: 22 | # Desktop browsers 23 | - platform: "WIN10" 24 | browserName: "chrome" 25 | version: "latest" 26 | - platform: "WIN10" 27 | browserName: "edge" 28 | version: "latest" 29 | - platform: "WIN10" 30 | browserName: "firefox" 31 | version: "latest" 32 | - platform: "WIN10" 33 | browserName: "opera" 34 | version: "latest" 35 | - platform: "VENTURA" 36 | browserName: "safari" 37 | version: "latest" 38 | 39 | steps: 40 | - name: Check-out repository 41 | uses: actions/checkout@v3.3.0 42 | 43 | - name: Set up Python 44 | uses: actions/setup-python@v4.5.0 45 | with: 46 | python-version: "3.9" 47 | 48 | - name: Install `tox` 49 | run: python3 -m pip install tox 50 | 51 | - name: Generate unique TestingBot Tunnel ID 52 | run: > 53 | echo "TESTINGBOT_TUNNEL_ID=TB-$(cat /proc/sys/kernel/random/uuid)" >> 54 | $GITHUB_ENV 55 | 56 | - name: Set up TestingBot Tunnel 57 | uses: testingbot/testingbot-tunnel-action@v1.1.0 58 | with: 59 | key: "${{ secrets.TESTINGBOT_KEY }}" 60 | secret: "${{ secrets.TESTINGBOT_SECRET }}" 61 | tunnelIdentifier: "${{ env.TESTINGBOT_TUNNEL_ID }}" 62 | 63 | - name: Dump target capabilities to JSON file 64 | run: > 65 | echo '{"capabilities": ${{ toJSON(matrix.capabilities) }}}' >> 66 | ${{ runner.temp }}/capabilities.json 67 | 68 | - name: Run end-to-end tests 69 | run: > 70 | python3 -m tox -e py3-end2end -- 71 | --driver TestingBot 72 | --capability tunnel-identifier "${{ env.TESTINGBOT_TUNNEL_ID }}" 73 | --capability selenium-host "http://localhost" 74 | --capability selenium-port "4445" 75 | --capability build "End-to-end tests #${{ github.run_number }}" 76 | --capability public "true" 77 | --variables "${{ runner.temp }}/capabilities.json" 78 | env: 79 | TESTINGBOT_KEY: "${{ secrets.TESTINGBOT_KEY }}" 80 | TESTINGBOT_SECRET: "${{ secrets.TESTINGBOT_SECRET }}" 81 | -------------------------------------------------------------------------------- /.github/workflows/run_linters.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Linters 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - "**" 8 | tags-ignore: 9 | - "**" 10 | push: 11 | branches: 12 | - "**" 13 | tags-ignore: 14 | - "**" 15 | 16 | jobs: 17 | ESLint: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Check-out repository 22 | uses: actions/checkout@v3.3.0 23 | 24 | - name: Set up Node.js 25 | uses: actions/setup-node@v3.6.0 26 | with: 27 | node-version: "18" 28 | 29 | - name: Install dependencies 30 | run: npm install 31 | 32 | - name: Run linter 33 | run: npm run lint 34 | 35 | Flake8: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - name: Check-out repository 40 | uses: actions/checkout@v3.3.0 41 | 42 | - name: Set up Python 43 | uses: actions/setup-python@v4.5.0 44 | with: 45 | python-version: "3.9" 46 | 47 | - name: Install `tox` 48 | run: python3 -m pip install tox 49 | 50 | - name: Run linter 51 | run: python3 -m tox -e flake8 52 | 53 | mypy: 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - name: Check-out repository 58 | uses: actions/checkout@v3.3.0 59 | 60 | - name: Set up Python 61 | uses: actions/setup-python@v4.5.0 62 | with: 63 | python-version: "3.9" 64 | 65 | - name: Install `tox` 66 | run: python3 -m pip install tox 67 | 68 | - name: Run linter 69 | run: python3 -m tox -e mypy 70 | 71 | Pylint: 72 | runs-on: ubuntu-latest 73 | 74 | steps: 75 | - name: Check-out repository 76 | uses: actions/checkout@v3.3.0 77 | 78 | - name: Set up Python 79 | uses: actions/setup-python@v4.5.0 80 | with: 81 | python-version: "3.9" 82 | 83 | - name: Install `tox` 84 | run: python3 -m pip install tox 85 | 86 | - name: Run linter 87 | run: python3 -m tox -e pylint 88 | -------------------------------------------------------------------------------- /.github/workflows/run_python_unit_tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Python unit tests 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - "**" 8 | tags-ignore: 9 | - "**" 10 | push: 11 | branches: 12 | - "**" 13 | tags-ignore: 14 | - "**" 15 | 16 | jobs: 17 | Test: 18 | runs-on: ubuntu-latest 19 | 20 | env: 21 | PY_COLORS: "1" 22 | 23 | strategy: 24 | matrix: 25 | include: 26 | - {tox_env: "py3-django42", python: "3.11"} 27 | - {tox_env: "py3-django42", python: "3.10"} 28 | - {tox_env: "py3-django42", python: "3.9"} 29 | - {tox_env: "py3-django42", python: "3.8"} 30 | 31 | steps: 32 | - name: Check-out repository 33 | uses: actions/checkout@v3.3.0 34 | 35 | - name: Set up Python ${{ matrix.python }} 36 | uses: actions/setup-python@v4.5.0 37 | with: 38 | python-version: ${{ matrix.python }} 39 | 40 | - name: Install `tox` 41 | run: python3 -m pip install tox 42 | 43 | - name: Run unit tests 44 | run: python3 -m tox -e ${{ matrix.tox_env }} -- --randomly-seed ${{ github.run_number }} 45 | -------------------------------------------------------------------------------- /.github/workflows/run_style_checks.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Style checks 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - "**" 8 | tags-ignore: 9 | - "**" 10 | push: 11 | branches: 12 | - "**" 13 | tags-ignore: 14 | - "**" 15 | 16 | jobs: 17 | Black: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Check-out repository 22 | uses: actions/checkout@v3.3.0 23 | 24 | - name: Set up Python 25 | uses: actions/setup-python@v4.5.0 26 | with: 27 | python-version: "3.9" 28 | 29 | - name: Install `tox` 30 | run: python3 -m pip install tox 31 | 32 | - name: Run linter 33 | run: python3 -m tox -e black -- --check 34 | 35 | isort: 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - name: Check-out repository 40 | uses: actions/checkout@v3.3.0 41 | 42 | - name: Set up Python 43 | uses: actions/setup-python@v4.5.0 44 | with: 45 | python-version: "3.9" 46 | 47 | - name: Install `tox` 48 | run: python3 -m pip install tox 49 | 50 | - name: Run linter 51 | run: python3 -m tox -e isort -- --check 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Default Python ### 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | 134 | 135 | ### Default JavaScript ### 136 | 137 | # Logs 138 | logs 139 | *.log 140 | npm-debug.log* 141 | yarn-debug.log* 142 | yarn-error.log* 143 | lerna-debug.log* 144 | 145 | # Diagnostic reports (https://nodejs.org/api/report.html) 146 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 147 | 148 | # Runtime data 149 | pids 150 | *.pid 151 | *.seed 152 | *.pid.lock 153 | 154 | # Directory for instrumented libs generated by jscoverage/JSCover 155 | lib-cov 156 | 157 | # Coverage directory used by tools like istanbul 158 | coverage 159 | *.lcov 160 | 161 | # nyc test coverage 162 | .nyc_output 163 | 164 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 165 | .grunt 166 | 167 | # Bower dependency directory (https://bower.io/) 168 | bower_components 169 | 170 | # node-waf configuration 171 | .lock-wscript 172 | 173 | # Compiled binary addons (https://nodejs.org/api/addons.html) 174 | build/Release 175 | 176 | # Dependency directories 177 | node_modules/ 178 | jspm_packages/ 179 | 180 | # Snowpack dependency directory (https://snowpack.dev/) 181 | web_modules/ 182 | 183 | # TypeScript cache 184 | *.tsbuildinfo 185 | 186 | # Optional npm cache directory 187 | .npm 188 | 189 | # Optional eslint cache 190 | .eslintcache 191 | 192 | # Microbundle cache 193 | .rpt2_cache/ 194 | .rts2_cache_cjs/ 195 | .rts2_cache_es/ 196 | .rts2_cache_umd/ 197 | 198 | # Optional REPL history 199 | .node_repl_history 200 | 201 | # Output of 'npm pack' 202 | *.tgz 203 | 204 | # Yarn Integrity file 205 | .yarn-integrity 206 | 207 | # dotenv environment variables file 208 | .env 209 | .env.test 210 | 211 | # parcel-bundler cache (https://parceljs.org/) 212 | .cache 213 | .parcel-cache 214 | 215 | # Next.js build output 216 | .next 217 | out 218 | 219 | # Nuxt.js build / generate output 220 | .nuxt 221 | dist 222 | 223 | # Gatsby files 224 | .cache/ 225 | # Comment in the public line in if your project uses Gatsby and not Next.js 226 | # https://nextjs.org/blog/next-9-1#public-directory-support 227 | # public 228 | 229 | # vuepress build output 230 | .vuepress/dist 231 | 232 | # Serverless directories 233 | .serverless/ 234 | 235 | # FuseBox cache 236 | .fusebox/ 237 | 238 | # DynamoDB Local files 239 | .dynamodb/ 240 | 241 | # TernJS port file 242 | .tern-port 243 | 244 | # Stores VSCode versions used for testing VSCode extensions 245 | .vscode-test 246 | 247 | # yarn v2 248 | .yarn/cache 249 | .yarn/unplugged 250 | .yarn/build-state.yml 251 | .yarn/install-state.gz 252 | .pnp.* 253 | 254 | 255 | 256 | ### Custom rules ### 257 | 258 | # Ignore minified JavaScript code 259 | convenient_formsets/static/convenient_formsets/convenient_formsets.min.js 260 | 261 | # Ignore `package-lock.json` as Node is only used for development 262 | package-lock.json 263 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ## Version 2.0 4 | - **BREAKING:** empty forms are now expected to appear inside the `