{"id":14972907,"url":"https://github.com/tj-django/django-extra-field-validation","last_synced_at":"2025-06-16T06:04:20.980Z","repository":{"id":37760894,"uuid":"134006032","full_name":"tj-django/django-extra-field-validation","owner":"tj-django","description":"Extends the Django model with required, conditional, and optional field validation.","archived":false,"fork":false,"pushed_at":"2025-06-06T21:35:22.000Z","size":388,"stargazers_count":27,"open_issues_count":19,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-09T11:16:28.354Z","etag":null,"topics":["django","django-application","django-forms","django-instance-validation","django-model-validation","django-model-validators","django-validator","django-validators","field-validation","python","validation"],"latest_commit_sha":null,"homepage":"https://tj-django.github.io/django-extra-field-validation/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tj-django.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"jackton1","patreon":null,"open_collective":"tj-django","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":[]}},"created_at":"2018-05-18T21:30:09.000Z","updated_at":"2025-02-05T15:08:21.000Z","dependencies_parsed_at":"2023-09-28T10:12:36.461Z","dependency_job_id":"ece21e00-53c6-4f12-9b86-5a2a6873450a","html_url":"https://github.com/tj-django/django-extra-field-validation","commit_stats":{"total_commits":618,"total_committers":11,"mean_commits":56.18181818181818,"dds":0.5938511326860841,"last_synced_commit":"2c219cb53765ed61fbd21b688cc02c4bde14a234"},"previous_names":["jackton1/django-dynamic-model-validation"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/tj-django/django-extra-field-validation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-extra-field-validation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-extra-field-validation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-extra-field-validation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-extra-field-validation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tj-django","download_url":"https://codeload.github.com/tj-django/django-extra-field-validation/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-extra-field-validation/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260109459,"owners_count":22960025,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["django","django-application","django-forms","django-instance-validation","django-model-validation","django-model-validators","django-validator","django-validators","field-validation","python","validation"],"created_at":"2024-09-24T13:47:43.665Z","updated_at":"2025-06-16T06:04:20.949Z","avatar_url":"https://github.com/tj-django.png","language":"Python","funding_links":["https://github.com/sponsors/jackton1","https://opencollective.com/tj-django"],"categories":[],"sub_categories":[],"readme":"# django-extra-field-validation\n\n![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)\n\n[![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)\n[![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\\\u0026utm_medium=referral\\\u0026utm_content=tj-django/django-extra-field-validation\\\u0026utm_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\\\u0026utm_medium=referral\\\u0026utm_content=tj-django/django-extra-field-validation\\\u0026utm_campaign=Badge_Coverage)\n\n## Table of Contents\n\n*   [Background](#background)\n*   [Installation](#installation)\n*   [Usage](#usage)\n    *   [Require all fields](#require-all-fields)\n    *   [Require at least one field in a collection](#require-at-least-one-field-in-a-collection)\n    *   [Optionally require at least one field in a collection](#optionally-require-at-least-one-field-in-a-collection)\n    *   [Conditionally require all fields](#conditionally-require-all-fields)\n    *   [Conditionally require at least one field in a collection](#conditionally-require-at-least-one-field-in-a-collection)\n*   [Model Attributes](#model-attributes)\n*   [License](#license)\n*   [TODO's](#todos)\n\n## Background\n\nThis package aims to provide tools needed to define custom field validation logic which can be used independently or with\ndjango forms, test cases, API implementation or any model operation that requires saving data to the database.\n\nThis can also be extended by defining check constraints if needed but currently validation\nwill only be handled at the model level.\n\n## Installation\n\n```shell script\npip install django-extra-field-validation\n```\n\n## Usage\n\n### Require all fields\n\n```py\n\nfrom django.db import models\nfrom extra_validator import FieldValidationMixin\n\n\nclass TestModel(FieldValidationMixin, models.Model):\n    amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)\n    fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)\n    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)\n\n    REQUIRED_FIELDS = ['amount']  # Always requires an amount to create the instance.\n```\n\nExample\n\n```python\nIn [1]: from decimal import Decimal\n\nIn [2]: from demo.models import TestModel\n\nIn [3]: TestModel.objects.create(fixed_price=Decimal('3.00'))\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {'amount': ValidationError([u'Please provide a value for: \"amount\".'])}\n\n```\n\n### Require at least one field in a collection\n\n```py\n\nfrom django.db import models\nfrom extra_validator import FieldValidationMixin\n\n\nclass TestModel(FieldValidationMixin, models.Model):\n    amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)\n    fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)\n    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)\n\n    REQUIRED_TOGGLE_FIELDS = [\n        ['amount', 'fixed_price', 'percentage'],  # Require only one of the following fields.\n    ]\n\n```\n\nExample\n\n```python\nIn [1]: from decimal import Decimal\n\nIn [2]: from demo.models import TestModel\n\nIn [3]: TestModel.objects.create(amount=Decimal('2.50'), fixed_price=Decimal('3.00'))\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {'fixed_price': ValidationError([u'Please provide only one of: Amount, Fixed price, Percentage'])}\n\n```\n\n### Optionally require at least one field in a collection\n\n```py\n\nfrom django.db import models\nfrom extra_validator import FieldValidationMixin\n\n\nclass TestModel(FieldValidationMixin, models.Model):\n    amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)\n    fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)\n    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)\n\n    OPTIONAL_TOGGLE_FIELDS = [\n        ['fixed_price', 'percentage']  # Optionally validates that only fixed price/percentage are provided when present.\n    ]\n\n```\n\nExample\n\n```python\nIn [1]: from decimal import Decimal\n\nIn [2]: from demo.models import TestModel\n\nIn [3]: first_obj = TestModel.objects.create(amount=Decimal('2.0'))\n\nIn [4]: second_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00'))\n\nIn [5]: third_obj = TestModel.objects.create(amount=Decimal('2.0'), fixed_price=Decimal('3.00'), percentage=Decimal('10.0'))\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {'percentage': ValidationError([u'Please provide only one of: Fixed price, Percentage'])}\n\n```\n\n### Conditionally require all fields\n\n```py\n\nfrom django.db import models\nfrom django.conf import settings\nfrom extra_validator import FieldValidationMixin\n\n\nclass TestModel(FieldValidationMixin, models.Model):\n    user = models.ForeignKey(settings.AUTH_USER_MODEL)\n\n    amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)\n    fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)\n    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)\n\n    CONDITIONAL_REQUIRED_FIELDS = [\n        (\n            lambda instance: instance.user.is_active, ['amount', 'percentage'],\n        ),\n    ]\n\n```\n\nExample\n\n```python\nIn [1]: from decimal import Decimal\n\nin [2]: from django.contrib.auth import get_user_model\n\nIn [3]: from demo.models import TestModel\n\nIn [4]: user = get_user_model().objects.create(username='test', is_active=True)\n\nIn [5]: first_obj = TestModel.objects.create(user=user, amount=Decimal('2.0'))\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {u'percentage': ValidationError([u'Please provide a value for: \"percentage\"'])}\n\n```\n\n### Conditionally require at least one field in a collection\n\n```py\n\nfrom django.db import models\nfrom django.conf import settings\nfrom extra_validator import FieldValidationMixin\n\n\nclass TestModel(FieldValidationMixin, models.Model):\n    user = models.ForeignKey(settings.AUTH_USER_MODEL)\n\n    amount = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True)\n    fixed_price = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True)\n    percentage = models.DecimalField(max_digits=3, decimal_places=0, null=True, blank=True)\n\n    CONDITIONAL_REQUIRED_TOGGLE_FIELDS = [\n        (\n            lambda instance: instance.user.is_active, ['fixed_price', 'percentage', 'amount'],\n        ),\n    ]\n```\n\nExample\n\n```python\nIn [1]: from decimal import Decimal\n\nin [2]: from django.contrib.auth import get_user_model\n\nIn [3]: from demo.models import TestModel\n\nIn [4]: user = get_user_model().objects.create(username='test', is_active=True)\n\nIn [5]: first_obj = TestModel.objects.create(user=user)\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {'__all__': ValidationError([u'Please provide a valid value for any of the following fields: Fixed price, Percentage, Amount'])}\n\nIn [6]: second_obj = TestModel.objects.create(user=user, amount=Decimal('2'), fixed_price=Decimal('2'))\n---------------------------------------------------------------------------\nValueError                   Traceback (most recent call last)\n...\n\nValueError: {'__all__': ValidationError([u'Please provide only one of the following fields: Fixed price, Percentage, Amount'])}\n```\n\n## Model Attributes\n\nThis is done using model attributes below.\n\n```py\n# A list of required fields\nREQUIRED_FIELDS = []\n\n#  A list of fields with at most one required.\nREQUIRED_TOGGLE_FIELDS = []\n\n# A list of field with at least one required.\nREQUIRED_MIN_FIELDS = []\n\n# Optional list of fields with at most one required.\nOPTIONAL_TOGGLE_FIELDS = []\n\n# Conditional field required list of tuples the condition a boolean or a callable.\n# [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' or 'last_name'\n# If condition is True ensure that all fields are set\nCONDITIONAL_REQUIRED_FIELDS = []\n\n# [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Either 'first_name' or 'last_name'\n# If condition is True ensure that at most one field is set\nCONDITIONAL_REQUIRED_TOGGLE_FIELDS = []\n\n# [(lambda user: user.is_admin, ['first_name', 'last_name'])] : At least 'first_name' or 'last_name' provided or both\n# If condition is True ensure that at least one field is set\nCONDITIONAL_REQUIRED_MIN_FIELDS = []\n\n# [(lambda user: user.is_admin, ['first_name', 'last_name'])] : Both 'first_name' and 'last_name' isn't provided\n# If condition is True ensure none of the fields are provided\nCONDITIONAL_REQUIRED_EMPTY_FIELDS = []\n\n```\n\n## License\n\ndjango-extra-field-validation is distributed under the terms of both\n\n*   [MIT License](https://choosealicense.com/licenses/mit)\n*   [Apache License, Version 2.0](https://choosealicense.com/licenses/apache-2.0)\n\nat your option.\n\n## TODO's\n\n*   \\[ ] Support `CONDITIONAL_NON_REQUIRED_TOGGLE_FIELDS`\n*   \\[ ] Support `CONDITIONAL_NON_REQUIRED_FIELDS`\n*   \\[ ] Move to support class and function based validators that use the instance object this should enable cross field model validation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftj-django%2Fdjango-extra-field-validation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftj-django%2Fdjango-extra-field-validation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftj-django%2Fdjango-extra-field-validation/lists"}