‘reverse’ ModelMultipleChoiceField not checking checkbox for existing relation

Model:

class Address(models.Model):

class Entity(models.Model):
addresses = models.ManyToManyField(to=Address, blank=True, related_name='entity_related_to_address')

class Person(Entity):
	pass

So the link I refer here are
Person(Entity) < addresses> → Address

And the reverse link:
Address <entity_related_to_address> → entity

form (simplefied):

class AddressModelForm(ContextModelForm):
    entity_related_to_address = forms.ModelMultipleChoiceField(
            queryset = Entity.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                { }
            ),
        )

def __init__(self, *args, **kwargs):
        super(AddressModelForm, self).__init__(*args, **kwargs)
	  
        self.fields['entity_related_to_address'].required = False

All added code to provide a searchfield and some added functionality like ‘forcing the selected fields to the front of the list with (s)css… not relevant here so not shown.

The template for a form with checkbox list:

{% load i18n %}

<div class="data-element-group">
  <label for="{{ form.entity_related_to_address.id_for_label }}">{% translate "entity" %}</label>

  <fieldset >
    <ul id="entityList">
      {% for entity in form.entity_related_to_address %}
      <li>
      <span for="{{ entity.id_for_label }}">
        <span class="radio">{{ entity.tag }}</span>  
        {{ entity.choice_label }}
      </span>
      </li>
      {% endfor %}
      
      <ul>
  </fieldset>
  
  <div class="errors"> {{ form.entity_related_to_address.errors }} </div>

  <div class="help"> {{ form.entity_related_to_address.help_text }} </div>
</div>

The Person html page shows excisting link to address
person
and address shows the entity (person in this case) by following the ‘reverse’ link via the ‘related_name’.
street
(The fuchsia lines are for testing :blush:)

The form for person shows the addres list with the correct checked address
person address list

But the entity form does not check the correct entity nor does it save any enity I check. This always fails for all implementations using the related_name:
search

So it works multiple times by following the ‘normal’ link but not the other way around. It does not check the excisting relations and it does not save any checked (new) relation.

How can I check the checkbox that excists as a link in the db and probably related… save it and any new link :question:

bonus: why does this code work one way and not the other way? I presume it has to do with the fact that I follow the reverse link.

Does anybody know if it is possible to edit / update a reverse ManyToManyField or or not? Or maybe I have to report a bug?

The quick answer is that it can be done.

The long answer, relative to the code you’ve supplied, will unfortunately take more time for me to write than what I have at the moment. It would help if you posted the complete AddressModelform class.

Also note that it’s not unusual for a good answer to a non-trivial question to take 3-5 days - especially when the question is posted on a weekend.

I have been looking for an answer online for weeks and most of the time you and others are super fast here. So I did not know if this was a bug, not possible or hard to answer or

Sorry if I come across as a spoiled brat :flushed:

Probably need the entity model

class Entity(models.Model):
    addresses = models.ManyToManyField(to=Address, blank=True )
    websites = models.ManyToManyField(to=Website, blank=True, related_name='entity_related_to_website')
    telephoneNumbers = models.ManyToManyField(to=PhoneNumber, blank=True, related_name='entity_related_to_phone_number')
    emails = models.ManyToManyField(to=Email, blank=True, related_name='entity_related_to_email')

    def __str__(self):
        if hasattr(self, 'person'):
            return _("Person") + ": " + self.person.name
        elif hasattr(self, 'company'):
            return self.organisation.__str__()
        else:
            return 'Error. you should not see entity outside child model'

I just removed one of the related_names because I was not sure if that was the problem. It was probably , related_name='entity_related_to_address'

It might be too complex… but I think I need it :slight_smile:

and the entityModelForm:

lass EntityModelForm(ContextModelForm):
    addresses = forms.ModelMultipleChoiceField(
            queryset = Address.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                {
                }
            ),
        )
    
    websites = forms.ModelMultipleChoiceField(
            queryset = Website.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                {
                }
            ),
        )
    
    telephoneNumbers = forms.ModelMultipleChoiceField(
            queryset = PhoneNumber.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                {
                }
            ),
        )
    
    emails = forms.ModelMultipleChoiceField(
            queryset = Email.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs=
                {
                }
            ),
        )

    def __init__(self, *args, **kwargs):
        super(EntityModelForm, self).__init__(*args, **kwargs)

        self.fields['addresses'].required = False
        self.fields['websites'].required = False
        self.fields['telephoneNumbers'].required = False
        self.fields['emails'].required = False

    class Meta:
        model = Entity
        fields = "__all__"

The Address model Form:

