Django Sorted Many-to-Many
django-sortedm2m is a drop-in replacement for Django's built-in ManyToManyField that preserves the order of related objects. It is actively maintained by the Jazzband community, with version 4.0.0 being the latest, offering support for recent Django and Python versions. Releases are frequent, focusing on compatibility updates, bug fixes, and minor enhancements.
Common errors
-
ModuleNotFoundError: No module named 'sortedm2m'
cause The 'sortedm2m' app is not listed in your project's `INSTALLED_APPS` or the package is not installed in your Python environment.fixAdd `'sortedm2m'` to your `INSTALLED_APPS` list in `settings.py`. If the package is not installed, run `pip install django-sortedm2m`. -
AttributeError: 'ManyToManyRel' object has no attribute 'is_hidden'
cause This error occurs when using `django-sortedm2m` with Django 5.1, if `django-sortedm2m` is an older version that predates the fix for this Django API change.fixUpgrade `django-sortedm2m` to version 4.0.0 or newer: `pip install --upgrade django-sortedm2m`. -
A migration created by Django's makemigrations will not work as expected when changing ManyToManyField to SortedManyToManyField
cause Django's automatic migration generation for changing field types (specifically to `SortedManyToManyField`) creates a generic `AlterField` operation, which does not correctly handle the intermediate model and ordering field setup specific to `django-sortedm2m`.fixManually edit the generated migration file. Change the `migrations.AlterField` operation to `AlterSortedManyToManyField` (import it from `sortedm2m.operations`).
Warnings
- breaking Version 4.0.0 of `django-sortedm2m` dropped support for older, outdated versions of Django and Python. Ensure your project meets the new requirements (Django 4.x, 5.0, 5.1 and Python 3.6+) before upgrading.
- breaking When upgrading `django-sortedm2m` with Django 5.1, versions prior to 4.0.0 may encounter an `AttributeError: 'ManyToManyRel' object has no attribute 'is_hidden'` due to a change in Django's internal API.
- gotcha When migrating an existing `ManyToManyField` to a `SortedManyToManyField` (or vice-versa), Django's `makemigrations` will generate an `AlterField` operation. This must be manually changed in the migration file to `AlterSortedManyToManyField` (imported from `sortedm2m.operations`) to ensure correct database schema changes.
- gotcha For proper functioning of the custom sortable widget in the Django admin, ensure 'sortedm2m' is included in your `INSTALLED_APPS` setting. Additionally, avoid listing `SortedManyToManyField` fields in `filter_horizontal` or `filter_vertical` tuples in your `ModelAdmin` definitions, as this can interfere with the custom widget's functionality.
Install
-
pip install django-sortedm2m
Imports
- SortedManyToManyField
from sortedm2m.fields import SortedManyToManyField
- AlterSortedManyToManyField
from sortedm2m.operations import AlterSortedManyToManyField
Quickstart
import os
import django
from django.conf import settings
from django.db import models
# Minimal Django settings for demonstration
settings.configure(
INSTALLED_APPS=[
'django.contrib.auth',
'django.contrib.contenttypes',
'sortedm2m',
'my_app', # Your app name
],
DATABASES={
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
},
MEDIA_ROOT=os.path.join(os.path.dirname(__file__), 'media'),
STATIC_URL='/static/',
DEBUG=True,
USE_TZ=True
)
django.setup()
# Define your models
class Photo(models.Model):
name = models.CharField(max_length=50)
image = models.ImageField(upload_to='photos', blank=True, null=True)
def __str__(self):
return self.name
class Gallery(models.Model):
name = models.CharField(max_length=50)
photos = SortedManyToManyField(Photo)
def __str__(self):
return self.name
# Example usage:
if __name__ == '__main__':
# Create some photos
photo1 = Photo.objects.create(name='Sunset View')
photo2 = Photo.objects.create(name='Mountain Hike')
photo3 = Photo.objects.create(name='City Lights')
# Create a gallery
gallery = Gallery.objects.create(name='Travel Memories')
# Add photos in a specific order
gallery.photos.add(photo2)
gallery.photos.add(photo1)
gallery.photos.add(photo3)
print(f"Gallery: {gallery.name}")
print("Photos in order:")
for photo in gallery.photos.all():
print(f"- {photo.name}")
# Reorder photos (example: move photo3 to the beginning)
gallery.photos.set([photo3.pk, photo2.pk, photo1.pk])
print("\nPhotos after reordering:")
for photo in gallery.photos.all():
print(f"- {photo.name}")
# Example with filtering and adding
new_gallery = Gallery.objects.create(name='Nature Wonders')
for photo in Photo.objects.order_by('name'):
new_gallery.photos.add(photo)
print(f"\nNew Gallery: {new_gallery.name}")
print("Photos added by name order:")
for photo in new_gallery.photos.all():
print(f"- {photo.name}")