{"id":41195866,"url":"https://github.com/tnware/django-admin-reversefields","last_synced_at":"2026-01-22T20:36:16.703Z","repository":{"id":315974201,"uuid":"1061439171","full_name":"tnware/django-admin-reversefields","owner":"tnware","description":null,"archived":false,"fork":false,"pushed_at":"2025-09-22T22:58:41.000Z","size":150,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-29T02:02:36.250Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tnware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-09-21T22:44:27.000Z","updated_at":"2025-09-22T23:02:38.000Z","dependencies_parsed_at":"2025-09-22T00:25:33.994Z","dependency_job_id":"d5bf62bc-064b-4f06-9e3c-16b1b1d9059b","html_url":"https://github.com/tnware/django-admin-reversefields","commit_stats":null,"previous_names":["tnware/django-admin-reversefields"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/tnware/django-admin-reversefields","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnware%2Fdjango-admin-reversefields","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnware%2Fdjango-admin-reversefields/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnware%2Fdjango-admin-reversefields/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnware%2Fdjango-admin-reversefields/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tnware","download_url":"https://codeload.github.com/tnware/django-admin-reversefields/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tnware%2Fdjango-admin-reversefields/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28670385,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-22T19:36:09.361Z","status":"ssl_error","status_checked_at":"2026-01-22T19:36:05.567Z","response_time":144,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-01-22T20:36:16.593Z","updated_at":"2026-01-22T20:36:16.684Z","avatar_url":"https://github.com/tnware.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# django-admin-reversefields\n\n[![PyPI - Downloads](https://img.shields.io/pypi/dw/django-admin-reversefields)](https://pypi.org/project/django-admin-reversefields/)\n[![PyPI - Version](https://img.shields.io/pypi/v/django-admin-reversefields)](https://pypi.org/project/django-admin-reversefields/)\n\nManage reverse ForeignKey/OneToOne bindings directly from a parent model’s Django admin form using a small, declarative mixin.\n\n- Add virtual fields to your `ModelAdmin` to bind/unbind reverse-side rows\n- Keep selections in sync with transactional, unbind-before-bind updates\n- Use stock admin widgets or plug in Unfold/DAL/custom widgets\n- Optional, flexible permission gating with clear UX (hide/disable)\n\n---\n\n## Install\n\n```bash\npip install django-admin-reversefields\n```\n\nSupported: Django 4.2/5.0/5.1/5.2; Python 3.10–3.13.\n\n---\n\n## Quickstart\n\n```python\nfrom django.contrib import admin\nfrom django.db.models import Q\n\nfrom django_admin_reversefields.mixins import (\n    ReverseRelationAdminMixin,\n    ReverseRelationConfig,\n)\nfrom .models import Company, Department, Project\n\n\ndef unbound_or_current(qs, instance, request):\n    if instance and instance.pk:\n        return qs.filter(Q(company__isnull=True) | Q(company=instance))\n    return qs.filter(company__isnull=True)\n\n\n@admin.register(Company)\nclass CompanyAdmin(ReverseRelationAdminMixin, admin.ModelAdmin):\n    reverse_relations = {\n        # Single-select: bind exactly one Department via its FK to Company\n        \"department_binding\": ReverseRelationConfig(\n            model=Department,\n            fk_field=\"company\",\n            limit_choices_to=unbound_or_current,\n        ),\n        # Multi-select: manage the entire set of Projects pointing at the Company\n        \"assigned_projects\": ReverseRelationConfig(\n            model=Project,\n            fk_field=\"company\",\n            multiple=True,\n            # optional: ordering=(\"name\",),\n        ),\n    }\n\n    fieldsets = ((\"Relations\", {\"fields\": (\"department_binding\", \"assigned_projects\")}),)\n```\n\n- Include each virtual field name (e.g. `\"department_binding\"`) in `fieldsets` or `fields` so the admin template renders it (or omit both `fields` and `fieldsets` and Django will render all fields, including the injected virtual fields).\n- Limiters run per request/object; commonly: “unbound or currently bound”.\n\n---\n\n## Core concepts (tl;dr)\n\n- Reverse fields are virtual `ModelChoiceField` / `ModelMultipleChoiceField` instances that point to the reverse-side model and its ForeignKey back to the admin’s model.\n- Querysets and initial values are computed per request/object.\n- On save, the mixin synchronizes the reverse-side ForeignKey(s) to match the submitted selection.\n  - Single-select: sets the chosen row’s FK to the parent and unbinds any other rows pointing to it.\n  - Multi-select: represents the entire desired set; rows not in the selection are unbound before binds.\n- Transactions: by default `reverse_relations_atomic=True` wraps all updates in one `transaction.atomic()` block and applies unbinds before binds to minimize uniqueness conflicts.\n\nPerformance: enable `bulk=True` on a `ReverseRelationConfig` to use `.update()` for unbind/bind operations. This improves performance with large datasets but bypasses model signals. Use only if your app doesn’t depend on `pre_save`/`post_save` on the reverse model.\n\nImportant: for single-select, unbinding others requires the reverse FK to be `null=True`, or set `required=True` on the virtual field when it must never be empty; otherwise an unbind can raise `IntegrityError`.\n\n---\n\n## Permissions (optional)\n\nEnable enforcement:\n\n```python\nclass ServiceAdmin(ReverseRelationAdminMixin, admin.ModelAdmin):\n    reverse_permissions_enabled = True\n    reverse_permission_mode = \"disable\"  # or \"hide\"\n```\n\n- Precedence for allow/deny:\n  1) Per-field `ReverseRelationConfig.permission`\n  2) `reverse_permission_policy` (admin-wide)\n  3) Default `user.has_perm(\"app.change_model\")` on the reverse model\n- Error message precedence: field override → per-field policy object → global policy object → default\n- Disable vs hide:\n  - \"disable\": render read-only and ignore posted changes. To avoid spurious validation, the mixin sets `required=False` on disabled reverse fields so forms won’t raise “This field is required.” when there is no initial value.\n  - \"hide\": remove the field entirely.\n- Optional: set `reverse_render_uses_field_policy=True` to have render-time visibility/disabled state decided by your per-field/global policy (called with `selection=None`).\n\nHidden/disabled fields are always ignored on save, so crafted POSTs cannot change unauthorized reverse fields.\n\n---\n\n## API surface\n\nImport:\n\n```python\nfrom django_admin_reversefields.mixins import ReverseRelationAdminMixin, ReverseRelationConfig\n```\n\n`ReverseRelationConfig` (per virtual field):\n\n- `model`: reverse-side `models.Model` that holds the ForeignKey to the admin model\n- `fk_field`: name of that ForeignKey on `model`\n- `label`, `help_text`: optional display strings\n- `required`: enforce non-empty selection (default False)\n- `multiple`: multi-select that syncs many rows (default False)\n- `limit_choices_to`: callable `(qs, instance, request) -\u003e qs` or `dict` passed to `.filter(**dict)`\n- `widget`: widget instance or class; defaults to admin `Select`/`FilteredSelectMultiple`\n- `ordering`: iterable for `.order_by()`\n- `clean(instance, selection, request)`: optional domain validation; raise `forms.ValidationError` to block\n- `permission`: optional policy (callable or object with `has_perm(...)`) to allow/deny edits\n- `permission_denied_message`: message used when a denial becomes a field error\n- `bulk`: when True, perform unbind/bind via `.update()` (bypasses model signals)\n\nMixin knobs:\n\n- `reverse_relations`: mapping of virtual field name → config\n- `reverse_relations_atomic`: wrap all updates in one transaction (default True)\n- `reverse_permissions_enabled`: enforce permission checks (default False)\n- `reverse_permission_mode`: \"disable\" | \"hide\"\n- `reverse_permission_policy`: optional global policy\n- `reverse_render_uses_field_policy`: use per-field/global policy at render time (selection=None)\n\n---\n\n## Recipes and docs\n\n- [Quickstart](https://tnware.github.io/django-admin-reversefields/quickstart.html)\n- [Core concepts](https://tnware.github.io/django-admin-reversefields/core-concepts.html)\n- [Permissions](https://tnware.github.io/django-admin-reversefields/permissions-guide.html)\n- [Architecture](https://tnware.github.io/django-admin-reversefields/architecture.html)\n- [Recipes](https://tnware.github.io/django-admin-reversefields/recipes.html)\n- [Caveats](https://tnware.github.io/django-admin-reversefields/caveats.html)\n- [Rendering \u0026 Visibility](https://tnware.github.io/django-admin-reversefields/rendering.html)\n\n---\n\n## Development\n\nWe use [`uv`](https://github.com/astral-sh/uv) for tooling.\n\n- `uv sync` — install project + docs deps\n- `uv run ruff check .` — lint\n- `uv run django-admin test` or `uv run python manage.py test` — tests\n- `uv run sphinx-build -b html docs docs/_build/html -W` — docs build\n\nRelease:\n\n```bash\nuv build\nTwine upload dist/*\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftnware%2Fdjango-admin-reversefields","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftnware%2Fdjango-admin-reversefields","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftnware%2Fdjango-admin-reversefields/lists"}