django-ordered-model

An abstract Django model mixin that keeps rows ordered relative to each other, with admin drag-style controls and DRF support.

Library
PyPI
v3.7.4
752stars
BSD 3-Clause License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
44/100Fair
Development Activity0
Maintenance20
Community76
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture82
Code Quality78
Innovation58
Learning Curve80

django-ordered-model gives Django models a persisted position field and a full set of methods for rearranging rows relative to one another: swap, up, down, to, above, below, top, and bottom. Instead of hand-rolling order-shifting update queries every time a list needs reordering, a model simply inherits from OrderedModel and gets a managed order PositiveIntegerField plus a custom manager/queryset that keeps every other row’s position consistent on save, delete, and bulk_create.

Ordering can be scoped to a subset of rows via order_with_respect_to, so a many-to-one relationship (contacts per user, line items per invoice) orders correctly within each group rather than across the whole table, including support for multi-field and cross-relation lookups. A separate OrderedManyToManyField addresses a long-standing Django limitation where many-to-many querysets don’t respect the intermediate model’s ordering.

For the admin, OrderedModelAdmin plus OrderedTabularInline/OrderedStackedInline add move-up/move-down arrow links directly in the change list and inline formsets, and a reorder_model management command can repair ordering that drifted from bypassed save/delete paths. A bundled OrderedModelSerializer lets Django REST Framework endpoints reorder objects by writing to the order field directly.

What You Get

  • An OrderedModel abstract base class adding a managed, indexed order field with correct positioning on create, delete, and bulk_create
  • Relative-move methods on every instance: swap(), up(), down(), to(position), above(ref), below(ref), top(), bottom()
  • order_with_respect_to for scoping order to a subset (single field, multiple fields, or a path through a related model)
  • OrderedModelAdmin, OrderedTabularInline, and OrderedStackedInline for admin move-up/move-down arrow controls
  • A reorder_model management command to repair ordering that drifted from bypassed save/delete calls
  • OrderedManyToManyField so ManyToManyField querysets respect the through model’s ordering
  • OrderedModelSerializer for Django REST Framework endpoints that need to reorder via API writes

Common Use Cases

  • Reordering items in a Django admin change list with click-to-move arrow links instead of a raw numeric field
  • Per-user or per-parent ordered lists, such as contacts under a user or line items under an invoice, via order_with_respect_to
  • Exposing drag-and-drop reordering through a DRF API by writing new position values through OrderedModelSerializer
  • Ordering a many-to-many relationship’s results consistently by swapping in OrderedManyToManyField
  • Recovering from ordering drift caused by raw SQL updates or bulk operations that bypass model methods, using the reorder_model command

Under The Hood

Architecture The library centers on OrderedModelBase (models.py), an abstract model that pairs a custom OrderedModelQuerySet/OrderedModelManager with per-instance move methods. Every mutating operation funnels through get_ordering_queryset(), which scopes to the current order_with_respect_to group, and to(), which is the single primitive that above(), below(), top(), bottom(), and up()/down() all delegate to for the actual increase/decrease-and-shift logic. save() and delete() detect when the WRT group has changed and re-shuffle the old and new groups accordingly, and a post_delete signal handler (registered per-subclass, not globally) repairs ordering when instances are removed via cascade or queryset .delete() rather than the instance method. A Meta.check() override adds custom Django system checks (E001-E006, W003) that catch missing Meta.ordering, wrong manager/queryset base classes, and invalid order_with_respect_to field paths at manage.py check time rather than at runtime. admin.py layers OrderedModelAdmin and the tabular/stacked inline variants on top, registering the extra URL routes needed for the arrow-link admin views.

Tech Stack Pure Python/Django with no runtime dependencies beyond Django itself (compatible across Django 3.x-5.x per the compatibility table, Python 3.10-3.12 for the current 3.8.x line). Optional integration with Django REST Framework (3.15+) via a bundled serializer mixin. Packaging is plain setuptools (setup.py), tests run through tox across a matrix of Django/Python versions, and CI is GitHub Actions running the black formatter as a lint gate plus a separate test-and-coverage workflow.

Code Quality The project has a single, extensive tests/tests.py file (1,700+ lines) covering swap/move/order-with-respect-to combinations, admin inline behavior, DRF serialization, and the custom system checks, run against a real Django test settings module rather than mocks. Code is formatted with black and enforced in CI. Naming is consistent and methods are documented with short docstrings; error handling favors explicit ValueError/TypeError raises with descriptive messages (e.g. rejecting non-integer to() calls, mismatched WRT groups) over silent failure. There is no static type-checking (no mypy config or type hints), consistent with the project’s age and the Django ecosystem norms at the time it was written.

What Makes It Unique Rather than treating ordering as a bolt-on utility function, the library models it as a first-class Django abstraction: system checks that fail loudly at check-time if the manager/queryset chain is set up incorrectly, signal-based repair for the delete paths Django itself doesn’t let you intercept per-instance, and an OrderedManyToManyField that specifically works around a documented Django limitation where M2M querysets ignore the through model’s Meta.ordering. That combination of proactive validation and edge-case coverage (cascaded deletes, bulk_create, cross-relation order_with_respect_to) is more thorough than most ad hoc “add a position field” implementations.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search