Andrew Gee
Andrew Gee

Reputation: 91

Django m2m and saving objects

I have a couple of simple objects that have a many-to-many relationship. Django has joined them using obj1_obj2 table and it looks like this in mysql;

id  |  person_id  |  nationality_id  
-----------------------------------  
 1  |     1       |      1  
 2  |     1       |      2  

Now when I save obj1 (which shows obj2 in as Multi-select in its form) the ids in the obj1_obj2 table increase even thow I have not changed them. For example I change a basic character field for obj1 on its form and save it and the the data in the joining table appears to be deleted and re-saved giving the entries new ids.

In fact I don't have to change anything all I have to do is save the form and the same thing happens.

All I am doing in the view is form.save(), nothing special. Is that the normal way that it works?

EDIT: Added Models, Views, Forms

class Person(models.Model):
    name = models.CharField()  
    birthdate = models.CharField()  
    nationality = models.ManyToMany(Nationality)


class Employee(Person):
    employeeNum = models.CharField()


class FamilyMember(Person):  
    employee = models.ForeignKey(Employee)
    relationship = models.CharField()


class Nationality(models.Model):
    abbrev = models.CharField()
    country = models.CharField()


class FamilyMemberDetailsForm(forms.ModelForm):
    class Meta:
        model = FamilyMemeber
        exclude = ['employee']


def editFamilyMember(request, familyMember_id):
    familyMember = get_object_404(FamilMember, familyMember_id)
    if request.method == 'POST':
        form = FamilyMemberDetailsForm(request.POST, instance=familyMember)
        if form.is_valid():  
          form.save()
    else:
        form = FamilyMemberDetailsForm(instance=familyMember) 


    return render_to_response(editForm.html, {'form':form},  
                              context_instance(RequestContext(request))

This is a cut down version of the models, but the same thing happens for saving an employee or familyMember. The FamilyMember I have shown because it is as simple as this I create the modelForm and then make changes and then save it. For the employee I do some more manipulation in the init of Form for the Nationality, mainly for presentation, and at first I thought it was this manipulation that was causing it, but as I said the same thing happens with the FamilyMember where I do nothing except save.

The Nationality is presented on the form as a multiselect box with a list and the user can select 1 or more from the list. If I just present the populated form and then save it without changing anything the id for the many-to-many table entry changes.

I have changed the example table titles also.

Thanks,
Andrew

Upvotes: 0

Views: 3419

Answers (1)

istruble
istruble

Reputation: 13712

Yes, the deletion of any existing rows in appname_obj1_obj2 is expected behavior when saving a form for an object that has a ManyToManyField.

You can see the clear() before the add(**values) in ReverseManyRelatedObjectsDescriptor and ManyRelatedObjectsDescriptor in django/db/models/fields/related.py.

Pop open a shell and take a look at the queries yourself. Something like this should show you the DELETE before the INSERT in the raw sql.

from django.db import connection
fm = FamilyMember.objects.get(pk=1)
form = FamilyMemberDetailsForm(instance=fm)
data = form.initial
data['name'] = "z%s" % data['name']  
form = FamilyMemberDetailsForm(data, instance=fm)
connection.queries = []  # clearing to limit the queries you have to look at
form.save()
for q in connection.queries:
    print("%s\n" % q['sql'])

Upvotes: 2

Related Questions