Thank you for your interest in contributing. This document explains how to work with this codebase, what standards are expected, and how contributions are reviewed and merged.
Please read it fully before opening a pull request.
- Project Overview
- Development Setup
- Branching and Workflow
- Code Standards
- Architecture Constraints
- Testing Requirements
- Pull Request Process
- Versioning and Changelog
- What Not to Do
internal-admin is a reusable, pip-installable administrative framework for FastAPI applications. It provides model-driven CRUD interfaces, session-based authentication, and a role-based permission system — all rendered server-side with Jinja2 and Bootstrap 5.
The public API is intentionally minimal:
AdminSiteModelAdminAdminConfig
Everything else is internal and may change between minor versions. Keep that boundary in mind when contributing.
Requirements: Python 3.10+, pip, git.
# Clone the repository
git clone https://github.com/ayahaustine/internal-admin.git
cd internal-admin
# Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install in editable mode with development dependencies
pip install -e ".[dev]"
# Install git hooks (enforces formatting and commit message format locally)
pre-commit install --install-hooks
pre-commit install --hook-type commit-msgTo create a superuser and run the demo server:
internal-admin createsuperuser
python3 demo.py
# Admin interface: http://localhost:8080/admin/To run the test suite:
pytest tests/ -v
# With coverage
pytest tests/ --cov=internal_admin --cov-report=term-missingmainis the stable branch. Do not commit directly to it.- Create a branch for every change:
feature/your-featureorfix/your-fix. - Keep branches focused. One logical change per pull request.
- Rebase on
mainbefore opening a PR to keep history clean.
git checkout -b feature/my-feature
# make changes
git push origin feature/my-feature
# open a pull requestLanguage: Python 3.10+. Use type annotations throughout.
Style: Follow PEP 8. Use a formatter (black or ruff) before pushing.
Imports: Absolute imports within the package. Internal modules should import from their own layer only — do not reach across boundaries (e.g., admin modules must not import from auth internals and vice versa).
Naming: Be explicit. Prefer model_admin_class over mac.
Avoid abbreviations unless they are universally understood.
Comments: Comment the why, not the what. If a block requires a comment to be understood, that is a signal to simplify the code first.
Error handling: Raise specific, descriptive exceptions. Never silently swallow exceptions unless the failure is genuinely inconsequential and the decision is documented.
Templates: Jinja2 + Bootstrap 5 only. No JavaScript frameworks. No business logic in templates — pass everything needed from the route handler.
These are hard constraints, not preferences. Pull requests that violate them will not be merged.
Query pipeline. All list-page query logic must pass through the query
engine pipeline: base_query → filters → search → ordering → pagination.
Route handlers must not manually modify queries outside this pipeline.
No unbounded queries. Pagination is mandatory on every list endpoint. Never query an entire table without a limit.
No N+1 queries. Use selectinload for related objects. Do not lazy-load
inside loops.
DB-agnostic. The codebase must work with both SQLite and PostgreSQL. No database-specific SQL, functions, or assumptions are permitted.
Public API stability. Do not add parameters, change signatures, or alter
behavior of AdminSite, ModelAdmin, or AdminConfig without a major
version bump and a migration note in the changelog.
Separation of concerns. Admin logic must not reference business logic. Routing, query building, form handling, and authentication are separate layers and must remain so.
No new heavy dependencies. If a change requires a new dependency, justify it in the PR description. Prefer the standard library or libraries already in use.
All new logic must be covered by tests. This is not optional.
Critical areas that require tests for any change:
- Registry validation
- Query engine pipeline (filters, search, ordering, pagination)
- Form validation and type casting
- Permission enforcement (route level and query level)
To add a test:
- Place it in
tests/. - Follow the naming convention:
test_<module>.py. - Use the fixtures defined in
tests/conftest.py. - Tests must pass against SQLite. PostgreSQL integration tests are encouraged for database-layer changes.
Coverage target: 85% or higher. Do not submit a PR that reduces overall coverage without a clear justification.
pytest tests/ --cov=internal_admin --cov-report=term-missing- Open an issue first for non-trivial changes. Discuss the approach before writing code.
- Write tests for everything you add or change.
- Update the changelog under
[Unreleased]inCHANGELOG.md. - Fill out the PR description with:
- What the change does
- Why it is needed
- What tests cover it
- Whether it affects the public API
- Keep the diff focused. Do not include unrelated formatting changes or refactors in the same PR.
- Address review comments directly. If you disagree with feedback, explain your reasoning — do not silently ignore it.
A PR requires at least one approving review before it can be merged.
Direct merges to main are not permitted.
This project uses Semantic Versioning and Conventional Commits. Releases are fully automated — you do not manually bump the version.
<type>(<scope>): <short description>
[optional body]
[optional footer]
| Type | Effect |
|---|---|
feat: |
Increments minor version (0.x.0) |
fix:, perf:, refactor: |
Increments patch version (0.0.x) |
feat!: or BREAKING CHANGE: in footer |
Increments major version (x.0.0) |
docs:, chore:, style:, test:, ci:, build: |
No version bump |
Examples:
feat(auth): add password reset via email
fix(query): prevent N+1 on foreign-key filter
feat!: remove deprecated `user_model` kwarg from AdminConfig
The pre-commit hook (conventional-pre-commit) enforces this format locally.
The CI pipeline validates it on every pull request.
When a pull request is merged into main:
- The Release GitHub Actions workflow runs.
python-semantic-releasereads all commits since the last tag.- It determines the version bump from commit types.
- If a bump is warranted it:
- Updates
versioninpyproject.toml - Updates
__version__ininternal_admin/__init__.py - Rewrites
CHANGELOG.md - Commits as
chore(release): <version> [skip ci] - Creates and pushes a
v<version>tag - Creates a GitHub release with build artefacts
- Publishes the wheel and sdist to PyPI
- Updates
If no commit since the last tag produces a version bump, no release occurs.
The following will cause a PR to be declined:
- Adding logic that bypasses the query engine
- Introducing unbounded queries or disabling pagination
- Coupling admin logic to application-specific business logic
- Expanding the public API without a version bump
- Adding magic behavior (auto-discovery, implicit side effects, monkey-patching)
- Submitting without tests
- Committing directly to
main - Adding frontend build tooling, JS frameworks, or CSS preprocessors
- Storing or logging plaintext passwords or session tokens
If you are unsure whether a change is appropriate, open an issue and ask before writing code.
Open a GitHub issue labeled question for anything not covered here.