Django : ModelForm Pre-populate Checkbox with Custom Query

2k views Asked by At

I am working on my first Django Project.

I have a Many-to-Many Relation between two models: User and Project. While Updating a Project, I want to show form with Add New members and remove Existing members field with correct Choices based on current Project Users.

Here is what I tried so far:

  1. Get the Current Project from URL
  2. Pass the current Project to Model Form
  3. In Form, run Custom queryset.

Problem: Not Displaying result of Query Set.

In views.py

class UpdateProject(LogInRequiredMixin, UpdateView):

""" Class to Edit Project.
"""

form_class = ProjectUpdateForm
template_name = 'project/create.html'

def get_object(self):
    self.project_instance = models.Project.objects.get(pk=self.kwargs['project'])
        return self.project_instance

def get_form_kwargs(self):
    kwargs = super(UpdateProject, self).get_form_kwargs()
    kwargs.update({'project': self.project_instance})
    return kwargs

For forms ProjectUpDateForm

class ProjectUpdateForm(forms.ModelForm):
    """ Form to update Project Field. """

    add_member = forms.CharField(label="Add New Members", widget=forms.CheckboxSelectMultiple)
    del_member = forms.CharField(label="Remove Members", widget=forms.CheckboxSelectMultiple)

    def __init__(self, *args, **kwargs):
        self.project = kwargs.pop('project')
        super(ProjectUpdateForm, self).__init__(*args, **kwargs)
        print MyUser.objects.exclude(pk__in=self.project.members.all())
        print MyUser.objects.filter(pk__in=self.project.members.all())

        self.fields['add_member'].queryset = MyUser.objects.exclude(pk__in=self.project.members.all())
        self.fields['del_member'].queryset = MyUser.objects.filter(pk__in=self.project.members.all())

   # Rest of Class Logic

Print Statements are working and returning correct result, however I am unable to view the results in app. It is displaying blank.

Also, I want to know is their easier way to achieve the same? (Seems to me that I shouldn't have to pass project explicitly? )

3

There are 3 answers

0
Kush Jain On

As far as I could find out, this solves the problem

When adding Many-To-Many field explicitly in Model Form, and you expect Multiple response, the module used should be ModelMultipleChoiceField

Also, since we are over-riding init method, and have a queryset in it, the best place to define it is inside init.

Final Form Code:

class ProjectUpdateForm(forms.ModelForm):
    """ Form to update Project Field. """


    def __init__(self, *args, **kwargs):
        self.project = kwargs.pop('project')
        super(ProjectUpdateForm, self).__init__(*args, **kwargs)

        self.fields['add_member'] = forms.ModelMultipleChoiceField(label="Add New members",
                                                                   widget=forms.CheckboxSelectMultiple,
                                                                   queryset=MyUser.objects.exclude(pk__in=self.project.members.all()))
        self.fields['del_member'] = forms.ModelMultipleChoiceField(label="Remove Members",
                                                                   widget=forms.CheckboxSelectMultiple,
                                                                   queryset=MyUser.objects.filter(pk__in=self.project.members.all()))

    class Meta:
        model = Project
        fields = ['title', 'description']

   #Rest of Form Logic

And this should do it!


May be useful to someone

In case, we already have defined field in our model, we don't need to completely over-write it in init (if query requires passed parameters)

We can define it queryset in init by self.fields['field_name'].queryset = logic

And add widget in Meta class.


PS: I am still searching for easier way to access current object rather than being explicitly passed by View though!

0
vmonteco On

I understand the idea of adding add_member and del_member actions.

But why couldn't you just use a single ModelMultipleChoiceField to display every user that could be inside the project, and just check boxes to show users who already take part to the project?

I didn't test this, but it could look like :

class ProjectForm(forms.ModelForm):

    class Meta:
        model = Project
        fields = ['title', 'description']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        current_members = self.instance.members.all() # What are the current members?
        possible_members = User.objects.all() # Change this if you want a different set of users to add.
        self.fields['members'] = forms.ModelMultipleChoiceField(label="Members",
                                                                widget=forms.CheckboxSelectMultiple,
                                                                queryset=possible_members,
                                                                initial=current_members,
                                                                required=False) # If it can be empty

    def save(self, commit=True):
        instance = super().save(commit=False)  # Saving with commit=False add a save_m2m method to self
        if commit:
            instance.members.clear()           # We delete every member
            for m in self.cleaned_data.get('members'): # We them add again every checked user as member at form submission
                instance.members.add(m)
            instance.save()                    # We save the instance
            self.save_m2m()                    # We also save the changes in the M2M relationship.
            return instance

The trick is retrieving the users being currently members of the project instance and precheck checkboxes to show them amongst the whole list of all users (checked : user who's a member, not checked : user who's not a member).

So if you check a box, you add the user, and if you uncheck it you remove it from members list.

1
Steven Wu On

front page HTML code:

<td><input type="checkbox" name="checks[]" value="{{ customer.pk }}"></td>

backend views.py code:

models.Customer.objects.filter(pk__in=request.POST.getlist('checks[]')).update(consultant_id=None)

infact name='V'==getlist('V') , didn't need []

===========

<td><input type="checkbox" name="checks" value="{{ customer.pk }}"></td>

models.Customer.objects.filter(pk__in=request.POST.getlist(''checks'))