class AddressModelForm(ContextModelForm):
    street = forms.CharField(
        widget = forms.TextInput(attrs=
            { # this set of attrs (except autofocus) is seen everywhere and I removed that
                "placeholder": "street",
                "autofocus": "autofocus",
                "type": "text",
                "size": 40, 
                "maxLength": Address._meta.get_field('street').max_length,
            }
        ))    

    buildingNumber = forms.CharField(
        widget = forms.TextInput(attrs= { } ),
)    

    postalCode = forms.CharField(
        widget = forms.TextInput(attrs= { } ),
    )    

    city = forms.CharField(
        widget = forms.TextInput(attrs= { } ),
       )    
    
    state = forms.CharField(
        widget = forms.TextInput(attrs= { } ),
        )    

    country = forms.CharField(
        widget = forms.TextInput(attrs= { } ),
        )    

    continent = forms.ChoiceField(
            choices= Address.CONTINENTS,
            widget = forms.Select(),
        )

    entity_set = forms.ModelMultipleChoiceField(
            queryset = Entity.objects.all(),
            widget = forms.CheckboxSelectMultiple(attrs= { } ),
        )

    def __init__(self, *args, **kwargs):
        super(AddressModelForm, self).__init__(*args, **kwargs)
        self.fields['street'].required = True
        self.fields['buildingNumber'].required = False
        self.fields['postalCode'].required = False
        self.fields['city'].required = False
        self.fields['state'].required = False
        self.fields['country'].required = False
        self.fields['continent'].required = False
        self.fields['entity_set'].required = False

    class Meta:
        model = Address
        fields = "__all__"

I removed some repetative code. Asume that is no problem?!

Oh, not at all. No worries, you’re good.

No problem, and very helpful, thanks.

Again - firing off-the-cuff here at the moment in the hope that it might point you in the right direction before I can get the opportunity to verify this - but I believe you need to set the initial attribute of the MultipleChoiceField in the __init__ method of the form. (But that’s what I need to verify, and it’s going to be a bit before I can get a chance to do that.)

Take your time. In the mean time I will try to figure it out … see if I can get it solved… finally

I am not there yet but… after your initial response:

I found this 14 year old post so the basics should be

self.fields["somefield"].initial = someobject

I already use the first part to set values required. So I added:

self.fields['entity_set'].initial = Entity.objects.all().first()

and that works. I also tried that with 2 values and first() --> [:2] but that does not work…
So the current state:

  1. I need to be able to set multiple values for the manytomany relationship.
  2. I need to get the ‘list’ of ‘already’ excisting relations
  3. I have to combine all… somewhere
    4 I still have to get it to store the relation.

A bit further but not there yet… I will keep on looking :slight_smile:

Update 15.35:
print('test: '+str(self.initial )) shows the correct data in the console:

test: {'id': 106, 'extra_information': '', 'basemodel_ptr': 106, 'context_ptr': 106, 'street': 'Test street', 'buildingNumber': '42', 'postalCode': '4213 XX', 'city': 'NoTown', 'state': '', 'country': '', 'continent': '', 'subjects': []}

But this adress does not contain the entity_set so I cannot get the initially related items. probably a query on address with that id will resolve that. I hope there is an easer way?!

I was thinking…isn’t the fact that the reverse link entity_set is not contained in the data (console above) the cause of all trouble?! Is that a bug? It excists so it should be passed in the previous list even when it is empty like ‘state’ above.

Btw I created entity_set = forms.ModelMultipleChoiceField( because I assumed that was needed but it can have any name and is confusing because that is the related_name too

This wouldn’t be the right query, because you only want the set of related items.

At most, the query would be something like self.instance.entity_set.all() to retrieve the set of Entity related to this form’s Address.

Now, you probably need to supply only the list of pk of that query as the initial - again, more conjecture on my part. So I would expect the statement to be more along the lines of:
self.fields['entity_set'].initial = self.instance.entity_set.all().values_list('id', flat=True)
(At least that would be my next guess.)

(Really minor side note here - there is no “c” in “exists”)

Nope, not a bug, expected behavior. Because the definition is on the “far” side of the M2M, that’s where it shows up as a “native field” and needs to be added here.

Yes it is needed, and yes, it can be any name. However, they are references to two different objects, a field in a form and a reference to a related object manager. (It’s basically the same situation with the regular fields in model forms, too. The form field in the form is not the same object as the model field in the model that is matched to the form field.)

If that is all I am very happy. It is around 20+ years ago I learned Oxford English and I do this from memory, and I am dyslexic. I expected I butchered my English sentences. So…thanks :slight_smile: I will try to remember.

Though did you check the updated release of new Oxford English the FireDragon extension? :wink:

All jokes aside…
You solved most and I will try to see if I can save it or need to return to get some help with step 4. Thanks… another solution will probably come you way soon and I am very happy with all the help so far. Thanks for that

1 Like

In my update view I added:

def form_valid(self, form):
     form.instance.entity_set.set(form.cleaned_data['entity_set'])

form.instance to refer to the instance updated in the updateView,
form.instance.entity_set to refer to the ‘reverse’ links in that model instance
and thanks to one of the best error messages after trying =
I used .set() instead and added the user data for the updated entity_set with form.cleaned_data['entity_set']

It works for both removing and adding the entity_set (content) to the model instance.

Small note: It feels like this is the wrong place to do that. I expected I had to do this in the:

def save(self, *args, **kwargs):

function because I am saving the data and the ‘reverse’ links. But it works… another problem solved. Thanks

It doesn’t need to be done there.

If you’re using one of the “edit” CBVs for that form, the form_valid function calls form.save(). So it is possible to add that to the save method of the form. (That’s assuming that your form_valid calls super().form_valid().)

that sounds like a mutch better place. Thank you