**Designed to hold a list of pages and page ranges for a book/magazine index.**
A custom model field (and accompanying form field) that saves comma-separated pages and page ranges in human-readable string form. Includes some clean-up code, so that you can add a new page or range at the end of an existing entry, and it will put it in numeric order and combine runs into ranges. So this:
4-33, 43, 45, 60-65, 44, 59
becomes the tidy
4-33, 43-45, 59-65
**NOTE:** If you comment out the raising of the `ValidationError` in the form field's validate() method, it will actually clean up any extraneous characters for you (which could be dangerous, but for me is usually what I want), so even this horrible mess:
;4-33, 46a fads i44 ,p45o
gets cleaned to
4-33, 44-46
**Updated 2026-06-26 — modernized for Django 3.2+ / 5.x + Python 3, and added a list-of-ints accessor.**
- Removed the Python-2 / pre-1.10 leftovers: `__metaclass__ = SubfieldBase`, `super(Class, self)`, and `_get_val_from_obj` (now `value_from_object` in `value_to_string`).
- Added `deconstruct()` so the field works with migrations.
- **New `to_list(value)` plus a generated `<field>_list` property** on the model — the pages expanded to a list of ints, for matching (`page in obj.pages_list`).
- `first_page()` returns `0` on empty input instead of raising `IndexError`.
- `depack()` now tolerates reversed ranges (`53-51`) and skips malformed parts (`12-13-15`, letters) instead of raising.
- Centralized normalization in one helper; the form field rejects stray characters with a clear error while the model stays lenient.
- Dropped the obsolete `from_db_value` `context` arg (removed in Django 2.0); precompiled the validation regex.
The `repack` consecutive-run grouping (groupby on value − index) is unchanged.
- multiple
- stringformat
- range
- pages
- index