Designed to hold a list of pages and page ranges for a book/magazine index.
Originally published 2014-06-01
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
Update 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(nowvalue_from_objectinvalue_to_string). - Added
deconstruct()so the field works with migrations. - New
to_list(value)plus a generated<field>_listproperty on the model — the pages expanded to a list of ints, for matching (page in obj.pages_list). first_page()returns0on empty input instead of raisingIndexError.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_valuecontextarg (removed in Django 2.0); precompiled the validation regex.
The repack consecutive-run grouping (groupby on value − index) is unchanged.