Skip to content

ClassifierSelectWidget should respect limit_choices_to on model field #713

@Prill

Description

@Prill

Describe the bug

ClassifierSelectWidget always shows all classifiers and terms even if the underlying model field definition restricts choices using limit_choices_to. This allows users to click on invalid options causing the form to fail validation.

Steps to reproduce

Steps to reproduce the behavior:

  1. Set up a classifier with multiple terms. For this example I'm imagining a classifier called "Genre" with the terms "Action", "Adventure", "Horror", "Thriller", "Science-Fiction".
  2. Make a snippet model (new or existing) with the following field:
classifier_terms = ParentalManyToManyField(
    'coderedcms.ClassifierTerm',
    blank=True,
    related_name="genres",
    limit_choices_to=Q(name__startswith="A"), # or any filter eliminates some terms.
)
  1. In the model's panels attribute, include FieldPanel("classifier_terms", widget=ClassifierSelectWidget()).
  2. Run migrations, register snippet, etc.
  3. Go to the snippet edit page for the snippet.
  4. Scroll to the "classifier terms" panel. See that all of the terms are present, not just ones matching the Q object.
  5. Select both a matching term and a non-matching term, and save the form. The form validation should fail and the error message should complain only about the ID of the non-matching term.

Expected behavior

Only the terms and classifiers matching the limit_choices_to filter should be available as choices, as happens when not using the ClassifierSelectWidget(). CheckboxSelectMultiple is very close to what I want, but it is not grouped by classifier.

Additional context

This should theoretically be a simple matter of updating the optgroups class to use self.choices.queryset.

I made a simple working example by changing the initial classifier queryset from Classifier.objects.all().select_related() to


classifiers = Classifier.objects.all().select_related().prefetch_related(
    Prefetch("terms", queryset=self.choices.queryset)
)

to stop Classifiers with no matching terms from appearing as empty groupings, I just changed the inner loop to this:


if terms := classifier.terms.all():
    for term in terms:

If this seems like something y'all would be interested in implementing, I could try my hand at making a merge request. Wanted to confirm that the team would consider it worthwhile.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type: BugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions