Add native support for CompositePrimaryKey in SingleObjectMixin

Howdy!

Now that we have CompositePrimaryKey (thank you :smiley: !) I wanted to make a proposal to add native support in SingleObjectMixin. Currently you can use generic views with CPK models by overriding get_object to allow filtering by the two or more path kwargs from the url which correspond to the CPK (assuming you don’t need request information).

If you have many models with CompositePrimaryKeys this can be a lot of boilerplate.

The proposed solution is a general mechanism using a new attribute queryset_filter_kwargs, which would be a mapping of path_kwarg and field_name.

# models.py
class Story(models.Model):
    slug = models.SlugField(primary_key=True)
    name = models.CharField()


class Chapter(models.Model):
    pk = models.CompositePrimaryKey("story_id", "number")
    story = models.ForeignKey(
        "Story", on_delete=models.CASCADE, related_name="chapters"
    )
    number = models.PositiveIntegerField()
    title = models.CharField()
    body = models.TextField(blank=True)
# views.py
class ChapterDetailView(DetailView):
    model = Chapter
    queryset_filter_kwargs = {
        "story_slug": "story_id", "chapter_number": "number",
    }
# urls.py
urlpatterns = [
    path(
        "<slug:story_slug>/chapter/<int:chapter_number>/", ChapterDetailView.as_view(),
    ),
]

Why queryset_filter_kwargs?

This is designed as a general attribute rather than something CPK-specific. While brainstorming possible solutions, I realized the problem was fundamentally about enabling queryset filtering based on multiple url_kwargs/fields—something I’ve needed to handle frequently.

Also, I couldn’t come up with a good cpk-specific name :sweat_smile: (considered allowing pk_url_kwarg to be a tuple, but also rejected).

By adopting this approach, we can eliminate the need for redundant get_queryset() and get_object() overrides, limiting such overrides to cases involving more complex queries or request-specific logic.

In addition, it could potentially also carry the work of (pk|slug)_url_kwarg and (pk|slug)_field, which seem slated for some change/removal? (#22724 (Improve SingleObjectMixin) – Django, #21898 (SingleObjectMixin should not require slug or pk if queryset is given) – Django).

Internally, the implementation could look something like:

# in get_queryset or get_object
cpk = {
    field_name: self.kwargs.get(path_kwarg)
    for path_kwarg, field_name in self.queryset_filter_kwargs.items()
}
queryset = queryset.filter(**cpk)

Would love your thoughts, feedback, or ideas for improvement. Does this sound like a valuable addition to Django?

class ChapterDetailView(DetailView):
    model = Chapter
    composite_pk_url_kwargs = ("story_slug", "chapter_number")

I think this looks good as a composite pk-specific addition.

(PS Pardon the noise if this is a bad proposal.)

ah yes, SingleObjectMixin has been overlooked…

no need to add a new attribute IMO, this is related to #35953 (Add composite PK admin support) – Django, the same idea could be implemented in SingleObjectMixin.get_object

2 Likes

That’s OK, then. Thanks for the feedback.

I suppose I can always write my own mixins to prefer /field1/field2/ over /field1,field2/.

ah yes, SingleObjectMixin has been overlooked…

Opened a ticket: #36187 (Add support for CompositePrimaryKey in SingleObjectMixin) – Django

2 Likes