tamirko
tamirko

Reputation: 529

Change django.forms.models.ModelChoiceIterator

The choices variable of one of my fields (django.forms.models.ModelChoiceIterator) in ModelForm contains 3 items. In some cases, I want to use only one of them (the first one in the "list") for the actual HTML.

Here's a part of the Django Python code:

class MyForm(ModelForm):

def __init__(self, *args, user=None, **kwargs):
  choices = self.fields['xxx'].choices
  qs = choices.queryset
  qs = qs[:1]
  ...
  ...

  self.fields['xxx'] = forms.ChoiceField(
    label="xxx",
      widget=forms.Select(
        attrs={
            'class': 'form-select' }
     ),

  self.fields['xxx'].choices  = choices
  self.fields['xxx'].choices.queryset  = qs

  class Meta:
    ...
    model = MyModel
    fields = ('xxx')

    ...

This throws an error : 'list' object has no attribute 'queryset'" and, obviously, the size and content of choices do not change.
choices is an instance of django.forms.models.ModelChoiceIterator How can I reduce the number of items in the choices which will be displayed in the HTML ?

Upvotes: 1

Views: 528

Answers (1)

charlesroelli
charlesroelli

Reputation: 437

It depends. If you want to limit the choices of the field and also apply this limit during validation, you could try something like this:

from django.forms import ModelForm


class MyForm(ModelForm):
    def __init__(self, *kargs, **kwargs):
        super().__init__(*kargs, **kwargs)
        self.fields["xxx"].queryset = self.fields["xxx"].queryset.filter(
            pk=self.fields["xxx"].queryset.first().pk
        )

You can't use a sliced queryset in this situation, because a sliced queryset cannot be filtered later on during validation.

On the other hand, if you want to limit the choices of the field without affecting validation, you could set the field's iterator to a custom subclass of ModelChoiceIterator, and then instantiate that field in MyForm yourself.

from django.forms import ModelForm
from django.forms.models import ModelChoiceField, ModelChoiceIterator


class FirstItemModelChoiceIterator(ModelChoiceIterator):
    def __init__(self, field):
        self.field = field
        self.queryset = field.queryset[:1]


class FirstItemModelChoiceField(ModelChoiceField):
    iterator = FirstItemModelChoiceIterator


class MyForm(ModelForm):
    xxx = FirstItemModelChoiceField(queryset=...)

The above code doesn't handle the queryset being empty.

Tested on Django 4.2.7.

Upvotes: 0

Related Questions