pythondjangodjango-formsdjango-ormdjango-select2

I cannot access reverse ManyToMany field from django ModelForm


I have two entities with a ManyToMany relation between them. ProcessModel has a ManyToManyField of RouteModel and a reverse relalion 'processes' is created. I have define two ModelForms, but I cannot access the reverse ManyToMany relation 'processes' from the routes form. The view holding the route form don't display the processes field. How can I show that reverse ManyToMany field and save the information using the form. Here is my code

class ProcessModel(models.Model):

    name = models.CharField(verbose_name="nombre", max_length=50, blank=False, null=False)
    code = models.CharField(verbose_name="Código", max_length=50, blank=True, null=True, unique=True)    
    routes = models.ManyToManyField("RouteModel", verbose_name="lista de rutas", related_name="processes", blank=True)

class RouteModel(models.Model):
    
    name = models.CharField(verbose_name="Nombre", max_length=50, blank=False, null=False)

    route_type_id = models.ForeignKey("RouteTypeModel", verbose_name="Tipo",
                                                 blank=True, null=True, on_delete=models.SET_NULL,
                                                 related_name="routes")

    start_date = models.DateField(verbose_name="Fecha inicio")
    end_date = models.DateField(verbose_name="Fecha inicio")


from django_select2.forms import ModelSelect2Widget, ModelSelect2MultipleWidget
class ProcessForm(forms.ModelForm):
    class Meta:
        model = ProcessModel
        exclude = ('id',)

        widgets = {
            'name':forms.TextInput(attrs={'class': 'form-control'}),
            'code':forms.TextInput(attrs={'class': 'form-control'}),
            'routes': ModelSelect2MultipleWidget(model=RouteModel, queryset=RouteModel.objects.filter(),
                                            search_fields=['name__icontains'],
                                            attrs={'style': 'width: 100%;'}), 

        } 


class RouteForm(forms.ModelForm):
    
    class Meta:
        model = RouteModel
        exclude = ("id",)
        widgets = {
            'name':forms.TextInput(attrs={'class': 'form-control'}),
            'route_type_id': forms.Select(attrs={'class': 'form-control'}),
            'start_date' :forms.DateInput(attrs={'class': 'form-control datepicker', 'autocomplete': 'off'}),
            'end_date' :forms.DateInput(attrs={'class': 'form-control datepicker', 'autocomplete': 'off'}),
            'processes': ModelSelect2MultipleWidget(model=ProcessModel, queryset=ProcessModel.objects.filter(),
                                            search_fields=['name__icontains'],
                                           attrs={'style': 'width: 100%;'}), 
            }

Solution

  • I just found the solution, I had to add the processes field and overwrite the 'init' and 'save' functions. Here is the deffinition of the RouteForm

    class RouteForm(forms.ModelForm):
        
        processes = forms.ModelMultipleChoiceField(
                queryset=ProcessModel.objects.all(),
                widget=ModelSelect2MultipleWidget(model=ProcessModel, queryset=ProcessModel.objects.filter(),
                                                search_fields=['name__icontains'],
                                                attrs={'style': 'width: 100%;'}),)
        class Meta:
            model = RouteModel
            exclude = ("id",)
            widgets = {
                'name':forms.TextInput(attrs={'class': 'form-control'}),
                'route_geom': LeafletWidget(),
                'route_type_id': forms.Select(attrs={'class': 'form-control'}),
                'start_date' :forms.DateInput(attrs={'class': 'form-control datepicker', 'autocomplete': 'off'}),
                'end_date' :forms.DateInput(attrs={'class': 'form-control datepicker', 'autocomplete': 'off'}), }
    
        def __init__(self, *args, **kwargs):
            super(RouteForm, self).__init__(*args, **kwargs)
    
            # Here we fetch the currently related projects into the field,     
            # so that they will display in the form.
            if self.instance.id:
                self.fields['processes'].initial = self.instance.processes.all(
                ).values_list('id', flat=True)
    
        def save(self, *args, **kwargs):
            instance = super(RouteForm, self).save(*args, **kwargs)
    
            # Here we save the modified project selection back into the database
            instance.processes.set(self.cleaned_data['processes'])
    
            return instance