# Posting a form with many to many data items

**URL:** <https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490>\
**Category:** Using the ORM\
**Created:** [June 19, 2025, 12:57pm UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490 "2025-06-19T12:57:27Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 19, 2025, 12:57pm UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/1 "2025-06-19T12:57:27Z")

</div>

Hi, I’m pretty new to Django so I’m probably being dumb but I’ve been looking at this for so long now I’ve gone snow-blind.

So, a small part of the app I’m trying to create handles the movement of backup tapes (a model called Tape) from one location to another. I have a Movement model with a many2many relationship with tapes, so I can move multiple tapes at once. I’ve created my views and they work as expected, so when I create a new movement and check a bunch of tapes, a movement record gets created and also an entry in the associated m2m table, so the data in the underlying db is good. However, as part of the save operation, I want to update the location field on each tape that was included in the movement and I just can’t see how to do that.

I tried writing my own save() method on the Movement object like so:

```auto
class Movement(models.Model):
    movement_date = models.DateField()
    tapes = models.ManyToManyField(Tape)
    location = models.ForeignKey(StorageLocation, on_delete=models.RESTRICT)
    comment = models.TextField(default='')

...stuff

    def save(self, **kwargs):
        super().save(**kwargs)

        # Update the location on all moved tapes...
        print(f"INSERTED ID: {self.id}")
        print(f"TAPES: {self.tapes}")

...stuff

class Tape(models.Model):
    label = models.CharField(max_length=30)
    media_type = models.ForeignKey(MediaType, on_delete=models.RESTRICT)
    location = models.ForeignKey(StorageLocation, on_delete=models.RESTRICT, default=-1)
    date_moved = models.DateTimeField(auto_now_add=True, null=True)

```

As you can see, all I’m trying to do at the moment is access the data for the tapes that were moved, but what gets printed on the console is:

```auto
[19/Jun/2025 13:48:56] "GET /movement/add/ HTTP/1.1" 200 4967
INSERTED ID: 31
TAPES: Doo.Tape.None
[19/Jun/2025 13:49:09] "POST /movement/add/ HTTP/1.1" 302 0

```

The id of the new movement record is printed correctly but I’ve tried many variations of `self.tapes` to get at the related tapes, to no avail.

What am I doing wrong?

Thanks very much!

---

<div class="post-metadata">

**Author:** ![white-seolpyo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/white-seolpyo/32/22386_2.png) [@white-seolpyo](https://forum.djangoproject.com/u/white-seolpyo)\
**Post date:** [June 20, 2025, 12:30am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/2 "2025-06-20T00:30:59Z")

</div>

As far as I remember, many-to-many (m2m) relationships are established after saving the object. Like this:

```auto
object.save()
{object.m2m.update process}

```

What you’re currently doing is checking before finishing `object.save()` , so the relationships likely haven’t been established yet."

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [June 20, 2025, 3:27am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/3 "2025-06-20T03:27:54Z")

</div>

Welcome @znac049 !

The ManyToManyField in a model is not a direct reference to the related objects - it’s actually a related object manager. (See [https://docs.djangoproject.com/en/5.2/ref/models/relations/#related-objects-reference](https://docs.djangoproject.com/en/5.2/ref/models/relations/#related-objects-reference) for more details.)

This means you do _not_ access the `Tapes` in the model as `self.tapes`. You access them as a queryset via an expression like `self.tapes.all()`.

---

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 20, 2025, 9:49am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/4 "2025-06-20T09:49:13Z")

</div>

Hi Ken,  
Thanks for that. So updating my code as follows:

```auto
    def save(self, **kwargs):
        super().save(**kwargs)

        # Update the location on all moved tapes...
        print(f"INSERTED ID: {self.id}")
        print(f"TAPES: {self.tapes.all()}")

```

produces:

```auto
[20/Jun/2025 10:42:55] "GET /movement/add/ HTTP/1.1" 200 4967
INSERTED ID: 33
TAPES: <QuerySet []>

```

I was hoping the query set would not be empty. After more googling I’ve seen a few people say the same as @white-seolpyo above, that m2m data is saved some time after the save operation, so I guess what I now need to figure out is how do I leverage that so that I can do my extra work after the m2m data has been saved? What do I need to override or what signal do I need to listen for?

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [June 20, 2025, 10:56am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/5 "2025-06-20T10:56:54Z")

</div>

Please post the forms and views associated with this process. It’s a lot easier to diagnose real code than to try and address this abstractly.

Also see the docs at [Creating forms from models | Django documentation | Django](https://docs.djangoproject.com/en/5.2/topics/forms/modelforms/#the-save-method) for more information.

---

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 20, 2025, 11:10am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/6 "2025-06-20T11:10:05Z")

</div>

Okay, here’s the view associated with creating a tape movement:

```auto
class CreateMovementView(CreateView):
    model = Movement
    fields = ['movement_date', 'tapes', 'location', 'comment']

     def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["side_menu"] = TapeViewHelper().get_side_menu()
        context["action"] = "New movement"
        return context

```

the associated form:

```auto
{% extends "site_page.html" %}

{% block view_content %}
<h1>{{ action }}</h1>
<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Save">
</form>
{% endblock %}

```

Most of what I’ve done so far comes from the official Django docs, no bells and whistles, so I’m hoping my basic approach is right.

---

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 20, 2025, 11:22am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/7 "2025-06-20T11:22:59Z")

</div>

## I have something that works!

I’m not saying it’s the best solution, indeed, I’m sure it’s not, but I found that by listening for a couple of signals,“_post\_save_” and “_m2m\_changed_” and sharing some information between them, I can get the job done. Here’s what I have:

```auto
# Is this really how you do static variables in Python?
this = sys.modules[__name__]
this.movement_date = None
this.location = None

@receiver(post_save, sender=Movement) 
def tape_movement_saved(sender, instance, created, **kwargs):
    if created:
        # Save when and where so it can be used by the m2m_changed 
        # signal handler
        this.location = instance.location
        this.movement_date = instance.movement_date

@receiver(m2m_changed)
def movement_m2m_changed(sender, instance, action, model, pk_set, **kwargs):
    instance_name = instance. __class__. __name__

    if action == 'post_add' and instance_name == 'Movement':
        for tape_id in pk_set:
            tape = Tape.objects.get(id=tape_id)
            tape.date_moved = this.movement_date
            tape.location = this.location
            tape.save()

```

Is this “the right” approach? Can anyone suggest a better way? For example, there must be a better way to share data between the two signal handlers.

Thanks again!

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [June 20, 2025, 11:37am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/8 "2025-06-20T11:37:10Z")

</div>

> [@znac049](#):
>
> Is this “the right” approach?

`<opinion>`  
No. I’d forget about using signals here. I see no reason, nor would find any benefit to using a signal in this process.  
`</opinion>`

I would override the `form_valid` method of the view and do this processing within that method.

If you want these functions to exist externally from the view, you can call them directly from the `form_valid` function in the view.

In the general case, I consider the use of signals within Django to be an anti-pattern. Yes, sometimes they are necessary, and when necessary, you do need to use them. This is not one of those times.

---

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 20, 2025, 12:11pm UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/9 "2025-06-20T12:11:34Z")

</div>

Thanks, Ken, that’s good to know. I’ll have another go using the approach you suggest and see how I get on.

---

<div class="post-metadata">

**Author:** ![znac049](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/znac049/32/28957_2.png) [@znac049](https://forum.djangoproject.com/u/znac049)\
**Post date:** [June 20, 2025, 12:41pm UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/10 "2025-06-20T12:41:52Z")

</div>

So I read the manual on forms **properly** this time. I’d discounted using the form\_valid function as I thought it was called to check if form data is valid as a precursor to saving the data. It turns out, and it’s right there in the docs, that it also takes care of actually saving the data, so your suggestion, @KenWhitesell, was spot-on. All the old code has been thrown away and replaced by this one extra function on the view:

```auto
    def form_valid(self, form):
        res = super().form_valid(form)

        tapes = form.cleaned_data['tapes']
        for tape in tapes:
            tape.date_moved = form.cleaned_data['movement_date']
            tape.location = form.cleaned_data['location']
            tape.save()

        return res

```

Happy days!

---

<div class="post-metadata">

**Author:** ![white-seolpyo](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/white-seolpyo/32/22386_2.png) [@white-seolpyo](https://forum.djangoproject.com/u/white-seolpyo)\
**Post date:** [June 21, 2025, 12:10am UTC](https://forum.djangoproject.com/t/posting-a-form-with-many-to-many-data-items/41490/11 "2025-06-21T00:10:29Z")

</div>

If you’re using a ModelForm, wouldn’t the many-to-many relationship be applied after saving the form?

```auto
obj = form.save()
obj.m2m.all

```
