pythondjangomodelchoicefield

Django set ModelForm ModelChoiceField initial value in UpdateView class


There are many related questions I checked on so before posting my own, but I still can't figure this out. Using Django 2.2.x.

I want the owner field of my html form to initially display only 1 username and this username has to be the current event instance owner.

Being a ModelChoiceField, the field on the html form is rendered as a dropdown. This dropdown for the time being contains all the app users, since the ModelChoiceField has queryset=User.objects.all()

I tried to override the get_initial() method of my UpdateView but, afaict, this has no effect on the ModelChoiceField. I'm able to set all forms.CharField() initial values though.

I tried also to override the __init__ method of my class EventForm(forms.ModelForm) without luck.

forms.py

class EventForm(forms.ModelForm):

    class Meta:
        model = Event
        fields = ["customer", "owner", ...]

    # the helper block comes from crispy_forms extension.
    # It rules the form layout, allowing to specify css classes, fields order
    # and even adding custom buttons
    helper = FormHelper()
    helper.layout = Layout(
        Field('customer'),
        Field('owner'),
        ...    
        Submit('submit', 'Update')
    )

    customer = forms.CharField(disabled=True)
    owner = forms.ModelChoiceField(queryset=User.objects.all())
    ...

views.py

class EventUpdateView(LoginRequiredMixin, UpdateView):
    """ this view renders the update form that allows service desk operators to update event status,
    ownership and event notes
    """
    model = Event
    form_class = EventForm
    template_name = "metamonitor/event_update_form.html"

    def get_initial(self):
        """ returns the initial data to populate form field on this view.
        We override this method to customize the `owner` initial value to the
        default event owner.
        """
        initial = super().get_initial()
        # the event instance being updated
        event = self.get_object()
        # this expression returns a queryset containing only 1 item,
        # like <QuerySet [<User: operator2>]>
        initial['owner'] = User.objects.filter(pk=event.owner.pk)

        return initial

    # without this, after a successfull event update we would get an error
    # because django needs a valid url to redirect to.
    def get_success_url(self):
        # app_name:url_name, as defined in the corresponding path() `name` argument in urls.py
        return reverse("metamonitor:event_update", kwargs={"pk": self.object.pk})

How can I properly set the initial value?


Solution

  • Here is an approach to solve your problem. It requires two steps:

    1. Override the get_form_kwargs method of your view, so it retrieves the 'owner' object and sends it to the form, stored in the kwargs argument

    2. Override the init of the form, and then assign the owner object (taken from kwargs), to the .queryset of the field you want to change:

    Override get_form_kwargs in your view:

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        event = self.object     # no need to call get_object() as you did in your post
        owner = User.objects.filter(pk=event.owner.pk)
        kwargs.update({'owner': owner})
        return kwargs
    

    Override init of your Form class in forms.py:

    def __init__(self, *args, **kwargs):
        owner = kwargs.pop('owner')   # get the owner object from kwargs
        super().__init__(*args, **kwargs)
        # assign owner to the field; note the use of .queryset on the field
        self.fields['owner'].queryset = owner 
    

    Another, different from above, approach is to simply modify the get_form method of your view:

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        event = self.object
        owner = User.objects.filter(pk=event.owner.pk)
        form.fields['owner'].queryset = owner
    
        return form
    

    This solution is notably quicker to implement than the first, it's true; I only mention it last because in my experience, people seem to prefer to modify the Form class itself when the desired goal is changing how the Form is displayed. Use whichever works best for your needs.