11

I have the following models with a ManyToMany and through relationship:

class Meeting(models.Model):
    site = models.ForeignKey(Site)
    meeting_title = models.CharField(default='', max_length=128, blank=True, null=True)
    meeting_visitors = models.ManyToManyField(Visitor, through="MeetingArrival", blank=False, null=False) 

class Visitor(models.Model):
    visitor_company = models.ForeignKey(Company)
    visitor_name = models.CharField(default='', max_length=128, blank=False, null=False)

class MeetingArrival(models.Model):
    visitor = models.ForeignKey(Visitor)
    meeting = models.ForeignKey(Meeting)
    arrival_status = models.BooleanField(default=False)

I have a form to create a meeting:

class AddMeetingForm(forms.ModelForm):

    class Meta:
        model = Meeting
        exclude = ['site',]

And a simple view to save the form:

def add_meeting(request): 
    add_meeting_form = AddMeetingForm(request.POST or None)
    site = Site.objects.get(user=request.user.id)

    if request.method == "POST":
        if add_meeting_form.is_valid():

            obj = add_meeting_form.save(commit=False)
            obj.site = site
            obj.save()

This saves the form, but not the meeting_visitors field, even though this field renders perfectly in the view. How do I save this relationship?

EDIT

If I add add_meeting_form.save_m2m() to the view, I get Cannot set values on a ManyToManyField which specifies an intermediary model. Use meetings.MeetingArrival's Manager instead.. How would I do this?

alias51
  • 8,178
  • 22
  • 94
  • 166

6 Answers6

18

You will have to explicitly save the MeetingArrival object in your view to save an intermediate model in case of a ManyToManyField with through argument.

For Django versions 2.1 and below, in case of ManyToManyField with an intermediary model, you can’t use add, create, or assignment which are available with normal many-to-many fields.

As per the Django 1.8 docs:

Unlike normal many-to-many fields, you can’t use add, create, or assignment to create relationships.

The only way to create this type of relationship is to create instances of the intermediate model.

So, you will have to explicitly create a MeetingArrival object in your view.

You can do it by:

def add_meeting(request): 
    add_meeting_form = AddMeetingForm(request.POST or None)
    site = Site.objects.get(user=request.user.id)

    if request.method == "POST":
        if add_meeting_form.is_valid():
            obj = add_meeting_form.save(commit=False)
            obj.site = site
            obj.save()

            # create an instance of 'MeetingArrival' object
            meeting_arrival_obj = MeetingArrival(meeting=obj, visitor=<your_visitor_object_here>, arrival_status=True)
            meeting_arrival_obj.save() # save the object in the db
Udi
  • 29,222
  • 9
  • 96
  • 129
Rahul Gupta
  • 46,769
  • 10
  • 112
  • 126
  • Thanks, how do I get `your_visitor_object_here`? The form input is a multiple selection. – alias51 Aug 06 '15 at 18:21
  • We can write the logic to get the visitor object in the view. Since, `Visitor` is a foreign key, you can find which `MeetingArrival` is related to which `Visitor` and then use that `Visitor` to create a `MeetingArrival` object. – Rahul Gupta Aug 06 '15 at 18:34
  • Thanks. If I have another `ManyToMany` field that is not a `through` field, how would I save the form then? e.g. ` meeting_team_members = models.ManyToManyField(Team, blank=False, null=False)` – alias51 Aug 06 '15 at 19:04
  • You can do that simply by `.add()`. Lets say `ABC` is the model of `meeting_team_members`. Then you can do `abc_object.meeting_team_members.add(team_object)` – Rahul Gupta Aug 06 '15 at 19:11
  • See https://stackoverflow.com/a/58985327/117268 for a simpler way to do this in > Django 2.0. – Emil Stenström Dec 27 '22 at 16:06
7

for django 1.x, it is as what Rahul said that you can't use add, create, etc

for django 2.x, you actually can per the document here django 2.x

You can also use add(), create(), or set() to create relationships, as long as you specify through_defaults for any required fields:

beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})
Community
  • 1
  • 1
miaoz2001
  • 221
  • 4
  • 12
3

When you use a through table, you need to save there manually.

MeetingArrival.objects.create( ... )
Gocht
  • 9,924
  • 3
  • 42
  • 81
1

Don't put the logic where you handle the ManyToManyField in your view, put it in your form instead, so you won't have to repeat yourself if you use the form in multiple places.

To this end, you need to override the save method of the ModelForm.

See my more thorough answer to another question: https://stackoverflow.com/a/40822731/2863603

Community
  • 1
  • 1
mehmet
  • 7,720
  • 5
  • 42
  • 48
0

I did it this way, still trying to get multiple id values from the select, I think there is something wrong in my javascript

but here is python code:

class ArticuloCreateView(View):
    def __init__(self):
        self.template_name = 'articulo/formulario.html'

    def get(self, request):
        formulario = ArticuloForm()
        contexto = {
            'form': formulario,
            'operation': "Nuevo"
        }
        return render(request, self.template_name, contexto)

    @transaction.atomic
    def post(self, request):
        punto_transaccion = transaction.savepoint()
        formulario = ArticuloForm(request.POST)
        almacenes = request.POST.get('almacenes', 0)
        almacenes = Almacen.objects.filter(id=almacenes)

        if formulario.is_valid():
            datos_formulario = formulario.cleaned_data
            articulo = Articulo()
            articulo.clave = datos_formulario.get('clave')
            articulo.descripcion = datos_formulario.get('descripcion')
            articulo.tipo = datos_formulario.get('tipo')
            articulo.udm = datos_formulario.get('udm')
            articulo.clave_jde = datos_formulario.get('clave_jde')
            articulo.save()

            for almacen in almacenes:
                Stock.objects.create(articulo=articulo, almacen=almacen)
            if punto_transaccion:
                transaction.savepoint_commit(punto_transaccion)

            return redirect(
                reverse('inventarios.articulos_lista')
            )
        contexto = {
            'form': formulario,
        }

        return render(request, self.template_name, contexto)
Angie Alejo
  • 743
  • 9
  • 15
0

user_profiles = UserProfile.objects.all()

NotificationUser.objects.bulk_create([NotificationUser(user=user_profile, notification=notification) for user_profile in user_profiles])

Otobong Jerome
  • 401
  • 6
  • 5