django-bookkeeper 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. django_bookkeeper-0.2.1/.claude/launch.json +23 -0
  2. django_bookkeeper-0.2.1/.github/workflows/ci.yml +65 -0
  3. django_bookkeeper-0.2.1/.github/workflows/publish-pypi.yml +63 -0
  4. django_bookkeeper-0.2.1/.github/workflows/publish-testpypi.yml +62 -0
  5. django_bookkeeper-0.2.1/.gitignore +23 -0
  6. django_bookkeeper-0.2.1/AGENTS.md +140 -0
  7. django_bookkeeper-0.2.1/CLAUDE.md +3 -0
  8. django_bookkeeper-0.2.1/Dockerfile.demo +16 -0
  9. django_bookkeeper-0.2.1/LICENSE +21 -0
  10. django_bookkeeper-0.2.1/Makefile +35 -0
  11. django_bookkeeper-0.2.1/PKG-INFO +191 -0
  12. django_bookkeeper-0.2.1/README.md +132 -0
  13. django_bookkeeper-0.2.1/demo/__init__.py +0 -0
  14. django_bookkeeper-0.2.1/demo/demo_project/__init__.py +0 -0
  15. django_bookkeeper-0.2.1/demo/demo_project/settings.py +67 -0
  16. django_bookkeeper-0.2.1/demo/demo_project/urls.py +10 -0
  17. django_bookkeeper-0.2.1/demo/manage.py +11 -0
  18. django_bookkeeper-0.2.1/demo/management/__init__.py +0 -0
  19. django_bookkeeper-0.2.1/demo/management/commands/__init__.py +0 -0
  20. django_bookkeeper-0.2.1/demo/management/commands/seed_demo.py +230 -0
  21. django_bookkeeper-0.2.1/demo/templates/registration/login.html +134 -0
  22. django_bookkeeper-0.2.1/docker-compose.yml +24 -0
  23. django_bookkeeper-0.2.1/pyproject.toml +73 -0
  24. django_bookkeeper-0.2.1/src/bookkeeper/__init__.py +1 -0
  25. django_bookkeeper-0.2.1/src/bookkeeper/admin.py +43 -0
  26. django_bookkeeper-0.2.1/src/bookkeeper/apps.py +10 -0
  27. django_bookkeeper-0.2.1/src/bookkeeper/forms.py +34 -0
  28. django_bookkeeper-0.2.1/src/bookkeeper/hooks.py +35 -0
  29. django_bookkeeper-0.2.1/src/bookkeeper/migrations/0001_initial.py +143 -0
  30. django_bookkeeper-0.2.1/src/bookkeeper/migrations/0002_chapter.py +30 -0
  31. django_bookkeeper-0.2.1/src/bookkeeper/migrations/__init__.py +0 -0
  32. django_bookkeeper-0.2.1/src/bookkeeper/models.py +243 -0
  33. django_bookkeeper-0.2.1/src/bookkeeper/readers/__init__.py +4 -0
  34. django_bookkeeper-0.2.1/src/bookkeeper/readers/base.py +28 -0
  35. django_bookkeeper-0.2.1/src/bookkeeper/readers/cbz.py +45 -0
  36. django_bookkeeper-0.2.1/src/bookkeeper/readers/epub.py +203 -0
  37. django_bookkeeper-0.2.1/src/bookkeeper/readers/pdf.py +37 -0
  38. django_bookkeeper-0.2.1/src/bookkeeper/readers/registry.py +16 -0
  39. django_bookkeeper-0.2.1/src/bookkeeper/signals.py +14 -0
  40. django_bookkeeper-0.2.1/src/bookkeeper/static/bookkeeper/css/bookkeeper.css +375 -0
  41. django_bookkeeper-0.2.1/src/bookkeeper/static/bookkeeper/css/reader.css +480 -0
  42. django_bookkeeper-0.2.1/src/bookkeeper/static/bookkeeper/js/bookkeeper.js +158 -0
  43. django_bookkeeper-0.2.1/src/bookkeeper/static/bookkeeper/js/reader.js +775 -0
  44. django_bookkeeper-0.2.1/src/bookkeeper/templates/bookkeeper/base.html +89 -0
  45. django_bookkeeper-0.2.1/src/bookkeeper/templates/bookkeeper/book_detail.html +140 -0
  46. django_bookkeeper-0.2.1/src/bookkeeper/templates/bookkeeper/chapter_eval.html +127 -0
  47. django_bookkeeper-0.2.1/src/bookkeeper/templates/bookkeeper/library.html +103 -0
  48. django_bookkeeper-0.2.1/src/bookkeeper/templates/bookkeeper/reader.html +225 -0
  49. django_bookkeeper-0.2.1/src/bookkeeper/templatetags/__init__.py +0 -0
  50. django_bookkeeper-0.2.1/src/bookkeeper/urls.py +41 -0
  51. django_bookkeeper-0.2.1/src/bookkeeper/views.py +435 -0
  52. django_bookkeeper-0.2.1/tests/__init__.py +0 -0
  53. django_bookkeeper-0.2.1/tests/settings.py +44 -0
  54. django_bookkeeper-0.2.1/tests/test_models.py +94 -0
  55. django_bookkeeper-0.2.1/tests/test_star_rating_url.py +106 -0
  56. django_bookkeeper-0.2.1/tests/urls.py +5 -0
  57. django_bookkeeper-0.2.1/uv.lock +1061 -0
