├── .bumpversion.cfg ├── .coveragerc ├── .editorconfig ├── .fussyfox.yml ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.yaml ├── dependabot.yml └── workflows │ ├── auto-approve.yml │ ├── auto-merge.yml │ ├── codacy-analysis.yml │ ├── codeql.yml │ ├── deploy.yml │ ├── gh-pages.yml │ ├── greetings.yml │ ├── test.yml │ └── update-doc-assets.yml ├── .gitignore ├── .pep8speaks.yml ├── .pre-commit-config.yaml ├── .pypirc ├── .pyup.yml ├── .whitesource ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE-APACHE ├── LICENSE-MIT ├── MANIFEST.in ├── Makefile ├── README.md ├── demo ├── __init__.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py └── models.py ├── django_extra_field_validation ├── __init__.py ├── settings.py └── wsgi.py ├── docs ├── .nojekyll ├── README.md └── index.html ├── extra_validator ├── __init__.py ├── apps.py ├── field_validation │ ├── __init__.py │ └── validator.py └── tests.py ├── manage.py ├── pyproject.toml ├── pytest.ini ├── renovate.json ├── requirements.txt ├── setup.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.2.3 3 | commit = True 4 | tag = False 5 | 6 | [bumpversion:file:setup.py] 7 | search = version="{current_version}" 8 | replace = version="{new_version}" 9 | 10 | [bumpversion:file:extra_validator/__init__.py] 11 | search = __version__ = "{current_version}" 12 | replace = __version__ = "{new_version}" 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = extra_validator 3 | omit = 4 | .tox 5 | demo/apps.py 6 | django_extra_field_validation/wsgi.py 7 | setup.py 8 | manage.py 9 | extra_validator/apps.py 10 | extra_validator/tests.py 11 | branch = True 12 | 13 | [report] 14 | exclude_lines = 15 | no cov 16 | no qa 17 | noqa 18 | pragma: no cover 19 | if __name__ == .__main__.: 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | -------------------------------------------------------------------------------- /.fussyfox.yml: -------------------------------------------------------------------------------- 1 | ## list of checks you would like to run on this repository 2 | # e.g.: 3 | - flake8 4 | - black 5 | # - pycodestyle 6 | # - pydocstyle 7 | # - pyflakes 8 | # - bandit 9 | # - isort 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: jackton1 4 | patreon: # Replace with a single Patreon username 5 | open_collective: tj-django 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: [] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: 🐞 Bug 2 | description: Create a report to help us improve 3 | title: "[BUG] " 4 | labels: [bug, needs triage] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | - type: checkboxes 12 | attributes: 13 | label: Is there an existing issue for this? 14 | description: Please search to see if an issue already exists for the bug you encountered. 15 | options: 16 | - label: I have searched the existing issues 17 | required: true 18 | - type: checkboxes 19 | attributes: 20 | label: Does this issue exist in the latest version? 21 | description: Please view all releases to confirm that this issue hasn't already been fixed. 22 | options: 23 | - label: I'm using the latest release 24 | required: true 25 | - type: textarea 26 | id: what-happened 27 | attributes: 28 | label: Describe the bug? 29 | description: A clear and concise description of what the bug is 30 | placeholder: Tell us what you see! 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: reproduce 35 | attributes: 36 | label: To Reproduce 37 | description: Steps to reproduce the behavior? 38 | placeholder: | 39 | 1. In this environment... 40 | 2. With this config... 41 | 3. Run '...' 42 | 4. See error... 43 | validations: 44 | required: true 45 | - type: dropdown 46 | id: os 47 | attributes: 48 | label: What OS are you seeing the problem on? 49 | multiple: true 50 | options: 51 | - Ubuntu 52 | - macOS 53 | - Windows 54 | - Other 55 | validations: 56 | required: false 57 | - type: textarea 58 | id: expected 59 | attributes: 60 | label: Expected behavior? 61 | description: A clear and concise description of what you expected to happen. 62 | placeholder: Tell us what you expected! 63 | validations: 64 | required: true 65 | - type: textarea 66 | id: logs 67 | attributes: 68 | label: Relevant log output 69 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 70 | placeholder: | 71 | This can be achieved by: 72 | 1. Re-running the workflow with debug logging enabled. 73 | 2. Copy or download the log archive. 74 | 3. Paste the contents here or upload the file in a subsequent comment. 75 | render: shell 76 | - type: textarea 77 | attributes: 78 | label: Anything else? 79 | description: | 80 | Links? or References? 81 | 82 | Anything that will give us more context about the issue you are encountering! 83 | 84 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 85 | validations: 86 | required: false 87 | - type: checkboxes 88 | id: terms 89 | attributes: 90 | label: Code of Conduct 91 | description: By submitting this issue, you agree to follow our [Code of Conduct](../blob/main/CODE_OF_CONDUCT.md) 92 | options: 93 | - label: I agree to follow this project's Code of Conduct 94 | required: true 95 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature] <title>" 4 | labels: [enhancement] 5 | 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this feature request! 11 | - type: checkboxes 12 | attributes: 13 | label: Is this feature missing in the latest version? 14 | description: Please upgrade to the latest version to verify that this feature is still missing. 15 | options: 16 | - label: I'm using the latest release 17 | required: true 18 | - type: textarea 19 | id: what-happened 20 | attributes: 21 | label: Is your feature request related to a problem? Please describe. 22 | description: | 23 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 24 | placeholder: Tell us what you see! 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: requests 29 | attributes: 30 | label: Describe the solution you'd like? 31 | description: A clear and concise description of what you want to happen. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: alternative 36 | attributes: 37 | label: Describe alternatives you've considered? 38 | description: A clear and concise description of any alternative solutions or features you've considered. 39 | validations: 40 | required: false 41 | - type: textarea 42 | attributes: 43 | label: Anything else? 44 | description: | 45 | Links? or References? 46 | 47 | Add any other context or screenshots about the feature request here. 48 | 49 | Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. 50 | validations: 51 | required: false 52 | - type: checkboxes 53 | id: terms 54 | attributes: 55 | label: Code of Conduct 56 | description: By submitting this issue, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md) 57 | options: 58 | - label: I agree to follow this project's Code of Conduct 59 | required: true 60 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | labels: 9 | - "dependencies" 10 | - "dependabot" 11 | - "automerge" 12 | - package-ecosystem: github-actions 13 | directory: "/" 14 | schedule: 15 | interval: daily 16 | open-pull-requests-limit: 10 17 | labels: 18 | - "dependencies" 19 | - "dependabot" 20 | - "automerge" 21 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve 2 | 3 | on: 4 | pull_request_target 5 | 6 | jobs: 7 | auto-approve: 8 | runs-on: ubuntu-latest 9 | if: | 10 | ( 11 | github.event.pull_request.user.login == 'dependabot[bot]' || 12 | github.event.pull_request.user.login == 'dependabot' || 13 | github.event.pull_request.user.login == 'dependabot-preview[bot]' || 14 | github.event.pull_request.user.login == 'dependabot-preview' || 15 | github.event.pull_request.user.login == 'renovate[bot]' || 16 | github.event.pull_request.user.login == 'renovate' || 17 | github.event.pull_request.user.login == 'github-actions[bot]' || 18 | github.event.pull_request.user.login == 'pre-commit-ci' || 19 | github.event.pull_request.user.login == 'pre-commit-ci[bot]' 20 | ) 21 | && 22 | ( 23 | github.actor == 'dependabot[bot]' || 24 | github.actor == 'dependabot' || 25 | github.actor == 'dependabot-preview[bot]' || 26 | github.actor == 'dependabot-preview' || 27 | github.actor == 'renovate[bot]' || 28 | github.actor == 'renovate' || 29 | github.actor == 'github-actions[bot]' || 30 | github.actor == 'pre-commit-ci' || 31 | github.actor == 'pre-commit-ci[bot]' 32 | ) 33 | steps: 34 | - uses: hmarr/auto-approve-action@v4 35 | with: 36 | github-token: ${{ secrets.PAT_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: automerge 2 | on: 3 | check_suite: 4 | types: 5 | - completed 6 | 7 | jobs: 8 | automerge: 9 | runs-on: ubuntu-latest 10 | if: | 11 | github.actor == 'dependabot[bot]' || 12 | github.actor == 'dependabot' || 13 | github.actor == 'dependabot-preview[bot]' || 14 | github.actor == 'dependabot-preview' || 15 | github.actor == 'renovate[bot]' || 16 | github.actor == 'renovate' 17 | steps: 18 | - name: automerge 19 | uses: pascalgn/automerge-action@v0.16.4 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} 22 | MERGE_METHOD: "rebase" 23 | UPDATE_METHOD: "rebase" 24 | MERGE_RETRIES: "6" 25 | MERGE_RETRY_SLEEP: "100000" 26 | MERGE_LABELS: "" 27 | -------------------------------------------------------------------------------- /.github/workflows/codacy-analysis.yml: -------------------------------------------------------------------------------- 1 | # This workflow checks out code, performs a Codacy security scan 2 | # and integrates the results with the 3 | # GitHub Advanced Security code scanning feature. For more information on 4 | # the Codacy security scan action usage and parameters, see 5 | # https://github.com/codacy/codacy-analysis-cli-action. 6 | # For more information on Codacy Analysis CLI in general, see 7 | # https://github.com/codacy/codacy-analysis-cli. 8 | 9 | name: Codacy Security Scan 10 | 11 | on: 12 | push: 13 | branches: [ main ] 14 | pull_request: 15 | # The branches below must be a subset of the branches above 16 | branches: [ main ] 17 | schedule: 18 | - cron: '15 16 * * 2' 19 | 20 | jobs: 21 | codacy-security-scan: 22 | name: Codacy Security Scan 23 | runs-on: ubuntu-latest 24 | steps: 25 | # Checkout the repository to the GitHub Actions runner 26 | - name: Checkout code 27 | uses: actions/checkout@v4 28 | 29 | # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis 30 | - name: Run Codacy Analysis CLI 31 | uses: codacy/codacy-analysis-cli-action@v4.4.5 32 | with: 33 | # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository 34 | # You can also omit the token and run the tools that support default configurations 35 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 36 | verbose: true 37 | output: results.sarif 38 | format: sarif 39 | # Adjust severity of non-security issues 40 | gh-code-scanning-compat: true 41 | # Force 0 exit code to allow SARIF file generation 42 | # This will handover control about PR rejection to the GitHub side 43 | max-allowed-issues: 2147483647 44 | 45 | # Upload the SARIF file generated in the previous step 46 | - name: Upload SARIF results file 47 | uses: github/codeql-action/upload-sarif@v3 48 | with: 49 | sarif_file: results.sarif 50 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "47 14 * * 1" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ python ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v3 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v3 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Run semver-diff 16 | id: semver-diff 17 | uses: tj-actions/semver-diff@v3.0.1 18 | 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: '3.7.x' 23 | 24 | - name: Upgrade pip 25 | run: | 26 | pip install -U pip 27 | 28 | - name: Install dependencies 29 | run: make install-deploy 30 | 31 | - name: Setup git 32 | run: | 33 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 34 | git config --local user.name "github-actions[bot]" 35 | 36 | - name: bumpversion 37 | run: | 38 | make increase-version PART="${{ steps.semver-diff.outputs.release_type }}" 39 | 40 | - name: Build and publish 41 | run: make release 42 | env: 43 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 44 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 45 | 46 | - name: Generate CHANGELOG 47 | uses: tj-actions/github-changelog-generator@v1.20 48 | 49 | - name: Create Pull Request 50 | uses: peter-evans/create-pull-request@v7 51 | with: 52 | base: "main" 53 | title: "Upgraded ${{ steps.semver-diff.outputs.old_version }} → ${{ steps.semver-diff.outputs.new_version }}" 54 | branch: "chore/upgrade-${{ steps.semver-diff.outputs.old_version }}-to-${{ steps.semver-diff.outputs.new_version }}" 55 | commit-message: "Upgraded from ${{ steps.semver-diff.outputs.old_version }} → ${{ steps.semver-diff.outputs.new_version }}" 56 | body: "View [CHANGES](https://github.com/${{ github.repository }}/compare/${{ steps.semver-diff.outputs.old_version }}...${{ steps.semver-diff.outputs.new_version }})" 57 | token: ${{ secrets.PAT_TOKEN }} 58 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4.2.2 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Deploy 17 | uses: peaceiris/actions-gh-pages@v4.0.0 18 | with: 19 | github_token: ${{ secrets.GITHUB_TOKEN }} 20 | publish_dir: ./docs 21 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request_target, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: "Thanks for reporting this issue, don't forget to star this project if you haven't already to help us reach a wider audience." 13 | pr-message: "Thanks for implementing a fix, could you ensure that the test covers your changes if applicable." 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - "README.md" 8 | - "docs/**" 9 | pull_request: 10 | branches: [ main ] 11 | paths-ignore: 12 | - "README.md" 13 | - "docs/**" 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.platform }} 18 | strategy: 19 | matrix: 20 | platform: [ubuntu-20.04, ubuntu-latest, macos-latest, windows-latest] 21 | python-version: [3.6, 3.7, 3.8, 3.9, '3.10', '3.11'] 22 | exclude: 23 | - platform: ubuntu-latest 24 | python-version: 3.6 25 | - platform: macos-latest 26 | python-version: '3.11' 27 | - platform: windows-latest 28 | python-version: 3.6 29 | - platform: windows-latest 30 | python-version: '3.11' 31 | 32 | steps: 33 | - uses: actions/checkout@v4.2.2 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v5.4.0 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | cache: 'pip' 39 | cache-dependency-path: '**/requirements.txt' 40 | - name: Install dependencies 41 | run: | 42 | python -m pip install --upgrade pip 43 | pip install tox tox-gh-actions 44 | - name: Test with tox 45 | run: tox 46 | env: 47 | DJANGO_SETTINGS_MODULE: django_extra_field_validation.settings 48 | CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} 49 | PLATFORM: ${{ matrix.platform }} 50 | -------------------------------------------------------------------------------- /.github/workflows/update-doc-assets.yml: -------------------------------------------------------------------------------- 1 | name: Sync doc assets. 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | sync-readme: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | 16 | - name: Run remark 17 | uses: tj-actions/remark@v3 18 | 19 | - name: Verify Changed files 20 | uses: tj-actions/verify-changed-files@v20 21 | id: verify_changed_files 22 | with: 23 | files: | 24 | README.md 25 | 26 | - name: README.md changed 27 | if: steps.verify_changed_files.outputs.files_changed == 'true' 28 | run: | 29 | echo "README.md has uncommited changes" 30 | exit 1 31 | 32 | - name: Create Pull Request 33 | if: failure() 34 | uses: peter-evans/create-pull-request@v7 35 | with: 36 | base: "main" 37 | title: "Updated README.md" 38 | branch: "chore/update-readme" 39 | commit-message: "Updated README.md" 40 | body: "Updated README.md" 41 | token: ${{ secrets.PAT_TOKEN }} 42 | 43 | - name: Copy README 44 | run: | 45 | cp -f README.md docs/README.md 46 | 47 | - name: Create Pull Request 48 | uses: peter-evans/create-pull-request@v7.0.6 49 | with: 50 | commit-message: Synced README changes to docs 51 | committer: github-actions[bot] <github-actions[bot]@users.noreply@github.com> 52 | author: github-actions[bot] <github-actions[bot]@users.noreply.github.com> 53 | branch: chore/update-docs 54 | base: main 55 | delete-branch: true 56 | title: Updated docs 57 | body: | 58 | Updated docs 59 | - Auto-generated by github-actions[bot] 60 | assignees: jackton1 61 | reviewers: jackton1 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pyc 3 | .cache/ 4 | .coverage 5 | .idea/ 6 | .vscode/ 7 | *.egg-info/ 8 | build/ 9 | dist/ 10 | docs/build/ 11 | venv/ 12 | wheelhouse/ 13 | .tox 14 | *.sqlite3 15 | .DS_* 16 | pip-wheel-metadata/ 17 | .envrc 18 | coverage.xml 19 | -------------------------------------------------------------------------------- /.pep8speaks.yml: -------------------------------------------------------------------------------- 1 | scanner: 2 | diff_only: True # If False, the entire file touched by the Pull Request is scanned for errors. If True, only the diff is scanned. 3 | linter: pycodestyle 4 | 5 | pycodestyle: # Same as scanner.linter value. Other option is flake8 6 | max-line-length: 100 # Default is 79 in PEP 8 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/PyCQA/autoflake 3 | rev: v2.1.1 4 | hooks: 5 | - id: autoflake 6 | args: ['--in-place', '--remove-all-unused-imports', '--remove-unused-variable'] 7 | 8 | - repo: https://github.com/pycqa/isort 9 | rev: 5.12.0 10 | hooks: 11 | - id: isort 12 | args: ["--profile", "black", "--filter-files"] 13 | 14 | - repo: https://github.com/pre-commit/pre-commit-hooks 15 | rev: v4.4.0 16 | hooks: 17 | - id: trailing-whitespace 18 | exclude: ^docs/.*|.*.md 19 | - id: end-of-file-fixer 20 | exclude: ^docs/.*|.*.md 21 | 22 | - repo: https://github.com/psf/black 23 | rev: 23.3.0 24 | hooks: 25 | - id: black 26 | language_version: python3 27 | -------------------------------------------------------------------------------- /.pypirc: -------------------------------------------------------------------------------- 1 | [distutils] 2 | index-servers = 3 | pypi 4 | 5 | [pypi] 6 | repository: https://upload.pypi.org/legacy/ 7 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | schedule: '' 5 | update: False 6 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | { 2 | "scanSettings": { 3 | "baseBranches": [] 4 | }, 5 | "checkRunSettings": { 6 | "vulnerableCheckRunConclusionLevel": "failure", 7 | "displayMode": "diff" 8 | }, 9 | "issueSettings": { 10 | "minSeverityLevel": "LOW" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v1.2.3](https://github.com/tj-django/django-extra-field-validation/tree/v1.2.3) (2023-01-14) 4 | 5 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.2.2...v1.2.3) 6 | 7 | **Merged pull requests:** 8 | 9 | - Upgraded v1.2.1 → v1.2.2 [\#308](https://github.com/tj-django/django-extra-field-validation/pull/308) ([jackton1](https://github.com/jackton1)) 10 | 11 | ## [v1.2.2](https://github.com/tj-django/django-extra-field-validation/tree/v1.2.2) (2023-01-14) 12 | 13 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.2.1...v1.2.2) 14 | 15 | ## [v1.2.1](https://github.com/tj-django/django-extra-field-validation/tree/v1.2.1) (2023-01-14) 16 | 17 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.2.0...v1.2.1) 18 | 19 | **Merged pull requests:** 20 | 21 | - chore: update Makefile [\#306](https://github.com/tj-django/django-extra-field-validation/pull/306) ([jackton1](https://github.com/jackton1)) 22 | - Update CHANGELOG [\#305](https://github.com/tj-django/django-extra-field-validation/pull/305) ([jackton1](https://github.com/jackton1)) 23 | 24 | ## [v1.2.0](https://github.com/tj-django/django-extra-field-validation/tree/v1.2.0) (2023-01-13) 25 | 26 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.1.1...v1.2.0) 27 | 28 | **Implemented enhancements:** 29 | 30 | - \[Feature\] Drop support for python 2.7 [\#186](https://github.com/tj-django/django-extra-field-validation/issues/186) 31 | 32 | **Closed issues:** 33 | 34 | - CVE-2022-40899 \(High\) detected in future-0.18.2.tar.gz - autoclosed [\#293](https://github.com/tj-django/django-extra-field-validation/issues/293) 35 | - Action Required: Fix Renovate Configuration [\#176](https://github.com/tj-django/django-extra-field-validation/issues/176) 36 | - Initial Update [\#160](https://github.com/tj-django/django-extra-field-validation/issues/160) 37 | - Dependency Dashboard [\#135](https://github.com/tj-django/django-extra-field-validation/issues/135) 38 | - Clean up bumpversion match to use just the version. [\#107](https://github.com/tj-django/django-extra-field-validation/issues/107) 39 | - Convert to github actions. [\#95](https://github.com/tj-django/django-extra-field-validation/issues/95) 40 | 41 | **Merged pull requests:** 42 | 43 | - Update dependency future to v0.18.3 [\#303](https://github.com/tj-django/django-extra-field-validation/pull/303) ([renovate[bot]](https://github.com/apps/renovate)) 44 | - Update actions/setup-python action to v4.5.0 [\#302](https://github.com/tj-django/django-extra-field-validation/pull/302) ([renovate[bot]](https://github.com/apps/renovate)) 45 | - chore: update packages [\#301](https://github.com/tj-django/django-extra-field-validation/pull/301) ([jackton1](https://github.com/jackton1)) 46 | - Updated docs [\#300](https://github.com/tj-django/django-extra-field-validation/pull/300) ([github-actions[bot]](https://github.com/apps/github-actions)) 47 | - Update actions/cache action to v3.2.3 [\#299](https://github.com/tj-django/django-extra-field-validation/pull/299) ([renovate[bot]](https://github.com/apps/renovate)) 48 | - Update actions/checkout action to v3.3.0 [\#298](https://github.com/tj-django/django-extra-field-validation/pull/298) ([renovate[bot]](https://github.com/apps/renovate)) 49 | - Update peaceiris/actions-gh-pages action to v3.9.1 [\#297](https://github.com/tj-django/django-extra-field-validation/pull/297) ([renovate[bot]](https://github.com/apps/renovate)) 50 | - Update wearerequired/lint-action action to v2.2.0 [\#296](https://github.com/tj-django/django-extra-field-validation/pull/296) ([renovate[bot]](https://github.com/apps/renovate)) 51 | - Update tj-actions/github-changelog-generator action to v1.17 [\#295](https://github.com/tj-django/django-extra-field-validation/pull/295) ([renovate[bot]](https://github.com/apps/renovate)) 52 | - Update actions/cache action to v3.2.2 [\#294](https://github.com/tj-django/django-extra-field-validation/pull/294) ([renovate[bot]](https://github.com/apps/renovate)) 53 | - Update actions/cache action to v3.2.1 [\#292](https://github.com/tj-django/django-extra-field-validation/pull/292) ([renovate[bot]](https://github.com/apps/renovate)) 54 | - Update actions/setup-python action to v4.4.0 [\#291](https://github.com/tj-django/django-extra-field-validation/pull/291) ([renovate[bot]](https://github.com/apps/renovate)) 55 | - Bump actions/cache from 3.0.11 to 3.2.0 [\#290](https://github.com/tj-django/django-extra-field-validation/pull/290) ([dependabot[bot]](https://github.com/apps/dependabot)) 56 | - Update actions/cache action to v3.2.0 [\#289](https://github.com/tj-django/django-extra-field-validation/pull/289) ([renovate[bot]](https://github.com/apps/renovate)) 57 | - Update tj-actions/verify-changed-files action to v13 [\#288](https://github.com/tj-django/django-extra-field-validation/pull/288) ([renovate[bot]](https://github.com/apps/renovate)) 58 | - Update actions/checkout action to v3.2.0 [\#286](https://github.com/tj-django/django-extra-field-validation/pull/286) ([renovate[bot]](https://github.com/apps/renovate)) 59 | - Update actions/setup-python action to v4.3.1 [\#285](https://github.com/tj-django/django-extra-field-validation/pull/285) ([renovate[bot]](https://github.com/apps/renovate)) 60 | - Add CodeQL workflow for GitHub code scanning [\#283](https://github.com/tj-django/django-extra-field-validation/pull/283) ([lgtm-com[bot]](https://github.com/apps/lgtm-com)) 61 | - Update peter-evans/create-pull-request action to v4.2.3 [\#282](https://github.com/tj-django/django-extra-field-validation/pull/282) ([renovate[bot]](https://github.com/apps/renovate)) 62 | - Update peter-evans/create-pull-request action to v4.2.2 [\#281](https://github.com/tj-django/django-extra-field-validation/pull/281) ([renovate[bot]](https://github.com/apps/renovate)) 63 | - Update peter-evans/create-pull-request action to v4.2.1 [\#280](https://github.com/tj-django/django-extra-field-validation/pull/280) ([renovate[bot]](https://github.com/apps/renovate)) 64 | - Bump hmarr/auto-approve-action from 2 to 3 [\#279](https://github.com/tj-django/django-extra-field-validation/pull/279) ([dependabot[bot]](https://github.com/apps/dependabot)) 65 | - Update peaceiris/actions-gh-pages action to v3.9.0 [\#278](https://github.com/tj-django/django-extra-field-validation/pull/278) ([renovate[bot]](https://github.com/apps/renovate)) 66 | - Update peter-evans/create-pull-request action to v4.2.0 [\#277](https://github.com/tj-django/django-extra-field-validation/pull/277) ([renovate[bot]](https://github.com/apps/renovate)) 67 | - Update pascalgn/automerge-action action to v0.15.5 [\#276](https://github.com/tj-django/django-extra-field-validation/pull/276) ([renovate[bot]](https://github.com/apps/renovate)) 68 | - Update peter-evans/create-pull-request action to v4.1.4 [\#275](https://github.com/tj-django/django-extra-field-validation/pull/275) ([renovate[bot]](https://github.com/apps/renovate)) 69 | - Update tj-actions/verify-changed-files action to v12 [\#274](https://github.com/tj-django/django-extra-field-validation/pull/274) ([renovate[bot]](https://github.com/apps/renovate)) 70 | - Update actions/cache action to v3.0.11 [\#273](https://github.com/tj-django/django-extra-field-validation/pull/273) ([renovate[bot]](https://github.com/apps/renovate)) 71 | - Update actions/setup-python action to v4.3.0 [\#272](https://github.com/tj-django/django-extra-field-validation/pull/272) ([renovate[bot]](https://github.com/apps/renovate)) 72 | - Update actions/first-interaction action to v1.1.1 [\#271](https://github.com/tj-django/django-extra-field-validation/pull/271) ([renovate[bot]](https://github.com/apps/renovate)) 73 | - Update actions/checkout action to v3.1.0 [\#270](https://github.com/tj-django/django-extra-field-validation/pull/270) ([renovate[bot]](https://github.com/apps/renovate)) 74 | - Update actions/cache action to v3.0.10 [\#269](https://github.com/tj-django/django-extra-field-validation/pull/269) ([renovate[bot]](https://github.com/apps/renovate)) 75 | - Update actions/cache action to v3.0.9 [\#268](https://github.com/tj-django/django-extra-field-validation/pull/268) ([renovate[bot]](https://github.com/apps/renovate)) 76 | - Update peter-evans/create-pull-request action to v4.1.3 [\#267](https://github.com/tj-django/django-extra-field-validation/pull/267) ([renovate[bot]](https://github.com/apps/renovate)) 77 | - Update peter-evans/create-pull-request action to v4.1.2 [\#266](https://github.com/tj-django/django-extra-field-validation/pull/266) ([renovate[bot]](https://github.com/apps/renovate)) 78 | - Bump codacy/codacy-analysis-cli-action from 4.1.0 to 4.2.0 [\#265](https://github.com/tj-django/django-extra-field-validation/pull/265) ([dependabot[bot]](https://github.com/apps/dependabot)) 79 | - \[pre-commit.ci\] pre-commit autoupdate [\#264](https://github.com/tj-django/django-extra-field-validation/pull/264) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 80 | - Update tj-actions/github-changelog-generator action to v1.15 [\#263](https://github.com/tj-django/django-extra-field-validation/pull/263) ([renovate[bot]](https://github.com/apps/renovate)) 81 | - Bump wearerequired/lint-action from 2.0.1 to 2.1.0 [\#262](https://github.com/tj-django/django-extra-field-validation/pull/262) ([dependabot[bot]](https://github.com/apps/dependabot)) 82 | - Update tj-actions/verify-changed-files action to v11 [\#261](https://github.com/tj-django/django-extra-field-validation/pull/261) ([renovate[bot]](https://github.com/apps/renovate)) 83 | - Update actions/cache action to v3.0.8 [\#260](https://github.com/tj-django/django-extra-field-validation/pull/260) ([renovate[bot]](https://github.com/apps/renovate)) 84 | - Update peter-evans/create-pull-request action to v4.1.1 [\#259](https://github.com/tj-django/django-extra-field-validation/pull/259) ([renovate[bot]](https://github.com/apps/renovate)) 85 | - Update peter-evans/create-pull-request action to v4.1.0 [\#258](https://github.com/tj-django/django-extra-field-validation/pull/258) ([renovate[bot]](https://github.com/apps/renovate)) 86 | - Update actions/cache action to v3.0.7 [\#257](https://github.com/tj-django/django-extra-field-validation/pull/257) ([renovate[bot]](https://github.com/apps/renovate)) 87 | - Update actions/cache action to v3.0.6 [\#256](https://github.com/tj-django/django-extra-field-validation/pull/256) ([renovate[bot]](https://github.com/apps/renovate)) 88 | - Update actions/setup-python action to v4.2.0 [\#255](https://github.com/tj-django/django-extra-field-validation/pull/255) ([renovate[bot]](https://github.com/apps/renovate)) 89 | - Update actions/cache action to v3.0.5 [\#254](https://github.com/tj-django/django-extra-field-validation/pull/254) ([renovate[bot]](https://github.com/apps/renovate)) 90 | - Bump actions/setup-python from 4.0.0 to 4.1.0 [\#253](https://github.com/tj-django/django-extra-field-validation/pull/253) ([dependabot[bot]](https://github.com/apps/dependabot)) 91 | - Update wearerequired/lint-action action to v2.0.1 [\#252](https://github.com/tj-django/django-extra-field-validation/pull/252) ([renovate[bot]](https://github.com/apps/renovate)) 92 | - Update tj-actions/github-changelog-generator action to v1.14 [\#251](https://github.com/tj-django/django-extra-field-validation/pull/251) ([renovate[bot]](https://github.com/apps/renovate)) 93 | - Update tj-actions/verify-changed-files action to v10 [\#250](https://github.com/tj-django/django-extra-field-validation/pull/250) ([renovate[bot]](https://github.com/apps/renovate)) 94 | - Update codacy/codacy-analysis-cli-action action to v4.1.0 [\#249](https://github.com/tj-django/django-extra-field-validation/pull/249) ([renovate[bot]](https://github.com/apps/renovate)) 95 | - \[pre-commit.ci\] pre-commit autoupdate [\#248](https://github.com/tj-django/django-extra-field-validation/pull/248) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 96 | - Bump actions/setup-python from 3.1.2 to 4.0.0 [\#247](https://github.com/tj-django/django-extra-field-validation/pull/247) ([dependabot[bot]](https://github.com/apps/dependabot)) 97 | - Update actions/cache action to v3.0.4 [\#246](https://github.com/tj-django/django-extra-field-validation/pull/246) ([renovate[bot]](https://github.com/apps/renovate)) 98 | - Update peter-evans/create-pull-request action to v4.0.4 [\#245](https://github.com/tj-django/django-extra-field-validation/pull/245) ([renovate[bot]](https://github.com/apps/renovate)) 99 | - Update actions/cache action to v3.0.3 [\#244](https://github.com/tj-django/django-extra-field-validation/pull/244) ([renovate[bot]](https://github.com/apps/renovate)) 100 | - Update pascalgn/automerge-action action to v0.15.3 [\#243](https://github.com/tj-django/django-extra-field-validation/pull/243) ([renovate[bot]](https://github.com/apps/renovate)) 101 | - Update peter-evans/create-pull-request action to v4.0.3 [\#242](https://github.com/tj-django/django-extra-field-validation/pull/242) ([renovate[bot]](https://github.com/apps/renovate)) 102 | - Update wearerequired/lint-action action to v2 [\#241](https://github.com/tj-django/django-extra-field-validation/pull/241) ([renovate[bot]](https://github.com/apps/renovate)) 103 | - Update github/codeql-action action to v2 [\#240](https://github.com/tj-django/django-extra-field-validation/pull/240) ([renovate[bot]](https://github.com/apps/renovate)) 104 | - Update actions/checkout action to v3.0.2 [\#238](https://github.com/tj-django/django-extra-field-validation/pull/238) ([renovate[bot]](https://github.com/apps/renovate)) 105 | - Update actions/checkout action to v3.0.1 [\#237](https://github.com/tj-django/django-extra-field-validation/pull/237) ([renovate[bot]](https://github.com/apps/renovate)) 106 | - Update actions/setup-python action to v3.1.2 [\#236](https://github.com/tj-django/django-extra-field-validation/pull/236) ([renovate[bot]](https://github.com/apps/renovate)) 107 | - Update actions/cache action to v3.0.2 [\#235](https://github.com/tj-django/django-extra-field-validation/pull/235) ([renovate[bot]](https://github.com/apps/renovate)) 108 | - Update peter-evans/create-pull-request action to v4.0.2 [\#234](https://github.com/tj-django/django-extra-field-validation/pull/234) ([renovate[bot]](https://github.com/apps/renovate)) 109 | - Update actions/setup-python action to v3.1.1 [\#233](https://github.com/tj-django/django-extra-field-validation/pull/233) ([renovate[bot]](https://github.com/apps/renovate)) 110 | - Update actions/setup-python action to v3.1.0 [\#232](https://github.com/tj-django/django-extra-field-validation/pull/232) ([renovate[bot]](https://github.com/apps/renovate)) 111 | - Update peter-evans/create-pull-request action to v4.0.1 [\#231](https://github.com/tj-django/django-extra-field-validation/pull/231) ([renovate[bot]](https://github.com/apps/renovate)) 112 | - Update actions/cache action to v3.0.1 [\#230](https://github.com/tj-django/django-extra-field-validation/pull/230) ([renovate[bot]](https://github.com/apps/renovate)) 113 | - \[pre-commit.ci\] pre-commit autoupdate [\#229](https://github.com/tj-django/django-extra-field-validation/pull/229) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 114 | - Update pascalgn/automerge-action action to v0.15.2 [\#228](https://github.com/tj-django/django-extra-field-validation/pull/228) ([renovate[bot]](https://github.com/apps/renovate)) 115 | - Update pascalgn/automerge-action action to v0.14.4 [\#227](https://github.com/tj-django/django-extra-field-validation/pull/227) ([renovate[bot]](https://github.com/apps/renovate)) 116 | - Update peter-evans/create-pull-request action [\#226](https://github.com/tj-django/django-extra-field-validation/pull/226) ([renovate[bot]](https://github.com/apps/renovate)) 117 | - Updated docs [\#225](https://github.com/tj-django/django-extra-field-validation/pull/225) ([github-actions[bot]](https://github.com/apps/github-actions)) 118 | - Update wearerequired/lint-action action to v1.12.0 [\#224](https://github.com/tj-django/django-extra-field-validation/pull/224) ([renovate[bot]](https://github.com/apps/renovate)) 119 | - Update actions/cache action to v3 [\#223](https://github.com/tj-django/django-extra-field-validation/pull/223) ([renovate[bot]](https://github.com/apps/renovate)) 120 | - Updated README.md [\#222](https://github.com/tj-django/django-extra-field-validation/pull/222) ([jackton1](https://github.com/jackton1)) 121 | - Bump tj-actions/remark from 2.3 to 3 [\#221](https://github.com/tj-django/django-extra-field-validation/pull/221) ([dependabot[bot]](https://github.com/apps/dependabot)) 122 | - Update tj-actions/github-changelog-generator action to v1.13 [\#220](https://github.com/tj-django/django-extra-field-validation/pull/220) ([renovate[bot]](https://github.com/apps/renovate)) 123 | - Update tj-actions/verify-changed-files action to v9 [\#219](https://github.com/tj-django/django-extra-field-validation/pull/219) ([renovate[bot]](https://github.com/apps/renovate)) 124 | - Update codacy/codacy-analysis-cli-action action to v4.0.2 [\#218](https://github.com/tj-django/django-extra-field-validation/pull/218) ([renovate[bot]](https://github.com/apps/renovate)) 125 | - Update codacy/codacy-analysis-cli-action action to v4.0.1 [\#217](https://github.com/tj-django/django-extra-field-validation/pull/217) ([renovate[bot]](https://github.com/apps/renovate)) 126 | - Update actions/checkout action [\#216](https://github.com/tj-django/django-extra-field-validation/pull/216) ([renovate[bot]](https://github.com/apps/renovate)) 127 | - Update peter-evans/create-pull-request action to v3.14.0 [\#215](https://github.com/tj-django/django-extra-field-validation/pull/215) ([renovate[bot]](https://github.com/apps/renovate)) 128 | - Update actions/setup-python action to v3 [\#214](https://github.com/tj-django/django-extra-field-validation/pull/214) ([renovate[bot]](https://github.com/apps/renovate)) 129 | - Update peter-evans/create-pull-request action to v3.13.0 [\#213](https://github.com/tj-django/django-extra-field-validation/pull/213) ([renovate[bot]](https://github.com/apps/renovate)) 130 | - Update tj-actions/github-changelog-generator action to v1.12 [\#212](https://github.com/tj-django/django-extra-field-validation/pull/212) ([renovate[bot]](https://github.com/apps/renovate)) 131 | - Update actions/setup-python action to v2.3.2 [\#211](https://github.com/tj-django/django-extra-field-validation/pull/211) ([renovate[bot]](https://github.com/apps/renovate)) 132 | - \[pre-commit.ci\] pre-commit autoupdate [\#210](https://github.com/tj-django/django-extra-field-validation/pull/210) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 133 | - Update peter-evans/create-pull-request action to v3.12.1 [\#209](https://github.com/tj-django/django-extra-field-validation/pull/209) ([renovate[bot]](https://github.com/apps/renovate)) 134 | - Updated docs [\#208](https://github.com/tj-django/django-extra-field-validation/pull/208) ([github-actions[bot]](https://github.com/apps/github-actions)) 135 | - Update wearerequired/lint-action action to v1.11.1 [\#207](https://github.com/tj-django/django-extra-field-validation/pull/207) ([renovate[bot]](https://github.com/apps/renovate)) 136 | - Update wearerequired/lint-action action to v1.11.0 [\#206](https://github.com/tj-django/django-extra-field-validation/pull/206) ([renovate[bot]](https://github.com/apps/renovate)) 137 | - Update tj-actions/remark action to v2.3 [\#205](https://github.com/tj-django/django-extra-field-validation/pull/205) ([renovate[bot]](https://github.com/apps/renovate)) 138 | - Update tj-actions/remark action to v2.2 [\#204](https://github.com/tj-django/django-extra-field-validation/pull/204) ([renovate[bot]](https://github.com/apps/renovate)) 139 | - Update tj-actions/remark action to v2 [\#203](https://github.com/tj-django/django-extra-field-validation/pull/203) ([renovate[bot]](https://github.com/apps/renovate)) 140 | - Update tj-actions/github-changelog-generator action to v1.11 [\#202](https://github.com/tj-django/django-extra-field-validation/pull/202) ([renovate[bot]](https://github.com/apps/renovate)) 141 | - Removed unused code [\#201](https://github.com/tj-django/django-extra-field-validation/pull/201) ([jackton1](https://github.com/jackton1)) 142 | - Updated docs [\#200](https://github.com/tj-django/django-extra-field-validation/pull/200) ([github-actions[bot]](https://github.com/apps/github-actions)) 143 | - Updated README.md [\#199](https://github.com/tj-django/django-extra-field-validation/pull/199) ([jackton1](https://github.com/jackton1)) 144 | - Updated README.md [\#198](https://github.com/tj-django/django-extra-field-validation/pull/198) ([jackton1](https://github.com/jackton1)) 145 | - Updated docs [\#197](https://github.com/tj-django/django-extra-field-validation/pull/197) ([github-actions[bot]](https://github.com/apps/github-actions)) 146 | - Updated docs [\#196](https://github.com/tj-django/django-extra-field-validation/pull/196) ([github-actions[bot]](https://github.com/apps/github-actions)) 147 | - Updated README.md [\#195](https://github.com/tj-django/django-extra-field-validation/pull/195) ([jackton1](https://github.com/jackton1)) 148 | - Updated README.md [\#194](https://github.com/tj-django/django-extra-field-validation/pull/194) ([jackton1](https://github.com/jackton1)) 149 | - Updated docs [\#193](https://github.com/tj-django/django-extra-field-validation/pull/193) ([github-actions[bot]](https://github.com/apps/github-actions)) 150 | - Updated README.md [\#192](https://github.com/tj-django/django-extra-field-validation/pull/192) ([jackton1](https://github.com/jackton1)) 151 | - Updated docs [\#191](https://github.com/tj-django/django-extra-field-validation/pull/191) ([github-actions[bot]](https://github.com/apps/github-actions)) 152 | - Updated docs [\#190](https://github.com/tj-django/django-extra-field-validation/pull/190) ([github-actions[bot]](https://github.com/apps/github-actions)) 153 | - Update tj-actions/github-changelog-generator action to v1.10 [\#189](https://github.com/tj-django/django-extra-field-validation/pull/189) ([renovate[bot]](https://github.com/apps/renovate)) 154 | - \[pre-commit.ci\] pre-commit autoupdate [\#188](https://github.com/tj-django/django-extra-field-validation/pull/188) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 155 | - Update peter-evans/create-pull-request action to v3.12.0 [\#187](https://github.com/tj-django/django-extra-field-validation/pull/187) ([renovate[bot]](https://github.com/apps/renovate)) 156 | - \[pre-commit.ci\] pre-commit autoupdate [\#185](https://github.com/tj-django/django-extra-field-validation/pull/185) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 157 | - Update actions/setup-python action to v2.3.1 [\#184](https://github.com/tj-django/django-extra-field-validation/pull/184) ([renovate[bot]](https://github.com/apps/renovate)) 158 | - Update actions/cache action to v2.1.7 [\#183](https://github.com/tj-django/django-extra-field-validation/pull/183) ([renovate[bot]](https://github.com/apps/renovate)) 159 | - \[pre-commit.ci\] pre-commit autoupdate [\#182](https://github.com/tj-django/django-extra-field-validation/pull/182) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 160 | - Update actions/setup-python action to v2.3.0 [\#181](https://github.com/tj-django/django-extra-field-validation/pull/181) ([renovate[bot]](https://github.com/apps/renovate)) 161 | - \[pre-commit.ci\] pre-commit autoupdate [\#180](https://github.com/tj-django/django-extra-field-validation/pull/180) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 162 | - Update peter-evans/create-pull-request action to v3.11.0 [\#179](https://github.com/tj-django/django-extra-field-validation/pull/179) ([renovate[bot]](https://github.com/apps/renovate)) 163 | - Update actions/checkout action to v2.4.0 [\#178](https://github.com/tj-django/django-extra-field-validation/pull/178) ([renovate[bot]](https://github.com/apps/renovate)) 164 | - \[pre-commit.ci\] pre-commit autoupdate [\#177](https://github.com/tj-django/django-extra-field-validation/pull/177) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 165 | - Update actions/checkout action to v2.3.5 [\#175](https://github.com/tj-django/django-extra-field-validation/pull/175) ([renovate[bot]](https://github.com/apps/renovate)) 166 | - \[pre-commit.ci\] pre-commit autoupdate [\#174](https://github.com/tj-django/django-extra-field-validation/pull/174) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 167 | - Update pascalgn/automerge-action action to v0.14.3 [\#173](https://github.com/tj-django/django-extra-field-validation/pull/173) ([renovate[bot]](https://github.com/apps/renovate)) 168 | - \[pre-commit.ci\] pre-commit autoupdate [\#172](https://github.com/tj-django/django-extra-field-validation/pull/172) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 169 | - Update peter-evans/create-pull-request action to v3.10.1 [\#171](https://github.com/tj-django/django-extra-field-validation/pull/171) ([renovate[bot]](https://github.com/apps/renovate)) 170 | - Update precommit hook pycqa/isort to v5.9.3 [\#170](https://github.com/tj-django/django-extra-field-validation/pull/170) ([renovate[bot]](https://github.com/apps/renovate)) 171 | - Update codacy/codacy-analysis-cli-action action to v4 [\#169](https://github.com/tj-django/django-extra-field-validation/pull/169) ([renovate[bot]](https://github.com/apps/renovate)) 172 | - \[pre-commit.ci\] pre-commit autoupdate [\#168](https://github.com/tj-django/django-extra-field-validation/pull/168) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 173 | - Update codacy/codacy-analysis-cli-action action to v3 [\#167](https://github.com/tj-django/django-extra-field-validation/pull/167) ([renovate[bot]](https://github.com/apps/renovate)) 174 | - Update precommit hook pycqa/isort to v5.9.2 [\#166](https://github.com/tj-django/django-extra-field-validation/pull/166) ([renovate[bot]](https://github.com/apps/renovate)) 175 | - Update precommit hook pycqa/isort to v5.9.1 [\#164](https://github.com/tj-django/django-extra-field-validation/pull/164) ([renovate[bot]](https://github.com/apps/renovate)) 176 | - Update precommit hook pycqa/isort to v5.9.0 [\#163](https://github.com/tj-django/django-extra-field-validation/pull/163) ([renovate[bot]](https://github.com/apps/renovate)) 177 | - \[pre-commit.ci\] pre-commit autoupdate [\#162](https://github.com/tj-django/django-extra-field-validation/pull/162) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 178 | - Config file for pyup.io [\#161](https://github.com/tj-django/django-extra-field-validation/pull/161) ([pyup-bot](https://github.com/pyup-bot)) 179 | - Updated docs [\#159](https://github.com/tj-django/django-extra-field-validation/pull/159) ([github-actions[bot]](https://github.com/apps/github-actions)) 180 | - Update README.md [\#158](https://github.com/tj-django/django-extra-field-validation/pull/158) ([jackton1](https://github.com/jackton1)) 181 | - Update wearerequired/lint-action action to v1.10.0 [\#156](https://github.com/tj-django/django-extra-field-validation/pull/156) ([renovate[bot]](https://github.com/apps/renovate)) 182 | - Update actions/cache action to v2.1.6 [\#155](https://github.com/tj-django/django-extra-field-validation/pull/155) ([renovate[bot]](https://github.com/apps/renovate)) 183 | - Update pascalgn/automerge-action action to v0.14.2 [\#154](https://github.com/tj-django/django-extra-field-validation/pull/154) ([renovate[bot]](https://github.com/apps/renovate)) 184 | - Bump peter-evans/create-pull-request from 3.9.2 to 3.10.0 [\#153](https://github.com/tj-django/django-extra-field-validation/pull/153) ([dependabot[bot]](https://github.com/apps/dependabot)) 185 | - Update precommit hook pre-commit/pre-commit-hooks to v4.0.1 [\#152](https://github.com/tj-django/django-extra-field-validation/pull/152) ([renovate[bot]](https://github.com/apps/renovate)) 186 | - Update precommit hook pre-commit/pre-commit-hooks to v4 [\#151](https://github.com/tj-django/django-extra-field-validation/pull/151) ([renovate[bot]](https://github.com/apps/renovate)) 187 | - Bump peter-evans/create-pull-request from 3.9.1 to 3.9.2 [\#150](https://github.com/tj-django/django-extra-field-validation/pull/150) ([dependabot[bot]](https://github.com/apps/dependabot)) 188 | - Bump actions/setup-python from 2 to 2.2.2 [\#148](https://github.com/tj-django/django-extra-field-validation/pull/148) ([dependabot[bot]](https://github.com/apps/dependabot)) 189 | - Bump peter-evans/create-pull-request from 3 to 3.9.1 [\#147](https://github.com/tj-django/django-extra-field-validation/pull/147) ([dependabot[bot]](https://github.com/apps/dependabot)) 190 | - Bump actions/cache from 2 to 2.1.5 [\#146](https://github.com/tj-django/django-extra-field-validation/pull/146) ([dependabot[bot]](https://github.com/apps/dependabot)) 191 | - Bump actions/first-interaction from 1 to 1.1.0 [\#145](https://github.com/tj-django/django-extra-field-validation/pull/145) ([dependabot[bot]](https://github.com/apps/dependabot)) 192 | - Bump actions/checkout from 2 to 2.3.4 [\#144](https://github.com/tj-django/django-extra-field-validation/pull/144) ([dependabot[bot]](https://github.com/apps/dependabot)) 193 | - Bump peaceiris/actions-gh-pages from 3 to 3.8.0 [\#143](https://github.com/tj-django/django-extra-field-validation/pull/143) ([dependabot[bot]](https://github.com/apps/dependabot)) 194 | - \[pre-commit.ci\] pre-commit autoupdate [\#141](https://github.com/tj-django/django-extra-field-validation/pull/141) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 195 | - Update dependency six to v1.16.0 [\#140](https://github.com/tj-django/django-extra-field-validation/pull/140) ([renovate[bot]](https://github.com/apps/renovate)) 196 | - Update tj-actions/github-changelog-generator action to v1.8 [\#139](https://github.com/tj-django/django-extra-field-validation/pull/139) ([renovate[bot]](https://github.com/apps/renovate)) 197 | - \[pre-commit.ci\] pre-commit autoupdate [\#138](https://github.com/tj-django/django-extra-field-validation/pull/138) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 198 | - Upgrade to GitHub-native Dependabot [\#137](https://github.com/tj-django/django-extra-field-validation/pull/137) ([dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 199 | - \[pre-commit.ci\] pre-commit autoupdate [\#136](https://github.com/tj-django/django-extra-field-validation/pull/136) ([pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci)) 200 | - Update tj-actions/github-changelog-generator action to v1.6 [\#134](https://github.com/tj-django/django-extra-field-validation/pull/134) ([renovate[bot]](https://github.com/apps/renovate)) 201 | - Updated docs [\#133](https://github.com/tj-django/django-extra-field-validation/pull/133) ([github-actions[bot]](https://github.com/apps/github-actions)) 202 | - Updated docs [\#132](https://github.com/tj-django/django-extra-field-validation/pull/132) ([github-actions[bot]](https://github.com/apps/github-actions)) 203 | - Update README.md [\#131](https://github.com/tj-django/django-extra-field-validation/pull/131) ([jackton1](https://github.com/jackton1)) 204 | - Updated docs [\#129](https://github.com/tj-django/django-extra-field-validation/pull/129) ([github-actions[bot]](https://github.com/apps/github-actions)) 205 | - Updated docs [\#128](https://github.com/tj-django/django-extra-field-validation/pull/128) ([github-actions[bot]](https://github.com/apps/github-actions)) 206 | - Create .fussyfox.yml [\#127](https://github.com/tj-django/django-extra-field-validation/pull/127) ([jackton1](https://github.com/jackton1)) 207 | - Update test.yml [\#126](https://github.com/tj-django/django-extra-field-validation/pull/126) ([jackton1](https://github.com/jackton1)) 208 | - Update CHANGELOG [\#124](https://github.com/tj-django/django-extra-field-validation/pull/124) ([jackton1](https://github.com/jackton1)) 209 | - Updated docs [\#122](https://github.com/tj-django/django-extra-field-validation/pull/122) ([github-actions[bot]](https://github.com/apps/github-actions)) 210 | - Update CHANGELOG [\#121](https://github.com/tj-django/django-extra-field-validation/pull/121) ([jackton1](https://github.com/jackton1)) 211 | 212 | ## [v1.1.1](https://github.com/tj-django/django-extra-field-validation/tree/v1.1.1) (2021-03-21) 213 | 214 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.1.0...v1.1.1) 215 | 216 | ## [v1.1.0](https://github.com/tj-django/django-extra-field-validation/tree/v1.1.0) (2021-03-21) 217 | 218 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.0.2...v1.1.0) 219 | 220 | **Merged pull requests:** 221 | 222 | - Updated docs [\#120](https://github.com/tj-django/django-extra-field-validation/pull/120) ([github-actions[bot]](https://github.com/apps/github-actions)) 223 | 224 | ## [v1.0.2](https://github.com/tj-django/django-extra-field-validation/tree/v1.0.2) (2021-03-20) 225 | 226 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.0.1...v1.0.2) 227 | 228 | ## [v1.0.1](https://github.com/tj-django/django-extra-field-validation/tree/v1.0.1) (2021-03-20) 229 | 230 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v1.0.0...v1.0.1) 231 | 232 | **Closed issues:** 233 | 234 | - Fix README adding links to sections [\#110](https://github.com/tj-django/django-extra-field-validation/issues/110) 235 | 236 | **Merged pull requests:** 237 | 238 | - Updated docs [\#118](https://github.com/tj-django/django-extra-field-validation/pull/118) ([github-actions[bot]](https://github.com/apps/github-actions)) 239 | - Updated docs [\#117](https://github.com/tj-django/django-extra-field-validation/pull/117) ([github-actions[bot]](https://github.com/apps/github-actions)) 240 | - Update wearerequired/lint-action action to v1.9.0 [\#116](https://github.com/tj-django/django-extra-field-validation/pull/116) ([renovate[bot]](https://github.com/apps/renovate)) 241 | - Updated docs [\#115](https://github.com/tj-django/django-extra-field-validation/pull/115) ([github-actions[bot]](https://github.com/apps/github-actions)) 242 | - Update wearerequired/lint-action action to v1.8.0 [\#114](https://github.com/tj-django/django-extra-field-validation/pull/114) ([renovate[bot]](https://github.com/apps/renovate)) 243 | - Update dependency bump2version to v1 [\#113](https://github.com/tj-django/django-extra-field-validation/pull/113) ([renovate[bot]](https://github.com/apps/renovate)) 244 | - Updated docs [\#112](https://github.com/tj-django/django-extra-field-validation/pull/112) ([github-actions[bot]](https://github.com/apps/github-actions)) 245 | - Update CHANGELOG [\#111](https://github.com/tj-django/django-extra-field-validation/pull/111) ([jackton1](https://github.com/jackton1)) 246 | 247 | ## [v1.0.0](https://github.com/tj-django/django-extra-field-validation/tree/v1.0.0) (2021-03-03) 248 | 249 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.2.2...v1.0.0) 250 | 251 | ## [v0.2.2](https://github.com/tj-django/django-extra-field-validation/tree/v0.2.2) (2021-03-03) 252 | 253 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.2.0...v0.2.2) 254 | 255 | **Merged pull requests:** 256 | 257 | - Updated docs [\#109](https://github.com/tj-django/django-extra-field-validation/pull/109) ([github-actions[bot]](https://github.com/apps/github-actions)) 258 | - Update to use bump2version [\#108](https://github.com/tj-django/django-extra-field-validation/pull/108) ([jackton1](https://github.com/jackton1)) 259 | - Increased test coverage and update tox config. [\#106](https://github.com/tj-django/django-extra-field-validation/pull/106) ([jackton1](https://github.com/jackton1)) 260 | - Update CHANGELOG [\#105](https://github.com/tj-django/django-extra-field-validation/pull/105) ([jackton1](https://github.com/jackton1)) 261 | 262 | ## [v0.2.0](https://github.com/tj-django/django-extra-field-validation/tree/v0.2.0) (2021-02-25) 263 | 264 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.13...v0.2.0) 265 | 266 | **Closed issues:** 267 | 268 | - Add black to auto fix lint errors. [\#99](https://github.com/tj-django/django-extra-field-validation/issues/99) 269 | - Update usage of ugettext to gettext to fix Deprecation warnings. [\#90](https://github.com/tj-django/django-extra-field-validation/issues/90) 270 | 271 | **Merged pull requests:** 272 | 273 | - Added support for generating CHANGELOG.md [\#104](https://github.com/tj-django/django-extra-field-validation/pull/104) ([jackton1](https://github.com/jackton1)) 274 | - Updated docs [\#103](https://github.com/tj-django/django-extra-field-validation/pull/103) ([github-actions[bot]](https://github.com/apps/github-actions)) 275 | - Update README.md [\#102](https://github.com/tj-django/django-extra-field-validation/pull/102) ([jackton1](https://github.com/jackton1)) 276 | - Added docs. [\#101](https://github.com/tj-django/django-extra-field-validation/pull/101) ([jackton1](https://github.com/jackton1)) 277 | - Update README.md [\#100](https://github.com/tj-django/django-extra-field-validation/pull/100) ([jackton1](https://github.com/jackton1)) 278 | - Increase test coverage. [\#98](https://github.com/tj-django/django-extra-field-validation/pull/98) ([jackton1](https://github.com/jackton1)) 279 | - Update and rename README.rst to README.md [\#97](https://github.com/tj-django/django-extra-field-validation/pull/97) ([jackton1](https://github.com/jackton1)) 280 | - Fixed test [\#96](https://github.com/tj-django/django-extra-field-validation/pull/96) ([jackton1](https://github.com/jackton1)) 281 | - Feature/resolve deprecation warning [\#94](https://github.com/tj-django/django-extra-field-validation/pull/94) ([jackton1](https://github.com/jackton1)) 282 | - Develop [\#93](https://github.com/tj-django/django-extra-field-validation/pull/93) ([jackton1](https://github.com/jackton1)) 283 | - Feature/remove pinned package versions [\#92](https://github.com/tj-django/django-extra-field-validation/pull/92) ([jackton1](https://github.com/jackton1)) 284 | - Add a Codacy badge to README.rst [\#91](https://github.com/tj-django/django-extra-field-validation/pull/91) ([codacy-badger](https://github.com/codacy-badger)) 285 | - Update setup.py [\#89](https://github.com/tj-django/django-extra-field-validation/pull/89) ([jackton1](https://github.com/jackton1)) 286 | - Update README.rst [\#88](https://github.com/tj-django/django-extra-field-validation/pull/88) ([jackton1](https://github.com/jackton1)) 287 | - Update dependency mock to v4 [\#87](https://github.com/tj-django/django-extra-field-validation/pull/87) ([renovate[bot]](https://github.com/apps/renovate)) 288 | - Update dependency yamllint to v1.24.2 [\#85](https://github.com/tj-django/django-extra-field-validation/pull/85) ([renovate[bot]](https://github.com/apps/renovate)) 289 | - Update dependency tox to v3.20.0 [\#84](https://github.com/tj-django/django-extra-field-validation/pull/84) ([renovate[bot]](https://github.com/apps/renovate)) 290 | - Update dependency six to v1.15.0 [\#26](https://github.com/tj-django/django-extra-field-validation/pull/26) ([renovate[bot]](https://github.com/apps/renovate)) 291 | - Update dependency isort to v4.3.21 [\#24](https://github.com/tj-django/django-extra-field-validation/pull/24) ([renovate[bot]](https://github.com/apps/renovate)) 292 | - Update dependency future to v0.18.2 [\#23](https://github.com/tj-django/django-extra-field-validation/pull/23) ([renovate[bot]](https://github.com/apps/renovate)) 293 | - Update README.rst [\#18](https://github.com/tj-django/django-extra-field-validation/pull/18) ([jackton1](https://github.com/jackton1)) 294 | - Configure Renovate [\#17](https://github.com/tj-django/django-extra-field-validation/pull/17) ([renovate[bot]](https://github.com/apps/renovate)) 295 | 296 | ## [v0.1.13](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.13) (2020-02-23) 297 | 298 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.12...v0.1.13) 299 | 300 | ## [v0.1.12](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.12) (2020-02-23) 301 | 302 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.11...v0.1.12) 303 | 304 | **Closed issues:** 305 | 306 | - Update the `long_description_content_type` to text/x-rst [\#14](https://github.com/tj-django/django-extra-field-validation/issues/14) 307 | 308 | **Merged pull requests:** 309 | 310 | - Update README.rst [\#16](https://github.com/tj-django/django-extra-field-validation/pull/16) ([jackton1](https://github.com/jackton1)) 311 | 312 | ## [v0.1.11](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.11) (2019-04-16) 313 | 314 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.8...v0.1.11) 315 | 316 | **Merged pull requests:** 317 | 318 | - Develop [\#13](https://github.com/tj-django/django-extra-field-validation/pull/13) ([jackton1](https://github.com/jackton1)) 319 | - Update README.rst [\#11](https://github.com/tj-django/django-extra-field-validation/pull/11) ([jackton1](https://github.com/jackton1)) 320 | - Fixed code style errors. [\#9](https://github.com/tj-django/django-extra-field-validation/pull/9) ([jackton1](https://github.com/jackton1)) 321 | 322 | ## [v0.1.8](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.8) (2019-04-16) 323 | 324 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.10...v0.1.8) 325 | 326 | ## [v0.1.10](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.10) (2019-01-29) 327 | 328 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.9...v0.1.10) 329 | 330 | ## [v0.1.9](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.9) (2019-01-29) 331 | 332 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.7...v0.1.9) 333 | 334 | **Fixed bugs:** 335 | 336 | - No module named `dynamic_validator.field_validation` [\#7](https://github.com/tj-django/django-extra-field-validation/issues/7) 337 | 338 | **Merged pull requests:** 339 | 340 | - Updated the MANIFEST.in [\#8](https://github.com/tj-django/django-extra-field-validation/pull/8) ([jackton1](https://github.com/jackton1)) 341 | 342 | ## [v0.1.7](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.7) (2019-01-29) 343 | 344 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.6...v0.1.7) 345 | 346 | ## [v0.1.6](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.6) (2019-01-29) 347 | 348 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.5...v0.1.6) 349 | 350 | ## [v0.1.5](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.5) (2019-01-27) 351 | 352 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.4...v0.1.5) 353 | 354 | **Merged pull requests:** 355 | 356 | - Removed redundant i.e statements [\#5](https://github.com/tj-django/django-extra-field-validation/pull/5) ([jackton1](https://github.com/jackton1)) 357 | - Updated Django version. [\#4](https://github.com/tj-django/django-extra-field-validation/pull/4) ([jackton1](https://github.com/jackton1)) 358 | - Update README.rst [\#2](https://github.com/tj-django/django-extra-field-validation/pull/2) ([jackton1](https://github.com/jackton1)) 359 | 360 | ## [v0.1.4](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.4) (2018-12-09) 361 | 362 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.3...v0.1.4) 363 | 364 | ## [v0.1.3](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.3) (2018-12-09) 365 | 366 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.2...v0.1.3) 367 | 368 | ## [v0.1.2](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.2) (2018-12-09) 369 | 370 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.1...v0.1.2) 371 | 372 | ## [v0.1.1](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.1) (2018-12-09) 373 | 374 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.1.0...v0.1.1) 375 | 376 | ## [v0.1.0](https://github.com/tj-django/django-extra-field-validation/tree/v0.1.0) (2018-12-09) 377 | 378 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/v0.0.1...v0.1.0) 379 | 380 | ## [v0.0.1](https://github.com/tj-django/django-extra-field-validation/tree/v0.0.1) (2018-12-09) 381 | 382 | [Full Changelog](https://github.com/tj-django/django-extra-field-validation/compare/82382cb1beb5a4deaf24e444a7c541368394758c...v0.0.1) 383 | 384 | 385 | 386 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* 387 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jtonye@ymail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Tonye Jack 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Tonye Jack 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # added by check_manifest.py 2 | prune test*.py 3 | prune extra_validator/tests.py 4 | recursive-include extra_validator *.py 5 | include *.py 6 | exclude *.toml 7 | include *.txt 8 | exclude *.xml 9 | exclude .bumpversion.cfg 10 | exclude .coveragerc 11 | exclude Makefile 12 | exclude pytest.ini 13 | exclude tox.ini 14 | prune demo 15 | exclude manage.py 16 | exclude extra_validator/tests.py 17 | recursive-exclude django_extra_field_validation *.py 18 | include LICENSE-APACHE 19 | include LICENSE-MIT 20 | include README.rst 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Self-Documented Makefile see https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 2 | 3 | .DEFAULT_GOAL := help 4 | 5 | PYTHON := /usr/bin/env python 6 | MANAGE_PY := $(PYTHON) manage.py 7 | PYTHON_PIP := /usr/bin/env pip 8 | PIP_COMPILE := /usr/bin/env pip-compile 9 | PART := patch 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-32s-\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 14 | 15 | .PHONY: help 16 | 17 | guard-%: ## Checks that env var is set else exits with non 0 mainly used in CI; 18 | @if [ -z '${${*}}' ]; then echo 'Environment variable $* not set' && exit 1; fi 19 | 20 | # -------------------------------------------------------- 21 | # ------- Python package (pip) management commands ------- 22 | # -------------------------------------------------------- 23 | 24 | clean-build: ## Clean project build artifacts. 25 | @echo "Removing build assets..." 26 | @$(PYTHON) setup.py clean 27 | @rm -rf build/ 28 | @rm -rf dist/ 29 | @rm -rf *.egg-info 30 | 31 | install: clean-build ## Install project dependencies. 32 | @echo "Installing project in dependencies..." 33 | @$(PYTHON_PIP) install -r requirements.txt 34 | 35 | install-lint: pipconf clean-build ## Install lint extra dependencies. 36 | @echo "Installing lint extra requirements..." 37 | @$(PYTHON_PIP) install -e .'[lint]' 38 | 39 | install-test: clean-build ## Install test extra dependencies. 40 | @echo "Installing test extra requirements..." 41 | @$(PYTHON_PIP) install -e .'[test]' 42 | 43 | install-dev: clean-build ## Install development extra dependencies. 44 | @echo "Installing development requirements..." 45 | @$(PYTHON_PIP) install -e .'[development]' -r requirements.txt 46 | 47 | install-deploy: clean-build ## Install deploy extra dependencies. 48 | @echo "Installing deploy extra requirements..." 49 | @$(PYTHON_PIP) install -e .'[deploy]' 50 | 51 | update-requirements: ## Updates the requirement.txt adding missing package dependencies 52 | @echo "Syncing the package requirements.txt..." 53 | @$(PIP_COMPILE) 54 | 55 | # ---------------------------------------------------------- 56 | # ---------- Release the project to PyPI ------------------- 57 | # ---------------------------------------------------------- 58 | increase-version: guard-PART ## Increase project version 59 | @bump2version $(PART) 60 | @git switch -c main 61 | 62 | dist: ## builds source and wheel package 63 | @pip install build twine 64 | @python -m build 65 | 66 | release: dist ## package and upload a release 67 | @twine upload dist/* 68 | 69 | # ---------------------------------------------------------- 70 | # --------- Run project Test ------------------------------- 71 | # ---------------------------------------------------------- 72 | test: 73 | @$(MANAGE_PY) test 74 | 75 | tox: install-test ## Run tox test 76 | @tox 77 | 78 | clean-test-all: clean-build ## Clean build and test assets. 79 | @rm -rf .tox/ 80 | @rm -rf .pytest_cache/ 81 | @rm test.db 82 | 83 | 84 | # ----------------------------------------------------------- 85 | # --------- Docs --------------------------------------- 86 | # ----------------------------------------------------------- 87 | create-docs: 88 | @npx docsify init ./docs 89 | 90 | serve-docs: 91 | @npx docsify serve ./docs 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-extra-field-validation 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/django-extra-field-validation) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-extra-field-validation) ![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-extra-field-validation) [![Downloads](https://pepy.tech/badge/django-extra-field-validation)](https://pepy.tech/project/django-extra-field-validation) 4 | 5 | [![CI Test](https://github.com/tj-django/django-extra-field-validation/actions/workflows/test.yml/badge.svg)](https://github.com/tj-django/django-extra-field-validation/actions/workflows/test.yml) 6 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6973bc063f1142afb66d897261d8f8f5)](https://www.codacy.com/gh/tj-django/django-extra-field-validation/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=tj-django/django-extra-field-validation\&utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/6973bc063f1142afb66d897261d8f8f5)](https://www.codacy.com/gh/tj-django/django-extra-field-validation/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=tj-django/django-extra-field-validation\&utm_campaign=Badge_Coverage) 7 | 8 | ## Table of Contents 9 | 10 | * [Background](#background) 11 | * [Installation](#installation) 12 | * [Usage](#usage) 13 | * [Require all fields](#require-all-fields) 14 | * [Require at least one field in a collection](#require-at-least-one-field-in-a-collection) 15 | * [Optionally require at least one field in a collection](#optionally-require-at-least-one-field-in-a-collection) 16 | * [Conditionally require all fields](#conditionally-require-all-fields) 17 | * [Conditionally require at least one field in a collection](#conditionally-require-at-least-one-field-in-a-collection) 18 | * [Model Attributes](#model-attributes) 19 | * [License](#license) 20 | * [TODO's](#todos) 21 | 22 | ## Background 23 | 24 | This package aims to provide tools needed to define custom field validation logic which can be used independently or with 25 | django forms, test cases, API implementation or any model operation that requires saving data to the database. 26 | 27 | This can also be extended by defining check constraints if needed but currently validation 28 | will only be handled at the model level. 29 | 30 | ## Installation 31 | 32 | ```shell script 33 | pip install django-extra-field-validation 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Require all fields 39 | 40 | ```py 41 | 42 | from django.db import models 43 | from extra_validator import FieldValidationMixin 44 | 45 | 46 | class TestModel(FieldValidationMixin, models.Model): 47 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 48 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 49 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 50 | 51 | REQUIRED_FIELDS = ['amount'] # Always requires an amount to create the instance. 52 | ``` 53 | 54 | Example 55 | 56 | ```python 57 | In [1]: from decimal import Decimal 58 | 59 | In [2]: from demo.models import TestModel 60 | 61 | In [3]: TestModel.objects.create(fixed_price=Decimal('3.00')) 62 | --------------------------------------------------------------------------- 63 | ValueError Traceback (most recent call last) 64 | ... 65 | 66 | ValueError: {'amount': ValidationError([u'Please provide a value for: "amount".'])} 67 | 68 | ``` 69 | 70 | ### Require at least one field in a collection 71 | 72 | ```py 73 | 74 | from django.db import models 75 | from extra_validator import FieldValidationMixin 76 | 77 | 78 | class TestModel(FieldValidationMixin, models.Model): 79 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 80 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 81 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 82 | 83 | REQUIRED_TOGGLE_FIELDS = [ 84 | ['amount', 'fixed_price', 'percentage'], # Require only one of the following fields. 85 | ] 86 | 87 | ``` 88 | 89 | Example 90 | 91 | ```python 92 | In [1]: from decimal import Decimal 93 | 94 | In [2]: from demo.models import TestModel 95 | 96 | In [3]: TestModel.objects.create(amount=Decimal('2.50'), fixed_price=Decimal('3.00')) 97 | --------------------------------------------------------------------------- 98 | ValueError Traceback (most recent call last) 99 | ... 100 | 101 | ValueError: {'fixed_price': ValidationError([u'Please provide only one of: Amount, Fixed price, Percentage'])} 102 | 103 | ``` 104 | 105 | ### Optionally require at least one field in a collection 106 | 107 | ```py 108 | 109 | from django.db import models 110 | from extra_validator import FieldValidationMixin 111 | 112 | 113 | class TestModel(FieldValidationMixin, models.Model): 114 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 115 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 116 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 117 | 118 | OPTIONAL_TOGGLE_FIELDS = [ 119 | ['fixed_price', 'percentage'] # Optionally validates that only fixed price/percentage are provided when present. 120 | ] 121 | 122 | ``` 123 | 124 | Example 125 | 126 | ```python 127 | In [1]: from decimal import Decimal 128 | 129 | In [2]: from demo.models import TestModel 130 | 131 | In [3]: first_obj = TestModel.objects.create(amount=Decimal('2.0')) 132 | 133 | In [4]: second_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00')) 134 | 135 | In [5]: third_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00'), percentage=Decimal('10.0')) 136 | --------------------------------------------------------------------------- 137 | ValueError Traceback (most recent call last) 138 | ... 139 | 140 | ValueError: {'percentage': ValidationError([u'Please provide only one of: Fixed price, Percentage'])} 141 | 142 | ``` 143 | 144 | ### Conditionally require all fields 145 | 146 | ```py 147 | 148 | from django.db import models 149 | from django.conf import settings 150 | from extra_validator import FieldValidationMixin 151 | 152 | 153 | class TestModel(FieldValidationMixin, models.Model): 154 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 155 | 156 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 157 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 158 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 159 | 160 | CONDITIONAL_REQUIRED_FIELDS = [ 161 | ( 162 | lambda instance: instance.user.is_active, ['amount', 'percentage'], 163 | ), 164 | ] 165 | 166 | ``` 167 | 168 | Example 169 | 170 | ```python 171 | In [1]: from decimal import Decimal 172 | 173 | in [2]: from django.contrib.auth import get_user_model 174 | 175 | In [3]: from demo.models import TestModel 176 | 177 | In [4]: user = get_user_model().objects.create(username='test', is_active=True) 178 | 179 | In [5]: first_obj = TestModel.objects.create(user=user, amount=Decimal('2.0')) 180 | --------------------------------------------------------------------------- 181 | ValueError Traceback (most recent call last) 182 | ... 183 | 184 | ValueError: {u'percentage': ValidationError([u'Please provide a value for: "percentage"'])} 185 | 186 | ``` 187 | 188 | ### Conditionally require at least one field in a collection 189 | 190 | ```py 191 | 192 | from django.db import models 193 | from django.conf import settings 194 | from extra_validator import FieldValidationMixin 195 | 196 | 197 | class TestModel(FieldValidationMixin, models.Model): 198 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 199 | 200 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 201 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 202 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 203 | 204 | CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [ 205 | ( 206 | lambda instance: instance.user.is_active, ['fixed_price', 'percentage', 'amount'], 207 | ), 208 | ] 209 | ``` 210 | 211 | Example 212 | 213 | ```python 214 | In [1]: from decimal import Decimal 215 | 216 | in [2]: from django.contrib.auth import get_user_model 217 | 218 | In [3]: from demo.models import TestModel 219 | 220 | In [4]: user = get_user_model().objects.create(username='test', is_active=True) 221 | 222 | In [5]: first_obj = TestModel.objects.create(user=user) 223 | --------------------------------------------------------------------------- 224 | ValueError Traceback (most recent call last) 225 | ... 226 | 227 | ValueError: {'__all__': ValidationError([u'Please provide a valid value for any of the following fields: Fixed price, Percentage, Amount'])} 228 | 229 | In [6]: second_obj = TestModel.objects.create(user=user, amount=Decimal('2'), fixed_price=Decimal('2')) 230 | --------------------------------------------------------------------------- 231 | ValueError Traceback (most recent call last) 232 | ... 233 | 234 | ValueError: {'__all__': ValidationError([u'Please provide only one of the following fields: Fixed price, Percentage, Amount'])} 235 | ``` 236 | 237 | ## Model Attributes 238 | 239 | This is done using model attributes below. 240 | 241 | ```py 242 | # A list of required fields 243 | REQUIRED_FIELDS = [] 244 | 245 | # A list of fields with at most one required. 246 | REQUIRED_TOGGLE_FIELDS = [] 247 | 248 | # A list of field with at least one required. 249 | REQUIRED_MIN_FIELDS = [] 250 | 251 | # Optional list of fields with at most one required. 252 | OPTIONAL_TOGGLE_FIELDS = [] 253 | 254 | # Conditional field required list of tuples the condition a boolean or a callable. 255 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' or 'last_name' 256 | # If condition is True ensure that all fields are set 257 | CONDITIONAL_REQUIRED_FIELDS = [] 258 | 259 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Either 'first_name' or 'last_name' 260 | # If condition is True ensure that at most one field is set 261 | CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [] 262 | 263 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : At least 'first_name' or 'last_name' provided or both 264 | # If condition is True ensure that at least one field is set 265 | CONDITIONAL_REQUIRED_MIN_FIELDS = [] 266 | 267 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' and 'last_name' isn't provided 268 | # If condition is True ensure none of the fields are provided 269 | CONDITIONAL_REQUIRED_EMPTY_FIELDS = [] 270 | 271 | ``` 272 | 273 | ## License 274 | 275 | django-extra-field-validation is distributed under the terms of both 276 | 277 | * [MIT License](https://choosealicense.com/licenses/mit) 278 | * [Apache License, Version 2.0](https://choosealicense.com/licenses/apache-2.0) 279 | 280 | at your option. 281 | 282 | ## TODO's 283 | 284 | * \[ ] Support `CONDITIONAL_NON_REQUIRED_TOGGLE_FIELDS` 285 | * \[ ] Support `CONDITIONAL_NON_REQUIRED_FIELDS` 286 | * \[ ] Move to support class and function based validators that use the instance object this should enable cross field model validation. 287 | -------------------------------------------------------------------------------- /demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj-django/django-extra-field-validation/e15a1177fc416945548655c227ff5811a9329b68/demo/__init__.py -------------------------------------------------------------------------------- /demo/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.apps import AppConfig 3 | 4 | 5 | class DemoConfig(AppConfig): 6 | name = "demo" 7 | -------------------------------------------------------------------------------- /demo/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-08 23:41 3 | 4 | import django.db.models.deletion 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | import extra_validator.field_validation.validator 9 | 10 | 11 | class Migration(migrations.Migration): 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name="TestModel", 21 | fields=[ 22 | ( 23 | "id", 24 | models.AutoField( 25 | auto_created=True, 26 | primary_key=True, 27 | serialize=False, 28 | verbose_name="ID", 29 | ), 30 | ), 31 | ( 32 | "amount", 33 | models.DecimalField( 34 | blank=True, decimal_places=2, max_digits=5, null=True 35 | ), 36 | ), 37 | ( 38 | "fixed_price", 39 | models.DecimalField( 40 | blank=True, decimal_places=2, max_digits=7, null=True 41 | ), 42 | ), 43 | ( 44 | "percentage", 45 | models.DecimalField( 46 | blank=True, decimal_places=0, max_digits=3, null=True 47 | ), 48 | ), 49 | ( 50 | "user", 51 | models.ForeignKey( 52 | on_delete=django.db.models.deletion.CASCADE, 53 | to=settings.AUTH_USER_MODEL, 54 | ), 55 | ), 56 | ], 57 | bases=( 58 | extra_validator.field_validation.validator.FieldValidationMixin, 59 | models.Model, 60 | ), 61 | ), 62 | ] 63 | -------------------------------------------------------------------------------- /demo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj-django/django-extra-field-validation/e15a1177fc416945548655c227ff5811a9329b68/demo/migrations/__init__.py -------------------------------------------------------------------------------- /demo/models.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.contrib.auth import get_user_model 3 | from django.db import models 4 | 5 | from extra_validator import FieldValidationMixin 6 | 7 | if django.VERSION <= (3, 0): 8 | from django.utils.translation import ugettext_noop as _ 9 | else: 10 | from django.utils.translation import gettext_noop as _ 11 | 12 | UserModel = get_user_model() 13 | 14 | 15 | class TestModel(FieldValidationMixin, models.Model): 16 | """Ensure that at least one of the following fields are provided.""" 17 | 18 | user = models.ForeignKey(UserModel, on_delete=models.CASCADE) 19 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 20 | fixed_price = models.DecimalField( 21 | max_digits=7, decimal_places=2, null=True, blank=True 22 | ) 23 | percentage = models.DecimalField( 24 | max_digits=3, decimal_places=0, null=True, blank=True 25 | ) 26 | 27 | def __str__(self): 28 | return _( 29 | "{0}: (#{1}, {2}%, ${3})".format( 30 | self.__class__.__name__, self.amount, self.percentage, self.fixed_price 31 | ) 32 | ) 33 | -------------------------------------------------------------------------------- /django_extra_field_validation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj-django/django-extra-field-validation/e15a1177fc416945548655c227ff5811a9329b68/django_extra_field_validation/__init__.py -------------------------------------------------------------------------------- /django_extra_field_validation/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for django_extra_field_validation project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "8vb(_&-!#z=hk_7k6j5u6qvu_yz2fr_oisvev+yybt@$@_$4bk" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "django.contrib.admin", 35 | "django.contrib.auth", 36 | "django.contrib.contenttypes", 37 | "django.contrib.sessions", 38 | "django.contrib.messages", 39 | "django.contrib.staticfiles", 40 | "extra_validator", 41 | "demo", 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "django.middleware.common.CommonMiddleware", 48 | "django.middleware.csrf.CsrfViewMiddleware", 49 | "django.contrib.auth.middleware.AuthenticationMiddleware", 50 | "django.contrib.messages.middleware.MessageMiddleware", 51 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 52 | ] 53 | 54 | TEMPLATES = [ 55 | { 56 | "BACKEND": "django.template.backends.django.DjangoTemplates", 57 | "DIRS": [], 58 | "APP_DIRS": True, 59 | "OPTIONS": { 60 | "context_processors": [ 61 | "django.template.context_processors.debug", 62 | "django.template.context_processors.request", 63 | "django.contrib.auth.context_processors.auth", 64 | "django.contrib.messages.context_processors.messages", 65 | ], 66 | }, 67 | }, 68 | ] 69 | 70 | WSGI_APPLICATION = "django_extra_field_validation.wsgi.application" 71 | 72 | 73 | # Database 74 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 75 | 76 | DATABASES = { 77 | "default": { 78 | "ENGINE": "django.db.backends.sqlite3", 79 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 80 | } 81 | } 82 | 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 86 | 87 | AUTH_PASSWORD_VALIDATORS = [ 88 | { 89 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 90 | }, 91 | { 92 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 93 | }, 94 | { 95 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 96 | }, 97 | { 98 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 99 | }, 100 | ] 101 | 102 | 103 | # Internationalization 104 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 105 | 106 | TIME_ZONE = "UTC" 107 | 108 | USE_I18N = True 109 | 110 | USE_L10N = True 111 | 112 | USE_TZ = True 113 | 114 | 115 | # Static files (CSS, JavaScript, Images) 116 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 117 | 118 | STATIC_URL = "/static/" 119 | 120 | SECURE_HSTS_SECONDS = 3600 121 | 122 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True 123 | 124 | SECURE_CONTENT_TYPE_NOSNIFF = True 125 | 126 | SECURE_SSL_REDIRECT = os.getenv("SECURE_SSL_REDIRECT_ENABLED") != "False" 127 | 128 | SESSION_COOKIE_SECURE = os.getenv("SESSION_COOKIE_SECURE_ENABLED") != "False" 129 | 130 | CSRF_COOKIE_SECURE = os.getenv("CSRF_COOKIE_SECURE_ENABLED") != "False" 131 | 132 | SECURE_HSTS_PRELOAD = True 133 | -------------------------------------------------------------------------------- /django_extra_field_validation/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for django_extra_field_validation project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault( 15 | "DJANGO_SETTINGS_MODULE", "django_extra_field_validation.settings" 16 | ) 17 | 18 | application = get_wsgi_application() 19 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj-django/django-extra-field-validation/e15a1177fc416945548655c227ff5811a9329b68/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # django-extra-field-validation 2 | 3 | ![PyPI](https://img.shields.io/pypi/v/django-extra-field-validation) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-extra-field-validation) ![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-extra-field-validation) [![Downloads](https://pepy.tech/badge/django-extra-field-validation)](https://pepy.tech/project/django-extra-field-validation) 4 | 5 | [![CI Test](https://github.com/tj-django/django-extra-field-validation/actions/workflows/test.yml/badge.svg)](https://github.com/tj-django/django-extra-field-validation/actions/workflows/test.yml) 6 | [![Codacy Badge](https://app.codacy.com/project/badge/Grade/6973bc063f1142afb66d897261d8f8f5)](https://www.codacy.com/gh/tj-django/django-extra-field-validation/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=tj-django/django-extra-field-validation\&utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/6973bc063f1142afb66d897261d8f8f5)](https://www.codacy.com/gh/tj-django/django-extra-field-validation/dashboard?utm_source=github.com\&utm_medium=referral\&utm_content=tj-django/django-extra-field-validation\&utm_campaign=Badge_Coverage) 7 | 8 | ## Table of Contents 9 | 10 | * [Background](#background) 11 | * [Installation](#installation) 12 | * [Usage](#usage) 13 | * [Require all fields](#require-all-fields) 14 | * [Require at least one field in a collection](#require-at-least-one-field-in-a-collection) 15 | * [Optionally require at least one field in a collection](#optionally-require-at-least-one-field-in-a-collection) 16 | * [Conditionally require all fields](#conditionally-require-all-fields) 17 | * [Conditionally require at least one field in a collection](#conditionally-require-at-least-one-field-in-a-collection) 18 | * [Model Attributes](#model-attributes) 19 | * [License](#license) 20 | * [TODO's](#todos) 21 | 22 | ## Background 23 | 24 | This package aims to provide tools needed to define custom field validation logic which can be used independently or with 25 | django forms, test cases, API implementation or any model operation that requires saving data to the database. 26 | 27 | This can also be extended by defining check constraints if needed but currently validation 28 | will only be handled at the model level. 29 | 30 | ## Installation 31 | 32 | ```shell script 33 | pip install django-extra-field-validation 34 | ``` 35 | 36 | ## Usage 37 | 38 | ### Require all fields 39 | 40 | ```py 41 | 42 | from django.db import models 43 | from extra_validator import FieldValidationMixin 44 | 45 | 46 | class TestModel(FieldValidationMixin, models.Model): 47 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 48 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 49 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 50 | 51 | REQUIRED_FIELDS = ['amount'] # Always requires an amount to create the instance. 52 | ``` 53 | 54 | Example 55 | 56 | ```python 57 | In [1]: from decimal import Decimal 58 | 59 | In [2]: from demo.models import TestModel 60 | 61 | In [3]: TestModel.objects.create(fixed_price=Decimal('3.00')) 62 | --------------------------------------------------------------------------- 63 | ValueError Traceback (most recent call last) 64 | ... 65 | 66 | ValueError: {'amount': ValidationError([u'Please provide a value for: "amount".'])} 67 | 68 | ``` 69 | 70 | ### Require at least one field in a collection 71 | 72 | ```py 73 | 74 | from django.db import models 75 | from extra_validator import FieldValidationMixin 76 | 77 | 78 | class TestModel(FieldValidationMixin, models.Model): 79 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 80 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 81 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 82 | 83 | REQUIRED_TOGGLE_FIELDS = [ 84 | ['amount', 'fixed_price', 'percentage'], # Require only one of the following fields. 85 | ] 86 | 87 | ``` 88 | 89 | Example 90 | 91 | ```python 92 | In [1]: from decimal import Decimal 93 | 94 | In [2]: from demo.models import TestModel 95 | 96 | In [3]: TestModel.objects.create(amount=Decimal('2.50'), fixed_price=Decimal('3.00')) 97 | --------------------------------------------------------------------------- 98 | ValueError Traceback (most recent call last) 99 | ... 100 | 101 | ValueError: {'fixed_price': ValidationError([u'Please provide only one of: Amount, Fixed price, Percentage'])} 102 | 103 | ``` 104 | 105 | ### Optionally require at least one field in a collection 106 | 107 | ```py 108 | 109 | from django.db import models 110 | from extra_validator import FieldValidationMixin 111 | 112 | 113 | class TestModel(FieldValidationMixin, models.Model): 114 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 115 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 116 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 117 | 118 | OPTIONAL_TOGGLE_FIELDS = [ 119 | ['fixed_price', 'percentage'] # Optionally validates that only fixed price/percentage are provided when present. 120 | ] 121 | 122 | ``` 123 | 124 | Example 125 | 126 | ```python 127 | In [1]: from decimal import Decimal 128 | 129 | In [2]: from demo.models import TestModel 130 | 131 | In [3]: first_obj = TestModel.objects.create(amount=Decimal('2.0')) 132 | 133 | In [4]: second_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00')) 134 | 135 | In [5]: third_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00'), percentage=Decimal('10.0')) 136 | --------------------------------------------------------------------------- 137 | ValueError Traceback (most recent call last) 138 | ... 139 | 140 | ValueError: {'percentage': ValidationError([u'Please provide only one of: Fixed price, Percentage'])} 141 | 142 | ``` 143 | 144 | ### Conditionally require all fields 145 | 146 | ```py 147 | 148 | from django.db import models 149 | from django.conf import settings 150 | from extra_validator import FieldValidationMixin 151 | 152 | 153 | class TestModel(FieldValidationMixin, models.Model): 154 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 155 | 156 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 157 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 158 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 159 | 160 | CONDITIONAL_REQUIRED_FIELDS = [ 161 | ( 162 | lambda instance: instance.user.is_active, ['amount', 'percentage'], 163 | ), 164 | ] 165 | 166 | ``` 167 | 168 | Example 169 | 170 | ```python 171 | In [1]: from decimal import Decimal 172 | 173 | in [2]: from django.contrib.auth import get_user_model 174 | 175 | In [3]: from demo.models import TestModel 176 | 177 | In [4]: user = get_user_model().objects.create(username='test', is_active=True) 178 | 179 | In [5]: first_obj = TestModel.objects.create(user=user, amount=Decimal('2.0')) 180 | --------------------------------------------------------------------------- 181 | ValueError Traceback (most recent call last) 182 | ... 183 | 184 | ValueError: {u'percentage': ValidationError([u'Please provide a value for: "percentage"'])} 185 | 186 | ``` 187 | 188 | ### Conditionally require at least one field in a collection 189 | 190 | ```py 191 | 192 | from django.db import models 193 | from django.conf import settings 194 | from extra_validator import FieldValidationMixin 195 | 196 | 197 | class TestModel(FieldValidationMixin, models.Model): 198 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 199 | 200 | amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) 201 | fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True) 202 | percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True) 203 | 204 | CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [ 205 | ( 206 | lambda instance: instance.user.is_active, ['fixed_price', 'percentage', 'amount'], 207 | ), 208 | ] 209 | ``` 210 | 211 | Example 212 | 213 | ```python 214 | In [1]: from decimal import Decimal 215 | 216 | in [2]: from django.contrib.auth import get_user_model 217 | 218 | In [3]: from demo.models import TestModel 219 | 220 | In [4]: user = get_user_model().objects.create(username='test', is_active=True) 221 | 222 | In [5]: first_obj = TestModel.objects.create(user=user) 223 | --------------------------------------------------------------------------- 224 | ValueError Traceback (most recent call last) 225 | ... 226 | 227 | ValueError: {'__all__': ValidationError([u'Please provide a valid value for any of the following fields: Fixed price, Percentage, Amount'])} 228 | 229 | In [6]: second_obj = TestModel.objects.create(user=user, amount=Decimal('2'), fixed_price=Decimal('2')) 230 | --------------------------------------------------------------------------- 231 | ValueError Traceback (most recent call last) 232 | ... 233 | 234 | ValueError: {'__all__': ValidationError([u'Please provide only one of the following fields: Fixed price, Percentage, Amount'])} 235 | ``` 236 | 237 | ## Model Attributes 238 | 239 | This is done using model attributes below. 240 | 241 | ```py 242 | # A list of required fields 243 | REQUIRED_FIELDS = [] 244 | 245 | # A list of fields with at most one required. 246 | REQUIRED_TOGGLE_FIELDS = [] 247 | 248 | # A list of field with at least one required. 249 | REQUIRED_MIN_FIELDS = [] 250 | 251 | # Optional list of fields with at most one required. 252 | OPTIONAL_TOGGLE_FIELDS = [] 253 | 254 | # Conditional field required list of tuples the condition a boolean or a callable. 255 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' or 'last_name' 256 | # If condition is True ensure that all fields are set 257 | CONDITIONAL_REQUIRED_FIELDS = [] 258 | 259 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Either 'first_name' or 'last_name' 260 | # If condition is True ensure that at most one field is set 261 | CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [] 262 | 263 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : At least 'first_name' or 'last_name' provided or both 264 | # If condition is True ensure that at least one field is set 265 | CONDITIONAL_REQUIRED_MIN_FIELDS = [] 266 | 267 | # [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' and 'last_name' isn't provided 268 | # If condition is True ensure none of the fields are provided 269 | CONDITIONAL_REQUIRED_EMPTY_FIELDS = [] 270 | 271 | ``` 272 | 273 | ## License 274 | 275 | django-extra-field-validation is distributed under the terms of both 276 | 277 | * [MIT License](https://choosealicense.com/licenses/mit) 278 | * [Apache License, Version 2.0](https://choosealicense.com/licenses/apache-2.0) 279 | 280 | at your option. 281 | 282 | ## TODO's 283 | 284 | * \[ ] Support `CONDITIONAL_NON_REQUIRED_TOGGLE_FIELDS` 285 | * \[ ] Support `CONDITIONAL_NON_REQUIRED_FIELDS` 286 | * \[ ] Move to support class and function based validators that use the instance object this should enable cross field model validation. 287 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html lang="en"> 3 | <head> 4 | <meta charset="UTF-8"> 5 | <title>Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Please wait...
14 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /extra_validator/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for django-extra-field-validation.""" 2 | 3 | __author__ = """Tonye Jack""" 4 | __email__ = "jtonye@ymail.com" 5 | __version__ = "1.2.3" 6 | 7 | from .field_validation import FieldValidationMixin 8 | 9 | __all__ = ["FieldValidationMixin"] 10 | -------------------------------------------------------------------------------- /extra_validator/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DynamicValidatorConfig(AppConfig): 5 | name = "extra_validator" 6 | -------------------------------------------------------------------------------- /extra_validator/field_validation/__init__.py: -------------------------------------------------------------------------------- 1 | from .validator import FieldValidationMixin # noqa 2 | -------------------------------------------------------------------------------- /extra_validator/field_validation/validator.py: -------------------------------------------------------------------------------- 1 | import django 2 | from django.core import validators 3 | from django.core.exceptions import NON_FIELD_ERRORS, ValidationError 4 | from django.db import models 5 | 6 | if django.VERSION <= (3, 0): 7 | from django.utils.translation import ugettext as _ 8 | else: 9 | from django.utils.translation import gettext as _ 10 | 11 | 12 | def field_to_str(fields): 13 | return _( 14 | ", ".join( 15 | map(lambda fname: fname.replace("_", " ").strip().capitalize(), fields) 16 | ), 17 | ) 18 | 19 | 20 | class FieldValidationMixin(object): 21 | # A list of required fields 22 | REQUIRED_FIELDS = [] 23 | # A list of fields with at most one required. 24 | REQUIRED_TOGGLE_FIELDS = [] 25 | # A list of field with at least one required. 26 | REQUIRED_MIN_FIELDS = [] 27 | # Optional list of fields with at most one required. 28 | OPTIONAL_TOGGLE_FIELDS = [] 29 | 30 | # Conditional field required list of tuples the condition a boolean or a callable. 31 | # (lambda o: o.offer_type != Offer.OfferType.OTHER.value, ['offer_detail_id', 'content_type']) 32 | # If condition is True ensure that all fields are set 33 | CONDITIONAL_REQUIRED_FIELDS = [] 34 | # If condition is True ensure that at most one field is set 35 | CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [] 36 | # If condition is True ensure that at least one field is set 37 | CONDITIONAL_REQUIRED_MIN_FIELDS = [] 38 | # If condition is True ensure none of the fields are provided 39 | CONDITIONAL_REQUIRED_EMPTY_FIELDS = [] 40 | 41 | ERROR_HANDLERS = {"form": ValidationError} 42 | 43 | EMPTY_VALUES = list(validators.EMPTY_VALUES) 44 | 45 | @staticmethod 46 | def _error_as_dict(field, msg, code="required", error_class=ValidationError): 47 | return {field: error_class(_(msg), code=code)} 48 | 49 | def _validate_only_one_option(self, selections, found_keys): 50 | error_dict = {} 51 | for section in selections: 52 | provided_fields = [] 53 | for key in found_keys: 54 | if key in section: 55 | provided_fields.append(key) 56 | if len(set(provided_fields)) > 1: 57 | msg = "Please provide only one of: {fields}".format( 58 | fields=field_to_str(section) 59 | ) 60 | error_dict.update( 61 | self._error_as_dict(provided_fields[-1], msg, code="invalid") 62 | ) 63 | break 64 | return error_dict 65 | 66 | def _clean_conditional_toggle_fields(self, exclude=None, context=None): 67 | if self.CONDITIONAL_REQUIRED_TOGGLE_FIELDS: 68 | return self._clean_conditional_fields( 69 | exclude, 70 | context, 71 | field_sets=self.CONDITIONAL_REQUIRED_TOGGLE_FIELDS, 72 | validate_one=True, 73 | ) 74 | 75 | def _clean_conditional_min_fields(self, exclude=None, context=None): 76 | if self.CONDITIONAL_REQUIRED_MIN_FIELDS: 77 | return self._clean_conditional_fields( 78 | exclude, 79 | context, 80 | field_sets=self.CONDITIONAL_REQUIRED_MIN_FIELDS, 81 | at_least_one=True, 82 | ) 83 | 84 | def _clean_conditional_empty_fields(self, exclude=None, context=None): 85 | if self.CONDITIONAL_REQUIRED_EMPTY_FIELDS: 86 | return self._clean_conditional_fields( 87 | exclude, 88 | context, 89 | field_sets=self.CONDITIONAL_REQUIRED_EMPTY_FIELDS, 90 | none_provided=True, 91 | ) 92 | 93 | def _clean_conditional_fields( 94 | self, 95 | exclude=None, 96 | context=None, 97 | field_sets=(), 98 | validate_one=False, 99 | at_least_one=False, 100 | none_provided=False, 101 | ): 102 | error_class = self.ERROR_HANDLERS.get(context, ValueError) 103 | exclude = exclude or [] 104 | errors = {} 105 | field_sets = field_sets or self.CONDITIONAL_REQUIRED_FIELDS 106 | 107 | if all([field_sets, isinstance(field_sets, (list, tuple))]): 108 | for condition, fields in field_sets: 109 | field_names = list(filter(lambda f: f not in exclude, fields)) 110 | if field_names: 111 | is_valid_condition = ( 112 | condition if isinstance(condition, bool) else False 113 | ) 114 | if callable(condition): 115 | is_valid_condition = condition(self) 116 | 117 | field_value_map = { 118 | field_name: getattr(self, field_name) 119 | for field_name in field_names 120 | if getattr(self, field_name) not in self.EMPTY_VALUES 121 | } 122 | 123 | if is_valid_condition: 124 | if not field_value_map and not none_provided: 125 | if len(fields) > 1: 126 | msg = ( 127 | "Please provide a valid value for the following fields: " 128 | "{fields}" 129 | if not validate_one 130 | else "Please provide a valid value for any of the following " 131 | "fields: {fields}".format( 132 | fields=field_to_str(fields) 133 | ) 134 | ) 135 | errors.update( 136 | self._error_as_dict(NON_FIELD_ERRORS, msg) 137 | ) 138 | else: 139 | field = fields[0] 140 | msg = 'Please provide a value for: "{field}"'.format( 141 | field=field 142 | ) 143 | errors.update(self._error_as_dict(field, msg)) 144 | break 145 | 146 | if field_value_map and none_provided: 147 | msg = "Please omit changes to the following fields: {fields}".format( 148 | fields=field_to_str(fields) 149 | ) 150 | errors.update(self._error_as_dict(NON_FIELD_ERRORS, msg)) 151 | break 152 | 153 | missing_fields = [ 154 | field_name 155 | for field_name in fields 156 | if field_name not in field_value_map.keys() 157 | ] 158 | 159 | if not validate_one and not at_least_one and not none_provided: 160 | for missing_field in missing_fields: 161 | msg = 'Please provide a value for: "{missing_field}"'.format( 162 | missing_field=missing_field 163 | ) 164 | errors.update(self._error_as_dict(missing_field, msg)) 165 | 166 | elif validate_one and len(fields) - 1 != len( 167 | list(missing_fields) 168 | ): 169 | msg = ( 170 | "Please provide only one of the following fields: " 171 | "{fields}".format(fields=field_to_str(fields)) 172 | ) 173 | errors.update(self._error_as_dict(NON_FIELD_ERRORS, msg)) 174 | 175 | if errors: 176 | raise error_class(errors) 177 | 178 | def _clean_required_and_optional_fields(self, exclude=None, context=None): 179 | """Provide extra validation for fields that are required and single selection fields.""" 180 | exclude = exclude or [] 181 | if any( 182 | [ 183 | self.REQUIRED_TOGGLE_FIELDS, 184 | self.REQUIRED_MIN_FIELDS, 185 | self.REQUIRED_FIELDS, 186 | self.OPTIONAL_TOGGLE_FIELDS, 187 | ] 188 | ): 189 | error_class = self.ERROR_HANDLERS.get(context, ValueError) 190 | found = [] 191 | errors = {} 192 | optional = [] 193 | 194 | for f in self._meta.fields: 195 | if f.name not in exclude: 196 | raw_value = getattr(self, f.attname) 197 | if f.name in self.REQUIRED_FIELDS: 198 | if raw_value in f.empty_values and not f.has_default(): 199 | msg = 'Please provide a value for: "{field_name}".'.format( 200 | field_name=f.name 201 | ) 202 | errors.update(self._error_as_dict(f.name, msg)) 203 | 204 | for required_min_field in self.REQUIRED_MIN_FIELDS: 205 | # Multiple selection of at least one required. 206 | if f.name in required_min_field: 207 | if raw_value not in f.empty_values: 208 | found.append({f.name: raw_value}) 209 | elif raw_value in f.empty_values and f.has_default(): 210 | if ( 211 | isinstance(f, models.CharField) 212 | and f.get_default() not in f.empty_values 213 | ): 214 | found.append({f.name: f.get_default()}) 215 | 216 | for required_toggle_field in self.REQUIRED_TOGGLE_FIELDS: 217 | # Single selection of at most one required. 218 | if f.name in required_toggle_field: 219 | if raw_value not in f.empty_values: 220 | found.append({f.name: raw_value}) 221 | elif raw_value in f.empty_values and f.has_default(): 222 | if ( 223 | isinstance(f, models.CharField) 224 | and f.get_default() not in f.empty_values 225 | ): 226 | found.append({f.name: f.get_default()}) 227 | 228 | for optional_toggle_field in self.OPTIONAL_TOGGLE_FIELDS: 229 | if ( 230 | f.name in optional_toggle_field 231 | and raw_value not in f.empty_values 232 | ): 233 | optional.append({f.name: raw_value}) 234 | 235 | if self.REQUIRED_MIN_FIELDS: 236 | if not found: 237 | fields_str = "\n, ".join( 238 | [field_to_str(fields) for fields in self.REQUIRED_MIN_FIELDS] 239 | ) 240 | fields_str = ( 241 | "\n {fields}".format(fields=fields_str) 242 | if len(self.REQUIRED_MIN_FIELDS) > 1 243 | else fields_str 244 | ) 245 | msg = "Please provide a valid value for any of the following fields: {fields}".format( 246 | fields=fields_str 247 | ) 248 | errors.update(self._error_as_dict(NON_FIELD_ERRORS, msg)) 249 | 250 | if self.REQUIRED_TOGGLE_FIELDS: 251 | if not found: 252 | fields_str = "\n, ".join( 253 | [field_to_str(fields) for fields in self.REQUIRED_TOGGLE_FIELDS] 254 | ) 255 | fields_str = ( 256 | "\n {fields}".format(fields=fields_str) 257 | if len(self.REQUIRED_TOGGLE_FIELDS) > 1 258 | else fields_str 259 | ) 260 | msg = "Please provide a valid value for any of the following fields: {fields}".format( 261 | fields=fields_str 262 | ) 263 | errors.update(self._error_as_dict(NON_FIELD_ERRORS, msg)) 264 | else: 265 | found_keys = [k for item in found for k in item.keys()] 266 | errors.update( 267 | self._validate_only_one_option( 268 | self.REQUIRED_TOGGLE_FIELDS, found_keys 269 | ), 270 | ) 271 | 272 | if self.OPTIONAL_TOGGLE_FIELDS: 273 | if optional: 274 | optional_keys = [k for item in optional for k in item.keys()] 275 | errors.update( 276 | self._validate_only_one_option( 277 | self.OPTIONAL_TOGGLE_FIELDS, optional_keys 278 | ), 279 | ) 280 | 281 | if errors: 282 | raise error_class(errors) 283 | 284 | def clean_fields(self, exclude=None): 285 | self._clean_conditional_toggle_fields(exclude=exclude, context="form") 286 | self._clean_conditional_min_fields(exclude=exclude, context="form") 287 | self._clean_conditional_empty_fields(exclude=exclude, context="form") 288 | self._clean_conditional_fields(exclude=exclude, context="form") 289 | self._clean_required_and_optional_fields(exclude=exclude, context="form") 290 | return super(FieldValidationMixin, self).clean_fields(exclude=exclude) 291 | 292 | def save(self, *args, **kwargs): 293 | self._clean_conditional_toggle_fields() 294 | self._clean_conditional_min_fields() 295 | self._clean_conditional_empty_fields() 296 | self._clean_conditional_fields() 297 | self._clean_required_and_optional_fields() 298 | return super(FieldValidationMixin, self).save(*args, **kwargs) 299 | -------------------------------------------------------------------------------- /extra_validator/tests.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.test import TestCase 3 | 4 | User = get_user_model() 5 | 6 | 7 | class FieldValidationTestCase(TestCase): 8 | @classmethod 9 | def setUpTestData(cls): 10 | cls.super_user = User.objects.create( 11 | username="super-test-user", is_superuser=True 12 | ) 13 | cls.user = User.objects.create(username="test-user") 14 | 15 | def test_conditional_required_field_raises_exception_when_missing(self): 16 | from demo.models import TestModel 17 | 18 | TestModel.CONDITIONAL_REQUIRED_FIELDS = [ 19 | ( 20 | lambda instance: instance.user.is_active, 21 | ["percentage"], 22 | ), 23 | ] 24 | 25 | with self.assertRaises(ValueError): 26 | TestModel.objects.create(user=self.user) 27 | 28 | def test_conditional_required_field_is_valid(self): 29 | from demo.models import TestModel 30 | 31 | TestModel.CONDITIONAL_REQUIRED_FIELDS = [ 32 | ( 33 | lambda instance: instance.user.is_active, 34 | ["percentage"], 35 | ), 36 | ] 37 | 38 | TestModel.objects.create(user=self.user, percentage=25) 39 | 40 | def test_conditional_required_toggle_field_raises_exception_when_missing(self): 41 | from demo.models import TestModel 42 | 43 | TestModel.CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [ 44 | ( 45 | lambda instance: instance.user.is_active, 46 | ["fixed_price", "percentage", "amount"], 47 | ), 48 | ] 49 | 50 | with self.assertRaises(ValueError): 51 | TestModel.objects.create(user=self.user) 52 | 53 | def test_conditional_required_toggle_field_raises_exception_with_2_fields( 54 | self, 55 | ): 56 | from demo.models import TestModel 57 | 58 | TestModel.CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [ 59 | ( 60 | lambda instance: instance.user.is_active, 61 | ["fixed_price", "percentage", "amount"], 62 | ), 63 | ] 64 | 65 | with self.assertRaises(ValueError): 66 | TestModel.objects.create(user=self.user, percentage=25, fixed_price=10) 67 | 68 | def test_conditional_required_toggle_field_is_valid(self): 69 | from demo.models import TestModel 70 | 71 | TestModel.CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [ 72 | ( 73 | lambda instance: instance.user.is_active, 74 | ["fixed_price", "percentage", "amount"], 75 | ), 76 | ] 77 | 78 | TestModel.objects.create(user=self.user, percentage=25) 79 | 80 | def test_required_fields_raises_exception(self): 81 | from demo.models import TestModel 82 | 83 | TestModel.REQUIRED_FIELDS = ["percentage"] 84 | 85 | with self.assertRaises(ValueError): 86 | TestModel.objects.create(user=self.user) 87 | 88 | def test_providing_a_required_field_saves_the_instance(self): 89 | from demo.models import TestModel 90 | 91 | TestModel.REQUIRED_FIELDS = ["percentage"] 92 | 93 | obj = TestModel.objects.create(user=self.user, percentage=25) 94 | 95 | self.assertEqual(obj.percentage, 25) 96 | 97 | def test_providing_more_than_one_required_field_raises_an_error(self): 98 | from demo.models import TestModel 99 | 100 | TestModel.REQUIRED_FIELDS = ["percentage", "fixed_price"] 101 | 102 | with self.assertRaises(ValueError): 103 | TestModel.objects.create(user=self.user, percentage=25, fixed_price=10) 104 | 105 | def test_optional_required_fields_is_valid(self): 106 | from demo.models import TestModel 107 | 108 | TestModel.REQUIRED_TOGGLE_FIELDS = ["fixed_price", "percentage", "amount"] 109 | 110 | TestModel.objects.create(user=self.user, percentage=25) 111 | 112 | def test_optional_required_fields_raised_exception_when_invalid(self): 113 | from demo.models import TestModel 114 | 115 | TestModel.REQUIRED_TOGGLE_FIELDS = ["fixed_price", "percentage", "amount"] 116 | 117 | with self.assertRaises(ValueError): 118 | TestModel.objects.create(user=self.user) 119 | TestModel.objects.create(user=self.user, percentage=25, amount=25) 120 | TestModel.objects.create(user=self.user, fixed_price=25, amount=25) 121 | 122 | def test_optional_toggle_fields_is_valid(self): 123 | from demo.models import TestModel 124 | 125 | TestModel.OPTIONAL_TOGGLE_FIELDS = ["fixed_price", "percentage", "amount"] 126 | 127 | TestModel.objects.create(user=self.user, percentage=25) 128 | 129 | def test_optional_toggle_fields_raised_exception_when_invalid(self): 130 | from demo.models import TestModel 131 | 132 | TestModel.OPTIONAL_TOGGLE_FIELDS = ["fixed_price", "percentage", "amount"] 133 | 134 | with self.assertRaises(ValueError): 135 | TestModel.objects.create(user=self.user, percentage=25, amount=25) 136 | TestModel.objects.create(user=self.user, fixed_price=25, amount=25) 137 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault( 7 | "DJANGO_SETTINGS_MODULE", "django_extra_field_validation.settings" 8 | ) 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) 17 | execute_from_command_line(sys.argv) 18 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = 'django-extra-field-validation' 3 | version = '0.0.1' 4 | description = '' 5 | author = 'Tonye Jack' 6 | author_email = 'jtonye@ymail.com' 7 | license = 'MIT/Apache-2.0' 8 | url = 'https://github.com/tj-django/django-extra-field-validation.git' 9 | 10 | [requires] 11 | python_version = ['2.7', '3.5', '3.6', 'pypy', 'pypy3'] 12 | 13 | [build-system] 14 | requires = ['setuptools', 'wheel'] 15 | 16 | [tool.hatch.commands] 17 | prerelease = 'hatch build' 18 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = django_extra_field_validation.settings 3 | python_files = tests.py test_*.py *_tests.py 4 | testpaths = extra_validator demo 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "enabled": true, 6 | "prHourlyLimit": 10, 7 | "prConcurrentLimit": 5, 8 | "rebaseWhen": "behind-base-branch", 9 | "addLabels": [ 10 | "dependencies" 11 | ], 12 | "assignees": [ 13 | "jackton1" 14 | ], 15 | "assignAutomerge": true, 16 | "dependencyDashboard": true, 17 | "dependencyDashboardAutoclose": true, 18 | "lockFileMaintenance": { 19 | "enabled": true, 20 | "automerge": true 21 | }, 22 | "packageRules": [ 23 | { 24 | "matchUpdateTypes": ["major", "minor", "patch", "pin", "digest"], 25 | "automerge": true, 26 | "rebaseWhen": "behind-base-branch", 27 | "addLabels": [ 28 | "automerge" 29 | ] 30 | }, 31 | { 32 | "description": "docker images", 33 | "matchLanguages": [ 34 | "docker" 35 | ], 36 | "matchUpdateTypes": ["major", "minor", "patch", "pin", "digest"], 37 | "rebaseWhen": "behind-base-branch", 38 | "addLabels": [ 39 | "automerge" 40 | ], 41 | "automerge": true 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile with Python 3.9 3 | # by the following command: 4 | # 5 | # pip-compile 6 | # 7 | asgiref==3.8.1 8 | # via django 9 | django==5.1.6 10 | # via django-extra-field-validation (setup.py) 11 | sqlparse==0.5.3 12 | # via django 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | 4 | from setuptools import find_packages, setup 5 | 6 | install_requires = ["Django"] 7 | 8 | test_requires = [ 9 | "tox", 10 | "pytest-django", 11 | "pluggy>=0.7", 12 | "mock", 13 | "codacy-coverage", 14 | ] 15 | 16 | deploy_requires = [ 17 | "readme_renderer[md]", 18 | "bump2version==1.0.1", 19 | ] 20 | 21 | lint_requires = [ 22 | "flake8", 23 | "yamllint", 24 | "isort", 25 | ] 26 | 27 | local_dev_requires = [ 28 | "pip-tools", 29 | "check-manifest", 30 | ] 31 | 32 | extras_require = { 33 | "development": [ 34 | local_dev_requires, 35 | install_requires, 36 | test_requires, 37 | lint_requires, 38 | ], 39 | "test": test_requires, 40 | "lint": lint_requires, 41 | "deploy": deploy_requires, 42 | "tox": local_dev_requires, 43 | } 44 | 45 | BASE_DIR = os.path.dirname(__file__) 46 | README_PATH = os.path.join(BASE_DIR, "README.md") 47 | 48 | if os.path.isfile(README_PATH): 49 | with io.open(README_PATH, encoding="utf-8") as f: 50 | LONG_DESCRIPTION = f.read() 51 | else: 52 | LONG_DESCRIPTION = "" 53 | 54 | 55 | setup( 56 | name="django-extra-field-validation", 57 | version="1.2.3", 58 | description="Extra django field validation.", 59 | python_requires=">=2.6", 60 | long_description=LONG_DESCRIPTION, 61 | long_description_content_type="text/markdown", 62 | author="Tonye Jack", 63 | author_email="jtonye@ymail.com", 64 | maintainer="Tonye Jack", 65 | maintainer_email="jtonye@ymail.com", 66 | url="https://github.com/tj-django/django-extra-field-validation.git", 67 | license="MIT/Apache-2.0", 68 | keywords=[ 69 | "django", 70 | "model validation", 71 | "django models", 72 | "django object validation", 73 | "field validation", 74 | "conditional validation", 75 | "cross field validation", 76 | "django validation", 77 | "django validators", 78 | "django custom validation", 79 | ], 80 | classifiers=[ 81 | "Development Status :: 5 - Production/Stable", 82 | "Intended Audience :: Developers", 83 | "License :: OSI Approved :: MIT License", 84 | "License :: OSI Approved :: Apache Software License", 85 | "Natural Language :: English", 86 | "Topic :: Internet :: WWW/HTTP", 87 | "Operating System :: OS Independent", 88 | "Programming Language :: Python :: 3.6", 89 | "Programming Language :: Python :: 3.7", 90 | "Programming Language :: Python :: 3.8", 91 | "Programming Language :: Python :: 3.9", 92 | "Programming Language :: Python :: Implementation :: CPython", 93 | "Programming Language :: Python :: Implementation :: PyPy", 94 | "Framework :: Django :: 2.0", 95 | "Framework :: Django :: 2.1", 96 | "Framework :: Django :: 2.2", 97 | "Framework :: Django :: 3.0", 98 | "Framework :: Django :: 3.1", 99 | ], 100 | install_requires=install_requires, 101 | tests_require=["coverage", "pytest"], 102 | extras_require=extras_require, 103 | packages=find_packages( 104 | exclude=["*.tests", "*.tests.*", "tests.*", "tests*", "demo", "manage.*"] 105 | ), 106 | ) 107 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.1.2 3 | skip_missing_interpreters = true 4 | 5 | envlist = 6 | py36-django{20,21,22,30,31,32,main}-{linux,macos,windows} 7 | py37-django{20,21,22,30,31,32,main}-{linux,macos,windows} 8 | py38-django{21,22,30,31,32,40,41,main}-{linux,macos,windows} 9 | py39-django{21,22,30,31,32,40,41,main}-{linux,macos,windows} 10 | py310-django{22,30,31,32,40,41,main}-{linux,macos,windows} 11 | py311-django{22,30,31,32,40,41,main}-{linux} 12 | 13 | [gh-actions] 14 | python = 15 | 3.6: py36 16 | 3.7: py37 17 | 3.8: py38 18 | 3.9: py39 19 | 3.10: py310 20 | 3.11: py311 21 | 22 | [gh-actions:env] 23 | PLATFORM = 24 | ubuntu-latest: linux 25 | macos-latest: macos 26 | windows-latest: windows 27 | 28 | [testenv] 29 | extras = tox 30 | passenv = * 31 | deps = 32 | django20: Django>=2.0,<2.1 33 | django21: Django>=2.1,<2.2 34 | django22: Django>=2.2,<2.3 35 | django30: Django>=3.0,<3.1 36 | django31: Django>=3.1,<3.2 37 | django32: Django>=3.2,<3.3 38 | django40: Django>=4.0,<4.1 39 | django41: Django>=4.1,<4.2 40 | main: https://github.com/django/django/archive/main.tar.gz 41 | coverage 42 | pytest-django 43 | codacy-coverage 44 | usedevelop = False 45 | commands = 46 | python -c "import django; print(django.VERSION)" 47 | coverage run -m pytest 48 | coverage report -m 49 | coverage xml 50 | - python-codacy-coverage -r coverage.xml 51 | 52 | 53 | [flake8] 54 | max-line-length = 120 55 | --------------------------------------------------------------------------------