ivotrnka
ivotrnka

Reputation: 21

Django UpdateView - get initial data from many-to-many field and add them when saving form

I'm using Django class based generic view. In my models.py I have a model called MyModel with many-to-many field called m2m. I have multiple groups of users they can edit the m2m field. Each group of users can only see and add their portion to the field - using get_form to set what they can see in the m2m field. The problem I'm having is that when one user enter his record it will delete the initial records in the m2m field. I need to somehow get the initial values from the m2m field save them and then add them to the new ones when the form is submitted. Here is my views.py:

class MyModelUpdate(UpdateView):
    model = MyModel
    fields = ['m2m']

    def get_initial(self):
        return initials

    def get_form(self, form_class=None):    
        form = super(MyModelUpdate, self).get_form(form_class)
        form.fields["m2m"].queryset = DiffModel.objects.filter(user = self.request.user)
        return form 

    def form_valid(self, form):
        form.instance.m2m.add( ??? add the initial values) 
        return super(MyModelUpdate, self).form_valid(form)

    def get_success_url(self):
        ...

Upvotes: 1

Views: 3128

Answers (2)

ivotrnka
ivotrnka

Reputation: 21

after few days of searching and coding I've found a solution.

views.py:

from itertools import chain
from .forms import MyForm,

def MyModelUpdate(request, pk):
    template_name = 'mytemplate.html'
    instance = MyModel.objects.get(pk = pk)
    
    instance_m2m = instance.m2m.exclude(user=request.user) 
    
    if request.method == "GET":
        form = MyForm(instance=instance, user=request.user)
        return render(request, template_name, {'form':form})
    else:
        form = MyForm(request.POST or None, instance=instance, user=request.user)
        if form.is_valid():
            post = form.save(commit=False)
            post.m2m = chain(form.cleaned_data['m2m'], instance_m2m)
            post.save()
            return redirect(...)

forms.py:

from django import forms
from .models import MyModel

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ['m2m']
        
    def __init__(self, *args, **kwargs):
        current_user = kwargs.pop('user')
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['m2m'].queryset = self.fields['m2m'].queryset.filter(user=current_user)

Upvotes: 1

Pere Picornell
Pere Picornell

Reputation: 1186

I'm adding this answer to offer a simplified explanation of this kind of problem, and also because the OP switches from an UpdateView to a function based view in his solution, which might not be what some users are looking for.

If you are using UpdateView for a model that has a ManyToMany field, but you are not displaying it to the user because you just want this data to be left alone, after saving the form all the m2m values will be erased.

That's obviously because Django expects this field to be included in the form, and not including it is the same as just sending it empty, therefore, to tell Django to delete all ManyToMany relationships.

In that simple case, you don't need to define the form_valid and then retrieve the original values and so on, you just need to tell Django not to expect this field.

So, if that's you view:

class ProjectFormView(generic.UpdateView):
    model = Project
    form_class = ProjectForm
    template_name = 'project.html'

In your form, exclude the m2m field:

class ProjectForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = '__all__'
        exclude = ['many_to_many_field']

Upvotes: 1

Related Questions