@@ -0,0 +1,23 @@
1
+ {
2
+ "version": "0.0.1",
3
+ "configurations": [
4
+ {
5
+ "name": "bookkeeper-demo",
6
+ "runtimeExecutable": "uv",
7
+ "runtimeArgs": [
8
+ "run",
9
+ "--with",
10
+ "django-bookkeeper",
11
+ "python",
12
+ "demo/manage.py",
13
+ "runserver",
14
+ "8765"
15
+ ],
16
+ "port": 8765,
17
+ "env": {
18
+ "DJANGO_SETTINGS_MODULE": "demo_project.settings",
19
+ "PYTHONPATH": "src:demo:."
20
+ }
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,65 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call: {}
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ lint:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Install uv
18
+ uses: astral-sh/setup-uv@v3
19
+
20
+ - name: Install dependencies
21
+ run: uv sync --extra dev
22
+
23
+ - name: Ruff check
24
+ run: uv run ruff check src/ tests/ demo/
25
+
26
+ test:
27
+ needs: lint
28
+ runs-on: ubuntu-latest
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ python-version: ["3.11", "3.12", "3.13"]
33
+ django-version: ["4.2", "5.0", "5.1"]
34
+ exclude:
35
+ # Django 5.0 dropped support before Python 3.13 was released
36
+ - python-version: "3.13"
37
+ django-version: "5.0"
38
+ # Django 4.2 didn't officially support 3.13 until later point releases
39
+ - python-version: "3.13"
40
+ django-version: "4.2"
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - name: Install uv
45
+ uses: astral-sh/setup-uv@v3
46
+
47
+ - name: Install Python ${{ matrix.python-version }}
48
+ run: uv python install ${{ matrix.python-version }}
49
+
50
+ - name: Install dependencies
51
+ run: uv sync --extra dev --python ${{ matrix.python-version }}
52
+
53
+ - name: Run tests against Django ${{ matrix.django-version }}
54
+ # `uv run` re-syncs the venv to uv.lock before executing, which would
55
+ # silently revert a `uv pip install` override done in a separate step.
56
+ # `--with` overrides the dependency for this invocation only, and
57
+ # survives the sync.
58
+ run: uv run --with "django~=${{ matrix.django-version }}.0" pytest --cov --cov-report=xml
59
+
60
+ - name: Upload coverage
61
+ if: matrix.python-version == '3.12' && matrix.django-version == '5.1'
62
+ uses: codecov/codecov-action@v4
63
+ with:
64
+ files: ./coverage.xml
65
+ fail_ci_if_error: false
@@ -0,0 +1,63 @@
1
+ name: Publish to PyPI
2
+
3
+ # Production releases publish on GitHub Release "published", not on tag
4
+ # push — tags get verified on TestPyPI first (see publish-testpypi.yml).
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ test:
15
+ uses: ./.github/workflows/ci.yml
16
+
17
+ build:
18
+ needs: test
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ ref: ${{ github.event.release.tag_name }}
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v3
27
+
28
+ - name: Verify release tag matches package version
29
+ if: github.event_name == 'release'
30
+ env:
31
+ TAG_NAME: ${{ github.event.release.tag_name }}
32
+ run: |
33
+ PKG_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
34
+ TAG_VERSION="${TAG_NAME#v}"
35
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
36
+ echo "::error::Release tag $TAG_NAME does not match pyproject.toml version $PKG_VERSION"
37
+ exit 1
38
+ fi
39
+
40
+ - name: Build sdist and wheel
41
+ run: uv build
42
+
43
+ - name: Upload build artifacts
44
+ uses: actions/upload-artifact@v4
45
+ with:
46
+ name: dist
47
+ path: dist/
48
+
49
+ publish:
50
+ needs: build
51
+ runs-on: ubuntu-latest
52
+ environment: pypi
53
+ permissions:
54
+ id-token: write # required for PyPI trusted publishing (OIDC)
55
+ steps:
56
+ - name: Download build artifacts
57
+ uses: actions/download-artifact@v4
58
+ with:
59
+ name: dist
60
+ path: dist/
61
+
62
+ - name: Publish to PyPI
63
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,62 @@
1
+ name: Publish to TestPyPI
2
+
3
+ # Tag pushes (vX.Y.Z) publish to TestPyPI for pre-release verification.
4
+ # Pushing to pypi.org happens separately, on GitHub Release "published".
5
+ on:
6
+ push:
7
+ tags:
8
+ - "v*"
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ test:
16
+ uses: ./.github/workflows/ci.yml
17
+
18
+ build:
19
+ needs: test
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v3
26
+
27
+ - name: Verify tag matches package version
28
+ if: github.event_name == 'push'
29
+ run: |
30
+ PKG_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
31
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
32
+ if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
33
+ echo "::error::Tag v$TAG_VERSION does not match pyproject.toml version $PKG_VERSION"
34
+ exit 1
35
+ fi
36
+
37
+ - name: Build sdist and wheel
38
+ run: uv build
39
+
40
+ - name: Upload build artifacts
41
+ uses: actions/upload-artifact@v4
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+
46
+ publish:
47
+ needs: build
48
+ runs-on: ubuntu-latest
49
+ environment: testpypi
50
+ permissions:
51
+ id-token: write # required for PyPI trusted publishing (OIDC)
52
+ steps:
53
+ - name: Download build artifacts
54
+ uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+
59
+ - name: Publish to TestPyPI
60
+ uses: pypa/gh-action-pypi-publish@release/v1
61
+ with:
62
+ repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,23 @@
1
+ .DS_Store
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ .env
11
+ *.sqlite3
12
+ .pytest_cache/
13
+ .coverage
14
+ coverage.xml
15
+ htmlcov/
16
+ .ruff_cache/
17
+ *.mo
18
+ *.pot
19
+ /media/
20
+ /static-collected/
21
+ node_modules/
22
+ demo/media/
23
+ demo/demo.sqlite3
@@ -0,0 +1,140 @@
1
+ # Agent instructions for django-bookkeeper
2
+
3
+ This file documents how to work in this repo so AI coding agents follow the
4
+ same process humans do. Read this before making changes.
5
+
6
+ ## What this project is
7
+
8
+ A reusable Django app (`src/bookkeeper/`) for storing, cataloguing, and
9
+ reading e-books (PDF, EPUB, CBZ), installable via pip/uv. `demo/` is a
10
+ throwaway Django project that exercises the app end-to-end — it is not
11
+ part of the published package.
12
+
13
+ ## Branching (GitFlow) — never push directly to main
14
+
15
+ - `main` — stable releases only. Never commit or push directly here.
16
+ - `develop` — integration branch. Feature/fix branches merge here via PR.
17
+ - `feature/*` — new features, branched from `develop`.
18
+ - `fix/*` — bug fixes, branched from `develop`.
19
+ - `release/*` — release prep branches.
20
+
21
+ Always create a branch and open a PR. Do not push to `develop` or `main`
22
+ directly unless explicitly told to.
23
+
24
+ ## Before every push
25
+
26
+ Run lint and tests. Both must pass:
27
+
28
+ ```bash
29
+ uv run ruff check src/ tests/ demo/
30
+ uv run pytest --cov
31
+ ```
32
+
33
+ CI runs the same `ruff check` command and a pytest matrix across Python
34
+ 3.11–3.13 and Django 4.2/5.0/5.1 — catch failures locally first.
35
+
36
+ Note: `ruff format` is not currently enforced (the existing codebase
37
+ hasn't been run through the formatter). Don't add a `ruff format --check`
38
+ CI gate without first reformatting the codebase as a deliberate, separate
39
+ change — otherwise it fails on ~11 pre-existing files immediately.
40
+
41
+ ## Working with the demo project
42
+
43
+ The demo lives in `demo/` but imports `bookkeeper` from `../src`. Because of
44
+ this, commands run from inside `demo/` need an explicit `PYTHONPATH`:
45
+
46
+ ```bash
47
+ cd demo
48
+ PYTHONPATH=../src:.. uv run python manage.py migrate
49
+ PYTHONPATH=../src:.. uv run python manage.py seed_demo
50
+ PYTHONPATH=../src:.. uv run python manage.py runserver
51
+ ```
52
+
53
+ (`make demo` from the repo root wraps this correctly — prefer it when you
54
+ just want to run the demo, use the explicit `PYTHONPATH` form when you need
55
+ a one-off management command.)
56
+
57
+ ### Re-seeding after model or extraction changes
58
+
59
+ `seed_demo --fast` skips books that already exist (matched by file hash),
60
+ so changing extraction logic (e.g. `EpubReader.extract_chapters()`) has
61
+ **no effect on already-seeded books** — they were extracted once at
62
+ upload time and the result is stored in the DB. To pick up extraction
63
+ changes:
64
+
65
+ ```bash
66
+ PYTHONPATH=../src:.. uv run python manage.py flush --no-input
67
+ PYTHONPATH=../src:.. uv run python manage.py seed_demo
68
+ ```
69
+
70
+ This applies to any manually-uploaded book too — re-upload it after
71
+ pulling a fix to extraction code.
72
+
73
+ ## Architecture notes
74
+
75
+ - `src/bookkeeper/readers/` — one module per format (`epub.py`, `pdf.py`,
76
+ `cbz.py`), each implementing `BaseReader` (metadata/cover extraction).
77
+ `EpubReader` additionally extracts chapter HTML at upload time (see
78
+ below) — this is EPUB-specific, not part of the shared interface.
79
+ - **EPUB chapters are extracted once, at upload time**, not re-parsed on
80
+ every read. `EpubReader.extract_chapters()` walks the spine, sanitizes
81
+ each XHTML item with BeautifulSoup, and stores the result in `Chapter`
82
+ rows. The reader serves `Chapter.html` directly — no epub.js, no
83
+ runtime EPUB parsing. epub.js remains only as a fallback for
84
+ fixed-layout (pre-paginated) EPUBs, which aren't extracted.
85
+ - `src/bookkeeper/hooks.py` — Django signals (`book_opened`,
86
+ `progress_updated`, `book_finished`, `book_rated`, `book_uploaded`,
87
+ `highlight_created`, `bookmark_created`) are the extension points for
88
+ consuming projects. Don't add new functionality that should be
89
+ observable externally without firing a signal.
90
+ - URLs are namespaced (`bookkeeper:`) and never hardcode a mount prefix
91
+ in templates — always use `{% url %}`. The demo mounts the app at `/`,
92
+ but consuming projects can mount it anywhere (e.g. `/books/`).
93
+ **JavaScript must follow the same rule**: never hardcode an API path
94
+ like `/books/api/...` — read it from a `data-url-*` attribute injected
95
+ via `{% url %}` in the template. (See `reader.html`/`reader.js` for the
96
+ pattern. A hardcoded path in `bookkeeper.js`'s star-rating widget is a
97
+ known bug — see issue #4.)
98
+
99
+ ## Known gotchas worth knowing before touching the reader
100
+
101
+ - **EPUB self-closing tags**: BeautifulSoup's `lxml-xml` parser (needed to
102
+ read well-formed XHTML) serializes empty elements as `<tag/>`, which is
103
+ valid XML but meaningless to a browser's HTML5 parser for non-void
104
+ elements like `<a>` — the tag stays open and swallows everything after
105
+ it. `EpubReader.extract_chapters()` works around this by forcing an
106
+ explicit empty text node on empty non-void, non-SVG elements before
107
+ serializing. Don't "simplify" this by reparsing the whole fragment
108
+ through an HTML parser — that lowercases SVG's case-sensitive
109
+ attributes (e.g. `viewBox` → `viewbox`), breaking SVG cover rendering.
110
+ - **Flexbox scroll containers need `min-height: 0`**: flex children
111
+ default to `min-height: auto`, meaning they won't shrink below their
112
+ content size. Any new scrollable panel inside the reader's flex layout
113
+ needs `min-height: 0` alongside `overflow-y: auto`, or it will grow to
114
+ fit all content instead of scrolling.
115
+ - **`[hidden]` can be overridden by author CSS**: if you give an element
116
+ with the `hidden` attribute its own `display: flex`/`block` rule, that
117
+ rule wins over the browser's `[hidden] { display: none }` UA rule. Any
118
+ element toggled via the `hidden` attribute needs an explicit
119
+ `#id[hidden] { display: none; }` rule alongside its own display rules
120
+ (see the four reader viewer divs in `reader.css` for the pattern).
121
+
122
+ ## Release process
123
+
124
+ Versioning is currently manual (static `version` in `pyproject.toml`,
125
+ not git-tag-derived).
126
+
127
+ 1. Bump `version` in `pyproject.toml`.
128
+ 2. Commit, merge to `main` via the normal release-branch flow.
129
+ 3. Tag and push `vX.Y.Z` — this triggers `publish-testpypi.yml`, which
130
+ verifies the tag matches the `pyproject.toml` version and publishes to
131
+ **test.pypi.org** via OIDC trusted publishing.
132
+ 4. Verify the TestPyPI release installs and works correctly.
133
+ 5. Cut a GitHub Release from that tag — this triggers `publish-pypi.yml`,
134
+ which publishes to **pypi.org**.
135
+
136
+ Both publish workflows gate on the CI workflow (lint + test matrix)
137
+ passing first, via `workflow_call`. Trusted publisher config (on both
138
+ PyPI and TestPyPI) must reference the exact workflow filename and
139
+ environment name — see `.github/workflows/publish-*.yml` for the
140
+ environment names (`testpypi` / `pypi`).
@@ -0,0 +1,3 @@
1
+ See [AGENTS.md](AGENTS.md) for project conventions, branching workflow,
2
+ demo setup, and release process. That file is the source of truth for
3
+ all coding agents working in this repo, not just Claude.
@@ -0,0 +1,16 @@
1
+ FROM python:3.12-slim
2
+
3
+ RUN apt-get update && apt-get install -y --no-install-recommends \
4
+ libmagic1 \
5
+ libmupdf-dev \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ WORKDIR /app
9
+
10
+ COPY pyproject.toml uv.lock ./
11
+ RUN pip install uv && uv sync --no-dev
12
+
13
+ COPY src/ ./src/
14
+ COPY demo/ ./demo/
15
+
16
+ EXPOSE 8000
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eric Williams
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.
@@ -0,0 +1,35 @@
1
+ .PHONY: demo demo-reset test lint
2
+
3
+ # ── Demo ─────────────────────────────────────────────────────────────────────
4
+
5
+ demo:
6
+ @echo "Setting up demo…"
7
+ cd demo && DJANGO_SETTINGS_MODULE=demo_project.settings PYTHONPATH=../src:.. \
8
+ uv run python manage.py migrate --run-syncdb
9
+ @echo "Seeding demo data (downloads ~5 public-domain EPUBs)…"
10
+ cd demo && DJANGO_SETTINGS_MODULE=demo_project.settings PYTHONPATH=../src:.. \
11
+ uv run python manage.py seed_demo
12
+ @echo ""
13
+ @echo "Starting server at http://127.0.0.1:8000/"
14
+ cd demo && DJANGO_SETTINGS_MODULE=demo_project.settings PYTHONPATH=../src:.. \
15
+ uv run python manage.py runserver
16
+
17
+ demo-reset:
18
+ rm -f demo/demo.sqlite3
19
+ rm -rf demo/media/bookkeeper
20
+ $(MAKE) demo
21
+
22
+ demo-run:
23
+ cd demo && DJANGO_SETTINGS_MODULE=demo_project.settings PYTHONPATH=../src:.. \
24
+ uv run python manage.py runserver
25
+
26
+ # ── Dev ──────────────────────────────────────────────────────────────────────
27
+
28
+ test:
29
+ uv run pytest tests/ -v
30
+
31
+ lint:
32
+ uv run ruff check src/ tests/ demo/
33
+
34
+ lint-fix:
35
+ uv run ruff check --fix src/ tests/ demo/
@@ -0,0 +1,191 @@
1
+ Metadata-Version: 2.4
2
+ Name: django-bookkeeper
3
+ Version: 0.2.1
4
+ Summary: A Django app for storing, cataloguing, and reading e-Books (PDF, EPUB, CBZ)
5
+ Project-URL: Homepage, https://github.com/ehwio/django-bookkeeper
6
+ Project-URL: Repository, https://github.com/ehwio/django-bookkeeper
7
+ Project-URL: Issues, https://github.com/ehwio/django-bookkeeper/issues
8
+ Author-email: Eric Williams <eric@subcritical.org>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Eric Williams
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: cbz,django,ebook,epub,library,pdf,reader
32
+ Classifier: Framework :: Django
33
+ Classifier: Framework :: Django :: 4.2
34
+ Classifier: Framework :: Django :: 5.0
35
+ Classifier: Framework :: Django :: 5.1
36
+ Classifier: Intended Audience :: Developers
37
+ Classifier: License :: OSI Approved :: MIT License
38
+ Classifier: Programming Language :: Python :: 3
39
+ Classifier: Topic :: Internet :: WWW/HTTP
40
+ Requires-Python: >=3.11
41
+ Requires-Dist: beautifulsoup4>=4.12
42
+ Requires-Dist: django-storages>=1.14
43
+ Requires-Dist: django>=4.2
44
+ Requires-Dist: ebooklib>=0.18
45
+ Requires-Dist: lxml>=5.0
46
+ Requires-Dist: pillow>=10.0
47
+ Requires-Dist: pymupdf>=1.24
48
+ Requires-Dist: python-magic>=0.4.27
49
+ Provides-Extra: dev
50
+ Requires-Dist: django-debug-toolbar>=4.3; extra == 'dev'
51
+ Requires-Dist: factory-boy>=3.3; extra == 'dev'
52
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
53
+ Requires-Dist: pytest-django>=4.8; extra == 'dev'
54
+ Requires-Dist: pytest>=8.0; extra == 'dev'
55
+ Requires-Dist: ruff>=0.4; extra == 'dev'
56
+ Provides-Extra: social
57
+ Requires-Dist: social-auth-app-django>=5.4; extra == 'social'
58
+ Description-Content-Type: text/markdown
59
+
60
+ # django-bookkeeper
61
+
62
+ A Django app for storing, cataloguing, and reading e-Books (PDF, EPUB, CBZ).
63
+
64
+ ## Features
65
+
66
+ - **Upload** PDF, EPUB, and CBZ files (drag-and-drop or browse)
67
+ - **Automatic metadata extraction** — title, author, publisher, cover image
68
+ - **Deduplication** by SHA-256 hash
69
+ - **Modern reader** with:
70
+ - EPUB rendering via [epub.js](https://github.com/futurepress/epub.js/)
71
+ - PDF rendering via [PDF.js](https://mozilla.github.io/pdf.js/)
72
+ - CBZ page-by-page comic reader
73
+ - Keyboard navigation (arrow keys)
74
+ - **Reader settings**: light/sepia/dark themes, font family & size, line height, column width
75
+ - **Highlights** in five colours with optional notes
76
+ - **Bookmarks** with titles and notes
77
+ - **Reading progress** — auto-saved position and percentage
78
+ - **5-star ratings** per user
79
+ - **Favourites** and finished-book tracking
80
+ - **Extensible hook signals** for recent-books lists, activity feeds, etc.
81
+ - Django best-practices: `django-storages` compatible, `AUTH_USER_MODEL` aware, namespaced URLs
82
+
83
+ ## Try the demo
84
+
85
+ The fastest way to see Bookkeeper in action — one command downloads five
86
+ public-domain classics from Project Gutenberg and starts a local server:
87
+
88
+ ```bash
89
+ git clone https://github.com/ehwio/django-bookkeeper
90
+ cd django-bookkeeper
91
+ uv sync
92
+ make demo
93
+ ```
94
+
95
+ Then open **http://127.0.0.1:8000/** and sign in as `demo` / `demo`.
96
+
97
+ **Or with Docker:**
98
+
99
+ ```bash
100
+ docker compose up
101
+ ```
102
+
103
+ The demo ships with:
104
+ - *Pride and Prejudice* — Jane Austen
105
+ - *Twenty Thousand Leagues Under the Seas* — Jules Verne
106
+ - *The Time Machine* — H.G. Wells
107
+ - *Alice's Adventures in Wonderland* — Lewis Carroll
108
+ - *Frankenstein* — Mary Wollstonecraft Shelley
109
+
110
+ > Books are downloaded from [Project Gutenberg](https://www.gutenberg.org/) on first run.
111
+ > They are public domain and freely distributable.
112
+
113
+ ---
114
+
115
+ ## Installation
116
+
117
+ ```bash
118
+ pip install django-bookkeeper
119
+ # or
120
+ uv add django-bookkeeper
121
+ ```
122
+
123
+ Add to `INSTALLED_APPS`:
124
+
125
+ ```python
126
+ INSTALLED_APPS = [
127
+ ...
128
+ "bookkeeper",
129
+ ]
130
+ ```
131
+
132
+ Include URLs:
133
+
134
+ ```python
135
+ # urls.py
136
+ from django.urls import include, path
137
+
138
+ urlpatterns = [
139
+ path("books/", include("bookkeeper.urls", namespace="bookkeeper")),
140
+ ]
141
+ ```
142
+
143
+ Run migrations:
144
+
145
+ ```bash
146
+ python manage.py migrate
147
+ ```
148
+
149
+ ## Optional dependencies
150
+
151
+ | Feature | Package |
152
+ |---------|---------|
153
+ | Social login | `django-social-auth-app-django` |
154
+ | Cloud storage | `django-storages` |
155
+
156
+ ## Hooks
157
+
158
+ Connect to Bookkeeper signals to extend behaviour:
159
+
160
+ ```python
161
+ from bookkeeper.hooks import book_opened, book_finished, progress_updated
162
+
163
+ @book_opened.connect
164
+ def track_recent(sender, user, book, **kwargs):
165
+ RecentBook.objects.update_or_create(user=user, defaults={"book": book})
166
+ ```
167
+
168
+ Available signals: `book_opened`, `progress_updated`, `book_finished`, `book_rated`,
169
+ `book_uploaded`, `highlight_created`, `bookmark_created`.
170
+
171
+ ## Development
172
+
173
+ ```bash
174
+ git clone https://github.com/ehwio/django-bookkeeper
175
+ cd django-bookkeeper
176
+ uv sync --extra dev
177
+ uv run pytest
178
+ uv run ruff check src/ tests/
179
+ ```
180
+
181
+ ### GitFlow
182
+
183
+ - `main` — stable releases
184
+ - `develop` — integration branch
185
+ - `feature/*` — new features
186
+ - `fix/*` — bug fixes
187
+ - `release/*` — release prep
188
+
189
+ ## License
190
+
191
+ MIT