Django m2m form save “through” table

Tengo problemas para guardar una información m2m, que contiene una tabla de clase “directa”. Quiero guardar todos los miembros seleccionados (seleccionados en el formulario) en la tabla directa. Pero no sé cómo inicializar la tabla “directa” en la vista.

mi código:

class Classroom(models.Model): user = models.ForeignKey(User, related_name = 'classroom_creator') classname = models.CharField(max_length=140, unique = True) date = models.DateTimeField(auto_now=True) open_class = models.BooleanField(default=True) members = models.ManyToManyField(User,related_name="list of invited members", through = 'Membership') class Membership(models.Model): accept = models.BooleanField(User) date = models.DateTimeField(auto_now = True) classroom = models.ForeignKey(Classroom, related_name = 'classroom_membership') member = models.ForeignKey(User, related_name = 'user_membership') 

y en la vista:

 def save_classroom(request): classroom_instance = Classroom() if request.method == 'POST': form = ClassroomForm(request.POST, request.FILES, user = request.user) if form.is_valid(): new_obj = form.save(commit=False) new_obj.user = request.user new_obj.save() membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj) membership.save() 

¿Cómo debo inicializar la membresía para que se llene la tabla de Membresía, verdad?

En caso de utilizar una relación m2m normal (no a través de una tabla intermedia) puede reemplazar:

 membership = Membership(member = HERE SELECTED ITEMS FROM FORM,classroom=new_obj) membership.save() 

con

 form.save_m2m() 

Pero en el caso de utilizar tablas intermedias, debe manejar manualmente datos POST y crear objetos de Membresía con todos los campos requeridos ( problema similar ). La solución más básica es cambiar su vista a algo como:

 def save_classroom(request): if request.method == 'POST': form = ClassroomForm(request.POST, request.FILES) if form.is_valid(): new_obj = form.save(commit=False) new_obj.user = request.user new_obj.save() for member_id in request.POST.getlist('members'): membership = Membership.objects.create(member_id = int(member_id), classroom = new_obj) return HttpResponseRedirect('/') else: form = ClassroomForm() return render_to_response('save_classroom.html', locals()) 

Observe cómo se manipula request.POST (.getlist). Esto se debe a que publicar y obtener son objetos QueryDict que tiene algunas implicaciones (request.POST [‘members’] devolverá siempre un objeto).

Puede modificar este código para hacerlo más confiable (manejo de errores, etc.) y más detallado, por ejemplo:

 member = get_object_or_404(User, pk = member_id) membership = Membership.objects.create(member = member , classroom = new_obj) 

Pero tenga en cuenta que está realizando algunas consultas db en un bucle, lo que no es una buena idea en general (en términos de rendimiento).

Como lo hizo dzida, pero use form.cleaned_data en lugar de request.post:

 def save_classroom(request): if request.method == 'POST': form = ClassroomForm(request.POST, request.FILES) if form.is_valid(): new_obj = form.save(commit=False) new_obj.user = request.user new_obj.save() for member in form.cleaned_data['members'].all(): Membership.objects.create(member = member, classroom = new_obj) return HttpResponseRedirect('/') else: form = ClassroomForm() return render_to_response('save_classroom.html', locals()) 

También debe considerar la posibilidad de eliminar algunas membresías, por lo que:

 def save_classroom(request): if request.method == 'POST': form = ClassroomForm(request.POST, request.FILES) if form.is_valid(): new_obj = form.save(commit=False) new_obj.user = request.user new_obj.save() final_members = form.cleaned_data['members'].all() initial_members = form.initial['members'].all() # create and save new members for member in final_members: if member not in initial_members: Membership.objects.create(member = member, classroom = new_obj) # delete old members that were removed from the form for member in initial_members: if member not in final_members: Membership.objects.filter(member = member, classroom = new_obj).delete() return HttpResponseRedirect('/') else: form = ClassroomForm() return render_to_response('save_classroom.html', locals()) 

Si usa formularios de modelo (como en un CBV genérico: form_class=ClassroomForm ), anule y coloque la lógica de guardado arriba en el método de save , algo como:

 ClassroomForm(forms.ModelForm): members = ModelMultipleChoiceField( queryset=Classroom.objects.all(), widget=SelectMultiple ) def save(self, commit=True): classroom = super().save(commit=False) if commit: classroom.save() if 'members' in self.changed_data: final_members = form.cleaned_data['members'].all() initial_members = form.initial['members'].all() # create and save new members for member in final_members: if member not in initial_members: Membership.objects.create(member = member, classroom = new_obj) # delete old members that were removed from the form for member in initial_members: if member not in final_members: Membership.objects.filter(member = member, classroom = new_obj).delete() return classroom 

También necesita especificar el aula para la membresía:

 membership = Membership(member = request.user, classroom=new_obj) #if new_obj if your classroom membership.save() 

Supongo que también debería eliminar User en accept = models.BooleanField(User) . ¡No debería ser necesario establecer la fecha de guardado si está usando auto_now ! Pero tal vez `auto_now_add sea más probable que lo que necesita ( http://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.DateField )

Así es como lo hice en una vista genérica de clase basada en UpdateForm (django 1.8) para una aplicación similar pero diferente usando el método form_valid.

 def form_valid(self, form): """ If the form is valid, save the associated model. """ self.object.members.clear() self.object = form.save(commit=False) self.object.user = self.request.user self.object.save() list_of_members = form.cleaned_data['members'] ClassRoom.objects.bulk_create([ Membership( Course=self.object, member=member_person, order=num) for num, member_person in enumerate(list_of_members) ]) return super(ModelFormMixin, self).form_valid(form)