0

I have a form which is intended to add a number of sections to a project. I am using a CheckboxSelectMultiple with a list of choices restricted to only those that have not previously been selected.

So with a new empty project with no sections:

Project.sections.all() = null
choices = [(1, 'a'),(2, 'b'),(3, 'c')]

First time the form is submitted, adding sections a & b.

Project.sections.all() = a, b
choices = [(3, 'c')]

Second time the form is submitted, adding section c. Project.sections.all() = c
choices = [(1, 'a'),(2, 'b')]

What I instead want is for c to be added onto the existing list of values for the project.

models.py

class Project(models.Model):
    number = models.CharField(max_length=4, unique=True)
    name = models.CharField(max_length=100)
    sections = models.ManyToManyField(Section)

class Section(models.Model):
    code = models.CharField(max_length=2)
    description = models.CharField(max_length=50)

views.py

def add_section(request, project_number):
    project = Project.objects.get(number=project_number)
    full_section_list = Section.objects.all()

    project_assigned_sections = project.sections.all().values_list('id', flat=True)
    choices = list(full_section_list.exclude(pk__in=project_assigned_sections).values_list('id', 'code'))

    if request.method == 'POST':
        form = AddSectionForm(choices, request.POST, instance=project)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse("project-page", args=(project_number)))
        else:
            print("invalid")

    else:
        form = AddSectionForm(choices, instance=project)

    return render(request, "app/add_section.html", {
        "project": project,
        "form": form,
        "choices": choices
    })

forms.py

class AddSectionForm(forms.ModelForm):
    def __init__(self, choices, *args, **kwargs):
        super(AddSectionForm, self).__init__(*args, **kwargs)

        self.fields['sections'] = forms.MultipleChoiceField(
            widget=forms.CheckboxSelectMultiple,
            required=False,
            choices=choices
        )

    class Meta:
        model = Project
        fields = ['sections']
Chonker
  • 21
  • 4

3 Answers3

0

I assume that Django is doing what it's intended to do ... so, work around it.

if request.method == 'POST':
    previous_sections = list( project.sections.values_list( 'pk', flat=True) )
    form = AddSectionForm(choices, request.POST, instance=project)
    if form.is_valid():
        new = form.save( commit = False)
        new.sections.add( previous_sections ) # make sure old ones are never removed
        new.save()
        return HttpResponseRedirect(reverse("project-page", args=(project_number)))
    else:
        print("invalid")
nigel222
  • 7,582
  • 1
  • 14
  • 22
  • I gave this a go, had to put an asterisk in the line: ```new.sections.add(*previous_sections) because it gets a list. It isn't saving anything though – Chonker Feb 18 '22 at 12:02
  • Have you printed `previous_sections`? It occurs to me that it might be an empty list, the problem being that something has cleared `.sections` before you get to this view. – nigel222 Feb 18 '22 at 13:35
  • yes, it's got a list of the primary keys. It's actually the same list as I'm using the filter the form's choices to only those that aren't already applied, just not at a queryset ```project_assigned_sections = project.sections.all().values_list('id', flat=True)``` – Chonker Feb 18 '22 at 15:39
0
else:   
    form = AddSectionForm()

dont pass instance just keep it empty form

Tanveer Ahmad
  • 706
  • 4
  • 12
  • I've removed the instance passing. I added it at some point because it seemed to clear up an error but perhaps was a mistake. – Chonker Feb 18 '22 at 12:06
0

Ok this works and seems a bit nicer (I'm not mapping integers to the returned list anymore).

            data = form.cleaned_data.get("sections") #get the list of id's from the form
            sections = Section.objects.filter(pk__in=data) #gets the objects from those id's
            for section in sections:  #iterate through them, adding to the project.
               project.sections.add(section)
Chonker
  • 21
  • 4